@kafca/agentdock 0.1.58 → 0.1.62

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 (127) hide show
  1. package/README.md +18 -0
  2. package/dist/renderer/assets/AutomationList-BgJgAdVO.js +16 -0
  3. package/dist/renderer/assets/{Badge-BfY1VaHS.js → Badge-Bq2hPVcR.js} +1 -1
  4. package/dist/renderer/assets/{Card-DWA_Tww_.js → Card-BPJNSZce.js} +1 -1
  5. package/dist/renderer/assets/{Config-nHj3g1U5.js → Config-aSFqlI88.js} +2 -2
  6. package/dist/renderer/assets/Dashboard-D3ezFmb7.js +36 -0
  7. package/dist/renderer/assets/{EmptyState-zatDjLj_.js → EmptyState-DK496gBq.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-CsM5n70d.js → HighlightedMarkdown-Ckre5jNv.js} +1 -1
  9. package/dist/renderer/assets/{Input-R1XdFK1K.js → Input-C5C1d0ba.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Co5sxeGd.js → KnowledgeDetail-N8Ey1ikc.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-DwziPw5m.js → KnowledgeHome-9aF8nJEf.js} +2 -2
  12. package/dist/renderer/assets/{Logs-NwZUtR_G.js → Logs-sgAdwEeS.js} +2 -2
  13. package/dist/renderer/assets/{Modal-CrcIwIFL.js → Modal-BB10hTRc.js} +3 -3
  14. package/dist/renderer/assets/{Page-Dp0obeSw.js → Page-DfC319Ck.js} +1 -1
  15. package/dist/renderer/assets/{Select-6mVQaf84.js → Select-RHJBmNER.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-BFzbn3L6.js → ThreadChat-BU1zCdr4.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-DCmo8BMm.js → Workspace-BoZCz7S8.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-9rjg_Hp4.js → arrow-left-D2V1eZSD.js} +1 -1
  19. package/dist/renderer/assets/{book-open-Bu8GCHnG.js → book-open-DUcBn7Bb.js} +1 -1
  20. package/dist/renderer/assets/{channels-BxVbG-6j.js → channels-DMhDSlf2.js} +2 -2
  21. package/dist/renderer/assets/{chevron-down-BE8uQmWM.js → chevron-down-Ca2FQDB7.js} +1 -1
  22. package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
  23. package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
  24. package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
  25. package/dist/renderer/assets/{index-B7TeF6Es.js → index-BlnvlekJ.js} +1 -1
  26. package/dist/renderer/assets/index-DRJ5wgw_.js +162 -0
  27. package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
  28. package/dist/renderer/assets/{knowledge-Kl19SqGS.js → knowledge-D1-k56eY.js} +1 -1
  29. package/dist/renderer/assets/{pencil-CPB7SK1m.js → pencil-BhWdv_86.js} +1 -1
  30. package/dist/renderer/assets/{plus-Bt26z-4q.js → plus-BacLMlK3.js} +1 -1
  31. package/dist/renderer/assets/{save-Une7GMxT.js → save-Bpoxbzau.js} +1 -1
  32. package/dist/renderer/assets/{search-DE0agJjJ.js → search-BVJcpdFX.js} +1 -1
  33. package/dist/renderer/assets/{shield-check-De7xaXZh.js → shield-check-C2cw_Ip6.js} +1 -1
  34. package/dist/renderer/assets/{threads-BnsNL5ei.js → threads-DOsBQBg_.js} +1 -1
  35. package/dist/renderer/assets/{trash-2-6iGF59Ac.js → trash-2-Bs3rEm3g.js} +1 -1
  36. package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
  37. package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
  38. package/dist/renderer/index.html +2 -2
  39. package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
  40. package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
  41. package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
  42. package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
  43. package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
  44. package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
  45. package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
  46. package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
  47. package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
  48. package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
  49. package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
  50. package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
  51. package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
  52. package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
  53. package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
  54. package/dist-electron/packages/contracts/src/automations.js +211 -0
  55. package/dist-electron/packages/contracts/src/index.js +1 -0
  56. package/dist-electron/packages/core-sdk/src/automations.js +87 -0
  57. package/dist-electron/packages/core-sdk/src/client.js +101 -0
  58. package/dist-electron/packages/core-sdk/src/index.js +2 -1
  59. package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
  60. package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
  63. package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
  64. package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
  65. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
  66. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
  67. package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
  68. package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
  69. package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
  70. package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
  71. package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
  72. package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
  73. package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
  74. package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
  75. package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
  76. package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
  77. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
  78. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
  79. package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
  80. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
  85. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
  86. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
  87. package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
  88. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
  89. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
  90. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
  91. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
  92. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
  93. package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
  94. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
  95. package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
  96. package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
  97. package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
  98. package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
  99. package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
  100. package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
  101. package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
  102. package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
  103. package/dist-electron/tests/electron/automation-engine.test.js +264 -0
  104. package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
  105. package/dist-electron/tests/electron/automation-sandbox-runner.test.js +333 -0
  106. package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
  107. package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
  108. package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
  109. package/dist-electron/tests/electron/automation-service.test.js +1078 -0
  110. package/dist-electron/tests/electron/automation-store.test.js +593 -0
  111. package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
  112. package/dist-electron/tests/electron/core-client.test.js +99 -0
  113. package/dist-electron/tests/electron/lac-cli.test.js +110 -0
  114. package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
  115. package/dist-electron/tests/integration/automation-routes.test.js +186 -0
  116. package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
  117. package/package.json +5 -6
  118. package/dist/renderer/assets/CronList-BwkA_I7v.js +0 -1
  119. package/dist/renderer/assets/Dashboard-D0_A7k02.js +0 -41
  120. package/dist/renderer/assets/MonitorList-CS1uLS5A.js +0 -1
  121. package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
  122. package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
  123. package/dist/renderer/assets/index-BkI9POxj.js +0 -167
  124. package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
  125. package/dist/renderer/assets/play-DGAO4OCu.js +0 -6
  126. package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
  127. package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
@@ -0,0 +1,499 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const node_process_1 = __importDefault(require("node:process"));
11
+ const node_test_1 = __importDefault(require("node:test"));
12
+ const script_package_js_1 = require("../../services/local-ai-core/src/automation/scripts/script-package.js");
13
+ const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
14
+ const globalTestHooks = globalThis;
15
+ function tempDir(prefix) {
16
+ return (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), prefix));
17
+ }
18
+ function removeTempTree(path) {
19
+ makeWritable(path);
20
+ (0, node_fs_1.rmSync)(path, { recursive: true, force: true });
21
+ }
22
+ function makeWritable(path) {
23
+ let stat;
24
+ try {
25
+ stat = (0, node_fs_1.lstatSync)(path);
26
+ }
27
+ catch {
28
+ return;
29
+ }
30
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
31
+ try {
32
+ (0, node_fs_1.chmodSync)(path, 0o644);
33
+ }
34
+ catch {
35
+ // Best effort for portable test cleanup.
36
+ }
37
+ return;
38
+ }
39
+ try {
40
+ (0, node_fs_1.chmodSync)(path, 0o755);
41
+ }
42
+ catch {
43
+ // Best effort for portable test cleanup.
44
+ }
45
+ for (const entry of (0, node_fs_1.readdirSync)(path)) {
46
+ makeWritable((0, node_path_1.join)(path, entry));
47
+ }
48
+ }
49
+ function writeBundle(root, overrides = {}) {
50
+ const manifest = overrides.manifest ?? {
51
+ protocolVersion: 1,
52
+ entrypoint: 'run.sh',
53
+ config: {},
54
+ configSchema: { type: 'object' },
55
+ capabilities: { network: 'none', internalAccess: false, allowedReadDirs: [] },
56
+ secretRefs: [],
57
+ env: [],
58
+ testPlan: { command: 'manual' },
59
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
60
+ };
61
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
62
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'run.sh'), overrides.entry ?? '#!/bin/sh\nnode helper.js\n');
63
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'helper.js'), overrides.helper ?? 'console.log("ok");\n');
64
+ }
65
+ function writeBundleWithNestedDir(root) {
66
+ writeBundle(root);
67
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'nested'));
68
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'nested', 'keep.js'), 'console.log("nested");\n');
69
+ }
70
+ function withDirectoryReadHook(hooks, run) {
71
+ const previousHooks = globalTestHooks.__automationScriptPackageTestHooks;
72
+ globalTestHooks.__automationScriptPackageTestHooks = hooks;
73
+ try {
74
+ run();
75
+ }
76
+ finally {
77
+ if (previousHooks) {
78
+ globalTestHooks.__automationScriptPackageTestHooks = previousHooks;
79
+ }
80
+ else {
81
+ delete globalTestHooks.__automationScriptPackageTestHooks;
82
+ }
83
+ }
84
+ }
85
+ (0, node_test_1.default)('stages immutable script packages with deterministic whole-package hashes and read-only files', () => {
86
+ const userDataPath = tempDir('automation-script-user-data-');
87
+ const first = tempDir('automation-script-bundle-a-');
88
+ const second = tempDir('automation-script-bundle-b-');
89
+ try {
90
+ writeBundle(first);
91
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(second, 'helper.js'), 'console.log("ok");\n');
92
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(second, 'run.sh'), '#!/bin/sh\nnode helper.js\n');
93
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(second, 'manifest.json'), `${JSON.stringify({
94
+ protocolVersion: 1,
95
+ entrypoint: 'run.sh',
96
+ config: {},
97
+ configSchema: { type: 'object' },
98
+ capabilities: { network: 'none', internalAccess: false, allowedReadDirs: [] },
99
+ secretRefs: [],
100
+ env: [],
101
+ testPlan: { command: 'manual' },
102
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
103
+ }, null, 2)}\n`);
104
+ const staged = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:alpha', sourceDir: first });
105
+ const same = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:alpha', sourceDir: second });
106
+ strict_1.default.equal(staged.packageSha256, same.packageSha256);
107
+ strict_1.default.equal(staged.packagePath, (0, node_path_1.join)(userDataPath, 'automations', 'scripts', 'script:alpha', staged.packageSha256));
108
+ strict_1.default.deepEqual(staged.entries.map((entry) => entry.path), ['helper.js', 'manifest.json', 'run.sh']);
109
+ strict_1.default.equal(staged.manifest.entrypoint, 'run.sh');
110
+ strict_1.default.equal(staged.shebang, '#!/bin/sh');
111
+ strict_1.default.equal((0, node_fs_1.lstatSync)(staged.packagePath).mode & 0o222, 0);
112
+ for (const entry of staged.entries) {
113
+ const mode = (0, node_fs_1.lstatSync)((0, node_path_1.join)(staged.packagePath, entry.path)).mode;
114
+ strict_1.default.equal(mode & 0o222, 0);
115
+ }
116
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(first, 'helper.js'), 'console.log("changed");\n');
117
+ const changed = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:alpha', sourceDir: first });
118
+ strict_1.default.notEqual(changed.packageSha256, staged.packageSha256);
119
+ strict_1.default.equal(changed.packagePath, (0, node_path_1.join)(userDataPath, 'automations', 'scripts', 'script:alpha', changed.packageSha256));
120
+ }
121
+ finally {
122
+ removeTempTree(userDataPath);
123
+ removeTempTree(first);
124
+ removeTempTree(second);
125
+ }
126
+ });
127
+ (0, node_test_1.default)('sorts package paths by deterministic UTF-8 byte order rather than locale collation', () => {
128
+ const userDataPath = tempDir('automation-script-user-data-');
129
+ const sourceDir = tempDir('automation-script-locale-order-');
130
+ try {
131
+ writeBundle(sourceDir);
132
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(sourceDir, 'a.js'), 'console.log("lower");\n');
133
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(sourceDir, 'z.js'), 'console.log("last ascii");\n');
134
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(sourceDir, 'ä.js'), 'console.log("non ascii");\n');
135
+ const staged = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:order', sourceDir });
136
+ strict_1.default.deepEqual(staged.entries.map((entry) => entry.path), ['a.js', 'helper.js', 'manifest.json', 'run.sh', 'z.js', 'ä.js']);
137
+ }
138
+ finally {
139
+ removeTempTree(userDataPath);
140
+ removeTempTree(sourceDir);
141
+ }
142
+ });
143
+ (0, node_test_1.default)('rejects source nested directories that redirect during traversal', () => {
144
+ const userDataPath = tempDir('automation-script-user-data-');
145
+ const sourceDir = tempDir('automation-script-race-source-');
146
+ const outsideDir = tempDir('automation-script-race-outside-');
147
+ try {
148
+ writeBundleWithNestedDir(sourceDir);
149
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outsideDir, 'escape.js'), 'console.log("outside");\n');
150
+ let swapped = false;
151
+ withDirectoryReadHook({
152
+ beforeDirectoryRead(directory) {
153
+ if (swapped || directory !== (0, node_path_1.join)(sourceDir, 'nested'))
154
+ return;
155
+ swapped = true;
156
+ (0, node_fs_1.rmSync)(directory, { recursive: true, force: true });
157
+ (0, node_fs_1.symlinkSync)(outsideDir, directory);
158
+ },
159
+ }, () => {
160
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:source-race', sourceDir }), /changed|redirect|outside|symlink|real directory/i);
161
+ });
162
+ strict_1.default.equal(swapped, true);
163
+ }
164
+ finally {
165
+ removeTempTree(userDataPath);
166
+ removeTempTree(sourceDir);
167
+ removeTempTree(outsideDir);
168
+ }
169
+ });
170
+ (0, node_test_1.default)('rejects existing package nested directories that redirect during verification', () => {
171
+ const userDataPath = tempDir('automation-script-user-data-');
172
+ const sourceDir = tempDir('automation-script-race-existing-');
173
+ const outsideDir = tempDir('automation-script-race-outside-');
174
+ try {
175
+ writeBundleWithNestedDir(sourceDir);
176
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outsideDir, 'escape.js'), 'console.log("outside");\n');
177
+ const staged = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:existing-race', sourceDir });
178
+ const nestedPackageDir = (0, node_path_1.join)(staged.packagePath, 'nested');
179
+ let swapped = false;
180
+ withDirectoryReadHook({
181
+ beforeDirectoryRead(directory) {
182
+ if (swapped || directory !== nestedPackageDir)
183
+ return;
184
+ swapped = true;
185
+ (0, node_fs_1.chmodSync)(staged.packagePath, 0o755);
186
+ (0, node_fs_1.chmodSync)(nestedPackageDir, 0o755);
187
+ (0, node_fs_1.rmSync)(nestedPackageDir, { recursive: true, force: true });
188
+ (0, node_fs_1.symlinkSync)(outsideDir, nestedPackageDir);
189
+ },
190
+ }, () => {
191
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:existing-race', sourceDir }), /changed|redirect|outside|symlink|real directory/i);
192
+ });
193
+ strict_1.default.equal(swapped, true);
194
+ }
195
+ finally {
196
+ removeTempTree(userDataPath);
197
+ removeTempTree(sourceDir);
198
+ removeTempTree(outsideDir);
199
+ }
200
+ });
201
+ (0, node_test_1.default)('rejects source file reads when the parent directory redirects during open', () => {
202
+ const userDataPath = tempDir('automation-script-user-data-');
203
+ const sourceDir = tempDir('automation-script-file-read-source-');
204
+ const outsideDir = tempDir('automation-script-file-read-outside-');
205
+ try {
206
+ writeBundleWithNestedDir(sourceDir);
207
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outsideDir, 'keep.js'), 'console.log("outside");\n');
208
+ const nestedFile = (0, node_path_1.join)(sourceDir, 'nested', 'keep.js');
209
+ let swapped = false;
210
+ withDirectoryReadHook({
211
+ beforeFileRead(filePath) {
212
+ if (swapped || filePath !== nestedFile)
213
+ return;
214
+ swapped = true;
215
+ (0, node_fs_1.rmSync)((0, node_path_1.dirname)(filePath), { recursive: true, force: true });
216
+ (0, node_fs_1.symlinkSync)(outsideDir, (0, node_path_1.dirname)(filePath));
217
+ },
218
+ }, () => {
219
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:file-read-source', sourceDir }), /changed|redirect|outside|symlink|real directory|package root/i);
220
+ });
221
+ strict_1.default.equal(swapped, true);
222
+ }
223
+ finally {
224
+ removeTempTree(userDataPath);
225
+ removeTempTree(sourceDir);
226
+ removeTempTree(outsideDir);
227
+ }
228
+ });
229
+ (0, node_test_1.default)('rejects existing package file reads when the parent directory redirects during verification', () => {
230
+ const userDataPath = tempDir('automation-script-user-data-');
231
+ const sourceDir = tempDir('automation-script-file-read-existing-');
232
+ const outsideDir = tempDir('automation-script-file-read-outside-');
233
+ try {
234
+ writeBundleWithNestedDir(sourceDir);
235
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outsideDir, 'keep.js'), 'console.log("outside");\n');
236
+ const staged = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:file-read-existing', sourceDir });
237
+ const nestedFile = (0, node_path_1.join)(staged.packagePath, 'nested', 'keep.js');
238
+ let swapped = false;
239
+ withDirectoryReadHook({
240
+ beforeFileRead(filePath) {
241
+ if (swapped || filePath !== nestedFile)
242
+ return;
243
+ swapped = true;
244
+ (0, node_fs_1.chmodSync)((0, node_path_1.dirname)((0, node_path_1.dirname)(filePath)), 0o755);
245
+ (0, node_fs_1.chmodSync)((0, node_path_1.dirname)(filePath), 0o755);
246
+ (0, node_fs_1.rmSync)((0, node_path_1.dirname)(filePath), { recursive: true, force: true });
247
+ (0, node_fs_1.symlinkSync)(outsideDir, (0, node_path_1.dirname)(filePath));
248
+ },
249
+ }, () => {
250
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:file-read-existing', sourceDir }), /changed|redirect|outside|symlink|real directory|package root/i);
251
+ });
252
+ strict_1.default.equal(swapped, true);
253
+ }
254
+ finally {
255
+ removeTempTree(userDataPath);
256
+ removeTempTree(sourceDir);
257
+ removeTempTree(outsideDir);
258
+ }
259
+ });
260
+ (0, node_test_1.default)('rejects chmod when the parent directory redirects before chmod applies', () => {
261
+ const userDataPath = tempDir('automation-script-user-data-');
262
+ const sourceDir = tempDir('automation-script-chmod-source-');
263
+ const outsideDir = tempDir('automation-script-chmod-outside-');
264
+ try {
265
+ writeBundleWithNestedDir(sourceDir);
266
+ const outsideFile = (0, node_path_1.join)(outsideDir, 'keep.js');
267
+ (0, node_fs_1.writeFileSync)(outsideFile, 'console.log("outside");\n');
268
+ (0, node_fs_1.chmodSync)(outsideFile, 0o600);
269
+ const outsideModeBefore = (0, node_fs_1.lstatSync)(outsideFile).mode & 0o777;
270
+ let swapped = false;
271
+ withDirectoryReadHook({
272
+ beforeChmod(filePath) {
273
+ if (swapped ||
274
+ !filePath.includes((0, node_path_1.join)('automations', 'scripts', 'script:chmod-race')) ||
275
+ !filePath.endsWith((0, node_path_1.join)('nested', 'keep.js'))) {
276
+ return;
277
+ }
278
+ swapped = true;
279
+ (0, node_fs_1.rmSync)((0, node_path_1.dirname)(filePath), { recursive: true, force: true });
280
+ (0, node_fs_1.symlinkSync)(outsideDir, (0, node_path_1.dirname)(filePath));
281
+ },
282
+ }, () => {
283
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:chmod-race', sourceDir }), /changed|redirect|outside|symlink|real directory|package root/i);
284
+ });
285
+ strict_1.default.equal(swapped, true);
286
+ strict_1.default.equal((0, node_fs_1.lstatSync)(outsideFile).mode & 0o777, outsideModeBefore);
287
+ }
288
+ finally {
289
+ removeTempTree(userDataPath);
290
+ removeTempTree(sourceDir);
291
+ removeTempTree(outsideDir);
292
+ }
293
+ });
294
+ (0, node_test_1.default)('rejects unsafe script package inputs before staging', () => {
295
+ const userDataPath = tempDir('automation-script-user-data-');
296
+ const sourceDirs = [];
297
+ const unsafeBundle = (name, overrides) => {
298
+ const sourceDir = tempDir(`automation-script-${name}-`);
299
+ sourceDirs.push(sourceDir);
300
+ writeBundle(sourceDir, overrides);
301
+ if (overrides.symlink)
302
+ (0, node_fs_1.symlinkSync)((0, node_path_1.join)(sourceDir, 'helper.js'), (0, node_path_1.join)(sourceDir, 'linked-helper.js'));
303
+ return sourceDir;
304
+ };
305
+ try {
306
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
307
+ userDataPath,
308
+ scriptId: 'script:unsafe',
309
+ sourceDir: unsafeBundle('traversal', {
310
+ manifest: {
311
+ protocolVersion: 1,
312
+ entrypoint: '../run.sh',
313
+ config: {},
314
+ configSchema: { type: 'object' },
315
+ capabilities: { network: 'none', internalAccess: false, allowedReadDirs: [] },
316
+ secretRefs: [],
317
+ env: [],
318
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
319
+ },
320
+ }),
321
+ }), /path traversal|relative/i);
322
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
323
+ userDataPath,
324
+ scriptId: 'script:unsafe',
325
+ sourceDir: unsafeBundle('absolute', {
326
+ manifest: {
327
+ protocolVersion: 1,
328
+ entrypoint: '/tmp/run.sh',
329
+ config: {},
330
+ configSchema: { type: 'object' },
331
+ capabilities: { network: 'none', internalAccess: false, allowedReadDirs: [] },
332
+ secretRefs: [],
333
+ env: [],
334
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
335
+ },
336
+ }),
337
+ }), /absolute|relative/i);
338
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
339
+ userDataPath,
340
+ scriptId: 'script:unsafe',
341
+ sourceDir: unsafeBundle('missing-shebang', { entry: 'echo missing shebang\n' }),
342
+ }), /shebang/i);
343
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
344
+ userDataPath,
345
+ scriptId: 'script:unsafe',
346
+ sourceDir: unsafeBundle('binary-entry', { entry: '#!/bin/sh\u0000\n' }),
347
+ }), /text|binary/i);
348
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
349
+ userDataPath,
350
+ scriptId: 'script:unsafe',
351
+ sourceDir: unsafeBundle('missing-protocol', { manifest: { entrypoint: 'run.sh' } }),
352
+ }), /protocolVersion/i);
353
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
354
+ userDataPath,
355
+ scriptId: 'script:unsafe',
356
+ sourceDir: unsafeBundle('minimal-manifest', { manifest: { protocolVersion: 1, entrypoint: 'run.sh' } }),
357
+ }), /config|capabilities|secretRefs|env|limits/i);
358
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({
359
+ userDataPath,
360
+ scriptId: 'script:unsafe',
361
+ sourceDir: unsafeBundle('symlink', { symlink: true }),
362
+ }), /symlink|regular file/i);
363
+ }
364
+ finally {
365
+ removeTempTree(userDataPath);
366
+ for (const sourceDir of sourceDirs) {
367
+ removeTempTree(sourceDir);
368
+ }
369
+ }
370
+ });
371
+ (0, node_test_1.default)('rejects existing package destinations that were replaced by symlinks', () => {
372
+ const userDataPath = tempDir('automation-script-user-data-');
373
+ const sourceDir = tempDir('automation-script-bundle-');
374
+ const symlinkTarget = tempDir('automation-script-symlink-target-');
375
+ try {
376
+ writeBundle(sourceDir);
377
+ writeBundle(symlinkTarget);
378
+ const staged = (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:symlink', sourceDir });
379
+ (0, node_fs_1.chmodSync)(staged.packagePath, 0o755);
380
+ (0, node_fs_1.rmSync)(staged.packagePath, { recursive: true, force: true });
381
+ (0, node_fs_1.symlinkSync)(symlinkTarget, staged.packagePath);
382
+ strict_1.default.throws(() => (0, script_package_js_1.stageImmutableScriptPackage)({ userDataPath, scriptId: 'script:symlink', sourceDir }), /symlink|real directory/i);
383
+ }
384
+ finally {
385
+ removeTempTree(userDataPath);
386
+ removeTempTree(sourceDir);
387
+ removeTempTree(symlinkTarget);
388
+ }
389
+ });
390
+ (0, node_test_1.default)('persists automation script and staged version metadata across reopen', () => {
391
+ const userDataPath = tempDir('automation-script-store-');
392
+ const sourceDir = tempDir('automation-script-bundle-');
393
+ try {
394
+ writeBundle(sourceDir);
395
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
396
+ const script = store.createAutomationScript({
397
+ workspaceId: 'workspace-1',
398
+ title: 'Check package',
399
+ description: 'approved script',
400
+ });
401
+ const version = store.createAutomationScriptVersionFromPackage({
402
+ scriptId: script.id,
403
+ sourceDir,
404
+ interpreterPath: '/bin/sh',
405
+ interpreterVersion: 'sh 1.0',
406
+ });
407
+ strict_1.default.equal(version.scriptId, script.id);
408
+ strict_1.default.equal(version.status, 'draft');
409
+ strict_1.default.match(version.packageSha256, /^[a-f0-9]{64}$/);
410
+ strict_1.default.equal(version.packagePath, (0, node_path_1.join)(userDataPath, 'automations', 'scripts', script.id, version.packageSha256));
411
+ strict_1.default.equal(version.shebang, '#!/bin/sh');
412
+ strict_1.default.deepEqual(version.config, {});
413
+ strict_1.default.equal(version.networkMode, 'none');
414
+ strict_1.default.equal(version.internalAccess, false);
415
+ strict_1.default.deepEqual(version.allowedReadDirs, []);
416
+ strict_1.default.deepEqual(version.env, []);
417
+ strict_1.default.deepEqual(version.limits, { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 });
418
+ store.close();
419
+ const reopened = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
420
+ strict_1.default.equal(reopened.getAutomationScript(script.id)?.title, 'Check package');
421
+ strict_1.default.equal(reopened.listAutomationScriptVersions(script.id)[0]?.packageSha256, version.packageSha256);
422
+ reopened.close();
423
+ }
424
+ finally {
425
+ removeTempTree(userDataPath);
426
+ removeTempTree(sourceDir);
427
+ }
428
+ });
429
+ (0, node_test_1.default)('rejects malformed optional automation script version JSON fields', () => {
430
+ const userDataPath = tempDir('automation-script-store-');
431
+ const sourceDir = tempDir('automation-script-bundle-');
432
+ try {
433
+ writeBundle(sourceDir);
434
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
435
+ const script = store.createAutomationScript({
436
+ workspaceId: 'workspace-1',
437
+ title: 'Check package',
438
+ });
439
+ const version = store.createAutomationScriptVersionFromPackage({
440
+ scriptId: script.id,
441
+ sourceDir,
442
+ interpreterPath: '/bin/sh',
443
+ interpreterVersion: 'sh 1.0',
444
+ });
445
+ const db = store.db;
446
+ db.prepare('UPDATE automation_script_versions SET version_json = ? WHERE id = ?').run(JSON.stringify({
447
+ ...version,
448
+ testAuthorization: { actor: 42, at: version.createdAt },
449
+ approval: { actor: 'security', at: 'not-a-date' },
450
+ rejection: 'malformed',
451
+ revocation: { actor: 'security' },
452
+ }), version.id);
453
+ strict_1.default.throws(() => store.listAutomationScriptVersions(script.id), /testAuthorization|approval|rejection|revocation|invalid persisted data/i);
454
+ store.close();
455
+ }
456
+ finally {
457
+ removeTempTree(userDataPath);
458
+ removeTempTree(sourceDir);
459
+ }
460
+ });
461
+ (0, node_test_1.default)('stages an API source bundle through a server-owned directory without accepting source paths', () => {
462
+ const userDataPath = tempDir('automation-script-upload-');
463
+ try {
464
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
465
+ const script = store.createAutomationScript({ workspaceId: 'workspace-1', title: 'Uploaded script' });
466
+ const manifest = {
467
+ protocolVersion: 1, entrypoint: 'run.js', config: {}, configSchema: {},
468
+ capabilities: { network: 'none', internalAccess: false, allowedReadDirs: [] }, secretRefs: [], env: [], testPlan: {},
469
+ limits: { timeoutMs: 30_000, stdoutBytes: 8192, stderrBytes: 8192, payloadBytes: 8192, stateBytes: 8192 },
470
+ };
471
+ const staged = store.stageAutomationScriptSource({
472
+ scriptId: script.id,
473
+ files: [
474
+ { path: 'manifest.json', content: JSON.stringify(manifest) },
475
+ { path: 'run.js', content: '#!/usr/bin/env node\nprocess.stdout.write("{\\"protocolVersion\\":1,\\"matched\\":false}")\n' },
476
+ ],
477
+ });
478
+ const version = store.createAutomationScriptVersionFromStaged({
479
+ scriptId: script.id, staged, interpreterPath: node_process_1.default.execPath, interpreterVersion: node_process_1.default.version,
480
+ });
481
+ strict_1.default.equal(version.status, 'draft');
482
+ strict_1.default.match(version.packagePath, new RegExp(`automations[/\\\\]scripts`));
483
+ strict_1.default.throws(() => store.stageAutomationScriptSource({ scriptId: script.id, files: [{ path: '../escape', content: 'nope' }] }), /traversal|relative POSIX/i);
484
+ const discarded = store.stageAutomationScriptSource({
485
+ scriptId: script.id,
486
+ files: [
487
+ { path: 'manifest.json', content: JSON.stringify({ ...manifest, config: { discarded: true } }) },
488
+ { path: 'run.js', content: '#!/usr/bin/env node\nprocess.exit(0)\n' },
489
+ ],
490
+ });
491
+ strict_1.default.equal((0, node_fs_1.existsSync)(discarded.packagePath), true);
492
+ store.discardUnreferencedAutomationScriptPackage(script.id, discarded);
493
+ strict_1.default.equal((0, node_fs_1.existsSync)(discarded.packagePath), false);
494
+ store.close();
495
+ }
496
+ finally {
497
+ removeTempTree(userDataPath);
498
+ }
499
+ });
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const script_protocol_runner_js_1 = require("../../services/local-ai-core/src/automation/scripts/script-protocol-runner.js");
9
+ const anthropic_sandbox_runner_js_1 = require("../../services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js");
10
+ class FakeSandbox {
11
+ input;
12
+ result = { exitCode: 0, signal: null, stdout: '{"protocolVersion":1,"matched":true,"summary":"ok","nextState":{"cursor":2}}', stderr: 'diagnostic\u0001' };
13
+ async probe() { return { available: true, platform: 'test', missing: [] }; }
14
+ async run(input) {
15
+ this.input = input;
16
+ if (input.command.endsWith(' --version'))
17
+ return { exitCode: 0, signal: null, stdout: 'sh 1.0', stderr: '' };
18
+ return this.result;
19
+ }
20
+ }
21
+ function version(overrides = {}) {
22
+ const packageSha256 = overrides.packageSha256 || 'a'.repeat(64);
23
+ return {
24
+ id: 'version-1', scriptId: 'script-1', status: 'approved', packageSha256, packagePath: `/packages/${packageSha256}`,
25
+ shebang: '#!/bin/sh', interpreterPath: '/bin/sh', interpreterVersion: 'sh 1.0', capabilities: {}, config: { threshold: 2 }, configSchema: {},
26
+ networkMode: 'none', internalAccess: false, allowedReadDirs: [], secretRefs: [], env: [],
27
+ limits: { timeoutMs: 30_000, stdoutBytes: 1024, stderrBytes: 1024, payloadBytes: 256, stateBytes: 256 },
28
+ staticCheck: {}, testPlan: {}, createdAt: '2026-07-12T00:00:00.000Z', updatedAt: '2026-07-12T00:00:00.000Z', ...overrides,
29
+ };
30
+ }
31
+ function runner(sandbox = new FakeSandbox(), current = version()) {
32
+ return {
33
+ sandbox,
34
+ runner: new script_protocol_runner_js_1.ScriptProtocolRunner({
35
+ sandbox,
36
+ getVersion: () => current,
37
+ verifyPackage: () => undefined,
38
+ entrypointFor: () => 'run.sh',
39
+ }),
40
+ };
41
+ }
42
+ (0, node_test_1.default)('sends the exact v1 request and accepts exactly one v1 JSON response', async () => {
43
+ const { sandbox, runner: subject } = runner();
44
+ const result = await subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'evaluation-1', triggeredAt: '2026-07-12T01:02:03.000Z', previousState: { cursor: 1 } });
45
+ strict_1.default.deepEqual(JSON.parse(sandbox.input?.stdin || ''), {
46
+ protocolVersion: 1, evaluationId: 'evaluation-1', triggeredAt: '2026-07-12T01:02:03.000Z', config: { threshold: 2 }, previousState: { cursor: 1 },
47
+ });
48
+ strict_1.default.equal(sandbox.input?.timeoutMs, 30_000);
49
+ strict_1.default.equal(sandbox.input?.stdoutBytes, 1024);
50
+ strict_1.default.equal(sandbox.input?.stderrBytes, 1024);
51
+ strict_1.default.equal(result.matched, true);
52
+ strict_1.default.deepEqual(result.nextState, { cursor: 2 });
53
+ strict_1.default.equal(result.stderr, 'diagnostic');
54
+ });
55
+ (0, node_test_1.default)('rejects non-single JSON stdout, failed exits, and oversized response fields', async () => {
56
+ const { sandbox, runner: subject } = runner();
57
+ sandbox.result = { exitCode: 0, signal: null, stdout: '{"protocolVersion":1,"matched":false}\n{}', stderr: '' };
58
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), /exactly one JSON/i);
59
+ sandbox.result = { exitCode: 1, signal: null, stdout: '', stderr: 'failure' };
60
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), /exit code 1/i);
61
+ sandbox.result = { exitCode: 0, signal: null, stdout: '{"protocolVersion":1,"matched":true,"payload":{"long":"'.concat('x'.repeat(300), '"}}'), stderr: '' };
62
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), /payload exceeds/i);
63
+ sandbox.result = { exitCode: 0, signal: null, stdout: '{"protocolVersion":1,"matched":true,"extra":1}', stderr: '' };
64
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), /unsupported field/i);
65
+ });
66
+ (0, node_test_1.default)('runtime sandbox loss after probing blocks execution fail-closed', async () => {
67
+ class LostSandbox extends FakeSandbox {
68
+ async run(input) {
69
+ if (input.command.endsWith(' --version'))
70
+ return { exitCode: 0, signal: null, stdout: 'sh 1.0', stderr: '' };
71
+ throw new anthropic_sandbox_runner_js_1.SandboxUnavailableError(['sandbox_runtime']);
72
+ }
73
+ }
74
+ const subject = runner(new LostSandbox()).runner;
75
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), (error) => error instanceof script_protocol_runner_js_1.ScriptProtocolError && error.code === 'sandbox_unavailable' && error.blockAutomation);
76
+ });
77
+ (0, node_test_1.default)('fails closed before execution for non-approved, changed package, or interpreter facts', async () => {
78
+ for (const changed of [version({ status: 'tested' }), version({ packageSha256: 'b'.repeat(64), packagePath: '/packages/a' }), version({ interpreterVersion: 'different' })]) {
79
+ const { sandbox, runner: subject } = runner(new FakeSandbox(), changed);
80
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), (error) => error instanceof script_protocol_runner_js_1.ScriptProtocolError && error.blockAutomation);
81
+ strict_1.default.equal(sandbox.input?.stdin, undefined);
82
+ }
83
+ });
84
+ (0, node_test_1.default)('enforces a 30 second default and five minute maximum', async () => {
85
+ const { sandbox, runner: subject } = runner(new FakeSandbox(), version({ limits: { timeoutMs: 999_999, stdoutBytes: 1024, stderrBytes: 1024, payloadBytes: 256, stateBytes: 256 } }));
86
+ await subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} });
87
+ strict_1.default.equal(sandbox.input?.timeoutMs, 300_000);
88
+ });
89
+ (0, node_test_1.default)('validates an interpreter only through the sandbox boundary', async () => {
90
+ const sandbox = new FakeSandbox();
91
+ const current = version({ interpreterPath: '/untrusted/side-effect-interpreter', interpreterVersion: 'different' });
92
+ const subject = new script_protocol_runner_js_1.ScriptProtocolRunner({ sandbox, getVersion: () => current, verifyPackage: () => undefined, entrypointFor: () => 'run.sh' });
93
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), (error) => error instanceof script_protocol_runner_js_1.ScriptProtocolError && error.code === 'interpreter_mismatch');
94
+ strict_1.default.match(sandbox.input?.command || '', /untrusted\/side-effect-interpreter/);
95
+ strict_1.default.equal(sandbox.input?.stdin, undefined);
96
+ });
97
+ (0, node_test_1.default)('injects only declared env secrets for one process and redacts them from persisted output', async () => {
98
+ const sandbox = new FakeSandbox();
99
+ sandbox.result = { exitCode: 0, signal: null, stdout: '{"protocolVersion":1,"matched":true,"summary":"token-value"}', stderr: 'token-value\u0002' };
100
+ const current = version({ secretRefs: ['env://API_TOKEN'], env: ['API_TOKEN'] });
101
+ const subject = new script_protocol_runner_js_1.ScriptProtocolRunner({
102
+ sandbox, getVersion: () => current, verifyPackage: () => undefined, entrypointFor: () => 'run.sh',
103
+ secretResolver: { get: (name) => name === 'API_TOKEN' ? 'token-value' : undefined },
104
+ });
105
+ const result = await subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} });
106
+ strict_1.default.deepEqual(sandbox.input?.env, { API_TOKEN: 'token-value' });
107
+ strict_1.default.deepEqual(sandbox.input?.allowedEnv, ['API_TOKEN']);
108
+ strict_1.default.equal(result.summary, '[REDACTED]');
109
+ strict_1.default.equal(result.stderr, '');
110
+ });
111
+ (0, node_test_1.default)('fails with secret_unavailable without running undeclared or unavailable secrets', async () => {
112
+ const sandbox = new FakeSandbox();
113
+ const current = version({ secretRefs: ['env://API_TOKEN'], env: [] });
114
+ const subject = new script_protocol_runner_js_1.ScriptProtocolRunner({
115
+ sandbox, getVersion: () => current, verifyPackage: () => undefined, entrypointFor: () => 'run.sh',
116
+ secretResolver: { get: () => 'must-not-read' },
117
+ });
118
+ await strict_1.default.rejects(subject.run({ scriptId: 'script-1', approvedVersionId: 'version-1', evaluationId: 'e', triggeredAt: '2026-07-12T00:00:00.000Z', previousState: {} }), (error) => error instanceof script_protocol_runner_js_1.ScriptProtocolError && error.code === 'secret_unavailable');
119
+ strict_1.default.equal(sandbox.input?.stdin, undefined);
120
+ });