@kafca/agentdock 0.1.24 → 0.1.26

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 (83) hide show
  1. package/README.md +2 -0
  2. package/dist/renderer/assets/{Badge-CXcUMqb-.js → Badge-qZHTUGI3.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BRxdPzi-.js → Chat-W35BzaZk.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-sWGBIfIb.js → ChatMarkdown-DxKT_YGp.js} +1 -1
  5. package/dist/renderer/assets/{Config-DqxuxIGg.js → Config-COTX6LDN.js} +1 -1
  6. package/dist/renderer/assets/{CronList-OgHxbti5.js → CronList-kxlfu9Iq.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-DmklXiH1.js → Dashboard-Y4PGxzuV.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BvDmi8dq.js → EmptyState-Cjq33EOF.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Bj59Hdpo.js → KnowledgeDetail-CztNt7i8.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-CigQ-nSo.js → KnowledgeHome-BAOyy3I9.js} +1 -1
  11. package/dist/renderer/assets/{Logs-Bfc7gBpY.js → Logs-PRZ1IIEi.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CfmiFknz.js → Modal-B1rgqkBM.js} +1 -1
  13. package/dist/renderer/assets/{Page-BxtLEw_b.js → Page-CjzAhgaU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-CcUphxGm.js → ProjectDetail-XGh_5XxQ.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-CVkvDIfy.js → ProjectList-BkPTpQRZ.js} +1 -1
  16. package/dist/renderer/assets/{Select-DgQMZ0MD.js → Select-BZd1QJVw.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-CRmGKfBx.js → SessionChat-C3sOTWGT.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-D0E8XbYw.js → SessionList-B5WK1big.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-DWiBLHC7.js +23 -0
  20. package/dist/renderer/assets/{Workspace-Q4g4rlRT.js → Workspace-CQ64VK-o.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-BySN_zhF.js → arrow-left-FLdaWHZ8.js} +1 -1
  22. package/dist/renderer/assets/{book-open-BGlrnrTc.js → book-open-DaSioPS9.js} +1 -1
  23. package/dist/renderer/assets/{check-BiJogEa5.js → check-BiSmsuGR.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-D2fHRJv6.js → chevron-down-pZ2KjmDR.js} +1 -1
  25. package/dist/renderer/assets/{circle-CIcjixmI.js → circle-296S3nw9.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DyA_NYew.js → desktop-88jgnOZV.js} +1 -1
  27. package/dist/renderer/assets/{desktop-DAXY9Bxv.js → desktop-BkMW-h5p.js} +2 -2
  28. package/dist/renderer/assets/{funnel-BUzwb-id.js → funnel-Dvz31PLy.js} +1 -1
  29. package/dist/renderer/assets/{heart-DZEQ1z_V.js → heart-D9mxSZVO.js} +1 -1
  30. package/dist/renderer/assets/{index-DgsIo4vJ.js → index-CZA4lqvM.js} +1 -1
  31. package/dist/renderer/assets/{index-CB_KyLOr.js → index-dOdOWAa2.js} +3 -3
  32. package/dist/renderer/assets/{pencil-DGL4agcl.js → pencil-B2fRbaAo.js} +1 -1
  33. package/dist/renderer/assets/{play-DzasCLM4.js → play-CWPRHypD.js} +1 -1
  34. package/dist/renderer/assets/{plug-dubNMe_m.js → plug-B7IBVwSo.js} +1 -1
  35. package/dist/renderer/assets/{plus-DN4kML4k.js → plus-Cn3Awirs.js} +1 -1
  36. package/dist/renderer/assets/projects-CmCopAi6.js +1 -0
  37. package/dist/renderer/assets/{save-CNIc9tjl.js → save-MVjXRqom.js} +1 -1
  38. package/dist/renderer/assets/{search-C0EGbF3I.js → search-Bu5c7wTX.js} +1 -1
  39. package/dist/renderer/assets/{server-BMxQ4zmc.js → server-aDvfqBg_.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Bf5O-iMi.js → session-utils-DjDpjT9r.js} +1 -1
  41. package/dist/renderer/assets/{sessions-BMSs6rEI.js → sessions-D7dV8h0H.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-i3Tf1TUz.js → shield-check-DJdEhc_e.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-SKrcp18f.js → trash-2-BF3ejnXL.js} +1 -1
  44. package/dist/renderer/assets/{user-cNUZddjZ.js → user-BwuWCrin.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Dyb84ANR.js → wifi-off-BahJZfDs.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/agent-runtime-detector.test.js +64 -0
  48. package/dist-electron/electron/architecture-docs.test.js +56 -0
  49. package/dist-electron/electron/channel-content.test.js +8 -0
  50. package/dist-electron/electron/lac-cli.test.js +248 -0
  51. package/dist-electron/electron/local-core-refactor.test.js +795 -20
  52. package/dist-electron/electron/plugin-kernel.test.js +108 -0
  53. package/dist-electron/electron/renderer-packaging.test.js +77 -0
  54. package/dist-electron/electron/workspace-task-store.test.js +57 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +67 -6
  56. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js +76 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +201 -0
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +29 -0
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +30 -18
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +112 -210
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +60 -0
  62. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +44 -27
  63. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +104 -12
  64. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +13 -1
  65. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +8 -1
  66. package/dist-electron/services/local-ai-core/src/cli/lac.js +3 -1
  67. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +1 -0
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -0
  69. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +221 -10
  70. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +39 -9
  71. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +1 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +439 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/server.js +208 -303
  75. package/dist-electron/shared/desktop.js +7 -3
  76. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +187 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-model.js +1 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +96 -0
  79. package/dist-electron/src/pages/Threads/thread-chat-permission.js +18 -0
  80. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +277 -0
  81. package/package.json +4 -2
  82. package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +0 -23
  83. package/dist/renderer/assets/projects-migPtfRe.js +0 -1
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-CB_KyLOr.js";/**
1
+ import{c as a}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-CB_KyLOr.js";/**
1
+ import{c as o}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-CB_KyLOr.js";/**
1
+ import{c as o}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-CB_KyLOr.js";/**
1
+ import{c as e}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -0,0 +1 @@
1
+ import{z as t}from"./index-dOdOWAa2.js";const e=()=>t.get("/projects"),r=o=>t.get(`/projects/${o}`);export{r as g,e as l};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-CB_KyLOr.js";/**
1
+ import{c as a}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-CB_KyLOr.js";/**
1
+ import{c}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-CB_KyLOr.js";/**
1
+ import{c as e}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-CB_KyLOr.js";/**
1
+ import{c as o}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{z as o}from"./index-CB_KyLOr.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
1
+ import{z as o}from"./index-dOdOWAa2.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
@@ -1,4 +1,4 @@
1
- import{c}from"./index-CB_KyLOr.js";/**
1
+ import{c}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-CB_KyLOr.js";/**
1
+ import{c as e}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-CB_KyLOr.js";/**
1
+ import{c as e}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-CB_KyLOr.js";/**
1
+ import{c as a}from"./index-dOdOWAa2.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AgentDock</title>
8
- <script type="module" crossorigin src="./assets/index-CB_KyLOr.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-dOdOWAa2.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-DFiJR0vv.css">
10
10
  </head>
11
11
  <body class="bg-[#f5f5f7] dark:bg-[#151516]">
@@ -26,6 +26,8 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
26
26
  strict_1.default.equal(byType.get('opencode')?.source, 'path');
27
27
  strict_1.default.equal(byType.get('opencode')?.command, opencode);
28
28
  strict_1.default.equal(byType.get('opencode')?.binaryPath, opencode);
29
+ strict_1.default.equal(byType.get('pi')?.installed, false);
30
+ strict_1.default.equal(byType.get('pi')?.status, 'not_installed');
29
31
  strict_1.default.equal(byType.get('codex')?.installed, false);
30
32
  strict_1.default.equal(byType.get('codex')?.status, 'not_installed');
31
33
  strict_1.default.equal(byType.get('localcore-acp')?.installed, true);
@@ -36,6 +38,15 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
36
38
  (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
37
39
  }
38
40
  });
41
+ (0, node_test_1.default)('agent runtime detector reports bundled pi coding agent when available', () => {
42
+ const pi = (0, agent_runtime_detector_js_1.detectInstalledAgentRuntimes)({
43
+ env: { PATH: '' },
44
+ }).find((runtime) => runtime.agentType === 'pi');
45
+ strict_1.default.equal(pi?.installed, true);
46
+ strict_1.default.equal(pi?.status, 'installed');
47
+ strict_1.default.equal(pi?.source, 'bundled');
48
+ strict_1.default.match(pi?.command || '', /@mariozechner[/\\]pi-coding-agent[/\\]dist[/\\]cli\.js$/);
49
+ });
39
50
  (0, node_test_1.default)('agent runtime detector reports bundled codex ACP when available', () => {
40
51
  const codex = (0, agent_runtime_detector_js_1.detectInstalledAgentRuntimes)({
41
52
  env: { PATH: '' },
@@ -45,6 +56,39 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
45
56
  strict_1.default.equal(codex?.source, 'bundled');
46
57
  strict_1.default.match(codex?.command || '', /@zed-industries[/\\]codex-acp[/\\]bin[/\\]codex-acp\.js$/);
47
58
  });
59
+ (0, node_test_1.default)('agent runtime detector honors configured pi command before bundled runtime', () => {
60
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-pi-config-'));
61
+ try {
62
+ const customPi = (0, node_path_1.join)(dir, 'custom-pi');
63
+ (0, node_fs_1.writeFileSync)(customPi, '#!/bin/sh\nexit 0\n', 'utf8');
64
+ (0, node_fs_1.chmodSync)(customPi, 0o755);
65
+ const runtimes = (0, agent_runtime_detector_js_1.detectInstalledAgentRuntimes)({
66
+ env: { PATH: '' },
67
+ config: {
68
+ projects: [
69
+ {
70
+ name: 'configured-pi',
71
+ agent: {
72
+ type: 'pi',
73
+ options: {
74
+ command: customPi,
75
+ },
76
+ },
77
+ platforms: [],
78
+ },
79
+ ],
80
+ },
81
+ });
82
+ const pi = runtimes.find((runtime) => runtime.agentType === 'pi');
83
+ strict_1.default.equal(pi?.installed, true);
84
+ strict_1.default.equal(pi?.status, 'installed');
85
+ strict_1.default.equal(pi?.source, 'config');
86
+ strict_1.default.equal(pi?.command, customPi);
87
+ }
88
+ finally {
89
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
90
+ }
91
+ });
48
92
  (0, node_test_1.default)('agent runtime detector honors configured project commands', () => {
49
93
  const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-config-'));
50
94
  try {
@@ -78,6 +122,26 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
78
122
  (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
79
123
  }
80
124
  });
125
+ (0, node_test_1.default)('agent runtime detector reports pi version when PATH command succeeds', () => {
126
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-pi-version-'));
127
+ try {
128
+ const pi = (0, node_path_1.join)(dir, 'pi');
129
+ (0, node_fs_1.writeFileSync)(pi, '#!/bin/sh\necho "pi 0.72.1"\n', 'utf8');
130
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'package.json'), '{}\n', 'utf8');
131
+ (0, node_fs_1.chmodSync)(pi, 0o755);
132
+ const piRuntime = (0, agent_runtime_detector_js_1.detectInstalledAgentRuntimes)({
133
+ env: { PATH: dir },
134
+ requireFrom: (0, node_path_1.join)(dir, 'package.json'),
135
+ }).find((runtime) => runtime.agentType === 'pi');
136
+ strict_1.default.equal(piRuntime?.status, 'installed');
137
+ strict_1.default.equal(piRuntime?.source, 'path');
138
+ strict_1.default.equal(piRuntime?.version, '0.72.1');
139
+ strict_1.default.equal(piRuntime?.issues.length, 0);
140
+ }
141
+ finally {
142
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
143
+ }
144
+ });
81
145
  (0, node_test_1.default)('agent runtime detector reports version when version command succeeds', () => {
82
146
  const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-version-'));
83
147
  try {
@@ -0,0 +1,56 @@
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_path_1 = require("node:path");
10
+ const node_process_1 = __importDefault(require("node:process"));
11
+ const rootDir = node_process_1.default.cwd();
12
+ (0, node_test_1.default)('state ownership docs identify Local AI Core owners for durable chat and channel state', () => {
13
+ const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'architecture', 'state-ownership.md'), 'utf8');
14
+ for (const state of [
15
+ 'Task lifecycle',
16
+ 'Run lifecycle',
17
+ 'Thread messages',
18
+ 'Permission requests',
19
+ 'Attachments',
20
+ 'Channel inbound content',
21
+ 'Channel outbound content',
22
+ ]) {
23
+ strict_1.default.match(content, new RegExp(`\\| ${state} \\|[^\\n]*Local AI Core`), `${state} must have a Local AI Core owner`);
24
+ }
25
+ strict_1.default.match(content, /Renderer owns transient UI state only/, 'renderer ownership must remain transient');
26
+ strict_1.default.match(content, /Shared packages define cross-process data shapes/, 'shared contracts boundary must remain explicit');
27
+ });
28
+ (0, node_test_1.default)('message and channel contract docs cover canonical concepts and field lifecycles', () => {
29
+ const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'architecture', 'message-and-channel-contracts.md'), 'utf8');
30
+ for (const concept of [
31
+ 'Thread',
32
+ 'Run',
33
+ 'Task',
34
+ 'PermissionRequest',
35
+ 'MessageBlock',
36
+ 'Attachment',
37
+ 'ChannelInboundContent',
38
+ 'ChannelOutboundContent',
39
+ ]) {
40
+ strict_1.default.match(content, new RegExp(`\\| ${concept} \\|`), `${concept} must be listed in the contract index`);
41
+ }
42
+ for (const heading of ['Persisted fields', 'Streamed fields', 'Rendered fields', 'Platform-specific fields']) {
43
+ strict_1.default.match(content, new RegExp(`\\| ${heading} `), `${heading} must stay documented`);
44
+ }
45
+ strict_1.default.match(content, /Permission requests are durable thread\/run state/, 'permission state must not be renderer-inferred');
46
+ strict_1.default.match(content, /Thinking blocks are not overwritten by final answer blocks/, 'thinking/final separation invariant must remain documented');
47
+ strict_1.default.match(content, /Sending files through a channel uses one outbound path/, 'outbound file path invariant must remain documented');
48
+ });
49
+ (0, node_test_1.default)('thread chat renderer state types reuse canonical contracts instead of ad hoc duplicates', () => {
50
+ const taskStateSource = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'src', 'pages', 'Threads', 'thread-chat-task-state.ts'), 'utf8');
51
+ const permissionSource = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'src', 'pages', 'Threads', 'thread-chat-permission.ts'), 'utf8');
52
+ strict_1.default.match(taskStateSource, /import type \{ ChatTaskState \} from '\.\/thread-chat-model';/, 'task-state helpers must reuse the canonical Thread Chat task-state type');
53
+ strict_1.default.doesNotMatch(taskStateSource, /type ChatTaskState\s*=/, 'task-state helpers must not redeclare ChatTaskState');
54
+ strict_1.default.match(permissionSource, /export type PendingPermissionRequest = ThreadPendingPermissionRequest;/, 'renderer pending permission state must alias the shared thread permission contract');
55
+ strict_1.default.doesNotMatch(permissionSource, /type PermissionTaskState\s*=/, 'permission helpers must not carry a second task-state union');
56
+ });
@@ -5,7 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_path_1 = require("node:path");
8
9
  const content_js_1 = require("../services/local-ai-core/src/channel/shared/content.js");
10
+ const file_utils_js_1 = require("../services/local-ai-core/src/channel/shared/file-utils.js");
9
11
  (0, node_test_1.default)('channel thread message input stays plain text when there are no attachments', () => {
10
12
  const input = (0, content_js_1.createChannelThreadMessageInput)('Alice: hello', [
11
13
  { type: 'text', text: 'hello' },
@@ -27,3 +29,9 @@ const content_js_1 = require("../services/local-ai-core/src/channel/shared/conte
27
29
  ],
28
30
  });
29
31
  });
32
+ (0, node_test_1.default)('channel outbound file paths resolve relative to the current workspace root', () => {
33
+ strict_1.default.equal((0, file_utils_js_1.resolveChannelFilePath)('reports/out.pdf', '/workspace/project'), (0, node_path_1.join)('/workspace/project', 'reports', 'out.pdf'));
34
+ });
35
+ (0, node_test_1.default)('channel outbound file paths keep allowed absolute paths unchanged', () => {
36
+ strict_1.default.equal((0, file_utils_js_1.resolveChannelFilePath)('/tmp/out.pdf', '/workspace/project'), '/tmp/out.pdf');
37
+ });
@@ -162,6 +162,131 @@ function createIo() {
162
162
  });
163
163
  strict_1.default.match(read().stdout, /Created scheduler job job-local-1/);
164
164
  });
165
+ (0, node_test_1.default)('lac channel send-file posts a workspace-relative file through outbound message parts', async () => {
166
+ let capturedBody = null;
167
+ const server = (0, node_http_1.createServer)(async (req, res) => {
168
+ if (req.method === 'POST' && req.url === '/api/local/v1/platforms/lark/%E7%9F%A5%E8%AF%86%E5%BA%93/messages') {
169
+ const chunks = [];
170
+ for await (const chunk of req) {
171
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
172
+ }
173
+ capturedBody = Buffer.concat(chunks).toString('utf8');
174
+ res.setHeader('content-type', 'application/json');
175
+ res.end(JSON.stringify({
176
+ ok: true,
177
+ data: {
178
+ platform: 'lark',
179
+ workspaceId: '知识库',
180
+ channelId: 'chat-1',
181
+ messageIds: ['msg-file-1'],
182
+ attachments: [{
183
+ kind: 'file',
184
+ attachmentId: 'file-key-1',
185
+ fileName: 'out.pdf',
186
+ fileSize: 123,
187
+ metadata: { fileKey: 'file-key-1' },
188
+ }],
189
+ },
190
+ }));
191
+ return;
192
+ }
193
+ res.statusCode = 404;
194
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
195
+ });
196
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
197
+ const address = server.address();
198
+ (0, strict_1.default)(address && typeof address === 'object');
199
+ const { io, read } = createIo();
200
+ const exitCode = await (0, lac_js_1.runCli)(['channel', 'send-file', '--path', 'reports/out.pdf', '--name', 'out.pdf'], {
201
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
202
+ LOCAL_AI_WORKSPACE_ID: '知识库',
203
+ LOCAL_AI_WORKSPACE_PATH: '/workspace/project',
204
+ LOCAL_AI_PLATFORM: 'lark',
205
+ LOCAL_AI_CHAT_ID: 'chat-1',
206
+ LOCAL_AI_PLATFORM_USER_ID: 'user-1',
207
+ }, io);
208
+ server.close();
209
+ strict_1.default.equal(exitCode, 0);
210
+ (0, strict_1.default)(capturedBody);
211
+ strict_1.default.deepEqual(JSON.parse(capturedBody), {
212
+ route: {
213
+ type: 'channel.chat',
214
+ channelId: 'chat-1',
215
+ participantId: 'user-1',
216
+ },
217
+ parts: [{
218
+ type: 'file',
219
+ path: 'reports/out.pdf',
220
+ fileName: 'out.pdf',
221
+ metadata: {
222
+ workspacePath: '/workspace/project',
223
+ },
224
+ }],
225
+ });
226
+ strict_1.default.match(read().stdout, /Sent file out\.pdf to chat-1: msg-file-1/);
227
+ });
228
+ (0, node_test_1.default)('lac channel send-file posts allowed absolute paths through the same outbound message path', async () => {
229
+ let capturedBody = null;
230
+ const server = (0, node_http_1.createServer)(async (req, res) => {
231
+ if (req.method === 'POST' && req.url === '/api/local/v1/platforms/lark/%E7%9F%A5%E8%AF%86%E5%BA%93/messages') {
232
+ const chunks = [];
233
+ for await (const chunk of req) {
234
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
235
+ }
236
+ capturedBody = Buffer.concat(chunks).toString('utf8');
237
+ res.setHeader('content-type', 'application/json');
238
+ res.end(JSON.stringify({
239
+ ok: true,
240
+ data: {
241
+ platform: 'lark',
242
+ workspaceId: '知识库',
243
+ channelId: 'chat-1',
244
+ messageIds: ['msg-file-2'],
245
+ attachments: [{
246
+ kind: 'file',
247
+ attachmentId: 'file-key-2',
248
+ fileName: 'absolute.pdf',
249
+ fileSize: 456,
250
+ metadata: { fileKey: 'file-key-2' },
251
+ }],
252
+ },
253
+ }));
254
+ return;
255
+ }
256
+ res.statusCode = 404;
257
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
258
+ });
259
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
260
+ const address = server.address();
261
+ (0, strict_1.default)(address && typeof address === 'object');
262
+ const { io, read } = createIo();
263
+ const exitCode = await (0, lac_js_1.runCli)(['channel', 'send-file', '--path', '/tmp/absolute.pdf'], {
264
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
265
+ LOCAL_AI_WORKSPACE_ID: '知识库',
266
+ LOCAL_AI_WORKSPACE_PATH: '/workspace/project',
267
+ LOCAL_AI_PLATFORM: 'lark',
268
+ LOCAL_AI_CHAT_ID: 'chat-1',
269
+ LOCAL_AI_PLATFORM_USER_ID: 'user-1',
270
+ }, io);
271
+ server.close();
272
+ strict_1.default.equal(exitCode, 0);
273
+ (0, strict_1.default)(capturedBody);
274
+ strict_1.default.deepEqual(JSON.parse(capturedBody), {
275
+ route: {
276
+ type: 'channel.chat',
277
+ channelId: 'chat-1',
278
+ participantId: 'user-1',
279
+ },
280
+ parts: [{
281
+ type: 'file',
282
+ path: '/tmp/absolute.pdf',
283
+ metadata: {
284
+ workspacePath: '/workspace/project',
285
+ },
286
+ }],
287
+ });
288
+ strict_1.default.match(read().stdout, /Sent file absolute\.pdf to chat-1: msg-file-2/);
289
+ });
165
290
  (0, node_test_1.default)('lac scheduler list shows workspace jobs by default', async () => {
166
291
  const server = (0, node_http_1.createServer)((req, res) => {
167
292
  if (req.method === 'GET' && req.url === '/api/local/v1/scheduler/jobs?workspace_id=%E7%9F%A5%E8%AF%86%E5%BA%93') {
@@ -267,6 +392,129 @@ function createIo() {
267
392
  strict_1.default.match(read().stdout, /Job: 826aff79/);
268
393
  strict_1.default.doesNotMatch(read().stdout, /job:826aff79/);
269
394
  });
395
+ (0, node_test_1.default)('lac scheduler edit patches a short job id and normalizes execution mode', async () => {
396
+ let capturedBody = null;
397
+ const server = (0, node_http_1.createServer)(async (req, res) => {
398
+ if (req.method === 'PATCH' && req.url === '/api/local/v1/scheduler/jobs/826aff79') {
399
+ const chunks = [];
400
+ for await (const chunk of req) {
401
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
402
+ }
403
+ capturedBody = Buffer.concat(chunks).toString('utf8');
404
+ res.setHeader('content-type', 'application/json');
405
+ res.end(JSON.stringify({
406
+ ok: true,
407
+ data: {
408
+ id: 'job:826aff79-570b-4308-822e-18318e2c96ba',
409
+ workspaceId: '知识库',
410
+ platform: 'lark',
411
+ route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1', threadId: 'thread-1' },
412
+ executionMode: 'side-thread',
413
+ triggerType: 'cron',
414
+ cronExpr: '0 10 * * *',
415
+ promptTemplate: 'updated ping',
416
+ description: 'daily updated ping',
417
+ enabled: false,
418
+ concurrencyPolicy: 'skip_if_running',
419
+ createdAt: '2026-04-22T06:00:00.000Z',
420
+ updatedAt: '2026-04-22T07:00:00.000Z',
421
+ },
422
+ }));
423
+ return;
424
+ }
425
+ res.statusCode = 404;
426
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
427
+ });
428
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
429
+ const address = server.address();
430
+ (0, strict_1.default)(address && typeof address === 'object');
431
+ const { io, read } = createIo();
432
+ const exitCode = await (0, lac_js_1.runCli)([
433
+ 'scheduler',
434
+ 'edit',
435
+ '826aff79',
436
+ '--cron',
437
+ '0 10 * * *',
438
+ '--message',
439
+ 'updated ping',
440
+ '--desc',
441
+ 'daily updated ping',
442
+ '--enabled',
443
+ 'false',
444
+ '--execution-mode',
445
+ 'side_thread',
446
+ ], {
447
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
448
+ }, io);
449
+ server.close();
450
+ strict_1.default.equal(exitCode, 0);
451
+ (0, strict_1.default)(capturedBody);
452
+ strict_1.default.deepEqual(JSON.parse(capturedBody), {
453
+ cronExpr: '0 10 * * *',
454
+ promptTemplate: 'updated ping',
455
+ description: 'daily updated ping',
456
+ enabled: false,
457
+ executionMode: 'side-thread',
458
+ });
459
+ strict_1.default.match(read().stdout, /Updated scheduler job 826aff79/);
460
+ strict_1.default.doesNotMatch(read().stdout, /job:826aff79/);
461
+ });
462
+ (0, node_test_1.default)('lac scheduler del deletes a short job id', async () => {
463
+ const server = (0, node_http_1.createServer)((req, res) => {
464
+ if (req.method === 'DELETE' && req.url === '/api/local/v1/scheduler/jobs/826aff79') {
465
+ res.setHeader('content-type', 'application/json');
466
+ res.end(JSON.stringify({ ok: true, data: { deleted: true } }));
467
+ return;
468
+ }
469
+ res.statusCode = 404;
470
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
471
+ });
472
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
473
+ const address = server.address();
474
+ (0, strict_1.default)(address && typeof address === 'object');
475
+ const { io, read } = createIo();
476
+ const exitCode = await (0, lac_js_1.runCli)(['scheduler', 'del', '826aff79'], {
477
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
478
+ }, io);
479
+ server.close();
480
+ strict_1.default.equal(exitCode, 0);
481
+ strict_1.default.match(read().stdout, /Deleted scheduler job 826aff79/);
482
+ strict_1.default.doesNotMatch(read().stdout, /job:826aff79/);
483
+ });
484
+ (0, node_test_1.default)('lac scheduler run triggers a short job id', async () => {
485
+ const server = (0, node_http_1.createServer)((req, res) => {
486
+ if (req.method === 'POST' && req.url === '/api/local/v1/scheduler/jobs/826aff79/run') {
487
+ res.setHeader('content-type', 'application/json');
488
+ res.end(JSON.stringify({
489
+ ok: true,
490
+ data: {
491
+ id: 'run-1',
492
+ jobId: 'job:826aff79-570b-4308-822e-18318e2c96ba',
493
+ status: 'queued',
494
+ triggeredAt: '2026-04-22T07:00:00.000Z',
495
+ startedAt: '2026-04-22T07:00:00.000Z',
496
+ completedAt: '',
497
+ output: '',
498
+ error: '',
499
+ },
500
+ }));
501
+ return;
502
+ }
503
+ res.statusCode = 404;
504
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
505
+ });
506
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
507
+ const address = server.address();
508
+ (0, strict_1.default)(address && typeof address === 'object');
509
+ const { io, read } = createIo();
510
+ const exitCode = await (0, lac_js_1.runCli)(['scheduler', 'run', '826aff79'], {
511
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
512
+ }, io);
513
+ server.close();
514
+ strict_1.default.equal(exitCode, 0);
515
+ strict_1.default.match(read().stdout, /Triggered scheduler job 826aff79: queued/);
516
+ strict_1.default.doesNotMatch(read().stdout, /job:826aff79/);
517
+ });
270
518
  (0, node_test_1.default)('lac scheduler list --thread filters by current thread context', async () => {
271
519
  const server = (0, node_http_1.createServer)((req, res) => {
272
520
  if (req.method === 'GET' && req.url === '/api/local/v1/scheduler/jobs?workspace_id=%E7%9F%A5%E8%AF%86%E5%BA%93') {