evolcore 0.0.33 → 0.0.34
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 +37 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +12 -1
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +82 -13
- package/dist/agents/codex/diagnostics.js +37 -11
- package/dist/agents/codex-runner.js +218 -44
- package/dist/agents/ecagent-runner.js +73 -20
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/channels/aun.js +16 -6
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +3 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +120 -19
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/index.js +20 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/memory-command.js +158 -145
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +1 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +138 -4
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +22 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +36 -1
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +35 -14
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation.js +91 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +2 -1
- package/dist/core/message/response-engine.js +584 -47
- package/dist/core/permission/ec-command-parser.js +93 -4
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +75 -5
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/thread-service.js +13 -20
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +2 -2
- package/kits/docs/evolcore/group.md +10 -10
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +18 -14
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/thread-chain.md +5 -5
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.json +0 -20
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +12 -15
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +4 -4
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
2
|
import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
3
|
+
import { grantSessionPath, listRuntimePathGrants, revokeSessionPath } from '../permission/session-path-grants.js';
|
|
3
4
|
import { closeDiagnosticSession, diagnosticTokenHash, isDiagnosticSessionActive } from './diag-service.js';
|
|
4
5
|
import { parseMenuClientArgs } from '../../cli/menu-client.js';
|
|
5
6
|
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
@@ -20,6 +21,7 @@ import { agentmdGet } from '../../aun/aid/agentmd.js';
|
|
|
20
21
|
import { parseDuration, parseTriggerSet, parseTriggerUpdate } from '../../trigger/parser.js';
|
|
21
22
|
import { applyTriggerPatch, replaceEditableTriggerDefinition } from '../../trigger/patch.js';
|
|
22
23
|
import { definitionRevision } from '../../trigger/validation.js';
|
|
24
|
+
import { isDaemonEventSessionTrigger, matchTriggerEvent } from '../../trigger/event-source.js';
|
|
23
25
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
24
26
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
25
27
|
import { buildTranscriptTurns } from '../session/session-transcript.js';
|
|
@@ -45,7 +47,7 @@ import { handleSlashCommand } from './slash-handler.js';
|
|
|
45
47
|
import { resolveChatMode } from '../message/peer-mode.js';
|
|
46
48
|
import { execMenuAction as menuExecMenuAction, execMenuForEcweb as menuExecMenuForEcweb, execMenuForControl as menuExecMenuForControl, execMenuQuery as menuExecMenuQuery, execMenuUpdate as menuExecMenuUpdate, getMenuItems as menuGetMenuItems, getSubMenuItems as menuGetSubMenuItems, buildTopicForkSourcePayload, } from './menu-handler.js';
|
|
47
49
|
import { getMenuCatalog, execMenuValueBatch, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
48
|
-
import { resolveCurrentSessionLoopTarget } from '../session/session-idle-monitor.js';
|
|
50
|
+
import { resolveCurrentSessionLoopTarget, sessionLoopError } from '../session/session-idle-monitor.js';
|
|
49
51
|
export { isProcessLevelOwner } from './menu-handler.js';
|
|
50
52
|
const CLI_EXEC_TIMEOUT_MS = 15_000;
|
|
51
53
|
const CLI_EXEC_MAX_OUTPUT = 128 * 1024;
|
|
@@ -372,6 +374,45 @@ export class CommandHandler {
|
|
|
372
374
|
getTriggerSchedulerForAgent(agentAid) {
|
|
373
375
|
return this.triggerSchedulerResolver?.(agentAid) ?? (this.triggerSchedulerFallbackAid === agentAid ? this.triggerSchedulerFallback : undefined);
|
|
374
376
|
}
|
|
377
|
+
assertSessionLoopTrigger(session) {
|
|
378
|
+
const daemonAid = this.getDaemonTriggerOwnerAid();
|
|
379
|
+
const schedulers = new Set([
|
|
380
|
+
this.getTriggerSchedulerForAgent(session.selfAID ?? ''),
|
|
381
|
+
daemonAid ? this.getTriggerSchedulerForAgent(daemonAid) : undefined,
|
|
382
|
+
]);
|
|
383
|
+
const triggers = [...schedulers].flatMap(scheduler => scheduler?.list({ all: true }) ?? [])
|
|
384
|
+
.filter(definition => definition.id === '__session-idle-check'
|
|
385
|
+
|| (definition.source.type === 'event' && definition.source.eventPattern === 'session:idle'));
|
|
386
|
+
if (triggers.length === 0) {
|
|
387
|
+
throw sessionLoopError('LOOP_TRIGGER_NOT_FOUND', '未找到 session:idle Trigger,无法开启 loop');
|
|
388
|
+
}
|
|
389
|
+
const enabled = triggers.filter(definition => definition.enabled);
|
|
390
|
+
if (enabled.length === 0) {
|
|
391
|
+
throw sessionLoopError('LOOP_TRIGGER_DISABLED', 'session:idle Trigger 未开启,无法开启 loop');
|
|
392
|
+
}
|
|
393
|
+
const event = {
|
|
394
|
+
type: 'session:idle',
|
|
395
|
+
sessionId: session.id,
|
|
396
|
+
agentAid: session.selfAID,
|
|
397
|
+
channel: session.channelType || session.channel,
|
|
398
|
+
channelName: session.channel,
|
|
399
|
+
channelId: session.channelId,
|
|
400
|
+
chatType: session.chatType,
|
|
401
|
+
...(session.threadId ? { threadId: session.threadId } : {}),
|
|
402
|
+
peerId: session.metadata?.peerId || session.channelId,
|
|
403
|
+
};
|
|
404
|
+
const valid = enabled.some(definition => definition.source.type === 'event'
|
|
405
|
+
&& definition.source.eventPattern === 'session:idle'
|
|
406
|
+
&& definition.execution.type === 'target_session'
|
|
407
|
+
&& !!definition.execution.prompt?.trim()
|
|
408
|
+
&& definition.feedback.strategy === 'event_session'
|
|
409
|
+
&& (definition.agentAid === session.selfAID || isDaemonEventSessionTrigger(definition))
|
|
410
|
+
&& matchTriggerEvent(definition.source, event).matched);
|
|
411
|
+
if (!valid) {
|
|
412
|
+
throw sessionLoopError('LOOP_TRIGGER_INVALID', 'session:idle Trigger 配置不符合要求:事件过滤条件须允许接收当前会话的闲置事件,'
|
|
413
|
+
+ '并使用 target_session 执行、event_session 动态路由,且提示词非空');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
375
416
|
triggerSourceFromSchedule(scheduleType, scheduleValue) {
|
|
376
417
|
if (scheduleType === 'once')
|
|
377
418
|
return { type: 'once' };
|
|
@@ -463,6 +504,7 @@ export class CommandHandler {
|
|
|
463
504
|
createdAt: definition.createdAt,
|
|
464
505
|
updatedAt: definition.updatedAt,
|
|
465
506
|
status: definition.enabled ? 'active' : 'disabled',
|
|
507
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
466
508
|
limits: definition.limits,
|
|
467
509
|
limitState: schedulerDetails?.limitState,
|
|
468
510
|
subscription: schedulerDetails?.subscription,
|
|
@@ -715,6 +757,7 @@ export class CommandHandler {
|
|
|
715
757
|
agentAid: schedulerAid,
|
|
716
758
|
enabled: true,
|
|
717
759
|
name,
|
|
760
|
+
...(parsed.description ? { description: parsed.description } : {}),
|
|
718
761
|
createdAt: now,
|
|
719
762
|
updatedAt: now,
|
|
720
763
|
...(authorizedBy ? { authorizedBy } : {}),
|
|
@@ -1822,6 +1865,7 @@ export class CommandHandler {
|
|
|
1822
1865
|
// ── Agent Ctl ──
|
|
1823
1866
|
static CTL_COMMANDS = [
|
|
1824
1867
|
'/status', '/pwd', '/file', '/send', '/rename', '/name', '/queue', '/restart', '/model', '/effort',
|
|
1868
|
+
'/path',
|
|
1825
1869
|
];
|
|
1826
1870
|
/**
|
|
1827
1871
|
* 从 session 恢复 ReplyContext,用于 ctl send 主动发送文本时的路由
|
|
@@ -2462,7 +2506,7 @@ export class CommandHandler {
|
|
|
2462
2506
|
selfAid: grant.selfAid,
|
|
2463
2507
|
peerKey: grant.peerKey,
|
|
2464
2508
|
channel: grant.channel,
|
|
2465
|
-
role: grant.issuedRole,
|
|
2509
|
+
role: grant.issuedRole ?? 'none',
|
|
2466
2510
|
taskId: grant.taskId,
|
|
2467
2511
|
messageId: grant.messageId,
|
|
2468
2512
|
argvHash: hashArgv(argv),
|
|
@@ -2507,7 +2551,7 @@ export class CommandHandler {
|
|
|
2507
2551
|
// stay pinned here, and authorizeOperation below remains the authority
|
|
2508
2552
|
// for every role (member carries currentRelationOnly; visitor is denied).
|
|
2509
2553
|
const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
|
|
2510
|
-
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
|
|
2554
|
+
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole ?? 'none'));
|
|
2511
2555
|
if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
|
|
2512
2556
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2513
2557
|
}
|
|
@@ -2530,7 +2574,7 @@ export class CommandHandler {
|
|
|
2530
2574
|
channelId: conversationId,
|
|
2531
2575
|
chatType: grant.chatType,
|
|
2532
2576
|
conversationId,
|
|
2533
|
-
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
|
|
2577
|
+
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole ?? null) } : {}),
|
|
2534
2578
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2535
2579
|
...trustedExecutionIdentityInput(grant),
|
|
2536
2580
|
});
|
|
@@ -2983,6 +3027,15 @@ export class CommandHandler {
|
|
|
2983
3027
|
const args = cmd.slice('/queue'.length).trim();
|
|
2984
3028
|
return await this.handleQueueCommand(sessionId, args);
|
|
2985
3029
|
}
|
|
3030
|
+
// 5.2 /path: 运行时路径授权(E3-a)。授权校验见 handlePathCommand。
|
|
3031
|
+
if (cmd === '/path' || cmd.startsWith('/path ')) {
|
|
3032
|
+
const args = cmd.slice('/path'.length).trim();
|
|
3033
|
+
return await this.handlePathCommand(sessionId, args, {
|
|
3034
|
+
selfAid: grantSelfAid,
|
|
3035
|
+
actorId: grant.actorId,
|
|
3036
|
+
role: ctlSubject.role,
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
2986
3039
|
// 6. 调用现有 handle(),不传 sendMessage 回调(结果直接返回)
|
|
2987
3040
|
try {
|
|
2988
3041
|
const result = await this._handleInternal(cmd, session.channel, session.channelId, undefined, // 不发送消息
|
|
@@ -3006,6 +3059,10 @@ export class CommandHandler {
|
|
|
3006
3059
|
source: 'agent-tool',
|
|
3007
3060
|
args: { ...relationArgs, ...extra },
|
|
3008
3061
|
});
|
|
3062
|
+
if (name === 'path') {
|
|
3063
|
+
const action = args[0] === 'grant' ? 'grant' : args[0] === 'revoke' ? 'revoke' : 'list';
|
|
3064
|
+
return intent(`ec.ctl.path.${action}`);
|
|
3065
|
+
}
|
|
3009
3066
|
if (name === 'send')
|
|
3010
3067
|
return intent('ec.ctl.send');
|
|
3011
3068
|
if (name === 'file')
|
|
@@ -3047,6 +3104,83 @@ export class CommandHandler {
|
|
|
3047
3104
|
}
|
|
3048
3105
|
return null;
|
|
3049
3106
|
}
|
|
3107
|
+
// ── Path grant command ──
|
|
3108
|
+
/**
|
|
3109
|
+
* `ec ctl path grant|revoke|list` —— 运行时路径授权(E3-a)。
|
|
3110
|
+
*
|
|
3111
|
+
* 这是提权操作:授予一条路径等于扩大该会话能触及的文件范围。约束有三条,
|
|
3112
|
+
* 缺一条这套机制就能被会话自己解除:
|
|
3113
|
+
* 1. 只有 owner / admin 可调用;
|
|
3114
|
+
* 2. 授予只对**当前会话**生效,不写进任何全局配置;
|
|
3115
|
+
* 3. 会话结束时由 releaseSessionPathGrants(sessionId) 清掉。
|
|
3116
|
+
*
|
|
3117
|
+
* 见 docs/memory/session-profile-design.md §4。
|
|
3118
|
+
*/
|
|
3119
|
+
async handlePathCommand(sessionId, args, actor) {
|
|
3120
|
+
if (actor.role !== 'owner' && actor.role !== 'admin') {
|
|
3121
|
+
return {
|
|
3122
|
+
ok: false,
|
|
3123
|
+
code: 'ROLE_ACCESS_DENIED',
|
|
3124
|
+
error: `路径授权仅 owner / admin 可操作(当前角色:${actor.role ?? 'none'})`,
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
const [action = 'list', ...rest] = args.split(/\s+/).filter(Boolean);
|
|
3128
|
+
const flag = (name) => {
|
|
3129
|
+
const index = rest.indexOf(name);
|
|
3130
|
+
return index >= 0 ? rest[index + 1] : undefined;
|
|
3131
|
+
};
|
|
3132
|
+
if (action === 'list') {
|
|
3133
|
+
const grants = listRuntimePathGrants(sessionId);
|
|
3134
|
+
if (grants.length === 0)
|
|
3135
|
+
return { ok: true, result: '当前会话没有运行时路径授权', data: [] };
|
|
3136
|
+
const data = grants.map(record => ({
|
|
3137
|
+
path: record.path,
|
|
3138
|
+
access: record.access,
|
|
3139
|
+
...(record.identity ? { identity: record.identity } : {}),
|
|
3140
|
+
...(record.grantedBy ? { grantedBy: record.grantedBy } : {}),
|
|
3141
|
+
grantedAt: new Date(record.grantedAt).toISOString(),
|
|
3142
|
+
}));
|
|
3143
|
+
return {
|
|
3144
|
+
ok: true,
|
|
3145
|
+
data,
|
|
3146
|
+
result: data.map(item => `${item.access.padEnd(5)} ${item.path}`).join('\n'),
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
const rawPath = rest[0];
|
|
3150
|
+
if (!rawPath || rawPath.startsWith('--')) {
|
|
3151
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '缺少路径。用法: ec ctl path grant <绝对路径> [--access read|write|none]' };
|
|
3152
|
+
}
|
|
3153
|
+
if (!path.isAbsolute(rawPath)) {
|
|
3154
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '路径必须是绝对路径' };
|
|
3155
|
+
}
|
|
3156
|
+
const resolvedPath = path.resolve(rawPath);
|
|
3157
|
+
if (action === 'revoke') {
|
|
3158
|
+
return revokeSessionPath(sessionId, resolvedPath)
|
|
3159
|
+
? { ok: true, result: `已撤销 ${resolvedPath}` }
|
|
3160
|
+
: { ok: false, code: 'NOT_FOUND', error: `当前会话没有该路径的运行时授权: ${resolvedPath}` };
|
|
3161
|
+
}
|
|
3162
|
+
if (action !== 'grant') {
|
|
3163
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: `未知子命令: ${action}(支持 grant / revoke / list)` };
|
|
3164
|
+
}
|
|
3165
|
+
const accessValue = flag('--access') ?? 'read';
|
|
3166
|
+
if (accessValue !== 'read' && accessValue !== 'write' && accessValue !== 'none') {
|
|
3167
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '--access 必须是 read / write / none' };
|
|
3168
|
+
}
|
|
3169
|
+
const identity = flag('--identity')?.trim();
|
|
3170
|
+
grantSessionPath({
|
|
3171
|
+
sessionId,
|
|
3172
|
+
path: resolvedPath,
|
|
3173
|
+
access: accessValue,
|
|
3174
|
+
...(identity ? { identity } : {}),
|
|
3175
|
+
...(actor.actorId ? { grantedBy: actor.actorId } : {}),
|
|
3176
|
+
grantedAt: Date.now(),
|
|
3177
|
+
});
|
|
3178
|
+
logger.info(`[PathGrant] granted session=${sessionId} path=${resolvedPath} access=${accessValue} by=${actor.actorId ?? 'unknown'} self=${actor.selfAid}`);
|
|
3179
|
+
return {
|
|
3180
|
+
ok: true,
|
|
3181
|
+
result: `✓ 已授予 ${accessValue} 于 ${resolvedPath}(仅当前会话,会话结束自动撤销)`,
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3050
3184
|
// ── Queue command ──
|
|
3051
3185
|
/** 提取命名参数值(如 --cancel msg123 → "msg123") */
|
|
3052
3186
|
extractArg(args, flag) {
|
|
@@ -30,6 +30,7 @@ const LOG_SOURCES = [
|
|
|
30
30
|
{ id: 'menu-protocol', label: 'Menu Protocol', pattern: /^menu-protocol(?:-.+)?\.log$/ },
|
|
31
31
|
{ id: 'causation', label: 'Causation spans', pattern: /^causation-spans(?:-.+)?\.log$/ },
|
|
32
32
|
{ id: 'command-audit', label: 'Command audit', pattern: /^command-audit(?:-.+)?\.log$/ },
|
|
33
|
+
{ id: 'delegation-audit', label: 'Delegation audit', pattern: /^delegation-audit(?:-.+)?\.log$/ },
|
|
33
34
|
{ id: 'role-mutations', label: 'Role mutations', pattern: /^role-mutations(?:-.+)?\.log$/ },
|
|
34
35
|
];
|
|
35
36
|
const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
@@ -178,6 +178,9 @@ export async function resolveMenuCatalogContext(input) {
|
|
|
178
178
|
processRole,
|
|
179
179
|
isDaemonOwner: processRole === 'daemon-owner',
|
|
180
180
|
fromControlChannel: subject.fromControlChannel,
|
|
181
|
+
ecwebManagement: subject.ecwebManagement,
|
|
182
|
+
agentFullAccessManagement: subject.agentFullAccessManagement,
|
|
183
|
+
canApprovePersistentFullAccess: subject.canApprovePersistentFullAccess,
|
|
181
184
|
allowAccess: processRole !== 'none' || rebuilt.allowAccess,
|
|
182
185
|
};
|
|
183
186
|
const access = authorizeAccess(bound);
|
|
@@ -1144,6 +1144,18 @@ function buildMenuAuthSubject(owner, params) {
|
|
|
1144
1144
|
fromControlChannel: params.fromControlChannel,
|
|
1145
1145
|
});
|
|
1146
1146
|
}
|
|
1147
|
+
function agentFullAccessMenuSubject(subject, source) {
|
|
1148
|
+
return {
|
|
1149
|
+
...subject,
|
|
1150
|
+
agentFullAccessManagement: source === 'control'
|
|
1151
|
+
? subject.agentFullAccessManagement === true
|
|
1152
|
+
: source === 'menu'
|
|
1153
|
+
&& subject.channelType === 'aun'
|
|
1154
|
+
&& !subject.fromControlChannel
|
|
1155
|
+
&& subject.canApprovePersistentFullAccess === true
|
|
1156
|
+
&& subject.processRole === 'daemon-owner',
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1147
1159
|
async function authorizeMenuIntent(params) {
|
|
1148
1160
|
const { intent, identity, session, channel, channelId, userId, fromControlChannel } = params;
|
|
1149
1161
|
if (!intent)
|
|
@@ -1719,8 +1731,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1719
1731
|
if (!selfAid)
|
|
1720
1732
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1721
1733
|
const res = this.agentApplicationService
|
|
1722
|
-
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, subject)
|
|
1723
|
-
: await execAgentQuery({ aid: selfAid }, undefined, subject);
|
|
1734
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
1735
|
+
: await execAgentQuery({ aid: selfAid }, undefined, agentFullAccessMenuSubject(subject, source));
|
|
1724
1736
|
if ('error' in res)
|
|
1725
1737
|
throw { code: res.code, message: res.error };
|
|
1726
1738
|
const ag = res.data;
|
|
@@ -2286,8 +2298,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2286
2298
|
if (fromControlChannel) {
|
|
2287
2299
|
if (args?.aid) {
|
|
2288
2300
|
return this.agentApplicationService
|
|
2289
|
-
? await execAgentQuery(args, this.agentApplicationService, subject)
|
|
2290
|
-
: await execAgentQuery(args, undefined, subject);
|
|
2301
|
+
? await execAgentQuery(args, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2302
|
+
: await execAgentQuery(args, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2291
2303
|
}
|
|
2292
2304
|
return this.agentApplicationService
|
|
2293
2305
|
? await execAgentOptions(args, this.agentApplicationService)
|
|
@@ -2297,8 +2309,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2297
2309
|
if (!selfAid)
|
|
2298
2310
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2299
2311
|
return this.agentApplicationService
|
|
2300
|
-
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, subject)
|
|
2301
|
-
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, subject);
|
|
2312
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2313
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2302
2314
|
}
|
|
2303
2315
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
2304
2316
|
if (cmdBase === '/gateway') {
|
|
@@ -2944,6 +2956,8 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2944
2956
|
const config = value === true
|
|
2945
2957
|
? { enabled: true, idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS }
|
|
2946
2958
|
: { enabled: false };
|
|
2959
|
+
if (config.enabled)
|
|
2960
|
+
this.assertSessionLoopTrigger(current.session);
|
|
2947
2961
|
return { data: sessionLoopPayload(writeSessionLoopConfig(this.sessionManager, current, config)) };
|
|
2948
2962
|
}
|
|
2949
2963
|
catch (error) {
|
|
@@ -3705,7 +3719,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3705
3719
|
}
|
|
3706
3720
|
}
|
|
3707
3721
|
}
|
|
3708
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, subject);
|
|
3722
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, agentFullAccessMenuSubject(subject, source));
|
|
3709
3723
|
}
|
|
3710
3724
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3711
3725
|
if (cmdBase === '/trigger') {
|
|
@@ -4820,6 +4834,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4820
4834
|
: undefined,
|
|
4821
4835
|
});
|
|
4822
4836
|
const trustedIdentity = trustedSubject.identity;
|
|
4837
|
+
trustedSubject.agentFullAccessManagement = context.source === 'control' && context.authMethod === 'daemon-owner';
|
|
4823
4838
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4824
4839
|
if (payload?.type === 'menu.action'
|
|
4825
4840
|
&& menuCommandToken(cmd) === '/system'
|
|
@@ -470,6 +470,7 @@ const STABLE_CODES = new Set([
|
|
|
470
470
|
'RESPONSE_TOO_LARGE',
|
|
471
471
|
'ACTIVITY_NOT_FOUND', 'ACTIVITY_EXPIRED',
|
|
472
472
|
'TOPIC_SESSION_NOT_FOUND', 'TOPIC_SESSION_LOOKUP_FAILED',
|
|
473
|
+
'LOOP_TRIGGER_NOT_FOUND', 'LOOP_TRIGGER_DISABLED', 'LOOP_TRIGGER_INVALID',
|
|
473
474
|
'ROOT_NOT_FOUND', 'ROOT_FORBIDDEN', 'ROOT_EXPIRED', 'ROOT_SCOPE_MISMATCH', 'ROOT_PEER_MISMATCH',
|
|
474
475
|
'INVALID_ROOT_ID', 'MISSING_ROOT_ID', 'INVALID_ROOT_PATH', 'NOT_A_DIRECTORY', 'FILE_TOO_LARGE',
|
|
475
476
|
]);
|
|
@@ -603,6 +603,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
603
603
|
}
|
|
604
604
|
try {
|
|
605
605
|
const latest = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
606
|
+
if (next.enabled)
|
|
607
|
+
this.assertSessionLoopTrigger(latest.session);
|
|
606
608
|
const updated = writeSessionLoopConfig(this.sessionManager, latest, next);
|
|
607
609
|
const payload = sessionLoopPayload(updated);
|
|
608
610
|
if (this.shouldSuppressCardTriggerResult(source, channel))
|
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { resolvePaths } from '../paths.js';
|
|
5
5
|
import { AUNChannel } from '../channels/aun.js';
|
|
6
6
|
import { BindService } from '../utils/aid-bind.js';
|
|
7
|
-
import {
|
|
7
|
+
import { detectBaseagentDetails } from '../agents/baseagent-availability.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
9
|
import { remoteAccessService } from './auth/remote-access-store.js';
|
|
10
10
|
import { IpcServer } from '../ipc.js';
|
|
@@ -13,12 +13,9 @@ import { controlReplyContext } from './message/outbound.js';
|
|
|
13
13
|
import { TriggerDefinitionManager } from '../trigger/manager.js';
|
|
14
14
|
import { seedUpgradeCheckTrigger } from '../trigger/system-definitions.js';
|
|
15
15
|
export function detectAvailableBaseagents() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
out.push(command);
|
|
20
|
-
}
|
|
21
|
-
return out;
|
|
16
|
+
// 与 init/ECWeb 的探测保持同一判据;claude 在这里也走内置二进制,
|
|
17
|
+
// 不再只看 PATH——否则 daemon 会对外声明一个实际跑不起来的 baseagent。
|
|
18
|
+
return detectBaseagentDetails().filter(item => item.detected).map(item => item.id);
|
|
22
19
|
}
|
|
23
20
|
export async function runBindBootstrapDaemon(daemonCfg) {
|
|
24
21
|
logger.warn('[daemon-bootstrap] starting control AID + IPC only');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// 读取受台账保护的文件,并按调用方策略决定是否校验。
|
|
2
|
+
//
|
|
3
|
+
// 为什么要有这一层:ECK 文档和技能文件的读取走 FileCache,`on-reload` 策略下
|
|
4
|
+
// 缓存命中时根本不会碰磁盘。篡改发生在首次读盘之后时,只看盘上字节是发现不了的;
|
|
5
|
+
// 所以这里校验的是**将要被使用的那份内容**(缓存里的或刚读到的),
|
|
6
|
+
// 让读到的内容就是校验过的内容(TOCTOU)。
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { kitsDir } from '../../paths.js';
|
|
10
|
+
import { logger } from '../../utils/logger.js';
|
|
11
|
+
import { verifyFile } from './store.js';
|
|
12
|
+
/**
|
|
13
|
+
* 判断一个路径是否属于随包发布的内容(`kits/`)。
|
|
14
|
+
*
|
|
15
|
+
* 只有随包发布的东西才该建立基线:它们在开发仓里由 agent 随手改、打包后应当冻结。
|
|
16
|
+
* `$EVOLCORE_HOME` 下的运行时数据不能套用这套语义——那些文件本来就是给用户和
|
|
17
|
+
* CLI 改的,一旦校验就会把合法修改回滚掉。
|
|
18
|
+
*/
|
|
19
|
+
export function isShippedPath(filePath) {
|
|
20
|
+
const kits = path.resolve(kitsDir());
|
|
21
|
+
const target = path.resolve(filePath);
|
|
22
|
+
const rel = path.relative(kits, target);
|
|
23
|
+
return rel !== '' && !path.isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${path.sep}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 返回可用于本次注入的文档内容。
|
|
27
|
+
*
|
|
28
|
+
* 返回 null 有三种情况,调用方都应拒绝使用该文档(本函数已记日志):
|
|
29
|
+
* - 文件不存在或读不出来
|
|
30
|
+
* - sha 与台账不符、且快照也损坏,恢复不了
|
|
31
|
+
*
|
|
32
|
+
* 台账无记录时按设计用本次读到的内容自动登记,视为 verified(安装模式首次加载建基线)。
|
|
33
|
+
*/
|
|
34
|
+
export function readVerifiedText(filePath, opts) {
|
|
35
|
+
const tag = opts.tag ?? 'file-integrity';
|
|
36
|
+
if (!opts.verify) {
|
|
37
|
+
try {
|
|
38
|
+
return { text: fs.readFileSync(filePath, 'utf-8'), status: 'unverified' };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger.warn(`[${tag}] read failed path=${filePath} ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const restoreOnFail = opts.restoreOnFail ?? true;
|
|
46
|
+
const result = verifyFile(filePath, { restoreOnFail });
|
|
47
|
+
if (result.status === 'missing') {
|
|
48
|
+
logger.warn(`[${tag}] missing path=${filePath}`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!result.bytes) {
|
|
52
|
+
// 要求恢复却没恢复成功:只有 expected/actual,没有 bytes
|
|
53
|
+
logger.error(`[${tag}] tampered-and-unrecoverable path=${filePath} expected=${result.expected} actual=${result.actual}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (result.status === 'restored') {
|
|
57
|
+
logger.warn(`[${tag}] restored path=${filePath}`);
|
|
58
|
+
return { text: result.bytes.toString('utf-8'), status: 'restored' };
|
|
59
|
+
}
|
|
60
|
+
return { text: result.bytes.toString('utf-8'), status: 'verified' };
|
|
61
|
+
}
|