evolcore 0.0.35 → 0.0.36
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 +31 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +134 -48
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +109 -34
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
|
@@ -1,34 +1,37 @@
|
|
|
1
|
+
import { parsePermissionApprovalDecision } from '../permission/approval-gateway.js';
|
|
1
2
|
import { hasModelSwitcher, hasPermissionController } from '../../agents/runner-types.js';
|
|
2
3
|
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
3
4
|
import { buildEnvelope, isDeliveryTargetForChannel } from '../message/message-utils.js';
|
|
4
5
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
5
6
|
import { logger } from '../../utils/logger.js';
|
|
6
|
-
import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
7
|
+
import { shouldSuppressRealRestart, createRestartRequest, updateRestartRequest, removeRestartRequest } from '../../utils/restart-safety.js';
|
|
8
|
+
import { RESTART_REQUEST_ID_ENV } from '../../product.js';
|
|
7
9
|
import crypto from 'crypto';
|
|
8
10
|
import path from 'path';
|
|
9
11
|
import fs from 'fs';
|
|
10
12
|
import { ensureFreshPackageVersionInfo, refreshManagedPackageVersions } from '../../utils/package-version-info.js';
|
|
11
|
-
import { loadDaemonConfig } from '../../config-store.js';
|
|
12
13
|
import { remoteAccessService } from '../auth/remote-access-store.js';
|
|
13
14
|
import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, } from '../../config/config-manager.js';
|
|
14
15
|
import { execAgentAction } from './agent-control.js';
|
|
15
16
|
import { handleMemorySlash, memoryHelpEntries } from './memory-slash.js';
|
|
16
17
|
import { handleSkillSlash, skillHelpEntries } from './skill-slash.js';
|
|
17
18
|
import { handleTopicSlash, topicHelpEntries, canUseTopicSlash } from './topic-slash.js';
|
|
18
|
-
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
19
|
+
import { authorizeOperation, authorizeFreshOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
20
|
+
import { getDaemonOwners, ownerResolver, refreshOwnershipForAuthorization, OWNERSHIP_CACHE_MAX_AGE_MS } from '../ownership/owner-resolver.js';
|
|
19
21
|
import { authorizationNextStep, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
20
22
|
import { resolvePermissionMode, writeScope } from '../model/config-scope.js';
|
|
21
23
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
24
|
+
import { isMainSession, isTopicSession, isTopicThreadId, sessionTopicThreadId } from '../thread/topic-id.js';
|
|
22
25
|
import { modelMatches, getCatalog, assertCatalogSelectable, resolveCatalogModel, CONFIGURABLE_EFFORTS, configurableEfforts } from '../model/model-catalog.js';
|
|
23
26
|
import { formatModelCheck, runModelCheck } from '../model/model-diagnostics.js';
|
|
24
27
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
25
28
|
import { resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
26
29
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
27
|
-
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
30
|
+
import { chatmodeFieldForPeer, isPrivateChatmodeRoleLocked, resolveChatModeForField } from '../message/peer-mode.js';
|
|
28
31
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
29
32
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
30
33
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
31
|
-
import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } from '../../utils/cross-platform.js';
|
|
34
|
+
import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage, isProcessRunning } from '../../utils/cross-platform.js';
|
|
32
35
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
33
36
|
import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
34
37
|
import { MAX_AUN_ATTACHMENT_SIZE } from '../../aun/msg/upload.js';
|
|
@@ -60,13 +63,14 @@ function resolveSlashChatmodeTarget(params) {
|
|
|
60
63
|
const field = slashChatmodeField(params.session, params.chatType);
|
|
61
64
|
return {
|
|
62
65
|
sel: { self, peerKey: formatPeerKey(channelType, peerId) },
|
|
66
|
+
role: params.role,
|
|
63
67
|
field,
|
|
64
68
|
fieldPath: `chatmode.${field}`,
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
function readSlashChatmode(target) {
|
|
68
72
|
try {
|
|
69
|
-
const mode = resolveChatModeForField({ ...target.sel, field: target.field });
|
|
73
|
+
const mode = resolveChatModeForField({ ...target.sel, role: target.role, field: target.field });
|
|
70
74
|
return mode === 'interactive' || mode === 'proactive'
|
|
71
75
|
? mode
|
|
72
76
|
: defaultSlashChatmode(target.field);
|
|
@@ -192,7 +196,7 @@ async function authorizeSlashIntent(params) {
|
|
|
192
196
|
if (intent.scope === 'relation') {
|
|
193
197
|
intent.args = buildSlashRelationIntentArgs({ args: intent.args, selfAid: subject.selfAid, peerKey: subject.peerKey ?? peerKey });
|
|
194
198
|
}
|
|
195
|
-
const decision = await
|
|
199
|
+
const decision = await authorizeFreshOperation({ source: 'slash', intent, subject });
|
|
196
200
|
if (!decision.allow) {
|
|
197
201
|
const reasonCode = decision.command?.reasonCode;
|
|
198
202
|
return {
|
|
@@ -260,7 +264,7 @@ async function canReadSlashModelList(params) {
|
|
|
260
264
|
processOwners: isDaemonOwner && userId ? [userId] : [],
|
|
261
265
|
fromControlChannel: false,
|
|
262
266
|
});
|
|
263
|
-
const decision = await
|
|
267
|
+
const decision = await authorizeFreshOperation({ source: 'slash', intent, subject, audit: false });
|
|
264
268
|
return decision.allow;
|
|
265
269
|
}
|
|
266
270
|
function getAgentBusyInfo(handler, aid, excludeSessionKey) {
|
|
@@ -394,6 +398,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
394
398
|
chatType = undefined;
|
|
395
399
|
// 解析身份(按实例名)
|
|
396
400
|
const narrowedChatType = chatType === 'group' ? 'group' : chatType === 'private' ? 'private' : undefined;
|
|
401
|
+
await refreshOwnershipForAuthorization(selfAID ?? this.agentRegistry?.resolveByChannel(channel)?.aid ?? this.resolveSelfAID(channel), {
|
|
402
|
+
maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS,
|
|
403
|
+
});
|
|
397
404
|
const identityConversationId = narrowedChatType === 'group' ? channelId : userId;
|
|
398
405
|
const resolvedIdentity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, narrowedChatType, identityConversationId);
|
|
399
406
|
const authSubject = overrideSubject ?? buildAuthSubject({
|
|
@@ -407,7 +414,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
407
414
|
chatType: narrowedChatType,
|
|
408
415
|
conversationId: identityConversationId,
|
|
409
416
|
identity: resolvedIdentity,
|
|
410
|
-
processOwners:
|
|
417
|
+
processOwners: getDaemonOwners(),
|
|
411
418
|
fromControlChannel: false,
|
|
412
419
|
});
|
|
413
420
|
const identity = authSubject.identity;
|
|
@@ -681,7 +688,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
681
688
|
return { ok: false, reason: 'unknown' };
|
|
682
689
|
}
|
|
683
690
|
}
|
|
684
|
-
if (!current || current
|
|
691
|
+
if (!current || isTopicSession(current))
|
|
685
692
|
return { ok: false, reason: 'missing' };
|
|
686
693
|
// Preserve the legacy main-session /compact exclusion. Topic commands
|
|
687
694
|
// use the stronger barrier below; main sessions retain the historical
|
|
@@ -712,7 +719,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
712
719
|
logger.warn(`[CommandHandler] session snapshot read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
713
720
|
return { ok: false, reason: 'unknown' };
|
|
714
721
|
}
|
|
715
|
-
|
|
722
|
+
// expectedThreadId 到这里一定是话题 id,用线上形态比较。
|
|
723
|
+
if (!current || sessionTopicThreadId(current) !== expectedThreadId) {
|
|
716
724
|
return { ok: false, reason: 'missing' };
|
|
717
725
|
}
|
|
718
726
|
const activity = probeBackendActivity(current, snapshot);
|
|
@@ -789,7 +797,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
789
797
|
'',
|
|
790
798
|
'🔐 权限管理:',
|
|
791
799
|
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
792
|
-
...(canAnswerPermission ? [' /perm
|
|
800
|
+
...(canAnswerPermission ? [' /perm <动作> - 审批权限请求(按授权卡选项)'] : []),
|
|
793
801
|
] : []),
|
|
794
802
|
'',
|
|
795
803
|
'其他:',
|
|
@@ -832,7 +840,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
832
840
|
'',
|
|
833
841
|
'🔐 权限管理:',
|
|
834
842
|
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
835
|
-
...(canAnswerPermission ? [' /perm
|
|
843
|
+
...(canAnswerPermission ? [' /perm <动作> - 审批权限请求(按授权卡选项)'] : []),
|
|
836
844
|
] : []),
|
|
837
845
|
...(canUseFile ? [
|
|
838
846
|
'',
|
|
@@ -885,7 +893,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
885
893
|
'🔐 权限管理:',
|
|
886
894
|
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
887
895
|
...(canReadPermission ? [' 权限模式请通过角色策略编辑器修改'] : []),
|
|
888
|
-
...(canAnswerPermission ? [' /perm
|
|
896
|
+
...(canAnswerPermission ? [' /perm <动作> - 审批权限请求(按授权卡选项)'] : []),
|
|
889
897
|
'',
|
|
890
898
|
'🛠️ 运维:',
|
|
891
899
|
' /status - 显示会话状态',
|
|
@@ -1027,9 +1035,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1027
1035
|
if (canReadPermission)
|
|
1028
1036
|
cmds.push({ command: '/perm', description: '查看当前角色决定的权限模式', category: '权限管理', roles: [identity.role] });
|
|
1029
1037
|
if (canAnswerPermission)
|
|
1030
|
-
cmds.push({ command: '/perm', args: '
|
|
1038
|
+
cmds.push({ command: '/perm', args: '[requestId] <动作>', description: '审批权限请求(按授权卡选项,支持文件/目录 60 分钟)', category: '权限管理', roles: [identity.role] });
|
|
1031
1039
|
// 记忆管理(紧跟权限管理):能一键执行的都展开成无参数条目
|
|
1032
|
-
const memoryOwnerForHelp =
|
|
1040
|
+
const memoryOwnerForHelp = ownerResolver.primaryOwner(authSubject.selfAid ?? '');
|
|
1033
1041
|
cmds.push(...memoryHelpEntries({
|
|
1034
1042
|
chatType: 'private',
|
|
1035
1043
|
role: identity.role,
|
|
@@ -1089,6 +1097,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1089
1097
|
const permissionDecisionLabels = {
|
|
1090
1098
|
allow: '✓ 已授权(本次),继续执行……',
|
|
1091
1099
|
always: '✓ 已授权(当前会话临时授权 30 分钟),继续执行……',
|
|
1100
|
+
file_60m: '✓ 已授权(同文件 60 分钟),继续执行……',
|
|
1101
|
+
directory_60m: '✓ 已授权(当前目录及子目录 60 分钟),继续执行……',
|
|
1102
|
+
all_directories_60m: '✓ 已授权(当前会话所有普通目录 60 分钟),继续执行……',
|
|
1092
1103
|
deny: '✓ 已拒绝',
|
|
1093
1104
|
};
|
|
1094
1105
|
// Explicit request IDs are globally unique and already bound to an exact
|
|
@@ -1096,11 +1107,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1096
1107
|
// lookup so an owner can answer a handoff in an otherwise idle AUN chat.
|
|
1097
1108
|
const explicitParts = args.split(/\s+/);
|
|
1098
1109
|
if (explicitParts.length === 2
|
|
1099
|
-
&& (explicitParts[1]
|
|
1110
|
+
&& parsePermissionApprovalDecision(explicitParts[1])) {
|
|
1100
1111
|
if (!this.permissionGateway) {
|
|
1101
1112
|
return { kind: 'command.error', text: '❌ 权限审批未启用' };
|
|
1102
1113
|
}
|
|
1103
|
-
const decision = explicitParts[1];
|
|
1114
|
+
const decision = parsePermissionApprovalDecision(explicitParts[1]);
|
|
1104
1115
|
const resolved = this.permissionGateway.resolvePermissionByRequestId(explicitParts[0], decision, userId, channel);
|
|
1105
1116
|
if (!resolved) {
|
|
1106
1117
|
return { kind: 'command.error', text: '❌ 请求不存在、无法验证审批人身份或审批资格已失效' };
|
|
@@ -1181,9 +1192,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1181
1192
|
// /perm <mode> 不再修改配置;allow/always/deny 仍用于快捷审批。
|
|
1182
1193
|
if (parts.length === 1) {
|
|
1183
1194
|
const arg = parts[0];
|
|
1184
|
-
// /perm
|
|
1185
|
-
|
|
1186
|
-
|
|
1195
|
+
// /perm <action>: approve the sole pending request with the offered action.
|
|
1196
|
+
const decision = parsePermissionApprovalDecision(arg);
|
|
1197
|
+
if (decision) {
|
|
1198
|
+
const authDenied = await authorizePermissionAnswer(decision);
|
|
1187
1199
|
if (authDenied)
|
|
1188
1200
|
return authDenied;
|
|
1189
1201
|
if (!this.permissionGateway) {
|
|
@@ -1197,7 +1209,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1197
1209
|
return { kind: 'command.error', text: `❌ 当前有 ${pendingIds.length} 个待审批请求,请指定 requestId:\n${pendingIds.map((id) => ` /perm ${id} ${arg}`).join('\n')}` };
|
|
1198
1210
|
}
|
|
1199
1211
|
const requestId = pendingIds[0];
|
|
1200
|
-
return resolvePermissionAnswer(requestId,
|
|
1212
|
+
return resolvePermissionAnswer(requestId, decision);
|
|
1201
1213
|
}
|
|
1202
1214
|
// /perm <mode>:提示改用角色策略编辑器。
|
|
1203
1215
|
if (hasPermissionController(permAgent)) {
|
|
@@ -1211,9 +1223,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1211
1223
|
if (PERMISSION_MODE_KEYS.includes(arg)) {
|
|
1212
1224
|
return { kind: 'command.error', text: '❌ permissionMode 只能通过角色策略编辑器修改' };
|
|
1213
1225
|
}
|
|
1214
|
-
return { kind: 'command.error', text: `❌ 未知参数: ${arg}\n用法: /perm
|
|
1226
|
+
return { kind: 'command.error', text: `❌ 未知参数: ${arg}\n用法: /perm [requestId] <动作>;动作以授权卡为准:allow|file_60m|directory_60m|all_directories_60m|always|deny` };
|
|
1215
1227
|
}
|
|
1216
|
-
return { kind: 'command.error', text: `❌ 未知参数: ${args}\n用法: /perm
|
|
1228
|
+
return { kind: 'command.error', text: `❌ 未知参数: ${args}\n用法: /perm [requestId] <动作>;动作以授权卡为准:allow|file_60m|directory_60m|all_directories_60m|always|deny` };
|
|
1217
1229
|
}
|
|
1218
1230
|
// /ask 命令:回答 AskUserQuestion / ExitPlanMode 的交互式问题
|
|
1219
1231
|
if (normalizedContent.startsWith('/ask')) {
|
|
@@ -1327,8 +1339,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1327
1339
|
}
|
|
1328
1340
|
const currentSession = await getExistingSessionForCommand();
|
|
1329
1341
|
const previousBaseagent = owningAgent.baseagent || this.parseDefaultBaseagent();
|
|
1330
|
-
|
|
1331
|
-
|
|
1342
|
+
const currentTopicThreadId = sessionTopicThreadId(currentSession);
|
|
1343
|
+
if (currentSession && currentTopicThreadId) {
|
|
1344
|
+
const rotation = await withIdleTopicBackend(currentSession.id, currentTopicThreadId, async (current) => {
|
|
1332
1345
|
try {
|
|
1333
1346
|
owningAgent.setActiveBaseagent(args);
|
|
1334
1347
|
}
|
|
@@ -1338,7 +1351,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1338
1351
|
try {
|
|
1339
1352
|
const result = await this.sessionManager.rotateTopicBackend({
|
|
1340
1353
|
sessionId: current.id,
|
|
1341
|
-
|
|
1354
|
+
// withIdleTopicBackend 已校验 sessionTopicThreadId(current) === currentTopicThreadId
|
|
1355
|
+
expectedThreadId: currentTopicThreadId,
|
|
1342
1356
|
baseagent: args,
|
|
1343
1357
|
});
|
|
1344
1358
|
return { ok: true, result };
|
|
@@ -2286,6 +2300,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2286
2300
|
if (arg === currentMode) {
|
|
2287
2301
|
return { kind: 'command.result', text: `📋 当前会话模式已是 ${arg}` };
|
|
2288
2302
|
}
|
|
2303
|
+
if (arg === 'interactive' && isPrivateChatmodeRoleLocked(target.field, target.role)) {
|
|
2304
|
+
return { kind: 'command.error', text: '❌ 私聊交互模式仅限 owner/admin,当前角色固定为 proactive' };
|
|
2305
|
+
}
|
|
2289
2306
|
// 仅在真正需要切换时才要求会话空闲
|
|
2290
2307
|
if (chatmodeSession && threadId) {
|
|
2291
2308
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
@@ -2473,7 +2490,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2473
2490
|
if (normalizedContent !== '/renew') {
|
|
2474
2491
|
return { kind: 'system.error', text: '❌ /renew 不接受参数,请直接使用 /renew', subtype: 'session.renew.invalid_arguments', recoverable: true };
|
|
2475
2492
|
}
|
|
2476
|
-
if (!threadId) {
|
|
2493
|
+
if (!isTopicThreadId(threadId)) {
|
|
2477
2494
|
return { kind: 'system.error', text: '⚠️ /renew 仅支持话题会话', subtype: 'session.renew.unsupported', recoverable: true };
|
|
2478
2495
|
}
|
|
2479
2496
|
// Card callbacks use synthetic message IDs on several adapters. They are
|
|
@@ -2515,7 +2532,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2515
2532
|
logger.warn(`[CommandHandler] /renew strict session read failed: session=${renewSession.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2516
2533
|
return { kind: 'unknown' };
|
|
2517
2534
|
}
|
|
2518
|
-
if (!current || current
|
|
2535
|
+
if (!current || sessionTopicThreadId(current) !== threadId)
|
|
2519
2536
|
return { kind: 'missing' };
|
|
2520
2537
|
let claim;
|
|
2521
2538
|
try {
|
|
@@ -2580,9 +2597,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2580
2597
|
? { kind: 'command.error', text: '⚠️ 当前会话状态未知,无法安全压缩,请稍后再试' }
|
|
2581
2598
|
: { kind: 'command.error', text: '❌ 当前没有活跃会话,无需压缩' };
|
|
2582
2599
|
}
|
|
2583
|
-
if (
|
|
2600
|
+
if (isMainSession(session))
|
|
2584
2601
|
await interruptPausedSessionBeforeReplacement(session);
|
|
2585
|
-
const outcome = await withIdleTopicBackend(session.id, session
|
|
2602
|
+
const outcome = await withIdleTopicBackend(session.id, sessionTopicThreadId(session), async (session) => {
|
|
2586
2603
|
if (!session.agentSessionId) {
|
|
2587
2604
|
return { kind: 'command.error', text: '❌ 当前会话没有历史记录,无需压缩' };
|
|
2588
2605
|
}
|
|
@@ -2711,7 +2728,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2711
2728
|
const sessionAgent = this.getAgent(channel, session.baseagent);
|
|
2712
2729
|
const isCurrentlyProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
2713
2730
|
const queueLength = this.messageQueue.getQueueLength(sessionKey);
|
|
2714
|
-
const isThread =
|
|
2731
|
+
const isThread = isTopicSession(session);
|
|
2715
2732
|
let sessionStatus = isCurrentlyProcessing ? '处理中' : '空闲';
|
|
2716
2733
|
// 处理中时显示时长
|
|
2717
2734
|
if (isCurrentlyProcessing) {
|
|
@@ -2833,7 +2850,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2833
2850
|
sourceChannel: sourceForConsolidation?.channelType || channel,
|
|
2834
2851
|
sourceChannelId: channelId,
|
|
2835
2852
|
sourceChatType: activeChatType,
|
|
2836
|
-
sourceThreadId: sourceForConsolidation
|
|
2853
|
+
sourceThreadId: sessionTopicThreadId(sourceForConsolidation),
|
|
2837
2854
|
sourceSelfAid: sourceForConsolidation?.selfAID
|
|
2838
2855
|
|| selfAID
|
|
2839
2856
|
|| this.getOwningAgent(channel)?.aid
|
|
@@ -3103,7 +3120,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3103
3120
|
}
|
|
3104
3121
|
// /restart 命令:重启服务(进程级,仅 daemon owner)
|
|
3105
3122
|
if (normalizedContent === '/restart') {
|
|
3106
|
-
//
|
|
3123
|
+
// 进程级操作:必须是已核验的 Control AID 协议 Owner,与 menu 协议 /system restart 一致。
|
|
3107
3124
|
// agent-channel 的 owner/admin 角色不足以重启整个 daemon。
|
|
3108
3125
|
const restartSelfAid = this.agentRegistry?.resolveByChannel(channel)?.aid;
|
|
3109
3126
|
const commandSession = await getExistingSessionForCommand();
|
|
@@ -3207,6 +3224,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3207
3224
|
channel,
|
|
3208
3225
|
channelId,
|
|
3209
3226
|
timestamp: Date.now(),
|
|
3227
|
+
requestId: crypto.randomUUID(),
|
|
3228
|
+
requesterPid: process.pid,
|
|
3229
|
+
status: 'requested',
|
|
3210
3230
|
...(restartReplyContext?.threadId ? { threadId: restartReplyContext.threadId } : {}),
|
|
3211
3231
|
...(restartReplyContext?.delivery ? { delivery: restartReplyContext.delivery } : {}),
|
|
3212
3232
|
...(restartReplyContext?.replyToMessageId ? { rootId: restartReplyContext.replyToMessageId } : {}),
|
|
@@ -3224,16 +3244,35 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3224
3244
|
}
|
|
3225
3245
|
const controlDir = daemonControlDir();
|
|
3226
3246
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
3227
|
-
|
|
3228
|
-
|
|
3247
|
+
const pendingFile = path.join(controlDir, 'restart-pending.json');
|
|
3248
|
+
if (scanInstances().restartMonitors.some(item => item.alive)
|
|
3249
|
+
|| !createRestartRequest(pendingFile, restartInfo, isProcessRunning)) {
|
|
3250
|
+
if (monitorEntry.stageDir)
|
|
3251
|
+
cleanupMonitorStage(monitorEntry.stageDir);
|
|
3252
|
+
return { code: 'RESTART_PENDING_CONFIRMATION', reason: '已有重启请求正在处理', requestId: restartInfo.requestId };
|
|
3253
|
+
}
|
|
3254
|
+
const confirmation = await launchMonitorAndConfirm([...monitorEntry.args, 'restart-monitor'], { EVOLCORE_HOME: resolvePaths().root, ...monitorEntry.env, [RESTART_REQUEST_ID_ENV]: restartInfo.requestId }, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid === pid
|
|
3255
|
+
&& m.record.requestId === restartInfo.requestId && m.record.phase === 'accepted'), pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid !== pid
|
|
3256
|
+
&& m.record.requestId === restartInfo.requestId && m.record.phase === 'accepted'), {
|
|
3257
|
+
requestId: restartInfo.requestId,
|
|
3258
|
+
logFilePath: path.join(resolvePaths().logs, `restart-launch-${restartInfo.requestId}.log`),
|
|
3259
|
+
onSpawn: pid => { updateRestartRequest(pendingFile, restartInfo.requestId, { launcherPid: pid }); },
|
|
3260
|
+
isPendingInRegistry: () => scanInstances().restartMonitors.some(m => m.alive && m.record.requestId === restartInfo.requestId),
|
|
3261
|
+
});
|
|
3229
3262
|
if (!confirmation.ok) {
|
|
3263
|
+
if (confirmation.pending) {
|
|
3264
|
+
logger.warn(`[System] Restart ownership remains unconfirmed (requestId=${restartInfo.requestId}, PID=${confirmation.pid ?? 'unknown'})`);
|
|
3265
|
+
return {
|
|
3266
|
+
code: 'RESTART_PENDING_CONFIRMATION',
|
|
3267
|
+
reason: confirmation.reason ?? 'ownership confirmation timed out',
|
|
3268
|
+
requestId: restartInfo.requestId,
|
|
3269
|
+
...(confirmation.pid ? { pid: confirmation.pid } : {}),
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3230
3272
|
// 没有看门狗就不要停服务:撤掉回执文件,让本进程继续跑。
|
|
3231
3273
|
if (monitorEntry.stageDir)
|
|
3232
3274
|
cleanupMonitorStage(monitorEntry.stageDir);
|
|
3233
|
-
|
|
3234
|
-
fs.rmSync(path.join(controlDir, 'restart-pending.json'), { force: true });
|
|
3235
|
-
}
|
|
3236
|
-
catch { }
|
|
3275
|
+
removeRestartRequest(pendingFile, restartInfo.requestId);
|
|
3237
3276
|
logger.error(`[System] Refusing restart: restart-monitor did not come up (${confirmation.reason})`);
|
|
3238
3277
|
for (const line of (confirmation.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
|
|
3239
3278
|
logger.error(`[System] | ${line}`);
|
|
@@ -3274,7 +3313,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3274
3313
|
},
|
|
3275
3314
|
},
|
|
3276
3315
|
});
|
|
3277
|
-
await adapter.send(envelope, { kind: 'command.result', text: '🔄
|
|
3316
|
+
await adapter.send(envelope, { kind: 'command.result', text: '🔄 重启监控已接管,服务正在重启;完成后会另行通知。' });
|
|
3278
3317
|
// 等待消息发送完成后再延迟 kill
|
|
3279
3318
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
3280
3319
|
}
|
|
@@ -3318,6 +3357,13 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3318
3357
|
reason: restarted.code,
|
|
3319
3358
|
};
|
|
3320
3359
|
}
|
|
3360
|
+
if (typeof restarted === 'object' && restarted.code === 'RESTART_PENDING_CONFIRMATION') {
|
|
3361
|
+
return {
|
|
3362
|
+
kind: 'command.error',
|
|
3363
|
+
text: `⚠️ 重启请求状态暂不可确认(requestId=${restarted.requestId})。监控进程仍在运行,可能继续执行;请勿立即重复发起。可稍后检查 restart.log 中该 requestId 的记录。`,
|
|
3364
|
+
reason: restarted.code,
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3321
3367
|
if (typeof restarted === 'object' && restarted.code === 'RESTART_MONITOR_DEAD') {
|
|
3322
3368
|
return {
|
|
3323
3369
|
kind: 'command.error',
|
|
@@ -3577,7 +3623,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3577
3623
|
}
|
|
3578
3624
|
// /slist — 仅显示 EvolCore 会话
|
|
3579
3625
|
const sessions = await this.sessionManager.listSessions(channel, channelId);
|
|
3580
|
-
const currentProjectSessions = sessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent &&
|
|
3626
|
+
const currentProjectSessions = sessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent && isMainSession(s));
|
|
3581
3627
|
// 从 SDK 同步会话名称(发现 CLI 改名)
|
|
3582
3628
|
try {
|
|
3583
3629
|
const sdkSessions = await this.sessionManager.listSdkSessions(session.projectPath, session.baseagent, session.selfAID);
|
|
@@ -3701,7 +3747,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3701
3747
|
if (!targetSession && /^\d+$/.test(sessionName) && session) {
|
|
3702
3748
|
const idx = parseInt(sessionName, 10);
|
|
3703
3749
|
const allSessions = await this.sessionManager.listSessions(channel, channelId);
|
|
3704
|
-
const visibleSessions = allSessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent &&
|
|
3750
|
+
const visibleSessions = allSessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent && isMainSession(s));
|
|
3705
3751
|
if (idx >= 1 && idx <= visibleSessions.length) {
|
|
3706
3752
|
targetSession = visibleSessions[idx - 1];
|
|
3707
3753
|
}
|
|
@@ -3712,7 +3758,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3712
3758
|
if (!targetSession && sessionName.length >= 8) {
|
|
3713
3759
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
3714
3760
|
}
|
|
3715
|
-
if (targetSession
|
|
3761
|
+
if (isTopicSession(targetSession)) {
|
|
3716
3762
|
return { kind: 'command.error', text: `❌ 话题会话不支持通过 /s 切换\n请在对应话题内继续对话` };
|
|
3717
3763
|
}
|
|
3718
3764
|
const canImport = policy.canImportCliSession(session?.chatType || 'private', identity.role);
|
|
@@ -3764,7 +3810,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3764
3810
|
return { kind: 'command.result', text: `当前已在会话: ${displaySessionTitle(targetSession.name, sessionName)}` };
|
|
3765
3811
|
}
|
|
3766
3812
|
// 阻止从主会话切换到话题会话
|
|
3767
|
-
if (
|
|
3813
|
+
if (isMainSession(session) && isTopicSession(targetSession)) {
|
|
3768
3814
|
return { kind: 'command.error', text: `❌ 无法从主会话切换到话题会话\n话题会话仅在对应话题内可用` };
|
|
3769
3815
|
}
|
|
3770
3816
|
await interruptPausedSessionBeforeReplacement(session);
|
|
@@ -3809,14 +3855,15 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3809
3855
|
});
|
|
3810
3856
|
}
|
|
3811
3857
|
// P1.2: 若当前是话题会话,同步更新话题索引
|
|
3812
|
-
|
|
3858
|
+
const renamedTopicThreadId = sessionTopicThreadId(session);
|
|
3859
|
+
if (success && renamedTopicThreadId) {
|
|
3813
3860
|
try {
|
|
3814
3861
|
const { ThreadService } = await import('../../core/thread/index.js');
|
|
3815
3862
|
const selfAid = session.selfAID;
|
|
3816
3863
|
const peer = session.channelId;
|
|
3817
3864
|
if (selfAid && peer) {
|
|
3818
3865
|
const svc = new ThreadService(this.sessionManager);
|
|
3819
|
-
await svc.renameThread({ selfAid, peer, threadId:
|
|
3866
|
+
await svc.renameThread({ selfAid, peer, threadId: renamedTopicThreadId, name: newName });
|
|
3820
3867
|
}
|
|
3821
3868
|
}
|
|
3822
3869
|
catch (err) {
|
|
@@ -3846,7 +3893,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3846
3893
|
if (!targetSession && /^\d+$/.test(sessionName)) {
|
|
3847
3894
|
const idx = parseInt(sessionName, 10);
|
|
3848
3895
|
const allSessions = await this.sessionManager.listSessions(channel, channelId);
|
|
3849
|
-
const visibleSessions = allSessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent &&
|
|
3896
|
+
const visibleSessions = allSessions.filter((s) => s.projectPath === session.projectPath && s.baseagent === session.baseagent && isMainSession(s));
|
|
3850
3897
|
if (idx >= 1 && idx <= visibleSessions.length) {
|
|
3851
3898
|
targetSession = visibleSessions[idx - 1];
|
|
3852
3899
|
}
|
|
@@ -3857,7 +3904,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3857
3904
|
if (!targetSession && sessionName.length >= 8) {
|
|
3858
3905
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
3859
3906
|
}
|
|
3860
|
-
if (targetSession
|
|
3907
|
+
if (isTopicSession(targetSession)) {
|
|
3861
3908
|
return { kind: 'command.error', text: `❌ 请使用话题管理删除话题会话` };
|
|
3862
3909
|
}
|
|
3863
3910
|
if (!targetSession) {
|
|
@@ -3949,12 +3996,13 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3949
3996
|
if (!['chat', 'file', 'all'].includes(mode)) {
|
|
3950
3997
|
return { kind: 'command.error', text: `❌ 无效模式 "${mode}",可选:chat | file | all` };
|
|
3951
3998
|
}
|
|
3952
|
-
|
|
3999
|
+
const rewindTopicThreadId = sessionTopicThreadId(session);
|
|
4000
|
+
if (rewindTopicThreadId) {
|
|
3953
4001
|
// 话题只开放 N>1;N=1 会清空 backend binding,仍保留原有禁止边界。
|
|
3954
4002
|
if (turnNum === 1) {
|
|
3955
4003
|
return { kind: 'command.error', text: '❌ 话题会话暂不支持 /rewind 1,仅开放 N>1' };
|
|
3956
4004
|
}
|
|
3957
|
-
const outcome = await withIdleTopicBackend(session.id,
|
|
4005
|
+
const outcome = await withIdleTopicBackend(session.id, rewindTopicThreadId, async (current) => {
|
|
3958
4006
|
if (!current.baseagent) {
|
|
3959
4007
|
return '❌ 当前会话缺少 baseagent 绑定,拒绝隐式切换';
|
|
3960
4008
|
}
|
|
@@ -4020,7 +4068,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
4020
4068
|
// A topic backend binding may only be changed by an explicit boundary
|
|
4021
4069
|
// rotation/activation. Repair clears runner-local state, but must not
|
|
4022
4070
|
// turn a legacy empty callback into an authoritative binding mutation.
|
|
4023
|
-
if (
|
|
4071
|
+
if (isMainSession(repairSession)) {
|
|
4024
4072
|
await this.sessionManager.updateAgentSessionIdBySessionId(repairSession.id, '');
|
|
4025
4073
|
}
|
|
4026
4074
|
repairAgent.updateSessionId(repairSession.id, '');
|
|
@@ -4071,7 +4119,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
4071
4119
|
// /trigger 命令
|
|
4072
4120
|
if (normalizedContent === '/memory' || normalizedContent.startsWith('/memory ')) {
|
|
4073
4121
|
const memoryAgent = this.agentRegistry?.get?.(authSubject.selfAid ?? '');
|
|
4074
|
-
const memoryOwnerAid =
|
|
4122
|
+
const memoryOwnerAid = ownerResolver.primaryOwner(authSubject.selfAid ?? '');
|
|
4075
4123
|
const memorySession = await getExistingSessionForCommand();
|
|
4076
4124
|
return await handleMemorySlash({
|
|
4077
4125
|
content: normalizedContent,
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* 不支持跨关系浏览以外的越权:非本关系的话题需要显式给出 peer,且按同一套角色判定。
|
|
14
14
|
*/
|
|
15
15
|
import { ThreadService, ThreadServiceError } from '../thread/thread-service.js';
|
|
16
|
+
import { isTopicThreadId } from '../thread/topic-id.js';
|
|
17
|
+
import { parseTimeArg, TIME_ARG_FORMATS } from '../../cli/cli-argv.js';
|
|
16
18
|
const TOPIC_STATUSES = ['pending', 'in_progress', 'completed', 'archived'];
|
|
17
19
|
const USAGE = [
|
|
18
20
|
'用法(更名与总结默认作用于当前话题,无需手写 ref):',
|
|
@@ -139,7 +141,7 @@ function formatThreadLine(t, currentThreadId) {
|
|
|
139
141
|
formatTime(t.lastActiveAt),
|
|
140
142
|
t.lastMsgSnippet ? truncate(t.lastMsgSnippet, 30) : '',
|
|
141
143
|
].filter(Boolean);
|
|
142
|
-
return ` ${bits.join(' · ')}${mark}\n ref: ${t.
|
|
144
|
+
return ` ${bits.join(' · ')}${mark}\n ref: ${t.threadId} (${t.msgTotal ?? 0} 条)`;
|
|
143
145
|
}
|
|
144
146
|
function formatThreadList(threads, opts, currentThreadId) {
|
|
145
147
|
if (threads.length === 0)
|
|
@@ -241,15 +243,16 @@ export async function handleTopicSlash(ctx) {
|
|
|
241
243
|
const target = resolveRef(ref, ctx, service);
|
|
242
244
|
if (isPayload(target))
|
|
243
245
|
return target;
|
|
244
|
-
if (!target.threadId)
|
|
245
|
-
return error('
|
|
246
|
+
if (!isTopicThreadId(target.threadId)) {
|
|
247
|
+
return error('当前是主话题;/thread show 只显示话题元数据,请指定 threadId');
|
|
248
|
+
}
|
|
246
249
|
const t = service.findInIndex(ctx.selfAid, target.peer, target.threadId);
|
|
247
250
|
if (!t)
|
|
248
251
|
return error(`话题不存在: ${target.peer}/${target.threadId}`);
|
|
249
252
|
const summary = service.showSummary(ctx.selfAid, t.sessionId);
|
|
250
253
|
const lines = [
|
|
251
254
|
`话题: ${t.name}`,
|
|
252
|
-
`ref: ${t.
|
|
255
|
+
`ref: ${t.threadId}`,
|
|
253
256
|
`session: ${t.sessionId}`,
|
|
254
257
|
`对端: ${t.peer}(${t.peerKind === 'group' ? '群' : '私聊'})`,
|
|
255
258
|
`状态: ${t.topicStatus}`,
|
|
@@ -269,11 +272,19 @@ export async function handleTopicSlash(ctx) {
|
|
|
269
272
|
const limit = typeof flags.limit === 'string' ? parseInt(flags.limit, 10) : 20;
|
|
270
273
|
if (!Number.isFinite(limit) || limit <= 0)
|
|
271
274
|
return error('--limit 必须是正整数');
|
|
275
|
+
const after = typeof flags.after === 'string' ? parseTimeArg(flags.after) : undefined;
|
|
276
|
+
if (typeof flags.after === 'string' && after === undefined)
|
|
277
|
+
return error(`--after 不是有效时间(支持 ${TIME_ARG_FORMATS})`);
|
|
278
|
+
const before = typeof flags.before === 'string' ? parseTimeArg(flags.before) : undefined;
|
|
279
|
+
if (typeof flags.before === 'string' && before === undefined)
|
|
280
|
+
return error(`--before 不是有效时间(支持 ${TIME_ARG_FORMATS})`);
|
|
272
281
|
const h = await service.historyThread({
|
|
273
282
|
selfAid: ctx.selfAid,
|
|
274
283
|
peer: target.peer,
|
|
275
284
|
threadId: target.threadId,
|
|
276
285
|
limit,
|
|
286
|
+
after,
|
|
287
|
+
before,
|
|
277
288
|
});
|
|
278
289
|
return result(formatHistory(h.messages, { total: h.total, peer: h.peer, threadId: h.threadId }));
|
|
279
290
|
}
|
|
@@ -307,7 +318,7 @@ export async function handleTopicSlash(ctx) {
|
|
|
307
318
|
...(typeof flags.label === 'string' ? { label: flags.label } : {}),
|
|
308
319
|
...(status ? { topicStatus: status } : {}),
|
|
309
320
|
});
|
|
310
|
-
return result(`✅ 已创建话题: ${created.name}\nref: ${created.
|
|
321
|
+
return result(`✅ 已创建话题: ${created.name}\nref: ${created.threadId}`);
|
|
311
322
|
}
|
|
312
323
|
if (sub === 'rename') {
|
|
313
324
|
const ref = refSource(positional, flags);
|
|
@@ -12,6 +12,7 @@ import { removeAll } from '../utils/instance-registry.js';
|
|
|
12
12
|
import { controlReplyContext } from './message/outbound.js';
|
|
13
13
|
import { TriggerDefinitionManager } from '../trigger/manager.js';
|
|
14
14
|
import { seedUpgradeCheckTrigger } from '../trigger/system-definitions.js';
|
|
15
|
+
import { ownerResolver } from './ownership/owner-resolver.js';
|
|
15
16
|
export function detectAvailableBaseagents() {
|
|
16
17
|
// 与 init/ECWeb 的探测保持同一判据;claude 在这里也走内置二进制,
|
|
17
18
|
// 不再只看 PATH——否则 daemon 会对外声明一个实际跑不起来的 baseagent。
|
|
@@ -36,6 +37,11 @@ export async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
36
37
|
getUptimeSeconds: () => Math.floor(process.uptime()),
|
|
37
38
|
});
|
|
38
39
|
bindService.startCleanup();
|
|
40
|
+
ownerResolver.setDaemonAid(daemonCfg.aid);
|
|
41
|
+
ownerResolver.setConfiguredOwners(daemonCfg.aid, [
|
|
42
|
+
daemonCfg.owner,
|
|
43
|
+
...(daemonCfg.owners ?? []),
|
|
44
|
+
].filter((owner) => typeof owner === 'string'));
|
|
39
45
|
let controlChannel;
|
|
40
46
|
let shutdown;
|
|
41
47
|
let pendingShutdownReason;
|
|
@@ -102,7 +108,7 @@ export async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
102
108
|
}));
|
|
103
109
|
ipcServer.setControlToken(controlToken);
|
|
104
110
|
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
105
|
-
ipcServer.setRemoteAccessOwnerProvider(() => daemonCfg.
|
|
111
|
+
ipcServer.setRemoteAccessOwnerProvider(() => ownerResolver.primaryOwner(daemonCfg.aid));
|
|
106
112
|
ipcServer.setShutdownExecutor(async (reason) => {
|
|
107
113
|
if (!shutdown) {
|
|
108
114
|
pendingShutdownReason = reason;
|
|
@@ -10,6 +10,8 @@ import { isProcessLevelOwner } from './command/command-handler.js';
|
|
|
10
10
|
import { onShutdown } from '../utils/cross-platform.js';
|
|
11
11
|
import { logger } from '../utils/logger.js';
|
|
12
12
|
import { resolvePaths } from '../paths.js';
|
|
13
|
+
import { ownerResolver, OWNERSHIP_AUTHORIZATION_MAX_AGE_MS, OWNERSHIP_CACHE_MAX_AGE_MS } from './ownership/owner-resolver.js';
|
|
14
|
+
import { subscribeUpgradeJob } from '../utils/upgrade-jobs.js';
|
|
13
15
|
const controlMenuDeduper = new MenuRequestDeduper();
|
|
14
16
|
export function startDaemonControlAid({ daemonCfg, cmdHandler, bindService, diagService, getProcessOwners, }) {
|
|
15
17
|
if (!daemonCfg.aid)
|
|
@@ -36,6 +38,11 @@ export function startDaemonControlAid({ daemonCfg, cmdHandler, bindService, diag
|
|
|
36
38
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
37
39
|
});
|
|
38
40
|
controlChannel.onMessage(async (opts) => {
|
|
41
|
+
const text = (opts.content || '').trim();
|
|
42
|
+
await ownerResolver.refresh(daemonCfg.aid, {
|
|
43
|
+
maxAgeMs: /^\/(?:pair|diag)(?:\s|$)/i.test(text)
|
|
44
|
+
? OWNERSHIP_AUTHORIZATION_MAX_AGE_MS : OWNERSHIP_CACHE_MAX_AGE_MS,
|
|
45
|
+
});
|
|
39
46
|
let activeMenuRequest;
|
|
40
47
|
let menuCompletionLogged = false;
|
|
41
48
|
const menuFlowContext = {
|
|
@@ -50,7 +57,6 @@ export function startDaemonControlAid({ daemonCfg, cmdHandler, bindService, diag
|
|
|
50
57
|
};
|
|
51
58
|
try {
|
|
52
59
|
const menuTiming = normalizeMenuResponseTiming(opts);
|
|
53
|
-
const text = (opts.content || '').trim();
|
|
54
60
|
let parsed;
|
|
55
61
|
try {
|
|
56
62
|
parsed = JSON.parse(text);
|
|
@@ -173,6 +179,18 @@ export function startDaemonControlAid({ daemonCfg, cmdHandler, bindService, diag
|
|
|
173
179
|
? menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, { code: 'CONFLICT', message: 'Request ID was already used with a different payload' })
|
|
174
180
|
: deduped.value;
|
|
175
181
|
await sendControlMenuResponse(response);
|
|
182
|
+
const upgradeData = 'data' in response ? response.data : undefined;
|
|
183
|
+
if (parsed?.type === 'menu.action' && parsed.action === 'upgrade' && (parsed.name === 'system' || parsed.cmd === '/system') && upgradeData?.jobId) {
|
|
184
|
+
const jobId = upgradeData.jobId;
|
|
185
|
+
subscribeUpgradeJob(jobId, async (event) => {
|
|
186
|
+
const permission = await cmdHandler.execMenuForControl({ type: 'menu.action', id: parsed.id, name: 'system', action: 'upgrade', args: { mode: 'status', jobId } }, opts.peerId, {
|
|
187
|
+
encrypted: opts.encrypted, protectedHeaders: opts.protectedHeaders,
|
|
188
|
+
});
|
|
189
|
+
if (permission.error)
|
|
190
|
+
throw new Error('Upgrade event authorization expired');
|
|
191
|
+
await controlChannel.sendStructuredOrThrow(opts.channelId, event, menuResponseContext());
|
|
192
|
+
}, [opts.channelId, opts.peerId, opts.peerDeviceId || '', jobId].join('\u001f'));
|
|
193
|
+
}
|
|
176
194
|
return;
|
|
177
195
|
}
|
|
178
196
|
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取远程控制配对码)、/diag <问题描述>、/diag stop', controlReplyContext(opts));
|