evolcore 0.0.35 → 0.0.37
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 +42 -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 +177 -85
- 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 +115 -36
- 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
|
@@ -10,6 +10,23 @@ import { checkDangerousCommand } from './tool-policy.js';
|
|
|
10
10
|
import { resolveProtectedCandidate } from './protected-paths.js';
|
|
11
11
|
import { auditPermissionDecision, auditPermissionLifecycle } from '../auth/authorization-audit.js';
|
|
12
12
|
import { ConfigTarget, onConfigWrite } from '../../config/config-manager.js';
|
|
13
|
+
import { fileChangeApprovalScope, permissionApprovalScope, filesystemScopeContains, } from './filesystem-approval-scope.js';
|
|
14
|
+
export function parsePermissionApprovalDecision(value) {
|
|
15
|
+
if (value === 'approve_once')
|
|
16
|
+
return 'allow';
|
|
17
|
+
if (value === 'approve_session_30m')
|
|
18
|
+
return 'always';
|
|
19
|
+
if (value === 'approve_file_60m')
|
|
20
|
+
return 'file_60m';
|
|
21
|
+
if (value === 'approve_directory_60m')
|
|
22
|
+
return 'directory_60m';
|
|
23
|
+
if (value === 'approve_all_directories_60m')
|
|
24
|
+
return 'all_directories_60m';
|
|
25
|
+
if (['allow', 'always', 'deny', 'file_60m', 'directory_60m', 'all_directories_60m'].includes(value)) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
13
30
|
export async function requestDangerousCommandPermission(gateway, sessionId, toolName, input, sendPrompt, context, grantScope = 'default', mode = 'request') {
|
|
14
31
|
const dangerCheck = checkDangerousCommand(toolName, input);
|
|
15
32
|
if (!dangerCheck.isDangerous) {
|
|
@@ -40,6 +57,7 @@ export async function requestDangerousCommandPermission(gateway, sessionId, tool
|
|
|
40
57
|
return { matched: true, decision };
|
|
41
58
|
}
|
|
42
59
|
const SESSION_GRANT_TTL_MS = 30 * 60 * 1000;
|
|
60
|
+
const FILESYSTEM_GRANT_TTL_MS = 60 * 60 * 1000;
|
|
43
61
|
const LOCAL_APPROVAL_TTL_MS = 20 * 60 * 1000;
|
|
44
62
|
const MANAGEMENT_APPROVAL_REVALIDATION_INTERVAL_MS = 5_000;
|
|
45
63
|
const APPROVAL_DETAIL_LIMIT = 800;
|
|
@@ -98,11 +116,19 @@ function collectFileChangeGrantPaths(value, output) {
|
|
|
98
116
|
}
|
|
99
117
|
function permissionGrantMatch(toolName, input, options) {
|
|
100
118
|
const fileChangeCwd = options?.fileChangeCwd?.trim();
|
|
119
|
+
const fileOnly = !['permissions', 'additionalPermissions', 'networkApprovalContext', 'proposedNetworkPolicyAmendments']
|
|
120
|
+
.some(key => input[key] !== undefined);
|
|
121
|
+
const rawPaths = [];
|
|
122
|
+
if (toolName === 'FileChange')
|
|
123
|
+
collectFileChangeGrantPaths(input.fileChanges, rawPaths);
|
|
124
|
+
const filesystem = toolName === 'FileChange' && fileChangeCwd && fileOnly
|
|
125
|
+
? fileChangeApprovalScope(rawPaths, fileChangeCwd, input.grantRoot)
|
|
126
|
+
: toolName === 'PermissionGrant' && options?.filesystemCwd && options.workspacePath
|
|
127
|
+
? permissionApprovalScope(input, options.filesystemCwd, options.workspacePath)
|
|
128
|
+
: undefined;
|
|
101
129
|
// grantRoot is already a broad filesystem capability. Keep it exact rather
|
|
102
130
|
// than silently widening it through the concrete-file convenience scope.
|
|
103
|
-
if (toolName === 'FileChange' && fileChangeCwd && !input.grantRoot) {
|
|
104
|
-
const rawPaths = [];
|
|
105
|
-
collectFileChangeGrantPaths(input.fileChanges, rawPaths);
|
|
131
|
+
if (toolName === 'FileChange' && fileChangeCwd && fileOnly && !input.grantRoot) {
|
|
106
132
|
try {
|
|
107
133
|
const canonicalPaths = [...new Set(rawPaths.map(candidate => {
|
|
108
134
|
const canonical = resolveProtectedCandidate(candidate, fileChangeCwd);
|
|
@@ -115,6 +141,7 @@ function permissionGrantMatch(toolName, input, options) {
|
|
|
115
141
|
paths: canonicalPaths,
|
|
116
142
|
}),
|
|
117
143
|
scope: 'file-change-paths',
|
|
144
|
+
filesystem,
|
|
118
145
|
};
|
|
119
146
|
}
|
|
120
147
|
}
|
|
@@ -126,8 +153,73 @@ function permissionGrantMatch(toolName, input, options) {
|
|
|
126
153
|
return {
|
|
127
154
|
inputFingerprint: permissionInputFingerprint(input),
|
|
128
155
|
scope: 'exact-operation',
|
|
156
|
+
filesystem,
|
|
129
157
|
};
|
|
130
158
|
}
|
|
159
|
+
function permissionGrantContextKey(context, approverPolicy) {
|
|
160
|
+
return permissionInputFingerprint({
|
|
161
|
+
selfAid: context.selfAid ?? context.approvalRouting?.selfAid,
|
|
162
|
+
userId: context.userId,
|
|
163
|
+
principalId: context.actor?.principalId,
|
|
164
|
+
channelKey: context.actor?.operatorChannelKey ?? context.channel,
|
|
165
|
+
role: context.role,
|
|
166
|
+
permissionMode: context.permissionMode,
|
|
167
|
+
originPrincipalId: context.approvalRouting?.originPrincipalId,
|
|
168
|
+
originAuthRevision: context.approvalRouting?.originAuthRevision,
|
|
169
|
+
approverPolicy,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function approvalButtons(match, route, context) {
|
|
173
|
+
const handoff = route.kind === 'handoff';
|
|
174
|
+
const buttons = [
|
|
175
|
+
{ key: handoff ? 'approve_once' : 'allow', label: handoff ? '批准本次' : '✓ 允许本次', style: 'primary' },
|
|
176
|
+
];
|
|
177
|
+
const offeredDecisions = ['allow', 'deny'];
|
|
178
|
+
if (match.scope === 'file-change-paths' && match.filesystem) {
|
|
179
|
+
buttons.push({ key: 'approve_file_60m', label: '同文件 60 分钟', style: 'default' });
|
|
180
|
+
offeredDecisions.push('file_60m');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
buttons.push({ key: handoff ? 'approve_session_30m' : 'always', label: handoff ? '本会话 30 分钟' : '同操作 30 分钟', style: 'default' });
|
|
184
|
+
offeredDecisions.push('always');
|
|
185
|
+
}
|
|
186
|
+
const scope = match.filesystem;
|
|
187
|
+
if (scope?.directory) {
|
|
188
|
+
buttons.push({ key: 'approve_directory_60m', label: '当前目录 60 分钟', style: 'default' });
|
|
189
|
+
offeredDecisions.push('directory_60m');
|
|
190
|
+
}
|
|
191
|
+
// A broad directory grant must be issued by an actual owner, including an
|
|
192
|
+
// owner answering a manager handoff. Never offer it to an admin/requester.
|
|
193
|
+
const agentAndHostOwner = !!route.approverPrincipalId
|
|
194
|
+
&& context.approvalRouting?.owners?.includes(route.approverPrincipalId) === true
|
|
195
|
+
&& context.approvalRouting.hostOwners?.includes(route.approverPrincipalId) === true;
|
|
196
|
+
const owner = !!route.approverPrincipalId && !!route.approverChannelKey
|
|
197
|
+
&& !!context.approvalRouting?.reauthorizeApproval
|
|
198
|
+
&& agentAndHostOwner;
|
|
199
|
+
if (scope && owner) {
|
|
200
|
+
buttons.push({ key: 'approve_all_directories_60m', label: '所有普通目录 60 分钟', style: 'default' });
|
|
201
|
+
offeredDecisions.push('all_directories_60m');
|
|
202
|
+
}
|
|
203
|
+
// Without matching Agent and host owners, keep the filesystem choices to
|
|
204
|
+
// the one-shot, per-file, and deny actions. Recursive directory grants are
|
|
205
|
+
// deliberately withheld from an owner that is not also the host Owner.
|
|
206
|
+
if (scope && !owner) {
|
|
207
|
+
const filtered = buttons.filter(button => button.key !== 'approve_directory_60m');
|
|
208
|
+
buttons.length = 0;
|
|
209
|
+
buttons.push(...filtered);
|
|
210
|
+
const filteredDecisions = offeredDecisions.filter(decision => decision !== 'directory_60m');
|
|
211
|
+
offeredDecisions.length = 0;
|
|
212
|
+
offeredDecisions.push(...filteredDecisions);
|
|
213
|
+
}
|
|
214
|
+
buttons.push({ key: 'deny', label: '✗ 拒绝', style: 'danger' });
|
|
215
|
+
const detail = scope ? [
|
|
216
|
+
...(scope.directory ? [`目录授权范围(含子目录):${scope.directory}`] : []),
|
|
217
|
+
...(owner ? ['“所有普通目录 60 分钟”覆盖当前会话内所有非受保护目录。'] : []),
|
|
218
|
+
`临时文件权限:${scope.access === 'write' ? '读写' : '只读'};仅当前会话和权限上下文有效。`,
|
|
219
|
+
'受保护路径和其他会话临时目录仍按现有策略校验;不授权 Shell 命令、网络或系统管理权限。',
|
|
220
|
+
].join('\n') : '';
|
|
221
|
+
return { buttons, offeredDecisions, detail };
|
|
222
|
+
}
|
|
131
223
|
function truncateApprovalDetail(value) {
|
|
132
224
|
const trimmed = value.trim();
|
|
133
225
|
return trimmed.length > APPROVAL_DETAIL_LIMIT
|
|
@@ -372,20 +464,60 @@ export class PermissionGateway {
|
|
|
372
464
|
}
|
|
373
465
|
hasTemporaryGrant(sessionId, toolName, toolInput, grantScope = 'default', matchOptions) {
|
|
374
466
|
const match = permissionGrantMatch(toolName, toolInput, matchOptions);
|
|
375
|
-
return !!this.getTemporaryGrant(sessionId, toolName, match
|
|
467
|
+
return !!this.getTemporaryGrant(sessionId, toolName, match, grantScope);
|
|
376
468
|
}
|
|
377
|
-
getTemporaryGrant(sessionId, toolName,
|
|
469
|
+
getTemporaryGrant(sessionId, toolName, match, grantScope = 'default', grantContextKey) {
|
|
378
470
|
const now = Date.now();
|
|
379
471
|
this.pruneTemporaryGrants(now);
|
|
380
|
-
const key
|
|
381
|
-
|
|
472
|
+
for (const [key, grant] of this.temporaryGrants) {
|
|
473
|
+
if (grant.sessionId !== sessionId || grant.toolName !== toolName || grant.grantScope !== grantScope
|
|
474
|
+
|| (grantContextKey !== undefined && grant.grantContextKey !== grantContextKey))
|
|
475
|
+
continue;
|
|
476
|
+
const target = grant.target;
|
|
477
|
+
const matches = target.kind === 'operation'
|
|
478
|
+
? target.inputFingerprint === match.inputFingerprint
|
|
479
|
+
: !!match.filesystem && filesystemScopeContains(target, match.filesystem);
|
|
480
|
+
if (!matches)
|
|
481
|
+
continue;
|
|
482
|
+
const authorization = this.authorizePendingResponse(grant.authorization, grant.authorization.approverOperatorId, grant.authorization.approverChannelKey);
|
|
483
|
+
if (authorization.status !== 'authorized') {
|
|
484
|
+
this.temporaryGrants.delete(key);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
return grant;
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
382
490
|
}
|
|
383
|
-
addTemporaryGrant(pending, causation) {
|
|
491
|
+
addTemporaryGrant(pending, decision, causation) {
|
|
384
492
|
const now = Date.now();
|
|
385
493
|
this.pruneTemporaryGrants(now);
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
|
|
494
|
+
const filesystem = pending.grantMatch.filesystem;
|
|
495
|
+
const target = decision === 'directory_60m' && filesystem?.directory
|
|
496
|
+
? { kind: 'directory', directory: filesystem.directory, access: filesystem.access }
|
|
497
|
+
: decision === 'all_directories_60m' && filesystem
|
|
498
|
+
? { kind: 'all-directories', access: filesystem.access }
|
|
499
|
+
: { kind: 'operation', inputFingerprint: pending.inputFingerprint };
|
|
500
|
+
const expiresAt = now + (decision === 'always' ? SESSION_GRANT_TTL_MS : FILESYSTEM_GRANT_TTL_MS);
|
|
501
|
+
const fingerprint = target.kind === 'operation'
|
|
502
|
+
? target.inputFingerprint : permissionInputFingerprint(target);
|
|
503
|
+
const key = this.temporaryGrantKey(pending.sessionId, pending.toolName, fingerprint, pending.grantScope);
|
|
504
|
+
const authorization = {
|
|
505
|
+
approverPolicy: decision === 'all_directories_60m' ? 'agent_owner' : pending.approverPolicy,
|
|
506
|
+
requiredApproverRole: decision === 'all_directories_60m' ? 'owner' : pending.requiredApproverRole,
|
|
507
|
+
approverOperatorId: pending.approverOperatorId,
|
|
508
|
+
approverChannelKey: pending.approverChannelKey,
|
|
509
|
+
approverPrincipalId: pending.approverPrincipalId,
|
|
510
|
+
selfAid: pending.selfAid,
|
|
511
|
+
originPrincipalId: pending.originPrincipalId,
|
|
512
|
+
originAuthRevision: pending.originAuthRevision,
|
|
513
|
+
reauthorizeApproval: pending.reauthorizeApproval,
|
|
514
|
+
requireHostOwner: decision === 'all_directories_60m',
|
|
515
|
+
};
|
|
516
|
+
this.temporaryGrants.set(key, {
|
|
517
|
+
sessionId: pending.sessionId, toolName: pending.toolName, grantScope: pending.grantScope,
|
|
518
|
+
grantContextKey: pending.grantContextKey, authorization, target,
|
|
519
|
+
expiresAt, causation: normalizeCausation(causation),
|
|
520
|
+
});
|
|
389
521
|
return expiresAt;
|
|
390
522
|
}
|
|
391
523
|
authorizePendingResponse(pending, operatorId, channelKey) {
|
|
@@ -455,6 +587,9 @@ export class PermissionGateway {
|
|
|
455
587
|
reason: stillAssigned ? 'approver_role_changed' : 'approver_revoked',
|
|
456
588
|
};
|
|
457
589
|
}
|
|
590
|
+
if (pending.requireHostOwner && !result.hostOwners?.includes(actor.principalId)) {
|
|
591
|
+
return { status: 'revoked', reason: 'approver_not_host_owner' };
|
|
592
|
+
}
|
|
458
593
|
if (pending.originPrincipalId !== undefined
|
|
459
594
|
&& result.originPrincipalId !== pending.originPrincipalId) {
|
|
460
595
|
return { status: 'revoked', reason: 'origin_authorization_changed' };
|
|
@@ -465,6 +600,20 @@ export class PermissionGateway {
|
|
|
465
600
|
}
|
|
466
601
|
return { status: 'authorized' };
|
|
467
602
|
}
|
|
603
|
+
pendingDecision(pending, action) {
|
|
604
|
+
const decision = parsePermissionApprovalDecision(action);
|
|
605
|
+
if (!decision)
|
|
606
|
+
return undefined;
|
|
607
|
+
// Old /perm always links keep their historical 30-minute exact-file grant.
|
|
608
|
+
if (decision === 'always' && pending.offeredDecisions.includes('file_60m'))
|
|
609
|
+
return decision;
|
|
610
|
+
return pending.offeredDecisions.includes(decision) ? decision : undefined;
|
|
611
|
+
}
|
|
612
|
+
decisionAuthorization(pending, decision) {
|
|
613
|
+
return decision === 'all_directories_60m'
|
|
614
|
+
? { ...pending, requiredApproverRole: 'owner', approverPolicy: 'agent_owner', requireHostOwner: true }
|
|
615
|
+
: pending;
|
|
616
|
+
}
|
|
468
617
|
/** Legacy diagnostics alias. Grants are no longer tool-wide or permanent. */
|
|
469
618
|
getAlwaysAllowList() {
|
|
470
619
|
this.pruneTemporaryGrants(Date.now());
|
|
@@ -474,7 +623,10 @@ export class PermissionGateway {
|
|
|
474
623
|
const pending = this.crossSessionPending.get(requestId);
|
|
475
624
|
if (!pending || pending.sessionId !== sessionId)
|
|
476
625
|
return false;
|
|
477
|
-
const
|
|
626
|
+
const selectedDecision = this.pendingDecision(pending, action);
|
|
627
|
+
if (!selectedDecision)
|
|
628
|
+
return false;
|
|
629
|
+
const authorization = this.authorizePendingResponse(this.decisionAuthorization(pending, selectedDecision), operatorId, channelKey);
|
|
478
630
|
if (authorization.status === 'identity_mismatch')
|
|
479
631
|
return false;
|
|
480
632
|
if (authorization.status === 'revoked') {
|
|
@@ -486,12 +638,12 @@ export class PermissionGateway {
|
|
|
486
638
|
pending.interactionRouter?.cancel(requestId);
|
|
487
639
|
this.crossSessionPending.delete(requestId);
|
|
488
640
|
this.syncManagementRevalidationTimer();
|
|
489
|
-
const sessionGrant =
|
|
490
|
-
const approved =
|
|
641
|
+
const sessionGrant = selectedDecision !== 'allow' && selectedDecision !== 'deny';
|
|
642
|
+
const approved = selectedDecision !== 'deny';
|
|
491
643
|
const decision = approved ? 'allow' : 'deny';
|
|
492
644
|
const decisionCausation = deriveCausation(pending.causation);
|
|
493
645
|
if (sessionGrant)
|
|
494
|
-
this.addTemporaryGrant(pending, decisionCausation);
|
|
646
|
+
this.addTemporaryGrant(pending, selectedDecision, decisionCausation);
|
|
495
647
|
pending.resolve(decision);
|
|
496
648
|
auditPermissionLifecycle({
|
|
497
649
|
phase: 'response',
|
|
@@ -695,6 +847,7 @@ export class PermissionGateway {
|
|
|
695
847
|
: undefined;
|
|
696
848
|
const approvalDeadline = new Date(expiresAt).toLocaleString('zh-CN', { hour12: false });
|
|
697
849
|
const target = formatApprovalTarget(toolName, toolInput);
|
|
850
|
+
const choices = approvalButtons(grantMatch, route, context);
|
|
698
851
|
const body = [
|
|
699
852
|
'**申请信息**',
|
|
700
853
|
'',
|
|
@@ -715,6 +868,7 @@ export class PermissionGateway {
|
|
|
715
868
|
'',
|
|
716
869
|
'- **风险:中** · 跨会话临时授权,请核对目标和参数',
|
|
717
870
|
`- **审批截止**:${escapeApprovalMarkdown(approvalDeadline)}(约 ${Math.round(ttlMs / 60000)} 分钟)`,
|
|
871
|
+
...(choices.detail ? ['', approvalCodeBlock(choices.detail)] : []),
|
|
718
872
|
].join('\n');
|
|
719
873
|
const interaction = {
|
|
720
874
|
type: 'interaction',
|
|
@@ -724,15 +878,7 @@ export class PermissionGateway {
|
|
|
724
878
|
title: '临时授权申请',
|
|
725
879
|
body,
|
|
726
880
|
bodyFormat: 'markdown',
|
|
727
|
-
buttons:
|
|
728
|
-
{ key: 'approve_once', label: '批准本次', style: 'primary' },
|
|
729
|
-
{
|
|
730
|
-
key: 'approve_session_30m',
|
|
731
|
-
label: grantMatch.scope === 'file-change-paths' ? '同文件 30 分钟' : '本会话 30 分钟',
|
|
732
|
-
style: 'default',
|
|
733
|
-
},
|
|
734
|
-
{ key: 'deny', label: '拒绝', style: 'danger' },
|
|
735
|
-
],
|
|
881
|
+
buttons: choices.buttons,
|
|
736
882
|
},
|
|
737
883
|
channelId: route.approverOperatorId,
|
|
738
884
|
sessionId,
|
|
@@ -740,11 +886,9 @@ export class PermissionGateway {
|
|
|
740
886
|
expiresAt,
|
|
741
887
|
fallback: {
|
|
742
888
|
command: 'perm',
|
|
743
|
-
buttonArgMap:
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
deny: `${requestId} deny`,
|
|
747
|
-
},
|
|
889
|
+
buttonArgMap: Object.fromEntries(choices.buttons.map(button => [
|
|
890
|
+
button.key, `${requestId} ${parsePermissionApprovalDecision(button.key)}`,
|
|
891
|
+
])),
|
|
748
892
|
},
|
|
749
893
|
};
|
|
750
894
|
// Register before delivery. Some adapters can surface a user response as
|
|
@@ -760,6 +904,9 @@ export class PermissionGateway {
|
|
|
760
904
|
resolve,
|
|
761
905
|
inputFingerprint: grantMatch.inputFingerprint,
|
|
762
906
|
grantScope,
|
|
907
|
+
grantMatch,
|
|
908
|
+
grantContextKey: permissionGrantContextKey(context, challenge.approverPolicy),
|
|
909
|
+
offeredDecisions: choices.offeredDecisions,
|
|
763
910
|
approverPolicy: challenge.approverPolicy,
|
|
764
911
|
approvalRouteKind: route.kind,
|
|
765
912
|
requiredApproverRole: requiredApproverRole(challenge.approverPolicy),
|
|
@@ -999,7 +1146,8 @@ export class PermissionGateway {
|
|
|
999
1146
|
return 'deny';
|
|
1000
1147
|
}
|
|
1001
1148
|
const grantMatch = permissionGrantMatch(toolName, toolInput, matchOptions);
|
|
1002
|
-
const
|
|
1149
|
+
const grantContextKey = permissionGrantContextKey(context, effectiveApproverPolicy);
|
|
1150
|
+
const temporaryGrant = this.getTemporaryGrant(sessionId, toolName, grantMatch, grantScope, grantContextKey);
|
|
1003
1151
|
if (temporaryGrant) {
|
|
1004
1152
|
const consumeCausation = deriveCausation(normalizeCausation(temporaryGrant.causation)
|
|
1005
1153
|
?? normalizeCausation(context?.causation)
|
|
@@ -1007,7 +1155,7 @@ export class PermissionGateway {
|
|
|
1007
1155
|
recordCausationSpan(consumeCausation, 'permission.consume', {
|
|
1008
1156
|
status: 'completed',
|
|
1009
1157
|
refs: { taskId: context?.taskId, sessionId },
|
|
1010
|
-
reason:
|
|
1158
|
+
reason: `temporary_grant:${temporaryGrant.target.kind}:expires=${temporaryGrant.expiresAt}`,
|
|
1011
1159
|
});
|
|
1012
1160
|
return 'allow';
|
|
1013
1161
|
}
|
|
@@ -1107,6 +1255,7 @@ export class PermissionGateway {
|
|
|
1107
1255
|
if (route.kind === 'handoff') {
|
|
1108
1256
|
return this.requestCrossSessionPermission(sessionId, challenge, route, sendPrompt, context, grantScope, requestCausation, grantMatch);
|
|
1109
1257
|
}
|
|
1258
|
+
const choices = approvalButtons(grantMatch, route, context);
|
|
1110
1259
|
// 构造 ActionInteraction
|
|
1111
1260
|
const interaction = {
|
|
1112
1261
|
type: 'interaction',
|
|
@@ -1114,16 +1263,8 @@ export class PermissionGateway {
|
|
|
1114
1263
|
kind: {
|
|
1115
1264
|
kind: 'action',
|
|
1116
1265
|
title: '🔐 权限请求',
|
|
1117
|
-
body: `工具:${toolName}\n操作:${displaySummary}${reasonLine}`,
|
|
1118
|
-
buttons:
|
|
1119
|
-
{ key: 'allow', label: '✅ 允许本次', style: 'primary' },
|
|
1120
|
-
{
|
|
1121
|
-
key: 'always',
|
|
1122
|
-
label: grantMatch.scope === 'file-change-paths' ? '⏱ 同文件 30 分钟' : '⏱ 同操作 30 分钟',
|
|
1123
|
-
style: 'default',
|
|
1124
|
-
},
|
|
1125
|
-
{ key: 'deny', label: '❌ 拒绝', style: 'danger' },
|
|
1126
|
-
],
|
|
1266
|
+
body: `工具:${toolName}\n操作:${displaySummary}${reasonLine}${choices.detail ? `\n\n${choices.detail}` : ''}`,
|
|
1267
|
+
buttons: choices.buttons,
|
|
1127
1268
|
},
|
|
1128
1269
|
channelId: context?.channelId || '',
|
|
1129
1270
|
sessionId,
|
|
@@ -1131,11 +1272,9 @@ export class PermissionGateway {
|
|
|
1131
1272
|
expiresAt: Date.now() + LOCAL_APPROVAL_TTL_MS,
|
|
1132
1273
|
fallback: {
|
|
1133
1274
|
command: 'perm',
|
|
1134
|
-
buttonArgMap:
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
deny: `${requestId} deny`,
|
|
1138
|
-
},
|
|
1275
|
+
buttonArgMap: Object.fromEntries(choices.buttons.map(button => [
|
|
1276
|
+
button.key, `${requestId} ${parsePermissionApprovalDecision(button.key)}`,
|
|
1277
|
+
])),
|
|
1139
1278
|
},
|
|
1140
1279
|
};
|
|
1141
1280
|
// Register before delivery so an immediate card/text response cannot beat
|
|
@@ -1147,6 +1286,9 @@ export class PermissionGateway {
|
|
|
1147
1286
|
toolName,
|
|
1148
1287
|
inputFingerprint: grantMatch.inputFingerprint,
|
|
1149
1288
|
grantScope,
|
|
1289
|
+
grantMatch,
|
|
1290
|
+
grantContextKey,
|
|
1291
|
+
offeredDecisions: choices.offeredDecisions,
|
|
1150
1292
|
approverPolicy: challenge.approverPolicy,
|
|
1151
1293
|
approvalRouteKind: route.kind,
|
|
1152
1294
|
requiredApproverRole: requiredApproverRole(challenge.approverPolicy),
|
|
@@ -1222,10 +1364,7 @@ export class PermissionGateway {
|
|
|
1222
1364
|
replyContext: context.replyContext,
|
|
1223
1365
|
causation: requestCausation,
|
|
1224
1366
|
});
|
|
1225
|
-
const
|
|
1226
|
-
? '同文件授权 30 分钟'
|
|
1227
|
-
: '同操作授权 30 分钟';
|
|
1228
|
-
const fallbackText = `🔐 权限请求 - ${toolName}\n${displaySummary}${reasonLine}\n回复 /perm ${requestId} allow 允许本次 / /perm ${requestId} always ${temporaryGrantLabel} / /perm ${requestId} deny 拒绝`;
|
|
1367
|
+
const fallbackText = renderActionAsText(interaction);
|
|
1229
1368
|
const result = await sendInteractionPayload(context.adapter, envelope, interaction, fallbackText, context.replyContext);
|
|
1230
1369
|
interactionSent = isInteractionSendAccepted(result);
|
|
1231
1370
|
}
|
|
@@ -1254,7 +1393,10 @@ export class PermissionGateway {
|
|
|
1254
1393
|
const pending = this.pending.get(requestId);
|
|
1255
1394
|
if (!pending || pending.sessionId !== sessionId)
|
|
1256
1395
|
return false;
|
|
1257
|
-
const
|
|
1396
|
+
const normalizedDecision = this.pendingDecision(pending, decision);
|
|
1397
|
+
if (!normalizedDecision)
|
|
1398
|
+
return false;
|
|
1399
|
+
const authorization = this.authorizePendingResponse(this.decisionAuthorization(pending, normalizedDecision), operatorId, channelKey);
|
|
1258
1400
|
if (authorization.status === 'identity_mismatch')
|
|
1259
1401
|
return false;
|
|
1260
1402
|
if (authorization.status === 'revoked') {
|
|
@@ -1262,9 +1404,6 @@ export class PermissionGateway {
|
|
|
1262
1404
|
this.failPendingPermission(sessionId, requestId, authorization.reason);
|
|
1263
1405
|
return false;
|
|
1264
1406
|
}
|
|
1265
|
-
const normalizedDecision = decision === 'allow' || decision === 'always'
|
|
1266
|
-
? decision
|
|
1267
|
-
: 'deny';
|
|
1268
1407
|
// The backend receives a one-shot allow. Future requests must return
|
|
1269
1408
|
// through this gateway so the selected match scope and TTL are checked.
|
|
1270
1409
|
this.clearPendingResources(pending);
|
|
@@ -1307,8 +1446,8 @@ export class PermissionGateway {
|
|
|
1307
1446
|
response: normalizedDecision,
|
|
1308
1447
|
});
|
|
1309
1448
|
const decisionCausation = deriveCausation(pending.causation);
|
|
1310
|
-
if (normalizedDecision
|
|
1311
|
-
this.addTemporaryGrant(pending, decisionCausation);
|
|
1449
|
+
if (normalizedDecision !== 'allow' && normalizedDecision !== 'deny') {
|
|
1450
|
+
this.addTemporaryGrant(pending, normalizedDecision, decisionCausation);
|
|
1312
1451
|
}
|
|
1313
1452
|
recordCausationSpan(decisionCausation, 'permission.decision', {
|
|
1314
1453
|
status: normalizedDecision === 'deny' ? 'failed' : 'completed',
|
|
@@ -1333,12 +1472,7 @@ export class PermissionGateway {
|
|
|
1333
1472
|
const crossSession = this.crossSessionPending.get(requestId);
|
|
1334
1473
|
if (!crossSession)
|
|
1335
1474
|
return false;
|
|
1336
|
-
|
|
1337
|
-
? 'approve_session_30m'
|
|
1338
|
-
: decision === 'allow'
|
|
1339
|
-
? 'approve_once'
|
|
1340
|
-
: 'deny';
|
|
1341
|
-
return this.resolveCrossSessionPermission(crossSession.sessionId, requestId, action, undefined, operatorId, channelKey);
|
|
1475
|
+
return this.resolveCrossSessionPermission(crossSession.sessionId, requestId, decision, undefined, operatorId, channelKey);
|
|
1342
1476
|
}
|
|
1343
1477
|
/**
|
|
1344
1478
|
* Proactively cancel management approvals after an identity or Agent config
|