evolcore 0.0.26 → 0.0.28
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 +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -3,7 +3,7 @@ import fs from 'fs';
|
|
|
3
3
|
import { CronExpressionParser } from 'cron-parser';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
5
|
import { matchTriggerEvent, TriggerEventSource } from './event-source.js';
|
|
6
|
-
import { definitionRevision, parseDurationMs, previewText, renderTemplate, resolveScriptPath, sha256, validateConcurrencyCombination, } from './validation.js';
|
|
6
|
+
import { definitionRevision, executionTimeoutMs, parseDurationMs, previewText, renderTemplate, resolveScriptPath, sha256, validateConcurrencyCombination, } from './validation.js';
|
|
7
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';
|
|
@@ -13,6 +13,13 @@ const MAX_TIMER_MS = 2_147_483_647;
|
|
|
13
13
|
const RUN_ABORT_SETTLE_GRACE_MS = 1_000;
|
|
14
14
|
const RUN_COMPLETION_BARRIER_MS = 1_500;
|
|
15
15
|
const TRIGGER_RESTART_RESUME_PROMPT = 'EvolCore 服务已重启。请基于当前会话记录继续完成刚才被中断的 Trigger 任务;不要重新从头执行已经完成的步骤。';
|
|
16
|
+
function eventSessionKey(payload, fallbackAgentAid) {
|
|
17
|
+
const agentAid = typeof payload.agentAid === 'string' && payload.agentAid.trim()
|
|
18
|
+
? payload.agentAid.trim()
|
|
19
|
+
: fallbackAgentAid;
|
|
20
|
+
const sessionId = typeof payload.sessionId === 'string' ? payload.sessionId : '';
|
|
21
|
+
return `${agentAid}:${sessionId}`;
|
|
22
|
+
}
|
|
16
23
|
export class TriggerRuntimeScheduler {
|
|
17
24
|
manager;
|
|
18
25
|
state;
|
|
@@ -818,6 +825,7 @@ export class TriggerRuntimeScheduler {
|
|
|
818
825
|
attemptId: `${runId}:attempt:${attempt}`,
|
|
819
826
|
firedAt,
|
|
820
827
|
prompt,
|
|
828
|
+
sourcePayload,
|
|
821
829
|
dryRun,
|
|
822
830
|
causation,
|
|
823
831
|
signal,
|
|
@@ -869,7 +877,17 @@ export class TriggerRuntimeScheduler {
|
|
|
869
877
|
+ ` model=${definition.execution.model ?? '<agent-default>'}`
|
|
870
878
|
+ ` effort=${definition.execution.effort ?? '<agent-default>'}`);
|
|
871
879
|
const attemptId = `${runId}:attempt:${attempt}`;
|
|
872
|
-
const
|
|
880
|
+
const executionStartedAt = Date.now();
|
|
881
|
+
const configuredExecutionTimeout = executionTimeoutMs(definition.execution);
|
|
882
|
+
const daemonExecutionDeadlineAt = this.daemonExecutionDeadlineAt(executionStartedAt);
|
|
883
|
+
const configuredExecutionDeadlineAt = configuredExecutionTimeout === undefined
|
|
884
|
+
? undefined
|
|
885
|
+
: executionStartedAt + configuredExecutionTimeout;
|
|
886
|
+
const executionDeadlineAt = daemonExecutionDeadlineAt === undefined
|
|
887
|
+
? configuredExecutionDeadlineAt
|
|
888
|
+
: configuredExecutionDeadlineAt === undefined
|
|
889
|
+
? daemonExecutionDeadlineAt
|
|
890
|
+
: Math.min(daemonExecutionDeadlineAt, configuredExecutionDeadlineAt);
|
|
873
891
|
const executionSession = await this.daemonChannel.getOrCreateConversationSession(definition, this.runtime.projectPath, runtimeBaseagent, runId, attemptId);
|
|
874
892
|
const activeRun = this.state.startExecutionAttempt?.(definition.id, runId, {
|
|
875
893
|
attempt,
|
|
@@ -963,7 +981,13 @@ export class TriggerRuntimeScheduler {
|
|
|
963
981
|
});
|
|
964
982
|
}
|
|
965
983
|
async checkConcurrency(definition, nextRunId, source) {
|
|
966
|
-
const
|
|
984
|
+
const allRuns = this.running.get(definition.id);
|
|
985
|
+
// An event-session trigger owns many independent sessions. Forbid/replace
|
|
986
|
+
// applies within the source session, so one failure cannot suppress another.
|
|
987
|
+
const runs = definition.feedback.strategy === 'event_session' && allRuns
|
|
988
|
+
? new Map([...allRuns].filter(([, runtime]) => eventSessionKey(runtime.source.payload, definition.agentAid)
|
|
989
|
+
=== eventSessionKey(source.payload, definition.agentAid)))
|
|
990
|
+
: allRuns;
|
|
967
991
|
if (!runs || runs.size === 0 || definition.reliability.concurrency === 'allow')
|
|
968
992
|
return undefined;
|
|
969
993
|
const first = runs.values().next().value;
|
|
@@ -1753,7 +1777,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1753
1777
|
publishTriggerRunOutcome(definition, audit) {
|
|
1754
1778
|
if (!this.eventBus || audit.status === 'dry-run')
|
|
1755
1779
|
return;
|
|
1756
|
-
const target = primaryTarget(definition);
|
|
1780
|
+
const target = audit.feedback?.target ?? primaryTarget(definition);
|
|
1757
1781
|
const targetChannel = target?.channelKey ?? 'daemon';
|
|
1758
1782
|
const targetChannelId = target?.channelId ?? definition.agentAid;
|
|
1759
1783
|
const base = {
|
|
@@ -2027,6 +2051,8 @@ function summarizeReply(reply, opts = {}) {
|
|
|
2027
2051
|
function primaryTarget(definition) {
|
|
2028
2052
|
if (definition.feedback.strategy === 'target')
|
|
2029
2053
|
return definition.feedback.target;
|
|
2054
|
+
if (definition.feedback.strategy === 'event_session')
|
|
2055
|
+
return undefined;
|
|
2030
2056
|
if (!definition.origin)
|
|
2031
2057
|
return undefined;
|
|
2032
2058
|
return {
|
|
@@ -3,6 +3,8 @@ 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
5
|
const MAX_SCRIPT_TIMEOUT_MS = 900_000;
|
|
6
|
+
const TRIGGER_SESSION_TIMEOUT_MS = 30 * 60_000;
|
|
7
|
+
const MAX_EXECUTION_TIMEOUT_MS = 2_147_483_647;
|
|
6
8
|
export const TRIGGER_SCHEMA_VERSION = 3.1;
|
|
7
9
|
const LEGACY_TRIGGER_SCHEMA_VERSION = 4;
|
|
8
10
|
const DEFAULT_NOOP_SENTINEL = '[[NOOP]]';
|
|
@@ -35,7 +37,7 @@ export function normalizeTriggerDefinition(input, opts = {}) {
|
|
|
35
37
|
* channelId shape inference at runtime.
|
|
36
38
|
*/
|
|
37
39
|
export function assertExplicitAunDeliveryRoutes(definition) {
|
|
38
|
-
if (definition.feedback.strategy === 'silent')
|
|
40
|
+
if (definition.feedback.strategy === 'silent' || definition.feedback.strategy === 'event_session')
|
|
39
41
|
return;
|
|
40
42
|
const target = definition.feedback.strategy === 'target'
|
|
41
43
|
? definition.feedback.target
|
|
@@ -58,6 +60,11 @@ export function assertExplicitAunDeliveryRoutes(definition) {
|
|
|
58
60
|
export function validateTriggerDefinition(definition) {
|
|
59
61
|
normalizeTriggerDefinition(definition);
|
|
60
62
|
}
|
|
63
|
+
/** Resolve a model-backed attempt's deadline; target sessions are unlimited by default. */
|
|
64
|
+
export function executionTimeoutMs(execution) {
|
|
65
|
+
return execution.timeoutMs ?? (execution.type === 'target_session'
|
|
66
|
+
? undefined : TRIGGER_SESSION_TIMEOUT_MS);
|
|
67
|
+
}
|
|
61
68
|
/**
|
|
62
69
|
* Keep legacy `per_run` definitions byte-stable while giving runtime code one
|
|
63
70
|
* canonical representation for session selection and validation.
|
|
@@ -428,8 +435,14 @@ function normalizeExecution(value, _opts) {
|
|
|
428
435
|
throw new Error('execution.type must be script, trigger_session, or target_session');
|
|
429
436
|
}
|
|
430
437
|
const thread = normalizeExecutionThread(raw.thread);
|
|
438
|
+
if (raw.timeoutMs !== undefined && (type === 'script'
|
|
439
|
+
|| typeof raw.timeoutMs !== 'number' || !Number.isSafeInteger(raw.timeoutMs)
|
|
440
|
+
|| raw.timeoutMs <= 0 || raw.timeoutMs > MAX_EXECUTION_TIMEOUT_MS)) {
|
|
441
|
+
throw new Error('execution.timeoutMs must be an integer between 1 and 2147483647 for a model-backed Trigger');
|
|
442
|
+
}
|
|
431
443
|
const execution = {
|
|
432
444
|
type,
|
|
445
|
+
...(raw.timeoutMs !== undefined ? { timeoutMs: raw.timeoutMs } : {}),
|
|
433
446
|
prompt: type === 'script' ? optionalString(raw.prompt) : requiredString(raw.prompt, 'execution.prompt'),
|
|
434
447
|
script: type === 'script' ? normalizeScript(raw.script) : undefined,
|
|
435
448
|
thread: type === 'trigger_session' ? (thread ?? 'by_trigger') : thread,
|
|
@@ -511,8 +524,8 @@ function normalizeFeedback(value, agentAid) {
|
|
|
511
524
|
throw new Error('feedback must be an object');
|
|
512
525
|
const raw = value;
|
|
513
526
|
const strategy = requiredString(raw.strategy, 'feedback.strategy');
|
|
514
|
-
if (strategy !== 'origin' && strategy !== 'target' && strategy !== 'silent') {
|
|
515
|
-
throw new Error('feedback.strategy must be origin, target, or silent');
|
|
527
|
+
if (strategy !== 'origin' && strategy !== 'target' && strategy !== 'event_session' && strategy !== 'silent') {
|
|
528
|
+
throw new Error('feedback.strategy must be origin, target, event_session, or silent');
|
|
516
529
|
}
|
|
517
530
|
return {
|
|
518
531
|
strategy,
|
|
@@ -630,6 +643,10 @@ function normalizeOnError(value) {
|
|
|
630
643
|
}
|
|
631
644
|
function validateTriggerSemantics(definition) {
|
|
632
645
|
const execution = definition.execution;
|
|
646
|
+
if (definition.feedback.strategy === 'event_session'
|
|
647
|
+
&& (definition.source.type !== 'event' || execution.type !== 'target_session')) {
|
|
648
|
+
throw new Error('feedback.strategy=event_session requires an event source and target_session execution');
|
|
649
|
+
}
|
|
633
650
|
if (execution.type === 'script') {
|
|
634
651
|
if (!execution.script)
|
|
635
652
|
throw new Error('execution.script is required when execution.type=script');
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import path from 'path';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import { randomUUID } from 'crypto';
|
|
20
|
+
import { isProcessRunning } from './cross-platform.js';
|
|
18
21
|
const HOT = '_'; // foo.json_ 旧版热备
|
|
19
22
|
const TMP = '__'; // foo.json__ 写入中
|
|
20
23
|
const HOT_BACKUP_FALLBACK_CODES = new Set(['EACCES', 'ENOSYS', 'ENOTSUP', 'EPERM', 'EXDEV']);
|
|
@@ -178,3 +181,98 @@ export function atomicWriteBytes(filePath, content) {
|
|
|
178
181
|
fs.writeFileSync(tmp, content);
|
|
179
182
|
fs.renameSync(tmp, filePath);
|
|
180
183
|
}
|
|
184
|
+
const pauseFileWrite = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
185
|
+
/** Cross-process lock for package state. A complete owner file is linked into place atomically. */
|
|
186
|
+
export async function withFileLock(file, action, timeoutMs = 30000) {
|
|
187
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
188
|
+
const token = randomUUID();
|
|
189
|
+
const candidate = `${file}.${process.pid}.${token}.candidate`;
|
|
190
|
+
const owner = JSON.stringify({ token, pid: process.pid, host: os.hostname() });
|
|
191
|
+
fs.writeFileSync(candidate, owner, { flag: 'wx', mode: 0o600 });
|
|
192
|
+
const deadline = Date.now() + timeoutMs;
|
|
193
|
+
try {
|
|
194
|
+
while (true) {
|
|
195
|
+
try {
|
|
196
|
+
fs.linkSync(candidate, file);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error.code !== 'EEXIST')
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
205
|
+
const existing = JSON.parse(raw);
|
|
206
|
+
if (typeof existing.token === 'string' && /^[a-f0-9-]{36}$/.test(existing.token)
|
|
207
|
+
&& existing.host === os.hostname() && existing.pid !== process.pid
|
|
208
|
+
&& Number.isSafeInteger(existing.pid) && existing.pid > 0 && !isProcessRunning(existing.pid)
|
|
209
|
+
&& fs.readFileSync(file, 'utf8') === raw) {
|
|
210
|
+
fs.unlinkSync(file);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch { /* Unknown or concurrently released owner: wait. */ }
|
|
215
|
+
if (Date.now() >= deadline)
|
|
216
|
+
throw new Error('Package state lock timed out');
|
|
217
|
+
await pauseFileWrite(40 + Math.floor(Math.random() * 20));
|
|
218
|
+
}
|
|
219
|
+
const lockIdentity = fs.statSync(file);
|
|
220
|
+
try {
|
|
221
|
+
return await action();
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
const releaseDeadline = Date.now() + 5000;
|
|
225
|
+
while (true) {
|
|
226
|
+
try {
|
|
227
|
+
const current = fs.statSync(file);
|
|
228
|
+
if (current.dev !== lockIdentity.dev || current.ino !== lockIdentity.ino) {
|
|
229
|
+
throw new Error('Package lock ownership changed');
|
|
230
|
+
}
|
|
231
|
+
fs.unlinkSync(file);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error.code === 'ENOENT')
|
|
236
|
+
break;
|
|
237
|
+
if (!['EPERM', 'EACCES', 'EBUSY'].includes(error.code ?? '')
|
|
238
|
+
|| Date.now() >= releaseDeadline)
|
|
239
|
+
throw error;
|
|
240
|
+
await pauseFileWrite(50);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
fs.rmSync(candidate, { force: true });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Atomic replacement without the in-place Windows fallback used by protected config files. */
|
|
250
|
+
export async function atomicReplaceJson(file, value) {
|
|
251
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
252
|
+
try {
|
|
253
|
+
const fd = fs.openSync(temporary, 'wx', 0o600);
|
|
254
|
+
try {
|
|
255
|
+
fs.writeFileSync(fd, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
256
|
+
fs.fsyncSync(fd);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
fs.closeSync(fd);
|
|
260
|
+
}
|
|
261
|
+
const deadline = Date.now() + 5000;
|
|
262
|
+
for (let attempt = 0;; attempt++) {
|
|
263
|
+
try {
|
|
264
|
+
fs.renameSync(temporary, file);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (!['EPERM', 'EACCES', 'EBUSY'].includes(error.code ?? '')
|
|
269
|
+
|| Date.now() >= deadline)
|
|
270
|
+
throw error;
|
|
271
|
+
await pauseFileWrite(Math.min(200, 25 * (attempt + 1)) + Math.floor(Math.random() * 25));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
fs.rmSync(temporary, { force: true });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -6,6 +6,7 @@ import { promisify } from 'util';
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import { getProcessStartTime, parseCimDate } from './process-introspect.js';
|
|
8
8
|
import { decodeWindowsOutput } from './windows-output.js';
|
|
9
|
+
import { resolveTrustedWindowsCmdPath } from './windows-shell-trust.js';
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
const CLI_PROBE_FAILURE_BACKOFF_MS = 5_000;
|
|
11
12
|
const CLI_PROBE_PACKAGES = {
|
|
@@ -17,9 +18,70 @@ const cliPathCache = new Map();
|
|
|
17
18
|
const cliInstallationCache = new Map();
|
|
18
19
|
const cliProbeCache = new Map();
|
|
19
20
|
export const isWindows = process.platform === 'win32';
|
|
21
|
+
// Keep this in sync with the Windows command-line metacharacters. The whole
|
|
22
|
+
// `/c` expression is passed as one verbatim argument so paths with spaces and
|
|
23
|
+
// shim-forwarded values such as `%`, `!`, `&`, and quotes stay data.
|
|
24
|
+
const WINDOWS_CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
|
|
25
|
+
function escapeWindowsCmdCommand(value) {
|
|
26
|
+
return value.replace(WINDOWS_CMD_META_RE, '^$1');
|
|
27
|
+
}
|
|
28
|
+
function escapeWindowsCmdArgument(value) {
|
|
29
|
+
let escaped = String(value);
|
|
30
|
+
escaped = escaped.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
|
|
31
|
+
escaped = escaped.replace(/(?=(\\+?)?)\1$/g, '$1$1');
|
|
32
|
+
escaped = `"${escaped}"`;
|
|
33
|
+
return escaped.replace(WINDOWS_CMD_META_RE, '^$1');
|
|
34
|
+
}
|
|
35
|
+
/** Build one argv-based CLI launch without consulting the parent terminal. */
|
|
36
|
+
export function buildCliLaunchCommand(executable, args, options = {}) {
|
|
37
|
+
const windows = options.isWindows ?? isWindows;
|
|
38
|
+
const extension = windows ? path.win32.extname(executable).toLowerCase() : '';
|
|
39
|
+
const shim = extension === '.cmd' || extension === '.bat';
|
|
40
|
+
if (!shim) {
|
|
41
|
+
return {
|
|
42
|
+
command: executable,
|
|
43
|
+
args,
|
|
44
|
+
resolvedPath: executable,
|
|
45
|
+
launcherKind: 'native-exe',
|
|
46
|
+
launcherPath: executable,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const resolveWindowsCmd = options.resolveWindowsCmd ?? resolveTrustedWindowsCmdPath;
|
|
50
|
+
const launcherPath = resolveWindowsCmd(options.comSpec);
|
|
51
|
+
if (!launcherPath) {
|
|
52
|
+
throw Object.assign(new Error('未找到受信任的系统 cmd.exe,已拒绝启动 Windows CLI shim'), {
|
|
53
|
+
code: 'EACCES',
|
|
54
|
+
path: executable,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const shellCommand = [
|
|
58
|
+
escapeWindowsCmdCommand(executable),
|
|
59
|
+
...args.map(escapeWindowsCmdArgument),
|
|
60
|
+
].join(' ');
|
|
61
|
+
return {
|
|
62
|
+
command: launcherPath,
|
|
63
|
+
args: ['/d', '/s', '/v:off', '/c', `"${shellCommand}"`],
|
|
64
|
+
resolvedPath: executable,
|
|
65
|
+
launcherKind: extension === '.cmd' ? 'cmd-shim' : 'bat-shim',
|
|
66
|
+
launcherPath,
|
|
67
|
+
windowsVerbatimArguments: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
20
70
|
/** Resolve a CLI executable without spawning where/which. */
|
|
21
71
|
export function resolveProbePath(command) {
|
|
22
|
-
const
|
|
72
|
+
const extensions = isWindows
|
|
73
|
+
? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(ext => /\.(com|exe|bat|cmd)$/i.test(ext))
|
|
74
|
+
: [''];
|
|
75
|
+
return resolveProbePathWithExtensions(command, extensions, 'executable');
|
|
76
|
+
}
|
|
77
|
+
/** Diagnose an incomplete npm installation without ever executing its PS1 shim. */
|
|
78
|
+
export function resolvePowerShellShimPath(command) {
|
|
79
|
+
if (!isWindows)
|
|
80
|
+
return null;
|
|
81
|
+
return resolveProbePathWithExtensions(command, ['.ps1'], 'powershell-shim');
|
|
82
|
+
}
|
|
83
|
+
function resolveProbePathWithExtensions(command, extensions, cacheKind) {
|
|
84
|
+
const pathKey = `${cacheKind}\0${command}\0${process.cwd()}\0${process.env.PATH ?? ''}\0${extensions.join(';')}`;
|
|
23
85
|
const cachedPath = cliPathCache.get(pathKey);
|
|
24
86
|
if (cachedPath && fs.existsSync(cachedPath))
|
|
25
87
|
return cachedPath;
|
|
@@ -27,9 +89,6 @@ export function resolveProbePath(command) {
|
|
|
27
89
|
const directories = (process.env.PATH ?? '').split(path.delimiter);
|
|
28
90
|
if (isWindows)
|
|
29
91
|
directories.unshift(process.cwd());
|
|
30
|
-
const extensions = isWindows
|
|
31
|
-
? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(ext => /\.(com|exe|bat|cmd)$/i.test(ext))
|
|
32
|
-
: [''];
|
|
33
92
|
for (const directory of directories) {
|
|
34
93
|
for (const extension of extensions) {
|
|
35
94
|
const candidate = path.resolve(directory.replace(/^"|"$/g, ''), command + extension);
|
|
@@ -98,20 +157,25 @@ export function invalidateCliProbes() {
|
|
|
98
157
|
function cliProbeTimeouts(command, options) {
|
|
99
158
|
return options.timeouts?.length ? options.timeouts : command === 'codex' ? [3_000, 15_000] : [3_000];
|
|
100
159
|
}
|
|
101
|
-
function prepareCliProbe(command, args) {
|
|
102
|
-
const executable = resolveProbePath(command);
|
|
160
|
+
function prepareCliProbe(command, args, options = {}) {
|
|
161
|
+
const executable = options.executable ?? resolveProbePath(command);
|
|
103
162
|
if (!executable)
|
|
104
163
|
throw Object.assign(new Error(`${command} CLI not found`), { code: 'ENOENT' });
|
|
105
|
-
const
|
|
164
|
+
const launchCommand = buildCliLaunchCommand(executable, args);
|
|
165
|
+
const launch = launchCommand;
|
|
166
|
+
const identity = `${probeInstallationIdentity(command, executable)}|${cliFileIdentity(launch.launcherPath)}`;
|
|
106
167
|
const key = JSON.stringify([command, args]);
|
|
107
168
|
let entry = cliProbeCache.get(key);
|
|
108
169
|
if (!entry || entry.identity !== identity || (entry.retryAt !== undefined && Date.now() >= entry.retryAt)) {
|
|
109
170
|
entry = { identity };
|
|
110
171
|
cliProbeCache.set(key, entry);
|
|
111
172
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
173
|
+
return {
|
|
174
|
+
entry,
|
|
175
|
+
command: launchCommand.command,
|
|
176
|
+
args: launchCommand.args,
|
|
177
|
+
launch,
|
|
178
|
+
};
|
|
115
179
|
}
|
|
116
180
|
function failCliProbe(entry, error) {
|
|
117
181
|
entry.error = error;
|
|
@@ -119,7 +183,7 @@ function failCliProbe(entry, error) {
|
|
|
119
183
|
throw error;
|
|
120
184
|
}
|
|
121
185
|
export function probeCliSync(command, args, options = {}) {
|
|
122
|
-
const request = prepareCliProbe(command, args);
|
|
186
|
+
const request = prepareCliProbe(command, args, options);
|
|
123
187
|
if (request.entry.output !== undefined)
|
|
124
188
|
return request.entry.output;
|
|
125
189
|
if (request.entry.error !== undefined)
|
|
@@ -127,10 +191,14 @@ export function probeCliSync(command, args, options = {}) {
|
|
|
127
191
|
for (const [index, timeout] of cliProbeTimeouts(command, options).entries()) {
|
|
128
192
|
const startedAt = Date.now();
|
|
129
193
|
try {
|
|
130
|
-
return request.entry.output = execFileSync(request.command, request.args, {
|
|
194
|
+
return request.entry.output = execFileSync(request.command, request.args, {
|
|
195
|
+
encoding: 'utf8', timeout, windowsHide: true,
|
|
196
|
+
windowsVerbatimArguments: request.launch.windowsVerbatimArguments,
|
|
197
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
198
|
+
});
|
|
131
199
|
}
|
|
132
200
|
catch (error) {
|
|
133
|
-
options.onFailure?.(args, startedAt, error, timeout);
|
|
201
|
+
options.onFailure?.(args, startedAt, error, timeout, request.launch);
|
|
134
202
|
if (error.code !== 'ETIMEDOUT' || index === cliProbeTimeouts(command, options).length - 1)
|
|
135
203
|
return failCliProbe(request.entry, error);
|
|
136
204
|
}
|
|
@@ -138,7 +206,7 @@ export function probeCliSync(command, args, options = {}) {
|
|
|
138
206
|
throw new Error('CLI probe exhausted');
|
|
139
207
|
}
|
|
140
208
|
export async function probeCli(command, args, options = {}) {
|
|
141
|
-
const request = prepareCliProbe(command, args);
|
|
209
|
+
const request = prepareCliProbe(command, args, options);
|
|
142
210
|
if (request.entry.output !== undefined)
|
|
143
211
|
return request.entry.output;
|
|
144
212
|
if (request.entry.error !== undefined)
|
|
@@ -151,7 +219,10 @@ export async function probeCli(command, args, options = {}) {
|
|
|
151
219
|
const startedAt = Date.now();
|
|
152
220
|
try {
|
|
153
221
|
const output = await new Promise((resolve, reject) => {
|
|
154
|
-
const child = execFile(request.command, request.args, {
|
|
222
|
+
const child = execFile(request.command, request.args, {
|
|
223
|
+
encoding: 'utf8', timeout, windowsHide: true,
|
|
224
|
+
windowsVerbatimArguments: request.launch.windowsVerbatimArguments,
|
|
225
|
+
}, (error, stdout, stderr) => {
|
|
155
226
|
if (error) {
|
|
156
227
|
if (error.killed && error.signal === 'SIGTERM' && error.code == null)
|
|
157
228
|
error.code = 'ETIMEDOUT';
|
|
@@ -166,7 +237,7 @@ export async function probeCli(command, args, options = {}) {
|
|
|
166
237
|
return request.entry.output = output;
|
|
167
238
|
}
|
|
168
239
|
catch (error) {
|
|
169
|
-
options.onFailure?.(args, startedAt, error, timeout);
|
|
240
|
+
options.onFailure?.(args, startedAt, error, timeout, request.launch);
|
|
170
241
|
if (error.code !== 'ETIMEDOUT' || index === timeouts.length - 1)
|
|
171
242
|
return failCliProbe(request.entry, error);
|
|
172
243
|
}
|
|
@@ -70,6 +70,7 @@ export async function fetchRemotePairingCode(authorizedBy) {
|
|
|
70
70
|
try {
|
|
71
71
|
const response = await ipcQuery(resolvePaths().socket, {
|
|
72
72
|
type: 'remote-auth.pair-code',
|
|
73
|
+
source: 'host-cli',
|
|
73
74
|
...(authorizedBy?.trim() ? { authorizedBy: authorizedBy.trim() } : {}),
|
|
74
75
|
}, 2_000);
|
|
75
76
|
if (!response?.ok || typeof response.code !== 'string' || typeof response.expiresAt !== 'number')
|
|
@@ -3,6 +3,27 @@ import path from 'path';
|
|
|
3
3
|
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
4
4
|
import { logger } from './logger.js';
|
|
5
5
|
import { isSandboxInitializationFailure } from '../core/permission/sandbox-runtime.js';
|
|
6
|
+
/** A deadline imposed by an outer executor, handled by the ordinary task timeout path. */
|
|
7
|
+
export class ExecutionTimeoutError extends Error {
|
|
8
|
+
reason;
|
|
9
|
+
timeoutMs;
|
|
10
|
+
constructor(reason, timeoutMs) {
|
|
11
|
+
super('TOTAL_EXECUTION_TIMEOUT');
|
|
12
|
+
this.reason = reason;
|
|
13
|
+
this.timeoutMs = timeoutMs;
|
|
14
|
+
this.name = 'ExecutionTimeoutError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class OrphanedTargetSessionError extends Error {
|
|
18
|
+
reason = 'orphaned_target_session';
|
|
19
|
+
constructor() {
|
|
20
|
+
super('ORPHANED_TARGET_SESSION');
|
|
21
|
+
this.name = 'OrphanedTargetSessionError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isExecutionFailure(error) {
|
|
25
|
+
return error instanceof ExecutionTimeoutError || error instanceof OrphanedTargetSessionError;
|
|
26
|
+
}
|
|
6
27
|
export const CLAUDE_STARTUP_ARGUMENTS_TOO_LONG = 'claude_startup_arguments_too_long';
|
|
7
28
|
/** A completed provider response exhausted its output budget, rather than a transport failure. */
|
|
8
29
|
export function isOutputTokenLimitError(error) {
|
|
@@ -288,7 +309,15 @@ function hasRetryableHttpStatus(text) {
|
|
|
288
309
|
return RETRYABLE_HTTP_STATUS_PATTERN.test(text);
|
|
289
310
|
}
|
|
290
311
|
export function classifyError(error) {
|
|
312
|
+
if (error?.code === 'RUNNER_INTERRUPT_UNCONFIRMED')
|
|
313
|
+
return ErrorType.SESSION_UNAVAILABLE;
|
|
291
314
|
const msg = (error?.message || '').toLowerCase();
|
|
315
|
+
// A damaged ecagent JSONL transcript is deterministic local state. Retrying
|
|
316
|
+
// the provider request cannot repair it and can append more entries to the
|
|
317
|
+
// same unusable file.
|
|
318
|
+
if (msg.includes('invalid jsonl session file') || msg.includes('invalid session file')) {
|
|
319
|
+
return ErrorType.SESSION_UNAVAILABLE;
|
|
320
|
+
}
|
|
292
321
|
// The broad dictionary rule for "stream" must not hide a provider budget limit.
|
|
293
322
|
if (isOutputTokenLimitError(error))
|
|
294
323
|
return ErrorType.MAX_OUTPUT_TOKENS;
|
|
@@ -346,11 +375,15 @@ export function classifyError(error) {
|
|
|
346
375
|
* 注意:401 认证错误不可重试(API Key 无效不会因重试恢复)
|
|
347
376
|
*/
|
|
348
377
|
export function isRetryableError(error) {
|
|
378
|
+
if (error?.code === 'RUNNER_INTERRUPT_UNCONFIRMED')
|
|
379
|
+
return false;
|
|
349
380
|
// Replaying the same request does not increase its output budget.
|
|
350
381
|
if (isOutputTokenLimitError(error))
|
|
351
382
|
return false;
|
|
352
383
|
const msg = error?.message || String(error);
|
|
353
384
|
const lower = msg.toLowerCase();
|
|
385
|
+
if (lower.includes('invalid jsonl session file') || lower.includes('invalid session file'))
|
|
386
|
+
return false;
|
|
354
387
|
// 字典优先 — 命中则直接返回
|
|
355
388
|
const rule = matchErrorRule(lower);
|
|
356
389
|
if (rule)
|
|
@@ -366,6 +399,8 @@ export function isRetryableError(error) {
|
|
|
366
399
|
return false;
|
|
367
400
|
}
|
|
368
401
|
export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
402
|
+
if (error?.code === 'RUNNER_INTERRUPT_UNCONFIRMED')
|
|
403
|
+
return `${includeEmoji ? '⚠️ ' : ''}${error.message}`;
|
|
369
404
|
if (terminalReason === 'max_output_tokens' || isOutputTokenLimitError(error)) {
|
|
370
405
|
const prefix = includeEmoji ? '⚠️ ' : '';
|
|
371
406
|
return `${prefix}模型响应达到输出上限(max_output_tokens),本轮未完成。`
|
|
@@ -395,7 +430,7 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
395
430
|
case 'permission_denied':
|
|
396
431
|
return `${prefix}权限被拒绝,操作已取消`;
|
|
397
432
|
case 'sandbox_initialization_failed':
|
|
398
|
-
return `${warnPrefix}
|
|
433
|
+
return `${warnPrefix}执行沙箱启动失败(sandbox_initialization_failed),当前操作未执行;请检查文件系统挂载规则及 user namespace/seccomp 配置`;
|
|
399
434
|
case 'claude_startup_arguments_too_long':
|
|
400
435
|
return `${warnPrefix}当前 Claude 启动参数过长,任务未执行`;
|
|
401
436
|
case 'aborted_streaming':
|
|
@@ -407,6 +442,9 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
407
442
|
const msg = error?.message || String(error);
|
|
408
443
|
const warnPrefix = includeEmoji ? '⚠️ ' : '';
|
|
409
444
|
const errPrefix = includeEmoji ? '❌ ' : '';
|
|
445
|
+
if (/invalid (?:jsonl )?session file/i.test(msg)) {
|
|
446
|
+
return `${errPrefix}ecagent 会话文件损坏,已停止自动重试;请使用 /repair 或 /new 恢复会话`;
|
|
447
|
+
}
|
|
410
448
|
// Codex app-server reports this when another app-server still owns the
|
|
411
449
|
// thread writer. Surface the handoff condition instead of hiding it behind
|
|
412
450
|
// the generic "processing failed" message.
|
|
@@ -429,7 +467,7 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
429
467
|
: `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
|
|
430
468
|
}
|
|
431
469
|
if (isSandboxInitializationFailure(error)) {
|
|
432
|
-
return `${warnPrefix}
|
|
470
|
+
return `${warnPrefix}执行沙箱启动失败(sandbox_initialization_failed),当前操作未执行;请检查文件系统挂载规则及 user namespace/seccomp 配置`;
|
|
433
471
|
}
|
|
434
472
|
if (msg.includes('401') || msg.includes('authentication_error') || msg.toLowerCase().includes('unauthorized')) {
|
|
435
473
|
return `${errPrefix}API Key 无效,请检查密钥配置。使用 /status 查看当前配置`;
|
package/dist/utils/logger.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolvePaths } from '../paths.js';
|
|
|
3
3
|
import { LogWriter } from './log-writer.js';
|
|
4
4
|
import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
5
5
|
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
6
|
-
import { buildToolLifecycleEventKey } from '../core/audit/event-key.js';
|
|
6
|
+
import { buildToolLifecycleEventKey, normalizeToolCorrelation } from '../core/audit/event-key.js';
|
|
7
7
|
let currentLevel = process.env.LOG_LEVEL || 'INFO';
|
|
8
8
|
const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
|
9
9
|
const config = {
|
|
@@ -53,8 +53,18 @@ export function normalizeStructuredLog(data) {
|
|
|
53
53
|
const nested = data.event && typeof data.event === 'object' ? data.event : undefined;
|
|
54
54
|
const eventType = data.type ?? nested?.type;
|
|
55
55
|
const eventKey = data.eventKey ?? data.event_key ?? nested?.eventKey ?? nested?.event_key;
|
|
56
|
-
const
|
|
57
|
-
const
|
|
56
|
+
const isToolResult = eventType === 'tool:result' || eventType === 'tool_result';
|
|
57
|
+
const isToolUse = eventType === 'tool:use' || eventType === 'tool_use';
|
|
58
|
+
const lifecycleRecord = isToolUse || isToolResult;
|
|
59
|
+
const eventPhase = data.eventPhase ?? data.event_phase ?? nested?.eventPhase ?? nested?.event_phase
|
|
60
|
+
?? (isToolUse ? 'use' : isToolResult ? 'result' : undefined);
|
|
61
|
+
const toolCorrelation = normalizeToolCorrelation({
|
|
62
|
+
callId: data.callId ?? data.call_id ?? data.toolUseId ?? data.tool_use_id
|
|
63
|
+
?? nested?.callId ?? nested?.call_id ?? nested?.toolUseId ?? nested?.tool_use_id,
|
|
64
|
+
correlationId: data.correlationId ?? data.correlation_id ?? nested?.correlationId ?? nested?.correlation_id,
|
|
65
|
+
requestId: data.requestId ?? data.request_id ?? nested?.requestId ?? nested?.request_id,
|
|
66
|
+
});
|
|
67
|
+
const correlationId = lifecycleRecord ? toolCorrelation.correlationId : data.correlationId
|
|
58
68
|
?? data.correlation_id
|
|
59
69
|
?? data.callId
|
|
60
70
|
?? data.call_id
|
|
@@ -69,7 +79,9 @@ export function normalizeStructuredLog(data) {
|
|
|
69
79
|
?? nested?.callId
|
|
70
80
|
?? nested?.call_id
|
|
71
81
|
?? nested?.toolUseId
|
|
72
|
-
?? nested?.tool_use_id
|
|
82
|
+
?? nested?.tool_use_id
|
|
83
|
+
?? nested?.requestId
|
|
84
|
+
?? nested?.request_id;
|
|
73
85
|
const sessionId = data.sessionId ?? data.session_id ?? nested?.sessionId ?? nested?.session_id;
|
|
74
86
|
const agentAid = data.agentAid ?? data.agent_aid ?? data.selfAid ?? nested?.agentAid ?? nested?.agent_aid ?? nested?.selfAid;
|
|
75
87
|
const rawAgentName = data.agentName ?? data.agent_name ?? nested?.agentName ?? nested?.agent_name;
|
|
@@ -91,18 +103,17 @@ export function normalizeStructuredLog(data) {
|
|
|
91
103
|
? normalizePermissionMode(rawPermissionMode).mode
|
|
92
104
|
: rawPermissionMode;
|
|
93
105
|
const toolName = data.toolName ?? data.tool ?? data.name ?? nested?.toolName ?? nested?.name;
|
|
94
|
-
const isToolResult = eventType === 'tool:result' || eventType === 'tool_result';
|
|
95
|
-
const isToolUse = eventType === 'tool:use' || eventType === 'tool_use';
|
|
96
106
|
const isError = data.isError ?? data.is_error ?? (data.ok === false ? true : undefined)
|
|
97
107
|
?? nested?.isError ?? nested?.is_error ?? (nested?.ok === false ? true : undefined);
|
|
98
108
|
const decisionSource = data.decisionSource ?? data.decision_source
|
|
99
|
-
?? nested?.decisionSource ?? nested?.decision_source
|
|
109
|
+
?? nested?.decisionSource ?? nested?.decision_source
|
|
110
|
+
?? (lifecycleRecord ? 'runner' : undefined);
|
|
100
111
|
const decision = data.decision
|
|
112
|
+
?? nested?.decision
|
|
101
113
|
?? data.status
|
|
102
114
|
?? (isToolResult && isError === true ? 'error' : undefined)
|
|
103
115
|
?? (isToolResult && isError === false ? 'allow' : undefined)
|
|
104
|
-
?? (isToolUse ? '
|
|
105
|
-
?? nested?.decision;
|
|
116
|
+
?? (isToolUse ? 'pending' : undefined);
|
|
106
117
|
const executed = data.executed ?? nested?.executed
|
|
107
118
|
?? (isToolResult ? true : isToolUse ? false : undefined);
|
|
108
119
|
const executionState = data.executionState ?? data.execution_state
|
|
@@ -118,13 +129,10 @@ export function normalizeStructuredLog(data) {
|
|
|
118
129
|
const classificationMissing = isToolResult && isError === true
|
|
119
130
|
&& !normalizeToolErrorCode(explicitErrorCode)
|
|
120
131
|
&& decisionSource !== 'policy' && decisionSource !== 'approval';
|
|
121
|
-
const lifecycleRecord = isToolUse || isToolResult;
|
|
122
132
|
const lifecycleEventKey = lifecycleRecord
|
|
123
133
|
? (eventKey ?? buildToolLifecycleEventKey({
|
|
124
134
|
sessionId,
|
|
125
|
-
|
|
126
|
-
correlationId: data.correlationId ?? data.correlation_id ?? nested?.correlationId ?? nested?.correlation_id,
|
|
127
|
-
requestId: data.requestId ?? data.request_id ?? nested?.requestId ?? nested?.request_id,
|
|
135
|
+
...toolCorrelation,
|
|
128
136
|
}))
|
|
129
137
|
: undefined;
|
|
130
138
|
const missingContextFields = lifecycleRecord
|
|
@@ -134,6 +142,8 @@ export function normalizeStructuredLog(data) {
|
|
|
134
142
|
...data,
|
|
135
143
|
...(lifecycleEventKey ? { eventKey: lifecycleEventKey } : {}),
|
|
136
144
|
...(eventPhase ? { eventPhase } : {}),
|
|
145
|
+
...(lifecycleRecord ? toolCorrelation : {}),
|
|
146
|
+
...(lifecycleRecord && !toolCorrelation.callId ? { correlationMissing: true } : {}),
|
|
137
147
|
...(correlationId ? { correlationId } : {}),
|
|
138
148
|
...(sessionId ? { sessionId } : lifecycleRecord ? { sessionId: 'unknown' } : {}),
|
|
139
149
|
...(agentAid ? { agentAid } : lifecycleRecord ? { agentAid: 'unknown' } : {}),
|