@kafca/agentdock 0.1.44 → 0.1.46

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 (126) hide show
  1. package/README.md +76 -11
  2. package/bin/agentdock.mjs +5 -5
  3. package/dist/renderer/assets/{Badge-Qk9qHovH.js → Badge-BIesaDYG.js} +1 -1
  4. package/dist/renderer/assets/Chat-CpataoDh.js +2 -0
  5. package/dist/renderer/assets/{ChatMarkdown-BKdW8OON.js → ChatMarkdown-_Mdmltgv.js} +1 -1
  6. package/dist/renderer/assets/Config-Pe5-zKZk.js +21 -0
  7. package/dist/renderer/assets/CronList-BnaOSPT8.js +1 -0
  8. package/dist/renderer/assets/Dashboard-D05l8GKE.js +26 -0
  9. package/dist/renderer/assets/{EmptyState-kD9yAwtw.js → EmptyState-BDBavDll.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-BAMCLrjz.js → KnowledgeDetail-DvR798l_.js} +2 -2
  11. package/dist/renderer/assets/KnowledgeHome-r2ZuvU8-.js +11 -0
  12. package/dist/renderer/assets/Logs-BaljtmAV.js +1 -0
  13. package/dist/renderer/assets/{Modal-DwY4lecS.js → Modal-CXhD_OWR.js} +1 -1
  14. package/dist/renderer/assets/MonitorList-D7tyipbx.js +1 -0
  15. package/dist/renderer/assets/{Page-CnB9ePsb.js → Page-KUuFYgjg.js} +1 -1
  16. package/dist/renderer/assets/ProjectDetail-Ddq94-yu.js +11 -0
  17. package/dist/renderer/assets/ProjectList-CbdHd5Ev.js +1 -0
  18. package/dist/renderer/assets/{Select-zfoksLGB.js → Select-Djc8BXMC.js} +1 -1
  19. package/dist/renderer/assets/SessionChat-DOy0iKQR.js +1 -0
  20. package/dist/renderer/assets/SessionList-DHgl1s1D.js +1 -0
  21. package/dist/renderer/assets/ThreadChat-I7dYibDc.js +23 -0
  22. package/dist/renderer/assets/Workspace-DTocqY-F.js +19 -0
  23. package/dist/renderer/assets/{arrow-left-CNEAuElg.js → arrow-left-CXyya3hP.js} +1 -1
  24. package/dist/renderer/assets/{book-open-BNJMhNI-.js → book-open-9n4Kxc7E.js} +1 -1
  25. package/dist/renderer/assets/bot-C9HBk5XH.js +6 -0
  26. package/dist/renderer/assets/{check-BnclClRU.js → check-CutSg9a5.js} +1 -1
  27. package/dist/renderer/assets/{chevron-down-Bh53e0O-.js → chevron-down-DrydR4i3.js} +1 -1
  28. package/dist/renderer/assets/{circle-ClZTWCWO.js → circle-BEjtahw6.js} +1 -1
  29. package/dist/renderer/assets/desktop-DATgk_ka.js +1 -0
  30. package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-JQkhNOF4.js} +5 -5
  31. package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
  32. package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
  33. package/dist/renderer/assets/{funnel-_5hsz0xI.js → funnel-BhqZpS4f.js} +1 -1
  34. package/dist/renderer/assets/{heart-u4Q1aOqj.js → heart-Dp8Vs-7_.js} +1 -1
  35. package/dist/renderer/assets/{index-BNFx4uYv.js → index-BKpv0P0s.js} +2 -2
  36. package/dist/renderer/assets/index-CO4fGqmv.css +11 -0
  37. package/dist/renderer/assets/{index-B36eowj-.js → index-cI3pWBnN.js} +36 -36
  38. package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
  39. package/dist/renderer/assets/{pencil-DLh9QNmT.js → pencil-ywcJzeOo.js} +1 -1
  40. package/dist/renderer/assets/{play-BDTk9Scm.js → play-AtN492Cx.js} +1 -1
  41. package/dist/renderer/assets/{plus-BSc8POsz.js → plus-BEj14BBV.js} +1 -1
  42. package/dist/renderer/assets/projects-Clar8lgb.js +1 -0
  43. package/dist/renderer/assets/save-D6ov4CNv.js +11 -0
  44. package/dist/renderer/assets/{search-CniPk7Ey.js → search-C3bvw7AR.js} +1 -1
  45. package/dist/renderer/assets/{server-GsXyQAZm.js → server-C1Uyedyx.js} +1 -1
  46. package/dist/renderer/assets/{session-utils-BDxzgTBC.js → session-utils-DpRwJ6Nh.js} +1 -1
  47. package/dist/renderer/assets/{sessions-CLcKA1Tt.js → sessions-BWmAppwK.js} +1 -1
  48. package/dist/renderer/assets/{shield-check-4i0E3hmf.js → shield-check-CVx1mEs2.js} +1 -1
  49. package/dist/renderer/assets/{trash-2-DhtOCsO5.js → trash-2-o-heXOgs.js} +1 -1
  50. package/dist/renderer/assets/user-dPOzUqIc.js +6 -0
  51. package/dist/renderer/assets/{wifi-off-BHPzPY_B.js → wifi-off-Fz5c3_x_.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/inbound.js +55 -1
  67. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +65 -10
  68. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
  69. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +139 -29
  70. package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
  71. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
  72. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
  73. package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
  74. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
  75. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
  76. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
  77. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
  78. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
  79. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
  80. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
  81. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
  83. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
  85. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
  86. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
  87. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
  88. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
  89. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
  90. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
  91. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
  92. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
  93. package/dist-electron/shared/desktop.js +85 -1
  94. package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
  95. package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
  96. package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
  97. package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
  98. package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
  99. package/dist-electron/tests/integration/lark-gateway.test.js +139 -0
  100. package/dist-electron/tests/integration/local-core-refactor.test.js +183 -20
  101. package/dist-electron/tests/integration/local-core-routes.test.js +34 -0
  102. package/package.json +2 -3
  103. package/dist/renderer/assets/Chat-BK-fm0Xr.js +0 -2
  104. package/dist/renderer/assets/Config-B93CQGvY.js +0 -16
  105. package/dist/renderer/assets/CronList-Z3TvSRKP.js +0 -1
  106. package/dist/renderer/assets/Dashboard-Ch3QkWwf.js +0 -26
  107. package/dist/renderer/assets/KnowledgeHome-ChvjOfrc.js +0 -11
  108. package/dist/renderer/assets/Logs-Cv0nth3j.js +0 -1
  109. package/dist/renderer/assets/MonitorList-JEvMoWUm.js +0 -1
  110. package/dist/renderer/assets/ProjectDetail-DJa3sMmU.js +0 -11
  111. package/dist/renderer/assets/ProjectList-D6CXumR_.js +0 -1
  112. package/dist/renderer/assets/SessionChat-DUTh4WoM.js +0 -1
  113. package/dist/renderer/assets/SessionList-D9o-lUst.js +0 -1
  114. package/dist/renderer/assets/ThreadChat-BYXlT-Kc.js +0 -23
  115. package/dist/renderer/assets/Workspace-BrhSwRQ-.js +0 -14
  116. package/dist/renderer/assets/desktop-C5vBT0yM.js +0 -1
  117. package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
  118. package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
  119. package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
  120. package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
  121. package/dist/renderer/assets/plug-BG0JHW6B.js +0 -6
  122. package/dist/renderer/assets/projects-DwfZwxDW.js +0 -1
  123. package/dist/renderer/assets/save-D5AO_G6a.js +0 -6
  124. package/dist/renderer/assets/user-BtC_pVXV.js +0 -11
  125. package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
  126. package/dist/renderer/assets/zh-TW-BtRwreY_.js +0 -1
@@ -10,8 +10,10 @@ const local_core_controller_js_1 = require("./local-core-controller.js");
10
10
  const server_js_1 = require("./server.js");
11
11
  async function main() {
12
12
  const userDataPath = node_process_1.default.env.AI_WORKSTATION_USER_DATA_DIR?.trim() || (0, node_path_1.join)(node_process_1.default.cwd(), '.agentdock-core');
13
+ const host = node_process_1.default.env.AI_WORKSTATION_HOST?.trim() || '127.0.0.1';
13
14
  (0, node_fs_1.mkdirSync)(userDataPath, { recursive: true });
14
15
  const controller = new local_core_controller_js_1.LocalCoreController(userDataPath);
16
+ const server = new server_js_1.LocalAiCoreServer(controller, { host });
15
17
  controller.on('logs', (line) => {
16
18
  if (!line) {
17
19
  return;
@@ -21,9 +23,8 @@ async function main() {
21
23
  controller.on('bridge', (event) => {
22
24
  node_process_1.default.stdout.write(`[local-ai-core bridge] ${JSON.stringify(event)}\n`);
23
25
  });
24
- await controller.init();
25
- const server = new server_js_1.LocalAiCoreServer(controller);
26
26
  await server.start();
27
+ await controller.init();
27
28
  node_process_1.default.on('SIGINT', async () => {
28
29
  await server.stop();
29
30
  await controller.close();
@@ -35,4 +36,13 @@ async function main() {
35
36
  node_process_1.default.exit(0);
36
37
  });
37
38
  }
38
- void main();
39
+ void main().catch((error) => {
40
+ const code = typeof error === 'object' && error ? error.code : '';
41
+ if (code === 'EADDRINUSE') {
42
+ node_process_1.default.stderr.write('[local-ai-core] Port 9831 is already in use; another Local AI Core process is listening.\n');
43
+ node_process_1.default.exit(0);
44
+ return;
45
+ }
46
+ node_process_1.default.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
47
+ node_process_1.default.exit(1);
48
+ });
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenSandboxClient = void 0;
4
+ const local_core_errors_js_1 = require("../kernel/local-core-errors.js");
5
+ class OpenSandboxClient {
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ async health() {
11
+ return this.request('GET', '/health');
12
+ }
13
+ async createSandbox(input) {
14
+ const response = await this.request('POST', '/v1/sandboxes', {
15
+ image: {
16
+ uri: input.image,
17
+ },
18
+ entrypoint: input.entrypoint,
19
+ env: input.env,
20
+ metadata: input.metadata,
21
+ volumes: input.volumes.map((volume, index) => ({
22
+ name: `agentdock-${index + 1}`,
23
+ host: {
24
+ path: volume.host,
25
+ },
26
+ mountPath: volume.container,
27
+ readOnly: volume.mode === 'ro',
28
+ })),
29
+ ports: input.ports,
30
+ timeout: input.timeoutSeconds,
31
+ timeoutSeconds: input.timeoutSeconds,
32
+ resourceLimits: {
33
+ cpu: input.cpu,
34
+ memory: input.memory,
35
+ },
36
+ });
37
+ const sandbox = response?.sandbox || response?.data || response;
38
+ const id = String(sandbox?.id || sandbox?.sandboxId || '').trim();
39
+ if (!id) {
40
+ throw new local_core_errors_js_1.LocalCoreError('sandbox_start_failed', 'OpenSandbox create response did not include a sandbox id.', {
41
+ details: { response },
42
+ });
43
+ }
44
+ return { ...sandbox, id };
45
+ }
46
+ async getSandbox(sandboxId) {
47
+ const response = await this.request('GET', `/v1/sandboxes/${encodeURIComponent(sandboxId)}`);
48
+ const sandbox = response?.sandbox || response?.data || response;
49
+ return { ...sandbox, id: String(sandbox?.id || sandbox?.sandboxId || sandboxId) };
50
+ }
51
+ async getEndpoint(sandboxId, port) {
52
+ const response = await this.request('GET', `/v1/sandboxes/${encodeURIComponent(sandboxId)}/endpoints/${port}`);
53
+ const endpoint = String(response?.endpoint || response?.data?.endpoint || response?.url || response?.data?.url || '').trim();
54
+ if (!endpoint) {
55
+ throw new local_core_errors_js_1.LocalCoreError('sandbox_endpoint_missing', `OpenSandbox did not return an endpoint for port ${port}.`, {
56
+ details: { sandboxId, port, response },
57
+ });
58
+ }
59
+ return { ...response, endpoint };
60
+ }
61
+ async deleteSandbox(sandboxId) {
62
+ await this.request('DELETE', `/v1/sandboxes/${encodeURIComponent(sandboxId)}`);
63
+ }
64
+ async request(method, path, body) {
65
+ const fetchImpl = this.options.fetchImpl || fetch;
66
+ const headers = {
67
+ accept: 'application/json',
68
+ };
69
+ if (body !== undefined) {
70
+ headers['content-type'] = 'application/json';
71
+ }
72
+ if (this.options.apiKey) {
73
+ headers['OPEN-SANDBOX-API-KEY'] = this.options.apiKey;
74
+ headers.authorization = `Bearer ${this.options.apiKey}`;
75
+ }
76
+ let response;
77
+ try {
78
+ response = await fetchImpl(`${this.options.serverUrl}${path}`, {
79
+ method,
80
+ headers,
81
+ body: body === undefined ? undefined : JSON.stringify(body),
82
+ });
83
+ }
84
+ catch (error) {
85
+ throw new local_core_errors_js_1.LocalCoreError('sandbox_unavailable', `OpenSandbox request failed: ${error instanceof Error ? error.message : String(error)}`, {
86
+ cause: error instanceof Error ? error.message : String(error),
87
+ details: { method, path, serverUrl: this.options.serverUrl },
88
+ });
89
+ }
90
+ const text = await response.text();
91
+ const json = text ? parseJson(text) : {};
92
+ if (!response.ok) {
93
+ throw new local_core_errors_js_1.LocalCoreError(response.status === 401 || response.status === 403 ? 'sandbox_unauthorized' : 'sandbox_request_failed', `OpenSandbox ${method} ${path} failed with ${response.status}.`, {
94
+ details: {
95
+ method,
96
+ path,
97
+ status: response.status,
98
+ response: json || text,
99
+ },
100
+ });
101
+ }
102
+ return (json || {});
103
+ }
104
+ }
105
+ exports.OpenSandboxClient = OpenSandboxClient;
106
+ function parseJson(text) {
107
+ try {
108
+ return JSON.parse(text);
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_STATE_MOUNT_PATH = exports.DEFAULT_WORKSPACE_MOUNT_PATH = exports.DEFAULT_SANDBOX_MEMORY = exports.DEFAULT_SANDBOX_CPU = exports.DEFAULT_SANDBOX_WARM_POOL_SIZE = exports.DEFAULT_SANDBOX_IDLE_SECONDS = exports.DEFAULT_SANDBOX_TIMEOUT_SECONDS = exports.DEFAULT_SANDBOX_ACP_PORT = exports.DEFAULT_PI_SANDBOX_IMAGE = exports.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV = exports.DEFAULT_OPENSANDBOX_API_KEY_ENV = exports.DEFAULT_OPENSANDBOX_SERVER_URL_ENV = exports.DEFAULT_OPENSANDBOX_SERVER_URL = void 0;
4
+ exports.getProjectSandboxOptions = getProjectSandboxOptions;
5
+ exports.isProjectSandboxEnabled = isProjectSandboxEnabled;
6
+ exports.normalizeSandboxLaunchConfig = normalizeSandboxLaunchConfig;
7
+ exports.resolveSandboxStateHostPath = resolveSandboxStateHostPath;
8
+ exports.cleanupRunScopedState = cleanupRunScopedState;
9
+ exports.materializeSandboxLaunchConfig = materializeSandboxLaunchConfig;
10
+ exports.defaultOpenSandboxServerUrl = defaultOpenSandboxServerUrl;
11
+ exports.sandboxProxyScriptPath = sandboxProxyScriptPath;
12
+ exports.resolveSandboxProxyCwd = resolveSandboxProxyCwd;
13
+ exports.sandboxProxyLaunchEnv = sandboxProxyLaunchEnv;
14
+ exports.sanitizePathSegment = sanitizePathSegment;
15
+ exports.resolveHostPathFromConfigDir = resolveHostPathFromConfigDir;
16
+ const node_fs_1 = require("node:fs");
17
+ const node_path_1 = require("node:path");
18
+ const index_js_1 = require("../../../../packages/contracts/src/index.js");
19
+ exports.DEFAULT_OPENSANDBOX_SERVER_URL = 'http://127.0.0.1:8080';
20
+ exports.DEFAULT_OPENSANDBOX_SERVER_URL_ENV = 'AGENTDOCK_OPENSANDBOX_SERVER_URL';
21
+ exports.DEFAULT_OPENSANDBOX_API_KEY_ENV = 'OPEN_SANDBOX_API_KEY';
22
+ exports.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV = 'AGENTDOCK_SANDBOX_STATE_HOST_ROOT';
23
+ exports.DEFAULT_PI_SANDBOX_IMAGE = 'agentdock/pi-acp:local';
24
+ exports.DEFAULT_SANDBOX_ACP_PORT = 8080;
25
+ exports.DEFAULT_SANDBOX_TIMEOUT_SECONDS = 7200;
26
+ exports.DEFAULT_SANDBOX_IDLE_SECONDS = 900;
27
+ exports.DEFAULT_SANDBOX_WARM_POOL_SIZE = 0;
28
+ exports.DEFAULT_SANDBOX_CPU = '1000m';
29
+ exports.DEFAULT_SANDBOX_MEMORY = '2Gi';
30
+ exports.DEFAULT_WORKSPACE_MOUNT_PATH = '/workspace';
31
+ exports.DEFAULT_STATE_MOUNT_PATH = '/agent-state';
32
+ function getProjectSandboxOptions(project) {
33
+ const sandbox = project.agent?.options?.sandbox;
34
+ return sandbox && typeof sandbox === 'object' ? sandbox : null;
35
+ }
36
+ function isProjectSandboxEnabled(project) {
37
+ return Boolean(getProjectSandboxOptions(project)?.enabled);
38
+ }
39
+ function normalizeSandboxLaunchConfig(input) {
40
+ const raw = getProjectSandboxOptions(input.project);
41
+ if (!raw?.enabled) {
42
+ return undefined;
43
+ }
44
+ const configDir = (0, node_path_1.dirname)(input.configState.path);
45
+ const userDataRoot = (0, node_path_1.dirname)(configDir);
46
+ const agentType = input.launchConfig.agentType || String(input.project.agent?.type || '').trim().toLowerCase() || 'agent';
47
+ const desktopConfig = input.configState.parsed || {};
48
+ const deploymentProfileId = String(raw.deployment_profile || desktopConfig.deployment_profile || process.env.AGENTDOCK_DEPLOYMENT_PROFILE || '').trim();
49
+ const profile = (0, index_js_1.getDesktopDeploymentProfile)(deploymentProfileId);
50
+ const sandboxProvider = resolveSandboxProvider(desktopConfig.sandbox_providers, raw, profile.id);
51
+ const runtimeImage = resolveSandboxRuntimeImage(desktopConfig.sandbox_runtime_images, raw, agentType);
52
+ const stateScope = normalizeStateScope(raw.state_scope);
53
+ const stateMountPath = normalizeAbsoluteContainerPath(raw.state_mount_path || runtimeImage.state_mount_path || profile.stateMountPath, exports.DEFAULT_STATE_MOUNT_PATH);
54
+ const projectId = sanitizePathSegment(input.project.name, 'project');
55
+ const rawOptions = input.project.agent?.options || {};
56
+ const userId = sanitizePathSegment(String(rawOptions.user_id || rawOptions.tenant_id || 'local'), 'local');
57
+ const agentId = sanitizePathSegment(agentType, 'agent');
58
+ const stateHostRoot = String(process.env[exports.DEFAULT_SANDBOX_STATE_HOST_ROOT_ENV] || '').trim() || (0, node_path_1.resolve)(userDataRoot, 'sandbox-state');
59
+ const proxyCwd = resolveSandboxProxyCwd(configDir);
60
+ const stateHostPath = (0, node_path_1.resolve)(stateHostRoot, 'users', userId, ...(stateScope === 'user'
61
+ ? ['agents', agentId]
62
+ : ['projects', projectId, 'agents', agentId]), ...(stateScope === 'thread'
63
+ ? ['threads', '${LOCAL_AI_THREAD_ID}', 'state']
64
+ : stateScope === 'run'
65
+ ? ['runs', '${AGENTDOCK_SANDBOX_RUN_ID}', 'state']
66
+ : ['state']));
67
+ if (stateHostPath && !stateHostPath.includes('${')) {
68
+ (0, node_fs_1.mkdirSync)(stateHostPath, { recursive: true, mode: 0o700 });
69
+ }
70
+ return {
71
+ enabled: true,
72
+ provider: String(raw.provider || sandboxProvider.type || 'opensandbox').trim() || 'opensandbox',
73
+ transport: normalizeSandboxTransport(runtimeImage.transport || raw.transport),
74
+ serverUrl: normalizeServerUrl(sandboxProvider.server_url || raw.server_url || profile.openSandboxServerUrl),
75
+ apiKeyEnv: String(sandboxProvider.api_key_env || raw.api_key_env || exports.DEFAULT_OPENSANDBOX_API_KEY_ENV).trim() || exports.DEFAULT_OPENSANDBOX_API_KEY_ENV,
76
+ image: String(runtimeImage.image || raw.image || defaultSandboxImage(agentType)).trim() || defaultSandboxImage(agentType),
77
+ acpPort: normalizePositiveInteger(runtimeImage.acp_port || raw.acp_port, exports.DEFAULT_SANDBOX_ACP_PORT),
78
+ entrypoint: normalizeEntrypoint(runtimeImage.entrypoint || raw.entrypoint),
79
+ timeoutSeconds: normalizePositiveInteger(raw.timeout_seconds, exports.DEFAULT_SANDBOX_TIMEOUT_SECONDS),
80
+ lifecycle: normalizeSandboxLifecycle(raw.sandbox_lifecycle),
81
+ idleSeconds: normalizePositiveInteger(raw.idle_seconds, exports.DEFAULT_SANDBOX_IDLE_SECONDS),
82
+ warmPoolSize: normalizeNonNegativeInteger(raw.warm_pool_size, exports.DEFAULT_SANDBOX_WARM_POOL_SIZE),
83
+ cpu: String(raw.cpu || exports.DEFAULT_SANDBOX_CPU).trim() || exports.DEFAULT_SANDBOX_CPU,
84
+ memory: String(raw.memory || exports.DEFAULT_SANDBOX_MEMORY).trim() || exports.DEFAULT_SANDBOX_MEMORY,
85
+ userId,
86
+ projectId,
87
+ stateScope,
88
+ workspaceHostPath: input.launchConfig.workDir,
89
+ workspaceMountPath: normalizeAbsoluteContainerPath(raw.workspace_mount_path || runtimeImage.workspace_mount_path || profile.workspaceMountPath, exports.DEFAULT_WORKSPACE_MOUNT_PATH),
90
+ proxyCwd,
91
+ stateHostPath,
92
+ stateMountPath,
93
+ stateMount: {
94
+ userId,
95
+ projectId,
96
+ agentType,
97
+ scope: stateScope,
98
+ hostPath: stateHostPath,
99
+ containerPath: stateMountPath,
100
+ },
101
+ runtimeCommand: normalizeRuntimeCommandForSandbox(agentType, input.launchConfig.command, runtimeImage.runtime_command),
102
+ runtimeArgs: normalizeRuntimeArgsForSandbox(agentType, input.launchConfig.args || [], runtimeImage.runtime_args),
103
+ runtimeEnv: normalizeRuntimeEnvForSandbox(agentType, input.launchConfig.env || {}, stateMountPath),
104
+ };
105
+ }
106
+ function resolveSandboxProvider(providers, raw, profileId) {
107
+ const providerId = String(raw.provider_id || '').trim();
108
+ const selected = Array.isArray(providers)
109
+ ? providers.find((provider) => provider.id === providerId)
110
+ || providers.find((provider) => provider.id === index_js_1.DEFAULT_SANDBOX_PROVIDER_ID)
111
+ : undefined;
112
+ if (selected) {
113
+ return selected;
114
+ }
115
+ if (raw.server_url || raw.api_key_env || raw.provider) {
116
+ return {
117
+ id: providerId || index_js_1.DEFAULT_SANDBOX_PROVIDER_ID,
118
+ type: raw.provider || 'opensandbox',
119
+ name: 'OpenSandbox',
120
+ server_url: raw.server_url || defaultOpenSandboxServerUrl(),
121
+ api_key_env: raw.api_key_env || exports.DEFAULT_OPENSANDBOX_API_KEY_ENV,
122
+ };
123
+ }
124
+ return {
125
+ ...(0, index_js_1.defaultSandboxProviderForProfile)(profileId),
126
+ server_url: defaultOpenSandboxServerUrl(),
127
+ };
128
+ }
129
+ function resolveSandboxRuntimeImage(images, raw, agentType) {
130
+ const runtimeImageId = String(raw.runtime_image_id || '').trim();
131
+ const selected = Array.isArray(images)
132
+ ? images.find((image) => image.id === runtimeImageId)
133
+ || images.find((image) => image.agent_type === agentType)
134
+ : undefined;
135
+ if (selected) {
136
+ const fallback = (0, index_js_1.defaultSandboxRuntimeImage)(agentType);
137
+ return {
138
+ ...selected,
139
+ transport: normalizeSandboxTransport(selected.transport || fallback.transport),
140
+ };
141
+ }
142
+ if (raw.image || raw.transport || raw.acp_port || raw.entrypoint || raw.workspace_mount_path || raw.state_mount_path) {
143
+ const fallback = (0, index_js_1.defaultSandboxRuntimeImage)(agentType);
144
+ return {
145
+ id: runtimeImageId || fallback.id,
146
+ agent_type: agentType,
147
+ image: raw.image || fallback.image,
148
+ transport: normalizeSandboxTransport(raw.transport || fallback.transport),
149
+ acp_port: normalizePositiveInteger(raw.acp_port, fallback.acp_port),
150
+ entrypoint: raw.entrypoint || fallback.entrypoint,
151
+ workspace_mount_path: raw.workspace_mount_path || fallback.workspace_mount_path,
152
+ state_mount_path: raw.state_mount_path || fallback.state_mount_path,
153
+ };
154
+ }
155
+ return (0, index_js_1.defaultSandboxRuntimeImage)(agentType);
156
+ }
157
+ function resolveSandboxStateHostPath(config, threadId, runId) {
158
+ if (!config.stateHostPath) {
159
+ return '';
160
+ }
161
+ const resolved = config.stateHostPath
162
+ .replaceAll('${LOCAL_AI_THREAD_ID}', sanitizePathSegment(threadId, 'thread'))
163
+ .replaceAll('${AGENTDOCK_SANDBOX_RUN_ID}', sanitizePathSegment(runId, 'run'));
164
+ (0, node_fs_1.mkdirSync)(resolved, { recursive: true, mode: 0o700 });
165
+ return resolved;
166
+ }
167
+ function cleanupRunScopedState(config, statePath) {
168
+ if (config.stateScope !== 'run' || !statePath || !(0, node_fs_1.existsSync)(statePath)) {
169
+ return;
170
+ }
171
+ (0, node_fs_1.rmSync)(statePath, { recursive: true, force: true });
172
+ }
173
+ function materializeSandboxLaunchConfig(config, env) {
174
+ const threadId = String(env.LOCAL_AI_THREAD_ID || 'thread');
175
+ const runId = String(env.AGENTDOCK_SANDBOX_RUN_ID || 'run');
176
+ const stateHostPath = resolveSandboxStateHostPath(config, threadId, runId);
177
+ return {
178
+ ...config,
179
+ stateHostPath,
180
+ };
181
+ }
182
+ function defaultSandboxImage(agentType) {
183
+ return agentType === 'pi' ? exports.DEFAULT_PI_SANDBOX_IMAGE : `agentdock/${agentType}-acp:local`;
184
+ }
185
+ function normalizeServerUrl(value) {
186
+ const fallback = defaultOpenSandboxServerUrl();
187
+ return String(value || fallback).trim().replace(/\/+$/, '') || fallback;
188
+ }
189
+ function defaultOpenSandboxServerUrl(env = process.env) {
190
+ return String(env[exports.DEFAULT_OPENSANDBOX_SERVER_URL_ENV] || exports.DEFAULT_OPENSANDBOX_SERVER_URL)
191
+ .trim()
192
+ .replace(/\/+$/, '') || exports.DEFAULT_OPENSANDBOX_SERVER_URL;
193
+ }
194
+ function normalizeStateScope(value) {
195
+ return value === 'user' || value === 'thread' || value === 'run' ? value : 'project';
196
+ }
197
+ function normalizeSandboxTransport(_value) {
198
+ return 'http-ndjson';
199
+ }
200
+ function normalizeSandboxLifecycle(value) {
201
+ return value === 'per_run' ? 'per_run' : 'per_thread';
202
+ }
203
+ function normalizePositiveInteger(value, fallback) {
204
+ const parsed = Number(value);
205
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
206
+ }
207
+ function normalizeNonNegativeInteger(value, fallback) {
208
+ const parsed = Number(value);
209
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
210
+ }
211
+ function normalizeAbsoluteContainerPath(value, fallback) {
212
+ const normalized = String(value || '').trim();
213
+ return normalized.startsWith('/') ? normalized : fallback;
214
+ }
215
+ function normalizeEntrypoint(value) {
216
+ if (Array.isArray(value)) {
217
+ const entries = value.map((entry) => String(entry || '').trim()).filter(Boolean);
218
+ if (entries.length > 0) {
219
+ return entries;
220
+ }
221
+ }
222
+ return ['node', '/opt/agentdock/acp-bridge.mjs'];
223
+ }
224
+ function normalizeRuntimeEnvForSandbox(agentType, env, stateMountPath) {
225
+ const output = {
226
+ ...env,
227
+ AGENTDOCK_SANDBOX_AGENT_TYPE: agentType,
228
+ };
229
+ if (agentType === 'pi') {
230
+ output.PI_CODING_AGENT_DIR = `${stateMountPath}/pi`;
231
+ output.PI_ACP_PI_COMMAND = '/usr/local/bin/pi';
232
+ }
233
+ return output;
234
+ }
235
+ function normalizeRuntimeCommandForSandbox(agentType, command, override) {
236
+ const runtimeCommand = String(override || '').trim();
237
+ if (runtimeCommand) {
238
+ return runtimeCommand;
239
+ }
240
+ return agentType === 'pi' ? '/usr/local/bin/pi-acp' : command;
241
+ }
242
+ function normalizeRuntimeArgsForSandbox(agentType, args, override) {
243
+ if (Array.isArray(override)) {
244
+ return override.map((entry) => String(entry || '').trim()).filter(Boolean);
245
+ }
246
+ return agentType === 'pi' ? [] : args;
247
+ }
248
+ function sandboxProxyScriptPath() {
249
+ return (0, node_path_1.resolve)(__dirname, 'sandbox-stdio-proxy.js');
250
+ }
251
+ function resolveSandboxProxyCwd(configDir) {
252
+ const candidates = [
253
+ String(process.env.AI_WORKSTATION_USER_DATA_DIR || '').trim(),
254
+ configDir,
255
+ process.cwd(),
256
+ '/tmp',
257
+ ].filter(Boolean);
258
+ return candidates.find((candidate) => (0, node_fs_1.existsSync)(candidate)) || process.cwd();
259
+ }
260
+ function sandboxProxyLaunchEnv(config) {
261
+ return {
262
+ AGENTDOCK_SANDBOX_CONFIG: JSON.stringify(config),
263
+ };
264
+ }
265
+ function sanitizePathSegment(value, fallback) {
266
+ return String(value || '')
267
+ .trim()
268
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
269
+ .replace(/^-+|-+$/g, '') || fallback;
270
+ }
271
+ function resolveHostPathFromConfigDir(configState, rawPath) {
272
+ return (0, node_path_1.isAbsolute)(rawPath) ? rawPath : (0, node_path_1.resolve)((0, node_path_1.dirname)(configState.path), rawPath);
273
+ }
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SandboxManager = exports.SANDBOX_ENDPOINT_HOST_ENV = void 0;
4
+ exports.resolveOpenSandboxApiKey = resolveOpenSandboxApiKey;
5
+ exports.buildOpenSandboxCreateInput = buildOpenSandboxCreateInput;
6
+ exports.normalizeEndpoint = normalizeEndpoint;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const local_core_errors_js_1 = require("../kernel/local-core-errors.js");
9
+ const sandbox_config_js_1 = require("./sandbox-config.js");
10
+ const opensandbox_client_js_1 = require("./opensandbox-client.js");
11
+ exports.SANDBOX_ENDPOINT_HOST_ENV = 'AGENTDOCK_SANDBOX_ENDPOINT_HOST';
12
+ class SandboxManager {
13
+ options;
14
+ activeSandboxId = '';
15
+ cleaned = false;
16
+ constructor(options) {
17
+ this.options = options;
18
+ }
19
+ async start() {
20
+ const startedAt = Date.now();
21
+ const config = (0, sandbox_config_js_1.materializeSandboxLaunchConfig)(this.options.config, this.options.env);
22
+ const apiKey = resolveOpenSandboxApiKey(config, this.options.env);
23
+ const client = this.options.client || new opensandbox_client_js_1.OpenSandboxClient({
24
+ serverUrl: config.serverUrl,
25
+ apiKey,
26
+ });
27
+ await client.health();
28
+ this.options.log?.(`OpenSandbox health ready in ${Date.now() - startedAt}ms`);
29
+ const createInput = buildOpenSandboxCreateInput(config, this.options.env);
30
+ const createStartedAt = Date.now();
31
+ const sandbox = await client.createSandbox(createInput);
32
+ this.options.log?.(`OpenSandbox sandbox created in ${Date.now() - createStartedAt}ms id=${sandbox.id}`);
33
+ this.activeSandboxId = sandbox.id;
34
+ const runningStartedAt = Date.now();
35
+ const ready = await this.waitForRunning(client, sandbox.id, config.timeoutSeconds);
36
+ if (!ready) {
37
+ throw new local_core_errors_js_1.LocalCoreError('sandbox_start_timeout', `OpenSandbox sandbox ${sandbox.id} did not reach Running state.`, {
38
+ details: { sandboxId: sandbox.id, timeoutSeconds: config.timeoutSeconds },
39
+ });
40
+ }
41
+ this.options.log?.(`OpenSandbox sandbox running in ${Date.now() - runningStartedAt}ms id=${sandbox.id}`);
42
+ const endpointStartedAt = Date.now();
43
+ const endpoint = await client.getEndpoint(sandbox.id, config.acpPort);
44
+ this.options.log?.(`OpenSandbox endpoint ready in ${Date.now() - endpointStartedAt}ms id=${sandbox.id}`);
45
+ return {
46
+ sandboxId: sandbox.id,
47
+ endpoint: normalizeEndpoint(endpoint.endpoint, this.options.env[exports.SANDBOX_ENDPOINT_HOST_ENV], config.transport),
48
+ config,
49
+ stateHostPath: config.stateHostPath || '',
50
+ };
51
+ }
52
+ async cleanup(run) {
53
+ if (this.cleaned) {
54
+ return;
55
+ }
56
+ this.cleaned = true;
57
+ const config = run?.config || (0, sandbox_config_js_1.materializeSandboxLaunchConfig)(this.options.config, this.options.env);
58
+ const sandboxId = run?.sandboxId || this.activeSandboxId;
59
+ if (sandboxId) {
60
+ try {
61
+ const client = this.options.client || new opensandbox_client_js_1.OpenSandboxClient({
62
+ serverUrl: config.serverUrl,
63
+ apiKey: resolveOpenSandboxApiKey(config, this.options.env),
64
+ });
65
+ await client.deleteSandbox(sandboxId);
66
+ }
67
+ catch (error) {
68
+ this.options.log?.(`OpenSandbox cleanup failed for ${sandboxId}: ${error instanceof Error ? error.message : String(error)}`);
69
+ }
70
+ }
71
+ try {
72
+ (0, sandbox_config_js_1.cleanupRunScopedState)(config, run?.stateHostPath || config.stateHostPath || '');
73
+ }
74
+ catch (error) {
75
+ this.options.log?.(`Sandbox run state cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
76
+ }
77
+ }
78
+ async waitForRunning(client, sandboxId, timeoutSeconds) {
79
+ const deadline = Date.now() + Math.min(timeoutSeconds * 1000, 120000);
80
+ let lastStatus = '';
81
+ while (Date.now() < deadline) {
82
+ const sandbox = await client.getSandbox(sandboxId);
83
+ lastStatus = sandboxStatus(sandbox);
84
+ if (lastStatus === 'running' || lastStatus === 'ready') {
85
+ return true;
86
+ }
87
+ if (lastStatus === 'failed' || lastStatus === 'error' || lastStatus === 'stopped') {
88
+ throw new local_core_errors_js_1.LocalCoreError('sandbox_start_failed', `OpenSandbox sandbox ${sandboxId} entered ${lastStatus} state.`, {
89
+ details: { sandboxId, status: lastStatus, sandbox },
90
+ });
91
+ }
92
+ await delay(500);
93
+ }
94
+ this.options.log?.(`OpenSandbox sandbox ${sandboxId} last status before timeout: ${lastStatus || 'unknown'}`);
95
+ return false;
96
+ }
97
+ }
98
+ exports.SandboxManager = SandboxManager;
99
+ function resolveOpenSandboxApiKey(config, env) {
100
+ const configured = String(env[config.apiKeyEnv] || '').trim();
101
+ if (configured) {
102
+ return configured;
103
+ }
104
+ return isLocalOpenSandboxServer(config.serverUrl) ? 'agentdock-local' : '';
105
+ }
106
+ function isLocalOpenSandboxServer(serverUrl) {
107
+ try {
108
+ const url = new URL(serverUrl);
109
+ return (url.hostname === '127.0.0.1' || url.hostname === 'localhost' || url.hostname === '::1') && url.port === '8080';
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ function buildOpenSandboxCreateInput(config, env) {
116
+ const runId = String(env.AGENTDOCK_SANDBOX_RUN_ID || '').trim();
117
+ const threadId = String(env.LOCAL_AI_THREAD_ID || '').trim();
118
+ const workspaceId = String(env.LOCAL_AI_WORKSPACE_ID || config.projectId).trim();
119
+ const runtimeEnv = {
120
+ ...config.runtimeEnv,
121
+ LOCAL_AI_WORKSPACE_ID: workspaceId,
122
+ LOCAL_AI_THREAD_ID: threadId,
123
+ LOCAL_AI_WORKSPACE_PATH: config.workspaceMountPath,
124
+ AGENTDOCK_ACP_PORT: String(config.acpPort),
125
+ AGENTDOCK_SANDBOX_RUN_ID: runId,
126
+ AGENTDOCK_SANDBOX_USER_ID: config.userId,
127
+ AGENTDOCK_SANDBOX_PROJECT_ID: config.projectId,
128
+ AGENTDOCK_SANDBOX_AGENT_TYPE: config.runtimeEnv.AGENTDOCK_SANDBOX_AGENT_TYPE || '',
129
+ AGENTDOCK_ACP_COMMAND: config.runtimeCommand,
130
+ AGENTDOCK_ACP_ARGS: JSON.stringify(config.runtimeArgs || []),
131
+ AGENTDOCK_ACP_CWD: config.workspaceMountPath,
132
+ AGENTDOCK_ACP_STATE_DIRS: JSON.stringify([
133
+ config.stateMountPath,
134
+ ...Object.entries(config.runtimeEnv)
135
+ .filter(([key]) => key.endsWith('_DIR'))
136
+ .map(([, value]) => value),
137
+ ].filter(Boolean)),
138
+ };
139
+ const volumes = [
140
+ {
141
+ host: config.workspaceHostPath,
142
+ container: config.workspaceMountPath,
143
+ mode: 'rw',
144
+ },
145
+ ];
146
+ if (config.stateHostPath) {
147
+ volumes.push({
148
+ host: config.stateHostPath,
149
+ container: config.stateMountPath,
150
+ mode: 'rw',
151
+ });
152
+ }
153
+ return {
154
+ image: config.image,
155
+ entrypoint: config.entrypoint,
156
+ env: runtimeEnv,
157
+ metadata: {
158
+ userId: metadataLabel(config.userId, 'local'),
159
+ projectId: metadataLabel(config.projectId, 'project'),
160
+ workspaceId: metadataLabel(workspaceId, 'workspace'),
161
+ threadId: metadataLabel(threadId, 'thread'),
162
+ runId: metadataLabel(runId, 'run'),
163
+ agentType: metadataLabel(String(config.runtimeEnv.AGENTDOCK_SANDBOX_AGENT_TYPE || ''), 'agent'),
164
+ stateScope: metadataLabel(config.stateScope, 'project'),
165
+ },
166
+ volumes,
167
+ ports: [config.acpPort],
168
+ timeoutSeconds: config.timeoutSeconds,
169
+ cpu: config.cpu,
170
+ memory: config.memory,
171
+ };
172
+ }
173
+ function sandboxStatus(sandbox) {
174
+ const status = sandbox.status;
175
+ if (status && typeof status === 'object') {
176
+ const state = status.state;
177
+ if (state) {
178
+ return String(state).toLowerCase();
179
+ }
180
+ }
181
+ return String(status || sandbox.state || '').toLowerCase();
182
+ }
183
+ function metadataLabel(value, fallback) {
184
+ const sanitized = (0, sandbox_config_js_1.sanitizePathSegment)(value, fallback).replaceAll('_', '-');
185
+ const trimmed = sanitized.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, '') || fallback;
186
+ if (trimmed.length <= 63) {
187
+ return trimmed;
188
+ }
189
+ const hash = (0, node_crypto_1.createHash)('sha256').update(trimmed).digest('hex').slice(0, 8);
190
+ return `${trimmed.slice(0, 54).replace(/[^a-zA-Z0-9]+$/g, '')}-${hash}`;
191
+ }
192
+ function normalizeEndpoint(endpoint, endpointHostOverride, _transport = 'http-ndjson') {
193
+ const trimmed = endpoint.trim();
194
+ const normalized = (() => {
195
+ if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
196
+ return trimmed;
197
+ }
198
+ return `http://${trimmed}`;
199
+ })();
200
+ const override = String(endpointHostOverride || '').trim();
201
+ if (!override) {
202
+ return normalized;
203
+ }
204
+ try {
205
+ const url = new URL(normalized);
206
+ url.hostname = override;
207
+ return url.toString();
208
+ }
209
+ catch {
210
+ return normalized;
211
+ }
212
+ }
213
+ function delay(ms) {
214
+ return new Promise((resolve) => setTimeout(resolve, ms));
215
+ }