evolcore 0.0.35 → 0.0.36

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 (224) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +134 -48
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +109 -34
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -1,6 +1,29 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { isTrustedWindowsShellExecutable } from '../../utils/windows-shell-trust.js';
4
+ export function parseJsonObjectArgument(raw, optionName = '--payload') {
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(raw);
8
+ }
9
+ catch (error) {
10
+ const detail = error instanceof Error ? `: ${error.message}` : '';
11
+ throw new Error(`${optionName} must be valid JSON${detail}`);
12
+ }
13
+ if (typeof parsed === 'string') {
14
+ try {
15
+ parsed = JSON.parse(parsed);
16
+ }
17
+ catch (error) {
18
+ const detail = error instanceof Error ? `: ${error.message}` : '';
19
+ throw new Error(`${optionName} JSON string must contain valid JSON${detail}`);
20
+ }
21
+ }
22
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
23
+ throw new Error(`${optionName} must be a JSON object`);
24
+ }
25
+ return parsed;
26
+ }
4
27
  function parseApplyPatchCommand(command, options = {}) {
5
28
  const input = command.trim();
6
29
  let commandText = input;
@@ -114,7 +137,8 @@ export function parseShellApplyPatchInvocation(input, options = {}, parsedArgv)
114
137
  else {
115
138
  addCandidate(rawCommand);
116
139
  }
117
- addCandidate(carrier?.command);
140
+ if (!carrier?.parseFailure)
141
+ addCandidate(carrier?.command);
118
142
  if (parsedArgv && argvUsesShellApplyPatch(parsedArgv)) {
119
143
  return parseApplyPatchArgv(parsedArgv);
120
144
  }
@@ -641,15 +665,68 @@ function hasCarrierEcCommandPrefix(command, dialect) {
641
665
  export function normalizeEvolcoreCommandArgv(argv) {
642
666
  if (argv.length === 0)
643
667
  return [];
644
- const executable = argv[0];
668
+ const materialized = materializeEvolcoreCmdShimArgv(argv);
669
+ const executable = materialized[0];
645
670
  if (typeof executable === 'string' && EVOLCORE_EXECUTABLE_ALIASES.has(executable.toLowerCase())) {
646
- return ['ec', ...argv.slice(1)];
671
+ return ['ec', ...materialized.slice(1)];
647
672
  }
648
- if (typeof executable === 'string' && isNodeExecutable(executable) && argv.length >= 2
649
- && typeof argv[1] === 'string' && normalizeNodeWrapperPath(argv[1])) {
650
- return ['ec', ...argv.slice(2)];
673
+ if (typeof executable === 'string' && isNodeExecutable(executable) && materialized.length >= 2
674
+ && typeof materialized[1] === 'string' && normalizeNodeWrapperPath(materialized[1])) {
675
+ return ['ec', ...materialized.slice(2)];
676
+ }
677
+ return materialized;
678
+ }
679
+ /**
680
+ * Materialize the argv that an npm `ec.cmd` shim gives to Node.
681
+ *
682
+ * PowerShell first produces an argv for the batch file and the shim then
683
+ * expands `%*` into a command line before starting Node. That second Windows
684
+ * command-line parse consumes an even half of each backslash run before a
685
+ * quote; an odd run leaves a literal quote. JSON commonly arrives as
686
+ * `{\"key\":\"value\"}` at this boundary, while Node actually receives
687
+ * `{\"key\":\"value\"}` with the escaping backslashes removed. The
688
+ * approval hash must describe the latter, because that is what the CLI uses
689
+ * when it authenticates the IPC request.
690
+ *
691
+ * This is deliberately limited to the trusted EC batch entrypoint. It is
692
+ * not a general cmd.exe parser and does not authorize shell composition.
693
+ */
694
+ function materializeEvolcoreCmdShimArg(value) {
695
+ let result = '';
696
+ let backslashes = 0;
697
+ const flushBackslashes = (count = backslashes) => {
698
+ if (count > 0)
699
+ result += '\\'.repeat(count);
700
+ backslashes = 0;
701
+ };
702
+ for (const char of value) {
703
+ if (char === '\\') {
704
+ backslashes++;
705
+ continue;
706
+ }
707
+ if (char === '"') {
708
+ result += '\\'.repeat(Math.floor(backslashes / 2));
709
+ if (backslashes % 2 === 1)
710
+ result += '"';
711
+ backslashes = 0;
712
+ continue;
713
+ }
714
+ flushBackslashes();
715
+ result += char;
651
716
  }
652
- return [...argv];
717
+ flushBackslashes();
718
+ return result;
719
+ }
720
+ function isEvolcoreCmdShimExecutable(value) {
721
+ if (!value)
722
+ return false;
723
+ const normalized = value.replaceAll('\\', '/').toLowerCase();
724
+ return normalized === 'ec.cmd' || normalized.endsWith('/ec.cmd');
725
+ }
726
+ function materializeEvolcoreCmdShimArgv(argv) {
727
+ if (!isEvolcoreCmdShimExecutable(argv[0]))
728
+ return [...argv];
729
+ return [argv[0], ...argv.slice(1).map(materializeEvolcoreCmdShimArg)];
653
730
  }
654
731
  const INLINE_TEXT_FLAGS = {
655
732
  agentNew: new Set(['--description', '--persona-text', '--handoff']),
@@ -669,6 +746,7 @@ const MESSAGE_POSITIONAL_FLAGS = new Set([
669
746
  '--file', '--payload', '--as', '--content-type', '--text', '--transcript',
670
747
  '--thread', '--title', '--description', '--text-from-file',
671
748
  '--handoff-context', '--aun-path', '--app', '--format', '--encrypt', '--no-encrypt',
749
+ '--return', '--timeout',
672
750
  ]);
673
751
  /** Decode the EC inline text contract exactly once, after shell parsing. */
674
752
  export function decodeEvolcoreInlineText(value) {
@@ -980,6 +1058,8 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
980
1058
  return parseFailure('untrusted-shell', carrier.command, 0, 0, carrier.dialect);
981
1059
  }
982
1060
  }
1061
+ if (carrier.parseFailure)
1062
+ return carrier.parseFailure;
983
1063
  const unwrapped = carrier.dialect === 'powershell'
984
1064
  ? unwrapPowerShellCredentialPrefix(carrier.command)
985
1065
  : { kind: 'none', command: carrier.command };
@@ -1012,9 +1092,10 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
1012
1092
  return parseFailure('shell-composition', unwrapped.command, 0, 0, carrier.dialect);
1013
1093
  }
1014
1094
  const bodyArgv = materialized.argv.slice(executeIndex + 1);
1015
- const materializedBody = bodyArgv.length === 1
1095
+ const parsedBody = bodyArgv.length === 1
1016
1096
  ? parseLiteralShellCommand(bodyArgv[0], { ...options, dialect: 'cmd' })
1017
1097
  : { ok: true, argv: normalizeEvolcoreCommandArgv(bodyArgv) };
1098
+ const materializedBody = parsedBody;
1018
1099
  if (!materializedBody.ok
1019
1100
  || materializedBody.argv.length !== nested.argv.length
1020
1101
  || materializedBody.argv.some((value, index) => value !== nested.argv[index])) {
@@ -1452,21 +1533,27 @@ function resolveCodexPowerShellCarrierString(command) {
1452
1533
  /** Resolve a POSIX shell carrier when app-server provides only a string. */
1453
1534
  function resolveCodexPosixShellCarrierString(command) {
1454
1535
  const input = trimShellSpacing(command);
1536
+ const parsed = parseLiteralShellCommand(input);
1537
+ if (parsed.ok) {
1538
+ const carrier = resolveCodexShellCarrierArgv(parsed.argv);
1539
+ if (carrier?.dialect === 'posix')
1540
+ return carrier;
1541
+ }
1455
1542
  const match = input.match(/^(?:"([^"]+)"|(\S+))\s+(-lc|-c)\s+(.+)$/s);
1456
1543
  if (!match)
1457
1544
  return undefined;
1458
1545
  const executable = match[1] ?? match[2];
1459
1546
  if (!isCodexPosixShellExecutable(executable))
1460
1547
  return undefined;
1461
- // A string-form carrier has one additional command-line quoting layer. Strip
1462
- // only the outer pair; quotes belonging to the nested command remain intact.
1463
- let nested = match[4].trim();
1464
- if (nested.length >= 2
1465
- && ((nested.startsWith('"') && nested.endsWith('"'))
1466
- || (nested.startsWith("'") && nested.endsWith("'")))) {
1467
- nested = nested.slice(1, -1);
1468
- }
1469
- return nested ? { command: nested, dialect: 'posix', executable } : undefined;
1548
+ const nested = match[4].trim().replace(/^['"]/, '');
1549
+ return {
1550
+ command: nested,
1551
+ dialect: 'posix',
1552
+ executable,
1553
+ parseFailure: parsed.ok
1554
+ ? parseFailure('shell-composition', input, 0, 0, 'posix')
1555
+ : parsed,
1556
+ };
1470
1557
  }
1471
1558
  export function resolveCodexShellCarrierString(command) {
1472
1559
  return resolveCodexCmdCarrierString(command)
@@ -2424,16 +2511,40 @@ export function parseCodexExecToolCommand(input) {
2424
2511
  }
2425
2512
  return decoded;
2426
2513
  }
2514
+ /**
2515
+ * parseCodexToolCommand 只接受单条字面量调用;`cd <dir> && ec …` 和 `ec … | <filter>`
2516
+ * 包裹的发送命令会被判为 shell-composition。classifyEvolcoreShellCommand 已对这两种
2517
+ * 复合形态做了安全边界校验,复用它提取的 ec argv 让下游发送校验看到和裸调用一致的字面量。
2518
+ */
2519
+ function classifyCompositeProactiveSend(commandText, options) {
2520
+ const classified = classifyEvolcoreShellCommand(commandText, options);
2521
+ if (classified.kind === 'cd-prefixed' || classified.kind === 'bounded-output') {
2522
+ const argv = normalizeEvolcoreCommandArgv(classified.command.argv);
2523
+ if (argv[0] === 'ec')
2524
+ return { ok: true, argv };
2525
+ }
2526
+ return null;
2527
+ }
2427
2528
  function parseProactiveToolCommand(toolName, input) {
2428
2529
  const options = {
2429
2530
  verifyShellExecutable: process.platform === 'win32',
2430
2531
  };
2431
- if (toolName === 'Bash' || toolName === 'Shell')
2432
- return parseCodexToolCommand(input, options);
2532
+ if (toolName === 'Bash' || toolName === 'Shell') {
2533
+ const literal = parseCodexToolCommand(input, options);
2534
+ if (literal.ok)
2535
+ return literal;
2536
+ const commandText = typeof input.command === 'string' ? input.command : '';
2537
+ return commandText ? (classifyCompositeProactiveSend(commandText, options) ?? literal) : literal;
2538
+ }
2433
2539
  if (toolName !== 'exec')
2434
2540
  return null;
2435
2541
  const command = parseCodexExecToolCommand(input);
2436
- return command ? parseCodexToolCommand({ command }, options) : null;
2542
+ if (!command)
2543
+ return null;
2544
+ const literal = parseCodexToolCommand({ command }, options);
2545
+ if (literal.ok)
2546
+ return literal;
2547
+ return classifyCompositeProactiveSend(command, options) ?? literal;
2437
2548
  }
2438
2549
  function scanBoundedOutputOperators(command) {
2439
2550
  const operators = [];
@@ -2888,7 +2999,7 @@ const PROACTIVE_SEND_VALUE_FLAGS = new Set([
2888
2999
  '--format', '--app', '--file', '--text-from-file', '--link', '--payload',
2889
3000
  '--title', '--description', '--text', '--transcript', '--as',
2890
3001
  '--content-type', '--mention', '--aun-path', '--thread', '--handoff-context',
2891
- '--check-every', '--trigger-prompt',
3002
+ '--check-every', '--trigger-prompt', '--return', '--timeout',
2892
3003
  ]);
2893
3004
  const PROACTIVE_SEND_BOOLEAN_FLAGS = new Set([
2894
3005
  '--encrypt', '--no-encrypt', '--mention-all',
@@ -3019,8 +3130,8 @@ function hasExecutableProactiveSendPayload(argv, scope) {
3019
3130
  }
3020
3131
  if (modes.has('payload')) {
3021
3132
  try {
3022
- const payload = JSON.parse(modes.get('payload'));
3023
- return !!payload && typeof payload === 'object' && !Array.isArray(payload);
3133
+ parseJsonObjectArgument(modes.get('payload'));
3134
+ return true;
3024
3135
  }
3025
3136
  catch {
3026
3137
  return false;
@@ -3134,8 +3245,10 @@ export function describeProactiveSendCommandFailure(toolName, input, session) {
3134
3245
  + ' "<正文>"';
3135
3246
  // 带 --thread 的发送走话题路由校验,允许省略 from/to;走到这里说明话题本身没解析通过。
3136
3247
  if (sendOptionValue(argv, '--thread')) {
3137
- return `${prefix}带 --thread 的跨话题发送未通过校验——话题必须已存在于本端索引,from(若给出)必须是本人,`
3138
- + `to(若给出)必须是该话题对端,且需携带 --check-every。可用 ec thread list <对方AID> 确认话题。`;
3248
+ return `${prefix}带 --thread 的跨话题发送未通过校验——话题必须已存在于本端:话题写裸 id,`
3249
+ + `主话题写 main:<aidA>:<aidB>(两端之一必须是本端)。from(若给出)必须是本人,`
3250
+ + `to(若给出)必须是该话题对端,且需携带 --check-every。`
3251
+ + `主话题 id 只在本端之间有效,不要转发给别人使用。可用 ec thread list <对方AID> 取得可用值。`;
3139
3252
  }
3140
3253
  // 私聊/群聊用错了 scope。
3141
3254
  if (session?.chatType && (session.chatType === 'group') !== (scope === 'group')) {
@@ -0,0 +1,141 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { isSameOrDescendant, resolveProtectedCandidate } from './protected-paths.js';
4
+ function literalPath(value, cwd) {
5
+ if (typeof value !== 'string' || !value.trim() || /[\0\r\n*?\[\]{}$`]/.test(value))
6
+ return undefined;
7
+ const canonical = resolveProtectedCandidate(value, cwd);
8
+ // Broad convenience grants never represent a filesystem/drive root.
9
+ if (path.dirname(canonical) === canonical)
10
+ return undefined;
11
+ return process.platform === 'win32' ? canonical.toLowerCase() : canonical;
12
+ }
13
+ export function fileChangeApprovalScope(rawPaths, cwd, grantRoot) {
14
+ // A grantRoot does not replace concrete targets (notably move destinations).
15
+ const candidates = grantRoot ? [grantRoot, ...rawPaths] : rawPaths;
16
+ if (candidates.length === 0)
17
+ return undefined;
18
+ const paths = candidates.map(candidate => literalPath(candidate, cwd));
19
+ if (paths.some(candidate => !candidate))
20
+ return undefined;
21
+ const canonical = [...new Set(paths)].sort();
22
+ const root = grantRoot ? literalPath(grantRoot, cwd) : undefined;
23
+ const directories = root
24
+ ? canonical.every(candidate => isSameOrDescendant(candidate, root)) ? [root] : []
25
+ : [...new Set(canonical.map(candidate => path.dirname(candidate)))];
26
+ return {
27
+ targets: canonical.map(candidate => ({ path: candidate, access: 'write' })),
28
+ ...(directories.length === 1 && path.dirname(directories[0]) !== directories[0]
29
+ ? { directory: directories[0] } : {}),
30
+ access: 'write',
31
+ };
32
+ }
33
+ /**
34
+ * Parse only proven, filesystem-only capabilities. Commands, network, globs,
35
+ * root aliases, unknown fields and future schemas retain exact-input approval.
36
+ */
37
+ export function permissionApprovalScope(input, cwd, workspace) {
38
+ if (Object.keys(input).some(key => !['permissions', 'cwd', 'reason'].includes(key)))
39
+ return undefined;
40
+ const profile = input.permissions;
41
+ if (!profile || typeof profile !== 'object' || Array.isArray(profile))
42
+ return undefined;
43
+ const record = profile;
44
+ if (Object.keys(record).length !== 1)
45
+ return undefined;
46
+ const filesystem = record.fileSystem ?? record.filesystem;
47
+ if (!filesystem || typeof filesystem !== 'object' || Array.isArray(filesystem))
48
+ return undefined;
49
+ const targets = [];
50
+ const add = (value, access) => {
51
+ let candidate = value;
52
+ let base = cwd;
53
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
54
+ const spec = value;
55
+ if (spec.type === 'special' && spec.value && typeof spec.value === 'object') {
56
+ const special = spec.value;
57
+ if (special.kind !== 'project_roots'
58
+ || Object.keys(spec).some(key => key !== 'type' && key !== 'value')
59
+ || Object.keys(special).some(key => key !== 'kind' && key !== 'subpath'))
60
+ return false;
61
+ candidate = special.subpath ?? '.';
62
+ base = workspace;
63
+ }
64
+ else if (Object.keys(spec).length === 1 && (typeof spec.path === 'string' || typeof spec.pattern === 'string')) {
65
+ candidate = spec.path ?? spec.pattern;
66
+ }
67
+ else
68
+ return false;
69
+ }
70
+ if (typeof candidate === 'string' && candidate.startsWith(':'))
71
+ return false;
72
+ const canonical = literalPath(candidate, base);
73
+ if (!canonical)
74
+ return false;
75
+ targets.push({ path: canonical, access });
76
+ return true;
77
+ };
78
+ for (const [key, value] of Object.entries(filesystem)) {
79
+ if (key === 'read' || key === 'write') {
80
+ if (!Array.isArray(value) || !value.every(candidate => add(candidate, key)))
81
+ return undefined;
82
+ }
83
+ else if (key === 'entries') {
84
+ if (!Array.isArray(value))
85
+ return undefined;
86
+ for (const entry of value) {
87
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
88
+ return undefined;
89
+ const rule = entry;
90
+ if (Object.keys(rule).some(field => field !== 'access' && field !== 'path'))
91
+ return undefined;
92
+ if (rule.access === 'deny')
93
+ continue;
94
+ if ((rule.access !== 'read' && rule.access !== 'write') || !add(rule.path, rule.access))
95
+ return undefined;
96
+ }
97
+ }
98
+ else if (value === 'read' || value === 'write') {
99
+ if (!add(key, value))
100
+ return undefined;
101
+ }
102
+ else if (value !== 'deny')
103
+ return undefined;
104
+ }
105
+ if (targets.length === 0)
106
+ return undefined;
107
+ const directories = [...new Set(targets.map(target => {
108
+ try {
109
+ if (fs.statSync(target.path).isDirectory())
110
+ return target.path;
111
+ }
112
+ catch { }
113
+ return path.dirname(target.path);
114
+ }))];
115
+ return {
116
+ targets,
117
+ ...(directories.length === 1 && path.dirname(directories[0]) !== directories[0]
118
+ ? { directory: directories[0] } : {}),
119
+ access: targets.some(target => target.access === 'write') ? 'write' : 'read',
120
+ };
121
+ }
122
+ export function filesystemScopeContains(scope, requested) {
123
+ // Re-resolve on every use, including missing/new-file ancestors. A directory
124
+ // replaced with a symlink/junction cannot redirect an existing grant.
125
+ if (scope.directory && literalPath(scope.directory, scope.directory) !== scope.directory)
126
+ return false;
127
+ return requested.targets.every(target => {
128
+ if (scope.access !== 'write' && target.access === 'write')
129
+ return false;
130
+ if (scope.directory && !isSameOrDescendant(target.path, scope.directory))
131
+ return false;
132
+ // Do not silently reuse a broad grant for hard-linked file aliases.
133
+ try {
134
+ const stat = fs.statSync(target.path);
135
+ if (stat.isFile() && stat.nlink > 1)
136
+ return false;
137
+ }
138
+ catch { }
139
+ return true;
140
+ });
141
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { resolveRoot } from '../../paths.js';
3
+ import { resolveInstanceSocketPath, resolveRoot } from '../../paths.js';
4
4
  import { isValidAid } from '../../aun/aid/identity.js';
5
5
  const H_CLASS_RELATIVE_PATTERNS = [
6
6
  // These are true secret or daemon-owned roots. Agent-owned content is not
@@ -11,6 +11,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
11
11
  /^CA(?:\/|$)/,
12
12
  /^\.device_id$/,
13
13
  /^\.env$/,
14
+ /^providers\.json(?:_|__|\.lock(?:\..*)?)?$/,
14
15
  /^\.seed(?:\.|$)/,
15
16
  /^\.migrated-/,
16
17
  /^\.lock$/,
@@ -19,6 +20,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
19
20
  /^agents\/[^/]+\/data\/(?:.*\/)?(?:context-tokens|menu-tokens)\.json(?:_|__|\.tmp-.*)?$/,
20
21
  ];
21
22
  const H_CLASS_REFERENCE_PATTERNS = [
23
+ /(?:^|[^A-Za-z0-9_.-])providers\.json(?:_|__|\.lock(?:\.[^/\\\s"']*)?)?(?=$|[\s"';&|<>)},])/,
22
24
  /(?:^|[/\\\s"'=<>])data[\\/]/,
23
25
  /(?:^|[/\\\s"'=<>])(?:AIDs|aids)[\\/]/,
24
26
  /(?:^|[^A-Za-z0-9_.-])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid)(?=$|[\s"';&|<>)},])/,
@@ -146,6 +148,7 @@ function matchesHClassRelative(relative) {
146
148
  return false;
147
149
  }
148
150
  const SELF_AWARE_H_CLASS_RELATIVE_PATTERNS = [
151
+ /^providers\.json(?:_|__|\.lock(?:\..*)?)?$/,
149
152
  /^data(?:\/|$)/,
150
153
  /^(?:AIDs|aids)(?:\/|$)/,
151
154
  /^CA(?:\/|$)/,
@@ -873,6 +876,16 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
873
876
  ...buildCodexHClassFilesystemRules(root, hClassOptions),
874
877
  ...lClassRules,
875
878
  }, platform);
879
+ if (platform === 'linux' && options.outerHClassGuard) {
880
+ const dataDirectory = path.join(resolveThroughExistingAncestor(root), 'data');
881
+ const socketPath = resolveThroughExistingAncestor(resolveInstanceSocketPath(root));
882
+ if (filesystem[dataDirectory] === 'deny'
883
+ && isSameOrDescendant(socketPath, dataDirectory)
884
+ && getExistingHClassMaskTargets(root, options).some(target => target.path === dataDirectory && target.kind === 'directory')) {
885
+ filesystem[dataDirectory] = 'read';
886
+ delete filesystem[dataDirectory + '/**'];
887
+ }
888
+ }
876
889
  assertWindowsCodexFilesystemRuleBudget(filesystem, platform);
877
890
  return filesystem;
878
891
  }