shennian 0.3.84 → 0.3.86

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 (99) hide show
  1. package/dist/bin/shennian.js +1 -1
  2. package/dist/publish-build-manifest.json +30 -30
  3. package/dist/src/agents/adapter.js +19 -1
  4. package/dist/src/agents/claude-managed-permissions.js +248 -1
  5. package/dist/src/agents/claude.js +393 -8
  6. package/dist/src/agents/codex-managed-permissions.js +294 -1
  7. package/dist/src/agents/codex-managed-room-tools.js +249 -1
  8. package/dist/src/agents/codex-utils.js +200 -7
  9. package/dist/src/agents/codex.js +1352 -17
  10. package/dist/src/agents/config-status.js +226 -1
  11. package/dist/src/agents/cursor-managed-permissions.js +299 -2
  12. package/dist/src/agents/cursor.js +314 -1
  13. package/dist/src/agents/custom.js +418 -4
  14. package/dist/src/agents/external-channel-instructions.js +94 -11
  15. package/dist/src/agents/gemini.js +180 -1
  16. package/dist/src/agents/manager.d.ts +2 -0
  17. package/dist/src/agents/manager.js +250 -18
  18. package/dist/src/agents/model-registry/cache.js +37 -1
  19. package/dist/src/agents/model-registry/discovery.js +188 -2
  20. package/dist/src/agents/model-registry/parsers.js +521 -4
  21. package/dist/src/agents/model-registry/runner.js +30 -1
  22. package/dist/src/agents/model-registry/service.js +115 -1
  23. package/dist/src/agents/model-registry/types.js +8 -1
  24. package/dist/src/agents/model-registry.js +19 -1
  25. package/dist/src/agents/native-capabilities.js +237 -1
  26. package/dist/src/agents/openclaw.js +293 -2
  27. package/dist/src/agents/opencode-managed-permissions.js +187 -2
  28. package/dist/src/agents/opencode.js +298 -1
  29. package/dist/src/agents/platform-instructions.js +46 -17
  30. package/dist/src/agents/runtime-compatibility.js +44 -1
  31. package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
  32. package/dist/src/agents/workbuddy.js +444 -3
  33. package/dist/src/commands/room.js +2 -2
  34. package/dist/src/fs/boundary.js +185 -1
  35. package/dist/src/fs/handler.js +130 -1
  36. package/dist/src/fs/security.js +32 -1
  37. package/dist/src/fs/text-decoder.js +110 -1
  38. package/dist/src/index.js +587 -3
  39. package/dist/src/manager/prompt.js +34 -29
  40. package/dist/src/manager/registry.js +317 -1
  41. package/dist/src/manager/retired-external-channel-compat.js +40 -1
  42. package/dist/src/manager/runtime.d.ts +4 -0
  43. package/dist/src/manager/runtime.js +1514 -23
  44. package/dist/src/native-fusion/codex-parser.js +660 -4
  45. package/dist/src/native-fusion/codex-user-input.js +102 -5
  46. package/dist/src/native-fusion/config.js +5 -1
  47. package/dist/src/native-fusion/file-watcher.js +191 -1
  48. package/dist/src/native-fusion/local-repository.js +569 -2
  49. package/dist/src/native-fusion/opencode-parser.js +130 -3
  50. package/dist/src/native-fusion/parsers.js +140 -3
  51. package/dist/src/native-fusion/service.js +739 -2
  52. package/dist/src/native-fusion/session-header.js +101 -2
  53. package/dist/src/native-fusion/state.js +104 -2
  54. package/dist/src/native-fusion/types.js +1 -1
  55. package/dist/src/personal-sync/machine-authority.js +34 -1
  56. package/dist/src/room/activation-audit.js +45 -2
  57. package/dist/src/room/activation-policy.js +30 -1
  58. package/dist/src/room/activation-service.d.ts +1 -0
  59. package/dist/src/room/activation-service.js +408 -8
  60. package/dist/src/room/invite-bootstrap.js +156 -1
  61. package/dist/src/room/managed-context.d.ts +1 -1
  62. package/dist/src/room/managed-context.js +1 -1
  63. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  64. package/dist/src/room/managed-prompt-composer.js +6 -5
  65. package/dist/src/room/managed-room-binding.js +167 -1
  66. package/dist/src/room/managed-room-ipc.d.ts +2 -2
  67. package/dist/src/room/managed-room-ipc.js +2 -2
  68. package/dist/src/room/mirror-sync-service.js +183 -1
  69. package/dist/src/room/room-action-client.d.ts +11 -0
  70. package/dist/src/room/room-action-client.js +1 -1
  71. package/dist/src/room/room-binding-client.d.ts +1 -0
  72. package/dist/src/room/room-binding-client.js +151 -1
  73. package/dist/src/security/managed-agent-policy.d.ts +3 -2
  74. package/dist/src/security/managed-agent-policy.js +1 -1
  75. package/dist/src/session/archive-zip.js +220 -1
  76. package/dist/src/session/deletion-sync-state.js +136 -2
  77. package/dist/src/session/handlers/agent-capabilities.js +14 -1
  78. package/dist/src/session/handlers/agent-config.js +150 -1
  79. package/dist/src/session/handlers/agents.js +55 -1
  80. package/dist/src/session/handlers/chat.d.ts +1 -1
  81. package/dist/src/session/handlers/chat.js +1030 -2
  82. package/dist/src/session/handlers/control.js +61 -1
  83. package/dist/src/session/handlers/fs.js +594 -1
  84. package/dist/src/session/handlers/message-status.js +15 -1
  85. package/dist/src/session/handlers/remote-access.js +43 -1
  86. package/dist/src/session/handlers/room-managed.js +101 -1
  87. package/dist/src/session/handlers/session-refresh.js +35 -1
  88. package/dist/src/session/handlers/title.js +79 -1
  89. package/dist/src/session/handlers/tool-detail.js +149 -1
  90. package/dist/src/session/handlers/voice-window.js +62 -1
  91. package/dist/src/session/history-subscriptions.js +470 -1
  92. package/dist/src/session/manager.js +658 -1
  93. package/dist/src/session/native-cleanup-outbox.js +287 -2
  94. package/dist/src/session/native-session-lifecycle.js +327 -6
  95. package/dist/src/session/queue.d.ts +5 -0
  96. package/dist/src/session/queue.js +8 -8
  97. package/dist/src/upgrade/scheduler.js +126 -1
  98. package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
  99. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import"../src/index.js";
2
+ import '../src/index.js';
@@ -36,8 +36,8 @@
36
36
  },
37
37
  {
38
38
  "file": "src/agents/codex-managed-room-tools.js",
39
- "beforeBytes": 8618,
40
- "afterBytes": 4667
39
+ "beforeBytes": 9529,
40
+ "afterBytes": 5253
41
41
  },
42
42
  {
43
43
  "file": "src/agents/codex-utils.js",
@@ -46,8 +46,8 @@
46
46
  },
47
47
  {
48
48
  "file": "src/agents/codex.js",
49
- "beforeBytes": 57278,
50
- "afterBytes": 30048
49
+ "beforeBytes": 58249,
50
+ "afterBytes": 30328
51
51
  },
52
52
  {
53
53
  "file": "src/agents/command-spec.js",
@@ -96,8 +96,8 @@
96
96
  },
97
97
  {
98
98
  "file": "src/agents/manager.js",
99
- "beforeBytes": 10395,
100
- "afterBytes": 5613
99
+ "beforeBytes": 10725,
100
+ "afterBytes": 5801
101
101
  },
102
102
  {
103
103
  "file": "src/agents/model-registry/cache.js",
@@ -226,8 +226,8 @@
226
226
  },
227
227
  {
228
228
  "file": "src/commands/room.js",
229
- "beforeBytes": 17175,
230
- "afterBytes": 9722
229
+ "beforeBytes": 17685,
230
+ "afterBytes": 10063
231
231
  },
232
232
  {
233
233
  "file": "src/commands/runtime.js",
@@ -341,8 +341,8 @@
341
341
  },
342
342
  {
343
343
  "file": "src/manager/runtime.js",
344
- "beforeBytes": 72214,
345
- "afterBytes": 36669
344
+ "beforeBytes": 72951,
345
+ "afterBytes": 36995
346
346
  },
347
347
  {
348
348
  "file": "src/native-fusion/codex-files.js",
@@ -461,8 +461,8 @@
461
461
  },
462
462
  {
463
463
  "file": "src/room/activation-service.js",
464
- "beforeBytes": 20861,
465
- "afterBytes": 10189
464
+ "beforeBytes": 21587,
465
+ "afterBytes": 10726
466
466
  },
467
467
  {
468
468
  "file": "src/room/attachment-cache.js",
@@ -496,13 +496,13 @@
496
496
  },
497
497
  {
498
498
  "file": "src/room/managed-context.js",
499
- "beforeBytes": 4512,
500
- "afterBytes": 2530
499
+ "beforeBytes": 4521,
500
+ "afterBytes": 2538
501
501
  },
502
502
  {
503
503
  "file": "src/room/managed-prompt-composer.js",
504
- "beforeBytes": 8405,
505
- "afterBytes": 7995
504
+ "beforeBytes": 8751,
505
+ "afterBytes": 8603
506
506
  },
507
507
  {
508
508
  "file": "src/room/managed-room-binding.js",
@@ -511,8 +511,8 @@
511
511
  },
512
512
  {
513
513
  "file": "src/room/managed-room-ipc.js",
514
- "beforeBytes": 11388,
515
- "afterBytes": 5518
514
+ "beforeBytes": 11997,
515
+ "afterBytes": 5784
516
516
  },
517
517
  {
518
518
  "file": "src/room/mirror-sync-service.js",
@@ -531,13 +531,13 @@
531
531
  },
532
532
  {
533
533
  "file": "src/room/room-action-client.js",
534
- "beforeBytes": 27147,
535
- "afterBytes": 13584
534
+ "beforeBytes": 27845,
535
+ "afterBytes": 13911
536
536
  },
537
537
  {
538
538
  "file": "src/room/room-binding-client.js",
539
- "beforeBytes": 7028,
540
- "afterBytes": 4032
539
+ "beforeBytes": 7085,
540
+ "afterBytes": 4068
541
541
  },
542
542
  {
543
543
  "file": "src/room/session-registry.js",
@@ -556,8 +556,8 @@
556
556
  },
557
557
  {
558
558
  "file": "src/security/managed-agent-policy.js",
559
- "beforeBytes": 7298,
560
- "afterBytes": 3524
559
+ "beforeBytes": 6728,
560
+ "afterBytes": 3141
561
561
  },
562
562
  {
563
563
  "file": "src/security/managed-agent-runtime.js",
@@ -606,8 +606,8 @@
606
606
  },
607
607
  {
608
608
  "file": "src/session/handlers/chat.js",
609
- "beforeBytes": 42754,
610
- "afterBytes": 17891
609
+ "beforeBytes": 43645,
610
+ "afterBytes": 18275
611
611
  },
612
612
  {
613
613
  "file": "src/session/handlers/control.js",
@@ -666,8 +666,8 @@
666
666
  },
667
667
  {
668
668
  "file": "src/session/manager.js",
669
- "beforeBytes": 30775,
670
- "afterBytes": 15951
669
+ "beforeBytes": 30970,
670
+ "afterBytes": 16083
671
671
  },
672
672
  {
673
673
  "file": "src/session/native-cleanup-outbox.js",
@@ -686,8 +686,8 @@
686
686
  },
687
687
  {
688
688
  "file": "src/session/queue.js",
689
- "beforeBytes": 26251,
690
- "afterBytes": 11986
689
+ "beforeBytes": 30478,
690
+ "afterBytes": 13987
691
691
  },
692
692
  {
693
693
  "file": "src/session/store.js",
@@ -1 +1,19 @@
1
- import{EventEmitter as r}from"node:events";class s extends r{}const n=new Map;function g(e,t){n.set(e,t)}function i(e){n.delete(e)}function c(e){const t=n.get(e);return t?t():null}function p(){return[...n.keys()]}export{s as AgentAdapter,c as createAgent,p as getRegisteredAgents,g as registerAgent,i as unregisterAgent};
1
+ // @arch docs/architecture/cli/agent-adapters.md
2
+ // @test src/__tests__/agents-e2e.ts
3
+ import { EventEmitter } from 'node:events';
4
+ export class AgentAdapter extends EventEmitter {
5
+ }
6
+ const registry = new Map();
7
+ export function registerAgent(type, factory) {
8
+ registry.set(type, factory);
9
+ }
10
+ export function unregisterAgent(type) {
11
+ registry.delete(type);
12
+ }
13
+ export function createAgent(type) {
14
+ const factory = registry.get(type);
15
+ return factory ? factory() : null;
16
+ }
17
+ export function getRegisteredAgents() {
18
+ return [...registry.keys()];
19
+ }
@@ -1 +1,248 @@
1
- import g from"node:fs";import n from"node:path";import{resolveProtectedShennianDirectories as N}from"../security/managed-agent-credential-paths.js";const m={commandNetwork:{allow:!0,deny:!0},webSearch:{allow:!0,deny:!0},browser:{allow:!0,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!0,deny:!0}},M={minimumRuntimeVersion:"2.1.260",maximumRuntimeVersionExclusive:"2.2.0",platforms:{darwin:{level:"hard",fileAccess:{"workspace-read":!0,"workspace-write":!0,"host-full":!0},externalCapabilities:m},linux:{level:"partial",fileAccess:{"workspace-read":!0,"workspace-write":!0,"host-full":!0},externalCapabilities:m,knownGaps:["internalTransport.unixSocketGranularity"]},wsl:{level:"partial",fileAccess:{"workspace-read":!0,"workspace-write":!0,"host-full":!0},externalCapabilities:m,knownGaps:["internalTransport.unixSocketGranularity"]},win32:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:{commandNetwork:{allow:!0,deny:!1},webSearch:{allow:!0,deny:!0},browser:{allow:!0,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!0,deny:!0}},knownGaps:["sandbox.nativeWindowsUnavailable"]}}},P=["//**/.env","//**/.env.*","//**/*.key","//**/*.pem","~/.ssh/**","~/.aws/**","~/.azure/**","~/.config/gcloud/**","~/.netrc","~/.npmrc","~/.shennian/**"],f=["/**/.env","/**/.env.*","/**/*.key","/**/*.pem","~/.ssh","~/.aws","~/.azure","~/.config/gcloud","~/.netrc","~/.npmrc"],x="~/.shennian",W=["Read","Glob","Grep","LSP"],z=["Edit","Write","NotebookEdit"];function G(a,e,l={}){const b=D(e),t=a.effectivePolicy,s=a.platform!=="win32",_=A(l.internalReadPaths),h=A(l.internalUnixSockets),S=d(n.parse(b).root),u=d(b),i=t.fileAccess==="host-full",c=t.fileAccess==="workspace-write",p=N(),o=[];c&&o.push("Edit(/**)"),i&&o.push("Read","Edit"),t.externalCapabilities.webSearch&&o.push("WebSearch","WebFetch"),t.externalCapabilities.mcp&&o.push("mcp__*"),s||o.push("Bash","PowerShell");const E=[...P.flatMap(r=>[`Read(${r})`,`Edit(${r})`]),...p.flatMap(r=>[`Read(${y(r)}/**)`,`Edit(${y(r)}/**)`]),...t.externalCapabilities.webSearch?[]:["WebSearch","WebFetch"],...t.externalCapabilities.mcp?[]:["mcp__*"]],C=s?{disabled:!1,allowRead:[...new Set([u,..._])],allowWrite:i?[S]:c?[u]:[],denyRead:[...f,x,...p],denyWrite:[...f,x,...p,...!i&&!c?[u]:[]]}:void 0,v=s?{allowedDomains:t.externalCapabilities.commandNetwork?["*"]:[],deniedDomains:t.externalCapabilities.commandNetwork?[]:["*"],strictAllowlist:!0,allowLocalBinding:t.externalCapabilities.commandNetwork,allowUnixSockets:a.platform==="darwin"?h:[],allowAllUnixSockets:(a.platform==="linux"||a.platform==="wsl")&&h.length>0}:void 0,k={permissions:{defaultMode:"dontAsk",disableBypassPermissionsMode:"disable",disableAutoMode:"disable",blockReadsOutsideWorkingDirectories:!i,allow:[...new Set(o)].sort(),deny:[...new Set(E)].sort()},disableArtifact:!0,disableRemoteControl:!0,disableWorkflows:!0,disableClaudeAiConnectors:!t.externalCapabilities.mcp,...s?{sandbox:{enabled:!0,failIfUnavailable:!0,autoAllowBashIfSandboxed:!0,allowUnsandboxedCommands:!1,excludedCommands:[],allowAppleEvents:!1,filesystem:C,network:v,credentials:{files:f.map(r=>({path:r,mode:"deny"})),envVars:I(l.shellSubprocessPolicy?.deniedEnvKeys).map(r=>({name:r,mode:"deny"}))}}}:{}},R=["Bash",...W,...c||i?z:[],...t.externalCapabilities.webSearch?["WebSearch","WebFetch"]:[]],w=["--restricted","--setting-sources","","--settings",JSON.stringify(k),"--permission-mode","dontAsk","--tools",[...new Set(R)].join(","),t.externalCapabilities.browser?"--chrome":"--no-chrome"];return i&&w.push("--add-dir",S),t.externalCapabilities.mcp||w.push("--strict-mcp-config","--mcp-config",JSON.stringify({mcpServers:{}})),{settings:k,args:w}}function D(a){const e=n.normalize(a.trim());if(!e||!n.isAbsolute(e))throw new Error("claude_managed_workdir_invalid");let l;try{l=g.realpathSync(e)}catch{throw new Error("claude_managed_workdir_unavailable")}if(n.normalize(l)!==e)throw new Error("claude_managed_workdir_not_realpath");return e}function A(a){return[...new Set(a??[])].map(e=>n.normalize(e.trim())).filter(e=>e&&n.isAbsolute(e)).map(d).sort()}function d(a){return a.replaceAll("\\","/")}function y(a){const e=d(a);return e.startsWith("/")?`/${e}`:`//${e}`}function I(a){return[...new Set(a??[])].filter(e=>/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)).sort()}export{M as CLAUDE_MANAGED_RUNTIME_SECURITY,G as compileClaudeManagedPermissions};
1
+ // @arch docs/features/managed-agent-restricted-runtime.md
2
+ // @test src/__tests__/claude-managed-permissions.test.ts
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { resolveProtectedShennianDirectories } from '../security/managed-agent-credential-paths.js';
6
+ const fullExternalCapabilitySupport = {
7
+ commandNetwork: { allow: true, deny: true },
8
+ webSearch: { allow: true, deny: true },
9
+ browser: { allow: true, deny: true },
10
+ // Claude Code's computer-use surface controls the real desktop and does not
11
+ // currently expose a Shennian-verifiable, non-interactive permission gate.
12
+ computerUse: { allow: false, deny: true },
13
+ mcp: { allow: true, deny: true },
14
+ };
15
+ /**
16
+ * v2.1.260 is the first evidence baseline used by Shennian: it includes
17
+ * restricted mode, source isolation, fail-closed sandbox startup, credential
18
+ * scrubbing, outside-workspace read blocking, and the macOS fix for that read
19
+ * boundary. Unknown and older builds therefore resolve as unverified.
20
+ */
21
+ export const CLAUDE_MANAGED_RUNTIME_SECURITY = {
22
+ minimumRuntimeVersion: '2.1.260',
23
+ maximumRuntimeVersionExclusive: '2.2.0',
24
+ platforms: {
25
+ darwin: {
26
+ level: 'hard',
27
+ fileAccess: { 'workspace-read': true, 'workspace-write': true, 'host-full': true },
28
+ externalCapabilities: fullExternalCapabilitySupport,
29
+ },
30
+ linux: {
31
+ level: 'partial',
32
+ fileAccess: { 'workspace-read': true, 'workspace-write': true, 'host-full': true },
33
+ externalCapabilities: fullExternalCapabilitySupport,
34
+ knownGaps: ['internalTransport.unixSocketGranularity'],
35
+ },
36
+ wsl: {
37
+ level: 'partial',
38
+ fileAccess: { 'workspace-read': true, 'workspace-write': true, 'host-full': true },
39
+ externalCapabilities: fullExternalCapabilitySupport,
40
+ knownGaps: ['internalTransport.unixSocketGranularity'],
41
+ },
42
+ win32: {
43
+ level: 'partial',
44
+ fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
45
+ externalCapabilities: {
46
+ commandNetwork: { allow: true, deny: false },
47
+ webSearch: { allow: true, deny: true },
48
+ browser: { allow: true, deny: true },
49
+ computerUse: { allow: false, deny: true },
50
+ mcp: { allow: true, deny: true },
51
+ },
52
+ knownGaps: ['sandbox.nativeWindowsUnavailable'],
53
+ },
54
+ },
55
+ };
56
+ const SENSITIVE_PERMISSION_PATHS = [
57
+ '//**/.env',
58
+ '//**/.env.*',
59
+ '//**/*.key',
60
+ '//**/*.pem',
61
+ '~/.ssh/**',
62
+ '~/.aws/**',
63
+ '~/.azure/**',
64
+ '~/.config/gcloud/**',
65
+ '~/.netrc',
66
+ '~/.npmrc',
67
+ '~/.shennian/**',
68
+ ];
69
+ const SENSITIVE_SANDBOX_PATHS = [
70
+ '/**/.env',
71
+ '/**/.env.*',
72
+ '/**/*.key',
73
+ '/**/*.pem',
74
+ '~/.ssh',
75
+ '~/.aws',
76
+ '~/.azure',
77
+ '~/.config/gcloud',
78
+ '~/.netrc',
79
+ '~/.npmrc',
80
+ ];
81
+ const SHENNIAN_HOME_SANDBOX_PATH = '~/.shennian';
82
+ const READ_TOOLS = ['Read', 'Glob', 'Grep', 'LSP'];
83
+ const WRITE_TOOLS = ['Edit', 'Write', 'NotebookEdit'];
84
+ /**
85
+ * Converts the daemon-verified effective policy into Claude Code's native,
86
+ * invocation-scoped permission and sandbox configuration. It never writes to
87
+ * ~/.claude or the project's .claude directory.
88
+ */
89
+ export function compileClaudeManagedPermissions(enforcement, resolvedWorkDir, options = {}) {
90
+ const workDir = validateResolvedWorkDir(resolvedWorkDir);
91
+ const policy = enforcement.effectivePolicy;
92
+ const hardSandbox = enforcement.platform !== 'win32';
93
+ const internalReadPaths = normalizeAbsolutePaths(options.internalReadPaths);
94
+ const internalUnixSockets = normalizeAbsolutePaths(options.internalUnixSockets);
95
+ const fileRoot = normalizeForClaude(path.parse(workDir).root);
96
+ const normalizedWorkDir = normalizeForClaude(workDir);
97
+ const hostFull = policy.fileAccess === 'host-full';
98
+ const workspaceWritable = policy.fileAccess === 'workspace-write';
99
+ const shennianDirectories = resolveProtectedShennianDirectories();
100
+ const allowRules = [];
101
+ if (workspaceWritable)
102
+ allowRules.push('Edit(/**)');
103
+ if (hostFull)
104
+ allowRules.push('Read', 'Edit');
105
+ if (policy.externalCapabilities.webSearch)
106
+ allowRules.push('WebSearch', 'WebFetch');
107
+ if (policy.externalCapabilities.mcp)
108
+ allowRules.push('mcp__*');
109
+ // Native Windows has no Bash sandbox. The compiler has already widened the
110
+ // effective policy and required limited-protection confirmation before this
111
+ // unsandboxed command surface can be reached.
112
+ if (!hardSandbox)
113
+ allowRules.push('Bash', 'PowerShell');
114
+ const denyRules = [
115
+ ...SENSITIVE_PERMISSION_PATHS.flatMap((entry) => [`Read(${entry})`, `Edit(${entry})`]),
116
+ ...shennianDirectories.flatMap((entry) => [
117
+ `Read(${toClaudeAbsolutePermissionPattern(entry)}/**)`,
118
+ `Edit(${toClaudeAbsolutePermissionPattern(entry)}/**)`,
119
+ ]),
120
+ ...(!policy.externalCapabilities.webSearch ? ['WebSearch', 'WebFetch'] : []),
121
+ ...(!policy.externalCapabilities.mcp ? ['mcp__*'] : []),
122
+ ];
123
+ const filesystem = hardSandbox
124
+ ? {
125
+ disabled: false,
126
+ // Claude's sandbox allowRead entries take precedence over denyRead.
127
+ // This keeps the product-owned directory opaque while restoring only
128
+ // the exact daemon transport files needed by the scoped Room CLI.
129
+ allowRead: [...new Set([normalizedWorkDir, ...internalReadPaths])],
130
+ allowWrite: hostFull ? [fileRoot] : workspaceWritable ? [normalizedWorkDir] : [],
131
+ denyRead: [
132
+ ...SENSITIVE_SANDBOX_PATHS,
133
+ SHENNIAN_HOME_SANDBOX_PATH,
134
+ ...shennianDirectories,
135
+ ],
136
+ denyWrite: [
137
+ ...SENSITIVE_SANDBOX_PATHS,
138
+ SHENNIAN_HOME_SANDBOX_PATH,
139
+ ...shennianDirectories,
140
+ ...(!hostFull && !workspaceWritable ? [normalizedWorkDir] : []),
141
+ ],
142
+ }
143
+ : undefined;
144
+ const network = hardSandbox
145
+ ? {
146
+ allowedDomains: policy.externalCapabilities.commandNetwork ? ['*'] : [],
147
+ deniedDomains: policy.externalCapabilities.commandNetwork ? [] : ['*'],
148
+ strictAllowlist: true,
149
+ allowLocalBinding: policy.externalCapabilities.commandNetwork,
150
+ allowUnixSockets: enforcement.platform === 'darwin' ? internalUnixSockets : [],
151
+ // Linux/WSL only expose an all-or-nothing AF_UNIX switch. The
152
+ // filesystem layer still exposes only the exact daemon path, but the
153
+ // runtime declaration reports this platform as partial.
154
+ allowAllUnixSockets: (enforcement.platform === 'linux' || enforcement.platform === 'wsl') &&
155
+ internalUnixSockets.length > 0,
156
+ }
157
+ : undefined;
158
+ const settings = {
159
+ permissions: {
160
+ defaultMode: 'dontAsk',
161
+ disableBypassPermissionsMode: 'disable',
162
+ disableAutoMode: 'disable',
163
+ blockReadsOutsideWorkingDirectories: !hostFull,
164
+ allow: [...new Set(allowRules)].sort(),
165
+ deny: [...new Set(denyRules)].sort(),
166
+ },
167
+ disableArtifact: true,
168
+ disableRemoteControl: true,
169
+ disableWorkflows: true,
170
+ disableClaudeAiConnectors: !policy.externalCapabilities.mcp,
171
+ ...(hardSandbox
172
+ ? {
173
+ sandbox: {
174
+ enabled: true,
175
+ failIfUnavailable: true,
176
+ autoAllowBashIfSandboxed: true,
177
+ allowUnsandboxedCommands: false,
178
+ excludedCommands: [],
179
+ allowAppleEvents: false,
180
+ filesystem,
181
+ network,
182
+ credentials: {
183
+ files: SENSITIVE_SANDBOX_PATHS.map((entry) => ({ path: entry, mode: 'deny' })),
184
+ envVars: normalizeEnvKeys(options.shellSubprocessPolicy?.deniedEnvKeys).map((name) => ({ name, mode: 'deny' })),
185
+ },
186
+ },
187
+ }
188
+ : {}),
189
+ };
190
+ const tools = [
191
+ 'Bash',
192
+ ...READ_TOOLS,
193
+ ...(workspaceWritable || hostFull ? WRITE_TOOLS : []),
194
+ ...(policy.externalCapabilities.webSearch ? ['WebSearch', 'WebFetch'] : []),
195
+ ];
196
+ const args = [
197
+ '--restricted',
198
+ '--setting-sources',
199
+ '',
200
+ '--settings',
201
+ JSON.stringify(settings),
202
+ '--permission-mode',
203
+ 'dontAsk',
204
+ '--tools',
205
+ [...new Set(tools)].join(','),
206
+ policy.externalCapabilities.browser ? '--chrome' : '--no-chrome',
207
+ ];
208
+ if (hostFull)
209
+ args.push('--add-dir', fileRoot);
210
+ if (!policy.externalCapabilities.mcp) {
211
+ args.push('--strict-mcp-config', '--mcp-config', JSON.stringify({ mcpServers: {} }));
212
+ }
213
+ return { settings, args };
214
+ }
215
+ function validateResolvedWorkDir(value) {
216
+ const normalized = path.normalize(value.trim());
217
+ if (!normalized || !path.isAbsolute(normalized)) {
218
+ throw new Error('claude_managed_workdir_invalid');
219
+ }
220
+ let realPath;
221
+ try {
222
+ realPath = fs.realpathSync(normalized);
223
+ }
224
+ catch {
225
+ throw new Error('claude_managed_workdir_unavailable');
226
+ }
227
+ if (path.normalize(realPath) !== normalized) {
228
+ throw new Error('claude_managed_workdir_not_realpath');
229
+ }
230
+ return normalized;
231
+ }
232
+ function normalizeAbsolutePaths(values) {
233
+ return [...new Set(values ?? [])]
234
+ .map((entry) => path.normalize(entry.trim()))
235
+ .filter((entry) => entry && path.isAbsolute(entry))
236
+ .map(normalizeForClaude)
237
+ .sort();
238
+ }
239
+ function normalizeForClaude(value) {
240
+ return value.replaceAll('\\', '/');
241
+ }
242
+ function toClaudeAbsolutePermissionPattern(value) {
243
+ const normalized = normalizeForClaude(value);
244
+ return normalized.startsWith('/') ? `/${normalized}` : `//${normalized}`;
245
+ }
246
+ function normalizeEnvKeys(values) {
247
+ return [...new Set(values ?? [])].filter((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value)).sort();
248
+ }