evolcore 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -178,6 +178,35 @@ function approvalCodeBlock(value) {
|
|
|
178
178
|
const fence = '`'.repeat(Math.max(3, longestRun + 1));
|
|
179
179
|
return `${fence}text\n${value}\n${fence}`;
|
|
180
180
|
}
|
|
181
|
+
function normalizeAidForApprovalRouting(value) {
|
|
182
|
+
const normalized = value?.trim().replace(/^@/, '');
|
|
183
|
+
return normalized || undefined;
|
|
184
|
+
}
|
|
185
|
+
function preferredOriginOwner(context, approval, owners) {
|
|
186
|
+
const originChannel = (approval?.originChannel ?? context.channel ?? '').split('#', 1)[0].toLowerCase();
|
|
187
|
+
if (context.chatType !== 'private' || originChannel !== 'aun')
|
|
188
|
+
return undefined;
|
|
189
|
+
const originPeerId = normalizeAidForApprovalRouting(approval?.originPeerId ?? context.userId);
|
|
190
|
+
if (!originPeerId)
|
|
191
|
+
return undefined;
|
|
192
|
+
return owners.find(owner => normalizeAidForApprovalRouting(owner) === originPeerId);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Owner approvals normally belong to the Owner's private main session. The
|
|
196
|
+
* one exception is an AUN private conversation whose peer is itself a
|
|
197
|
+
* configured Owner: preserve that conversation's topic so the request stays
|
|
198
|
+
* with the Owner's current work.
|
|
199
|
+
*/
|
|
200
|
+
function ownerApprovalThreadId(context, approval, approverOperatorId) {
|
|
201
|
+
const originChannel = (approval.originChannel ?? context.channel ?? '').split('#', 1)[0].toLowerCase();
|
|
202
|
+
const originPeerId = normalizeAidForApprovalRouting(approval.originPeerId ?? context.userId);
|
|
203
|
+
const approverId = normalizeAidForApprovalRouting(approverOperatorId);
|
|
204
|
+
const isOwnerPrivateConversation = context.chatType === 'private'
|
|
205
|
+
&& originChannel === 'aun'
|
|
206
|
+
&& !!originPeerId
|
|
207
|
+
&& originPeerId === approverId;
|
|
208
|
+
return isOwnerPrivateConversation ? (approval.originThreadId ?? '') : '';
|
|
209
|
+
}
|
|
181
210
|
export function planApprovalRoute(challenge, context) {
|
|
182
211
|
if (!challenge.grantable) {
|
|
183
212
|
return { kind: 'unavailable', reason: 'challenge_not_grantable' };
|
|
@@ -235,9 +264,12 @@ export function planApprovalRoute(challenge, context) {
|
|
|
235
264
|
approverPrincipalId: actor.principalId,
|
|
236
265
|
};
|
|
237
266
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
267
|
+
const preferredOwner = preferredOriginOwner(context ?? {}, routing, owners);
|
|
268
|
+
const handoffCandidates = preferredOwner
|
|
269
|
+
? [preferredOwner]
|
|
270
|
+
: challenge.approverPolicy === 'agent_manager'
|
|
271
|
+
? Array.from(new Set([...admins, ...owners]))
|
|
272
|
+
: owners;
|
|
241
273
|
if (handoffCandidates.length === 0) {
|
|
242
274
|
return {
|
|
243
275
|
kind: 'unavailable',
|
|
@@ -777,6 +809,7 @@ export class PermissionGateway {
|
|
|
777
809
|
}
|
|
778
810
|
const ownerReplyContext = {
|
|
779
811
|
delivery: { chatType: 'private' },
|
|
812
|
+
threadId: ownerApprovalThreadId(context, approval, route.approverOperatorId),
|
|
780
813
|
metadata: {
|
|
781
814
|
source: 'handoff',
|
|
782
815
|
chatmode: 'interactive',
|
|
@@ -16,6 +16,13 @@ const CODEX_CUSTOM_POWERSHELL_NAMES = new Set(['pwsh.exe', 'powershell.exe']);
|
|
|
16
16
|
const CODEX_CUSTOM_CMD_NAMES = new Set(['cmd.exe']);
|
|
17
17
|
const CODEX_CMD_EXECUTABLES = new Set(['cmd', 'cmd.exe']);
|
|
18
18
|
const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|SysWOW64|Sysnative)[\\/]cmd\.exe)$/i;
|
|
19
|
+
// These switches only suppress interactive/profile UI behavior. `-Command`
|
|
20
|
+
// is handled separately as the required execute switch. Options such as
|
|
21
|
+
// `-NoExit`, `-ExecutionPolicy`, `-EncodedCommand`, and `-File` can change
|
|
22
|
+
// execution semantics and must never be accepted as a trusted carrier.
|
|
23
|
+
const CODEX_POWERSHELL_INERT_SWITCHES = new Set([
|
|
24
|
+
'-noprofile', '-noninteractive', '-nologo',
|
|
25
|
+
]);
|
|
19
26
|
// These switches affect only cmd startup/output formatting. `/c` is handled
|
|
20
27
|
// separately as the required execute switch. Flags such as `/k`, `/v:on`,
|
|
21
28
|
// `/e:*`, and `/f:*` can change process lifetime or command semantics and are
|
|
@@ -222,24 +229,27 @@ export function normalizeEvolcoreCommandArgv(argv) {
|
|
|
222
229
|
}
|
|
223
230
|
return [...argv];
|
|
224
231
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
232
|
+
const POWERSHELL_CREDENTIAL_ENV_RE = /^(EVOLCORE_CODEX_SESSION_TOKEN|EVOLCORE_DELEGATION_TOKEN)$/i;
|
|
233
|
+
/** Detect a PowerShell prefix that materializes an EvolCore session credential. */
|
|
234
|
+
export function hasPowerShellCredentialAssignment(command) {
|
|
235
|
+
return /^\s*\$env\s*:\s*(?:EVOLCORE_CODEX_SESSION_TOKEN|EVOLCORE_DELEGATION_TOKEN)\b/i.test(command)
|
|
228
236
|
&& /\bec(?:\.cmd|\.exe)?(?:\s|$)/i.test(command);
|
|
229
237
|
}
|
|
230
238
|
/**
|
|
231
239
|
* Windows Codex may materialize one environment variable assignment directly
|
|
232
240
|
* in the PowerShell command body instead of inheriting it from the process
|
|
233
|
-
* environment. Only
|
|
234
|
-
* when followed by one command
|
|
235
|
-
*
|
|
236
|
-
*
|
|
241
|
+
* environment. Only an EvolCore session credential variable is accepted,
|
|
242
|
+
* and only when followed by one command in the same shell invocation. The
|
|
243
|
+
* legacy delegation name remains readable for rolling upgrades, but its value
|
|
244
|
+
* is still compared with the credential bound to the managed session.
|
|
237
245
|
*/
|
|
238
|
-
function
|
|
246
|
+
function unwrapPowerShellCredentialPrefix(command) {
|
|
239
247
|
const input = command.trim();
|
|
240
|
-
|
|
248
|
+
const prefix = input.match(/^\$env\s*:\s*([A-Za-z_][A-Za-z0-9_]*)\b/i);
|
|
249
|
+
if (!prefix || !POWERSHELL_CREDENTIAL_ENV_RE.test(prefix[1])) {
|
|
241
250
|
return { kind: 'none', command };
|
|
242
251
|
}
|
|
252
|
+
const credentialEnvName = prefix[1];
|
|
243
253
|
let index = 0;
|
|
244
254
|
const skipSpace = () => {
|
|
245
255
|
while (index < input.length && (input[index] === ' ' || input[index] === '\t'))
|
|
@@ -256,7 +266,7 @@ function unwrapPowerShellDelegationPrefix(command) {
|
|
|
256
266
|
return { kind: 'invalid', command };
|
|
257
267
|
index++;
|
|
258
268
|
skipSpace();
|
|
259
|
-
if (!consume(
|
|
269
|
+
if (!consume(credentialEnvName))
|
|
260
270
|
return { kind: 'invalid', command };
|
|
261
271
|
skipSpace();
|
|
262
272
|
if (input[index] !== '=')
|
|
@@ -328,20 +338,20 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
|
|
|
328
338
|
}
|
|
329
339
|
}
|
|
330
340
|
const unwrapped = carrier.dialect === 'powershell'
|
|
331
|
-
?
|
|
341
|
+
? unwrapPowerShellCredentialPrefix(carrier.command)
|
|
332
342
|
: { kind: 'none', command: carrier.command };
|
|
333
343
|
// Windows PowerShell may materialize the thread's shell environment as a
|
|
334
|
-
// single `$env:
|
|
344
|
+
// single `$env:EVOLCORE_CODEX_SESSION_TOKEN = '...'` prefix. Keep the token as
|
|
335
345
|
// metadata for the approval layer, which must compare it with the runner's
|
|
336
|
-
//
|
|
346
|
+
// session credential before arming delegation. Malformed or unrelated
|
|
337
347
|
// assignments remain syntax failures and can never be authorized.
|
|
338
348
|
if (unwrapped.kind === 'invalid'
|
|
339
349
|
|| (carrier.dialect === 'powershell'
|
|
340
|
-
&&
|
|
350
|
+
&& hasPowerShellCredentialAssignment(carrier.command)
|
|
341
351
|
&& unwrapped.kind === 'none')
|
|
342
352
|
|| (unwrapped.kind === 'valid'
|
|
343
|
-
&& options.
|
|
344
|
-
&& options.
|
|
353
|
+
&& options.expectedCredentialToken !== undefined
|
|
354
|
+
&& options.expectedCredentialToken !== unwrapped.token)) {
|
|
345
355
|
return parseFailure('shell-composition', carrier.command, 0, 0, carrier.dialect);
|
|
346
356
|
}
|
|
347
357
|
const parsed = parseLiteralShellCommand(unwrapped.command, {
|
|
@@ -352,7 +362,7 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
|
|
|
352
362
|
? {
|
|
353
363
|
...parsed,
|
|
354
364
|
argv: normalizeEvolcoreCommandArgv(parsed.argv),
|
|
355
|
-
...(unwrapped.kind === 'valid' ? {
|
|
365
|
+
...(unwrapped.kind === 'valid' ? { credentialToken: unwrapped.token } : {}),
|
|
356
366
|
}
|
|
357
367
|
: parsed;
|
|
358
368
|
}
|
|
@@ -366,6 +376,14 @@ function parseFailure(issue, input, offset, tokenIndex, dialect) {
|
|
|
366
376
|
inputLength: input.length,
|
|
367
377
|
};
|
|
368
378
|
}
|
|
379
|
+
// PowerShell uses smart quotes as delimiters, including when the opening
|
|
380
|
+
// quote was ASCII. Treating them as text changes argv and delegation hashes.
|
|
381
|
+
function isPowerShellSingleQuote(char) {
|
|
382
|
+
return char !== undefined && /['\u2018\u2019\u201a\u201b]/u.test(char);
|
|
383
|
+
}
|
|
384
|
+
function isPowerShellDoubleQuote(char) {
|
|
385
|
+
return char !== undefined && /["\u201c\u201d\u201e]/u.test(char);
|
|
386
|
+
}
|
|
369
387
|
function parseLiteralPowerShellCommand(command, options = {}) {
|
|
370
388
|
const input = trimShellSpacing(command);
|
|
371
389
|
if (!input)
|
|
@@ -389,11 +407,11 @@ function parseLiteralPowerShellCommand(command, options = {}) {
|
|
|
389
407
|
for (let index = 0; index < input.length; index++) {
|
|
390
408
|
const char = input[index];
|
|
391
409
|
if (quote === 'single') {
|
|
392
|
-
if (char
|
|
393
|
-
token +=
|
|
410
|
+
if (isPowerShellSingleQuote(char) && isPowerShellSingleQuote(input[index + 1])) {
|
|
411
|
+
token += input[index + 1];
|
|
394
412
|
index++;
|
|
395
413
|
}
|
|
396
|
-
else if (char
|
|
414
|
+
else if (isPowerShellSingleQuote(char)) {
|
|
397
415
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
398
416
|
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
399
417
|
quote = null;
|
|
@@ -410,7 +428,7 @@ function parseLiteralPowerShellCommand(command, options = {}) {
|
|
|
410
428
|
return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
|
|
411
429
|
}
|
|
412
430
|
if (!options.allowManagedTmpDir || !options.managedTempDir
|
|
413
|
-
|| (next !== undefined && !/[\\/\s"']/u.test(next))) {
|
|
431
|
+
|| (next !== undefined && !/[\\/\s"']/u.test(next) && !isPowerShellSingleQuote(next) && !isPowerShellDoubleQuote(next))) {
|
|
414
432
|
return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
|
|
415
433
|
}
|
|
416
434
|
// Append the registered value directly: spaces remain in this argument,
|
|
@@ -421,7 +439,7 @@ function parseLiteralPowerShellCommand(command, options = {}) {
|
|
|
421
439
|
continue;
|
|
422
440
|
}
|
|
423
441
|
if (quote === 'double') {
|
|
424
|
-
if (char
|
|
442
|
+
if (isPowerShellDoubleQuote(char)) {
|
|
425
443
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
426
444
|
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
427
445
|
quote = null;
|
|
@@ -448,12 +466,12 @@ function parseLiteralPowerShellCommand(command, options = {}) {
|
|
|
448
466
|
if (/\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f') {
|
|
449
467
|
return parseFailure('invalid-control-char', input, index, argv.length, 'powershell');
|
|
450
468
|
}
|
|
451
|
-
if (char
|
|
469
|
+
if (isPowerShellSingleQuote(char)) {
|
|
452
470
|
tokenStarted = true;
|
|
453
471
|
quote = 'single';
|
|
454
472
|
continue;
|
|
455
473
|
}
|
|
456
|
-
if (char
|
|
474
|
+
if (isPowerShellDoubleQuote(char)) {
|
|
457
475
|
tokenStarted = true;
|
|
458
476
|
quote = 'double';
|
|
459
477
|
continue;
|
|
@@ -599,6 +617,25 @@ function resolveCodexCmdCarrierArgv(argv) {
|
|
|
599
617
|
? { command, dialect: 'cmd', executable: argv[0] }
|
|
600
618
|
: undefined;
|
|
601
619
|
}
|
|
620
|
+
function resolveCodexPowerShellCarrierArgv(argv) {
|
|
621
|
+
if (argv.length < 3 || !isCodexPowerShellExecutable(argv[0]))
|
|
622
|
+
return undefined;
|
|
623
|
+
const commandIndex = argv.length - 2;
|
|
624
|
+
if (argv[commandIndex].toLowerCase() !== '-command')
|
|
625
|
+
return undefined;
|
|
626
|
+
const seen = new Set();
|
|
627
|
+
for (const rawSwitch of argv.slice(1, commandIndex)) {
|
|
628
|
+
const switchValue = rawSwitch.toLowerCase();
|
|
629
|
+
if (!CODEX_POWERSHELL_INERT_SWITCHES.has(switchValue) || seen.has(switchValue)) {
|
|
630
|
+
return undefined;
|
|
631
|
+
}
|
|
632
|
+
seen.add(switchValue);
|
|
633
|
+
}
|
|
634
|
+
const command = argv.at(-1);
|
|
635
|
+
return typeof command === 'string' && command.trim()
|
|
636
|
+
? { command, dialect: 'powershell', executable: argv[0] }
|
|
637
|
+
: undefined;
|
|
638
|
+
}
|
|
602
639
|
/** Resolve a Windows cmd carrier when the app-server provides only a string. */
|
|
603
640
|
function resolveCodexCmdCarrierString(command) {
|
|
604
641
|
const input = trimShellSpacing(command);
|
|
@@ -614,10 +651,14 @@ function resolveCodexCmdCarrierString(command) {
|
|
|
614
651
|
const argv = [executable, ...switches, match[4]];
|
|
615
652
|
return resolveCodexCmdCarrierArgv(argv);
|
|
616
653
|
}
|
|
617
|
-
/**
|
|
654
|
+
/**
|
|
655
|
+
* Resolve a PowerShell carrier when the app-server provides only a string.
|
|
656
|
+
* The prefix is converted to the same argv shape used by commandArgv so the
|
|
657
|
+
* startup-switch policy cannot diverge between the two transport forms.
|
|
658
|
+
*/
|
|
618
659
|
function resolveCodexPowerShellCarrierString(command) {
|
|
619
660
|
const input = trimShellSpacing(command);
|
|
620
|
-
const match = input.match(/^(?:"([^"]+)"|(\S+))\
|
|
661
|
+
const match = input.match(/^(?:"([^"\r\n]+)"|(\S+))((?:[ \t]+-[A-Za-z]+)+)[ \t]+(.+)$/);
|
|
621
662
|
if (!match)
|
|
622
663
|
return undefined;
|
|
623
664
|
const executable = match[1] ?? match[2];
|
|
@@ -626,13 +667,14 @@ function resolveCodexPowerShellCarrierString(command) {
|
|
|
626
667
|
// A string-form carrier has one additional command-line quoting layer. The
|
|
627
668
|
// command argument is commonly serialized as `"ec ..."`; remove exactly
|
|
628
669
|
// that outer pair and leave quotes belonging to EC arguments intact.
|
|
629
|
-
let nested = match[
|
|
670
|
+
let nested = match[4].trim();
|
|
630
671
|
if (nested.length >= 2
|
|
631
672
|
&& ((nested.startsWith('"') && nested.endsWith('"'))
|
|
632
673
|
|| (nested.startsWith("'") && nested.endsWith("'")))) {
|
|
633
674
|
nested = nested.slice(1, -1);
|
|
634
675
|
}
|
|
635
|
-
|
|
676
|
+
const switches = match[3].trim().split(/[ \t]+/);
|
|
677
|
+
return resolveCodexPowerShellCarrierArgv([executable, ...switches, nested]);
|
|
636
678
|
}
|
|
637
679
|
/** Resolve a POSIX shell carrier when app-server provides only a string. */
|
|
638
680
|
function resolveCodexPosixShellCarrierString(command) {
|
|
@@ -868,11 +910,9 @@ export function resolveCodexShellCarrierArgv(argv) {
|
|
|
868
910
|
&& isCodexPosixShellExecutable(argv[0])) {
|
|
869
911
|
return { command: argv[2], dialect: 'posix', executable: argv[0] };
|
|
870
912
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
}
|
|
913
|
+
const powershellCarrier = resolveCodexPowerShellCarrierArgv(argv);
|
|
914
|
+
if (powershellCarrier)
|
|
915
|
+
return powershellCarrier;
|
|
876
916
|
// Node/cross-spawn and Codex use cmd.exe with a small set of startup flags.
|
|
877
917
|
// The execute switch must be the final switch before one command argument;
|
|
878
918
|
// validation itself is shared with string-only Windows payloads below.
|
|
@@ -1645,7 +1685,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1645
1685
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1646
1686
|
return { kind: 'literal' };
|
|
1647
1687
|
if (!nested.ok && (hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)
|
|
1648
|
-
|| (carrier.dialect === 'powershell' &&
|
|
1688
|
+
|| (carrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(carrier.command)))) {
|
|
1649
1689
|
return { kind: 'composite', issue: nested.issue };
|
|
1650
1690
|
}
|
|
1651
1691
|
}
|
|
@@ -1655,7 +1695,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1655
1695
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1656
1696
|
return { kind: 'literal' };
|
|
1657
1697
|
if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1658
|
-
|| (stringCarrier.dialect === 'powershell' &&
|
|
1698
|
+
|| (stringCarrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(stringCarrier.command)))) {
|
|
1659
1699
|
return { kind: 'composite', issue: nested.issue };
|
|
1660
1700
|
}
|
|
1661
1701
|
}
|
|
@@ -1677,7 +1717,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1677
1717
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1678
1718
|
return { kind: 'literal' };
|
|
1679
1719
|
if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1680
|
-
|| (stringCarrier.dialect === 'powershell' &&
|
|
1720
|
+
|| (stringCarrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(stringCarrier.command)))) {
|
|
1681
1721
|
return { kind: 'composite', issue: nested.issue };
|
|
1682
1722
|
}
|
|
1683
1723
|
return { kind: 'none' };
|
|
@@ -28,9 +28,13 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
28
28
|
/^data\/outbox(?:\/|$)/,
|
|
29
29
|
/^data\/handoff(?:\/|$)/,
|
|
30
30
|
/^agents\/[^/]+\/sessions(?:\/|$)/,
|
|
31
|
+
/^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
|
|
32
|
+
/^agents\/[^/]+\/data\/aun(?:\/|$)/,
|
|
31
33
|
/^agents\/[^/]+\/data\/(?:channels(?:\/|$)|handoff(?:\/|$)|(?:outbox|activity-outbox)\.jsonl(?:_|__|\.tmp-.*)?$|contact-audit\.jsonl$)/,
|
|
32
34
|
];
|
|
33
35
|
const H_CLASS_REFERENCE_PATTERNS = [
|
|
36
|
+
/(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]async-questions(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
37
|
+
/(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]aun(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
34
38
|
/(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
|
|
35
39
|
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
36
40
|
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
@@ -43,6 +47,8 @@ const H_CLASS_REFERENCE_PATTERNS = [
|
|
|
43
47
|
// protected file. Descendants inherit the directory classification unless an
|
|
44
48
|
// existing regular file gives us a more precise answer.
|
|
45
49
|
const H_CLASS_DIRECTORY_RELATIVE_PATTERNS = [
|
|
50
|
+
/^agents\/[^/]+\/data\/aun(?:\/|$)/,
|
|
51
|
+
/^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
|
|
46
52
|
/^backups\/config(?:\/|$)/,
|
|
47
53
|
/^\.snapshots(?:\/|$)/,
|
|
48
54
|
/^CA(?:\/|$)/,
|
|
@@ -286,6 +292,8 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
286
292
|
path.join('agents', '*', 'sessions', '**'),
|
|
287
293
|
path.join('agents', '*', 'data', 'channels', '**'),
|
|
288
294
|
path.join('agents', '*', 'data', 'handoff', '**'),
|
|
295
|
+
path.join('agents', '*', 'data', 'async-questions', '**'),
|
|
296
|
+
path.join('agents', '*', 'data', 'aun', '**'),
|
|
289
297
|
path.join('agents', '*', 'data', 'outbox.jsonl*'),
|
|
290
298
|
path.join('agents', '*', 'data', 'activity-outbox.jsonl*'),
|
|
291
299
|
path.join('agents', '*', 'data', 'contact-audit.jsonl'),
|
|
@@ -303,6 +311,15 @@ export function getLClassWritePatterns(root = resolveRoot()) {
|
|
|
303
311
|
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
304
312
|
const CLAUDE_GLOB_MAGIC = /[*?[\]]/;
|
|
305
313
|
const CODEX_GLOB_MAGIC = /[*?[\]]/;
|
|
314
|
+
// Codex wraps the resolved Windows ACL payload in JSON and base64 before
|
|
315
|
+
// passing it as one CreateProcess argument. Real Codex 0.154 payloads are
|
|
316
|
+
// roughly 3-5x larger than EvolCore's input map, so leave enough room below
|
|
317
|
+
// Windows' 32,767-character command-line ceiling for helper metadata.
|
|
318
|
+
const CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES = 7 * 1024;
|
|
319
|
+
const CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS = [
|
|
320
|
+
'**/*.json_',
|
|
321
|
+
'**/*.json.migrated',
|
|
322
|
+
];
|
|
306
323
|
function addExistingTarget(targets, value) {
|
|
307
324
|
try {
|
|
308
325
|
const stat = fs.lstatSync(value);
|
|
@@ -370,6 +387,8 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
370
387
|
addExistingTarget(targets, path.join(agentRoot, 'sessions'));
|
|
371
388
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'channels'));
|
|
372
389
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'handoff'));
|
|
390
|
+
addExistingTarget(targets, path.join(agentRoot, 'data', 'async-questions'));
|
|
391
|
+
addExistingTarget(targets, path.join(agentRoot, 'data', 'aun'));
|
|
373
392
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'outbox.jsonl'));
|
|
374
393
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'activity-outbox.jsonl'));
|
|
375
394
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'contact-audit.jsonl'));
|
|
@@ -427,14 +446,14 @@ export function buildClaudeProtectedFilesystem(root = resolveRoot(), options = {
|
|
|
427
446
|
denyWrite: allPaths,
|
|
428
447
|
};
|
|
429
448
|
}
|
|
430
|
-
export function buildCodexHClassFilesystemRules(root = resolveRoot()) {
|
|
449
|
+
export function buildCodexHClassFilesystemRules(root = resolveRoot(), platform = process.platform) {
|
|
431
450
|
const filesystem = {
|
|
432
451
|
// Codex pre-expands unbounded deny globs on Linux/WSL/Windows. Without a
|
|
433
452
|
// bound, patterns such as **/*.json_ are accepted but may not be enforced.
|
|
434
453
|
glob_scan_max_depth: 64,
|
|
435
|
-
...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root)).map(pattern => [pattern, 'deny'])),
|
|
454
|
+
...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root), platform).map(pattern => [pattern, 'deny'])),
|
|
436
455
|
};
|
|
437
|
-
return compactWindowsCodexFilesystemRules(filesystem);
|
|
456
|
+
return materializeWindowsCodexRecursiveFileDenies(compactWindowsCodexFilesystemRules(filesystem, platform), root, platform);
|
|
438
457
|
}
|
|
439
458
|
/**
|
|
440
459
|
* Windows Codex sandbox setup materializes a path while applying its deny-read
|
|
@@ -462,20 +481,21 @@ export function filterWindowsCodexFilesystemTargets(patterns, platform = process
|
|
|
462
481
|
});
|
|
463
482
|
}
|
|
464
483
|
/**
|
|
465
|
-
* Codex expands recursive
|
|
484
|
+
* Codex expands recursive ACL globs into individual targets. Large protected
|
|
466
485
|
* trees on Windows can then fail during AGENTS.md loading with os error 206.
|
|
467
|
-
* Directory
|
|
468
|
-
*
|
|
486
|
+
* Directory deny/read ACLs inherit to descendants, avoiding redundant targets.
|
|
487
|
+
* Keep broad recursive filename globs intact for the materialization pass.
|
|
469
488
|
*/
|
|
470
489
|
export function compactWindowsCodexFilesystemRules(filesystem, platform = process.platform) {
|
|
471
490
|
if (platform !== 'win32')
|
|
472
491
|
return filesystem;
|
|
473
492
|
const compacted = {};
|
|
493
|
+
const projectedKeys = new Map();
|
|
474
494
|
for (const [pattern, access] of Object.entries(filesystem)) {
|
|
475
|
-
const
|
|
495
|
+
const isTerminalRecursiveAcl = (access === 'deny' || access === 'read')
|
|
476
496
|
&& (pattern.endsWith('/**') || pattern.endsWith('\\**'));
|
|
477
|
-
const recursiveRoot =
|
|
478
|
-
// Only collapse a recursive
|
|
497
|
+
const recursiveRoot = isTerminalRecursiveAcl ? pattern.slice(0, -3) : undefined;
|
|
498
|
+
// Only collapse a recursive ACL when its root is a real directory. A
|
|
479
499
|
// missing root must remain a glob: turning it into an exact target can
|
|
480
500
|
// make the Windows sandbox materialize that path as a directory.
|
|
481
501
|
const canCompact = !!recursiveRoot
|
|
@@ -488,12 +508,104 @@ export function compactWindowsCodexFilesystemRules(filesystem, platform = proces
|
|
|
488
508
|
}
|
|
489
509
|
})());
|
|
490
510
|
const compactPattern = canCompact ? recursiveRoot : pattern;
|
|
491
|
-
|
|
492
|
-
|
|
511
|
+
let projectedPattern = compactPattern;
|
|
512
|
+
{
|
|
513
|
+
// Resolve only the literal prefix and only casing aliases. Do not fold
|
|
514
|
+
// glob spelling, unresolved paths, case-sensitive directories or links.
|
|
515
|
+
const globIndex = compactPattern.search(CODEX_GLOB_MAGIC);
|
|
516
|
+
const prefix = globIndex < 0 ? compactPattern : compactPattern.slice(0, Math.max(compactPattern.lastIndexOf('/', globIndex), compactPattern.lastIndexOf('\\', globIndex)));
|
|
517
|
+
let key = compactPattern;
|
|
518
|
+
try {
|
|
519
|
+
const canonical = fs.realpathSync.native(prefix);
|
|
520
|
+
if (canonical.toLowerCase() === prefix.toLowerCase())
|
|
521
|
+
key = canonical + compactPattern.slice(prefix.length);
|
|
522
|
+
}
|
|
523
|
+
catch { /* An unresolved alias must retain its deny rule. */ }
|
|
524
|
+
projectedPattern = projectedKeys.get(key) ?? compactPattern;
|
|
525
|
+
projectedKeys.set(key, projectedPattern);
|
|
526
|
+
}
|
|
527
|
+
if (compacted[projectedPattern] !== 'deny')
|
|
528
|
+
compacted[projectedPattern] = access;
|
|
529
|
+
}
|
|
530
|
+
// Directory deny ACLs inherit. After H/L rules are combined, omit exact
|
|
531
|
+
// file denies already covered by an existing directory deny. Keep globs
|
|
532
|
+
// and any child for which a read/write override could change precedence.
|
|
533
|
+
const entries = Object.entries(compacted);
|
|
534
|
+
const denyDirectories = entries.filter(([pattern, access]) => {
|
|
535
|
+
if (access !== 'deny' || CODEX_GLOB_MAGIC.test(pattern))
|
|
536
|
+
return false;
|
|
537
|
+
try {
|
|
538
|
+
return fs.lstatSync(pattern).isDirectory();
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
}).map(([pattern]) => pattern);
|
|
544
|
+
for (const [pattern, access] of entries) {
|
|
545
|
+
if (access !== 'deny' || CODEX_GLOB_MAGIC.test(pattern) || denyDirectories.includes(pattern))
|
|
546
|
+
continue;
|
|
547
|
+
if (denyDirectories.some(parent => isSameOrDescendant(pattern, parent)
|
|
548
|
+
&& !entries.some(([override, value]) => (value === 'read' || value === 'write')
|
|
549
|
+
&& (CODEX_GLOB_MAGIC.test(override) || isSameOrDescendant(override, parent))))) {
|
|
550
|
+
delete compacted[pattern];
|
|
551
|
+
}
|
|
493
552
|
}
|
|
494
553
|
return compacted;
|
|
495
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Codex 0.154 serializes resolved Windows deny-read targets into a base64
|
|
557
|
+
* command-line argument for codex-windows-sandbox-setup.exe. Recursive file
|
|
558
|
+
* globs can expand into enough absolute paths to exceed CreateProcess' command
|
|
559
|
+
* line limit. Claude avoids the analogous limit by externalizing settings;
|
|
560
|
+
* Codex has no per-thread settings-file input, so materialize only these broad
|
|
561
|
+
* file globs in EvolCore and keep the compact exact projection in thread config.
|
|
562
|
+
*
|
|
563
|
+
* Matches below an already denied directory are omitted because the Windows
|
|
564
|
+
* sandbox ACL on that directory inherits to descendants. Other H-class rules
|
|
565
|
+
* remain dynamic globs, preserving protection for newly created agent/config
|
|
566
|
+
* paths without recreating the unbounded recursive expansion.
|
|
567
|
+
*/
|
|
568
|
+
export function materializeWindowsCodexRecursiveFileDenies(filesystem, root = resolveRoot(), platform = process.platform) {
|
|
569
|
+
if (platform !== 'win32')
|
|
570
|
+
return filesystem;
|
|
571
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
572
|
+
const materializedPatterns = new Set(CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS.map(pattern => path.join(absoluteRoot, pattern)));
|
|
573
|
+
const deniedDirectories = getExistingHClassMaskTargets(absoluteRoot)
|
|
574
|
+
.filter(target => target.kind === 'directory')
|
|
575
|
+
.map(target => target.path);
|
|
576
|
+
const projected = {};
|
|
577
|
+
for (const [pattern, access] of Object.entries(filesystem)) {
|
|
578
|
+
if (access !== 'deny' || !materializedPatterns.has(pattern)) {
|
|
579
|
+
projected[pattern] = access;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const relativePattern = path.relative(absoluteRoot, pattern).split(path.sep).join('/');
|
|
583
|
+
let matches;
|
|
584
|
+
try {
|
|
585
|
+
matches = fs.globSync(relativePattern, { cwd: absoluteRoot });
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
throw new Error(`[CodexSandbox] failed to materialize Windows protected-file rules: ${error instanceof Error ? error.message : String(error)}`);
|
|
589
|
+
}
|
|
590
|
+
for (const match of matches) {
|
|
591
|
+
const candidate = path.resolve(absoluteRoot, match);
|
|
592
|
+
if (deniedDirectories.some(directory => isSameOrDescendant(candidate, directory)))
|
|
593
|
+
continue;
|
|
594
|
+
projected[candidate] = access;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return projected;
|
|
598
|
+
}
|
|
599
|
+
export function assertWindowsCodexFilesystemRuleBudget(filesystem, platform = process.platform) {
|
|
600
|
+
if (platform !== 'win32')
|
|
601
|
+
return;
|
|
602
|
+
const bytes = Buffer.byteLength(JSON.stringify(filesystem), 'utf8');
|
|
603
|
+
if (bytes > CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES) {
|
|
604
|
+
throw new Error(`[CodexSandbox] Windows filesystem rule budget exceeded: ${bytes} > ${CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
496
607
|
export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options = {}) {
|
|
608
|
+
const platform = options.platform ?? process.platform;
|
|
497
609
|
const lClassRules = {};
|
|
498
610
|
const lClassAccess = options.denyLClassRead ? 'deny' : 'read';
|
|
499
611
|
// Codex's Linux sandbox expands glob deny rules into per-file mount targets.
|
|
@@ -506,10 +618,14 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
|
|
|
506
618
|
const expandLClassReadDeny = options.expandLClassReadDeny ?? true;
|
|
507
619
|
for (const target of getExistingLClassReadOnlyTargets(root)) {
|
|
508
620
|
if (target.kind === 'directory') {
|
|
509
|
-
|
|
621
|
+
// :workspace/:read-only already grants L-class read access on Windows.
|
|
622
|
+
// Omitting redundant read ACLs keeps Codex's serialized helper payload
|
|
623
|
+
// below CreateProcess' command-line limit. Readonly still needs deny.
|
|
624
|
+
const includeWindowsRead = platform !== 'win32' || options.denyLClassRead;
|
|
625
|
+
if (includeWindowsRead && (!expandLClassReadDeny || options.denyLClassRead || path.basename(target.path) === 'triggers')) {
|
|
510
626
|
lClassRules[target.path] = lClassAccess;
|
|
511
627
|
}
|
|
512
|
-
if (expandLClassReadDeny || !options.denyLClassRead) {
|
|
628
|
+
if (includeWindowsRead && (expandLClassReadDeny || !options.denyLClassRead)) {
|
|
513
629
|
// Use '/**' suffix instead of path.join to satisfy Codex's glob requirement
|
|
514
630
|
lClassRules[target.path + '/**'] = lClassAccess;
|
|
515
631
|
}
|
|
@@ -518,10 +634,12 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
|
|
|
518
634
|
lClassRules[target.path] = lClassAccess;
|
|
519
635
|
}
|
|
520
636
|
}
|
|
521
|
-
|
|
522
|
-
...buildCodexHClassFilesystemRules(root),
|
|
637
|
+
const filesystem = compactWindowsCodexFilesystemRules({
|
|
638
|
+
...buildCodexHClassFilesystemRules(root, platform),
|
|
523
639
|
...lClassRules,
|
|
524
|
-
});
|
|
640
|
+
}, platform);
|
|
641
|
+
assertWindowsCodexFilesystemRuleBudget(filesystem, platform);
|
|
642
|
+
return filesystem;
|
|
525
643
|
}
|
|
526
644
|
export function hClassGrantIncludesProtectedPath(value, options = {}) {
|
|
527
645
|
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|