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
|
@@ -15,12 +15,13 @@ export function decodeTriggerScriptFile(contentBase64) {
|
|
|
15
15
|
}
|
|
16
16
|
return contents;
|
|
17
17
|
}
|
|
18
|
-
export function
|
|
18
|
+
export function resolveManagedLocalPath(input, scope, kind) {
|
|
19
|
+
const messages = managedLocalFileMessages(kind);
|
|
19
20
|
if (!input || /[\0\r\n]/.test(input) || hasUnsafeWindowsPathSyntax(input))
|
|
20
|
-
throw new Error(
|
|
21
|
+
throw new Error(messages.invalidPath);
|
|
21
22
|
const normalizedInput = input.replace(/\\/g, '/');
|
|
22
23
|
if (normalizedInput.split('/').includes('..'))
|
|
23
|
-
throw new Error(
|
|
24
|
+
throw new Error(messages.parentTraversal);
|
|
24
25
|
const lexical = path.resolve(scope?.cwd ?? process.cwd(), input);
|
|
25
26
|
if (!scope)
|
|
26
27
|
return fs.realpathSync.native(lexical);
|
|
@@ -28,17 +29,17 @@ export function resolveTriggerLocalPath(input, scope) {
|
|
|
28
29
|
const runtime = scope.sessionRuntimeDir
|
|
29
30
|
? fs.realpathSync.native(scope.sessionRuntimeDir)
|
|
30
31
|
: undefined;
|
|
31
|
-
assertSessionRuntimeScope(lexical, runtime);
|
|
32
|
+
assertSessionRuntimeScope(lexical, runtime, messages.otherSessionTemp);
|
|
32
33
|
const inProjectLexically = isSameOrDescendant(lexical, project);
|
|
33
34
|
const inSessionTempLexically = !!runtime && isSameOrDescendant(lexical, runtime);
|
|
34
35
|
if (!inProjectLexically && !inSessionTempLexically) {
|
|
35
|
-
throw new Error(
|
|
36
|
+
throw new Error(messages.outsideScope);
|
|
36
37
|
}
|
|
37
38
|
let current = lexical;
|
|
38
39
|
while (true) {
|
|
39
40
|
const stat = fs.lstatSync(current);
|
|
40
41
|
if (stat.isSymbolicLink()) {
|
|
41
|
-
throw new Error(
|
|
42
|
+
throw new Error(messages.symbolicLink);
|
|
42
43
|
}
|
|
43
44
|
if (current === project || current === runtime)
|
|
44
45
|
break;
|
|
@@ -48,23 +49,49 @@ export function resolveTriggerLocalPath(input, scope) {
|
|
|
48
49
|
current = parent;
|
|
49
50
|
}
|
|
50
51
|
const resolved = fs.realpathSync.native(lexical);
|
|
51
|
-
assertSessionRuntimeScope(resolved, runtime);
|
|
52
|
+
assertSessionRuntimeScope(resolved, runtime, messages.otherSessionTemp);
|
|
52
53
|
const inProject = isSameOrDescendant(lexical, project) && isSameOrDescendant(resolved, project);
|
|
53
54
|
const inSessionTemp = !!runtime
|
|
54
55
|
&& isSameOrDescendant(lexical, runtime)
|
|
55
56
|
&& isSameOrDescendant(resolved, runtime);
|
|
56
57
|
if (!inProject && !inSessionTemp) {
|
|
57
|
-
throw new Error(
|
|
58
|
+
throw new Error(messages.outsideScope);
|
|
58
59
|
}
|
|
59
60
|
const root = resolveRoot();
|
|
60
61
|
const lexicalClass = classifyProtectedPath(lexical, { root, selfAid: scope.selfAid });
|
|
61
62
|
const resolvedClass = classifyProtectedPath(resolved, { root, selfAid: scope.selfAid });
|
|
62
|
-
if (
|
|
63
|
-
throw new Error(
|
|
63
|
+
if (isProtectedManagedInputClass(lexicalClass) || isProtectedManagedInputClass(resolvedClass)) {
|
|
64
|
+
throw new Error(messages.protectedPath);
|
|
64
65
|
}
|
|
65
66
|
return resolved;
|
|
66
67
|
}
|
|
67
|
-
function
|
|
68
|
+
export function resolveTriggerLocalPath(input, scope) {
|
|
69
|
+
return resolveManagedLocalPath(input, scope, 'trigger');
|
|
70
|
+
}
|
|
71
|
+
export function resolveAttachmentLocalPath(input, scope) {
|
|
72
|
+
return resolveManagedLocalPath(input, scope, 'attachment');
|
|
73
|
+
}
|
|
74
|
+
function managedLocalFileMessages(kind) {
|
|
75
|
+
if (kind === 'trigger') {
|
|
76
|
+
return {
|
|
77
|
+
invalidPath: 'invalid Trigger input path',
|
|
78
|
+
parentTraversal: 'Trigger input path cannot traverse parent directories',
|
|
79
|
+
outsideScope: 'Trigger input must be inside the current project or session TMPDIR',
|
|
80
|
+
symbolicLink: 'Trigger input must not traverse a symbolic link or junction',
|
|
81
|
+
protectedPath: 'Trigger input references a protected path',
|
|
82
|
+
otherSessionTemp: 'Trigger input must not reference another session TMPDIR',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
invalidPath: 'Invalid attachment source path',
|
|
87
|
+
parentTraversal: 'Attachment source path cannot traverse parent directories',
|
|
88
|
+
outsideScope: 'Attachment source must be inside the current project or session TMPDIR',
|
|
89
|
+
symbolicLink: 'Attachment source must not traverse a symbolic link or junction',
|
|
90
|
+
protectedPath: 'Attachment source references a protected path',
|
|
91
|
+
otherSessionTemp: 'Attachment source must not reference another session TMPDIR',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function isProtectedManagedInputClass(value) {
|
|
68
95
|
return value === 'h-file' || value === 'h-directory' || value === 'l-path' || value === 'unknown';
|
|
69
96
|
}
|
|
70
97
|
function hasUnsafeWindowsPathSyntax(input) {
|
|
@@ -74,7 +101,7 @@ function hasUnsafeWindowsPathSyntax(input) {
|
|
|
74
101
|
const withoutDrive = /^[A-Za-z]:\//.test(normalized) ? normalized.slice(2) : normalized;
|
|
75
102
|
return withoutDrive.includes(':') || withoutDrive.split('/').some(segment => (/[. ]$/.test(segment) && segment !== '.' && segment !== '..') || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(segment));
|
|
76
103
|
}
|
|
77
|
-
function assertSessionRuntimeScope(candidate, runtime) {
|
|
104
|
+
function assertSessionRuntimeScope(candidate, runtime, errorMessage) {
|
|
78
105
|
if (!runtime)
|
|
79
106
|
return;
|
|
80
107
|
const parent = path.dirname(runtime);
|
|
@@ -83,27 +110,29 @@ function assertSessionRuntimeScope(candidate, runtime) {
|
|
|
83
110
|
const firstSegment = path.relative(parent, candidate).split(path.sep)[0];
|
|
84
111
|
if (/^ec-session-[a-f0-9]{24}$/i.test(firstSegment)
|
|
85
112
|
&& !isSameOrDescendant(candidate, runtime)) {
|
|
86
|
-
throw new Error(
|
|
113
|
+
throw new Error(errorMessage);
|
|
87
114
|
}
|
|
88
115
|
}
|
|
89
|
-
export function
|
|
90
|
-
const
|
|
116
|
+
export function readManagedLocalFile(input, scope, kind) {
|
|
117
|
+
const sourceLabel = kind === 'trigger' ? 'Trigger input' : 'Attachment source';
|
|
118
|
+
const resolved = resolveManagedLocalPath(input, scope, kind);
|
|
91
119
|
const expected = fs.lstatSync(resolved);
|
|
92
120
|
if (!expected.isFile())
|
|
93
|
-
throw new Error(
|
|
121
|
+
throw new Error(`${sourceLabel} must be a regular file`);
|
|
94
122
|
if (scope && expected.nlink !== 1)
|
|
95
|
-
throw new Error(
|
|
123
|
+
throw new Error(`${sourceLabel} must not be hard-linked`);
|
|
96
124
|
const noFollow = fs.constants.O_NOFOLLOW ?? 0;
|
|
97
125
|
const descriptor = fs.openSync(resolved, fs.constants.O_RDONLY | noFollow);
|
|
98
126
|
try {
|
|
99
127
|
const opened = fs.fstatSync(descriptor);
|
|
100
128
|
if (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino
|
|
101
129
|
|| (scope && opened.nlink !== 1)
|
|
102
|
-
||
|
|
103
|
-
throw new Error(
|
|
130
|
+
|| resolveManagedLocalPath(input, scope, kind) !== resolved) {
|
|
131
|
+
throw new Error(`${sourceLabel} changed during validation`);
|
|
132
|
+
}
|
|
133
|
+
if (opened.size > TRIGGER_LOCAL_FILE_LIMIT) {
|
|
134
|
+
throw new Error(`${sourceLabel} exceeds the 4 MiB limit`);
|
|
104
135
|
}
|
|
105
|
-
if (opened.size > TRIGGER_LOCAL_FILE_LIMIT)
|
|
106
|
-
throw new Error('Trigger input exceeds the 4 MiB limit');
|
|
107
136
|
const openedMtime = opened.mtimeMs;
|
|
108
137
|
const openedCtime = opened.ctimeMs;
|
|
109
138
|
const buffer = Buffer.alloc(Math.min(opened.size + 1, TRIGGER_LOCAL_FILE_LIMIT + 1));
|
|
@@ -122,8 +151,8 @@ export function readTriggerLocalFile(input, scope) {
|
|
|
122
151
|
|| !afterPath.isFile() || afterPath.isSymbolicLink()
|
|
123
152
|
|| afterPath.size !== opened.size || afterPath.mtimeMs !== openedMtime || afterPath.ctimeMs !== openedCtime
|
|
124
153
|
|| (scope && (afterRead.nlink !== 1 || afterPath.nlink !== 1))
|
|
125
|
-
||
|
|
126
|
-
throw new Error(
|
|
154
|
+
|| resolveManagedLocalPath(input, scope, kind) !== resolved) {
|
|
155
|
+
throw new Error(`${sourceLabel} changed during reading`);
|
|
127
156
|
}
|
|
128
157
|
return buffer.subarray(0, length);
|
|
129
158
|
}
|
|
@@ -131,3 +160,6 @@ export function readTriggerLocalFile(input, scope) {
|
|
|
131
160
|
fs.closeSync(descriptor);
|
|
132
161
|
}
|
|
133
162
|
}
|
|
163
|
+
export function readTriggerLocalFile(input, scope) {
|
|
164
|
+
return readManagedLocalFile(input, scope, 'trigger');
|
|
165
|
+
}
|
|
@@ -8,6 +8,7 @@ import { TargetSessionLockRegistry } from './session-lock.js';
|
|
|
8
8
|
import { enterTrigger, formatTriggerPath } from '../core/causation/context.js';
|
|
9
9
|
import { createRootCausation } from '../core/causation/context.js';
|
|
10
10
|
import { recordCausationSpan } from '../core/causation/audit.js';
|
|
11
|
+
import { isRetryableError } from '../utils/error-utils.js';
|
|
11
12
|
import { accumulateTriggerExecutionAnomalies, drainTriggerExecutionAnomalies, mergeTriggerExecutionAnomalies, } from './anomaly-store.js';
|
|
12
13
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
13
14
|
const RUN_ABORT_SETTLE_GRACE_MS = 1_000;
|
|
@@ -441,7 +442,7 @@ export class TriggerRuntimeScheduler {
|
|
|
441
442
|
}
|
|
442
443
|
return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'invalid_execution_configuration', audit, error: message };
|
|
443
444
|
}
|
|
444
|
-
const executionAuthorization = this.runtime.authorizeExecution?.(definition);
|
|
445
|
+
const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
|
|
445
446
|
if (executionAuthorization && !executionAuthorization.allowed) {
|
|
446
447
|
const reason = executionAuthorization.reason || 'execution_authorization_denied';
|
|
447
448
|
const audit = this.buildAudit({
|
|
@@ -583,7 +584,7 @@ export class TriggerRuntimeScheduler {
|
|
|
583
584
|
}
|
|
584
585
|
if (!payload.dryRun) {
|
|
585
586
|
const executionFailed = execution.reply
|
|
586
|
-
?
|
|
587
|
+
? !['success', 'noop'].includes(execution.reply.outcome)
|
|
587
588
|
: execution.feedbackResult?.status === 'failed';
|
|
588
589
|
this.state.appendEvent(definition.id, runId, {
|
|
589
590
|
event: executionFailed ? 'execution.failed' : 'execution.completed',
|
|
@@ -758,7 +759,7 @@ export class TriggerRuntimeScheduler {
|
|
|
758
759
|
}
|
|
759
760
|
async runRecordedExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt, previousAttempt) {
|
|
760
761
|
const attemptId = `${runId}:attempt:${attempt}`;
|
|
761
|
-
const executionAuthorization = this.runtime.authorizeExecution?.(definition);
|
|
762
|
+
const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
|
|
762
763
|
if (executionAuthorization && !executionAuthorization.allowed) {
|
|
763
764
|
const reply = errorReply(runId, 0, 'authorization_denied', executionAuthorization.reason || 'Trigger execution authorization denied');
|
|
764
765
|
const execution = {
|
|
@@ -836,11 +837,30 @@ export class TriggerRuntimeScheduler {
|
|
|
836
837
|
reply,
|
|
837
838
|
};
|
|
838
839
|
}
|
|
840
|
+
// 复查通知里"第 N 次 / 已过多久"是**渲染期**数据,必须在每次 fire 时重算。
|
|
841
|
+
// fireCount 来自 history 聚合(skipped / dry-run 不计),本轮尚未落历史,
|
|
842
|
+
// 因此本轮序号 = 已完成次数 + 1。
|
|
843
|
+
const runNumber = this.audit.stats(definition.id).fireCount + 1;
|
|
844
|
+
const firstSentAt = Number(definition.labels?.firstSentAt);
|
|
845
|
+
const elapsedMs = Number.isFinite(firstSentAt) && firstSentAt > 0 ? Math.max(0, firedAt - firstSentAt) : undefined;
|
|
846
|
+
const triggerView = {
|
|
847
|
+
...definition,
|
|
848
|
+
labels: {
|
|
849
|
+
...(definition.labels ?? {}),
|
|
850
|
+
...(Number.isFinite(firstSentAt) && firstSentAt > 0
|
|
851
|
+
? { firstSentAtTime: new Date(firstSentAt).toLocaleString('zh-CN', { hour12: false }) }
|
|
852
|
+
: {}),
|
|
853
|
+
},
|
|
854
|
+
};
|
|
839
855
|
const originalPrompt = renderTemplate(definition.execution.prompt ?? '', {
|
|
840
|
-
trigger:
|
|
856
|
+
trigger: triggerView,
|
|
841
857
|
timestamp: firedAt,
|
|
842
858
|
event: sourcePayload,
|
|
843
859
|
source: { type: definition.source.type, payload: sourcePayload },
|
|
860
|
+
run: {
|
|
861
|
+
number: runNumber,
|
|
862
|
+
elapsed: elapsedMs === undefined ? '' : formatElapsed(elapsedMs),
|
|
863
|
+
},
|
|
844
864
|
});
|
|
845
865
|
const prompt = previousAttempt
|
|
846
866
|
? renderRetryContinuationPrompt(originalPrompt, previousAttempt)
|
|
@@ -973,7 +993,8 @@ export class TriggerRuntimeScheduler {
|
|
|
973
993
|
};
|
|
974
994
|
}
|
|
975
995
|
catch (err) {
|
|
976
|
-
const
|
|
996
|
+
const errorText = err?.message || String(err);
|
|
997
|
+
const reply = errorReply(runId, 0, 'agent_execution_error', errorText, isRetryableError(err));
|
|
977
998
|
return {
|
|
978
999
|
branch: 'onFailure',
|
|
979
1000
|
script: null,
|
|
@@ -1059,7 +1080,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1059
1080
|
async reclaimOpenRun(definition, run) {
|
|
1060
1081
|
if (!definition.enabled || !this.isResumableCheckpoint(definition, run))
|
|
1061
1082
|
return false;
|
|
1062
|
-
const executionAuthorization = this.runtime.authorizeExecution?.(definition);
|
|
1083
|
+
const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
|
|
1063
1084
|
if (executionAuthorization && !executionAuthorization.allowed)
|
|
1064
1085
|
return false;
|
|
1065
1086
|
const session = await this.daemonChannel.getResumableConversationSession(run.executionSessionId);
|
|
@@ -1162,7 +1183,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1162
1183
|
return { anomalies };
|
|
1163
1184
|
}
|
|
1164
1185
|
const executionFailed = execution.reply
|
|
1165
|
-
?
|
|
1186
|
+
? !['success', 'noop'].includes(execution.reply.outcome)
|
|
1166
1187
|
: execution.feedbackResult?.status === 'failed';
|
|
1167
1188
|
this.state.appendEvent(definition.id, runId, {
|
|
1168
1189
|
event: executionFailed ? 'execution.failed' : 'execution.completed',
|
|
@@ -1348,7 +1369,8 @@ export class TriggerRuntimeScheduler {
|
|
|
1348
1369
|
};
|
|
1349
1370
|
}
|
|
1350
1371
|
catch (error) {
|
|
1351
|
-
const
|
|
1372
|
+
const errorText = error?.message || String(error);
|
|
1373
|
+
const reply = errorReply(run.runId, 0, 'agent_execution_error', errorText, isRetryableError(error));
|
|
1352
1374
|
return {
|
|
1353
1375
|
branch: 'onFailure',
|
|
1354
1376
|
script: null,
|
|
@@ -1839,6 +1861,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1839
1861
|
...base,
|
|
1840
1862
|
messageId: this.triggerOutcomeMessageId(audit),
|
|
1841
1863
|
error: audit.error?.message ?? audit.reason ?? 'trigger failed',
|
|
1864
|
+
recoverable: audit.error?.recoverable,
|
|
1842
1865
|
phase: 'execute',
|
|
1843
1866
|
});
|
|
1844
1867
|
return;
|
|
@@ -1929,6 +1952,10 @@ function branchFromReply(reply) {
|
|
|
1929
1952
|
return 'onFailure';
|
|
1930
1953
|
}
|
|
1931
1954
|
function shouldRetryExecution(reply) {
|
|
1955
|
+
if (reply.outcome === 'success' || reply.outcome === 'noop' || reply.error?.reason === 'authorization_denied')
|
|
1956
|
+
return false;
|
|
1957
|
+
if (reply.error?.recoverable !== undefined)
|
|
1958
|
+
return reply.error.recoverable;
|
|
1932
1959
|
return reply.outcome === 'error'
|
|
1933
1960
|
? reply.error?.reason !== 'authorization_denied'
|
|
1934
1961
|
: reply.outcome === 'timeout' || reply.outcome === 'interrupted';
|
|
@@ -1948,17 +1975,18 @@ function triggerCancellationReason(signal) {
|
|
|
1948
1975
|
function shouldRetryExecutionResult(execution) {
|
|
1949
1976
|
if (execution.reply)
|
|
1950
1977
|
return shouldRetryExecution(execution.reply);
|
|
1951
|
-
return execution.feedbackResult?.status === 'failed'
|
|
1978
|
+
return execution.feedbackResult?.status === 'failed'
|
|
1979
|
+
&& execution.feedbackResult.error?.recoverable === true;
|
|
1952
1980
|
}
|
|
1953
1981
|
function statusWithExecutionAnomalies(status, anomalies) {
|
|
1954
1982
|
return status === 'completed' && anomalies.length ? 'degraded' : status;
|
|
1955
1983
|
}
|
|
1956
|
-
function errorReply(runId, durationMs, reason, text) {
|
|
1984
|
+
function errorReply(runId, durationMs, reason, text, recoverable) {
|
|
1957
1985
|
return {
|
|
1958
1986
|
outcome: 'error',
|
|
1959
1987
|
text: '',
|
|
1960
1988
|
files: [],
|
|
1961
|
-
error: { reason, text },
|
|
1989
|
+
error: { reason, text, ...(recoverable !== undefined ? { recoverable } : {}) },
|
|
1962
1990
|
meta: {
|
|
1963
1991
|
runId,
|
|
1964
1992
|
durationMs,
|
|
@@ -2013,6 +2041,7 @@ function replyErrorSummary(reply, fallbackCode) {
|
|
|
2013
2041
|
return {
|
|
2014
2042
|
code: reply.error?.reason || fallbackCode,
|
|
2015
2043
|
message: previewText(reply.error?.text || `Trigger attempt ${fallbackCode}`, 600),
|
|
2044
|
+
...(reply.error?.recoverable !== undefined ? { recoverable: reply.error.recoverable } : {}),
|
|
2016
2045
|
};
|
|
2017
2046
|
}
|
|
2018
2047
|
function errorSummary(error, fallbackCode) {
|
|
@@ -2224,6 +2253,23 @@ function executionAnomaliesFromError(error) {
|
|
|
2224
2253
|
function limitsSignature(limits) {
|
|
2225
2254
|
return JSON.stringify(limits ?? null);
|
|
2226
2255
|
}
|
|
2256
|
+
/** 人类可读的时长(复查通知里"已过"字段):`25 分钟` / `3 小时 5 分钟` / `2 天`。 */
|
|
2257
|
+
function formatElapsed(ms) {
|
|
2258
|
+
const minutes = Math.floor(ms / 60_000);
|
|
2259
|
+
if (minutes < 1)
|
|
2260
|
+
return '不到 1 分钟';
|
|
2261
|
+
const days = Math.floor(minutes / 1440);
|
|
2262
|
+
const hours = Math.floor((minutes % 1440) / 60);
|
|
2263
|
+
const mins = minutes % 60;
|
|
2264
|
+
const parts = [];
|
|
2265
|
+
if (days)
|
|
2266
|
+
parts.push(`${days} 天`);
|
|
2267
|
+
if (hours)
|
|
2268
|
+
parts.push(`${hours} 小时`);
|
|
2269
|
+
if (mins)
|
|
2270
|
+
parts.push(`${mins} 分钟`);
|
|
2271
|
+
return parts.join(' ') || '不到 1 分钟';
|
|
2272
|
+
}
|
|
2227
2273
|
function mergeStats(legacy, current) {
|
|
2228
2274
|
if (!legacy)
|
|
2229
2275
|
return current;
|
|
@@ -2,6 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import { normalizePermissionMode as normalizePermissionModeContract } from '../core/permission/mode.js';
|
|
4
4
|
import { formatChannelKey } from '../core/channel-loader.js';
|
|
5
|
+
import { isTopicThreadId } from '../core/thread/topic-id.js';
|
|
5
6
|
const MAX_SCRIPT_TIMEOUT_MS = 900_000;
|
|
6
7
|
const TRIGGER_SESSION_TIMEOUT_MS = 30 * 60_000;
|
|
7
8
|
const MAX_EXECUTION_TIMEOUT_MS = 2_147_483_647;
|
|
@@ -204,6 +205,7 @@ export function renderTemplate(template, ctx) {
|
|
|
204
205
|
error: ctx.error,
|
|
205
206
|
event: ctx.event,
|
|
206
207
|
source: ctx.source,
|
|
208
|
+
run: ctx.run,
|
|
207
209
|
};
|
|
208
210
|
const source = template ?? defaultTemplate(fullCtx);
|
|
209
211
|
return source.replace(/\{\{\s*([A-Za-z0-9_.]+)\s*\}\}/g, (_m, key) => {
|
|
@@ -243,6 +245,7 @@ function normalizeV3Phase2(raw, opts) {
|
|
|
243
245
|
name,
|
|
244
246
|
description: optionalString(raw.description),
|
|
245
247
|
authorizedBy: optionalString(raw.authorizedBy),
|
|
248
|
+
operatorPeers: normalizeOperatorPeers(raw.operatorPeers),
|
|
246
249
|
createdAt,
|
|
247
250
|
updatedAt,
|
|
248
251
|
origin: normalizeOrigin(raw.origin),
|
|
@@ -269,18 +272,35 @@ function normalizeV3Phase2(raw, opts) {
|
|
|
269
272
|
validateTriggerSemantics(definition);
|
|
270
273
|
return definition;
|
|
271
274
|
}
|
|
275
|
+
/** 操作主体列表:字符串数组、去重、截断到 16 项;保证落盘值可直接用于权限比对。 */
|
|
276
|
+
function normalizeOperatorPeers(value) {
|
|
277
|
+
if (value === undefined || value === null)
|
|
278
|
+
return undefined;
|
|
279
|
+
if (!Array.isArray(value))
|
|
280
|
+
throw new Error('operatorPeers must be an array of AID strings');
|
|
281
|
+
const peers = [];
|
|
282
|
+
for (const item of value) {
|
|
283
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
284
|
+
throw new Error('operatorPeers entries must be non-empty strings');
|
|
285
|
+
}
|
|
286
|
+
const peer = item.trim();
|
|
287
|
+
if (!peers.includes(peer))
|
|
288
|
+
peers.push(peer);
|
|
289
|
+
}
|
|
290
|
+
if (peers.length === 0)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (peers.length > 16)
|
|
293
|
+
throw new Error('operatorPeers supports at most 16 entries');
|
|
294
|
+
return peers;
|
|
295
|
+
}
|
|
272
296
|
function normalizeOrigin(value) {
|
|
273
297
|
if (value === undefined)
|
|
274
298
|
return undefined;
|
|
275
299
|
if (!isObject(value))
|
|
276
300
|
throw new Error('origin must be an object');
|
|
277
301
|
const raw = value;
|
|
278
|
-
const rawSession = normalizeSessionKind(raw.session, 'origin.session');
|
|
279
302
|
const threadId = optionalString(raw.threadId);
|
|
280
|
-
|
|
281
|
-
throw new Error('origin.threadId is not allowed when origin.session=main');
|
|
282
|
-
if (rawSession === 'thread' && !threadId)
|
|
283
|
-
throw new Error('origin.threadId is required when origin.session=thread');
|
|
303
|
+
const rawSession = inferSessionKind(threadId, raw.session, 'origin.session');
|
|
284
304
|
const rawChannelId = requiredString(raw.channelId, 'origin.channelId');
|
|
285
305
|
const peerId = optionalString(raw.peerId);
|
|
286
306
|
const channelKey = requiredString(raw.channelKey, 'origin.channelKey');
|
|
@@ -572,12 +592,8 @@ function normalizeFeedbackTarget(value, label, agentAid) {
|
|
|
572
592
|
if (!isObject(value))
|
|
573
593
|
throw new Error(`${label} must be an object`);
|
|
574
594
|
const raw = value;
|
|
575
|
-
const session = normalizeSessionKind(raw.session, `${label}.session`);
|
|
576
595
|
const threadId = optionalString(raw.threadId);
|
|
577
|
-
|
|
578
|
-
throw new Error(`${label}.threadId is not allowed when ${label}.session=main`);
|
|
579
|
-
if (session === 'thread' && !threadId)
|
|
580
|
-
throw new Error(`${label}.threadId is required when ${label}.session=thread`);
|
|
596
|
+
const session = inferSessionKind(threadId, raw.session, `${label}.session`);
|
|
581
597
|
const channelKey = requiredString(raw.channelKey, `${label}.channelKey`);
|
|
582
598
|
const channelId = requiredString(raw.channelId, `${label}.channelId`);
|
|
583
599
|
const rawChatType = raw.chatType;
|
|
@@ -615,11 +631,24 @@ function normalizeFeedbackTemplate(value, label) {
|
|
|
615
631
|
const raw = value;
|
|
616
632
|
return { template: optionalTemplate(raw.template) };
|
|
617
633
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
634
|
+
/**
|
|
635
|
+
* 从 threadId 自动推导 session 类型。用户填写的 session 必须与推导结果一致。
|
|
636
|
+
* - threadId 不存在 → 'main'
|
|
637
|
+
* - threadId 是主话题 id → 'main'
|
|
638
|
+
* - threadId 是话题 id → 'thread'
|
|
639
|
+
*/
|
|
640
|
+
function inferSessionKind(threadId, rawSession, label) {
|
|
641
|
+
const inferred = threadId && isTopicThreadId(threadId) ? 'thread' : 'main';
|
|
642
|
+
const explicit = optionalString(rawSession);
|
|
643
|
+
if (explicit !== undefined) {
|
|
644
|
+
if (explicit !== 'main' && explicit !== 'thread') {
|
|
645
|
+
throw new Error(`${label} must be main or thread`);
|
|
646
|
+
}
|
|
647
|
+
if (explicit !== inferred) {
|
|
648
|
+
throw new Error(`${label}=${explicit} conflicts with threadId: ${threadId ? `"${threadId}" requires session=` + inferred : 'missing threadId requires session=main'}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return inferred;
|
|
623
652
|
}
|
|
624
653
|
function normalizeReliability(value) {
|
|
625
654
|
if (value === undefined) {
|