evolcore 0.0.33 → 0.0.34

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 (129) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +12 -1
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +82 -13
  6. package/dist/agents/codex/diagnostics.js +37 -11
  7. package/dist/agents/codex-runner.js +218 -44
  8. package/dist/agents/ecagent-runner.js +73 -20
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/identity.js +1 -1
  11. package/dist/channels/aun.js +16 -6
  12. package/dist/channels/daemon.js +6 -3
  13. package/dist/cli/aun-commands.js +3 -2
  14. package/dist/cli/ctl-command.js +3 -1
  15. package/dist/cli/daemon-commands.js +120 -19
  16. package/dist/cli/fs-command.js +2 -1
  17. package/dist/cli/index.js +20 -6
  18. package/dist/cli/integrity-command.js +196 -0
  19. package/dist/cli/memory-command.js +158 -145
  20. package/dist/cli/thread-command.js +5 -3
  21. package/dist/cli/trigger-command.js +6 -1
  22. package/dist/config/config-field-policy.js +4 -0
  23. package/dist/config/config-manager.js +19 -0
  24. package/dist/config/contact-book-store.js +5 -0
  25. package/dist/config/role-service.js +13 -0
  26. package/dist/config/runtime-env.js +16 -0
  27. package/dist/config-store.js +30 -0
  28. package/dist/core/audit/log-integrity.js +3 -1
  29. package/dist/core/auth/agent-delegation.js +108 -35
  30. package/dist/core/auth/delegation-diagnostics.js +414 -0
  31. package/dist/core/auth/operation-authorizer.js +1 -1
  32. package/dist/core/auth/operation-catalog.js +16 -0
  33. package/dist/core/command/agent-control.js +18 -26
  34. package/dist/core/command/command-handler.js +138 -4
  35. package/dist/core/command/daemon-diagnostics.js +1 -0
  36. package/dist/core/command/menu-catalog.js +3 -0
  37. package/dist/core/command/menu-handler.js +22 -7
  38. package/dist/core/command/menu-protocol.js +1 -0
  39. package/dist/core/command/slash-handler.js +2 -0
  40. package/dist/core/daemon-bootstrap.js +4 -7
  41. package/dist/core/file-integrity/read.js +61 -0
  42. package/dist/core/file-integrity/store.js +262 -0
  43. package/dist/core/ipc-registration.js +36 -1
  44. package/dist/core/memory/batch-meta.js +132 -0
  45. package/dist/core/memory/consolidation-context.js +35 -14
  46. package/dist/core/memory/consolidation-ops-records.js +14 -16
  47. package/dist/core/memory/consolidation.js +91 -26
  48. package/dist/core/memory/index-manager.js +19 -28
  49. package/dist/core/memory/memory-admin.js +19 -14
  50. package/dist/core/memory/memory-document.js +53 -9
  51. package/dist/core/memory/memory-writer.js +30 -15
  52. package/dist/core/message/im-renderer.js +20 -4
  53. package/dist/core/message/message-bridge.js +2 -1
  54. package/dist/core/message/response-engine.js +584 -47
  55. package/dist/core/permission/ec-command-parser.js +93 -4
  56. package/dist/core/permission/protected-paths.js +5 -1
  57. package/dist/core/permission/readonly-shell-query.js +43 -5
  58. package/dist/core/permission/session-path-grants.js +334 -0
  59. package/dist/core/permission/tool-canonical.js +226 -0
  60. package/dist/core/permission/tool-policy.js +30 -62
  61. package/dist/core/role/runtime-policy.js +21 -10
  62. package/dist/core/session/session-fs-store.js +123 -8
  63. package/dist/core/session/session-manager.js +75 -5
  64. package/dist/core/skill/index-generator.js +17 -3
  65. package/dist/core/system-session/grants.js +40 -0
  66. package/dist/core/system-session/memory.js +222 -0
  67. package/dist/core/system-session/registry.js +74 -0
  68. package/dist/core/system-session/types.js +14 -0
  69. package/dist/core/thread/thread-service.js +13 -20
  70. package/dist/eck/kit-renderer.js +7 -2
  71. package/dist/eck/manifest-engine.js +24 -4
  72. package/dist/index.js +11 -2
  73. package/dist/ipc.js +51 -40
  74. package/dist/paths.js +70 -0
  75. package/dist/stats/billing.js +20 -8
  76. package/dist/trigger/feedback.js +6 -1
  77. package/dist/trigger/parser.js +8 -1
  78. package/dist/trigger/system-definitions.js +3 -3
  79. package/dist/utils/cross-platform.js +11 -2
  80. package/dist/utils/error-dict.json +18 -4
  81. package/dist/utils/error-utils.js +121 -0
  82. package/dist/utils/instance-registry.js +13 -8
  83. package/dist/utils/log-writer.js +12 -0
  84. package/dist/utils/process-introspect.js +11 -0
  85. package/kits/docs/context-assembly.md +0 -1
  86. package/kits/docs/evolcore/agent-create.md +5 -4
  87. package/kits/docs/evolcore/agent.md +2 -2
  88. package/kits/docs/evolcore/config.md +2 -2
  89. package/kits/docs/evolcore/ctl.md +2 -2
  90. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  91. package/kits/docs/evolcore/fs-architecture.md +4 -5
  92. package/kits/docs/evolcore/fs.md +1 -1
  93. package/kits/docs/evolcore/group-collaboration.md +2 -2
  94. package/kits/docs/evolcore/group.md +10 -10
  95. package/kits/docs/evolcore/memory-architecture.md +4 -5
  96. package/kits/docs/evolcore/memory-consolidation.md +18 -14
  97. package/kits/docs/evolcore/memory-tags.md +3 -1
  98. package/kits/docs/evolcore/memory.md +24 -12
  99. package/kits/docs/evolcore/model.md +16 -0
  100. package/kits/docs/evolcore/msg.md +7 -7
  101. package/kits/docs/evolcore/thread-chain.md +5 -5
  102. package/kits/docs/prompt-loading-architecture.md +0 -1
  103. package/kits/eck_manifest.json +0 -20
  104. package/kits/eck_manifest.memory.json +0 -12
  105. package/kits/rules/01-overview.md +11 -0
  106. package/kits/rules/03-identity.md +6 -3
  107. package/kits/rules/06-channel.md +1 -1
  108. package/kits/rules/07-memory-and-thread.md +12 -15
  109. package/kits/schemas/_meta.json +7 -1
  110. package/kits/schemas/agent-config.schema.13.json +1 -1
  111. package/kits/schemas/daemon.schema.7.json +140 -0
  112. package/kits/skills/ec-group/SKILL.md +4 -4
  113. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  114. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  115. package/kits/templates/memory-consolidation/forget.md +2 -2
  116. package/kits/templates/memory-consolidation/generalize.md +4 -4
  117. package/kits/templates/memory-consolidation/notes.md +11 -11
  118. package/kits/templates/memory-consolidation/profile.md +3 -3
  119. package/kits/templates/memory-consolidation/promote.md +3 -3
  120. package/kits/templates/memory-consolidation/workflow.md +6 -6
  121. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  122. package/kits/templates/system-fragments/bootstrap.md +4 -3
  123. package/kits/templates/system-fragments/channel.md +2 -2
  124. package/kits/templates/system-fragments/memory.md +16 -9
  125. package/kits/templates/system-fragments/reply.md +6 -6
  126. package/kits/templates/system-fragments/session.md +1 -1
  127. package/package.json +4 -3
  128. package/dist/core/memory/memory-session-policy.js +0 -41
  129. 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,7 +933,7 @@ 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 暂时不可用';
936
+ const reason = getErrorRawText(error) || 'Codex 暂时不可用';
558
937
  return `⚠️ Codex 已接收本次消息,但本轮未能确认完成。为避免在会话中重复写入同一消息,系统未自动重试。请检查会话状态后重新发送。\n原因:${reason}`;
559
938
  }
560
939
  function isNonErrorInterruptReason(reason) {
@@ -567,7 +946,7 @@ function isExpectedTerminalInterrupt(reason) {
567
946
  return isAbortTerminalReason(reason);
568
947
  }
569
948
  function formatRetryableErrorFinalMessage(error, retries) {
570
- const reason = getErrorMessage(error, undefined, false) || 'API 暂时不可用';
949
+ const reason = getErrorRawText(error) || 'API 暂时不可用';
571
950
  return `❌ API 暂时不可用,已自动重试 ${retries} 次仍失败,任务已停止。\n原因:${reason}`;
572
951
  }
573
952
  /**
@@ -678,6 +1057,20 @@ function getStreamErrorText(result) {
678
1057
  }
679
1058
  return parts.join('\n');
680
1059
  }
1060
+ function getStreamRawErrorText(result, fallback) {
1061
+ const fullText = result.fullText.trim();
1062
+ const fullTextLooksLikeDiagnostic = fullText.length > 0 && (isRetryableError(new Error(fullText))
1063
+ || isContextTooLongText(fullText));
1064
+ const fallbackLooksLikeDiagnostic = typeof fallback === 'string' && fallback.trim().length > 0 && (isRetryableError(new Error(fallback))
1065
+ || isContextTooLongText(fallback));
1066
+ const candidates = [
1067
+ result.syntheticDiagnosticText,
1068
+ result.errors?.join('\n'),
1069
+ fullTextLooksLikeDiagnostic ? fullText : undefined,
1070
+ fallbackLooksLikeDiagnostic ? fallback : undefined,
1071
+ ];
1072
+ return candidates.find((part) => typeof part === 'string' && part.trim().length > 0)?.trim();
1073
+ }
681
1074
  function getStreamErrorMessage(result, includeEmoji = false) {
682
1075
  const raw = getStreamErrorText(result);
683
1076
  return getErrorMessage(new Error(raw), result.terminalReason, includeEmoji);
@@ -993,7 +1386,7 @@ export class ResponseEngine {
993
1386
  // revocation for legacy events without a taskId.
994
1387
  const interruptedTaskId = eventTaskId;
995
1388
  if (interruptedTaskId && activeTask?.taskId === interruptedTaskId) {
996
- this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId);
1389
+ this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId, 'interrupted');
997
1390
  }
998
1391
  }
999
1392
  });
@@ -1337,7 +1730,8 @@ export class ResponseEngine {
1337
1730
  || this.isPauseRequested(sessionId) || this.retryDelayWaiters.has(sessionId))
1338
1731
  return { state: 'live', ...identity };
1339
1732
  const runner = this.getAgent(session.metadata?.channelKey || session.channel, session.baseagent, selfAid);
1340
- return { state: runner.hasActiveStream(sessionId) ? 'live' : 'missing', ...identity,
1733
+ const owned = runner.hasActiveOperation?.(sessionId, active.taskId) ?? runner.hasActiveStream(sessionId);
1734
+ return { state: owned ? 'live' : 'missing', ...identity,
1341
1735
  hasTerminalOwner: this.activeTimeoutHandlers.has(sessionId) };
1342
1736
  }
1343
1737
  // Never adopt or interrupt a newer task to clean up an older attempt.
@@ -1380,6 +1774,14 @@ export class ResponseEngine {
1380
1774
  const interruptedTaskId = interruptedTurn?.active?.taskId;
1381
1775
  this.interruptedSessions.set(sessionId, effectiveReason);
1382
1776
  this.messageQueue?.markActiveTerminal?.(sessionId, effectiveReason);
1777
+ const channelKey = session.metadata?.channelKey || session.channel;
1778
+ const agent = this.getAgent(channelKey, session.baseagent, session.selfAID);
1779
+ const cancelledQueuedTurn = interruptedTurn?.active
1780
+ ? agent.cancelQueuedTurn?.(sessionId, {
1781
+ taskId: interruptedTurn.active.taskId,
1782
+ generation: interruptedTurn.active.generation,
1783
+ }) ?? false
1784
+ : false;
1383
1785
  // 终态必须先于渠道发送和 runner I/O 落盘。否则任一外部调用卡住时,
1384
1786
  // shutdown 超时退出会再次留下 started/interrupting 的悬空任务。
1385
1787
  const runtimeContext = this.activeTaskRuntimeContexts.get(sessionId);
@@ -1406,12 +1808,10 @@ export class ResponseEngine {
1406
1808
  this.activeTaskRuntimeContexts.delete(sessionId);
1407
1809
  this.activeRunnerModels.delete(sessionId);
1408
1810
  }
1409
- this.agentDelegationRegistry?.revokeTask(sessionId, taskId);
1811
+ this.agentDelegationRegistry?.revokeTask(sessionId, taskId, 'interrupted');
1410
1812
  }
1411
1813
  this.closeActiveRunnerTasks(sessionId, effectiveReason, taskSpan?.causation ?? runtimeContext?.causation);
1412
1814
  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
1815
  const terminalStatus = (() => {
1416
1816
  // TimeoutController owns timeout delivery (including user-facing timeout
1417
1817
  // details) and already guarantees a single terminal status. Sending it
@@ -1442,7 +1842,9 @@ export class ResponseEngine {
1442
1842
  logger.debug(`[ResponseEngine] interrupt terminal status failure: session=${sessionId} task=${interruptedTaskId} error=${error instanceof Error ? error.message : String(error)}`);
1443
1843
  });
1444
1844
  })();
1445
- const runnerInterrupt = agent.interrupt(sessionId).catch(error => {
1845
+ const runnerInterrupt = (cancelledQueuedTurn
1846
+ ? Promise.resolve()
1847
+ : agent.interrupt(sessionId)).catch(error => {
1446
1848
  logger.debug(`[ResponseEngine] interrupt barrier runner failure: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
1447
1849
  if (strict)
1448
1850
  throw error;
@@ -1759,9 +2161,15 @@ export class ResponseEngine {
1759
2161
  if (this.turnCoordinator.isCurrent(turnLease))
1760
2162
  return false;
1761
2163
  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
- });
2164
+ const cancelledQueuedTurn = agent.cancelQueuedTurn?.(sessionId, {
2165
+ taskId: turnLease.taskId,
2166
+ generation: turnLease.generation,
2167
+ }) ?? false;
2168
+ if (!cancelledQueuedTurn) {
2169
+ await agent.interrupt(sessionId).catch(interruptError => {
2170
+ logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
2171
+ });
2172
+ }
1765
2173
  agent.cleanupStream(streamKey);
1766
2174
  return true;
1767
2175
  }
@@ -3099,7 +3507,7 @@ export class ResponseEngine {
3099
3507
  this.activeTaskRuntimeContexts.delete(session.id);
3100
3508
  this.activeRunnerModels.delete(session.id); // F1修复
3101
3509
  }
3102
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
3510
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'interrupted');
3103
3511
  const daemonTrigger = this.isTrustedDaemonTrigger(message);
3104
3512
  if (!daemonTrigger) {
3105
3513
  await adapter.send(envelope, statusPayload).catch(() => { });
@@ -3107,7 +3515,11 @@ export class ResponseEngine {
3107
3515
  await adapter.send(envelope, {
3108
3516
  kind: 'result.error',
3109
3517
  text: userMessage,
3110
- reason: isExecutionFailure(timeoutError) ? timeoutError.reason : isTotalExecutionTimeout ? 'total_execution_timeout' : 'timeout',
3518
+ ...(getErrorRawText(timeoutError) ? { raw: getErrorRawText(timeoutError) } : {}),
3519
+ subtype: normalizeCoreErrorSubtype(timeoutError, undefined, isTotalExecutionTimeout ? 'timeout' : 'timeout'),
3520
+ reason: isExecutionFailure(timeoutError)
3521
+ ? normalizeCoreErrorReason(timeoutError.reason)
3522
+ : 'timeout',
3111
3523
  }).catch(() => { });
3112
3524
  if (daemonTrigger) {
3113
3525
  await adapter.send(envelope, statusPayload).catch(() => { });
@@ -3575,11 +3987,53 @@ export class ResponseEngine {
3575
3987
  ? resolvedMode.mode.configureRun(modeProcessCtx)
3576
3988
  : undefined;
3577
3989
  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;
3990
+ // 系统会话:路径 / 工具由 system-session 模块判定,与角色体系无关。
3991
+ // 有标记就只走模块,不挂模式策略、不算授权表、不并 runtime grant。
3992
+ const sessionMetadata = session.metadata;
3993
+ const declaredSessionType = session.sessionType ?? (typeof sessionMetadata?.sessionType === 'string' ? sessionMetadata.sessionType : undefined);
3994
+ const systemSessionMarker = readSystemSessionMarker(sessionMetadata);
3995
+ if (isSystemSessionType(declaredSessionType) && !systemSessionMarker) {
3996
+ throw new Error(`系统会话 ${declaredSessionType} 缺少有效 systemSession 标记`);
3997
+ }
3998
+ if (systemSessionMarker && declaredSessionType && declaredSessionType !== systemSessionMarker.type) {
3999
+ throw new Error(`系统会话标记类型 ${systemSessionMarker.type} 与 sessionType ${declaredSessionType} 不一致`);
4000
+ }
4001
+ const systemSessionModule = systemSessionMarker
4002
+ ? getSystemSessionModule(systemSessionMarker.type)
4003
+ : undefined;
4004
+ const systemSessionContext = systemSessionModule && systemSessionMarker
4005
+ ? buildSystemSessionContext({
4006
+ marker: systemSessionMarker,
4007
+ sessionId: session.id,
4008
+ selfAid: selfAid || session.selfAID || '',
4009
+ projectPath: absoluteProjectPath,
4010
+ managedTempDir: sessionRuntimeDir,
4011
+ metadata: sessionMetadata,
4012
+ })
4013
+ : undefined;
4014
+ const systemSessionCheck = systemSessionModule && systemSessionContext
4015
+ ? (toolName, toolInput) => systemSessionModule.checkTool(toolName, toolInput, systemSessionContext)
4016
+ : undefined;
4017
+ // 沙箱声明与 runner 有关,但当前所有 runner 共用同一个结论;仍按 runner 问一次,
4018
+ // 以便将来某个 runner 需要不同取舍时只改模块。
4019
+ const systemSessionSandbox = systemSessionModule && systemSessionContext
4020
+ ? systemSessionModule.runnerLaunch(normalizeBaseagent(agent.name).canonical, systemSessionContext).sandbox
4021
+ : undefined;
4022
+ const pureSessionPolicyHook = systemSessionMarker ? undefined : modePolicyHook;
4023
+ // 路径授权(仅用户会话):身份就是角色名;运行时授予(ec ctl path grant)按 sessionId 再并一层。
4024
+ const pathGrantIdentity = systemSessionMarker ? undefined : (peerRole || undefined);
4025
+ const grantedReadRoots = (() => {
4026
+ if (!pathGrantIdentity)
4027
+ return [];
4028
+ const table = mergeRuntimeGrantsForSession(BUILTIN_SESSION_PATH_GRANTS, session.id, pathGrantIdentity);
4029
+ return resolveGrantedReadRoots(table, {
4030
+ selfAid,
4031
+ peerKey: authPeerKey,
4032
+ projectPath: absoluteProjectPath,
4033
+ identity: pathGrantIdentity,
4034
+ permissionMode: effectivePermissionMode,
4035
+ });
4036
+ })();
3583
4037
  // 设置权限审批的交互上下文(支持交互卡片)
3584
4038
  const permissionContext = {
3585
4039
  sendPrompt: permissionPromptForOrigin,
@@ -3593,10 +4047,12 @@ export class ResponseEngine {
3593
4047
  agentName: agentNameForStats,
3594
4048
  taskId,
3595
4049
  chatmode: isProactive ? 'proactive' : 'interactive',
3596
- role: peerRole,
4050
+ // 系统会话没有角色
4051
+ role: systemSessionMarker ? undefined : peerRole,
3597
4052
  chatType: authChatType,
3598
4053
  selfAid,
3599
4054
  managedTempDir: sessionRuntimeDir,
4055
+ grantedReadRoots,
3600
4056
  allowReadonlySourceDiagnostics: effectiveAgentConfig?.readonlySourceDiagnostics === true,
3601
4057
  peerKey: authPeerKey,
3602
4058
  causation: taskCausation,
@@ -3606,6 +4062,13 @@ export class ResponseEngine {
3606
4062
  recordExecutionAnomaly,
3607
4063
  pauseController: taskPauseController,
3608
4064
  preToolUsePolicyHook: pureSessionPolicyHook,
4065
+ systemSessionCheck,
4066
+ ...(systemSessionModule && systemSessionContext
4067
+ ? {
4068
+ systemSessionGrants: systemSessionModule.grants(systemSessionContext),
4069
+ ...(systemSessionSandbox ? { systemSessionSandbox } : {}),
4070
+ }
4071
+ : {}),
3609
4072
  armApprovedDelegationCommand: this.agentDelegationRegistry
3610
4073
  ? (sessionToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
3611
4074
  sessionToken,
@@ -3614,6 +4077,11 @@ export class ResponseEngine {
3614
4077
  commandHash,
3615
4078
  })
3616
4079
  : undefined,
4080
+ bindSessionCredential: this.agentDelegationRegistry
4081
+ ? (sessionToken, threadId) => this.agentDelegationRegistry.registerSessionCredential({
4082
+ sessionToken, threadId, sessionId: session.id, taskId,
4083
+ })
4084
+ : undefined,
3617
4085
  turn: {
3618
4086
  sessionId: turnLease.sessionId,
3619
4087
  taskId,
@@ -3797,7 +4265,15 @@ export class ResponseEngine {
3797
4265
  const triggerPermissionModeOverride = message.triggerMeta?.permissionModeOverride;
3798
4266
  const commandFullAccess = message.executionPermissionOverride;
3799
4267
  const triggerFullAccess = triggerPermissionModeOverride === 'fullaccess';
3800
- if (commandFullAccess || triggerFullAccess) {
4268
+ if (systemSessionMarker) {
4269
+ // 系统会话不走角色 / trigger override / fullaccess:runner 模式由模块固定
4270
+ if (commandFullAccess || triggerFullAccess) {
4271
+ throw new Error('系统会话不支持 fullaccess');
4272
+ }
4273
+ effectivePermissionMode = systemSessionModule
4274
+ .runnerLaunch(normalizedBaseagent.canonical, systemSessionContext).runnerMode;
4275
+ }
4276
+ else if (commandFullAccess || triggerFullAccess) {
3801
4277
  if (commandFullAccess) {
3802
4278
  if (commandFullAccess.permissionMode !== 'fullaccess'
3803
4279
  || commandFullAccess.processRole !== 'fullaccess-run'
@@ -4011,10 +4487,13 @@ export class ResponseEngine {
4011
4487
  bootstrapAgentMdPath: agentMdPath(selfAid),
4012
4488
  };
4013
4489
  }
4490
+ // persona.md 是唯一的人格/交接文件(personal.md 已废弃并入)。
4491
+ // 该白名单让 owner 会话可直接用文件工具分段更新 `## Bootstrap 交接`,
4492
+ // 避免把长 Markdown 交接摘要塞进 `ec agent persona --text` 的 shell 转义。
4014
4493
  if (selfAid && peerRole === 'owner' && effectivePermissionMode !== 'readonly') {
4015
4494
  modelOverride = {
4016
4495
  ...(modelOverride || {}),
4017
- personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'personal.md'),
4496
+ personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'persona.md'),
4018
4497
  };
4019
4498
  }
4020
4499
  if (normalizedBaseagent.canonical === 'claude') {
@@ -4258,9 +4737,12 @@ export class ResponseEngine {
4258
4737
  // Bootstrap always uses the lifecycle-gated main manifest. A custom
4259
4738
  // session manifest (for example the auxiliary manifest with `always`
4260
4739
  // sections) must not reintroduce normal rules or command context.
4740
+ // 系统会话:manifest 由模块按 type/subtype 决定,不读 config
4261
4741
  const manifestFile = isBootstrapping
4262
4742
  ? DEFAULT_MANIFEST_FILE
4263
- : resolveSessionManifest(sessionType, sessionManifests);
4743
+ : systemSessionMarker
4744
+ ? getSystemSessionModule(systemSessionMarker.type).manifest(systemSessionMarker.subtype)
4745
+ : resolveSessionManifest(sessionType, sessionManifests);
4264
4746
  const kitContext = renderKitSections(kitCtx, manifestFile);
4265
4747
  effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
4266
4748
  // ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
@@ -4495,7 +4977,8 @@ export class ResponseEngine {
4495
4977
  peerKey,
4496
4978
  peerName: peerName || undefined,
4497
4979
  peerType: message.peerType || session.metadata?.peerType || undefined,
4498
- peerRole,
4980
+ // 系统会话没有角色
4981
+ ...(systemSessionMarker ? {} : { peerRole }),
4499
4982
  // 会话原型与整理模式:ipc 侧靠 sessionType 拦非整理会话的写命令,
4500
4983
  // 靠 memoryMode 校验 --scope 是否与本模式匹配。
4501
4984
  sessionType: renderedSessionType,
@@ -4532,7 +5015,8 @@ export class ResponseEngine {
4532
5015
  chatType: configChatType,
4533
5016
  selfAid,
4534
5017
  peerKey,
4535
- issuedRole: peerRole,
5018
+ // 系统会话与角色体系无关:带标记、不签角色
5019
+ ...(systemSessionMarker ? { systemSession: systemSessionMarker } : { issuedRole: peerRole }),
4536
5020
  executionSource: fullAccessAuthorization?.source ?? (message.source === 'trigger' ? 'trigger' : 'user'),
4537
5021
  permissionMode: effectivePermissionMode,
4538
5022
  approvalInteractionPolicy,
@@ -4798,7 +5282,7 @@ export class ResponseEngine {
4798
5282
  const candidateHint = fallbackCandidates.length === 0
4799
5283
  ? '未配置可用备用模型'
4800
5284
  : '备用模型均不可用';
4801
- throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`);
5285
+ throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`, retryError);
4802
5286
  }
4803
5287
  if (retryAttemptTimedOut || isRetryableError(retryError)) {
4804
5288
  await recordRetryHealthError(retryError);
@@ -4926,7 +5410,7 @@ export class ResponseEngine {
4926
5410
  this.activeTaskRuntimeContexts.delete(session.id);
4927
5411
  this.activeRunnerModels.delete(session.id); // F1修复
4928
5412
  }
4929
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5413
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
4930
5414
  logger.info(`[ResponseEngine] Stale turn stopped before publish: session=${session.id} task=${taskId}`);
4931
5415
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
4932
5416
  expected: turnLease?.generation,
@@ -4942,7 +5426,7 @@ export class ResponseEngine {
4942
5426
  this.activeTaskRuntimeContexts.delete(session.id);
4943
5427
  this.activeRunnerModels.delete(session.id); // F1修复
4944
5428
  }
4945
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5429
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
4946
5430
  logger.info(`[ResponseEngine] Stale turn stopped before protocol rejection: session=${session.id} task=${taskId}`);
4947
5431
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
4948
5432
  expected: turnLease?.generation,
@@ -5096,6 +5580,7 @@ export class ResponseEngine {
5096
5580
  let sentErrorAsSystemError = false;
5097
5581
  const finalReplyText = streamResult.lastReplyText || streamResult.fullText;
5098
5582
  const visibleFinalReplyText = stripContextLimitErrorLines(finalReplyText);
5583
+ const streamRawErrorText = getStreamRawErrorText(streamResult, visibleFinalReplyText || undefined);
5099
5584
  const isTriggerNoop = message.source === 'trigger'
5100
5585
  && typeof message.triggerMeta?.noopSentinel === 'string'
5101
5586
  && message.triggerMeta.noopSentinel.length > 0
@@ -5107,7 +5592,7 @@ export class ResponseEngine {
5107
5592
  this.activeTaskRuntimeContexts.delete(session.id);
5108
5593
  this.activeRunnerModels.delete(session.id); // F1修复
5109
5594
  }
5110
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5595
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
5111
5596
  logger.info(`[ResponseEngine] Stale turn stopped before final flush: session=${session.id} task=${taskId}`);
5112
5597
  await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
5113
5598
  return;
@@ -5128,10 +5613,14 @@ export class ResponseEngine {
5128
5613
  if (streamResult.isError) {
5129
5614
  // 用原始诊断文本(含错误码)判断可重试性,不用已过 stripContextLimitErrorLines 的展示文本
5130
5615
  const recoverable = isRetryableError(new Error(getStreamErrorText(streamResult)));
5616
+ const subtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || getStreamErrorText(streamResult)), streamResult.subtype, streamResult.terminalReason);
5617
+ const reason = normalizeCoreErrorReason(streamResult.terminalReason);
5131
5618
  await adapter.send(envelope, {
5132
5619
  kind: 'system.error',
5133
5620
  text: errorText,
5134
- subtype: streamResult.subtype ?? 'api_error',
5621
+ ...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
5622
+ subtype,
5623
+ ...(reason ? { reason } : {}),
5135
5624
  recoverable,
5136
5625
  }).catch(() => { });
5137
5626
  sentErrorAsSystemError = true;
@@ -5148,7 +5637,7 @@ export class ResponseEngine {
5148
5637
  this.activeTaskRuntimeContexts.delete(session.id);
5149
5638
  this.activeRunnerModels.delete(session.id); // F1修复
5150
5639
  }
5151
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
5640
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, streamResult.isError ? 'failed' : 'completed');
5152
5641
  logger.info(`[ResponseEngine] session ${session.id} processing cleared task=${taskId}`);
5153
5642
  import('../../utils/msg-diag.js').then(m => m.diagLog('task_done', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, text: message.content ?? '' })).catch(() => { });
5154
5643
  // 降级模型回复末尾追加标记(代码层硬注入,不依赖模型输出)
@@ -5217,6 +5706,10 @@ export class ResponseEngine {
5217
5706
  : getStreamErrorMessage(streamResult, false);
5218
5707
  const rawSubtype = streamResult.subtype || 'agent_error';
5219
5708
  const errorType = prefixErrorType(ERROR_PREFIX.AGENT, rawSubtype);
5709
+ const coreSubtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || errorSummary), rawSubtype, streamResult.terminalReason);
5710
+ const coreReason = permissionTerminalFailure
5711
+ ? normalizeCoreErrorReason(permissionTerminalFailure.reason)
5712
+ : normalizeCoreErrorReason(streamResult.terminalReason);
5220
5713
  // 用户主动打断(新消息/​/stop/​撤回)会让 SDK 流在工具调用中途被掐断,
5221
5714
  // 末尾 result message 形状异常并被标记为 error(含 SDK 内部 ede_diagnostic 串)。
5222
5715
  // 这不是真正的失败,不应把诊断串暴露给用户,也不计入错误统计。
@@ -5241,12 +5734,24 @@ export class ResponseEngine {
5241
5734
  if (this.isTrustedDaemonTrigger(message)) {
5242
5735
  await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
5243
5736
  if (!sentErrorAsSystemError) {
5244
- await adapter.send(envelope, { kind: 'result.error', text: userErrorSummary, reason: rawSubtype }).catch(() => { });
5737
+ await adapter.send(envelope, {
5738
+ kind: 'result.error',
5739
+ text: userErrorSummary,
5740
+ ...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
5741
+ subtype: coreSubtype,
5742
+ ...(coreReason ? { reason: coreReason } : {}),
5743
+ }).catch(() => { });
5245
5744
  }
5246
5745
  }
5247
5746
  else {
5248
5747
  if (!sentErrorAsSystemError) {
5249
- 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(() => { });
5250
5755
  }
5251
5756
  await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
5252
5757
  }
@@ -5592,7 +6097,7 @@ export class ResponseEngine {
5592
6097
  this.activeTaskRuntimeContexts.delete(session.id);
5593
6098
  this.activeRunnerModels.delete(session.id); // F1修复
5594
6099
  }
5595
- this.agentDelegationRegistry?.revokeTask(session.id, taskId);
6100
+ this.agentDelegationRegistry?.revokeTask(session.id, taskId, this.interruptedSessions.has(session.id) ? 'interrupted' : 'failed');
5596
6101
  logger.info(`[ResponseEngine] session ${session.id} processing cleared (on error) task=${taskId}`);
5597
6102
  }
5598
6103
  catch { }
@@ -5739,10 +6244,29 @@ export class ResponseEngine {
5739
6244
  sessionId: session.id,
5740
6245
  metadata: withTaskMetadata(sendOpts?.metadata),
5741
6246
  };
6247
+ const errorReason = isExecutionFailure(error)
6248
+ ? normalizeCoreErrorReason(error.reason)
6249
+ : retryExhaustedCount
6250
+ ? 'retry_exhausted'
6251
+ : retryInputAlreadySubmitted
6252
+ ? 'input_already_submitted'
6253
+ : modelFallbackExhaustedMessage
6254
+ ? 'fallback_exhausted'
6255
+ : isTotalExecutionTimeout || isTimeout
6256
+ ? 'timeout'
6257
+ : undefined;
6258
+ const errorSubtype = error instanceof OrphanedTargetSessionError
6259
+ ? ErrorType.SESSION_UNAVAILABLE
6260
+ : modelFallbackExhaustedMessage
6261
+ ? ErrorType.MODEL_UNAVAILABLE
6262
+ : normalizeCoreErrorSubtype(error, undefined, isTotalExecutionTimeout || isTimeout ? 'timeout' : undefined);
6263
+ const rawErrorText = getErrorRawText(error);
5742
6264
  const errorPayload = {
5743
6265
  kind: 'result.error',
5744
6266
  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,
6267
+ subtype: errorSubtype,
6268
+ ...(rawErrorText ? { raw: rawErrorText } : {}),
6269
+ ...(errorReason ? { reason: errorReason } : {}),
5746
6270
  };
5747
6271
  if (!isUserInterrupt && daemonTrigger && procStatus === 'error') {
5748
6272
  // DaemonChannel settles a trigger on the first terminal payload. Put
@@ -6178,10 +6702,11 @@ export class ResponseEngine {
6178
6702
  if (event.type === 'complete') {
6179
6703
  event = normalizeCompleteAgentEvent(event);
6180
6704
  }
6181
- if (event.type === 'tool_result' && event.isError) {
6705
+ if (event.type === 'tool_result' && isToolResultFailure(event)) {
6182
6706
  const classification = normalizeToolErrorClassification(event);
6183
6707
  event = {
6184
6708
  ...event,
6709
+ isError: true,
6185
6710
  errorCode: classification.errorCode,
6186
6711
  errorDetailCode: classification.errorDetailCode,
6187
6712
  classificationMissing: classification.classificationMissing || event.classificationMissing,
@@ -6327,7 +6852,20 @@ export class ResponseEngine {
6327
6852
  logger.info(`[ResponseEngine] Event: type=${event.type}${eventDetail}`);
6328
6853
  }
6329
6854
  // IMRenderer 旁路:proactive 模式逐事件投影为 thought(fire-and-forget)
6330
- renderer.emit(event);
6855
+ let diagnosticSuffix = '';
6856
+ let projectedEvent = event;
6857
+ if (event.type === 'tool_result' && isToolResultFailure(event)) {
6858
+ const byCallId = event.callId ? toolInputByCallId.get(event.callId) : undefined;
6859
+ const byCorrelationId = event.correlationId
6860
+ ? toolInputByCorrelationId.get(event.correlationId)
6861
+ : undefined;
6862
+ const original = event.callId && event.correlationId
6863
+ ? byCallId === byCorrelationId ? byCallId : undefined
6864
+ : byCallId ?? byCorrelationId;
6865
+ diagnosticSuffix = formatToolInputDiagnostics(diagnoseToolInputAfterFailure(original, event));
6866
+ projectedEvent = { ...event, error: getToolResultErrorText(event) + diagnosticSuffix };
6867
+ }
6868
+ renderer.emit(projectedEvent);
6331
6869
  if (event.type === 'text_start') {
6332
6870
  await renderer.sendTextStart(event.contentIndex, event.turn);
6333
6871
  continue;
@@ -6751,15 +7289,14 @@ export class ResponseEngine {
6751
7289
  }
6752
7290
  // 从 tool_use 阶段缓存的描述中回溯
6753
7291
  const cachedDesc = event.callId ? toolDescByCallId.get(event.callId) : undefined;
6754
- if (event.isError && !shouldSuppress()) {
7292
+ const toolResultFailed = isToolResultFailure(event);
7293
+ if (toolResultFailed && !shouldSuppress()) {
6755
7294
  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);
7295
+ const errorMsg = getToolResultErrorText(event) + diagnosticSuffix;
7296
+ renderer.addToolResult(event.name || '\u5de5\u5177', false, event.result, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
6760
7297
  }
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);
7298
+ else if (!toolResultFailed && !shouldSuppress()) {
7299
+ renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
6763
7300
  }
6764
7301
  }
6765
7302
  // 非终态运行时告警走 status notify,不进入 activity/thought 历史。