@kafca/agentdock 0.1.44 → 0.1.45

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 (113) hide show
  1. package/README.md +71 -11
  2. package/bin/agentdock.mjs +5 -5
  3. package/dist/renderer/assets/{Badge-Qk9qHovH.js → Badge-BIojwD6s.js} +1 -1
  4. package/dist/renderer/assets/{Chat-BK-fm0Xr.js → Chat-wjBR3XEX.js} +1 -1
  5. package/dist/renderer/assets/{ChatMarkdown-BKdW8OON.js → ChatMarkdown-DqszsPB_.js} +1 -1
  6. package/dist/renderer/assets/Config-BDCHs5A2.js +21 -0
  7. package/dist/renderer/assets/{CronList-Z3TvSRKP.js → CronList-CEWQYTov.js} +1 -1
  8. package/dist/renderer/assets/Dashboard-DrWftfk3.js +26 -0
  9. package/dist/renderer/assets/{EmptyState-kD9yAwtw.js → EmptyState-BqCx1oWr.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-BAMCLrjz.js → KnowledgeDetail-BwK8Gpfa.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-ChvjOfrc.js → KnowledgeHome-hhDTZMDO.js} +3 -3
  12. package/dist/renderer/assets/{Logs-Cv0nth3j.js → Logs-C9xPEhFT.js} +1 -1
  13. package/dist/renderer/assets/{Modal-DwY4lecS.js → Modal-D2U4r2V0.js} +1 -1
  14. package/dist/renderer/assets/{MonitorList-JEvMoWUm.js → MonitorList-CayKbfwJ.js} +1 -1
  15. package/dist/renderer/assets/{Page-CnB9ePsb.js → Page-urf5GcT_.js} +1 -1
  16. package/dist/renderer/assets/ProjectDetail-B1GyAr7a.js +11 -0
  17. package/dist/renderer/assets/{ProjectList-D6CXumR_.js → ProjectList-BoipQypE.js} +1 -1
  18. package/dist/renderer/assets/{Select-zfoksLGB.js → Select-CAscWpy5.js} +1 -1
  19. package/dist/renderer/assets/{SessionChat-DUTh4WoM.js → SessionChat-BAVy5221.js} +1 -1
  20. package/dist/renderer/assets/{SessionList-D9o-lUst.js → SessionList-BZpuMvh0.js} +1 -1
  21. package/dist/renderer/assets/{ThreadChat-BYXlT-Kc.js → ThreadChat-JX7m9biG.js} +1 -1
  22. package/dist/renderer/assets/Workspace-CFW8otjU.js +14 -0
  23. package/dist/renderer/assets/{arrow-left-CNEAuElg.js → arrow-left-7HrNuS-V.js} +1 -1
  24. package/dist/renderer/assets/{book-open-BNJMhNI-.js → book-open-BDoAu82s.js} +1 -1
  25. package/dist/renderer/assets/{check-BnclClRU.js → check-DGlyq4xX.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-Bh53e0O-.js → chevron-down-Bb_3EHq9.js} +1 -1
  27. package/dist/renderer/assets/{circle-ClZTWCWO.js → circle-DIpqZnYw.js} +1 -1
  28. package/dist/renderer/assets/desktop-Ci1fnRDR.js +1 -0
  29. package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-Do_UrC-o.js} +5 -5
  30. package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
  31. package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
  32. package/dist/renderer/assets/{funnel-_5hsz0xI.js → funnel-Bbf0ERLP.js} +1 -1
  33. package/dist/renderer/assets/{heart-u4Q1aOqj.js → heart-CGqU-qfw.js} +1 -1
  34. package/dist/renderer/assets/{index-BNFx4uYv.js → index-Df-Trovt.js} +2 -2
  35. package/dist/renderer/assets/{index-B36eowj-.js → index-DwhTjLA1.js} +36 -36
  36. package/dist/renderer/assets/index-RaTga2BC.css +11 -0
  37. package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
  38. package/dist/renderer/assets/{pencil-DLh9QNmT.js → pencil-CHiy1MvK.js} +1 -1
  39. package/dist/renderer/assets/{play-BDTk9Scm.js → play-CGYnmC4Q.js} +1 -1
  40. package/dist/renderer/assets/{plug-BG0JHW6B.js → plug-Mexu4gyV.js} +1 -1
  41. package/dist/renderer/assets/{plus-BSc8POsz.js → plus-B_Q5-Oj7.js} +1 -1
  42. package/dist/renderer/assets/projects-C6bbkC-O.js +1 -0
  43. package/dist/renderer/assets/{save-D5AO_G6a.js → save-CJ_QFPw0.js} +1 -1
  44. package/dist/renderer/assets/{search-CniPk7Ey.js → search-D8Dkhsyq.js} +1 -1
  45. package/dist/renderer/assets/{server-GsXyQAZm.js → server-Cd96R7WL.js} +1 -1
  46. package/dist/renderer/assets/{session-utils-BDxzgTBC.js → session-utils-BNUbQBfF.js} +1 -1
  47. package/dist/renderer/assets/{sessions-CLcKA1Tt.js → sessions-BACfV8s1.js} +1 -1
  48. package/dist/renderer/assets/{shield-check-4i0E3hmf.js → shield-check-Cnn-yQUA.js} +1 -1
  49. package/dist/renderer/assets/{trash-2-DhtOCsO5.js → trash-2-oWCxwQ9D.js} +1 -1
  50. package/dist/renderer/assets/{user-BtC_pVXV.js → user-BzE9BwsY.js} +1 -1
  51. package/dist/renderer/assets/{wifi-off-BHPzPY_B.js → wifi-off-DHxnNkdY.js} +1 -1
  52. package/dist/renderer/assets/zh-CcJ9vBZj.js +1 -0
  53. package/dist/renderer/assets/zh-TW-elI72AC1.js +1 -0
  54. package/dist/renderer/index.html +2 -2
  55. package/dist-electron/electron/main.js +26 -1
  56. package/dist-electron/packages/core-sdk/src/index.js +28 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +111 -62
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +80 -7
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +78 -5
  60. package/dist-electron/services/local-ai-core/src/acp/store/external-store.js +98 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +33 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/model-provider-store.js +88 -0
  63. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +43 -0
  64. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +2 -119
  65. package/dist-electron/services/local-ai-core/src/agents/pi/provider-materializer.js +148 -0
  66. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +65 -10
  67. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
  68. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +139 -29
  69. package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
  70. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
  71. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
  72. package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
  73. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
  74. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
  75. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
  76. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
  77. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
  78. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
  79. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
  80. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
  82. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
  84. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
  85. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
  86. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
  87. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
  88. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
  89. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
  90. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
  91. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
  92. package/dist-electron/shared/desktop.js +85 -1
  93. package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
  94. package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
  95. package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
  96. package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
  97. package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
  98. package/dist-electron/tests/integration/lark-gateway.test.js +59 -0
  99. package/dist-electron/tests/integration/local-core-refactor.test.js +183 -20
  100. package/dist-electron/tests/integration/local-core-routes.test.js +34 -0
  101. package/package.json +2 -3
  102. package/dist/renderer/assets/Config-B93CQGvY.js +0 -16
  103. package/dist/renderer/assets/Dashboard-Ch3QkWwf.js +0 -26
  104. package/dist/renderer/assets/ProjectDetail-DJa3sMmU.js +0 -11
  105. package/dist/renderer/assets/Workspace-BrhSwRQ-.js +0 -14
  106. package/dist/renderer/assets/desktop-C5vBT0yM.js +0 -1
  107. package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
  108. package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
  109. package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
  110. package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
  111. package/dist/renderer/assets/projects-DwfZwxDW.js +0 -1
  112. package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
  113. package/dist/renderer/assets/zh-TW-BtRwreY_.js +0 -1
@@ -0,0 +1,541 @@
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 node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const sandbox_config_js_1 = require("../../services/local-ai-core/src/sandbox/sandbox-config.js");
12
+ const workspace_route_config_js_1 = require("../../services/local-ai-core/src/router/workspace-route-config.js");
13
+ const sandbox_manager_js_1 = require("../../services/local-ai-core/src/sandbox/sandbox-manager.js");
14
+ const sandbox_manager_js_2 = require("../../services/local-ai-core/src/sandbox/sandbox-manager.js");
15
+ const config_migration_js_1 = require("../../services/local-ai-core/src/runtime/config-migration.js");
16
+ const deployment_diagnostics_js_1 = require("../../services/local-ai-core/src/runtime/deployment-diagnostics.js");
17
+ function configState(path) {
18
+ return {
19
+ path,
20
+ exists: true,
21
+ raw: '',
22
+ parsed: { projects: [] },
23
+ };
24
+ }
25
+ function project(sandbox) {
26
+ return {
27
+ name: 'my-project',
28
+ agent: {
29
+ type: 'pi',
30
+ options: {
31
+ work_dir: '/workspace/source',
32
+ sandbox,
33
+ },
34
+ providers: [],
35
+ },
36
+ platforms: [],
37
+ };
38
+ }
39
+ function launchConfig() {
40
+ return {
41
+ workspaceId: 'my-project',
42
+ agentType: 'pi',
43
+ workDir: '/workspace/source',
44
+ command: process.execPath,
45
+ args: ['/host/node_modules/pi-acp/dist/index.js'],
46
+ env: {
47
+ PI_ACP_PI_COMMAND: '/host/node_modules/pi/dist/cli.js',
48
+ OPENAI_API_KEY: 'secret',
49
+ },
50
+ model: 'gpt-5',
51
+ };
52
+ }
53
+ (0, node_test_1.default)('sandbox config normalizes project scoped Pi defaults', () => {
54
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
55
+ try {
56
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
57
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
58
+ project: project({ enabled: true }),
59
+ launchConfig: launchConfig(),
60
+ });
61
+ strict_1.default.equal(sandbox?.enabled, true);
62
+ strict_1.default.equal(sandbox?.serverUrl, 'http://127.0.0.1:8080');
63
+ strict_1.default.equal(sandbox?.image, 'agentdock/pi-acp:local');
64
+ strict_1.default.equal(sandbox?.transport, 'http-ndjson');
65
+ strict_1.default.equal(sandbox?.stateScope, 'project');
66
+ strict_1.default.equal(sandbox?.lifecycle, 'per_thread');
67
+ strict_1.default.equal(sandbox?.idleSeconds, 900);
68
+ strict_1.default.equal(sandbox?.warmPoolSize, 0);
69
+ strict_1.default.equal(sandbox?.workspaceMountPath, '/workspace');
70
+ strict_1.default.equal(sandbox?.stateMountPath, '/agent-state');
71
+ strict_1.default.deepEqual(sandbox?.stateMount, {
72
+ userId: 'local',
73
+ projectId: 'my-project',
74
+ agentType: 'pi',
75
+ scope: 'project',
76
+ hostPath: sandbox?.stateHostPath,
77
+ containerPath: '/agent-state',
78
+ });
79
+ strict_1.default.equal(sandbox?.acpPort, 8080);
80
+ strict_1.default.match(sandbox?.stateHostPath || '', /sandbox-state[/\\]users[/\\]local[/\\]projects[/\\]my-project[/\\]agents[/\\]pi[/\\]state$/);
81
+ strict_1.default.equal(sandbox?.runtimeCommand, '/usr/local/bin/pi-acp');
82
+ strict_1.default.deepEqual(sandbox?.runtimeArgs, []);
83
+ strict_1.default.equal(sandbox?.runtimeEnv.PI_CODING_AGENT_DIR, '/agent-state/pi');
84
+ strict_1.default.equal(sandbox?.runtimeEnv.PI_ACP_PI_COMMAND, '/usr/local/bin/pi');
85
+ strict_1.default.equal(sandbox?.runtimeEnv.OPENAI_API_KEY, 'secret');
86
+ }
87
+ finally {
88
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
89
+ }
90
+ });
91
+ (0, node_test_1.default)('sandbox config supports user scoped state shared across projects', () => {
92
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
93
+ try {
94
+ const first = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
95
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
96
+ project: {
97
+ ...project({ enabled: true, state_scope: 'user' }),
98
+ name: 'first-project',
99
+ agent: {
100
+ ...project({ enabled: true, state_scope: 'user' }).agent,
101
+ options: {
102
+ work_dir: '/workspace/source',
103
+ user_id: 'alice@example.com',
104
+ sandbox: { enabled: true, state_scope: 'user' },
105
+ },
106
+ },
107
+ },
108
+ launchConfig: { ...launchConfig(), workspaceId: 'first-project' },
109
+ });
110
+ const second = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
111
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
112
+ project: {
113
+ ...project({ enabled: true, state_scope: 'user' }),
114
+ name: 'second-project',
115
+ agent: {
116
+ ...project({ enabled: true, state_scope: 'user' }).agent,
117
+ options: {
118
+ work_dir: '/workspace/source',
119
+ user_id: 'alice@example.com',
120
+ sandbox: { enabled: true, state_scope: 'user' },
121
+ },
122
+ },
123
+ },
124
+ launchConfig: { ...launchConfig(), workspaceId: 'second-project' },
125
+ });
126
+ strict_1.default.ok(first);
127
+ strict_1.default.ok(second);
128
+ strict_1.default.equal(first.stateHostPath, second.stateHostPath);
129
+ strict_1.default.match(first.stateHostPath || '', /sandbox-state[/\\]users[/\\]alice-example.com[/\\]agents[/\\]pi[/\\]state$/);
130
+ strict_1.default.equal(first.stateMount?.scope, 'user');
131
+ }
132
+ finally {
133
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
134
+ }
135
+ });
136
+ (0, node_test_1.default)('sandbox config supports thread scoped state materialization', () => {
137
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
138
+ try {
139
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
140
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
141
+ project: project({ enabled: true, state_scope: 'thread' }),
142
+ launchConfig: launchConfig(),
143
+ });
144
+ strict_1.default.ok(sandbox);
145
+ strict_1.default.match(sandbox.stateHostPath || '', /\$\{LOCAL_AI_THREAD_ID\}/);
146
+ const statePath = (0, sandbox_config_js_1.resolveSandboxStateHostPath)(sandbox, 'thread:abc/def', 'run-1');
147
+ strict_1.default.match(statePath, /threads[/\\]thread-abc-def[/\\]state$/);
148
+ }
149
+ finally {
150
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
151
+ }
152
+ });
153
+ (0, node_test_1.default)('sandbox proxy env stores serialized launch config', () => {
154
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
155
+ try {
156
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
157
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
158
+ project: project({ enabled: true }),
159
+ launchConfig: launchConfig(),
160
+ });
161
+ strict_1.default.ok(sandbox);
162
+ const env = (0, sandbox_config_js_1.sandboxProxyLaunchEnv)(sandbox);
163
+ strict_1.default.equal(JSON.parse(env.AGENTDOCK_SANDBOX_CONFIG).image, 'agentdock/pi-acp:local');
164
+ }
165
+ finally {
166
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
167
+ }
168
+ });
169
+ (0, node_test_1.default)('workspace route launches sandbox proxy when sandbox is enabled', () => {
170
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
171
+ try {
172
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'runtime'), { recursive: true });
173
+ const route = (0, workspace_route_config_js_1.toLocalCoreProjectConfig)(configState((0, node_path_1.join)(root, 'runtime', 'config.toml')), project({
174
+ enabled: true,
175
+ }));
176
+ strict_1.default.equal(route.command, process.execPath);
177
+ strict_1.default.match(route.args[0], /sandbox-stdio-proxy\.js$/);
178
+ strict_1.default.equal(route.workDir, '/workspace/source');
179
+ strict_1.default.equal(route.execution?.mode, 'sandbox');
180
+ strict_1.default.equal(route.execution?.transport, 'sandbox-http-ndjson-stdio-proxy');
181
+ strict_1.default.equal(route.execution?.sandbox?.stateScope, 'project');
182
+ strict_1.default.equal(route.execution?.sandbox?.transport, 'http-ndjson');
183
+ strict_1.default.equal(route.sandbox?.workspaceHostPath, '/workspace/source');
184
+ strict_1.default.equal(route.sandbox?.proxyCwd, (0, node_path_1.join)(root, 'runtime'));
185
+ strict_1.default.equal(route.sandbox?.image, 'agentdock/pi-acp:local');
186
+ strict_1.default.equal(JSON.parse(route.env.AGENTDOCK_SANDBOX_CONFIG).image, 'agentdock/pi-acp:local');
187
+ }
188
+ finally {
189
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
190
+ }
191
+ });
192
+ (0, node_test_1.default)('legacy sandbox image overrides without transport use the generic HTTP bridge', () => {
193
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
194
+ try {
195
+ const route = (0, workspace_route_config_js_1.toLocalCoreProjectConfig)(configState((0, node_path_1.join)(root, 'runtime', 'config.toml')), project({
196
+ enabled: true,
197
+ image: 'agentdock/pi-acp:legacy',
198
+ }));
199
+ strict_1.default.equal(route.execution?.transport, 'sandbox-http-ndjson-stdio-proxy');
200
+ strict_1.default.equal(route.sandbox?.transport, 'http-ndjson');
201
+ strict_1.default.equal(route.sandbox?.image, 'agentdock/pi-acp:legacy');
202
+ }
203
+ finally {
204
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
205
+ }
206
+ });
207
+ (0, node_test_1.default)('sandbox runtime image can override the stdio runtime command behind the generic bridge', () => {
208
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
209
+ try {
210
+ const state = configState((0, node_path_1.join)(root, 'runtime', 'config.toml'));
211
+ state.parsed = {
212
+ projects: [],
213
+ sandbox_runtime_images: [{
214
+ id: 'fake-acp',
215
+ agent_type: 'pi',
216
+ image: 'agentdock/fake-acp:local',
217
+ transport: 'http-ndjson',
218
+ acp_port: 8080,
219
+ entrypoint: ['node', '/opt/agentdock/acp-bridge.mjs'],
220
+ runtime_command: '/usr/local/bin/node',
221
+ runtime_args: ['/opt/agentdock/fake-acp.mjs'],
222
+ }],
223
+ };
224
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
225
+ configState: state,
226
+ project: project({ enabled: true, runtime_image_id: 'fake-acp' }),
227
+ launchConfig: launchConfig(),
228
+ });
229
+ strict_1.default.equal(sandbox?.runtimeCommand, '/usr/local/bin/node');
230
+ strict_1.default.deepEqual(sandbox?.runtimeArgs, ['/opt/agentdock/fake-acp.mjs']);
231
+ strict_1.default.equal(sandbox?.transport, 'http-ndjson');
232
+ }
233
+ finally {
234
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
235
+ }
236
+ });
237
+ (0, node_test_1.default)('sandbox proxy cwd is independent from host workspace path', () => {
238
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
239
+ const previous = process.env.AI_WORKSTATION_USER_DATA_DIR;
240
+ try {
241
+ process.env.AI_WORKSTATION_USER_DATA_DIR = root;
242
+ const route = (0, workspace_route_config_js_1.toLocalCoreProjectConfig)(configState((0, node_path_1.join)(root, 'runtime', 'config.toml')), {
243
+ ...project({ enabled: true }),
244
+ agent: {
245
+ ...project({ enabled: true }).agent,
246
+ options: {
247
+ work_dir: '/Users/example/persistent-project',
248
+ sandbox: { enabled: true },
249
+ },
250
+ },
251
+ });
252
+ strict_1.default.equal(route.workDir, '/Users/example/persistent-project');
253
+ strict_1.default.equal(route.sandbox?.workspaceHostPath, '/Users/example/persistent-project');
254
+ strict_1.default.equal(route.sandbox?.workspaceMountPath, '/workspace');
255
+ strict_1.default.equal(route.sandbox?.proxyCwd, root);
256
+ strict_1.default.notEqual(route.sandbox?.proxyCwd, route.workDir);
257
+ }
258
+ finally {
259
+ if (previous === undefined) {
260
+ delete process.env.AI_WORKSTATION_USER_DATA_DIR;
261
+ }
262
+ else {
263
+ process.env.AI_WORKSTATION_USER_DATA_DIR = previous;
264
+ }
265
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
266
+ }
267
+ });
268
+ (0, node_test_1.default)('workspace route records local execution when sandbox is disabled', () => {
269
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
270
+ try {
271
+ const route = (0, workspace_route_config_js_1.toLocalCoreProjectConfig)(configState((0, node_path_1.join)(root, 'runtime', 'config.toml')), project({
272
+ enabled: false,
273
+ }));
274
+ strict_1.default.equal(route.execution?.mode, 'local');
275
+ strict_1.default.equal(route.execution?.transport, 'stdio');
276
+ strict_1.default.equal(route.sandbox, undefined);
277
+ }
278
+ finally {
279
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
280
+ }
281
+ });
282
+ (0, node_test_1.default)('desktop config migration normalizes user id, DeepSeek provider, and sandbox defaults', () => {
283
+ const migrated = (0, config_migration_js_1.migrateDesktopConnectConfig)({
284
+ projects: [{
285
+ name: 'cloud-project',
286
+ agent: {
287
+ type: 'pi',
288
+ options: {
289
+ tenant_id: 'team-a',
290
+ sandbox: { enabled: true },
291
+ },
292
+ providers: [{
293
+ name: 'deepseek-v4-flash',
294
+ api_key: 'secret',
295
+ model: 'deepseek-v4-flash',
296
+ }],
297
+ },
298
+ platforms: [],
299
+ }],
300
+ });
301
+ strict_1.default.equal(migrated.changed, true);
302
+ strict_1.default.equal(migrated.config.config_version, 2);
303
+ const projectConfig = migrated.config.projects?.[0];
304
+ strict_1.default.equal(projectConfig?.agent.options?.user_id, 'team-a');
305
+ strict_1.default.equal((projectConfig?.agent.options).tenant_id, undefined);
306
+ strict_1.default.equal(projectConfig?.agent.providers?.[0]?.name, 'deepseek');
307
+ strict_1.default.equal(projectConfig?.agent.options?.sandbox?.provider_id, 'opensandbox-default');
308
+ strict_1.default.equal(projectConfig?.agent.options?.sandbox?.runtime_image_id, 'pi-acp-local');
309
+ strict_1.default.equal(projectConfig?.agent.options?.sandbox?.sandbox_lifecycle, 'per_thread');
310
+ strict_1.default.equal(projectConfig?.agent.options?.sandbox?.idle_seconds, 900);
311
+ strict_1.default.equal(migrated.config.sandbox_providers?.[0]?.server_url, 'http://127.0.0.1:8080');
312
+ strict_1.default.equal(migrated.config.sandbox_runtime_images?.[0]?.image, 'agentdock/pi-acp:local');
313
+ strict_1.default.equal(migrated.config.sandbox_runtime_images?.[0]?.transport, 'http-ndjson');
314
+ });
315
+ (0, node_test_1.default)('desktop config migration upgrades built-in Pi sandbox image to HTTP transport', () => {
316
+ const migrated = (0, config_migration_js_1.migrateDesktopConnectConfig)({
317
+ config_version: 2,
318
+ projects: [{
319
+ name: 'cloud-project',
320
+ agent: {
321
+ type: 'pi',
322
+ options: {
323
+ sandbox: {
324
+ enabled: true,
325
+ provider_id: 'opensandbox-default',
326
+ runtime_image_id: 'pi-acp-local',
327
+ },
328
+ },
329
+ },
330
+ platforms: [],
331
+ }],
332
+ sandbox_runtime_images: [{
333
+ id: 'pi-acp-local',
334
+ agent_type: 'pi',
335
+ image: 'agentdock/pi-acp:local',
336
+ acp_port: 8080,
337
+ entrypoint: ['node', '/opt/agentdock/acp-bridge.mjs'],
338
+ workspace_mount_path: '/workspace',
339
+ state_mount_path: '/agent-state',
340
+ }],
341
+ });
342
+ strict_1.default.equal(migrated.changed, true);
343
+ strict_1.default.equal(migrated.config.sandbox_runtime_images?.[0]?.transport, 'http-ndjson');
344
+ });
345
+ (0, node_test_1.default)('OpenSandbox create input includes volumes, resources, env, and metadata', () => {
346
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
347
+ try {
348
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
349
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
350
+ project: project({ enabled: true, cpu: '2000m', memory: '4Gi' }),
351
+ launchConfig: launchConfig(),
352
+ });
353
+ strict_1.default.ok(sandbox);
354
+ const input = (0, sandbox_manager_js_1.buildOpenSandboxCreateInput)(sandbox, {
355
+ LOCAL_AI_WORKSPACE_ID: 'my-project',
356
+ LOCAL_AI_THREAD_ID: 'thread-1',
357
+ AGENTDOCK_SANDBOX_RUN_ID: 'run-1',
358
+ });
359
+ strict_1.default.equal(input.image, 'agentdock/pi-acp:local');
360
+ strict_1.default.deepEqual(input.ports, [8080]);
361
+ strict_1.default.equal(input.cpu, '2000m');
362
+ strict_1.default.equal(input.memory, '4Gi');
363
+ strict_1.default.deepEqual(input.volumes.map((volume) => volume.container), ['/workspace', '/agent-state']);
364
+ strict_1.default.equal(input.volumes[0]?.host, '/workspace/source');
365
+ strict_1.default.equal(input.env.AGENTDOCK_ACP_COMMAND, '/usr/local/bin/pi-acp');
366
+ strict_1.default.equal(input.env.LOCAL_AI_WORKSPACE_PATH, '/workspace');
367
+ strict_1.default.equal(input.env.AGENTDOCK_ACP_STATE_DIRS, '["/agent-state","/agent-state/pi"]');
368
+ strict_1.default.equal(input.metadata.userId, 'local');
369
+ strict_1.default.equal(input.metadata.runId, 'run-1');
370
+ strict_1.default.equal(input.metadata.agentType, 'pi');
371
+ }
372
+ finally {
373
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
374
+ }
375
+ });
376
+ (0, node_test_1.default)('OpenSandbox metadata is Kubernetes label safe while env keeps raw ids', () => {
377
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
378
+ try {
379
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
380
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
381
+ project: project({ enabled: true }),
382
+ launchConfig: launchConfig(),
383
+ });
384
+ strict_1.default.ok(sandbox);
385
+ const rawThreadId = 'my-project::ca0ab5c3-8b05-4460-a66f-0bf2d1361968';
386
+ const rawRunId = `run:${rawThreadId}:1778671476585`;
387
+ const input = (0, sandbox_manager_js_1.buildOpenSandboxCreateInput)(sandbox, {
388
+ LOCAL_AI_WORKSPACE_ID: 'my-project',
389
+ LOCAL_AI_THREAD_ID: rawThreadId,
390
+ AGENTDOCK_SANDBOX_RUN_ID: rawRunId,
391
+ });
392
+ strict_1.default.equal(input.env.LOCAL_AI_THREAD_ID, rawThreadId);
393
+ strict_1.default.equal(input.env.AGENTDOCK_SANDBOX_RUN_ID, rawRunId);
394
+ strict_1.default.match(input.metadata.threadId, /^[a-zA-Z0-9]([-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9])?$/);
395
+ strict_1.default.match(input.metadata.runId, /^[a-zA-Z0-9]([-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9])?$/);
396
+ strict_1.default.ok(input.metadata.threadId.length <= 63);
397
+ strict_1.default.ok(input.metadata.runId.length <= 63);
398
+ }
399
+ finally {
400
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
401
+ }
402
+ });
403
+ (0, node_test_1.default)('normalizeEndpoint keeps HTTP endpoints for the sandbox ACP bridge', () => {
404
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('http://127.0.0.1:3000'), 'http://127.0.0.1:3000');
405
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('https://example.test/acp'), 'https://example.test/acp');
406
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('127.0.0.1:3000'), 'http://127.0.0.1:3000');
407
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('127.0.0.1:3000', 'host.docker.internal'), 'http://host.docker.internal:3000/');
408
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('http://127.0.0.1:3000', '', 'http-ndjson'), 'http://127.0.0.1:3000');
409
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('https://example.test/acp', '', 'http-ndjson'), 'https://example.test/acp');
410
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('127.0.0.1:3000', '', 'http-ndjson'), 'http://127.0.0.1:3000');
411
+ strict_1.default.equal((0, sandbox_manager_js_1.normalizeEndpoint)('127.0.0.1:3000', 'host.docker.internal', 'http-ndjson'), 'http://host.docker.internal:3000/');
412
+ });
413
+ (0, node_test_1.default)('OpenSandbox local compose auth falls back to the default local API key', () => {
414
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
415
+ try {
416
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
417
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
418
+ project: project({ enabled: true }),
419
+ launchConfig: launchConfig(),
420
+ });
421
+ strict_1.default.ok(sandbox);
422
+ strict_1.default.equal((0, sandbox_manager_js_1.resolveOpenSandboxApiKey)(sandbox, {}), 'agentdock-local');
423
+ strict_1.default.equal((0, sandbox_manager_js_1.resolveOpenSandboxApiKey)({ ...sandbox, serverUrl: 'https://sandbox.example.com' }, {}), '');
424
+ strict_1.default.equal((0, sandbox_manager_js_1.resolveOpenSandboxApiKey)(sandbox, { OPEN_SANDBOX_API_KEY: 'custom-key' }), 'custom-key');
425
+ }
426
+ finally {
427
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
428
+ }
429
+ });
430
+ (0, node_test_1.default)('OpenSandbox default server URL can be overridden for compose networks', () => {
431
+ strict_1.default.equal((0, sandbox_config_js_1.defaultOpenSandboxServerUrl)({}), 'http://127.0.0.1:8080');
432
+ strict_1.default.equal((0, sandbox_config_js_1.defaultOpenSandboxServerUrl)({ AGENTDOCK_OPENSANDBOX_SERVER_URL: 'http://opensandbox-server:8080/' }), 'http://opensandbox-server:8080');
433
+ });
434
+ (0, node_test_1.default)('sandbox state host root can be overridden for compose host mounts', () => {
435
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
436
+ const previous = process.env[sandbox_config_js_1.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV];
437
+ const stateRoot = (0, node_path_1.join)(root, 'host-state');
438
+ try {
439
+ process.env[sandbox_config_js_1.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV] = stateRoot;
440
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
441
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
442
+ project: project({ enabled: true, state_scope: 'project' }),
443
+ launchConfig: launchConfig(),
444
+ });
445
+ strict_1.default.ok(sandbox);
446
+ strict_1.default.ok(sandbox.stateHostPath?.startsWith(stateRoot));
447
+ }
448
+ finally {
449
+ if (previous === undefined) {
450
+ delete process.env[sandbox_config_js_1.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV];
451
+ }
452
+ else {
453
+ process.env[sandbox_config_js_1.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV] = previous;
454
+ }
455
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
456
+ }
457
+ });
458
+ (0, node_test_1.default)('deployment diagnostics delegate compose sandbox workspace paths to OpenSandbox', async () => {
459
+ const originalFetch = globalThis.fetch;
460
+ try {
461
+ globalThis.fetch = (async () => new Response('{}', { status: 200 }));
462
+ const result = await (0, deployment_diagnostics_js_1.runDeploymentDiagnostics)({
463
+ config: {
464
+ deployment_profile: 'docker-compose',
465
+ sandbox_providers: [{
466
+ id: 'opensandbox-default',
467
+ type: 'opensandbox',
468
+ name: 'OpenSandbox',
469
+ server_url: 'http://opensandbox-server:8080',
470
+ api_key_env: 'OPEN_SANDBOX_API_KEY',
471
+ }],
472
+ projects: [{
473
+ name: 'remote-project',
474
+ agent: {
475
+ type: 'pi',
476
+ options: {
477
+ work_dir: '/Users/example/persistent-project',
478
+ sandbox: { enabled: true },
479
+ },
480
+ },
481
+ platforms: [],
482
+ }],
483
+ },
484
+ env: {
485
+ AI_WORKSTATION_HOST: '0.0.0.0',
486
+ OPEN_SANDBOX_HOST_MOUNT_ALLOWLIST: '/Users,/tmp',
487
+ },
488
+ });
489
+ const workspaceCheck = result.checks.find((check) => check.id === 'workspace.paths');
490
+ strict_1.default.equal(workspaceCheck?.status, 'pass');
491
+ strict_1.default.equal(workspaceCheck?.summary, 'Sandbox workspace host paths are delegated to OpenSandbox.');
492
+ }
493
+ finally {
494
+ globalThis.fetch = originalFetch;
495
+ }
496
+ });
497
+ (0, node_test_1.default)('sandbox manager deletes sandbox and run scoped state on cleanup', async () => {
498
+ const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
499
+ try {
500
+ const sandbox = (0, sandbox_config_js_1.normalizeSandboxLaunchConfig)({
501
+ configState: configState((0, node_path_1.join)(root, 'runtime', 'config.toml')),
502
+ project: project({ enabled: true, state_scope: 'run' }),
503
+ launchConfig: launchConfig(),
504
+ });
505
+ strict_1.default.ok(sandbox);
506
+ const deleted = [];
507
+ const fakeClient = {
508
+ async health() { },
509
+ async createSandbox() {
510
+ return { id: 'sandbox-1', status: { state: 'Running' } };
511
+ },
512
+ async getSandbox() {
513
+ return { id: 'sandbox-1', status: { state: 'Running' } };
514
+ },
515
+ async getEndpoint() {
516
+ return { endpoint: 'http://127.0.0.1:39231' };
517
+ },
518
+ async deleteSandbox(id) {
519
+ deleted.push(id);
520
+ },
521
+ };
522
+ const manager = new sandbox_manager_js_2.SandboxManager({
523
+ config: sandbox,
524
+ env: {
525
+ LOCAL_AI_THREAD_ID: 'thread-1',
526
+ LOCAL_AI_WORKSPACE_ID: 'my-project',
527
+ AGENTDOCK_SANDBOX_RUN_ID: 'run-1',
528
+ },
529
+ client: fakeClient,
530
+ });
531
+ const run = await manager.start();
532
+ strict_1.default.equal(run.sandboxId, 'sandbox-1');
533
+ strict_1.default.equal((0, node_fs_1.existsSync)(run.stateHostPath), true);
534
+ await manager.cleanup(run);
535
+ strict_1.default.deepEqual(deleted, ['sandbox-1']);
536
+ strict_1.default.equal((0, node_fs_1.existsSync)(run.stateHostPath), false);
537
+ }
538
+ finally {
539
+ (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
540
+ }
541
+ });