evolcore 0.0.21 → 0.0.23

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.
Files changed (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
@@ -1,7 +1,11 @@
1
1
  import { CronExpressionParser } from 'cron-parser';
2
2
  const TRIGGER_UPDATE_FLAGS = new Set([
3
3
  'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
4
- 'prompt', 'name',
4
+ 'prompt', 'name', 'description',
5
+ 'script-file', 'script-runtime', 'script-args', 'script-timeout',
6
+ 'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
7
+ 'trigger-thread', 'baseagent',
8
+ 'on-error', 'noop-sentinel', 'retry-attempts', 'retry-backoff',
5
9
  'model', 'effort', 'permission',
6
10
  'max-runs', 'max-duration', 'concurrency', 'missed-policy',
7
11
  ]);
@@ -395,18 +399,22 @@ function commonParsed(flags, opts = {}) {
395
399
  if (!opts.update && !execution.value) {
396
400
  return { ok: false, error: '创建 trigger 必须显式指定 --exec script|trigger-session|target-session' };
397
401
  }
402
+ // update 场景下执行类型未知(不重命名 --exec),execution-type 相关校验
403
+ // 全部交给 patch/validation 按现有定义执行。
398
404
  const inferredExecution = execution.value;
399
- if (baseagent.value && inferredExecution !== 'trigger_session') {
400
- return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
401
- }
402
- if (inferredExecution === 'script') {
403
- if (!scriptPath && !opts.update)
404
- return { ok: false, error: '--exec script 需要 --script-path' };
405
- if (scriptPath && !scriptRuntime && !opts.update)
406
- return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
407
- }
408
- else if (!prompt && !opts.update) {
409
- return { ok: false, error: '--prompt 为必填项' };
405
+ if (!opts.update) {
406
+ if (baseagent.value && inferredExecution !== 'trigger_session') {
407
+ return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
408
+ }
409
+ if (inferredExecution === 'script') {
410
+ if (!scriptPath)
411
+ return { ok: false, error: '--exec script 需要 --script-path' };
412
+ if (scriptPath && !scriptRuntime)
413
+ return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
414
+ }
415
+ else if (!prompt) {
416
+ return { ok: false, error: '--prompt 为必填项' };
417
+ }
410
418
  }
411
419
  const targetSessionValue = targetSession.value ?? (targetThreadId ? 'thread' : 'main');
412
420
  const feedbackStrategy = feedback.value ?? (hasTargetField ? 'target' : 'origin');
@@ -552,7 +560,7 @@ export function parseTriggerUpdateArgv(nameOrId, args, opts = {}) {
552
560
  const parsedFlags = flagsFromArgv(args, 'update');
553
561
  if (!parsedFlags.ok)
554
562
  return { ok: false, error: withPromptFileHint(parsedFlags.error, args, opts) };
555
- const parsed = parseTriggerUpdateFlags(nameOrId, parsedFlags.flags, opts);
563
+ const parsed = parseTriggerUpdateFlags(nameOrId, parsedFlags.flags, { ...opts, allowScriptFile: true });
556
564
  return parsed.ok ? parsed : { ok: false, error: withPromptFileHint(parsed.error, args, opts) };
557
565
  }
558
566
  function flagsFromArgv(args, command) {
@@ -585,6 +593,9 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
585
593
  if (!TRIGGER_UPDATE_FLAGS.has(flag))
586
594
  return { ok: false, error: `update 不支持参数 --${flag}` };
587
595
  }
596
+ if (flags.has('script-file') && !opts.allowScriptFile) {
597
+ return { ok: false, error: '--script-file 仅支持 ec trigger update CLI;请通过受控 CLI 上传脚本文件' };
598
+ }
588
599
  const parsed = commonParsed(flags, { update: true, ...opts });
589
600
  if (!parsed.ok)
590
601
  return parsed;
@@ -607,6 +618,11 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
607
618
  update.name = value.name;
608
619
  if (value.prompt !== undefined)
609
620
  update.prompt = value.prompt;
621
+ const scriptFile = optionalFlagString(flags, 'script-file');
622
+ if ('error' in scriptFile)
623
+ return { ok: false, error: scriptFile.error };
624
+ if (scriptFile.value !== undefined)
625
+ update.scriptFile = scriptFile.value;
610
626
  if (value.model !== undefined)
611
627
  update.model = value.model;
612
628
  if (value.effort !== undefined)
@@ -636,6 +652,63 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
636
652
  }
637
653
  update.missedPolicy = normalized;
638
654
  }
655
+ const description = optionalFlagString(flags, 'description');
656
+ if ('error' in description)
657
+ return { ok: false, error: description.error };
658
+ if (description.value !== undefined)
659
+ update.description = description.value || null;
660
+ if (flags.has('feedback') && value.feedbackStrategy !== undefined)
661
+ update.feedback = value.feedbackStrategy;
662
+ if (value.targetChannel !== undefined)
663
+ update.targetChannel = value.targetChannel;
664
+ if (value.targetChannelId !== undefined)
665
+ update.targetChannelId = value.targetChannelId;
666
+ if (value.targetChatType !== undefined)
667
+ update.targetChatType = value.targetChatType;
668
+ if (value.targetSession !== undefined && flags.has('target-session'))
669
+ update.targetSession = value.targetSession;
670
+ if (value.targetThreadId !== undefined)
671
+ update.targetThreadId = value.targetThreadId;
672
+ if (value.triggerThread !== undefined)
673
+ update.triggerThread = value.triggerThread;
674
+ if (value.baseagent !== undefined)
675
+ update.baseagent = value.baseagent;
676
+ const onError = optionalFlagString(flags, 'on-error');
677
+ if ('error' in onError)
678
+ return { ok: false, error: onError.error };
679
+ if (onError.value !== undefined) {
680
+ if (onError.value !== 'fail' && onError.value !== 'retry') {
681
+ return { ok: false, error: '--on-error 只接受 fail 或 retry' };
682
+ }
683
+ update.onError = onError.value;
684
+ }
685
+ const noopSentinel = optionalFlagString(flags, 'noop-sentinel');
686
+ if ('error' in noopSentinel)
687
+ return { ok: false, error: noopSentinel.error };
688
+ if (noopSentinel.value !== undefined)
689
+ update.noopSentinel = noopSentinel.value || null;
690
+ const retryAttempts = optionalFlagString(flags, 'retry-attempts');
691
+ if ('error' in retryAttempts)
692
+ return { ok: false, error: retryAttempts.error };
693
+ if (retryAttempts.value !== undefined) {
694
+ if (!/^\d+$/.test(retryAttempts.value))
695
+ return { ok: false, error: '--retry-attempts 必须是非负整数' };
696
+ update.retryAttempts = Number(retryAttempts.value);
697
+ }
698
+ const retryBackoff = optionalFlagString(flags, 'retry-backoff');
699
+ if ('error' in retryBackoff)
700
+ return { ok: false, error: retryBackoff.error };
701
+ if (retryBackoff.value !== undefined) {
702
+ if (!/^\d+$/.test(retryBackoff.value))
703
+ return { ok: false, error: '--retry-backoff 必须是毫秒正整数' };
704
+ update.retryBackoff = Number(retryBackoff.value);
705
+ }
706
+ if (value.scriptRuntime !== undefined)
707
+ update.scriptRuntime = value.scriptRuntime;
708
+ if (value.scriptArgs !== undefined)
709
+ update.scriptArgs = value.scriptArgs;
710
+ if (value.scriptTimeoutMs !== undefined)
711
+ update.scriptTimeout = value.scriptTimeoutMs;
639
712
  if (Object.keys(update).length === 0)
640
713
  return { ok: false, error: '没有可更新的 trigger 参数' };
641
714
  return { ok: true, nameOrId, value: update };
@@ -1,6 +1,7 @@
1
1
  import { normalizeTriggerDefinition, parseDurationMs, TRIGGER_SCHEMA_VERSION } from './validation.js';
2
2
  const PATCH_KEYS = new Set([
3
3
  'name',
4
+ 'description',
4
5
  'scheduleType',
5
6
  'scheduleValue',
6
7
  'timezone',
@@ -12,6 +13,21 @@ const PATCH_KEYS = new Set([
12
13
  'maxDuration',
13
14
  'concurrency',
14
15
  'missedPolicy',
16
+ 'feedback',
17
+ 'targetChannel',
18
+ 'targetChannelId',
19
+ 'targetChatType',
20
+ 'targetSession',
21
+ 'targetThreadId',
22
+ 'baseagent',
23
+ 'triggerThread',
24
+ 'onError',
25
+ 'noopSentinel',
26
+ 'retryAttempts',
27
+ 'retryBackoff',
28
+ 'scriptRuntime',
29
+ 'scriptArgs',
30
+ 'scriptTimeout',
15
31
  ]);
16
32
  export function applyTriggerPatch(definition, input, opts = {}) {
17
33
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
@@ -48,6 +64,12 @@ export function applyTriggerPatch(definition, input, opts = {}) {
48
64
  updated.reliability.concurrency = patch.concurrency;
49
65
  if (patch.missedPolicy !== undefined)
50
66
  updated.reliability.missedPolicy = patch.missedPolicy;
67
+ if (patch.description !== undefined) {
68
+ if (patch.description === null || patch.description === '')
69
+ delete updated.description;
70
+ else
71
+ updated.description = String(patch.description);
72
+ }
51
73
  if (patch.maxRuns !== undefined || patch.maxDuration !== undefined) {
52
74
  const limits = updated.limits ? { ...updated.limits } : {};
53
75
  if (patch.maxRuns !== undefined) {
@@ -64,6 +86,85 @@ export function applyTriggerPatch(definition, input, opts = {}) {
64
86
  }
65
87
  updated.limits = Object.keys(limits).length > 0 ? limits : undefined;
66
88
  }
89
+ // feedback strategy
90
+ if (patch.feedback !== undefined) {
91
+ updated.feedback.strategy = patch.feedback;
92
+ if (patch.feedback !== 'target')
93
+ delete updated.feedback.target;
94
+ }
95
+ // feedback target fields
96
+ const hasTargetPatch = patch.targetChannel !== undefined
97
+ || patch.targetChannelId !== undefined
98
+ || patch.targetChatType !== undefined
99
+ || patch.targetSession !== undefined
100
+ || patch.targetThreadId !== undefined;
101
+ if (hasTargetPatch) {
102
+ if ((patch.targetChannel === undefined) !== (patch.targetChannelId === undefined)) {
103
+ throw new Error('targetChannel 与 targetChannelId 必须同时指定');
104
+ }
105
+ const prev = updated.feedback.target;
106
+ const next = {
107
+ channelKey: patch.targetChannel ?? prev?.channelKey ?? '',
108
+ channelId: patch.targetChannelId ?? prev?.channelId ?? '',
109
+ session: patch.targetSession ?? prev?.session ?? 'main',
110
+ threadId: patch.targetThreadId === null || patch.targetThreadId === ''
111
+ ? undefined
112
+ : (patch.targetThreadId ?? prev?.threadId),
113
+ chatType: patch.targetChatType === null
114
+ ? undefined
115
+ : (patch.targetChatType ?? prev?.chatType),
116
+ };
117
+ if (!next.channelKey || !next.channelId) {
118
+ throw new Error('target 修改需要提供有效的 channelKey 和 channelId');
119
+ }
120
+ updated.feedback.target = next;
121
+ if (updated.feedback.strategy !== 'target')
122
+ updated.feedback.strategy = 'target';
123
+ }
124
+ // execution fields
125
+ if (patch.baseagent !== undefined) {
126
+ if (updated.execution.type !== 'trigger_session') {
127
+ throw new Error('baseagent 仅适用于 trigger_session 类型');
128
+ }
129
+ setOptional(updated.execution, 'baseagent', patch.baseagent);
130
+ }
131
+ if (patch.triggerThread !== undefined)
132
+ setOptional(updated.execution, 'thread', patch.triggerThread);
133
+ if (patch.onError !== undefined)
134
+ updated.execution.onError = patch.onError;
135
+ if (patch.noopSentinel !== undefined) {
136
+ if (patch.noopSentinel === null || patch.noopSentinel === '')
137
+ updated.execution.noopSentinel = '[[NOOP]]';
138
+ else
139
+ updated.execution.noopSentinel = patch.noopSentinel;
140
+ }
141
+ // reliability retry
142
+ if (patch.retryAttempts !== undefined || patch.retryBackoff !== undefined) {
143
+ const retry = { ...updated.reliability.retry };
144
+ if (patch.retryAttempts !== undefined)
145
+ retry.maxAttempts = patch.retryAttempts ?? 0;
146
+ if (patch.retryBackoff !== undefined)
147
+ retry.backoffMs = patch.retryBackoff ?? 30_000;
148
+ updated.reliability = { ...updated.reliability, retry };
149
+ }
150
+ // script fields
151
+ if (patch.scriptRuntime !== undefined || patch.scriptArgs !== undefined || patch.scriptTimeout !== undefined) {
152
+ if (updated.execution.type !== 'script' || !updated.execution.script) {
153
+ throw new Error('scriptRuntime/scriptArgs/scriptTimeout 仅适用于 script 类型 trigger');
154
+ }
155
+ const script = { ...updated.execution.script };
156
+ if (patch.scriptRuntime !== undefined)
157
+ script.runtime = patch.scriptRuntime;
158
+ if (patch.scriptArgs !== undefined) {
159
+ if (patch.scriptArgs === null)
160
+ delete script.args;
161
+ else
162
+ script.args = patch.scriptArgs;
163
+ }
164
+ if (patch.scriptTimeout !== undefined)
165
+ script.timeoutMs = patch.scriptTimeout ?? 30_000;
166
+ updated.execution.script = script;
167
+ }
67
168
  return normalizeTriggerDefinition({
68
169
  ...updated,
69
170
  id: definition.id,
@@ -98,7 +199,9 @@ export function replaceEditableTriggerDefinition(existing, input) {
98
199
  }
99
200
  function assertBaseagentImmutable(existing, updated) {
100
201
  if (existing.execution.baseagent !== updated.execution.baseagent) {
101
- throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
202
+ if (existing.execution.type !== 'trigger_session' || updated.execution.type !== 'trigger_session') {
203
+ throw new Error('execution.baseagent is only mutable between trigger_session Triggers');
204
+ }
102
205
  }
103
206
  }
104
207
  function applySourcePatch(updated, patch) {
@@ -120,12 +120,12 @@ export class TriggerRuntimeScheduler {
120
120
  }
121
121
  listItems(definitions) {
122
122
  const latestRuns = this.audit.latest(definitions.map(definition => definition.id));
123
- return definitions.map(definition => this.buildListItem(definition, latestRuns.get(definition.id)));
123
+ return definitions.map(definition => this.buildListItem(definition, latestRuns.get(definition.id), this.stats(definition.id)));
124
124
  }
125
125
  listItem(definition) {
126
- return this.buildListItem(definition, this.audit.recent(definition.id, 1)[0]);
126
+ return this.buildListItem(definition, this.audit.recent(definition.id, 1)[0], this.stats(definition.id));
127
127
  }
128
- buildListItem(definition, lastRun) {
128
+ buildListItem(definition, lastRun, stats = this.stats(definition.id)) {
129
129
  const schedule = this.state.readSchedule(definition.id);
130
130
  return {
131
131
  id: definition.id,
@@ -139,6 +139,10 @@ export class TriggerRuntimeScheduler {
139
139
  ...(definition.feedback.target ? { target: definition.feedback.target } : {}),
140
140
  },
141
141
  ...(schedule ? { nextFireAt: schedule.nextFireAt } : {}),
142
+ fireCount: stats.fireCount,
143
+ failCount: stats.failCount,
144
+ ...(stats.lastFiredAt !== undefined ? { lastFiredAt: stats.lastFiredAt } : {}),
145
+ ...(stats.lastResult ? { lastResult: stats.lastResult } : {}),
142
146
  ...(lastRun ? { lastStatus: lastRun.status, lastFinishedAt: lastRun.finishedAt } : {}),
143
147
  updatedAt: definition.updatedAt,
144
148
  };
@@ -627,6 +631,7 @@ export class TriggerRuntimeScheduler {
627
631
  effects: feedbackResult.effects,
628
632
  error: feedbackResult.error,
629
633
  conflictRunId: feedbackResult.conflictRunId,
634
+ interruption: feedbackResult.interruption,
630
635
  causation,
631
636
  });
632
637
  if (!payload.dryRun) {
@@ -1147,6 +1152,7 @@ export class TriggerRuntimeScheduler {
1147
1152
  anomalies,
1148
1153
  effects: feedbackResult.effects,
1149
1154
  error: feedbackResult.error,
1155
+ interruption: feedbackResult.interruption,
1150
1156
  causation,
1151
1157
  });
1152
1158
  this.audit.write(audit);
@@ -1469,6 +1475,7 @@ export class TriggerRuntimeScheduler {
1469
1475
  status: input.status,
1470
1476
  reason: input.reason,
1471
1477
  conflictRunId: input.conflictRunId,
1478
+ ...(input.interruption ? { interruption: input.interruption } : {}),
1472
1479
  ...(activeRun?.executionSessionId ? { executionSessionId: activeRun.executionSessionId } : {}),
1473
1480
  ...(attempts?.length ? { attempts } : {}),
1474
1481
  ...(finalAttempt ? { finalAttempt: finalAttempt.attempt } : {}),
@@ -1783,6 +1790,7 @@ export class TriggerRuntimeScheduler {
1783
1790
  type: 'trigger:skipped',
1784
1791
  ...base,
1785
1792
  reason: audit.reason ?? 'skipped',
1793
+ ...(audit.interruption ?? {}),
1786
1794
  });
1787
1795
  }
1788
1796
  }
@@ -1900,6 +1908,15 @@ function errorReply(runId, durationMs, reason, text) {
1900
1908
  }
1901
1909
  function attemptSummaryFromExecution(attempt, attemptId, execution) {
1902
1910
  if (execution.feedbackResult) {
1911
+ if (execution.feedbackResult.interruption) {
1912
+ return {
1913
+ status: 'interrupted',
1914
+ error: {
1915
+ code: execution.feedbackResult.interruption.reasonCode,
1916
+ message: execution.feedbackResult.reason ?? execution.feedbackResult.interruption.reasonCode,
1917
+ },
1918
+ };
1919
+ }
1903
1920
  const failed = execution.feedbackResult.status !== 'completed';
1904
1921
  return {
1905
1922
  status: failed ? 'failed' : 'completed',
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { spawn } from 'child_process';
4
- import { resolvePaths } from '../paths.js';
4
+ import { getPackageRoot, resolvePaths } from '../paths.js';
5
5
  import { WEB_PACKAGE_NAME } from '../product.js';
6
6
  import { resolveGlobalPkg } from './npm-ops.js';
7
7
  import { ecwebVersionRequirementError, resolveEcwebLaunchCommand } from './ecweb-utils.js';
@@ -218,7 +218,16 @@ export function createEcwebSupervisor(options) {
218
218
  detached: true,
219
219
  stdio: ['ignore', childLogFd, childLogFd],
220
220
  windowsHide: platform.isWindows,
221
- env: { ...process.env, EVOLCORE_HOME: root },
221
+ // ECWeb dynamically imports EvolCore's dist modules for config,
222
+ // roles, authorization and session compatibility. Pin that lookup
223
+ // to the same package root as this daemon; otherwise a separately
224
+ // installed ECWeb can resolve its peer dependency and silently load
225
+ // a different (possibly stale) EvolCore copy from node_modules.
226
+ env: {
227
+ ...process.env,
228
+ EVOLCORE_HOME: root,
229
+ EVOLCORE_PACKAGE_ROOT: getPackageRoot(),
230
+ },
222
231
  });
223
232
  }
224
233
  catch (error) {
@@ -4,6 +4,7 @@ import { compareVersions, resolveGlobalPkg } from './npm-ops.js';
4
4
  import { isWindows as platformIsWindows, resolveCommandPath } from './cross-platform.js';
5
5
  import { MIN_ECWEB_VERSION, WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
6
6
  import { resolvePaths } from '../paths.js';
7
+ import { ipcQuery } from '../ipc.js';
7
8
  /** EC Web owns the session-log reader, so older builds cannot safely serve a newer daemon. */
8
9
  export function isEcwebVersionSupported(version) {
9
10
  const normalized = version?.trim();
@@ -65,34 +66,15 @@ export function resolveEcwebLaunchCommand(ecwebArgs, opts = {}) {
65
66
  }
66
67
  return { command: commandPath, args: ecwebArgs, source: 'command' };
67
68
  }
68
- /**
69
- * ECWeb 配对码取码 helper(共享给 daemon 和 CLI)。
70
- *
71
- * 配对码是 ecweb 进程自己生成并持有的内部状态。daemon/CLI 通过 ecweb 的
72
- * localhost-only HTTP 接口 GET /api/pair-code 取当前码(远程访问被 ecweb 403 拒绝)。
73
- */
74
- /** 经 localhost 拉取 ecweb 当前配对码(仅本机可取)。失败返回 null。
75
- * authorizedBy 仅由 daemon 的 AUN 控制通道传入,用于把配对码换出的 token
76
- * 绑定到实际批准的 daemon-owner;普通 CLI 取码不携带该字段。 */
77
- export async function fetchEcwebPairCode(port, authorizedBy) {
69
+ export async function fetchRemotePairingCode(authorizedBy) {
78
70
  try {
79
- const headers = {};
80
- const owner = authorizedBy?.trim();
81
- if (owner)
82
- headers['x-evolcore-pair-authorized-by'] = owner;
83
- try {
84
- const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
85
- if (controlToken)
86
- headers['x-evolcore-control-token'] = controlToken;
87
- }
88
- catch { }
89
- const resp = await fetch(`http://127.0.0.1:${port}/api/pair-code`, {
90
- headers,
91
- signal: AbortSignal.timeout(2000),
92
- });
93
- if (!resp.ok)
71
+ const response = await ipcQuery(resolvePaths().socket, {
72
+ type: 'remote-auth.pair-code',
73
+ ...(authorizedBy?.trim() ? { authorizedBy: authorizedBy.trim() } : {}),
74
+ }, 2_000);
75
+ if (!response?.ok || typeof response.code !== 'string' || typeof response.expiresAt !== 'number')
94
76
  return null;
95
- return await resp.json();
77
+ return { code: response.code, expiresAt: response.expiresAt };
96
78
  }
97
79
  catch {
98
80
  return null;
@@ -380,6 +380,13 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
380
380
  }
381
381
  // 回退到原有的错误消息匹配逻辑
382
382
  const msg = error?.message || String(error);
383
+ // Codex app-server reports this when another app-server still owns the
384
+ // thread writer. Surface the handoff condition instead of hiding it behind
385
+ // the generic "processing failed" message.
386
+ if (error?.code === 'THREAD_ACTIVE_WRITER' || /already has an active writer/i.test(msg)) {
387
+ const prefix = includeEmoji ? '⚠️ ' : '';
388
+ return `${prefix}Codex 会话正在由另一个执行通道占用,请稍后重试(THREAD_ACTIVE_WRITER)`;
389
+ }
383
390
  // 字典优先 — 命中且有自定义消息则直接返回
384
391
  const rule = matchErrorRule(msg.toLowerCase());
385
392
  if (rule?.message)
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import { resolvePaths } from '../paths.js';
3
3
  import { LogWriter } from './log-writer.js';
4
- import { classifyToolErrorCode } from '../core/permission/tool-error-code.js';
4
+ import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
5
5
  import { normalizePermissionMode } from '../core/permission/mode.js';
6
6
  import { buildToolLifecycleEventKey } from '../core/audit/event-key.js';
7
7
  let currentLevel = process.env.LOG_LEVEL || 'INFO';
@@ -95,8 +95,8 @@ export function normalizeStructuredLog(data) {
95
95
  const isToolUse = eventType === 'tool:use' || eventType === 'tool_use';
96
96
  const isError = data.isError ?? data.is_error ?? (data.ok === false ? true : undefined)
97
97
  ?? nested?.isError ?? nested?.is_error ?? (nested?.ok === false ? true : undefined);
98
- const error = data.error ?? data.errorMessage ?? nested?.error ?? nested?.errorMessage;
99
- const result = data.result ?? data.content ?? nested?.result ?? nested?.content;
98
+ const decisionSource = data.decisionSource ?? data.decision_source
99
+ ?? nested?.decisionSource ?? nested?.decision_source;
100
100
  const decision = data.decision
101
101
  ?? data.status
102
102
  ?? (isToolResult && isError === true ? 'error' : undefined)
@@ -113,8 +113,11 @@ export function normalizeStructuredLog(data) {
113
113
  ?? nested?.errorCode
114
114
  ?? nested?.error_code;
115
115
  const errorCode = isToolResult && isError === true
116
- ? classifyToolErrorCode({ errorCode: explicitErrorCode, error, result })
116
+ ? classifyToolErrorCode({ errorCode: explicitErrorCode, decisionSource })
117
117
  : explicitErrorCode;
118
+ const classificationMissing = isToolResult && isError === true
119
+ && !normalizeToolErrorCode(explicitErrorCode)
120
+ && decisionSource !== 'policy' && decisionSource !== 'approval';
118
121
  const lifecycleRecord = isToolUse || isToolResult;
119
122
  const lifecycleEventKey = lifecycleRecord
120
123
  ? (eventKey ?? buildToolLifecycleEventKey({
@@ -137,10 +140,12 @@ export function normalizeStructuredLog(data) {
137
140
  ...(agentName ? { agentName } : lifecycleRecord ? { agentName: 'unknown' } : {}),
138
141
  ...(permissionMode ? { permissionMode } : lifecycleRecord ? { permissionMode: 'unknown' } : {}),
139
142
  ...(toolName ? { toolName } : {}),
143
+ ...(decisionSource ? { decisionSource } : {}),
140
144
  ...(decision ? { decision } : {}),
141
145
  ...(executed !== undefined ? { executed } : {}),
142
146
  ...(executionState ? { executionState } : {}),
143
147
  ...(errorCode ? { errorCode } : {}),
148
+ ...(classificationMissing ? { classificationMissing: true } : {}),
144
149
  ...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
145
150
  };
146
151
  }
@@ -14,7 +14,7 @@ import { getPackageRoot } from '../paths.js';
14
14
  import { isWindows } from './cross-platform.js';
15
15
  const execFileAsync = promisify(execFile);
16
16
  // ── npm install -g (shared) ────────────────────────────────────────────────
17
- export async function npmInstallGlobal(pkg) {
17
+ export async function npmInstallGlobal(pkg, options = {}) {
18
18
  let cmd;
19
19
  let args;
20
20
  // Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
@@ -28,14 +28,24 @@ export async function npmInstallGlobal(pkg) {
28
28
  args = ['install', '-g', pkg];
29
29
  }
30
30
  try {
31
- await execFileAsync(cmd, args, { timeout: 180000, windowsHide: isWindows });
31
+ await execFileAsync(cmd, args, {
32
+ timeout: options.timeoutMs ?? 180000,
33
+ windowsHide: isWindows,
34
+ signal: options.signal,
35
+ });
32
36
  }
33
37
  catch (e) {
38
+ if (options.signal?.aborted)
39
+ throw e;
34
40
  if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
35
41
  if (isWindows) {
36
42
  throw new Error('权限不足。请以管理员身份运行 PowerShell 或 CMD,然后重试');
37
43
  }
38
- await execFileAsync('sudo', ['npm', 'install', '-g', pkg], { timeout: 180000, windowsHide: false });
44
+ await execFileAsync('sudo', ['npm', 'install', '-g', pkg], {
45
+ timeout: options.timeoutMs ?? 180000,
46
+ windowsHide: false,
47
+ signal: options.signal,
48
+ });
39
49
  }
40
50
  else {
41
51
  throw e;
@@ -119,15 +129,19 @@ export function resolveGlobalPkg(pkgName) {
119
129
  * 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
120
130
  * 超时 10 秒,失败返回 null。
121
131
  */
122
- export async function checkLatestVersion(pkg = 'evolcore') {
132
+ export async function checkLatestVersion(pkg = 'evolcore', timeoutMs = 10000, signal) {
133
+ const controller = new AbortController();
134
+ const abort = () => controller.abort();
135
+ if (signal?.aborted)
136
+ abort();
137
+ else
138
+ signal?.addEventListener('abort', abort, { once: true });
139
+ const timer = setTimeout(abort, timeoutMs);
123
140
  try {
124
- const controller = new AbortController();
125
- const timer = setTimeout(() => controller.abort(), 10000);
126
141
  const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
127
142
  signal: controller.signal,
128
143
  headers: { 'Accept': 'application/json' },
129
144
  });
130
- clearTimeout(timer);
131
145
  if (!res.ok)
132
146
  return null;
133
147
  const data = await res.json();
@@ -136,20 +150,26 @@ export async function checkLatestVersion(pkg = 'evolcore') {
136
150
  catch {
137
151
  return null;
138
152
  }
153
+ finally {
154
+ clearTimeout(timer);
155
+ signal?.removeEventListener('abort', abort);
156
+ }
139
157
  }
140
158
  /**
141
159
  * 完整升级流程:检查 → 比较 → 安装(失败重试一次)
142
160
  */
143
- export async function tryUpgrade() {
161
+ export async function tryUpgrade(options = {}) {
144
162
  // 开发模式跳过
145
163
  if (isLinkedInstall()) {
146
164
  return { status: 'skipped' };
147
165
  }
148
166
  const localVer = getLocalVersion();
149
167
  // 查询 registry
150
- const remoteVer = await checkLatestVersion();
168
+ const remoteVer = await checkLatestVersion('evolcore', options.checkTimeoutMs, options.signal);
151
169
  if (!remoteVer) {
152
- return { status: 'skipped', error: 'Failed to check remote version' };
170
+ return options.signal?.aborted
171
+ ? { status: 'failed', from: localVer, error: 'Upgrade cancelled or timed out' }
172
+ : { status: 'skipped', error: 'Failed to check remote version' };
153
173
  }
154
174
  // 版本比较
155
175
  if (compareVersions(localVer, remoteVer) >= 0) {
@@ -159,11 +179,16 @@ export async function tryUpgrade() {
159
179
  let lastError;
160
180
  for (let attempt = 0; attempt < 2; attempt++) {
161
181
  try {
162
- await npmInstallGlobal('evolcore@latest');
182
+ await npmInstallGlobal('evolcore@latest', {
183
+ timeoutMs: options.installTimeoutMs,
184
+ signal: options.signal,
185
+ });
163
186
  return { status: 'upgraded', from: localVer, to: remoteVer };
164
187
  }
165
188
  catch (e) {
166
189
  lastError = e.stderr || e.message || String(e);
190
+ if (options.signal?.aborted)
191
+ break;
167
192
  }
168
193
  }
169
194
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
@@ -173,7 +198,7 @@ export async function tryUpgrade() {
173
198
  * 仅在包已安装时检查升级,未安装则跳过(resolveFn 返回 null)。
174
199
  * 用于 EC Web 等真正独立于 EvolCore 的全局包。
175
200
  */
176
- export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
201
+ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName, options = {}) {
177
202
  if (isLinkedInstall()) {
178
203
  return { status: 'skipped' };
179
204
  }
@@ -182,9 +207,11 @@ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
182
207
  return { status: 'skipped' }; // not installed, skip
183
208
  }
184
209
  const localVer = installed.version;
185
- const remoteVer = await checkLatestVersion(pkgName);
210
+ const remoteVer = await checkLatestVersion(pkgName, options.checkTimeoutMs, options.signal);
186
211
  if (!remoteVer) {
187
- return { status: 'skipped', error: 'Failed to check remote version' };
212
+ return options.signal?.aborted
213
+ ? { status: 'failed', from: localVer, error: 'Upgrade cancelled or timed out' }
214
+ : { status: 'skipped', error: 'Failed to check remote version' };
188
215
  }
189
216
  if (compareVersions(localVer, remoteVer) >= 0) {
190
217
  return { status: 'no-update', from: localVer };
@@ -192,11 +219,16 @@ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
192
219
  let lastError;
193
220
  for (let attempt = 0; attempt < 2; attempt++) {
194
221
  try {
195
- await npmInstallGlobal(`${pkgName}@latest`);
222
+ await npmInstallGlobal(`${pkgName}@latest`, {
223
+ timeoutMs: options.installTimeoutMs,
224
+ signal: options.signal,
225
+ });
196
226
  return { status: 'upgraded', from: localVer, to: remoteVer };
197
227
  }
198
228
  catch (e) {
199
229
  lastError = e.stderr || e.message || String(e);
230
+ if (options.signal?.aborted)
231
+ break;
200
232
  }
201
233
  }
202
234
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };