evolcore 0.0.31 → 0.0.32
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 +11 -0
- package/dist/agents/codex-runner.js +28 -5
- package/dist/agents/ecagent-runner.js +4 -0
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +10 -135
- package/dist/cli/agent-command.js +6 -7
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +15 -69
- package/dist/cli/aun-commands.js +149 -5
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +1 -1
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +15 -5
- package/dist/cli/memory-command.js +85 -11
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +46 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/skill-command.js +9 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +15 -0
- package/dist/cli/trigger-command.js +73 -49
- package/dist/config/builtin-roles.js +8 -7
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +17 -1
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-catalog.js +11 -3
- package/dist/core/command/command-handler.js +164 -21
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/ipc-registration.js +71 -7
- package/dist/core/message/aun-ipc-sender.js +48 -4
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/response-engine.js +30 -2
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/index.js +2 -2
- package/dist/ipc.js +146 -8
- package/dist/trigger/authorization.js +2 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/script-sandbox.js +58 -77
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/06-channel.md +7 -1
- package/kits/templates/system-fragments/commands.md +1 -1
- package/package.json +1 -1
package/dist/cli/task-context.js
CHANGED
|
@@ -27,6 +27,7 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
27
27
|
return {
|
|
28
28
|
taskId: optionalString(value.taskId),
|
|
29
29
|
sessionId: optionalString(value.sessionId),
|
|
30
|
+
projectPath: optionalAbsolutePath(value.projectPath),
|
|
30
31
|
messageId: optionalString(value.messageId),
|
|
31
32
|
baseagent: optionalString(value.baseagent),
|
|
32
33
|
channel: optionalString(value.channel),
|
|
@@ -347,6 +348,20 @@ export function parseTaskRuntimeContext(raw) {
|
|
|
347
348
|
export function readTaskRuntimeContextFromEnv(env = process.env) {
|
|
348
349
|
return parseTaskRuntimeContext(env[TASK_RUNTIME_CONTEXT_ENV]);
|
|
349
350
|
}
|
|
351
|
+
export async function authorizeLocalCommand(argv) {
|
|
352
|
+
const sessionId = process.env.EVOLCORE_SESSION_ID;
|
|
353
|
+
const delegationToken = readDelegationCredential();
|
|
354
|
+
if (!sessionId || !delegationToken)
|
|
355
|
+
throw new Error('Local command requires an active task delegation');
|
|
356
|
+
const [{ resolvePaths }, { ipcQuery }] = await Promise.all([import('../paths.js'), import('../ipc.js')]);
|
|
357
|
+
const result = await ipcQuery(resolvePaths().socket, {
|
|
358
|
+
type: 'local-command-context', argv, sessionId, delegationToken,
|
|
359
|
+
}, 10_000);
|
|
360
|
+
if (!result?.ok || !result.context?.selfAid || !result.context.peerRole) {
|
|
361
|
+
throw new Error(result?.error ?? 'Daemon command authorization is unavailable');
|
|
362
|
+
}
|
|
363
|
+
return result.context;
|
|
364
|
+
}
|
|
350
365
|
export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 500, env = process.env) {
|
|
351
366
|
if (!sessionId)
|
|
352
367
|
return undefined;
|
|
@@ -7,10 +7,11 @@ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
|
|
|
7
7
|
import { readTaskRuntimeContextFromEnv } from './task-context.js';
|
|
8
8
|
import { assertEcSingleLine } from './cli-argv.js';
|
|
9
9
|
import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
|
|
10
|
+
import { MAIN_CLI_BIN } from '../product.js';
|
|
10
11
|
import { readTriggerLocalFile, resolveTriggerLocalPath } from '../trigger/local-files.js';
|
|
11
12
|
import { normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from '../trigger/validation.js';
|
|
12
13
|
export async function cmdTrigger(args) {
|
|
13
|
-
args = canonicalizeEvolcoreCommandArgv([
|
|
14
|
+
args = canonicalizeEvolcoreCommandArgv([MAIN_CLI_BIN, 'trigger', ...args]).slice(2);
|
|
14
15
|
const sub = args[0];
|
|
15
16
|
if (sub === 'create' || sub === 'set' || sub === 'update') {
|
|
16
17
|
const nameIndex = args.indexOf('--name');
|
|
@@ -41,29 +42,27 @@ export async function cmdTrigger(args) {
|
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new Error('daemon did not provide a valid Trigger file scope');
|
|
64
|
-
}
|
|
65
|
-
fileScope = authorization.fileScope;
|
|
45
|
+
if (process.env.EVOLCORE_SESSION_ID) {
|
|
46
|
+
const response = await request({ type: 'trigger.op', argv: ['trigger', ...args] }, sub === 'run' ? 120_000 : 30_000);
|
|
47
|
+
if (json)
|
|
48
|
+
return printJson(response);
|
|
49
|
+
if (sub === 'list')
|
|
50
|
+
return listTriggers(rest, false, response);
|
|
51
|
+
if (sub === 'show')
|
|
52
|
+
return showTrigger(rest, false, response);
|
|
53
|
+
if (sub === 'history')
|
|
54
|
+
return showHistory(rest, false, response);
|
|
55
|
+
if (sub === 'run') {
|
|
56
|
+
console.log(`${response.result.status}${response.result.reason ? ` (${response.result.reason})` : ''}: ${response.result.runId}`);
|
|
57
|
+
if (response.result.error)
|
|
58
|
+
console.log(response.result.error);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const verb = { create: 'created', set: 'created', update: 'updated', enable: 'enabled',
|
|
62
|
+
disable: 'disabled', cancel: 'disabled', delete: 'deleted', remove: 'deleted', rm: 'deleted' }[sub];
|
|
63
|
+
console.log(`✓ ${verb} ${response.trigger.id}${['create', 'set', 'update'].includes(sub) ? ` (${response.trigger.name})` : ''}`);
|
|
66
64
|
}
|
|
65
|
+
return;
|
|
67
66
|
}
|
|
68
67
|
switch (sub) {
|
|
69
68
|
case 'list':
|
|
@@ -77,10 +76,10 @@ export async function cmdTrigger(args) {
|
|
|
77
76
|
return;
|
|
78
77
|
case 'create':
|
|
79
78
|
case 'set':
|
|
80
|
-
await createTrigger(rest, json
|
|
79
|
+
await createTrigger(rest, json);
|
|
81
80
|
return;
|
|
82
81
|
case 'update':
|
|
83
|
-
await updateTrigger(rest, json
|
|
82
|
+
await updateTrigger(rest, json);
|
|
84
83
|
return;
|
|
85
84
|
case 'enable':
|
|
86
85
|
case 'disable':
|
|
@@ -175,9 +174,9 @@ Update 参数:
|
|
|
175
174
|
省略 --agent 时使用当前 agent AID;无会话上下文或跨身份操作时需要显式指定。
|
|
176
175
|
任务会话中的跨身份操作仅允许目标 Agent 上具备 owner/admin 权限的调用者执行 list/show/history;创建、修改、启停、删除和运行仍只允许当前 Agent。`);
|
|
177
176
|
}
|
|
178
|
-
async function listTriggers(args, json) {
|
|
177
|
+
async function listTriggers(args, json, response) {
|
|
179
178
|
const agentAid = resolveAgentAid(args);
|
|
180
|
-
const res = await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all') });
|
|
179
|
+
const res = response ?? await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all') });
|
|
181
180
|
if (json)
|
|
182
181
|
return printJson(res);
|
|
183
182
|
const triggers = res.triggers;
|
|
@@ -189,10 +188,10 @@ async function listTriggers(args, json) {
|
|
|
189
188
|
console.log(`${t.enabled ? 'on ' : 'off'} ${t.id} ${t.name} ${sourceLabel(t)}`);
|
|
190
189
|
}
|
|
191
190
|
}
|
|
192
|
-
async function showTrigger(args, json) {
|
|
191
|
+
async function showTrigger(args, json, response) {
|
|
193
192
|
const agentAid = resolveAgentAid(args);
|
|
194
193
|
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
195
|
-
const res = await request({ type: 'trigger.show', agentAid, triggerId });
|
|
194
|
+
const res = response ?? await request({ type: 'trigger.show', agentAid, triggerId });
|
|
196
195
|
if (json)
|
|
197
196
|
return printJson(res);
|
|
198
197
|
const t = res.definition;
|
|
@@ -239,7 +238,7 @@ async function showTrigger(args, json) {
|
|
|
239
238
|
}
|
|
240
239
|
}
|
|
241
240
|
}
|
|
242
|
-
async function showHistory(args, json) {
|
|
241
|
+
async function showHistory(args, json, response) {
|
|
243
242
|
const agentAid = resolveAgentAid(args);
|
|
244
243
|
const triggerId = optionalPositional(args, 0, ['--agent', '--limit', '--format']);
|
|
245
244
|
const rawLimit = flagValue(args, '--limit');
|
|
@@ -247,7 +246,7 @@ async function showHistory(args, json) {
|
|
|
247
246
|
if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000) {
|
|
248
247
|
throw new Error('--limit 必须是 1 到 10000 的整数');
|
|
249
248
|
}
|
|
250
|
-
const res = await request({ type: 'trigger.history', agentAid, triggerId, limit });
|
|
249
|
+
const res = response ?? await request({ type: 'trigger.history', agentAid, triggerId, limit });
|
|
251
250
|
if (json)
|
|
252
251
|
return printJson(res);
|
|
253
252
|
const events = Array.isArray(res.events) ? res.events : [];
|
|
@@ -266,6 +265,12 @@ async function showHistory(args, json) {
|
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
async function createTrigger(args, json, fileScope) {
|
|
268
|
+
const res = await request(await buildTriggerCreateRequest(args, fileScope), 30_000);
|
|
269
|
+
if (json)
|
|
270
|
+
return printJson(res);
|
|
271
|
+
console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
|
|
272
|
+
}
|
|
273
|
+
export async function buildTriggerCreateRequest(args, fileScope, defaultAgentAid) {
|
|
269
274
|
if (!hasOption(args, '--exec')) {
|
|
270
275
|
throw new Error('创建 trigger 必须显式指定 --exec script|trigger-session|target-session');
|
|
271
276
|
}
|
|
@@ -280,16 +285,12 @@ async function createTrigger(args, json, fileScope) {
|
|
|
280
285
|
if (definition.execution.type !== executionType) {
|
|
281
286
|
throw new Error(`--exec ${flagValue(args, '--exec')} 与 trigger.json 的 execution.type=${definition.execution.type} 不一致`);
|
|
282
287
|
}
|
|
283
|
-
|
|
288
|
+
return {
|
|
284
289
|
type: 'trigger.create',
|
|
285
290
|
definition,
|
|
286
291
|
files,
|
|
287
292
|
enable: hasFlag(args, '--enable') ? true : undefined,
|
|
288
|
-
}
|
|
289
|
-
if (json)
|
|
290
|
-
return printJson(res);
|
|
291
|
-
console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
|
|
292
|
-
return;
|
|
293
|
+
};
|
|
293
294
|
}
|
|
294
295
|
// Flag mode: use parser
|
|
295
296
|
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
@@ -302,7 +303,7 @@ async function createTrigger(args, json, fileScope) {
|
|
|
302
303
|
throw new Error(parseResult.error);
|
|
303
304
|
}
|
|
304
305
|
const parsed = parseResult.value;
|
|
305
|
-
const agentAid = parsed.agentId ?? resolveAgentAid(args);
|
|
306
|
+
const agentAid = parsed.agentId ?? defaultAgentAid ?? resolveAgentAid(args);
|
|
306
307
|
const eventSession = parsed.feedbackStrategy === 'event_session';
|
|
307
308
|
if (parsed.feedbackStrategy !== 'target' && !eventSession) {
|
|
308
309
|
throw new Error('CLI flag 模式仅支持 --feedback target;origin/silent 请使用 /trigger 或 --file 导入 Trigger V3 第二阶段 JSON');
|
|
@@ -364,15 +365,12 @@ async function createTrigger(args, json, fileScope) {
|
|
|
364
365
|
relativePath: script.path.replace(/\\/g, '/'),
|
|
365
366
|
contentBase64: readTriggerLocalFile(parsed.scriptPath, fileScope).toString('base64'),
|
|
366
367
|
}] : [];
|
|
367
|
-
|
|
368
|
+
return {
|
|
368
369
|
type: 'trigger.create',
|
|
369
370
|
definition,
|
|
370
371
|
files,
|
|
371
372
|
enable: hasFlag(args, '--enable') ? true : undefined,
|
|
372
|
-
}
|
|
373
|
-
if (json)
|
|
374
|
-
return printJson(res);
|
|
375
|
-
console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
|
|
373
|
+
};
|
|
376
374
|
}
|
|
377
375
|
function triggerCreateArgs(args) {
|
|
378
376
|
const out = [];
|
|
@@ -453,7 +451,13 @@ function shellQuoteArg(value) {
|
|
|
453
451
|
return /[\s"'\\]/.test(value) ? `"${value.replace(/(["\\$`])/g, '\\$1')}"` : value;
|
|
454
452
|
}
|
|
455
453
|
async function updateTrigger(args, json, fileScope) {
|
|
456
|
-
const
|
|
454
|
+
const res = await request(await buildTriggerUpdateRequest(args, fileScope), 30_000);
|
|
455
|
+
if (json)
|
|
456
|
+
return printJson(res);
|
|
457
|
+
console.log(`✓ updated ${res.trigger.id} (${res.trigger.name})`);
|
|
458
|
+
}
|
|
459
|
+
export async function buildTriggerUpdateRequest(args, fileScope, defaultAgentAid) {
|
|
460
|
+
const agentAid = flagValue(args, '--agent') ?? defaultAgentAid ?? resolveAgentAid(args);
|
|
457
461
|
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file']);
|
|
458
462
|
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
459
463
|
const usesScriptFile = hasOption(args, '--script-file');
|
|
@@ -469,7 +473,7 @@ async function updateTrigger(args, json, fileScope) {
|
|
|
469
473
|
const scriptFileBase64 = scriptFilePath
|
|
470
474
|
? readTriggerLocalFile(scriptFilePath, fileScope).toString('base64')
|
|
471
475
|
: undefined;
|
|
472
|
-
|
|
476
|
+
return {
|
|
473
477
|
type: 'trigger.update',
|
|
474
478
|
agentAid,
|
|
475
479
|
triggerId,
|
|
@@ -477,10 +481,30 @@ async function updateTrigger(args, json, fileScope) {
|
|
|
477
481
|
...(usesPromptFile ? { promptFile: true } : {}),
|
|
478
482
|
...(scriptFileBase64 !== undefined ? { scriptFileBase64 } : {}),
|
|
479
483
|
...(expectedRevision ? { expectedRevision } : {}),
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
export function resolveTriggerCommandArgv(argv, defaultAgentAid) {
|
|
487
|
+
if (argv[0] !== 'trigger')
|
|
488
|
+
throw new Error('Expected a trigger command');
|
|
489
|
+
const sub = argv[1];
|
|
490
|
+
const args = argv.slice(2);
|
|
491
|
+
const types = {
|
|
492
|
+
list: 'trigger.list', show: 'trigger.show', history: 'trigger.history', create: 'trigger.create',
|
|
493
|
+
set: 'trigger.create', update: 'trigger.update', enable: 'trigger.setEnabled', disable: 'trigger.setEnabled',
|
|
494
|
+
cancel: 'trigger.cancel', delete: 'trigger.delete', remove: 'trigger.delete', rm: 'trigger.delete', run: 'trigger.run',
|
|
495
|
+
};
|
|
496
|
+
if (!types[sub])
|
|
497
|
+
throw new Error(`Unknown trigger command: ${sub}`);
|
|
498
|
+
const agentAid = flagValue(args, '--agent') ?? defaultAgentAid;
|
|
499
|
+
const valueFlags = ['--agent', '--format', '--limit', '--if-revision', '--prompt-file', '--script-file'];
|
|
500
|
+
const triggerId = ['list', 'create', 'set'].includes(sub) ? undefined : optionalPositional(args, 0, valueFlags);
|
|
501
|
+
if (!['list', 'history', 'create', 'set'].includes(sub) && !triggerId)
|
|
502
|
+
throw new Error('missing triggerId');
|
|
503
|
+
const limit = Number(flagValue(args, '--limit') ?? 100);
|
|
504
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000)
|
|
505
|
+
throw new Error('Invalid history limit');
|
|
506
|
+
return { type: types[sub], agentAid, triggerId, limit, all: hasFlag(args, '--all'),
|
|
507
|
+
enabled: sub === 'enable', dryRun: hasFlag(args, '--dry-run') };
|
|
484
508
|
}
|
|
485
509
|
function triggerUpdateArgs(args) {
|
|
486
510
|
const out = [];
|
|
@@ -42,6 +42,7 @@ export function getManagementCommandPermissions(role) {
|
|
|
42
42
|
'role.policy.read': { allow: true, scopes: ['agent'] },
|
|
43
43
|
'role.relation.read': { allow: true, scopes: ['agent'] },
|
|
44
44
|
'agent.baseagent.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
45
|
+
'agent.rename': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
45
46
|
'observable.current': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
|
|
46
47
|
'observable.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
47
48
|
'file.root.agent': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
@@ -64,13 +65,13 @@ export function getManagementCommandPermissions(role) {
|
|
|
64
65
|
'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
65
66
|
'ec.aid.agentmd.set': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
66
67
|
'ec.aid.avatar': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentOwner: true } },
|
|
67
|
-
'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
68
|
-
'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
69
|
-
'ec.fs.copy': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
70
|
-
'ec.fs.move': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
71
|
-
'ec.fs.remove': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
72
|
-
'ec.fs.getfacl': { allow: true, scopes: ['relation'
|
|
73
|
-
'ec.fs.setfacl': { allow: true, permissionMode: 'auto', scopes: ['relation'
|
|
68
|
+
'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
69
|
+
'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
70
|
+
'ec.fs.copy': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
71
|
+
'ec.fs.move': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
72
|
+
'ec.fs.remove': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
73
|
+
'ec.fs.getfacl': { allow: true, scopes: ['relation', 'agent'] },
|
|
74
|
+
'ec.fs.setfacl': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
|
|
74
75
|
'*': { allow: true },
|
|
75
76
|
// Reload is valid from an Agent's own channel and the daemon control plane.
|
|
76
77
|
// The control-channel gate still requires a daemon owner for cross-Agent reloads.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { loadDaemonConfig } from '../config-store.js';
|
|
4
|
-
import { parseAgentWelcomeArgs, parseInitializationArgs, resolveInitializationPlan, } from '../cli/agent-create.js';
|
|
4
|
+
import { parseAgentWelcomeArgs, parseInitializationArgs, resolveInitializationPlan, readInitializationPersona, } from '../cli/agent-create.js';
|
|
5
5
|
import { trustedExecutionIdentityInput } from './auth/agent-delegation.js';
|
|
6
6
|
import { authorizeOperation, buildAuthSubject } from './auth/auth-gateway.js';
|
|
7
7
|
import { authorizationDenialData } from './auth/authorization-denial.js';
|
|
8
8
|
import { parsePeerKey } from './relation/peer-identity.js';
|
|
9
9
|
import { isHClassPath, isLClassPath, isSameOrDescendant } from './permission/protected-paths.js';
|
|
10
|
-
|
|
10
|
+
import { isValidAid } from '../aun/aid/validation.js';
|
|
11
|
+
export function registerAgentOperationExecutor({ ipcServer, sessionManager, agentDelegationRegistry, eventBus, responseEngine, handoffRuntime, agentApplicationService, agentInitializationService, executeConfigOperation, }) {
|
|
11
12
|
ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
12
13
|
const fail = (code, error, reasonCode) => ({
|
|
13
14
|
ok: false,
|
|
@@ -17,9 +18,16 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
17
18
|
});
|
|
18
19
|
if (!Array.isArray(argv) || argv[0] !== 'agent')
|
|
19
20
|
return fail('INVALID_REQUEST', 'agent.op requires an agent command');
|
|
20
|
-
const
|
|
21
|
+
const shorthand = !!argv[1] && isValidAid(argv[1]);
|
|
22
|
+
const sub = shorthand ? 'show' : argv[1];
|
|
21
23
|
if (!sub)
|
|
22
24
|
return fail('INVALID_ARGS', 'missing agent subcommand');
|
|
25
|
+
if (sub === 'get' || sub === 'set') {
|
|
26
|
+
const result = await executeConfigOperation(argv, sessionId, delegationToken, delegationCommandHash);
|
|
27
|
+
return result.ok
|
|
28
|
+
? { ok: true, result: result.result }
|
|
29
|
+
: { ok: false, code: result.code ?? 'CONFIG_OPERATION_FAILED', error: result.error ?? 'config operation failed' };
|
|
30
|
+
}
|
|
23
31
|
const value = (flag) => {
|
|
24
32
|
const index = argv.indexOf(flag);
|
|
25
33
|
return index >= 0 && index + 1 < argv.length && !argv[index + 1].startsWith('--') ? argv[index + 1] : undefined;
|
|
@@ -31,6 +39,8 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
31
39
|
if (!taskSession || taskSession.selfAID !== delegation.grant.selfAid) {
|
|
32
40
|
return fail('INVALID_DELEGATION', 'delegation does not match the current session');
|
|
33
41
|
}
|
|
42
|
+
if (shorthand)
|
|
43
|
+
argv = ['agent', 'show', ...argv.slice(1)];
|
|
34
44
|
const taskAid = delegation.grant.selfAid;
|
|
35
45
|
let welcomeArgs;
|
|
36
46
|
if (sub === 'welcome') {
|
|
@@ -58,12 +68,13 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
58
68
|
: sub === 'show' ? 'agent.show'
|
|
59
69
|
: sub === 'persona' ? 'agent.persona'
|
|
60
70
|
: sub === 'welcome' ? 'agent.welcome'
|
|
61
|
-
: sub === '
|
|
62
|
-
: sub === 'new' ? 'agent.
|
|
63
|
-
: sub === '
|
|
64
|
-
: sub === '
|
|
65
|
-
: sub === '
|
|
66
|
-
: sub === '
|
|
71
|
+
: sub === 'rename' ? 'agent.rename'
|
|
72
|
+
: sub === 'initialize' || (sub === 'new' && argv.includes('--initialize')) ? 'agent.initialize'
|
|
73
|
+
: sub === 'new' ? 'agent.create'
|
|
74
|
+
: sub === 'enable' ? 'agent.enable'
|
|
75
|
+
: sub === 'disable' ? 'agent.disable'
|
|
76
|
+
: sub === 'reload' ? 'agent.reload'
|
|
77
|
+
: sub === 'delete' ? 'agent.delete' : undefined;
|
|
67
78
|
if (!operation)
|
|
68
79
|
return fail('INVALID_ARGS', `unsupported managed agent command: ${sub}`);
|
|
69
80
|
if (sub === 'new' && !argv.includes('--non-interactive') && !argv.includes('--initialize')) {
|
|
@@ -95,7 +106,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
95
106
|
subject,
|
|
96
107
|
intent: {
|
|
97
108
|
operation,
|
|
98
|
-
scope: operation === 'agent.show' || operation === 'agent.persona' || operation === 'agent.welcome'
|
|
109
|
+
scope: operation === 'agent.show' || operation === 'agent.persona' || operation === 'agent.welcome' || operation === 'agent.rename'
|
|
99
110
|
? 'agent'
|
|
100
111
|
: operation === 'agent.reload' && targetAid
|
|
101
112
|
? 'agent'
|
|
@@ -170,15 +181,23 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
170
181
|
if (!hasFormatSuffix && rawPersonaArgs.includes('--format')) {
|
|
171
182
|
return fail('INVALID_ARGS', 'persona 仅支持末尾的 --format json');
|
|
172
183
|
}
|
|
173
|
-
if (personaArgs.length === 2 && personaArgs[0] === '--file') {
|
|
174
|
-
return fail('INVALID_ARGS', '托管 persona --file 必须由 CLI 读取后作为文本提交');
|
|
175
|
-
}
|
|
176
184
|
const wantsShow = personaArgs.length === 1 && personaArgs[0] === 'show';
|
|
177
|
-
|
|
185
|
+
let text = personaArgs.length === 2 && personaArgs[0] === '--text'
|
|
178
186
|
? personaArgs[1]
|
|
179
187
|
: undefined;
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
const file = personaArgs.length === 2 && personaArgs[0] === '--file' ? personaArgs[1] : undefined;
|
|
189
|
+
if (file !== undefined) {
|
|
190
|
+
try {
|
|
191
|
+
text = readInitializationPersona(file, true, {
|
|
192
|
+
projectCwd: taskSession.projectPath, tempDir: delegation.grant.sessionRuntimeDir,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return fail('INVALID_ARGS', error.message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!wantsShow && !(personaArgs.length === 2 && (personaArgs[0] === '--text' || personaArgs[0] === '--file'))) {
|
|
200
|
+
return fail('INVALID_ARGS', '用法: ec agent persona show | --text <text> | --file <path>');
|
|
182
201
|
}
|
|
183
202
|
if (text === undefined && !wantsShow)
|
|
184
203
|
return fail('INVALID_ARGS', 'persona --text 缺少参数');
|
|
@@ -211,12 +230,32 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
211
230
|
...(result.sha256 ? { sha256: result.sha256 } : {}),
|
|
212
231
|
} };
|
|
213
232
|
}
|
|
233
|
+
if (operation === 'agent.rename') {
|
|
234
|
+
const rawRenameArgs = argv.slice(2);
|
|
235
|
+
const hasFormatSuffix = rawRenameArgs.length >= 2
|
|
236
|
+
&& rawRenameArgs.at(-2) === '--format'
|
|
237
|
+
&& rawRenameArgs.at(-1) === 'json';
|
|
238
|
+
const renameArgs = hasFormatSuffix ? rawRenameArgs.slice(0, -2) : rawRenameArgs;
|
|
239
|
+
if (!hasFormatSuffix && rawRenameArgs.includes('--format')) {
|
|
240
|
+
return fail('INVALID_ARGS', 'rename 仅支持末尾的 --format json');
|
|
241
|
+
}
|
|
242
|
+
if (renameArgs.length !== 2 || !renameArgs[0] || !renameArgs[1]) {
|
|
243
|
+
return fail('INVALID_ARGS', '用法: ec agent rename <aid> <name>');
|
|
244
|
+
}
|
|
245
|
+
if (/[\x00-\x1f\x7f]/u.test(renameArgs[1])) {
|
|
246
|
+
return fail('INVALID_ARGS', 'name 不允许换行或控制字符');
|
|
247
|
+
}
|
|
248
|
+
const result = await execAgentAction('update', { aid: targetAid ?? taskAid, patch: { name: renameArgs[1] } }, delegation.grant.actorId, eventBus, agentApplicationService, subject);
|
|
249
|
+
return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
|
|
250
|
+
}
|
|
214
251
|
if (operation === 'agent.initialize') {
|
|
215
252
|
try {
|
|
216
253
|
const parsed = parseInitializationArgs(argv.slice(1));
|
|
217
254
|
const actor = delegation.grant.actorId;
|
|
218
|
-
if (parsed.resume)
|
|
219
|
-
|
|
255
|
+
if (parsed.resume) {
|
|
256
|
+
agentInitializationService.resume(parsed.aid, actor);
|
|
257
|
+
return { ok: true, result: await agentInitializationService.wait(parsed.aid, parsed.wait) };
|
|
258
|
+
}
|
|
220
259
|
const createPermission = authorizeOperation({
|
|
221
260
|
source: 'agent-tool', subject,
|
|
222
261
|
intent: { operation: 'agent.create', scope: 'control', source: 'agent-tool', args: { aid: parsed.aid } },
|
|
@@ -226,14 +265,16 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
226
265
|
return fail(createPermission.code, createPermission.reason);
|
|
227
266
|
if (!argv.includes('--owner') && !argv.includes('--owners'))
|
|
228
267
|
return fail('INITIALIZE_OWNER_MISMATCH', 'Explicit Owner required');
|
|
229
|
-
const plan = resolveInitializationPlan(argv.slice(1)
|
|
268
|
+
const plan = resolveInitializationPlan(argv.slice(1), {
|
|
269
|
+
projectCwd: taskSession.projectPath, tempDir: delegation.grant.sessionRuntimeDir,
|
|
270
|
+
});
|
|
230
271
|
if (plan.avatarFile?.startsWith('attachment:')) {
|
|
231
272
|
const { readRelationAttachment } = await import('../utils/media-cache.js');
|
|
232
273
|
plan.avatarFile = readRelationAttachment(taskAid, delegation.grant.peerKey, plan.avatarFile.slice('attachment:'.length)).attachment.filePath;
|
|
233
274
|
}
|
|
234
275
|
else if (plan.avatarFile) {
|
|
235
276
|
const session = await sessionManager.getSessionById(sessionId);
|
|
236
|
-
const candidate = path.resolve(plan.avatarFile);
|
|
277
|
+
const candidate = path.resolve(taskSession.projectPath, plan.avatarFile);
|
|
237
278
|
const roots = [session?.projectPath, delegation.grant.sessionRuntimeDir]
|
|
238
279
|
.filter((root) => !!root && path.isAbsolute(root))
|
|
239
280
|
.map(root => fs.realpathSync(root));
|
|
@@ -245,7 +286,8 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
245
286
|
}
|
|
246
287
|
if (!plan.owners.includes(actor))
|
|
247
288
|
return fail('INITIALIZE_OWNER_MISMATCH', 'The authenticated requesting Owner must be retained');
|
|
248
|
-
|
|
289
|
+
const result = agentInitializationService.start(plan, actor, parsed.dryRun);
|
|
290
|
+
return { ok: true, result: parsed.dryRun ? result : await agentInitializationService.wait(parsed.aid, parsed.wait) };
|
|
249
291
|
}
|
|
250
292
|
catch (error) {
|
|
251
293
|
return fail(error.code ?? 'INVALID_ARGS', error.message);
|
|
@@ -283,7 +325,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
283
325
|
avatarFile: value('--avatar-file'),
|
|
284
326
|
avatarUrl: value('--avatar-url'),
|
|
285
327
|
force: argv.includes('--force'),
|
|
286
|
-
});
|
|
328
|
+
}, taskSession.projectPath);
|
|
287
329
|
const validationError = validateAgentCreatePlan(plan);
|
|
288
330
|
if (validationError)
|
|
289
331
|
return fail('INVALID_ARGS', validationError);
|
|
@@ -319,6 +361,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
|
|
|
319
361
|
channelType: delegation.grant.channelType,
|
|
320
362
|
chatType: delegation.grant.chatType,
|
|
321
363
|
peerKey: delegation.grant.peerKey,
|
|
364
|
+
approvalInteractionPolicy: delegation.grant.approvalInteractionPolicy,
|
|
322
365
|
},
|
|
323
366
|
causation: responseEngine.getTaskRuntimeContext(sessionId)?.causation,
|
|
324
367
|
});
|
|
@@ -4,6 +4,7 @@ import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
|
|
|
4
4
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import { canonicalizeEvolcoreCommandArgv } from '../permission/ec-command-parser.js';
|
|
7
|
+
import { MAIN_CLI_BIN } from '../../product.js';
|
|
7
8
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
8
9
|
export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
|
|
9
10
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
@@ -278,7 +279,22 @@ export function hashDelegatedCommandArgv(argv) {
|
|
|
278
279
|
.digest('hex');
|
|
279
280
|
}
|
|
280
281
|
export function hashCurrentDelegatedCommand(processArgv = process.argv) {
|
|
282
|
+
const argv = currentDelegatedCommandArgv(processArgv);
|
|
283
|
+
return argv ? hashDelegatedCommandArgv([MAIN_CLI_BIN, ...argv]) : undefined;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Return the command arguments used by a delegated CLI invocation.
|
|
287
|
+
*
|
|
288
|
+
* The returned value excludes the executable because IPC request builders add
|
|
289
|
+
* the canonical executable before hashing. Keeping this conversion beside
|
|
290
|
+
* hashCurrentDelegatedCommand prevents typed IPC requests from hashing a
|
|
291
|
+
* different representation than the implicit current-command path.
|
|
292
|
+
*/
|
|
293
|
+
export function currentDelegatedCommandArgv(processArgv = process.argv) {
|
|
281
294
|
if (processArgv.length < 3)
|
|
282
295
|
return undefined;
|
|
283
|
-
|
|
296
|
+
const canonical = canonicalizeEvolcoreCommandArgv([MAIN_CLI_BIN, ...processArgv.slice(2)]);
|
|
297
|
+
if (canonical[0] !== MAIN_CLI_BIN)
|
|
298
|
+
return undefined;
|
|
299
|
+
return canonical.slice(1);
|
|
284
300
|
}
|
|
@@ -52,6 +52,7 @@ export function createDelegationAuthorizer({ agentDelegationRegistry, sessionMan
|
|
|
52
52
|
aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
|
|
53
53
|
dataScope: subject.dataScope,
|
|
54
54
|
management: isManagementRole(subject.role),
|
|
55
|
+
sessionRuntimeDir: delegation.grant.sessionRuntimeDir,
|
|
55
56
|
}
|
|
56
57
|
: { ok: false, code: decision.code, error: decision.reason };
|
|
57
58
|
};
|
|
@@ -81,15 +81,15 @@ const GROUP_FS_READ_OPERATIONS = ['ls', 'stat', 'lstat', 'cat', 'find', 'df', 'g
|
|
|
81
81
|
category: 'read',
|
|
82
82
|
dangerous: false,
|
|
83
83
|
defaultScopes: ['relation', 'agent'],
|
|
84
|
-
description: `Read
|
|
84
|
+
description: `Read managed filesystem (${kind})`,
|
|
85
85
|
sources: ['agent-tool'],
|
|
86
86
|
}));
|
|
87
87
|
const GROUP_FS_WRITE_OPERATIONS = [['mkdir', 'write-own'], ['upload', 'write-own'], ['copy', 'write-own'], ['move', 'write-own'], ['remove', 'write-own'], ['setfacl', 'write-agent']].map(([kind, category]) => ({
|
|
88
88
|
id: `ec.fs.${kind}`,
|
|
89
89
|
category,
|
|
90
90
|
dangerous: false,
|
|
91
|
-
defaultScopes: ['relation'],
|
|
92
|
-
description: `Write
|
|
91
|
+
defaultScopes: ['relation', 'agent'],
|
|
92
|
+
description: `Write managed filesystem (${kind})`,
|
|
93
93
|
sources: ['agent-tool'],
|
|
94
94
|
}));
|
|
95
95
|
const HANDOFF_READ_OPERATIONS = ['status', 'list', 'trace'].map(kind => ({
|
|
@@ -719,6 +719,14 @@ const OPERATIONS = [
|
|
|
719
719
|
description: '读取或更新当前 Agent 的 Bootstrap welcome.md',
|
|
720
720
|
sources: ['agent-tool', 'control'],
|
|
721
721
|
},
|
|
722
|
+
{
|
|
723
|
+
id: 'agent.rename',
|
|
724
|
+
category: 'write-agent',
|
|
725
|
+
dangerous: true,
|
|
726
|
+
defaultScopes: ['agent'],
|
|
727
|
+
description: '更新并发布当前 Agent 的 agent.md 显示名称',
|
|
728
|
+
sources: ['agent-tool'],
|
|
729
|
+
},
|
|
722
730
|
{
|
|
723
731
|
id: 'agent.create',
|
|
724
732
|
category: 'process',
|