evolcore 0.0.33 → 0.0.35-test.0
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 +59 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +38 -4
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +93 -14
- package/dist/agents/codex/diagnostics.js +79 -22
- package/dist/agents/codex-runner.js +245 -47
- package/dist/agents/ecagent-runner.js +90 -27
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/agentmd.js +13 -2
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/msg/managed-operation.js +16 -0
- package/dist/aun/msg/p2p.js +18 -2
- package/dist/channels/aun.js +123 -7
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +39 -14
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +130 -23
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/getmeta-command.js +211 -0
- package/dist/cli/index.js +26 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/memory-command.js +200 -156
- package/dist/cli/thread-command.js +17 -7
- 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/resolved-config-op.js +25 -8
- 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 +22 -3
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/channel-runtime.js +1 -1
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +145 -5
- 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 +24 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/command/topic-slash.js +4 -7
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/event-catalog.js +14 -0
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +47 -2
- package/dist/core/memory/batch-meta.js +133 -0
- package/dist/core/memory/consolidation-context.js +132 -28
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation-trigger.js +15 -7
- package/dist/core/memory/consolidation.js +146 -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 +50 -19
- package/dist/core/message/aun-ipc-sender.js +19 -1
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +5 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +17 -0
- package/dist/core/message/response-engine.js +598 -50
- package/dist/core/permission/ec-command-parser.js +169 -44
- 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 +60 -62
- package/dist/core/relation/peer-identity.js +87 -12
- 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 +131 -6
- package/dist/core/session/session-title.js +26 -1
- 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 +289 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +6 -1
- package/dist/core/thread/ipc-operation.js +33 -16
- package/dist/core/thread/thread-operation.js +62 -24
- package/dist/core/thread/thread-service.js +258 -51
- 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 +17 -5
- 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 +9 -10
- package/kits/docs/evolcore/fs.md +2 -2
- package/kits/docs/evolcore/group-collaboration.md +3 -3
- package/kits/docs/evolcore/group.md +14 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -16
- 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/stats.md +1 -1
- package/kits/docs/evolcore/thread-chain.md +30 -14
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.auxiliary.json +9 -0
- package/kits/eck_manifest.json +20 -20
- package/kits/eck_manifest.memory.json +20 -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 +13 -16
- 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 +8 -5
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/thread-ops/SKILL.md +18 -18
- 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 +12 -12
- 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 +42 -7
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bash-description.md +9 -0
- 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),
|
|
@@ -2498,7 +2542,11 @@ export class CommandHandler {
|
|
|
2498
2542
|
}
|
|
2499
2543
|
}
|
|
2500
2544
|
else {
|
|
2501
|
-
|
|
2545
|
+
// `--thread` 是话题作用域读取:目标对端由话题推断,日志属于本 agent 自己,
|
|
2546
|
+
// 所以既允许在群会话里发起,也允许读当前对端以外的话题历史。
|
|
2547
|
+
const threadScoped = parsed.command.kind === 'history' && !!parsed.command.threadId;
|
|
2548
|
+
if (!hasTrustedFullAccessDelegation(grant) && !threadScoped
|
|
2549
|
+
&& (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
|
|
2502
2550
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
|
|
2503
2551
|
}
|
|
2504
2552
|
// A management role may read history for any peer, one peer per query.
|
|
@@ -2507,7 +2555,8 @@ export class CommandHandler {
|
|
|
2507
2555
|
// stay pinned here, and authorizeOperation below remains the authority
|
|
2508
2556
|
// for every role (member carries currentRelationOnly; visitor is denied).
|
|
2509
2557
|
const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
|
|
2510
|
-
||
|
|
2558
|
+
|| threadScoped
|
|
2559
|
+
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole ?? 'none'));
|
|
2511
2560
|
if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
|
|
2512
2561
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2513
2562
|
}
|
|
@@ -2530,7 +2579,7 @@ export class CommandHandler {
|
|
|
2530
2579
|
channelId: conversationId,
|
|
2531
2580
|
chatType: grant.chatType,
|
|
2532
2581
|
conversationId,
|
|
2533
|
-
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
|
|
2582
|
+
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole ?? null) } : {}),
|
|
2534
2583
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2535
2584
|
...trustedExecutionIdentityInput(grant),
|
|
2536
2585
|
});
|
|
@@ -2557,6 +2606,7 @@ export class CommandHandler {
|
|
|
2557
2606
|
...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
|
|
2558
2607
|
...(parsed.command.after !== undefined ? { after: parsed.command.after } : {}),
|
|
2559
2608
|
...(parsed.command.direction ? { direction: parsed.command.direction } : {}),
|
|
2609
|
+
...(parsed.command.threadId ? { threadId: parsed.command.threadId } : {}),
|
|
2560
2610
|
},
|
|
2561
2611
|
},
|
|
2562
2612
|
auditMetadata: {
|
|
@@ -2983,6 +3033,15 @@ export class CommandHandler {
|
|
|
2983
3033
|
const args = cmd.slice('/queue'.length).trim();
|
|
2984
3034
|
return await this.handleQueueCommand(sessionId, args);
|
|
2985
3035
|
}
|
|
3036
|
+
// 5.2 /path: 运行时路径授权(E3-a)。授权校验见 handlePathCommand。
|
|
3037
|
+
if (cmd === '/path' || cmd.startsWith('/path ')) {
|
|
3038
|
+
const args = cmd.slice('/path'.length).trim();
|
|
3039
|
+
return await this.handlePathCommand(sessionId, args, {
|
|
3040
|
+
selfAid: grantSelfAid,
|
|
3041
|
+
actorId: grant.actorId,
|
|
3042
|
+
role: ctlSubject.role,
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
2986
3045
|
// 6. 调用现有 handle(),不传 sendMessage 回调(结果直接返回)
|
|
2987
3046
|
try {
|
|
2988
3047
|
const result = await this._handleInternal(cmd, session.channel, session.channelId, undefined, // 不发送消息
|
|
@@ -3006,6 +3065,10 @@ export class CommandHandler {
|
|
|
3006
3065
|
source: 'agent-tool',
|
|
3007
3066
|
args: { ...relationArgs, ...extra },
|
|
3008
3067
|
});
|
|
3068
|
+
if (name === 'path') {
|
|
3069
|
+
const action = args[0] === 'grant' ? 'grant' : args[0] === 'revoke' ? 'revoke' : 'list';
|
|
3070
|
+
return intent(`ec.ctl.path.${action}`);
|
|
3071
|
+
}
|
|
3009
3072
|
if (name === 'send')
|
|
3010
3073
|
return intent('ec.ctl.send');
|
|
3011
3074
|
if (name === 'file')
|
|
@@ -3047,6 +3110,83 @@ export class CommandHandler {
|
|
|
3047
3110
|
}
|
|
3048
3111
|
return null;
|
|
3049
3112
|
}
|
|
3113
|
+
// ── Path grant command ──
|
|
3114
|
+
/**
|
|
3115
|
+
* `ec ctl path grant|revoke|list` —— 运行时路径授权(E3-a)。
|
|
3116
|
+
*
|
|
3117
|
+
* 这是提权操作:授予一条路径等于扩大该会话能触及的文件范围。约束有三条,
|
|
3118
|
+
* 缺一条这套机制就能被会话自己解除:
|
|
3119
|
+
* 1. 只有 owner / admin 可调用;
|
|
3120
|
+
* 2. 授予只对**当前会话**生效,不写进任何全局配置;
|
|
3121
|
+
* 3. 会话结束时由 releaseSessionPathGrants(sessionId) 清掉。
|
|
3122
|
+
*
|
|
3123
|
+
* 见 docs/memory/session-profile-design.md §4。
|
|
3124
|
+
*/
|
|
3125
|
+
async handlePathCommand(sessionId, args, actor) {
|
|
3126
|
+
if (actor.role !== 'owner' && actor.role !== 'admin') {
|
|
3127
|
+
return {
|
|
3128
|
+
ok: false,
|
|
3129
|
+
code: 'ROLE_ACCESS_DENIED',
|
|
3130
|
+
error: `路径授权仅 owner / admin 可操作(当前角色:${actor.role ?? 'none'})`,
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
const [action = 'list', ...rest] = args.split(/\s+/).filter(Boolean);
|
|
3134
|
+
const flag = (name) => {
|
|
3135
|
+
const index = rest.indexOf(name);
|
|
3136
|
+
return index >= 0 ? rest[index + 1] : undefined;
|
|
3137
|
+
};
|
|
3138
|
+
if (action === 'list') {
|
|
3139
|
+
const grants = listRuntimePathGrants(sessionId);
|
|
3140
|
+
if (grants.length === 0)
|
|
3141
|
+
return { ok: true, result: '当前会话没有运行时路径授权', data: [] };
|
|
3142
|
+
const data = grants.map(record => ({
|
|
3143
|
+
path: record.path,
|
|
3144
|
+
access: record.access,
|
|
3145
|
+
...(record.identity ? { identity: record.identity } : {}),
|
|
3146
|
+
...(record.grantedBy ? { grantedBy: record.grantedBy } : {}),
|
|
3147
|
+
grantedAt: new Date(record.grantedAt).toISOString(),
|
|
3148
|
+
}));
|
|
3149
|
+
return {
|
|
3150
|
+
ok: true,
|
|
3151
|
+
data,
|
|
3152
|
+
result: data.map(item => `${item.access.padEnd(5)} ${item.path}`).join('\n'),
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
const rawPath = rest[0];
|
|
3156
|
+
if (!rawPath || rawPath.startsWith('--')) {
|
|
3157
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '缺少路径。用法: ec ctl path grant <绝对路径> [--access read|write|none]' };
|
|
3158
|
+
}
|
|
3159
|
+
if (!path.isAbsolute(rawPath)) {
|
|
3160
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '路径必须是绝对路径' };
|
|
3161
|
+
}
|
|
3162
|
+
const resolvedPath = path.resolve(rawPath);
|
|
3163
|
+
if (action === 'revoke') {
|
|
3164
|
+
return revokeSessionPath(sessionId, resolvedPath)
|
|
3165
|
+
? { ok: true, result: `已撤销 ${resolvedPath}` }
|
|
3166
|
+
: { ok: false, code: 'NOT_FOUND', error: `当前会话没有该路径的运行时授权: ${resolvedPath}` };
|
|
3167
|
+
}
|
|
3168
|
+
if (action !== 'grant') {
|
|
3169
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: `未知子命令: ${action}(支持 grant / revoke / list)` };
|
|
3170
|
+
}
|
|
3171
|
+
const accessValue = flag('--access') ?? 'read';
|
|
3172
|
+
if (accessValue !== 'read' && accessValue !== 'write' && accessValue !== 'none') {
|
|
3173
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '--access 必须是 read / write / none' };
|
|
3174
|
+
}
|
|
3175
|
+
const identity = flag('--identity')?.trim();
|
|
3176
|
+
grantSessionPath({
|
|
3177
|
+
sessionId,
|
|
3178
|
+
path: resolvedPath,
|
|
3179
|
+
access: accessValue,
|
|
3180
|
+
...(identity ? { identity } : {}),
|
|
3181
|
+
...(actor.actorId ? { grantedBy: actor.actorId } : {}),
|
|
3182
|
+
grantedAt: Date.now(),
|
|
3183
|
+
});
|
|
3184
|
+
logger.info(`[PathGrant] granted session=${sessionId} path=${resolvedPath} access=${accessValue} by=${actor.actorId ?? 'unknown'} self=${actor.selfAid}`);
|
|
3185
|
+
return {
|
|
3186
|
+
ok: true,
|
|
3187
|
+
result: `✓ 已授予 ${accessValue} 于 ${resolvedPath}(仅当前会话,会话结束自动撤销)`,
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3050
3190
|
// ── Queue command ──
|
|
3051
3191
|
/** 提取命名参数值(如 --cancel msg123 → "msg123") */
|
|
3052
3192
|
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;
|
|
@@ -1757,6 +1769,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1757
1769
|
value: definition.id,
|
|
1758
1770
|
label: definition.name,
|
|
1759
1771
|
desc: `${view.scheduleType}${view.nextFireAt ? ` | 下次 ${new Date(view.nextFireAt).toLocaleString()}` : ''}`,
|
|
1772
|
+
// 只有 trigger 有说明时才带上 description 字段,前端按需渲染
|
|
1773
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
1760
1774
|
status: definition.enabled ? 'active' : 'disabled',
|
|
1761
1775
|
};
|
|
1762
1776
|
});
|
|
@@ -2286,8 +2300,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2286
2300
|
if (fromControlChannel) {
|
|
2287
2301
|
if (args?.aid) {
|
|
2288
2302
|
return this.agentApplicationService
|
|
2289
|
-
? await execAgentQuery(args, this.agentApplicationService, subject)
|
|
2290
|
-
: await execAgentQuery(args, undefined, subject);
|
|
2303
|
+
? await execAgentQuery(args, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2304
|
+
: await execAgentQuery(args, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2291
2305
|
}
|
|
2292
2306
|
return this.agentApplicationService
|
|
2293
2307
|
? await execAgentOptions(args, this.agentApplicationService)
|
|
@@ -2297,8 +2311,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2297
2311
|
if (!selfAid)
|
|
2298
2312
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2299
2313
|
return this.agentApplicationService
|
|
2300
|
-
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, subject)
|
|
2301
|
-
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, subject);
|
|
2314
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2315
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2302
2316
|
}
|
|
2303
2317
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
2304
2318
|
if (cmdBase === '/gateway') {
|
|
@@ -2944,6 +2958,8 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2944
2958
|
const config = value === true
|
|
2945
2959
|
? { enabled: true, idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS }
|
|
2946
2960
|
: { enabled: false };
|
|
2961
|
+
if (config.enabled)
|
|
2962
|
+
this.assertSessionLoopTrigger(current.session);
|
|
2947
2963
|
return { data: sessionLoopPayload(writeSessionLoopConfig(this.sessionManager, current, config)) };
|
|
2948
2964
|
}
|
|
2949
2965
|
catch (error) {
|
|
@@ -3705,7 +3721,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3705
3721
|
}
|
|
3706
3722
|
}
|
|
3707
3723
|
}
|
|
3708
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, subject);
|
|
3724
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, agentFullAccessMenuSubject(subject, source));
|
|
3709
3725
|
}
|
|
3710
3726
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3711
3727
|
if (cmdBase === '/trigger') {
|
|
@@ -4820,6 +4836,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4820
4836
|
: undefined,
|
|
4821
4837
|
});
|
|
4822
4838
|
const trustedIdentity = trustedSubject.identity;
|
|
4839
|
+
trustedSubject.agentFullAccessManagement = context.source === 'control' && context.authMethod === 'daemon-owner';
|
|
4823
4840
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4824
4841
|
if (payload?.type === 'menu.action'
|
|
4825
4842
|
&& 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))
|
|
@@ -474,18 +474,15 @@ async function summaryCommand(ctx, service, positional, flags, write) {
|
|
|
474
474
|
}
|
|
475
475
|
// 覆盖游标由 service 自动记录,无需传参
|
|
476
476
|
const ifRev = typeof flags['if-rev'] === 'string' ? parseInt(flags['if-rev'], 10) : undefined;
|
|
477
|
-
const r = service.setSummary(ctx.selfAid, sessionId, content, Number.isFinite(ifRev) ? ifRev : undefined);
|
|
477
|
+
const r = await service.setSummary(ctx.selfAid, sessionId, content, Number.isFinite(ifRev) ? ifRev : undefined);
|
|
478
478
|
const cursorNote = r.coveredThroughTs ? `,覆盖至 ${new Date(r.coveredThroughTs).toLocaleString('zh-CN')}` : '';
|
|
479
|
-
// --name
|
|
479
|
+
// --name:顺带改名。主会话(threadId 为空)同样可以改,走 renameThread 的主会话分支。
|
|
480
480
|
const rawName = flags.name.trim();
|
|
481
481
|
const newName = rawName && rawName !== '-' ? rawName : undefined;
|
|
482
|
-
if (newName
|
|
483
|
-
return result(`✅ summary 已写入(rev ${r.rev}${cursorNote})\n(主会话没有话题名,--name 已忽略)`);
|
|
484
|
-
}
|
|
485
|
-
if (newName && target.threadId) {
|
|
482
|
+
if (newName) {
|
|
486
483
|
try {
|
|
487
484
|
const renamed = await service.renameThread({
|
|
488
|
-
selfAid: ctx.selfAid, peer: target.peer, threadId: target.threadId, name: newName,
|
|
485
|
+
selfAid: ctx.selfAid, peer: target.peer, threadId: target.threadId ?? '', name: newName,
|
|
489
486
|
});
|
|
490
487
|
return result(`✅ summary 已写入(rev ${r.rev}${cursorNote})\n`
|
|
491
488
|
+ `✅ 话题已改名: ${renamed.name}`);
|
|
@@ -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');
|
|
@@ -173,6 +173,20 @@ const CATALOG = [
|
|
|
173
173
|
{ path: 'newName', type: 'string' },
|
|
174
174
|
],
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
type: 'thread:renamed',
|
|
178
|
+
namespace: 'thread',
|
|
179
|
+
name: 'renamed',
|
|
180
|
+
description: '对端协议消息改写本端话题名',
|
|
181
|
+
fields: [
|
|
182
|
+
{ path: 'sessionId', type: 'string', optional: true },
|
|
183
|
+
{ path: 'peer', type: 'string' },
|
|
184
|
+
{ path: 'threadId', type: 'string' },
|
|
185
|
+
{ path: 'name', type: 'string' },
|
|
186
|
+
{ path: 'nameUpdatedAt', type: 'number' },
|
|
187
|
+
{ path: 'by', type: 'string' },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
176
190
|
{
|
|
177
191
|
type: 'session:forked',
|
|
178
192
|
namespace: 'session',
|
|
@@ -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
|
+
}
|