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
package/dist/product.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
export const PRODUCT_DISPLAY_NAME = 'EvolCore';
|
|
8
8
|
export const MAIN_PACKAGE_NAME = 'evolcore';
|
|
9
9
|
export const MAIN_CLI_BIN = 'ec';
|
|
10
|
+
export const RESTART_REQUEST_ID_ENV = 'EVOLCORE_RESTART_REQUEST_ID';
|
|
10
11
|
export const WEB_PACKAGE_NAME = 'ec-web';
|
|
11
12
|
export const WEB_CLI_BIN = 'ec-web';
|
|
12
13
|
export const WEB_PACKAGE_LATEST = `${WEB_PACKAGE_NAME}@latest`;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* flow 是无状态单例:per-message 运行时状态存 ctx.state['proactive'],
|
|
9
9
|
* 依赖全部由调用点显式传入。
|
|
10
10
|
*/
|
|
11
|
+
import { sessionTopicThreadId } from '../../../core/thread/topic-id.js';
|
|
11
12
|
const STATE_KEY = 'proactive';
|
|
12
13
|
const SNAPSHOT_KEY = 'proactive:continuation';
|
|
13
14
|
const OWNER_KEY = 'proactive:owner';
|
|
@@ -248,7 +249,7 @@ function canRestoreSnapshot(ctx, state, snapshot) {
|
|
|
248
249
|
|| snapshot.sessionId !== ctx.session.id || snapshot.selfAid !== ctx.session.selfAID
|
|
249
250
|
|| snapshot.channel !== ctx.session.channel || snapshot.channelId !== ctx.session.channelId
|
|
250
251
|
|| snapshot.chatType !== ctx.message.chatType || snapshot.chatType !== ctx.session.chatType
|
|
251
|
-
|| snapshot.threadId !==
|
|
252
|
+
|| snapshot.threadId !== (sessionTopicThreadId(ctx.session) ?? '')
|
|
252
253
|
|| snapshot.threadId !== normalizedThread(continuation.threadId)
|
|
253
254
|
|| snapshot.baseagent !== ctx.session.baseagent || snapshot.agentSessionId !== ctx.session.agentSessionId
|
|
254
255
|
|| snapshot.firstSendRequired !== state.firstSendRequired
|
|
@@ -283,7 +284,7 @@ function saveContinuation(ctx) {
|
|
|
283
284
|
channel: ctx.session.channel,
|
|
284
285
|
channelId: ctx.session.channelId,
|
|
285
286
|
chatType: state.chatType,
|
|
286
|
-
threadId:
|
|
287
|
+
threadId: sessionTopicThreadId(ctx.session) ?? '',
|
|
287
288
|
baseagent: ctx.session.baseagent,
|
|
288
289
|
agentSessionId: ctx.session.agentSessionId,
|
|
289
290
|
turnId: terminal.turnId,
|
|
@@ -460,8 +461,14 @@ export const proactiveFlow = {
|
|
|
460
461
|
&& ctx.executionState !== 'blocked' && ctx.executionState !== 'failed'
|
|
461
462
|
&& ctx.decision !== 'deny' && ctx.executed !== false;
|
|
462
463
|
if (isSend && succeeded) {
|
|
464
|
+
const cmd = typeof ctx.toolInput?.command === 'string' ? ctx.toolInput.command : '';
|
|
465
|
+
const returnMatch = cmd.match(/--return\s+(required|none)\b/);
|
|
466
|
+
const requiresBusinessReturn = returnMatch?.[1] === 'required';
|
|
467
|
+
const businessDone = requiresBusinessReturn
|
|
468
|
+
? hasSuccessfulSendReceipt(ctx.result, ctx.isError, true)
|
|
469
|
+
: true;
|
|
463
470
|
state.roundHasIntentSuccess = true;
|
|
464
|
-
if (!state.firstToolDone)
|
|
471
|
+
if (!state.firstToolDone && businessDone)
|
|
465
472
|
state.firstToolDone = true;
|
|
466
473
|
state.violationCount = 0;
|
|
467
474
|
}
|
package/dist/stats/db.js
CHANGED
|
@@ -222,6 +222,8 @@ function _initTables(db) {
|
|
|
222
222
|
peer_key TEXT NOT NULL,
|
|
223
223
|
call_index INTEGER NOT NULL,
|
|
224
224
|
model TEXT NOT NULL,
|
|
225
|
+
source TEXT NOT NULL DEFAULT 'request',
|
|
226
|
+
provider_thread_id TEXT,
|
|
225
227
|
request_id TEXT,
|
|
226
228
|
message_id TEXT,
|
|
227
229
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -291,6 +293,12 @@ function _initTables(db) {
|
|
|
291
293
|
// 使用上轮真实 context/max/compact window,避免按模型名猜窗口。
|
|
292
294
|
try {
|
|
293
295
|
const cols = db.prepare(`PRAGMA table_info(model_calls)`).all();
|
|
296
|
+
if (!cols.some(c => c.name === 'source')) {
|
|
297
|
+
db.exec(`ALTER TABLE model_calls ADD COLUMN source TEXT NOT NULL DEFAULT 'request'`);
|
|
298
|
+
}
|
|
299
|
+
if (!cols.some(c => c.name === 'provider_thread_id')) {
|
|
300
|
+
db.exec(`ALTER TABLE model_calls ADD COLUMN provider_thread_id TEXT`);
|
|
301
|
+
}
|
|
294
302
|
if (!cols.some(c => c.name === 'context_tokens')) {
|
|
295
303
|
db.exec(`ALTER TABLE model_calls ADD COLUMN context_tokens INTEGER`);
|
|
296
304
|
}
|
package/dist/stats/writer.js
CHANGED
|
@@ -2,9 +2,31 @@
|
|
|
2
2
|
* writer.ts — 写入 usage_events 和 context_breakdown。
|
|
3
3
|
*/
|
|
4
4
|
import { getDb, resetStatsDb, startDailyRollupMaintenance } from './db.js';
|
|
5
|
+
import { normalizeUsage } from './normalizer.js';
|
|
5
6
|
import { resolvePrices } from './price-resolver.js';
|
|
6
7
|
import { logger } from '../utils/logger.js';
|
|
7
|
-
export function insertUsageEvent(evolcoreHome, event, gatewayPricing) {
|
|
8
|
+
export function insertUsageEvent(evolcoreHome, event, gatewayPricing, modelCalls) {
|
|
9
|
+
if (modelCalls?.some(call => call.source === 'subagent')) {
|
|
10
|
+
const events = modelCalls.map((call, index) => normalizeUsage({ ...call.tokenUsage,
|
|
11
|
+
cache_creation_input_tokens: call.tokenUsage.cache_creation_input_tokens ?? undefined,
|
|
12
|
+
cache_read_input_tokens: call.tokenUsage.cache_read_input_tokens ?? undefined,
|
|
13
|
+
}, {
|
|
14
|
+
...event, model: call.model || 'unknown', turns: index === 0 ? event.turns : 0,
|
|
15
|
+
duration_ms: index === 0 ? event.duration_ms : undefined,
|
|
16
|
+
context_window_pct: index === 0 ? event.context_window_pct : undefined,
|
|
17
|
+
}));
|
|
18
|
+
for (const field of ['input_tokens', 'output_tokens', 'cache_creation_tokens', 'cache_read_tokens']) {
|
|
19
|
+
if (events.reduce((sum, entry) => sum + entry[field], 0) !== event[field]) {
|
|
20
|
+
throw new Error('Model usage breakdown does not match the turn total');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const prices = events.map(entry => insertUsageEvent(evolcoreHome, entry, gatewayPricing));
|
|
24
|
+
const sum = (kind) => prices.every(price => price[kind] !== null)
|
|
25
|
+
? { usd: prices.reduce((total, price) => total + price[kind].usd, 0),
|
|
26
|
+
cny: prices.reduce((total, price) => total + price[kind].cny, 0) }
|
|
27
|
+
: null;
|
|
28
|
+
return { official: sum('official'), gateway: sum('gateway') };
|
|
29
|
+
}
|
|
8
30
|
const db = getDb(evolcoreHome);
|
|
9
31
|
// 写入时计算费用(官方价格 + 网关价格)。即使 DB 不可用也返回价格对,供调用方合入状态回执。
|
|
10
32
|
const prices = resolvePrices(evolcoreHome, event, gatewayPricing);
|
|
@@ -155,13 +177,13 @@ export function insertModelCalls(evolcoreHome, rows) {
|
|
|
155
177
|
const stmt = db.prepare(`
|
|
156
178
|
INSERT INTO model_calls
|
|
157
179
|
(ts, task_id, session_id, agent_session_id, agent_aid, peer_key, call_index, model,
|
|
158
|
-
request_id, message_id, input_tokens, output_tokens, cache_creation_tokens,
|
|
180
|
+
source, provider_thread_id, request_id, message_id, input_tokens, output_tokens, cache_creation_tokens,
|
|
159
181
|
cache_read_tokens, context_tokens, max_tokens, auto_compact_tokens, degraded)
|
|
160
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
161
183
|
`);
|
|
162
184
|
db.exec('BEGIN');
|
|
163
185
|
for (const r of rows) {
|
|
164
|
-
stmt.run(r.ts, r.task_id, r.session_id ?? null, r.agent_session_id ?? null, r.agent_aid, r.peer_key, r.call_index, r.model, r.request_id ?? null, r.message_id ?? null, r.input_tokens, r.output_tokens, r.cache_creation_tokens, r.cache_read_tokens, r.context_tokens ?? null, r.max_tokens ?? null, r.auto_compact_tokens ?? null, r.degraded);
|
|
186
|
+
stmt.run(r.ts, r.task_id, r.session_id ?? null, r.agent_session_id ?? null, r.agent_aid, r.peer_key, r.call_index, r.model, r.source ?? 'request', r.provider_thread_id ?? null, r.request_id ?? null, r.message_id ?? null, r.input_tokens, r.output_tokens, r.cache_creation_tokens, r.cache_read_tokens, r.context_tokens ?? null, r.max_tokens ?? null, r.auto_compact_tokens ?? null, r.degraded);
|
|
165
187
|
}
|
|
166
188
|
db.exec('COMMIT');
|
|
167
189
|
}
|
|
@@ -2,10 +2,12 @@ import crypto from 'crypto';
|
|
|
2
2
|
import { eventPatternIncludesInternal } from '../core/event-catalog.js';
|
|
3
3
|
import { trustedExecutionIdentityInput } from '../core/auth/agent-delegation.js';
|
|
4
4
|
import { authorizeOperation, buildAuthSubject } from '../core/auth/auth-gateway.js';
|
|
5
|
+
import { refreshOwnershipForAuthorization } from '../core/ownership/owner-resolver.js';
|
|
5
6
|
import { isManagementRole } from '../config/builtin-roles.js';
|
|
6
7
|
import { checkRoleAccess, resolvePeerRoleDetail, roleToSessionIdentity } from '../config/peer-role-resolver.js';
|
|
7
8
|
import { validateModelSelectionForRole } from '../core/model/model-permission.js';
|
|
8
9
|
import { isValidAunDeliveryForChannel } from '../core/message/outbound.js';
|
|
10
|
+
import { sessionTopicThreadId } from '../core/thread/topic-id.js';
|
|
9
11
|
import { tryParseChannelKey } from '../core/channel-loader.js';
|
|
10
12
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from '../core/role/runtime-policy.js';
|
|
11
13
|
import { resolveThreadRouteTarget } from '../core/thread/thread-route.js';
|
|
@@ -21,13 +23,16 @@ export function originFromActorSession(session, authenticatedPeerId, authenticat
|
|
|
21
23
|
}
|
|
22
24
|
const isGroup = authenticatedChatType === 'group' || session.chatType === 'group';
|
|
23
25
|
const channelId = isGroup ? session.channelId : peerId;
|
|
24
|
-
|
|
26
|
+
// 只有话题会让 origin 变成 `thread`;主话题(含 `main:<a>:<b>` 规范 id)
|
|
27
|
+
// 在 Trigger 里仍用 `session: 'main'` 表达,主话题 id 不写进 origin.threadId。
|
|
28
|
+
const topicThreadId = sameConversation ? sessionTopicThreadId(session) : undefined;
|
|
29
|
+
const sessionKind = topicThreadId ? 'thread' : 'main';
|
|
25
30
|
return {
|
|
26
31
|
channelKey,
|
|
27
32
|
channelType: session.channelType || tryParseChannelKey(channelKey)?.type || channelKey,
|
|
28
33
|
channelId,
|
|
29
34
|
session: sessionKind,
|
|
30
|
-
threadId:
|
|
35
|
+
threadId: topicThreadId,
|
|
31
36
|
peerId,
|
|
32
37
|
sessionKey: sameConversation ? session.sessionKey : undefined,
|
|
33
38
|
delivery: isGroup ? { chatType: 'group', groupId: channelId } : { chatType: 'private' },
|
|
@@ -168,7 +173,46 @@ export function validateTriggerDefinitionForActor(definition, actor, dependencie
|
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Trigger 的操作主体集合:谁能 cancel / update / show 这条 Trigger。
|
|
178
|
+
*
|
|
179
|
+
* origin 只记录"从哪个会话创建",feedback 目标才是"结果投给谁"。跨话题复查
|
|
180
|
+
* (origin = 来源话题对端 A,target = 目标话题对端 C)要求 C 侧也能操作,
|
|
181
|
+
* 否则结果回来之后在 C 那边完全看不见、也取消不掉。旧定义没有 operatorPeers
|
|
182
|
+
* 字段时按同样规则现算,不需要数据迁移。
|
|
183
|
+
*/
|
|
184
|
+
export function triggerOperatorPeerIds(definition) {
|
|
185
|
+
const declared = (definition.operatorPeers ?? []).filter(peer => typeof peer === 'string' && peer.length > 0);
|
|
186
|
+
if (declared.length > 0)
|
|
187
|
+
return [...new Set(declared)];
|
|
188
|
+
const peers = [];
|
|
189
|
+
if (definition.origin)
|
|
190
|
+
peers.push(originConversationPeerId(definition.origin));
|
|
191
|
+
const target = definition.feedback.strategy === 'target' ? definition.feedback.target : undefined;
|
|
192
|
+
if (target?.channelId)
|
|
193
|
+
peers.push(target.channelId);
|
|
194
|
+
return [...new Set(peers.filter(peer => typeof peer === 'string' && peer.length > 0))];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 会话的对端身份:私聊是对方 AID,群聊是群 AID。
|
|
198
|
+
*
|
|
199
|
+
* 群会话里 `origin.peerId` 记的是**群内发言人**(`channels/aun.ts` 的
|
|
200
|
+
* `buildGroupReplyContext` 把 `replyContext.peerId` 设为 senderAid),
|
|
201
|
+
* 群本身的身份只在 `channelId`。协议里 `group_id` 与 `group_aid` 是同一个值
|
|
202
|
+
* (`10-Group-子协议.md`:`{"group_id":"g-abc.pub","group_aid":"g-abc.pub"}`),
|
|
203
|
+
* 所以群场景取 `channelId` 才是群 AID。若按 peerId 比对,群内每个发言人都会
|
|
204
|
+
* 得到不同身份,授权必然对不上。
|
|
205
|
+
*/
|
|
206
|
+
export function originConversationPeerId(origin) {
|
|
207
|
+
return origin.delivery?.chatType === 'group' ? origin.channelId : (origin.peerId || origin.channelId);
|
|
208
|
+
}
|
|
209
|
+
export function isTriggerOperator(definition, actorConversationPeerId) {
|
|
210
|
+
if (!definition || !actorConversationPeerId)
|
|
211
|
+
return false;
|
|
212
|
+
return triggerOperatorPeerIds(definition).includes(actorConversationPeerId);
|
|
213
|
+
}
|
|
214
|
+
export async function authorizeTriggerExecution(definition, dependencies) {
|
|
215
|
+
await refreshOwnershipForAuthorization(definition.agentAid);
|
|
172
216
|
if (definition.execution.permissionMode === 'fullaccess') {
|
|
173
217
|
if (!definition.authorizedBy) {
|
|
174
218
|
return { allowed: false, reason: 'fullaccess Trigger approval provenance is missing' };
|
|
@@ -342,6 +386,7 @@ export async function authenticatedTriggerActor(dependencies, agentAid, actorSes
|
|
|
342
386
|
throw new Error('trigger delegation actor does not match session peer');
|
|
343
387
|
}
|
|
344
388
|
const actorChatType = triggerOriginChatType(actorOrigin);
|
|
389
|
+
await refreshOwnershipForAuthorization(agentAid);
|
|
345
390
|
const actorConversationId = actorChatType === 'group' ? actorOrigin.channelId : actorOrigin.peerId;
|
|
346
391
|
const roleDetail = resolvePeerRoleDetail({
|
|
347
392
|
selfAid: agentAid,
|
package/dist/trigger/feedback.js
CHANGED
|
@@ -2,7 +2,8 @@ import { buildEnvelope } from '../core/message/message-utils.js';
|
|
|
2
2
|
import { executionTimeoutMs, renderTemplate, previewText, sha256 } from './validation.js';
|
|
3
3
|
import { isDaemonEventSessionTrigger } from './event-source.js';
|
|
4
4
|
import { TASK_RECOVERY_PROMPT } from '../core/task/task-recovery-service.js';
|
|
5
|
-
import { ExecutionTimeoutError, OrphanedTargetSessionError } from '../utils/error-utils.js';
|
|
5
|
+
import { ExecutionTimeoutError, OrphanedTargetSessionError, isRetryableError } from '../utils/error-utils.js';
|
|
6
|
+
import { isTopicThreadId, sessionThreadKind, sessionTopicThreadId } from '../core/thread/topic-id.js';
|
|
6
7
|
const FEEDBACK_DEADLINE_MS = 30_000;
|
|
7
8
|
const TARGET_SESSION_CANCEL_GRACE_MS = 1_000;
|
|
8
9
|
const TARGET_SESSION_OUTCOME_GRACE_MS = 5_000;
|
|
@@ -89,12 +90,15 @@ export class TriggerFeedbackDispatcher {
|
|
|
89
90
|
if (channelTypeFromKey(channelKey) === 'aun' && typeof payload.encrypted !== 'boolean') {
|
|
90
91
|
return this.failed(feedback, [], 'event_session_encryption_missing', 'AUN event source must preserve the original encryption mode');
|
|
91
92
|
}
|
|
93
|
+
const eventSourceKind = sessionThreadKind(eventSession);
|
|
92
94
|
unresolvedTarget = {
|
|
93
95
|
channelKey,
|
|
94
96
|
channelId: eventSession.channelId,
|
|
95
97
|
chatType: eventSession.chatType,
|
|
96
|
-
|
|
97
|
-
...(
|
|
98
|
+
// 只有话题让目标变成 `thread`;主话题用 `session: 'main'` 表达,不写 threadId(同 originFromActorSession)。
|
|
99
|
+
...(eventSourceKind.kind === 'topic'
|
|
100
|
+
? { session: 'thread', threadId: eventSourceKind.threadId }
|
|
101
|
+
: { session: 'main' }),
|
|
98
102
|
};
|
|
99
103
|
}
|
|
100
104
|
if (!unresolvedTarget)
|
|
@@ -104,6 +108,9 @@ export class TriggerFeedbackDispatcher {
|
|
|
104
108
|
return this.failed(feedback, [], 'route_missing', 'AUN target_session has no explicit delivery route');
|
|
105
109
|
}
|
|
106
110
|
const isTaskErrorRecovery = !!eventSession && input.sourcePayload?.type === 'task:error';
|
|
111
|
+
if (isTaskErrorRecovery && input.sourcePayload?.recoverable !== true) {
|
|
112
|
+
return { status: 'skipped', reason: 'not_recoverable', feedback, effects: [], error: null };
|
|
113
|
+
}
|
|
107
114
|
if (isTaskErrorRecovery && (typeof input.sourcePayload?.taskId !== 'string' || !input.sourcePayload.taskId
|
|
108
115
|
|| !Number.isSafeInteger(input.sourcePayload.generation))) {
|
|
109
116
|
return this.failed(feedback, [], 'recovery_event_invalid', 'task:error must identify the original task and generation');
|
|
@@ -155,7 +162,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
155
162
|
let session;
|
|
156
163
|
try {
|
|
157
164
|
const existingLookup = eventSession ?? (target.session === 'thread'
|
|
158
|
-
? target.threadId && this.deps.sessionManager.getThreadSession
|
|
165
|
+
? isTopicThreadId(target.threadId) && this.deps.sessionManager.getThreadSession
|
|
159
166
|
? this.deps.sessionManager.getThreadSession(target.channelKey, target.channelId, target.threadId)
|
|
160
167
|
: undefined
|
|
161
168
|
: this.deps.sessionManager.getActiveSession
|
|
@@ -168,7 +175,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
168
175
|
session = existing;
|
|
169
176
|
}
|
|
170
177
|
else if (target.session === 'thread') {
|
|
171
|
-
if (!target.threadId) {
|
|
178
|
+
if (!isTopicThreadId(target.threadId)) {
|
|
172
179
|
return this.failed(feedback, [], 'target_missing', 'target thread id is missing');
|
|
173
180
|
}
|
|
174
181
|
const mainLookup = this.deps.sessionManager.getActiveSession?.(target.channelKey, target.channelId);
|
|
@@ -240,7 +247,9 @@ export class TriggerFeedbackDispatcher {
|
|
|
240
247
|
channelId: target.channelId,
|
|
241
248
|
selfAID: targetAgentAid,
|
|
242
249
|
baseagent: session.baseagent || binding.baseagent,
|
|
243
|
-
|
|
250
|
+
// Message.threadId 是入站路由字段:只有话题才非空,主会话为 ''(与入站消息、
|
|
251
|
+
// task-recovery 一致)。下游 resolveSession / message-queue scope 以它区分主会话。
|
|
252
|
+
threadId: sessionTopicThreadId(session) ?? '',
|
|
244
253
|
chatType: messageChatType,
|
|
245
254
|
peerId: `trigger:${input.trigger.id}`,
|
|
246
255
|
peerName: input.trigger.name,
|
|
@@ -282,6 +291,11 @@ export class TriggerFeedbackDispatcher {
|
|
|
282
291
|
noopSentinel: input.trigger.execution.noopSentinel,
|
|
283
292
|
authorizedBy: input.trigger.authorizedBy,
|
|
284
293
|
labels: input.trigger.labels,
|
|
294
|
+
// 跨话题复查:标记本次投递的类型,执行侧据此走 T2 会话自身身份,
|
|
295
|
+
// 而不是 originPeerId(来源话题的对端)。
|
|
296
|
+
...(input.trigger.labels?.kind === 'cross-thread-review'
|
|
297
|
+
? { reviewKind: 'cross-thread-review' }
|
|
298
|
+
: {}),
|
|
285
299
|
},
|
|
286
300
|
};
|
|
287
301
|
outcome = this.waitForTriggerOutcome(input.runId, input.attemptId, delivery.signal);
|
|
@@ -324,7 +338,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
324
338
|
const event = observed.outcome === 'completed'
|
|
325
339
|
? { ...base, type: 'trigger:completed', durationMs: Date.now() - startedAt }
|
|
326
340
|
: observed.outcome === 'failed'
|
|
327
|
-
? { ...base, type: 'trigger:failed', error: observed.reason ?? 'target_session_failed', phase: 'execute' }
|
|
341
|
+
? { ...base, type: 'trigger:failed', error: observed.reason ?? 'target_session_failed', recoverable: observed.recoverable, phase: 'execute' }
|
|
328
342
|
: { ...base, type: 'trigger:skipped', reason: observed.reason ?? 'stale_generation',
|
|
329
343
|
decisionSource: 'infrastructure', executionState: 'interrupted' };
|
|
330
344
|
outcome?.settle(event);
|
|
@@ -423,7 +437,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
423
437
|
reason: event.phase,
|
|
424
438
|
feedback,
|
|
425
439
|
effects: [this.inboundEffect('success', target, session.id, startedAt)],
|
|
426
|
-
error: { code: event.phase || 'target_session_failed', message: event.error },
|
|
440
|
+
error: { code: event.phase || 'target_session_failed', message: event.error, recoverable: event.recoverable },
|
|
427
441
|
};
|
|
428
442
|
}
|
|
429
443
|
if (event?.type === 'trigger:skipped') {
|
|
@@ -473,7 +487,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
473
487
|
reason: err instanceof OrphanedTargetSessionError ? err.reason : 'enqueue_error',
|
|
474
488
|
feedback,
|
|
475
489
|
effects: [this.inboundEffect('failed', target, session.id, startedAt, undefined, err?.message || String(err))],
|
|
476
|
-
error: { code: err instanceof OrphanedTargetSessionError ? err.reason : 'enqueue_error', message: err?.message || String(err) },
|
|
490
|
+
error: { code: err instanceof OrphanedTargetSessionError ? err.reason : 'enqueue_error', message: err?.message || String(err), recoverable: isRetryableError(err) },
|
|
477
491
|
};
|
|
478
492
|
}
|
|
479
493
|
}
|
|
@@ -848,6 +862,7 @@ function errorFromReply(reply) {
|
|
|
848
862
|
return {
|
|
849
863
|
code: reply.error?.reason ?? reply.outcome,
|
|
850
864
|
message: reply.error?.text ?? reply.outcome,
|
|
865
|
+
...(reply.error?.recoverable !== undefined ? { recoverable: reply.error.recoverable } : {}),
|
|
851
866
|
};
|
|
852
867
|
}
|
|
853
868
|
function originAsTarget(origin) {
|
|
@@ -864,8 +879,11 @@ function replyContextFromTarget(target) {
|
|
|
864
879
|
const delivery = targetDeliveryFor(target);
|
|
865
880
|
if (delivery)
|
|
866
881
|
ctx.delivery = delivery;
|
|
867
|
-
|
|
882
|
+
// 话题 id 上线;主话题 id 只进本地日志(`localTopicId`),不进报文。
|
|
883
|
+
if (isTopicThreadId(target.threadId))
|
|
868
884
|
ctx.threadId = target.threadId;
|
|
885
|
+
else if (target.threadId)
|
|
886
|
+
ctx.localTopicId = target.threadId;
|
|
869
887
|
return Object.keys(ctx).length > 0 ? ctx : undefined;
|
|
870
888
|
}
|
|
871
889
|
function targetChatType(target) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getEventCatalog } from '../core/event-catalog.js';
|
|
2
|
-
import {
|
|
2
|
+
import { authorizeFreshOperation, canApprovePersistentFullAccessTrigger, isCrossAgentTriggerOperationAllowed, } from '../core/auth/auth-gateway.js';
|
|
3
3
|
import { auditFullAccessEvent } from '../core/auth/authorization-audit.js';
|
|
4
|
-
import { assertTriggerLocalFileAuthority, daemonPrivilegedTriggerReason, } from './authorization.js';
|
|
4
|
+
import { assertTriggerLocalFileAuthority, daemonPrivilegedTriggerReason, isTriggerOperator, originConversationPeerId, triggerOperatorPeerIds, } from './authorization.js';
|
|
5
5
|
import { decodeTriggerScriptFile } from './local-files.js';
|
|
6
6
|
import { applyTriggerPatch } from './patch.js';
|
|
7
7
|
import { definitionRevision, normalizeTriggerDefinition, splitScriptCommand } from './validation.js';
|
|
@@ -106,6 +106,63 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
106
106
|
if (approvedActor && (agentAid !== approvedAgentAid || operation !== approvedOperation)) {
|
|
107
107
|
throw new Error('Trigger operation does not match its approved argv');
|
|
108
108
|
}
|
|
109
|
+
// Allow a trigger session to cancel, delete, or disable its own trigger.
|
|
110
|
+
// The trigger session's channelId is "trigger:<triggerId>", which fails the normal
|
|
111
|
+
// delegation peer check because it isn't a real AID. For these de-escalation
|
|
112
|
+
// operations we synthesise an actor directly from the trigger definition's origin,
|
|
113
|
+
// which already passed vetting when the trigger was created.
|
|
114
|
+
const isSelfDeescalation = !approvedActor
|
|
115
|
+
&& triggerId
|
|
116
|
+
&& (operation === 'trigger.cancel' || operation === 'trigger.delete' || operation === 'trigger.setEnabled')
|
|
117
|
+
&& typeof cmd.actorSessionId === 'string';
|
|
118
|
+
if (isSelfDeescalation) {
|
|
119
|
+
const actorSession = await dependencies.getSessionById(cmd.actorSessionId);
|
|
120
|
+
const sessionChannelId = actorSession?.channelId;
|
|
121
|
+
if (sessionChannelId === `trigger:${triggerId}`) {
|
|
122
|
+
const definition = schedulerFor(agentAid).list({ all: true }).find(t => t.id === triggerId);
|
|
123
|
+
if (!definition?.origin?.peerId)
|
|
124
|
+
throw new Error('trigger not found or access denied');
|
|
125
|
+
const { buildAuthSubject } = await import('../core/auth/auth-gateway.js');
|
|
126
|
+
const { resolvePeerRoleDetail, roleToSessionIdentity } = await import('../config/peer-role-resolver.js');
|
|
127
|
+
const { tryParseChannelKey } = await import('../core/channel-loader.js');
|
|
128
|
+
const origin = definition.origin;
|
|
129
|
+
const channelType = origin.channelType || tryParseChannelKey(origin.channelKey)?.type || origin.channelKey;
|
|
130
|
+
const chatType = origin.delivery?.chatType ?? 'private';
|
|
131
|
+
const conversationId = chatType === 'group' ? origin.channelId : origin.peerId;
|
|
132
|
+
const roleDetail = resolvePeerRoleDetail({
|
|
133
|
+
selfAid: agentAid,
|
|
134
|
+
channelKey: origin.channelKey,
|
|
135
|
+
channelType,
|
|
136
|
+
chatType,
|
|
137
|
+
actorId: origin.peerId,
|
|
138
|
+
conversationId: conversationId,
|
|
139
|
+
});
|
|
140
|
+
const subject = buildAuthSubject({
|
|
141
|
+
selfAid: agentAid,
|
|
142
|
+
actorId: origin.peerId,
|
|
143
|
+
channel: origin.channelKey,
|
|
144
|
+
channelType,
|
|
145
|
+
channelId: origin.channelId,
|
|
146
|
+
chatType,
|
|
147
|
+
conversationId,
|
|
148
|
+
roleDetail,
|
|
149
|
+
identity: roleToSessionIdentity(roleDetail.effectiveRole),
|
|
150
|
+
processOwners: [],
|
|
151
|
+
fromControlChannel: false,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
origin,
|
|
155
|
+
management: false,
|
|
156
|
+
daemonOwner: false,
|
|
157
|
+
daemonPrivileged: false,
|
|
158
|
+
fullAccess: false,
|
|
159
|
+
selfAid: agentAid,
|
|
160
|
+
control: false,
|
|
161
|
+
role: roleDetail.effectiveRole || 'none',
|
|
162
|
+
subject,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
109
166
|
const actor = approvedActor ?? await dependencies.authenticateActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken, contextOnly);
|
|
110
167
|
if (actor.permissionMode === 'readonly' && !actor.fullAccess
|
|
111
168
|
&& !['trigger.list', 'trigger.show', 'trigger.history', 'trigger.eventCatalog'].includes(operation)) {
|
|
@@ -124,7 +181,7 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
124
181
|
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
125
182
|
throw new Error('trigger agent does not match authenticated task agent');
|
|
126
183
|
}
|
|
127
|
-
const operationDecision =
|
|
184
|
+
const operationDecision = await authorizeFreshOperation({
|
|
128
185
|
source: actor.control ? 'control' : 'agent-tool',
|
|
129
186
|
subject: actor.subject,
|
|
130
187
|
intent: {
|
|
@@ -143,6 +200,10 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
143
200
|
});
|
|
144
201
|
if (!operationDecision.allow)
|
|
145
202
|
throw new Error(operationDecision.reason);
|
|
203
|
+
actor.daemonOwner = actor.subject.isDaemonOwner;
|
|
204
|
+
actor.daemonPrivileged = actor.control || actor.daemonOwner || actor.fullAccess;
|
|
205
|
+
actor.role = actor.subject.role;
|
|
206
|
+
actor.management = ['owner', 'admin'].includes(actor.role) || actor.daemonPrivileged;
|
|
146
207
|
if (!triggerId)
|
|
147
208
|
return actor;
|
|
148
209
|
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
@@ -157,9 +218,7 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
157
218
|
}
|
|
158
219
|
if (actor.management)
|
|
159
220
|
return actor;
|
|
160
|
-
if (!definition
|
|
161
|
-
|| definition.origin?.peerId !== actor.origin?.peerId
|
|
162
|
-
|| definition.origin?.channelKey !== actor.origin?.channelKey) {
|
|
221
|
+
if (!isTriggerOperator(definition, actor.origin ? originConversationPeerId(actor.origin) : undefined)) {
|
|
163
222
|
throw new Error('trigger not found or access denied');
|
|
164
223
|
}
|
|
165
224
|
return actor;
|
|
@@ -213,8 +272,7 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
213
272
|
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged
|
|
214
273
|
|| !daemonPrivilegedTriggerReason(trigger)
|
|
215
274
|
|| daemonPrivilegedTriggerReason(trigger) === 'persistent fullaccess Trigger')
|
|
216
|
-
&& (actor.management
|
|
217
|
-
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey))));
|
|
275
|
+
&& (actor.management || isTriggerOperator(trigger, actor.origin ? originConversationPeerId(actor.origin) : undefined))));
|
|
218
276
|
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
219
277
|
}
|
|
220
278
|
case 'trigger.show': {
|
|
@@ -269,7 +327,15 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
269
327
|
}
|
|
270
328
|
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
271
329
|
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
272
|
-
|
|
330
|
+
// 操作主体在创建时固定下来:origin 对端 + feedback 目标对端。跨话题复查的
|
|
331
|
+
// feedback 目标(C)因此也拿得到 cancel/update/show 权限,与 thread-ops
|
|
332
|
+
// 技能"在目标话题里结束复查"的流程一致。
|
|
333
|
+
const operatorPeers = triggerOperatorPeerIds({
|
|
334
|
+
origin: actor.origin,
|
|
335
|
+
feedback: rawDefinition.feedback,
|
|
336
|
+
operatorPeers: undefined,
|
|
337
|
+
});
|
|
338
|
+
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin, operatorPeers }, agentAid, dependencies);
|
|
273
339
|
let definition = normalizeTriggerDefinition(materialized);
|
|
274
340
|
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
275
341
|
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
@@ -319,6 +385,8 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
319
385
|
}
|
|
320
386
|
decodeTriggerScriptFile(scriptFileBase64);
|
|
321
387
|
}
|
|
388
|
+
// 操作主体之间不做字段限制:operatorPeers 是创建者对"谁能管理这条 Trigger"的授权,
|
|
389
|
+
// 被授权方与 origin 侧拥有同样的修改权限。
|
|
322
390
|
const currentRevision = definitionRevision(existing);
|
|
323
391
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
324
392
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
@@ -328,7 +396,15 @@ export function createTriggerIpcExecutor(dependencies) {
|
|
|
328
396
|
: existing;
|
|
329
397
|
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
330
398
|
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
331
|
-
|
|
399
|
+
// update 时只有 feedback 被修改才重新校验目标限制;
|
|
400
|
+
// 未改 feedback 的 patch(如只改 prompt)对已合法存在的 trigger 不应再执行创建时的
|
|
401
|
+
// feedbackTargetAllowedForNonManagement 检查,否则 operator peers 侧的 member
|
|
402
|
+
// 更新 prompt 会被拒。其他校验(permissionMode/model/effort)照常执行。
|
|
403
|
+
const feedbackChanged = 'feedback' in definitionPatch;
|
|
404
|
+
const actorForValidation = feedbackChanged || actor.management || actor.daemonPrivileged
|
|
405
|
+
? actor
|
|
406
|
+
: { ...actor, management: true };
|
|
407
|
+
dependencies.validateTriggerDefinitionForActor(definition, actorForValidation);
|
|
332
408
|
dependencies.validateTriggerFeedbackChannels(definition);
|
|
333
409
|
const files = scriptFileBase64 !== undefined && existing.execution.script
|
|
334
410
|
? [{
|