evolcore 0.0.33 → 0.0.35-test.0

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 (152) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +38 -4
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +93 -14
  6. package/dist/agents/codex/diagnostics.js +79 -22
  7. package/dist/agents/codex-runner.js +245 -47
  8. package/dist/agents/ecagent-runner.js +90 -27
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/agentmd.js +13 -2
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/identity.js +1 -1
  13. package/dist/aun/msg/managed-operation.js +16 -0
  14. package/dist/aun/msg/p2p.js +18 -2
  15. package/dist/channels/aun.js +123 -7
  16. package/dist/channels/daemon.js +6 -3
  17. package/dist/cli/aun-commands.js +39 -14
  18. package/dist/cli/ctl-command.js +3 -1
  19. package/dist/cli/daemon-commands.js +130 -23
  20. package/dist/cli/fs-command.js +2 -1
  21. package/dist/cli/getmeta-command.js +211 -0
  22. package/dist/cli/index.js +26 -6
  23. package/dist/cli/integrity-command.js +196 -0
  24. package/dist/cli/managed-command-guard.js +3 -0
  25. package/dist/cli/memory-command.js +200 -156
  26. package/dist/cli/thread-command.js +17 -7
  27. package/dist/cli/trigger-command.js +6 -1
  28. package/dist/config/config-field-policy.js +4 -0
  29. package/dist/config/config-manager.js +19 -0
  30. package/dist/config/contact-book-store.js +5 -0
  31. package/dist/config/resolved-config-op.js +25 -8
  32. package/dist/config/role-service.js +13 -0
  33. package/dist/config/runtime-env.js +16 -0
  34. package/dist/config-store.js +30 -0
  35. package/dist/core/audit/log-integrity.js +3 -1
  36. package/dist/core/auth/agent-delegation.js +108 -35
  37. package/dist/core/auth/delegation-diagnostics.js +414 -0
  38. package/dist/core/auth/operation-authorizer.js +22 -3
  39. package/dist/core/auth/operation-catalog.js +16 -0
  40. package/dist/core/channel-runtime.js +1 -1
  41. package/dist/core/command/agent-control.js +18 -26
  42. package/dist/core/command/command-handler.js +145 -5
  43. package/dist/core/command/daemon-diagnostics.js +1 -0
  44. package/dist/core/command/menu-catalog.js +3 -0
  45. package/dist/core/command/menu-handler.js +24 -7
  46. package/dist/core/command/menu-protocol.js +1 -0
  47. package/dist/core/command/slash-handler.js +2 -0
  48. package/dist/core/command/topic-slash.js +4 -7
  49. package/dist/core/daemon-bootstrap.js +4 -7
  50. package/dist/core/event-catalog.js +14 -0
  51. package/dist/core/file-integrity/read.js +61 -0
  52. package/dist/core/file-integrity/store.js +262 -0
  53. package/dist/core/ipc-registration.js +47 -2
  54. package/dist/core/memory/batch-meta.js +133 -0
  55. package/dist/core/memory/consolidation-context.js +132 -28
  56. package/dist/core/memory/consolidation-ops-records.js +14 -16
  57. package/dist/core/memory/consolidation-trigger.js +15 -7
  58. package/dist/core/memory/consolidation.js +146 -26
  59. package/dist/core/memory/index-manager.js +19 -28
  60. package/dist/core/memory/memory-admin.js +19 -14
  61. package/dist/core/memory/memory-document.js +53 -9
  62. package/dist/core/memory/memory-writer.js +50 -19
  63. package/dist/core/message/aun-ipc-sender.js +19 -1
  64. package/dist/core/message/im-renderer.js +20 -4
  65. package/dist/core/message/message-bridge.js +5 -4
  66. package/dist/core/message/message-log.js +1 -0
  67. package/dist/core/message/peer-mode.js +17 -0
  68. package/dist/core/message/response-engine.js +598 -50
  69. package/dist/core/permission/ec-command-parser.js +169 -44
  70. package/dist/core/permission/protected-paths.js +5 -1
  71. package/dist/core/permission/readonly-shell-query.js +43 -5
  72. package/dist/core/permission/session-path-grants.js +334 -0
  73. package/dist/core/permission/tool-canonical.js +226 -0
  74. package/dist/core/permission/tool-policy.js +60 -62
  75. package/dist/core/relation/peer-identity.js +87 -12
  76. package/dist/core/role/runtime-policy.js +21 -10
  77. package/dist/core/session/session-fs-store.js +123 -8
  78. package/dist/core/session/session-manager.js +131 -6
  79. package/dist/core/session/session-title.js +26 -1
  80. package/dist/core/skill/index-generator.js +17 -3
  81. package/dist/core/system-session/grants.js +40 -0
  82. package/dist/core/system-session/memory.js +289 -0
  83. package/dist/core/system-session/registry.js +74 -0
  84. package/dist/core/system-session/types.js +14 -0
  85. package/dist/core/thread/command-parser.js +6 -1
  86. package/dist/core/thread/ipc-operation.js +33 -16
  87. package/dist/core/thread/thread-operation.js +62 -24
  88. package/dist/core/thread/thread-service.js +258 -51
  89. package/dist/eck/kit-renderer.js +7 -2
  90. package/dist/eck/manifest-engine.js +24 -4
  91. package/dist/index.js +11 -2
  92. package/dist/ipc.js +51 -40
  93. package/dist/paths.js +70 -0
  94. package/dist/stats/billing.js +20 -8
  95. package/dist/trigger/feedback.js +6 -1
  96. package/dist/trigger/parser.js +8 -1
  97. package/dist/trigger/system-definitions.js +3 -3
  98. package/dist/utils/cross-platform.js +17 -5
  99. package/dist/utils/error-dict.json +18 -4
  100. package/dist/utils/error-utils.js +121 -0
  101. package/dist/utils/instance-registry.js +13 -8
  102. package/dist/utils/log-writer.js +12 -0
  103. package/dist/utils/process-introspect.js +11 -0
  104. package/kits/docs/context-assembly.md +0 -1
  105. package/kits/docs/evolcore/agent-create.md +5 -4
  106. package/kits/docs/evolcore/agent.md +2 -2
  107. package/kits/docs/evolcore/config.md +2 -2
  108. package/kits/docs/evolcore/ctl.md +2 -2
  109. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  110. package/kits/docs/evolcore/fs-architecture.md +9 -10
  111. package/kits/docs/evolcore/fs.md +2 -2
  112. package/kits/docs/evolcore/group-collaboration.md +3 -3
  113. package/kits/docs/evolcore/group.md +14 -12
  114. package/kits/docs/evolcore/memory-architecture.md +4 -5
  115. package/kits/docs/evolcore/memory-consolidation.md +19 -16
  116. package/kits/docs/evolcore/memory-tags.md +3 -1
  117. package/kits/docs/evolcore/memory.md +24 -12
  118. package/kits/docs/evolcore/model.md +16 -0
  119. package/kits/docs/evolcore/msg.md +7 -7
  120. package/kits/docs/evolcore/stats.md +1 -1
  121. package/kits/docs/evolcore/thread-chain.md +30 -14
  122. package/kits/docs/prompt-loading-architecture.md +0 -1
  123. package/kits/eck_manifest.auxiliary.json +9 -0
  124. package/kits/eck_manifest.json +20 -20
  125. package/kits/eck_manifest.memory.json +20 -12
  126. package/kits/rules/01-overview.md +11 -0
  127. package/kits/rules/03-identity.md +6 -3
  128. package/kits/rules/06-channel.md +1 -1
  129. package/kits/rules/07-memory-and-thread.md +13 -16
  130. package/kits/schemas/_meta.json +7 -1
  131. package/kits/schemas/agent-config.schema.13.json +1 -1
  132. package/kits/schemas/daemon.schema.7.json +140 -0
  133. package/kits/skills/ec-group/SKILL.md +8 -5
  134. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  135. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  136. package/kits/skills/thread-ops/SKILL.md +18 -18
  137. package/kits/templates/memory-consolidation/forget.md +2 -2
  138. package/kits/templates/memory-consolidation/generalize.md +4 -4
  139. package/kits/templates/memory-consolidation/notes.md +12 -12
  140. package/kits/templates/memory-consolidation/profile.md +3 -3
  141. package/kits/templates/memory-consolidation/promote.md +3 -3
  142. package/kits/templates/memory-consolidation/workflow.md +42 -7
  143. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  144. package/kits/templates/system-fragments/bash-description.md +9 -0
  145. package/kits/templates/system-fragments/bootstrap.md +4 -3
  146. package/kits/templates/system-fragments/channel.md +2 -2
  147. package/kits/templates/system-fragments/memory.md +16 -9
  148. package/kits/templates/system-fragments/reply.md +6 -6
  149. package/kits/templates/system-fragments/session.md +1 -1
  150. package/package.json +4 -3
  151. package/dist/core/memory/memory-session-policy.js +0 -41
  152. package/dist/core/memory/memory-signature.js +0 -257
@@ -3,13 +3,15 @@ import path from 'path';
3
3
  import fs from 'fs';
4
4
  import os from 'os';
5
5
  import crypto from 'crypto';
6
+ import { MAIN_CLI_BIN } from '../../product.js';
6
7
  import { BaseagentRunnerUnavailableError, hasCompact, autoCompactWindowForModel, isClaudeContextUsageModel, numericToken, formatAsyncQuestionAnswer, parseAsyncUserQuestions, } from '../../agents/runner-types.js';
7
8
  import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
8
9
  import { isDiagnosticSessionActive } from '../command/diag-service.js';
9
10
  import { evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission } from './message-admission.js';
10
11
  import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
11
12
  import { buildTaskRuntimeEnv, ensureRuntimeLockDir, ensureSessionRuntimeDir } from '../../cli/task-context.js';
12
- import { memorySessionPolicyHook } from '../memory/memory-session-policy.js';
13
+ import { BUILTIN_SESSION_PATH_GRANTS, mergeRuntimeGrantsForSession, resolveGrantedReadRoots, } from '../permission/session-path-grants.js';
14
+ import { buildSystemSessionContext, getSystemSessionModule, isSystemSessionType, readSystemSessionMarker } from '../system-session/registry.js';
13
15
  import { IMRenderer } from './im-renderer.js';
14
16
  import { projectSubtaskStarted, projectSubtaskTerminal } from './subtask-tool-projection.js';
15
17
  import { TaskActivityStore } from '../task/task-activity-store.js';
@@ -18,9 +20,9 @@ import { createDecisionInferenceProvider, createTextInferenceProvider } from '..
18
20
  import { StreamIdleMonitor } from './stream-idle-monitor.js';
19
21
  import { logger } from '../../utils/logger.js';
20
22
  import { isHostChinese } from '../../utils/locale.js';
21
- import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure, getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
23
+ import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure, getErrorMessage, getErrorRawText, getToolResultErrorText, classifyError, normalizeCoreErrorReason, normalizeCoreErrorSubtype, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
22
24
  import { readEvolcoreRuntimeFlavor, tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
23
- import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession, isThreadRoutedEvolcoreSendCommand } from '../permission/ec-command-parser.js';
25
+ import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession, isThreadRoutedEvolcoreSendCommand, parseCodexToolCommand, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from '../permission/ec-command-parser.js';
24
26
  import { resolveThreadRouteTarget } from '../thread/thread-route.js';
25
27
  import { summarizeToolInputForDisplay } from '../../utils/tool-summary.js';
26
28
  import { agentDataDir, agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths, debugSessionsDir } from '../../paths.js';
@@ -414,6 +416,381 @@ export class PauseController {
414
416
  function isShowActivitiesMode(value) {
415
417
  return value === 'all' || value === 'text' || value === 'none';
416
418
  }
419
+ const TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH = 96_000;
420
+ const TOOL_INPUT_DIAGNOSTIC_MAX_FINDINGS = 3;
421
+ const TOOL_INPUT_DIAGNOSTIC_TIMEOUT_MS = 1_000;
422
+ function isToolResultFailure(event) {
423
+ return event.isError === true
424
+ || event.executionState === 'failed'
425
+ || event.executionState === 'blocked'
426
+ || event.decision === 'deny'
427
+ || event.decision === 'error'
428
+ || (typeof event.exitCode === 'number' && Number.isFinite(event.exitCode) && event.exitCode !== 0);
429
+ }
430
+ function isStringArgv(value) {
431
+ return Array.isArray(value) && value.every(item => typeof item === 'string');
432
+ }
433
+ function boundedDiagnosticInput(value) {
434
+ return value.length <= TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH;
435
+ }
436
+ function findApplyPatchArgumentsOffset(value) {
437
+ const match = value.trimStart().match(/^(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s;&|<>"']+))(?=\s|[;&|<>]|$)/u);
438
+ const executable = match?.[1] ?? match?.[2] ?? match?.[3];
439
+ if (!executable)
440
+ return -1;
441
+ const basename = executable.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase();
442
+ if (basename !== 'apply_patch' && basename !== 'apply_patch.exe')
443
+ return -1;
444
+ return value.length - value.trimStart().length + match[0].length;
445
+ }
446
+ function findUnquotedDiagnosticText(value, needle, startOffset) {
447
+ let quote = null;
448
+ let escaped = false;
449
+ for (let index = startOffset; index < value.length; index++) {
450
+ const char = value[index];
451
+ if (escaped) {
452
+ escaped = false;
453
+ continue;
454
+ }
455
+ if (quote === 'single') {
456
+ if (char === "'")
457
+ quote = null;
458
+ continue;
459
+ }
460
+ if (quote === 'double') {
461
+ if (char === '\\')
462
+ escaped = true;
463
+ else if (char === '"')
464
+ quote = null;
465
+ continue;
466
+ }
467
+ if (char === '\\') {
468
+ escaped = true;
469
+ continue;
470
+ }
471
+ if (char === "'") {
472
+ quote = 'single';
473
+ continue;
474
+ }
475
+ if (char === '"') {
476
+ quote = 'double';
477
+ continue;
478
+ }
479
+ if (value.startsWith(needle, index))
480
+ return index;
481
+ }
482
+ return -1;
483
+ }
484
+ function diagnosticLineEnd(value, startOffset) {
485
+ const newline = value.indexOf('\n', startOffset);
486
+ return newline < 0 ? value.length : newline;
487
+ }
488
+ function parseDiagnosticHeredoc(value, argumentsOffset) {
489
+ const openOffset = findUnquotedDiagnosticText(value, '<<', argumentsOffset);
490
+ if (openOffset < 0 || openOffset >= diagnosticLineEnd(value, argumentsOffset)
491
+ || value[openOffset + 2] === '<')
492
+ return undefined;
493
+ let cursor = openOffset + 2;
494
+ const stripTabs = value[cursor] === '-';
495
+ if (stripTabs)
496
+ cursor++;
497
+ while (value[cursor] === ' ' || value[cursor] === '\t')
498
+ cursor++;
499
+ const quote = value[cursor] === "'" || value[cursor] === '"' ? value[cursor] : undefined;
500
+ if (quote)
501
+ cursor++;
502
+ const delimiterStart = cursor;
503
+ while (cursor < value.length) {
504
+ const char = value[cursor];
505
+ if (quote ? char === quote : char === ' ' || char === '\t' || char === '\r' || char === '\n')
506
+ break;
507
+ cursor++;
508
+ }
509
+ const delimiter = value.slice(delimiterStart, cursor);
510
+ if (!delimiter)
511
+ return undefined;
512
+ const firstNewline = value.indexOf('\n', cursor);
513
+ if (firstNewline < 0)
514
+ return {};
515
+ let lineStart = firstNewline + 1;
516
+ let trailing;
517
+ while (lineStart <= value.length) {
518
+ const end = diagnosticLineEnd(value, lineStart);
519
+ const rawLine = value.slice(lineStart, end).replace(/\r$/u, '');
520
+ const line = stripTabs ? rawLine.replace(/^\t+/u, '') : rawLine;
521
+ if (line === delimiter) {
522
+ let extraOffset;
523
+ let next = end < value.length ? end + 1 : value.length;
524
+ while (next < value.length) {
525
+ const nextEnd = diagnosticLineEnd(value, next);
526
+ const nextLine = value.slice(next, nextEnd).trim();
527
+ if (nextLine !== '' && !nextLine.startsWith('#')) {
528
+ extraOffset = next;
529
+ break;
530
+ }
531
+ next = nextEnd < value.length ? nextEnd + 1 : value.length;
532
+ }
533
+ return { delimiterOffset: lineStart + rawLine.length - line.length, extraOffset };
534
+ }
535
+ if (line.startsWith(delimiter) && line.length > delimiter.length) {
536
+ trailing ??= { trailingOffset: lineStart + rawLine.length - line.length + delimiter.length, trailingEndOffset: end };
537
+ }
538
+ if (end >= value.length)
539
+ break;
540
+ lineStart = end + 1;
541
+ }
542
+ return trailing ?? {};
543
+ }
544
+ function findDiagnosticShellSeparator(value, startOffset, endOffset) {
545
+ let quote = null;
546
+ let escaped = false;
547
+ for (let index = startOffset; index < endOffset; index++) {
548
+ const char = value[index];
549
+ if (escaped) {
550
+ escaped = false;
551
+ continue;
552
+ }
553
+ if (quote === 'single') {
554
+ if (char === "'")
555
+ quote = null;
556
+ continue;
557
+ }
558
+ if (quote === 'double') {
559
+ if (char === '\\')
560
+ escaped = true;
561
+ else if (char === '"')
562
+ quote = null;
563
+ continue;
564
+ }
565
+ if (char === '\\') {
566
+ escaped = true;
567
+ continue;
568
+ }
569
+ if (char === "'") {
570
+ quote = 'single';
571
+ continue;
572
+ }
573
+ if (char === '"') {
574
+ quote = 'double';
575
+ continue;
576
+ }
577
+ if (char === ';' || char === '|' || char === '&' || char === '\n' || char === '\r')
578
+ return index;
579
+ }
580
+ return -1;
581
+ }
582
+ function diagnosticLocation(source, startOffset, endOffset = startOffset) {
583
+ const start = Math.max(0, Math.min(startOffset, source.value.length));
584
+ const end = Math.max(start, Math.min(endOffset, source.value.length));
585
+ const lineStart = Math.max(source.value.lastIndexOf('\n', Math.max(0, start - 1)) + 1, 0);
586
+ return {
587
+ field: source.field,
588
+ ...(source.argumentIndex !== undefined ? { argumentIndex: source.argumentIndex } : {}),
589
+ line: source.value.slice(0, start).split('\n').length,
590
+ column: [...source.value.slice(lineStart, start)].length + 1,
591
+ startOffset: start,
592
+ endOffset: end,
593
+ };
594
+ }
595
+ function diagnosticFinding(ruleId, title, advice, confidence, source, startOffset, endOffset) {
596
+ return { ruleId, title, advice, confidence, location: diagnosticLocation(source, startOffset, endOffset) };
597
+ }
598
+ function diagnosticCandidates(input) {
599
+ const candidates = [];
600
+ if (typeof input.command === 'string' && boundedDiagnosticInput(input.command)) {
601
+ const source = { field: 'command', value: input.command };
602
+ const carrier = resolveCodexShellCarrierString(input.command);
603
+ if (carrier) {
604
+ const commandOffset = input.command.indexOf(carrier.command);
605
+ candidates.push({
606
+ source,
607
+ command: carrier.command,
608
+ commandOffset: commandOffset >= 0 ? commandOffset : 0,
609
+ carrier: true,
610
+ });
611
+ }
612
+ else {
613
+ candidates.push({ source, command: input.command, commandOffset: 0, carrier: false });
614
+ }
615
+ }
616
+ if (isStringArgv(input.commandArgv) && input.commandArgv.every(boundedDiagnosticInput)) {
617
+ const carrier = resolveCodexShellCarrierArgv(input.commandArgv);
618
+ if (carrier) {
619
+ const argumentIndex = input.commandArgv.length - 1;
620
+ candidates.push({
621
+ source: { field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex },
622
+ command: carrier.command,
623
+ commandOffset: 0,
624
+ carrier: true,
625
+ });
626
+ }
627
+ else {
628
+ const argumentIndex = 0;
629
+ if (findApplyPatchArgumentsOffset(input.commandArgv[argumentIndex] ?? '') >= 0) {
630
+ candidates.push({
631
+ source: { field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex },
632
+ command: input.commandArgv[argumentIndex],
633
+ commandOffset: 0,
634
+ carrier: false,
635
+ });
636
+ }
637
+ }
638
+ }
639
+ return candidates;
640
+ }
641
+ function addHeredocDiagnosticFindings(findings, candidate) {
642
+ const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
643
+ if (argumentsOffset < 0)
644
+ return;
645
+ const heredoc = parseDiagnosticHeredoc(candidate.command, argumentsOffset);
646
+ if (!heredoc)
647
+ return;
648
+ const map = (offset) => candidate.commandOffset + offset;
649
+ if (heredoc.trailingOffset !== undefined) {
650
+ findings.push(diagnosticFinding('heredoc-terminator-trailing-characters', 'heredoc 结束标记行包含尾随字符', '让结束标记单独占一行,不要在标记后追加引号、命令或其它字符。', '高', candidate.source, map(heredoc.trailingOffset), map(heredoc.trailingEndOffset ?? heredoc.trailingOffset)));
651
+ }
652
+ else if (heredoc.delimiterOffset === undefined) {
653
+ findings.push(diagnosticFinding('heredoc-terminator-missing', '未找到匹配的 heredoc 结束标记', '补上与起始标记完全一致、且单独占一行的结束标记。', '高', candidate.source, map(candidate.command.length), map(candidate.command.length)));
654
+ }
655
+ else if (heredoc.extraOffset !== undefined) {
656
+ findings.push(diagnosticFinding('heredoc-extra-command', 'heredoc 结束标记后仍有额外命令或文本', '将 apply_patch 与测试或其它命令拆成两次工具调用。', '高', candidate.source, map(heredoc.extraOffset), map(heredoc.extraOffset)));
657
+ }
658
+ }
659
+ function addApplyPatchCompositionFinding(findings, candidate) {
660
+ const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
661
+ if (argumentsOffset < 0 || candidate.carrier)
662
+ return;
663
+ const executableMatch = candidate.command.trimStart().match(/^(?:"([^"]+)"|'([^']+)'|([^\s]+))/u);
664
+ const executable = executableMatch?.[1] ?? executableMatch?.[2] ?? executableMatch?.[3] ?? '';
665
+ const basename = executable.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase();
666
+ if (basename !== 'apply_patch' && basename !== 'apply_patch.exe')
667
+ return;
668
+ const heredocOffset = findUnquotedDiagnosticText(candidate.command, '<<', argumentsOffset);
669
+ const endOffset = heredocOffset >= 0 ? heredocOffset : candidate.command.length;
670
+ const separator = findDiagnosticShellSeparator(candidate.command, argumentsOffset, endOffset);
671
+ if (separator < 0)
672
+ return;
673
+ findings.push(diagnosticFinding('apply-patch-composed-command', 'apply_patch 与其它 Shell 命令组合提交', '一次工具调用只提交完整的 apply_patch;测试、检查或其它命令另行调用。', '高', candidate.source, candidate.commandOffset + separator, candidate.commandOffset + separator + 1));
674
+ }
675
+ function addNestedShellDiagnosticFinding(findings, candidate) {
676
+ if (!candidate.carrier || findApplyPatchArgumentsOffset(candidate.command) < 0)
677
+ return;
678
+ findings.push(diagnosticFinding('nested-shell-escaping-risk', '命令包含嵌套 Shell 包装层', '尽量直接提交单层 Shell 命令;需要 heredoc 或 JSON 时,减少外层转义和包装。', '中', candidate.source, candidate.commandOffset, candidate.commandOffset + candidate.command.length));
679
+ }
680
+ function addJsonArrayDiagnosticFinding(findings, input) {
681
+ const rawArgv = isStringArgv(input.commandArgv) ? input.commandArgv : undefined;
682
+ const parsedArgv = rawArgv ?? (typeof input.command === 'string'
683
+ ? (() => {
684
+ const parsed = parseCodexToolCommand({ command: input.command });
685
+ return parsed.ok ? parsed.argv : undefined;
686
+ })()
687
+ : undefined);
688
+ if (!parsedArgv || parsedArgv.length < 6)
689
+ return;
690
+ if (parsedArgv[0].toLowerCase() !== MAIN_CLI_BIN || parsedArgv[1].toLowerCase() !== 'aid'
691
+ || parsedArgv[2].toLowerCase() !== 'agentmd' || parsedArgv[3].toLowerCase() !== 'set'
692
+ || parsedArgv[5].toLowerCase() !== 'tags')
693
+ return;
694
+ const value = parsedArgv[6];
695
+ const source = rawArgv
696
+ ? { field: 'commandArgv', value: rawArgv[6] ?? '', argumentIndex: 6 }
697
+ : { field: 'command', value: typeof input.command === 'string' ? input.command : '' };
698
+ if (value === undefined) {
699
+ findings.push(diagnosticFinding('json-array-argument-missing', 'tags 参数缺少完整 JSON 数组值', '将 tags 作为一个完整参数传入,例如 PowerShell 使用单引号包住 ["DSH","AUN"]。', '高', source, source.value.length));
700
+ return;
701
+ }
702
+ let valid = false;
703
+ try {
704
+ const decoded = JSON.parse(value);
705
+ valid = Array.isArray(decoded) && decoded.every(item => typeof item === 'string');
706
+ }
707
+ catch {
708
+ valid = false;
709
+ }
710
+ if (valid)
711
+ return;
712
+ const split = rawArgv !== undefined && rawArgv.length > 7 && !rawArgv[7].startsWith('--')
713
+ && (value.startsWith('[') || value.endsWith(','));
714
+ findings.push(diagnosticFinding(split ? 'json-array-argument-splitting' : 'json-array-argument-invalid', split ? 'tags 的 JSON 数组疑似被拆成多个参数' : 'tags 参数不是可解析的 JSON 字符串数组', '保证整个 JSON 数组位于同一个参数中;PowerShell 普通单引号字符串内不要再添加反斜杠转义。', split ? '中' : '高', source, split ? source.value.length : 0, source.value.length));
715
+ }
716
+ function addCommandArgvMismatchFinding(findings, input) {
717
+ if (typeof input.command !== 'string' || !isStringArgv(input.commandArgv))
718
+ return;
719
+ const commandArgv = input.commandArgv;
720
+ if (!boundedDiagnosticInput(input.command) || commandArgv.some(value => !boundedDiagnosticInput(value)))
721
+ return;
722
+ if (resolveCodexShellCarrierArgv(commandArgv) || resolveCodexShellCarrierString(input.command))
723
+ return;
724
+ const parsed = parseCodexToolCommand({ command: input.command });
725
+ if (!parsed.ok || parsed.argv.length === commandArgv.length
726
+ && parsed.argv.every((value, index) => value === commandArgv[index]))
727
+ return;
728
+ findings.push(diagnosticFinding('command-argv-mismatch', 'command 与 commandArgv 的解析结果不一致', '以执行器提供的结构化 commandArgv 为准,避免在外层重新拼接或改写命令文本。', '中', { field: 'command', value: input.command }, 0, input.command.length));
729
+ }
730
+ function deduplicateDiagnosticFindings(findings) {
731
+ const seen = new Set();
732
+ return findings.filter(item => {
733
+ const key = `${item.ruleId}:${item.location.field}:${item.location.argumentIndex ?? ''}:${item.location.startOffset}`;
734
+ if (seen.has(key))
735
+ return false;
736
+ seen.add(key);
737
+ return true;
738
+ }).slice(0, TOOL_INPUT_DIAGNOSTIC_MAX_FINDINGS);
739
+ }
740
+ function diagnoseToolInput(original, event, deadline) {
741
+ if (!isToolResultFailure(event) || !original.input || typeof original.input !== 'object')
742
+ return undefined;
743
+ const input = original.input;
744
+ if (typeof input.command === 'string' && !boundedDiagnosticInput(input.command))
745
+ return undefined;
746
+ if (Array.isArray(input.commandArgv)) {
747
+ if (input.commandArgv.length > 1_024 || !isStringArgv(input.commandArgv)
748
+ || input.commandArgv.reduce((length, value) => length + value.length, 0) > TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH) {
749
+ return undefined;
750
+ }
751
+ }
752
+ const candidates = diagnosticCandidates(input);
753
+ if (Date.now() > deadline)
754
+ return undefined;
755
+ const findings = [];
756
+ for (const candidate of candidates) {
757
+ if (Date.now() > deadline)
758
+ return undefined;
759
+ addHeredocDiagnosticFindings(findings, candidate);
760
+ addApplyPatchCompositionFinding(findings, candidate);
761
+ addNestedShellDiagnosticFinding(findings, candidate);
762
+ }
763
+ if (Date.now() <= deadline)
764
+ addJsonArrayDiagnosticFinding(findings, input);
765
+ if (Date.now() <= deadline)
766
+ addCommandArgvMismatchFinding(findings, input);
767
+ if (Date.now() > deadline)
768
+ return undefined;
769
+ const deduplicated = deduplicateDiagnosticFindings(findings);
770
+ return deduplicated.length > 0 ? { findings: deduplicated } : undefined;
771
+ }
772
+ export function diagnoseToolInputAfterFailure(original, event) {
773
+ if (!original)
774
+ return undefined;
775
+ try {
776
+ return diagnoseToolInput(original, event, Date.now() + TOOL_INPUT_DIAGNOSTIC_TIMEOUT_MS);
777
+ }
778
+ catch {
779
+ return undefined;
780
+ }
781
+ }
782
+ function formatToolInputDiagnosticLocation(location) {
783
+ const field = location.argumentIndex === undefined
784
+ ? location.field
785
+ : `${location.field}[${location.argumentIndex}]`;
786
+ return `${field},第 ${location.line} 行第 ${location.column} 列,UTF-16 偏移 ${location.startOffset}-${location.endOffset}`;
787
+ }
788
+ export function formatToolInputDiagnostics(report) {
789
+ if (!report || report.findings.length === 0)
790
+ return '';
791
+ const lines = report.findings.map(item => `- [${item.confidence}] ${item.title}(${formatToolInputDiagnosticLocation(item.location)})\n 建议:${item.advice}`);
792
+ return `\n\n输入核验(辅助):\n${lines.join('\n')}`;
793
+ }
417
794
  function approvalActorRevision(actor, owners = [], admins = [], selfAid) {
418
795
  if (!actor)
419
796
  return undefined;
@@ -532,8 +909,10 @@ function markRetryHealthRecorded(error) {
532
909
  function wasRetryHealthRecorded(error) {
533
910
  return !!(error && typeof error === 'object' && error[RETRY_HEALTH_RECORDED_MARK] === true);
534
911
  }
535
- function markModelFallbackExhausted(error, message) {
912
+ function markModelFallbackExhausted(error, message, cause) {
536
913
  error[MODEL_FALLBACK_EXHAUSTED_MARK] = message;
914
+ if (cause !== undefined)
915
+ error.cause = cause;
537
916
  return error;
538
917
  }
539
918
  function getModelFallbackExhaustedMessage(error) {
@@ -554,8 +933,18 @@ function wasRetryInputAlreadySubmitted(error) {
554
933
  return !!(error && typeof error === 'object' && error[RETRY_INPUT_ALREADY_SUBMITTED_MARK] === true);
555
934
  }
556
935
  function formatRetryInputAlreadySubmittedMessage(error) {
557
- const reason = getErrorMessage(error, undefined, false) || 'Codex 暂时不可用';
558
- return `⚠️ Codex 已接收本次消息,但本轮未能确认完成。为避免在会话中重复写入同一消息,系统未自动重试。请检查会话状态后重新发送。\n原因:${reason}`;
936
+ const raw = getErrorRawText(error);
937
+ const record = error && typeof error === 'object' ? error : undefined;
938
+ const retryCount = typeof record?.codexRetryCount === 'number' && Number.isSafeInteger(record.codexRetryCount)
939
+ ? record.codexRetryCount : undefined;
940
+ const statusCode = raw?.match(/\b([45]\d{2})\b/)?.[1];
941
+ if (retryCount !== undefined && statusCode) {
942
+ return `⚠️ Codex 已重连 ${retryCount} 次模型服务后仍失败(${statusCode})。本次执行已停止。`;
943
+ }
944
+ if (retryCount !== undefined) {
945
+ return `⚠️ Codex 已重连 ${retryCount} 次模型服务后仍失败。本次执行已停止。`;
946
+ }
947
+ return '⚠️ Codex 已接收本次消息,但模型服务请求失败。本次执行已停止。';
559
948
  }
560
949
  function isNonErrorInterruptReason(reason) {
561
950
  return reason === 'new_message'
@@ -567,7 +956,7 @@ function isExpectedTerminalInterrupt(reason) {
567
956
  return isAbortTerminalReason(reason);
568
957
  }
569
958
  function formatRetryableErrorFinalMessage(error, retries) {
570
- const reason = getErrorMessage(error, undefined, false) || 'API 暂时不可用';
959
+ const reason = getErrorRawText(error) || 'API 暂时不可用';
571
960
  return `❌ API 暂时不可用,已自动重试 ${retries} 次仍失败,任务已停止。\n原因:${reason}`;
572
961
  }
573
962
  /**
@@ -678,6 +1067,20 @@ function getStreamErrorText(result) {
678
1067
  }
679
1068
  return parts.join('\n');
680
1069
  }
1070
+ function getStreamRawErrorText(result, fallback) {
1071
+ const fullText = result.fullText.trim();
1072
+ const fullTextLooksLikeDiagnostic = fullText.length > 0 && (isRetryableError(new Error(fullText))
1073
+ || isContextTooLongText(fullText));
1074
+ const fallbackLooksLikeDiagnostic = typeof fallback === 'string' && fallback.trim().length > 0 && (isRetryableError(new Error(fallback))
1075
+ || isContextTooLongText(fallback));
1076
+ const candidates = [
1077
+ result.syntheticDiagnosticText,
1078
+ result.errors?.join('\n'),
1079
+ fullTextLooksLikeDiagnostic ? fullText : undefined,
1080
+ fallbackLooksLikeDiagnostic ? fallback : undefined,
1081
+ ];
1082
+ return candidates.find((part) => typeof part === 'string' && part.trim().length > 0)?.trim();
1083
+ }
681
1084
  function getStreamErrorMessage(result, includeEmoji = false) {
682
1085
  const raw = getStreamErrorText(result);
683
1086
  return getErrorMessage(new Error(raw), result.terminalReason, includeEmoji);
@@ -993,7 +1396,7 @@ export class ResponseEngine {
993
1396
  // revocation for legacy events without a taskId.
994
1397
  const interruptedTaskId = eventTaskId;
995
1398
  if (interruptedTaskId && activeTask?.taskId === interruptedTaskId) {
996
- this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId);
1399
+ this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId, 'interrupted');
997
1400
  }
998
1401
  }
999
1402
  });
@@ -1337,7 +1740,8 @@ export class ResponseEngine {
1337
1740
  || this.isPauseRequested(sessionId) || this.retryDelayWaiters.has(sessionId))
1338
1741
  return { state: 'live', ...identity };
1339
1742
  const runner = this.getAgent(session.metadata?.channelKey || session.channel, session.baseagent, selfAid);
1340
- return { state: runner.hasActiveStream(sessionId) ? 'live' : 'missing', ...identity,
1743
+ const owned = runner.hasActiveOperation?.(sessionId, active.taskId) ?? runner.hasActiveStream(sessionId);
1744
+ return { state: owned ? 'live' : 'missing', ...identity,
1341
1745
  hasTerminalOwner: this.activeTimeoutHandlers.has(sessionId) };
1342
1746
  }
1343
1747
  // Never adopt or interrupt a newer task to clean up an older attempt.
@@ -1380,6 +1784,14 @@ export class ResponseEngine {
1380
1784
  const interruptedTaskId = interruptedTurn?.active?.taskId;
1381
1785
  this.interruptedSessions.set(sessionId, effectiveReason);
1382
1786
  this.messageQueue?.markActiveTerminal?.(sessionId, effectiveReason);
1787
+ const channelKey = session.metadata?.channelKey || session.channel;
1788
+ const agent = this.getAgent(channelKey, session.baseagent, session.selfAID);
1789
+ const cancelledQueuedTurn = interruptedTurn?.active
1790
+ ? agent.cancelQueuedTurn?.(sessionId, {
1791
+ taskId: interruptedTurn.active.taskId,
1792
+ generation: interruptedTurn.active.generation,
1793
+ }) ?? false
1794
+ : false;
1383
1795
  // 终态必须先于渠道发送和 runner I/O 落盘。否则任一外部调用卡住时,
1384
1796
  // shutdown 超时退出会再次留下 started/interrupting 的悬空任务。
1385
1797
  const runtimeContext = this.activeTaskRuntimeContexts.get(sessionId);
@@ -1406,12 +1818,10 @@ export class ResponseEngine {
1406
1818
  this.activeTaskRuntimeContexts.delete(sessionId);
1407
1819
  this.activeRunnerModels.delete(sessionId);
1408
1820
  }
1409
- this.agentDelegationRegistry?.revokeTask(sessionId, taskId);
1821
+ this.agentDelegationRegistry?.revokeTask(sessionId, taskId, 'interrupted');
1410
1822
  }
1411
1823
  this.closeActiveRunnerTasks(sessionId, effectiveReason, taskSpan?.causation ?? runtimeContext?.causation);
1412
1824
  await this.interactionRouter?.cancelAll(sessionId, effectiveReason);
1413
- const channelKey = session.metadata?.channelKey || session.channel;
1414
- const agent = this.getAgent(channelKey, session.baseagent, session.selfAID);
1415
1825
  const terminalStatus = (() => {
1416
1826
  // TimeoutController owns timeout delivery (including user-facing timeout
1417
1827
  // details) and already guarantees a single terminal status. Sending it
@@ -1442,7 +1852,9 @@ export class ResponseEngine {
1442
1852
  logger.debug(`[ResponseEngine] interrupt terminal status failure: session=${sessionId} task=${interruptedTaskId} error=${error instanceof Error ? error.message : String(error)}`);
1443
1853
  });
1444
1854
  })();
1445
- const runnerInterrupt = agent.interrupt(sessionId).catch(error => {
1855
+ const runnerInterrupt = (cancelledQueuedTurn
1856
+ ? Promise.resolve()
1857
+ : agent.interrupt(sessionId)).catch(error => {
1446
1858
  logger.debug(`[ResponseEngine] interrupt barrier runner failure: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
1447
1859
  if (strict)
1448
1860
  throw error;
@@ -1759,9 +2171,15 @@ export class ResponseEngine {
1759
2171
  if (this.turnCoordinator.isCurrent(turnLease))
1760
2172
  return false;
1761
2173
  logger.info(`[ResponseEngine] Stale turn detected after agent.runQuery start; cancelling unconsumed stream: session=${sessionId} task=${taskId}`);
1762
- await agent.interrupt(sessionId).catch(interruptError => {
1763
- logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
1764
- });
2174
+ const cancelledQueuedTurn = agent.cancelQueuedTurn?.(sessionId, {
2175
+ taskId: turnLease.taskId,
2176
+ generation: turnLease.generation,
2177
+ }) ?? false;
2178
+ if (!cancelledQueuedTurn) {
2179
+ await agent.interrupt(sessionId).catch(interruptError => {
2180
+ logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
2181
+ });
2182
+ }
1765
2183
  agent.cleanupStream(streamKey);
1766
2184
  return true;
1767
2185
  }
@@ -3099,7 +3517,7 @@ export class ResponseEngine {
3099
3517
  this.activeTaskRuntimeContexts.delete(session.id);
3100
3518
  this.activeRunnerModels.delete(session.id); // F1修复
3101
3519
  }
3102
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
3520
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'interrupted');
3103
3521
  const daemonTrigger = this.isTrustedDaemonTrigger(message);
3104
3522
  if (!daemonTrigger) {
3105
3523
  await adapter.send(envelope, statusPayload).catch(() => { });
@@ -3107,7 +3525,11 @@ export class ResponseEngine {
3107
3525
  await adapter.send(envelope, {
3108
3526
  kind: 'result.error',
3109
3527
  text: userMessage,
3110
- reason: isExecutionFailure(timeoutError) ? timeoutError.reason : isTotalExecutionTimeout ? 'total_execution_timeout' : 'timeout',
3528
+ ...(getErrorRawText(timeoutError) ? { raw: getErrorRawText(timeoutError) } : {}),
3529
+ subtype: normalizeCoreErrorSubtype(timeoutError, undefined, isTotalExecutionTimeout ? 'timeout' : 'timeout'),
3530
+ reason: isExecutionFailure(timeoutError)
3531
+ ? normalizeCoreErrorReason(timeoutError.reason)
3532
+ : 'timeout',
3111
3533
  }).catch(() => { });
3112
3534
  if (daemonTrigger) {
3113
3535
  await adapter.send(envelope, statusPayload).catch(() => { });
@@ -3575,11 +3997,53 @@ export class ResponseEngine {
3575
3997
  ? resolvedMode.mode.configureRun(modeProcessCtx)
3576
3998
  : undefined;
3577
3999
  const modePolicyHook = runModeConfig?.policyHook;
3578
- // 整理会话:写工具全拦、Bash 只放行 ec memory create|update|archive,先于模式策略判定
3579
- const isMemorySession = (session.sessionType ?? session.metadata?.sessionType) === 'memory';
3580
- const pureSessionPolicyHook = isMemorySession
3581
- ? (toolName, toolInput) => memorySessionPolicyHook(toolName, toolInput) ?? modePolicyHook?.(toolName, toolInput)
3582
- : modePolicyHook;
4000
+ // 系统会话:路径 / 工具由 system-session 模块判定,与角色体系无关。
4001
+ // 有标记就只走模块,不挂模式策略、不算授权表、不并 runtime grant。
4002
+ const sessionMetadata = session.metadata;
4003
+ const declaredSessionType = session.sessionType ?? (typeof sessionMetadata?.sessionType === 'string' ? sessionMetadata.sessionType : undefined);
4004
+ const systemSessionMarker = readSystemSessionMarker(sessionMetadata);
4005
+ if (isSystemSessionType(declaredSessionType) && !systemSessionMarker) {
4006
+ throw new Error(`系统会话 ${declaredSessionType} 缺少有效 systemSession 标记`);
4007
+ }
4008
+ if (systemSessionMarker && declaredSessionType && declaredSessionType !== systemSessionMarker.type) {
4009
+ throw new Error(`系统会话标记类型 ${systemSessionMarker.type} 与 sessionType ${declaredSessionType} 不一致`);
4010
+ }
4011
+ const systemSessionModule = systemSessionMarker
4012
+ ? getSystemSessionModule(systemSessionMarker.type)
4013
+ : undefined;
4014
+ const systemSessionContext = systemSessionModule && systemSessionMarker
4015
+ ? buildSystemSessionContext({
4016
+ marker: systemSessionMarker,
4017
+ sessionId: session.id,
4018
+ selfAid: selfAid || session.selfAID || '',
4019
+ projectPath: absoluteProjectPath,
4020
+ managedTempDir: sessionRuntimeDir,
4021
+ metadata: sessionMetadata,
4022
+ })
4023
+ : undefined;
4024
+ const systemSessionCheck = systemSessionModule && systemSessionContext
4025
+ ? (toolName, toolInput) => systemSessionModule.checkTool(toolName, toolInput, systemSessionContext)
4026
+ : undefined;
4027
+ // 沙箱声明与 runner 有关,但当前所有 runner 共用同一个结论;仍按 runner 问一次,
4028
+ // 以便将来某个 runner 需要不同取舍时只改模块。
4029
+ const systemSessionSandbox = systemSessionModule && systemSessionContext
4030
+ ? systemSessionModule.runnerLaunch(normalizeBaseagent(agent.name).canonical, systemSessionContext).sandbox
4031
+ : undefined;
4032
+ const pureSessionPolicyHook = systemSessionMarker ? undefined : modePolicyHook;
4033
+ // 路径授权(仅用户会话):身份就是角色名;运行时授予(ec ctl path grant)按 sessionId 再并一层。
4034
+ const pathGrantIdentity = systemSessionMarker ? undefined : (peerRole || undefined);
4035
+ const grantedReadRoots = (() => {
4036
+ if (!pathGrantIdentity)
4037
+ return [];
4038
+ const table = mergeRuntimeGrantsForSession(BUILTIN_SESSION_PATH_GRANTS, session.id, pathGrantIdentity);
4039
+ return resolveGrantedReadRoots(table, {
4040
+ selfAid,
4041
+ peerKey: authPeerKey,
4042
+ projectPath: absoluteProjectPath,
4043
+ identity: pathGrantIdentity,
4044
+ permissionMode: effectivePermissionMode,
4045
+ });
4046
+ })();
3583
4047
  // 设置权限审批的交互上下文(支持交互卡片)
3584
4048
  const permissionContext = {
3585
4049
  sendPrompt: permissionPromptForOrigin,
@@ -3593,10 +4057,12 @@ export class ResponseEngine {
3593
4057
  agentName: agentNameForStats,
3594
4058
  taskId,
3595
4059
  chatmode: isProactive ? 'proactive' : 'interactive',
3596
- role: peerRole,
4060
+ // 系统会话没有角色
4061
+ role: systemSessionMarker ? undefined : peerRole,
3597
4062
  chatType: authChatType,
3598
4063
  selfAid,
3599
4064
  managedTempDir: sessionRuntimeDir,
4065
+ grantedReadRoots,
3600
4066
  allowReadonlySourceDiagnostics: effectiveAgentConfig?.readonlySourceDiagnostics === true,
3601
4067
  peerKey: authPeerKey,
3602
4068
  causation: taskCausation,
@@ -3606,6 +4072,13 @@ export class ResponseEngine {
3606
4072
  recordExecutionAnomaly,
3607
4073
  pauseController: taskPauseController,
3608
4074
  preToolUsePolicyHook: pureSessionPolicyHook,
4075
+ systemSessionCheck,
4076
+ ...(systemSessionModule && systemSessionContext
4077
+ ? {
4078
+ systemSessionGrants: systemSessionModule.grants(systemSessionContext),
4079
+ ...(systemSessionSandbox ? { systemSessionSandbox } : {}),
4080
+ }
4081
+ : {}),
3609
4082
  armApprovedDelegationCommand: this.agentDelegationRegistry
3610
4083
  ? (sessionToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
3611
4084
  sessionToken,
@@ -3614,6 +4087,11 @@ export class ResponseEngine {
3614
4087
  commandHash,
3615
4088
  })
3616
4089
  : undefined,
4090
+ bindSessionCredential: this.agentDelegationRegistry
4091
+ ? (sessionToken, threadId) => this.agentDelegationRegistry.registerSessionCredential({
4092
+ sessionToken, threadId, sessionId: session.id, taskId,
4093
+ })
4094
+ : undefined,
3617
4095
  turn: {
3618
4096
  sessionId: turnLease.sessionId,
3619
4097
  taskId,
@@ -3797,7 +4275,15 @@ export class ResponseEngine {
3797
4275
  const triggerPermissionModeOverride = message.triggerMeta?.permissionModeOverride;
3798
4276
  const commandFullAccess = message.executionPermissionOverride;
3799
4277
  const triggerFullAccess = triggerPermissionModeOverride === 'fullaccess';
3800
- if (commandFullAccess || triggerFullAccess) {
4278
+ if (systemSessionMarker) {
4279
+ // 系统会话不走角色 / trigger override / fullaccess:runner 模式由模块固定
4280
+ if (commandFullAccess || triggerFullAccess) {
4281
+ throw new Error('系统会话不支持 fullaccess');
4282
+ }
4283
+ effectivePermissionMode = systemSessionModule
4284
+ .runnerLaunch(normalizedBaseagent.canonical, systemSessionContext).runnerMode;
4285
+ }
4286
+ else if (commandFullAccess || triggerFullAccess) {
3801
4287
  if (commandFullAccess) {
3802
4288
  if (commandFullAccess.permissionMode !== 'fullaccess'
3803
4289
  || commandFullAccess.processRole !== 'fullaccess-run'
@@ -4011,10 +4497,13 @@ export class ResponseEngine {
4011
4497
  bootstrapAgentMdPath: agentMdPath(selfAid),
4012
4498
  };
4013
4499
  }
4500
+ // persona.md 是唯一的人格/交接文件(personal.md 已废弃并入)。
4501
+ // 该白名单让 owner 会话可直接用文件工具分段更新 `## Bootstrap 交接`,
4502
+ // 避免把长 Markdown 交接摘要塞进 `ec agent persona --text` 的 shell 转义。
4014
4503
  if (selfAid && peerRole === 'owner' && effectivePermissionMode !== 'readonly') {
4015
4504
  modelOverride = {
4016
4505
  ...(modelOverride || {}),
4017
- personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'personal.md'),
4506
+ personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'persona.md'),
4018
4507
  };
4019
4508
  }
4020
4509
  if (normalizedBaseagent.canonical === 'claude') {
@@ -4181,7 +4670,8 @@ export class ResponseEngine {
4181
4670
  peerId: peerIdRaw || undefined,
4182
4671
  peerKey,
4183
4672
  peerName: peerName || undefined,
4184
- peerRole,
4673
+ // 系统会话与角色体系无关:不给 peerRole,消息头就不渲染 `· role:` 段
4674
+ peerRole: systemSessionMarker ? undefined : peerRole,
4185
4675
  // Display missing identity explicitly without changing the mode resolver's legacy fallback.
4186
4676
  peerType: peerType || 'unknown',
4187
4677
  sameDevice: message.sameDevice ?? false,
@@ -4258,9 +4748,12 @@ export class ResponseEngine {
4258
4748
  // Bootstrap always uses the lifecycle-gated main manifest. A custom
4259
4749
  // session manifest (for example the auxiliary manifest with `always`
4260
4750
  // sections) must not reintroduce normal rules or command context.
4751
+ // 系统会话:manifest 由模块按 type/subtype 决定,不读 config
4261
4752
  const manifestFile = isBootstrapping
4262
4753
  ? DEFAULT_MANIFEST_FILE
4263
- : resolveSessionManifest(sessionType, sessionManifests);
4754
+ : systemSessionMarker
4755
+ ? getSystemSessionModule(systemSessionMarker.type).manifest(systemSessionMarker.subtype)
4756
+ : resolveSessionManifest(sessionType, sessionManifests);
4264
4757
  const kitContext = renderKitSections(kitCtx, manifestFile);
4265
4758
  effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
4266
4759
  // ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
@@ -4312,7 +4805,7 @@ export class ResponseEngine {
4312
4805
  } : {}),
4313
4806
  peerId: message.peerId, peerName: peerName || undefined,
4314
4807
  peerType: message.peerType,
4315
- peerRole: message.batchRole || session.identity?.role || 'none',
4808
+ peerRole: systemSessionMarker ? undefined : (message.batchRole || session.identity?.role || 'none'),
4316
4809
  sameDevice: message.sameDevice, sameNetwork: message.sameNetwork, sameEgressIp: message.sameEgressIp,
4317
4810
  encrypted: message.encrypted,
4318
4811
  content: message.content, timestamp: message.timestamp,
@@ -4495,7 +4988,8 @@ export class ResponseEngine {
4495
4988
  peerKey,
4496
4989
  peerName: peerName || undefined,
4497
4990
  peerType: message.peerType || session.metadata?.peerType || undefined,
4498
- peerRole,
4991
+ // 系统会话没有角色
4992
+ ...(systemSessionMarker ? {} : { peerRole }),
4499
4993
  // 会话原型与整理模式:ipc 侧靠 sessionType 拦非整理会话的写命令,
4500
4994
  // 靠 memoryMode 校验 --scope 是否与本模式匹配。
4501
4995
  sessionType: renderedSessionType,
@@ -4532,7 +5026,8 @@ export class ResponseEngine {
4532
5026
  chatType: configChatType,
4533
5027
  selfAid,
4534
5028
  peerKey,
4535
- issuedRole: peerRole,
5029
+ // 系统会话与角色体系无关:带标记、不签角色
5030
+ ...(systemSessionMarker ? { systemSession: systemSessionMarker } : { issuedRole: peerRole }),
4536
5031
  executionSource: fullAccessAuthorization?.source ?? (message.source === 'trigger' ? 'trigger' : 'user'),
4537
5032
  permissionMode: effectivePermissionMode,
4538
5033
  approvalInteractionPolicy,
@@ -4798,7 +5293,7 @@ export class ResponseEngine {
4798
5293
  const candidateHint = fallbackCandidates.length === 0
4799
5294
  ? '未配置可用备用模型'
4800
5295
  : '备用模型均不可用';
4801
- throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`);
5296
+ throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`, retryError);
4802
5297
  }
4803
5298
  if (retryAttemptTimedOut || isRetryableError(retryError)) {
4804
5299
  await recordRetryHealthError(retryError);
@@ -4926,7 +5421,7 @@ export class ResponseEngine {
4926
5421
  this.activeTaskRuntimeContexts.delete(session.id);
4927
5422
  this.activeRunnerModels.delete(session.id); // F1修复
4928
5423
  }
4929
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5424
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
4930
5425
  logger.info(`[ResponseEngine] Stale turn stopped before publish: session=${session.id} task=${taskId}`);
4931
5426
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
4932
5427
  expected: turnLease?.generation,
@@ -4942,7 +5437,7 @@ export class ResponseEngine {
4942
5437
  this.activeTaskRuntimeContexts.delete(session.id);
4943
5438
  this.activeRunnerModels.delete(session.id); // F1修复
4944
5439
  }
4945
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5440
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
4946
5441
  logger.info(`[ResponseEngine] Stale turn stopped before protocol rejection: session=${session.id} task=${taskId}`);
4947
5442
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
4948
5443
  expected: turnLease?.generation,
@@ -5096,6 +5591,7 @@ export class ResponseEngine {
5096
5591
  let sentErrorAsSystemError = false;
5097
5592
  const finalReplyText = streamResult.lastReplyText || streamResult.fullText;
5098
5593
  const visibleFinalReplyText = stripContextLimitErrorLines(finalReplyText);
5594
+ const streamRawErrorText = getStreamRawErrorText(streamResult, visibleFinalReplyText || undefined);
5099
5595
  const isTriggerNoop = message.source === 'trigger'
5100
5596
  && typeof message.triggerMeta?.noopSentinel === 'string'
5101
5597
  && message.triggerMeta.noopSentinel.length > 0
@@ -5107,7 +5603,7 @@ export class ResponseEngine {
5107
5603
  this.activeTaskRuntimeContexts.delete(session.id);
5108
5604
  this.activeRunnerModels.delete(session.id); // F1修复
5109
5605
  }
5110
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5606
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
5111
5607
  logger.info(`[ResponseEngine] Stale turn stopped before final flush: session=${session.id} task=${taskId}`);
5112
5608
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
5113
5609
  return;
@@ -5128,10 +5624,14 @@ export class ResponseEngine {
5128
5624
  if (streamResult.isError) {
5129
5625
  // 用原始诊断文本(含错误码)判断可重试性,不用已过 stripContextLimitErrorLines 的展示文本
5130
5626
  const recoverable = isRetryableError(new Error(getStreamErrorText(streamResult)));
5627
+ const subtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || getStreamErrorText(streamResult)), streamResult.subtype, streamResult.terminalReason);
5628
+ const reason = normalizeCoreErrorReason(streamResult.terminalReason);
5131
5629
  await adapter.send(envelope, {
5132
5630
  kind: 'system.error',
5133
5631
  text: errorText,
5134
- subtype: streamResult.subtype ?? 'api_error',
5632
+ ...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
5633
+ subtype,
5634
+ ...(reason ? { reason } : {}),
5135
5635
  recoverable,
5136
5636
  }).catch(() => { });
5137
5637
  sentErrorAsSystemError = true;
@@ -5148,7 +5648,7 @@ export class ResponseEngine {
5148
5648
  this.activeTaskRuntimeContexts.delete(session.id);
5149
5649
  this.activeRunnerModels.delete(session.id); // F1修复
5150
5650
  }
5151
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5651
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, streamResult.isError ? 'failed' : 'completed');
5152
5652
  logger.info(`[ResponseEngine] session ${session.id} processing cleared task=${taskId}`);
5153
5653
  import('../../utils/msg-diag.js').then(m => m.diagLog('task_done', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, text: message.content ?? '' })).catch(() => { });
5154
5654
  // 降级模型回复末尾追加标记(代码层硬注入,不依赖模型输出)
@@ -5217,6 +5717,10 @@ export class ResponseEngine {
5217
5717
  : getStreamErrorMessage(streamResult, false);
5218
5718
  const rawSubtype = streamResult.subtype || 'agent_error';
5219
5719
  const errorType = prefixErrorType(ERROR_PREFIX.AGENT, rawSubtype);
5720
+ const coreSubtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || errorSummary), rawSubtype, streamResult.terminalReason);
5721
+ const coreReason = permissionTerminalFailure
5722
+ ? normalizeCoreErrorReason(permissionTerminalFailure.reason)
5723
+ : normalizeCoreErrorReason(streamResult.terminalReason);
5220
5724
  // 用户主动打断(新消息/​/stop/​撤回)会让 SDK 流在工具调用中途被掐断,
5221
5725
  // 末尾 result message 形状异常并被标记为 error(含 SDK 内部 ede_diagnostic 串)。
5222
5726
  // 这不是真正的失败,不应把诊断串暴露给用户,也不计入错误统计。
@@ -5241,12 +5745,24 @@ export class ResponseEngine {
5241
5745
  if (this.isTrustedDaemonTrigger(message)) {
5242
5746
  await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
5243
5747
  if (!sentErrorAsSystemError) {
5244
- await adapter.send(envelope, { kind: 'result.error', text: userErrorSummary, reason: rawSubtype }).catch(() => { });
5748
+ await adapter.send(envelope, {
5749
+ kind: 'result.error',
5750
+ text: userErrorSummary,
5751
+ ...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
5752
+ subtype: coreSubtype,
5753
+ ...(coreReason ? { reason: coreReason } : {}),
5754
+ }).catch(() => { });
5245
5755
  }
5246
5756
  }
5247
5757
  else {
5248
5758
  if (!sentErrorAsSystemError) {
5249
- await adapter.send(envelope, { kind: 'result.error', text: userErrorSummary, reason: rawSubtype }).catch(() => { });
5759
+ await adapter.send(envelope, {
5760
+ kind: 'result.error',
5761
+ text: userErrorSummary,
5762
+ ...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
5763
+ subtype: coreSubtype,
5764
+ ...(coreReason ? { reason: coreReason } : {}),
5765
+ }).catch(() => { });
5250
5766
  }
5251
5767
  await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
5252
5768
  }
@@ -5592,7 +6108,7 @@ export class ResponseEngine {
5592
6108
  this.activeTaskRuntimeContexts.delete(session.id);
5593
6109
  this.activeRunnerModels.delete(session.id); // F1修复
5594
6110
  }
5595
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
6111
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, this.interruptedSessions.has(session.id) ? 'interrupted' : 'failed');
5596
6112
  logger.info(`[ResponseEngine] session ${session.id} processing cleared (on error) task=${taskId}`);
5597
6113
  }
5598
6114
  catch { }
@@ -5739,10 +6255,29 @@ export class ResponseEngine {
5739
6255
  sessionId: session.id,
5740
6256
  metadata: withTaskMetadata(sendOpts?.metadata),
5741
6257
  };
6258
+ const errorReason = isExecutionFailure(error)
6259
+ ? normalizeCoreErrorReason(error.reason)
6260
+ : retryExhaustedCount
6261
+ ? 'retry_exhausted'
6262
+ : retryInputAlreadySubmitted
6263
+ ? 'input_already_submitted'
6264
+ : modelFallbackExhaustedMessage
6265
+ ? 'fallback_exhausted'
6266
+ : isTotalExecutionTimeout || isTimeout
6267
+ ? 'timeout'
6268
+ : undefined;
6269
+ const errorSubtype = error instanceof OrphanedTargetSessionError
6270
+ ? ErrorType.SESSION_UNAVAILABLE
6271
+ : modelFallbackExhaustedMessage
6272
+ ? ErrorType.MODEL_UNAVAILABLE
6273
+ : normalizeCoreErrorSubtype(error, undefined, isTotalExecutionTimeout || isTimeout ? 'timeout' : undefined);
6274
+ const rawErrorText = getErrorRawText(error);
5742
6275
  const errorPayload = {
5743
6276
  kind: 'result.error',
5744
6277
  text: userMessage,
5745
- reason: isExecutionFailure(error) ? error.reason : retryExhaustedCount ? 'retry_exhausted' : retryInputAlreadySubmitted ? 'input_already_submitted' : modelFallbackExhaustedMessage ? 'model_unavailable' : isTotalExecutionTimeout ? 'total_execution_timeout' : isTimeout ? 'timeout' : errType,
6278
+ subtype: errorSubtype,
6279
+ ...(rawErrorText ? { raw: rawErrorText } : {}),
6280
+ ...(errorReason ? { reason: errorReason } : {}),
5746
6281
  };
5747
6282
  if (!isUserInterrupt && daemonTrigger && procStatus === 'error') {
5748
6283
  // DaemonChannel settles a trigger on the first terminal payload. Put
@@ -6178,10 +6713,11 @@ export class ResponseEngine {
6178
6713
  if (event.type === 'complete') {
6179
6714
  event = normalizeCompleteAgentEvent(event);
6180
6715
  }
6181
- if (event.type === 'tool_result' && event.isError) {
6716
+ if (event.type === 'tool_result' && isToolResultFailure(event)) {
6182
6717
  const classification = normalizeToolErrorClassification(event);
6183
6718
  event = {
6184
6719
  ...event,
6720
+ isError: true,
6185
6721
  errorCode: classification.errorCode,
6186
6722
  errorDetailCode: classification.errorDetailCode,
6187
6723
  classificationMissing: classification.classificationMissing || event.classificationMissing,
@@ -6327,7 +6863,20 @@ export class ResponseEngine {
6327
6863
  logger.info(`[ResponseEngine] Event: type=${event.type}${eventDetail}`);
6328
6864
  }
6329
6865
  // IMRenderer 旁路:proactive 模式逐事件投影为 thought(fire-and-forget)
6330
- renderer.emit(event);
6866
+ let diagnosticSuffix = '';
6867
+ let projectedEvent = event;
6868
+ if (event.type === 'tool_result' && isToolResultFailure(event)) {
6869
+ const byCallId = event.callId ? toolInputByCallId.get(event.callId) : undefined;
6870
+ const byCorrelationId = event.correlationId
6871
+ ? toolInputByCorrelationId.get(event.correlationId)
6872
+ : undefined;
6873
+ const original = event.callId && event.correlationId
6874
+ ? byCallId === byCorrelationId ? byCallId : undefined
6875
+ : byCallId ?? byCorrelationId;
6876
+ diagnosticSuffix = formatToolInputDiagnostics(diagnoseToolInputAfterFailure(original, event));
6877
+ projectedEvent = { ...event, error: getToolResultErrorText(event) + diagnosticSuffix };
6878
+ }
6879
+ renderer.emit(projectedEvent);
6331
6880
  if (event.type === 'text_start') {
6332
6881
  await renderer.sendTextStart(event.contentIndex, event.turn);
6333
6882
  continue;
@@ -6751,15 +7300,14 @@ export class ResponseEngine {
6751
7300
  }
6752
7301
  // 从 tool_use 阶段缓存的描述中回溯
6753
7302
  const cachedDesc = event.callId ? toolDescByCallId.get(event.callId) : undefined;
6754
- if (event.isError && !shouldSuppress()) {
7303
+ const toolResultFailed = isToolResultFailure(event);
7304
+ if (toolResultFailed && !shouldSuppress()) {
6755
7305
  hasErrorResult = true;
6756
- let errorMsg = event.error || (typeof event.result === 'string' ? event.result : JSON.stringify(event.result)) || '\u6267\u884c\u5931\u8d25';
6757
- // 移除 XML 风格的错误标签
6758
- errorMsg = errorMsg.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
6759
- renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
7306
+ const errorMsg = getToolResultErrorText(event) + diagnosticSuffix;
7307
+ renderer.addToolResult(event.name || '\u5de5\u5177', false, event.result, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
6760
7308
  }
6761
- else if (!event.isError && !shouldSuppress()) {
6762
- renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc, undefined, event.assistantUuid);
7309
+ else if (!toolResultFailed && !shouldSuppress()) {
7310
+ renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
6763
7311
  }
6764
7312
  }
6765
7313
  // 非终态运行时告警走 status notify,不进入 activity/thought 历史。