@fitlab-ai/agent-infra 0.8.2 → 0.8.4

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 (198) hide show
  1. package/README.zh-CN.md +10 -0
  2. package/bin/cli.ts +1 -1
  3. package/dist/bin/cli.js +2 -2
  4. package/dist/lib/cp.js +57 -16
  5. package/dist/lib/decide.js +2 -2
  6. package/dist/lib/defaults.json +2 -0
  7. package/dist/lib/init.js +7 -7
  8. package/dist/lib/merge.js +1 -1
  9. package/dist/lib/prompt.js +1 -1
  10. package/dist/lib/run/index.js +7 -7
  11. package/dist/lib/run/prompt.js +1 -1
  12. package/dist/lib/sandbox/capture.js +10 -8
  13. package/dist/lib/sandbox/clipboard/bridge.js +59 -21
  14. package/dist/lib/sandbox/clipboard/inbox.js +82 -0
  15. package/dist/lib/sandbox/clipboard/index.js +5 -6
  16. package/dist/lib/sandbox/clipboard/linux.js +91 -0
  17. package/dist/lib/sandbox/clipboard/paths.js +1 -1
  18. package/dist/lib/sandbox/clipboard/win32.js +144 -0
  19. package/dist/lib/sandbox/commands/create.js +106 -88
  20. package/dist/lib/sandbox/commands/enter.js +9 -9
  21. package/dist/lib/sandbox/commands/list-running.js +2 -2
  22. package/dist/lib/sandbox/commands/ls.js +7 -7
  23. package/dist/lib/sandbox/commands/prune.js +8 -8
  24. package/dist/lib/sandbox/commands/rebuild.js +38 -50
  25. package/dist/lib/sandbox/commands/refresh.js +2 -2
  26. package/dist/lib/sandbox/commands/rm.js +11 -11
  27. package/dist/lib/sandbox/commands/show.js +4 -4
  28. package/dist/lib/sandbox/commands/start.js +4 -4
  29. package/dist/lib/sandbox/commands/vm.js +4 -4
  30. package/dist/lib/sandbox/config.js +10 -4
  31. package/dist/lib/sandbox/constants.js +4 -1
  32. package/dist/lib/sandbox/credentials.js +1 -1
  33. package/dist/lib/sandbox/dotfiles.js +1 -1
  34. package/dist/lib/sandbox/engine.js +3 -3
  35. package/dist/lib/sandbox/engines/index.js +5 -5
  36. package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
  37. package/dist/lib/sandbox/image-build.js +80 -0
  38. package/dist/lib/sandbox/image-prune.js +2 -2
  39. package/dist/lib/sandbox/index.js +10 -10
  40. package/dist/lib/sandbox/managed-fs.js +1 -1
  41. package/dist/lib/sandbox/readme-scaffold.js +1 -1
  42. package/dist/lib/sandbox/runtimes/base.dockerfile +3 -0
  43. package/dist/lib/sandbox/shell.js +9 -1
  44. package/dist/lib/sandbox/tools.js +4 -4
  45. package/dist/lib/server/adapters/feishu/index.js +10 -3
  46. package/dist/lib/server/adapters/feishu/renderer.js +88 -0
  47. package/dist/lib/server/adapters/feishu/transport.js +2 -13
  48. package/dist/lib/server/daemon.js +64 -43
  49. package/dist/lib/server/display.js +83 -0
  50. package/dist/lib/server/index.js +2 -2
  51. package/dist/lib/server/process-control.js +2 -2
  52. package/dist/lib/server/protocol.js +2 -2
  53. package/dist/lib/server/streamer.js +5 -4
  54. package/dist/lib/task/commands/cat.js +2 -2
  55. package/dist/lib/task/commands/decisions.js +4 -4
  56. package/dist/lib/task/commands/files.js +3 -3
  57. package/dist/lib/task/commands/grep.js +3 -3
  58. package/dist/lib/task/commands/issue-body.js +4 -4
  59. package/dist/lib/task/commands/log.js +5 -5
  60. package/dist/lib/task/commands/ls.js +3 -3
  61. package/dist/lib/task/commands/show.js +1 -1
  62. package/dist/lib/task/commands/status.js +71 -25
  63. package/dist/lib/task/index.js +9 -9
  64. package/dist/lib/task/resolve-ref.js +1 -1
  65. package/dist/lib/task/workflow-warnings.js +94 -0
  66. package/dist/lib/update.js +4 -4
  67. package/lib/cp.ts +56 -15
  68. package/lib/defaults.json +2 -0
  69. package/lib/sandbox/capture.ts +5 -3
  70. package/lib/sandbox/clipboard/bridge.ts +62 -18
  71. package/lib/sandbox/clipboard/inbox.ts +83 -0
  72. package/lib/sandbox/clipboard/index.ts +5 -6
  73. package/lib/sandbox/clipboard/linux.ts +124 -0
  74. package/lib/sandbox/clipboard/win32.ts +173 -0
  75. package/lib/sandbox/commands/create.ts +132 -88
  76. package/lib/sandbox/commands/rebuild.ts +42 -54
  77. package/lib/sandbox/config.ts +13 -1
  78. package/lib/sandbox/constants.ts +4 -0
  79. package/lib/sandbox/image-build.ts +134 -0
  80. package/lib/sandbox/runtimes/base.dockerfile +3 -0
  81. package/lib/sandbox/shell.ts +11 -2
  82. package/lib/sandbox/tools.ts +5 -5
  83. package/lib/server/adapters/_contract.ts +3 -0
  84. package/lib/server/adapters/feishu/index.ts +11 -3
  85. package/lib/server/adapters/feishu/renderer.ts +99 -0
  86. package/lib/server/adapters/feishu/transport.ts +5 -18
  87. package/lib/server/daemon.ts +74 -38
  88. package/lib/server/display.ts +136 -0
  89. package/lib/server/streamer.ts +5 -4
  90. package/lib/task/commands/log.ts +3 -3
  91. package/lib/task/commands/status.ts +102 -21
  92. package/lib/task/workflow-warnings.ts +121 -0
  93. package/package.json +2 -2
  94. package/templates/.agents/README.en.md +3 -0
  95. package/templates/.agents/README.zh-CN.md +3 -0
  96. package/templates/.agents/rules/create-issue.github.en.md +4 -4
  97. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
  98. package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
  99. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
  100. package/templates/.agents/rules/issue-sync.github.en.md +13 -0
  101. package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
  102. package/templates/.agents/rules/next-step-output.en.md +15 -1
  103. package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
  104. package/templates/.agents/rules/pr-sync.github.en.md +17 -2
  105. package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
  106. package/templates/.agents/rules/review-handshake.en.md +2 -2
  107. package/templates/.agents/rules/review-handshake.zh-CN.md +2 -2
  108. package/templates/.agents/rules/task-management.en.md +1 -1
  109. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  110. package/templates/.agents/rules/testing-discipline.en.md +3 -41
  111. package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
  112. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
  113. package/templates/.agents/scripts/validate-artifact.js +213 -0
  114. package/templates/.agents/scripts/workflow-warnings.js +290 -0
  115. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  116. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  117. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
  118. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  119. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  120. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  121. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  122. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
  123. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  124. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  125. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  127. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
  130. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
  131. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
  132. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  133. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  134. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  135. package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
  136. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
  137. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
  138. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -0
  139. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
  140. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
  141. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
  142. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
  143. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
  144. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
  145. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  148. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  149. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  150. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  151. package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
  152. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
  153. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
  155. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  157. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
  158. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
  159. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
  160. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
  161. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
  162. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
  163. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  164. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  165. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  166. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  167. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  168. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  169. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  170. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  171. package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
  172. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
  173. package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
  174. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
  175. package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
  176. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
  177. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
  178. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
  179. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  180. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  181. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  182. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  183. package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
  184. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  185. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
  186. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  187. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  188. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
  189. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
  190. package/templates/.agents/templates/task.en.md +7 -0
  191. package/templates/.agents/templates/task.zh-CN.md +7 -0
  192. package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
  193. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
  194. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
  195. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
  196. package/templates/.github/workflows/metadata-sync.yml +5 -0
  197. package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
  198. package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
package/README.zh-CN.md CHANGED
@@ -207,3 +207,13 @@ my-project/
207
207
  ## 许可协议
208
208
 
209
209
  [MIT](License.txt)
210
+
211
+ ## 社区
212
+
213
+ - 微信公众号:`FitFramework`
214
+ - QQ 技术交流群:`1029802553`
215
+
216
+ <p align="center">
217
+ <img src="./assets/qrcode_for_wechat_gh.png" alt="FitFramework 微信公众号二维码" width="250">
218
+ <img src="./assets/qrcode_for_qq_01.png" alt="FitFramework QQ 技术交流群二维码" width="250">
219
+ </p>
package/bin/cli.ts CHANGED
@@ -15,7 +15,7 @@ const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructur
15
15
  Usage: ai <command> [options]
16
16
 
17
17
  Commands:
18
- cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
18
+ cp <ssh-alias> Copy local clipboard image to a remote macOS clipboard or Linux sandbox over SSH
19
19
  decide Record a human decision for a task HD item
20
20
  help Show this help message
21
21
  init Initialize a new project with update-agent-infra seed command
package/dist/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  }
8
8
  return path;
9
9
  };
10
- import { VERSION } from "../lib/version.js";
10
+ import { VERSION } from '../lib/version.js';
11
11
  // Node.js version check
12
12
  const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
13
13
  if (major < 22 || (major === 22 && minor < 9)) {
@@ -19,7 +19,7 @@ const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructur
19
19
  Usage: ai <command> [options]
20
20
 
21
21
  Commands:
22
- cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
22
+ cp <ssh-alias> Copy local clipboard image to a remote macOS clipboard or Linux sandbox over SSH
23
23
  decide Record a human decision for a task HD item
24
24
  help Show this help message
25
25
  init Initialize a new project with update-agent-infra seed command
package/dist/lib/cp.js CHANGED
@@ -4,8 +4,9 @@ import fs from 'node:fs';
4
4
  import { platform as currentPlatform, tmpdir as defaultTmpdir } from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { parseArgs } from 'node:util';
7
- import { createClipboardAdapter } from "./sandbox/clipboard/index.js";
8
- const USAGE = 'Usage: ai cp <ssh-alias>\n\nCopy the local clipboard image (PNG) to a remote macOS NSPasteboard over ssh/scp.\n';
7
+ import { createClipboardAdapter } from './sandbox/clipboard/index.js';
8
+ import { RECEIVER_CAPABILITY, receiveRemoteClipboardPng } from './sandbox/clipboard/inbox.js';
9
+ const USAGE = 'Usage: ai cp <ssh-alias>\n\nCopy the local clipboard image (PNG) to a remote macOS clipboard or Linux sandbox over SSH.\n';
9
10
  const COMMAND_TIMEOUT_MS = 30_000;
10
11
  export function runCommand(cmd, args, input) {
11
12
  const result = spawnSync(cmd, args, {
@@ -23,6 +24,22 @@ export function runCommand(cmd, args, input) {
23
24
  }
24
25
  export async function cmdCp(args, deps = {}) {
25
26
  const { platform = currentPlatform(), createAdapter = createClipboardAdapter, spawnFn = runCommand, randomId = randomUUID, mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, rmFn = fs.rmSync, tmpdir = defaultTmpdir, writeStdout = (chunk) => process.stdout.write(chunk), writeStderr = (chunk) => process.stderr.write(chunk) } = deps;
27
+ if (args.length === 1 && args[0] === '--remote-receive-capability') {
28
+ if (platform === 'linux')
29
+ writeStdout(`${RECEIVER_CAPABILITY}\n`);
30
+ return platform === 'linux' ? 0 : 1;
31
+ }
32
+ if (args.length === 2 && args[0] === '--remote-receive-v1') {
33
+ try {
34
+ const result = receiveRemoteClipboardPng(process.env.HOME || process.env.USERPROFILE || '', args[1], platform);
35
+ writeStdout(`${result}\n`);
36
+ return 0;
37
+ }
38
+ catch (error) {
39
+ writeStderr(`${error instanceof Error ? error.message : 'clipboard receive failed'}\n`);
40
+ return 1;
41
+ }
42
+ }
26
43
  if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
27
44
  writeStdout(USAGE);
28
45
  return 0;
@@ -62,6 +79,34 @@ export async function cmdCp(args, deps = {}) {
62
79
  const localPng = path.join(localTmpDir, 'clipboard.png');
63
80
  writeFileFn(localPng, png);
64
81
  remotePath = `/tmp/agent-infra-cp-${randomId()}.png`;
82
+ const remoteSystem = spawnFn('ssh', [
83
+ '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'uname', '-s'
84
+ ]);
85
+ if (remoteSystem.status !== 0) {
86
+ writeStderr(`failed to detect remote platform on ${alias}:\n${commandDetail(remoteSystem)}\n`);
87
+ return 1;
88
+ }
89
+ const remotePlatform = remoteSystem.stdout.trim();
90
+ if (remotePlatform !== 'Darwin' && remotePlatform !== 'Linux') {
91
+ writeStderr(`unsupported remote platform on ${alias}: ${remotePlatform || 'unknown'}\n`);
92
+ return 1;
93
+ }
94
+ if (remotePlatform === 'Linux') {
95
+ const homeCheck = spawnFn('ssh', [
96
+ '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'test', '-d', '$HOME/.agent-infra'
97
+ ]);
98
+ if (homeCheck.status !== 0) {
99
+ writeStderr(`remote agent-infra installation is missing on ${alias}\n`);
100
+ return 1;
101
+ }
102
+ const capability = spawnFn('ssh', [
103
+ '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'ai', 'cp', '--remote-receive-capability'
104
+ ]);
105
+ if (capability.status !== 0 || capability.stderr !== '' || capability.stdout.replace(/\n$/u, '') !== RECEIVER_CAPABILITY) {
106
+ writeStderr(`remote agent-infra clipboard receiver is unavailable or incompatible on ${alias}; update agent-infra\n`);
107
+ return 1;
108
+ }
109
+ }
65
110
  const upload = spawnFn('scp', [
66
111
  '-o',
67
112
  'BatchMode=yes',
@@ -75,24 +120,20 @@ export async function cmdCp(args, deps = {}) {
75
120
  return 1;
76
121
  }
77
122
  uploaded = true;
78
- // Remote write currently targets macOS only: it pipes an AppleScript to the
79
- // remote `osascript` to set its NSPasteboard. This is the extension point for
80
- // other remote platforms later (e.g. dispatch on remote OS to wl-copy/xclip
81
- // on Linux); a non-macOS remote fails here with a clear non-zero error today.
82
- const setRemote = spawnFn('ssh', [
83
- '-o',
84
- 'BatchMode=yes',
85
- '-o',
86
- 'ConnectTimeout=10',
87
- alias,
88
- 'osascript',
89
- '-'
90
- ], remoteSetScript(remotePath));
123
+ const setRemote = remotePlatform === 'Darwin'
124
+ ? spawnFn('ssh', [
125
+ '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'osascript', '-'
126
+ ], remoteSetScript(remotePath))
127
+ : spawnFn('ssh', [
128
+ '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'ai', 'cp', '--remote-receive-v1', remotePath
129
+ ]);
91
130
  if (setRemote.status !== 0) {
92
131
  writeStderr(`failed to set remote clipboard on ${alias}:\n${commandDetail(setRemote)}\n`);
93
132
  return 1;
94
133
  }
95
- writeStdout(`copied clipboard image to ${alias}\n`);
134
+ writeStdout(remotePlatform === 'Linux'
135
+ ? `uploaded clipboard image to ${alias}; return to the SSH session and press Ctrl+V\n`
136
+ : `copied clipboard image to ${alias}\n`);
96
137
  return 0;
97
138
  }
98
139
  finally {
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
- import { VERSION } from "./version.js";
5
- import { resolveTaskRef } from "./task/resolve-ref.js";
4
+ import { VERSION } from './version.js';
5
+ import { resolveTaskRef } from './task/resolve-ref.js';
6
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
7
7
  function detectRepoRoot() {
8
8
  return execFileSync('git', ['rev-parse', '--show-toplevel'], {
@@ -14,6 +14,7 @@
14
14
  "gemini-cli",
15
15
  "opencode"
16
16
  ],
17
+ "refreshIntervalDays": 7,
17
18
  "dockerfile": null,
18
19
  "vm": {
19
20
  "cpu": null,
@@ -51,6 +52,7 @@
51
52
  "**/test-integration.*",
52
53
  "**/test.*",
53
54
  "**/upgrade-dependency.*",
55
+ ".agents/rules/testing-discipline.*",
54
56
  ".agents/skills/post-release/SKILL.*",
55
57
  ".agents/skills/release/SKILL.*",
56
58
  ".agents/skills/test-integration/SKILL.*",
package/dist/lib/init.js CHANGED
@@ -2,13 +2,13 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
4
  import { platform } from 'node:os';
5
- import { info, ok, err } from "./log.js";
6
- import { prompt, select, multiSelect, closePrompt } from "./prompt.js";
7
- import { resolveTemplateDir } from "./paths.js";
8
- import { renderFile, copySkillDir, KNOWN_PLATFORMS } from "./render.js";
9
- import { enginesForPlatform } from "./sandbox/engines/index.js";
10
- import { VERSION } from "./version.js";
11
- import { BUILTIN_TUI_IDS, BUILTIN_TUI_DISPLAY, isPathOwnedByDisabledTUI, resolveEnabledTUIs } from "./builtin-tuis.js";
5
+ import { info, ok, err } from './log.js';
6
+ import { prompt, select, multiSelect, closePrompt } from './prompt.js';
7
+ import { resolveTemplateDir } from './paths.js';
8
+ import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.js';
9
+ import { enginesForPlatform } from './sandbox/engines/index.js';
10
+ import { VERSION } from './version.js';
11
+ import { BUILTIN_TUI_IDS, BUILTIN_TUI_DISPLAY, isPathOwnedByDisabledTUI, resolveEnabledTUIs } from './builtin-tuis.js';
12
12
  const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
13
13
  const PLATFORM_DEFAULT_ENGINES = Object.freeze({
14
14
  linux: 'native',
package/dist/lib/merge.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { info, ok } from "./log.js";
3
+ import { info, ok } from './log.js';
4
4
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
5
5
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
6
6
  const TITLE_RE = /^# (.+)$/m;
@@ -1,5 +1,5 @@
1
1
  import readline from 'node:readline';
2
- import { ask } from "./log.js";
2
+ import { ask } from './log.js';
3
3
  let _rl = null;
4
4
  let _lines = [];
5
5
  let _lineResolve = null;
@@ -1,12 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { loadServerConfig } from "../server/config.js";
4
- import { resolveTaskBranch } from "../sandbox/task-resolver.js";
5
- import { createRunId, runInSandbox } from "../sandbox/capture.js";
6
- import { loadShortIdByTaskId, normalizeShortIdInput } from "../task/short-id.js";
7
- import { buildTuiCommand, renderPrompt, selectTui } from "./tui.js";
8
- import { getSkillRunSpec } from "./skills.js";
9
- import { runHostCommand } from "./host.js";
3
+ import { loadServerConfig } from '../server/config.js';
4
+ import { resolveTaskBranch } from '../sandbox/task-resolver.js';
5
+ import { createRunId, runInSandbox } from '../sandbox/capture.js';
6
+ import { loadShortIdByTaskId, normalizeShortIdInput } from '../task/short-id.js';
7
+ import { buildTuiCommand, renderPrompt, selectTui } from './tui.js';
8
+ import { getSkillRunSpec } from './skills.js';
9
+ import { runHostCommand } from './host.js';
10
10
  const USAGE = `Usage: ai run <skill> [task-ref] [args...] [--tui <name>]
11
11
 
12
12
  Task skills are scheduled inside the sandbox tmux session; ai run returns once
@@ -1,2 +1,2 @@
1
- export { renderPrompt } from "./tui.js";
1
+ export { renderPrompt } from './tui.js';
2
2
  //# sourceMappingURL=prompt.js.map
@@ -1,9 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { loadConfig } from "./config.js";
3
- import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from "./constants.js";
4
- import { detectEngine } from "./engine.js";
5
- import { hostTimezoneEnvFlags, terminalEnvFlags } from "./commands/enter.js";
6
- import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from "./commands/list-running.js";
2
+ import { loadConfig } from './config.js';
3
+ import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.js';
4
+ import { detectEngine } from './engine.js';
5
+ import { commandForEngine } from './shell.js';
6
+ import { hostTimezoneEnvFlags, terminalEnvFlags } from './commands/enter.js';
7
+ import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from './commands/list-running.js';
7
8
  async function spawnCapture(file, args) {
8
9
  return new Promise((resolve, reject) => {
9
10
  const child = spawn(file, args, { stdio: ['ignore', 'pipe', 'pipe'] });
@@ -49,12 +50,12 @@ function buildRunScript(params) {
49
50
  return `#!/bin/sh
50
51
  set -u
51
52
  cd /workspace
52
- date "+%Y-%m-%d %H:%M:%S%:z" > ${runDir}/started_at
53
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)$/\\1:\\2/' > ${runDir}/started_at
53
54
  printf '%s\\n' running > ${runDir}/status
54
55
  ${command}
55
56
  code=$?
56
57
  printf '%s\\n' "$code" > ${runDir}/exit_code
57
- date "+%Y-%m-%d %H:%M:%S%:z" > ${runDir}/finished_at
58
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)$/\\1:\\2/' > ${runDir}/finished_at
58
59
  if [ "$code" -eq 0 ]; then
59
60
  printf '%s\\n' completed > ${runDir}/status
60
61
  else
@@ -144,7 +145,8 @@ export async function runInSandbox(request, options = {}) {
144
145
  '-lc',
145
146
  buildTmuxLauncher({ request, runId })
146
147
  ];
147
- const result = await (options.spawn ?? spawnCapture)('docker', dockerArgs);
148
+ const command = commandForEngine(engine, 'docker', dockerArgs);
149
+ const result = await (options.spawn ?? spawnCapture)(command.cmd, command.args);
148
150
  return {
149
151
  ...result,
150
152
  run: {
@@ -1,12 +1,14 @@
1
1
  import { StringDecoder } from 'node:string_decoder';
2
2
  import { spawnSync } from 'node:child_process';
3
- import { createClipboardAdapter } from "./index.js";
4
- import { buildBracketedPaste, CtrlVDetector } from "./keys.js";
5
- import { clipboardHostDir, containerClipboardPath, pngClipboardFilename, pruneClipboardDir, writeClipboardPngAtomic } from "./paths.js";
6
- import { commandForEngine, restoreTerminal, runInteractiveEngine, runOkEngine } from "../shell.js";
7
- import { loadNodePty } from "./node-pty.js";
3
+ import { createClipboardAdapter } from './index.js';
4
+ import { buildBracketedPaste, CtrlVDetector } from './keys.js';
5
+ import { clipboardHostDir, containerClipboardPath, pngClipboardFilename, pruneClipboardDir, writeClipboardPngAtomic } from './paths.js';
6
+ import { isClipboardInboxReadable, readPendingClipboardPath } from './inbox.js';
7
+ import { commandForEngine, restoreTerminal, runInteractiveEngine, runOkEngine } from '../shell.js';
8
+ import { loadNodePty } from './node-pty.js';
8
9
  const FALLBACK_PREFIX = 'Warning: clipboard image paste bridge disabled';
9
10
  const PARTIAL_ESCAPE_FLUSH_MS = 30;
11
+ const BRACKETED_PASTE_RE = /^\x1b\[200~([\s\S]*)\x1b\[201~$/u;
10
12
  // Node's stdin.setRawMode(true) uses libuv's RAW mode, which (unlike the
11
13
  // cfmakeraw that `docker exec -it` applies on the non-bridge path) keeps ONLCR
12
14
  // set on the shared host TTY. With ONLCR on, the kernel rewrites the bare \n
@@ -36,13 +38,10 @@ export async function runInteractiveWithClipboardBridge(options) {
36
38
  if (!stdin.isTTY || !stdout.isTTY) {
37
39
  return fallback('host stdin/stdout is not a TTY');
38
40
  }
39
- if (!adapter) {
40
- return fallback('no clipboard adapter available on this platform');
41
- }
42
- const available = adapter.available();
43
- if (!available.ok) {
41
+ const available = adapter?.available() ?? { ok: false, reason: 'no clipboard adapter available on this platform' };
42
+ const inboxReady = isClipboardInboxReadable(home);
43
+ if (!available.ok && !inboxReady)
44
44
  return fallback(available.reason);
45
- }
46
45
  if (!runOk(engine, 'docker', ['exec', container, 'sh', '-c', '[ -d /clipboard ] && [ -r /clipboard ]'])) {
47
46
  return fallback('container /clipboard mount is missing; rebuild the sandbox to enable image paste');
48
47
  }
@@ -68,7 +67,7 @@ export async function runInteractiveWithClipboardBridge(options) {
68
67
  return runBridge({
69
68
  child,
70
69
  home,
71
- adapter,
70
+ adapter: available.ok ? adapter : null,
72
71
  writeStderr,
73
72
  stdin,
74
73
  stdout,
@@ -83,7 +82,7 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
83
82
  clearFlushTimer();
84
83
  for (const token of detector.feed(inputDecoder.write(chunk))) {
85
84
  if (token.kind === 'text') {
86
- child.write(token.raw);
85
+ handleText(token.raw, child);
87
86
  }
88
87
  else {
89
88
  handleCtrlV(token, child);
@@ -94,7 +93,7 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
94
93
  flushTimer = null;
95
94
  for (const token of detector.flush()) {
96
95
  if (token.kind === 'text') {
97
- child.write(token.raw);
96
+ handleText(token.raw, child);
98
97
  }
99
98
  else {
100
99
  handleCtrlV(token, child);
@@ -106,22 +105,53 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
106
105
  const onResize = () => child.resize(stdout.columns || 120, stdout.rows || 40);
107
106
  const onSigint = () => child.kill('SIGINT');
108
107
  const onSigterm = () => child.kill('SIGTERM');
108
+ function writeImagePaste(png, target) {
109
+ const filename = pngClipboardFilename(png);
110
+ writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
111
+ pruneClipboardDir(clipboardHostDir(home));
112
+ target.write(buildBracketedPaste(containerClipboardPath(filename)));
113
+ }
114
+ function handleText(raw, target) {
115
+ const textAdapter = adapter;
116
+ const pastedText = extractSinglePathPaste(raw);
117
+ if (!textAdapter || !textAdapter.readImageFromText || pastedText === null) {
118
+ target.write(raw);
119
+ return;
120
+ }
121
+ try {
122
+ const png = textAdapter.readImageFromText(pastedText);
123
+ if (!png) {
124
+ target.write(raw);
125
+ return;
126
+ }
127
+ writeImagePaste(png, target);
128
+ }
129
+ catch (error) {
130
+ target.write(raw);
131
+ if (!warnedPasteFailure) {
132
+ warnedPasteFailure = true;
133
+ writeStderr(`Warning: clipboard image paste failed; forwarded pasted text (${error instanceof Error ? error.message : 'unknown error'})\n`);
134
+ }
135
+ }
136
+ }
109
137
  function handleCtrlV(match, target) {
110
138
  try {
139
+ const pendingPath = readPendingClipboardPath(home);
140
+ if (pendingPath) {
141
+ target.write(buildBracketedPaste(pendingPath));
142
+ return;
143
+ }
111
144
  // readImagePng returns null both for "no image on clipboard" and for
112
145
  // unexpected read failures; both cases forward the original Ctrl+V so
113
146
  // the container app handles it as a regular keystroke. The throw branch
114
147
  // below only fires on truly unexpected exceptions (e.g. fs write
115
148
  // errors writing to the host clipboard dir).
116
- const png = adapter.readImagePng();
149
+ const png = adapter?.readImagePng() ?? null;
117
150
  if (!png) {
118
151
  target.write(match.raw);
119
152
  return;
120
153
  }
121
- const filename = pngClipboardFilename(png);
122
- writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
123
- pruneClipboardDir(clipboardHostDir(home));
124
- target.write(buildBracketedPaste(containerClipboardPath(filename)));
154
+ writeImagePaste(png, target);
125
155
  }
126
156
  catch (error) {
127
157
  target.write(match.raw);
@@ -155,7 +185,7 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
155
185
  try {
156
186
  for (const token of detector.feed(inputDecoder.end())) {
157
187
  if (token.kind === 'text') {
158
- child.write(token.raw);
188
+ handleText(token.raw, child);
159
189
  }
160
190
  else {
161
191
  handleCtrlV(token, child);
@@ -163,7 +193,7 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
163
193
  }
164
194
  for (const token of detector.flush()) {
165
195
  if (token.kind === 'text') {
166
- child.write(token.raw);
196
+ handleText(token.raw, child);
167
197
  }
168
198
  }
169
199
  }
@@ -181,6 +211,14 @@ async function runBridge({ child, home, adapter, writeStderr, stdin, stdout, det
181
211
  restoreTerminal();
182
212
  }
183
213
  }
214
+ function extractSinglePathPaste(raw) {
215
+ const match = raw.match(BRACKETED_PASTE_RE);
216
+ const text = (match ? match[1] : raw)?.trim();
217
+ if (!text || /[\r\n]/u.test(text)) {
218
+ return null;
219
+ }
220
+ return text;
221
+ }
184
222
  function onceExit(child, stdin) {
185
223
  return new Promise((resolve) => {
186
224
  let settled = false;
@@ -0,0 +1,82 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { clipboardHostDir, containerClipboardPath, pngClipboardFilename, pruneClipboardDir, writeClipboardPngAtomic } from './paths.js';
4
+ export const RECEIVER_CAPABILITY = 'AGENT_INFRA_CLIPBOARD_RECEIVER:v1';
5
+ const MARKER = '.pending.json';
6
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
7
+ const HASHED_PNG = /^[a-f0-9]{16}\.png$/u;
8
+ const REMOTE_TEMP = /^agent-infra-cp-[0-9a-f-]{36}\.png$/u;
9
+ export function isClipboardInboxReadable(home) {
10
+ const dir = clipboardHostDir(home);
11
+ try {
12
+ fs.accessSync(dir, fs.constants.R_OK);
13
+ return fs.statSync(dir).isDirectory();
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ export function receiveRemoteClipboardPng(home, tempPath, platformName = process.platform) {
20
+ if (platformName !== 'linux')
21
+ throw new Error('remote clipboard receiver requires Linux');
22
+ if (path.dirname(tempPath) !== '/tmp' || !REMOTE_TEMP.test(path.basename(tempPath))) {
23
+ throw new Error('invalid clipboard upload path');
24
+ }
25
+ const signature = Buffer.alloc(PNG_SIGNATURE.length);
26
+ const fd = fs.openSync(tempPath, 'r');
27
+ let signatureBytes;
28
+ try {
29
+ signatureBytes = fs.readSync(fd, signature, 0, signature.length, 0);
30
+ }
31
+ finally {
32
+ fs.closeSync(fd);
33
+ }
34
+ if (signatureBytes !== PNG_SIGNATURE.length || !signature.equals(PNG_SIGNATURE)) {
35
+ throw new Error('uploaded clipboard file is not a PNG');
36
+ }
37
+ const png = fs.readFileSync(tempPath);
38
+ const dir = clipboardHostDir(home);
39
+ if (!fs.existsSync(path.dirname(dir)))
40
+ throw new Error('agent-infra is not installed on this host');
41
+ const filename = pngClipboardFilename(png);
42
+ writeClipboardPngAtomic(dir, filename, png);
43
+ pruneClipboardDir(dir);
44
+ const markerTmp = path.join(dir, `.${MARKER}.${process.pid}.tmp`);
45
+ fs.writeFileSync(markerTmp, JSON.stringify({ version: 1, filename, createdAt: new Date().toISOString() }) + '\n', { mode: 0o600 });
46
+ fs.renameSync(markerTmp, path.join(dir, MARKER));
47
+ return containerClipboardPath(filename);
48
+ }
49
+ export function readPendingClipboardPath(home) {
50
+ const dir = clipboardHostDir(home);
51
+ const markerPath = path.join(dir, MARKER);
52
+ let markerText;
53
+ try {
54
+ markerText = fs.readFileSync(markerPath, 'utf8');
55
+ }
56
+ catch (error) {
57
+ if (error.code === 'ENOENT')
58
+ return null;
59
+ throw new Error('invalid clipboard pending marker', { cause: error });
60
+ }
61
+ let marker;
62
+ try {
63
+ marker = JSON.parse(markerText);
64
+ }
65
+ catch (error) {
66
+ throw new Error('invalid clipboard pending marker', { cause: error });
67
+ }
68
+ if (marker.version !== 1 || typeof marker.filename !== 'string' || !HASHED_PNG.test(marker.filename)) {
69
+ throw new Error('invalid clipboard pending marker');
70
+ }
71
+ const filePath = path.join(dir, marker.filename);
72
+ try {
73
+ const stat = fs.lstatSync(filePath);
74
+ if (!stat.isFile() || stat.isSymbolicLink())
75
+ throw new Error('pending clipboard image is not a regular file');
76
+ }
77
+ catch (error) {
78
+ throw new Error('invalid clipboard pending marker', { cause: error });
79
+ }
80
+ return containerClipboardPath(marker.filename);
81
+ }
82
+ //# sourceMappingURL=inbox.js.map
@@ -1,16 +1,15 @@
1
1
  import { platform } from 'node:os';
2
- import { createDarwinClipboardAdapter } from "./darwin.js";
2
+ import { createDarwinClipboardAdapter } from './darwin.js';
3
+ import { createLinuxClipboardAdapter } from './linux.js';
4
+ import { createWin32ClipboardAdapter } from './win32.js';
3
5
  export function createClipboardAdapter({ platformName = platform() } = {}) {
4
6
  switch (platformName) {
5
7
  case 'darwin':
6
8
  return createDarwinClipboardAdapter();
7
9
  case 'linux':
8
- // Future work: dispatch based on $WAYLAND_DISPLAY (wl-paste) or $DISPLAY (xclip);
9
- // see Issue #386 follow-up. Returning null disables the bridge for now.
10
- return null;
10
+ return createLinuxClipboardAdapter();
11
11
  case 'win32':
12
- // Future work: native Win32 clipboard reader. Returning null disables the bridge.
13
- return null;
12
+ return createWin32ClipboardAdapter();
14
13
  default:
15
14
  return null;
16
15
  }
@@ -0,0 +1,91 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ const PROBE_TIMEOUT_MS = 2_000;
6
+ const READ_IMAGE_TIMEOUT_MS = 5_000;
7
+ const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
8
+ export function createLinuxClipboardAdapter({ env = process.env, execFn = execFileSync, execToFileFn = execToFile, mkdtempFn = fs.mkdtempSync, readFileFn = fs.readFileSync, rmFn = fs.rmSync } = {}) {
9
+ return {
10
+ available() {
11
+ const backend = selectBackend(env);
12
+ try {
13
+ execFn(backend.command, versionArgs(backend), {
14
+ encoding: 'utf8',
15
+ timeout: PROBE_TIMEOUT_MS
16
+ });
17
+ return { ok: true };
18
+ }
19
+ catch {
20
+ return { ok: false, reason: unavailableReason(backend) };
21
+ }
22
+ },
23
+ readImagePng() {
24
+ const backend = selectBackend(env);
25
+ try {
26
+ const mimeTypes = String(execFn(backend.command, mimeArgs(backend), {
27
+ encoding: 'utf8',
28
+ timeout: PROBE_TIMEOUT_MS
29
+ }));
30
+ if (!hasPngMime(mimeTypes)) {
31
+ return null;
32
+ }
33
+ const tmpDir = mkdtempFn(path.join(os.tmpdir(), 'agent-infra-clipboard-'));
34
+ const outputPath = path.join(tmpDir, 'clipboard.png');
35
+ try {
36
+ execToFileFn(backend.command, imageArgs(backend), outputPath, READ_IMAGE_TIMEOUT_MS);
37
+ const png = Buffer.from(readFileFn(outputPath));
38
+ return isPng(png) ? png : null;
39
+ }
40
+ finally {
41
+ rmFn(tmpDir, { recursive: true, force: true });
42
+ }
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ };
49
+ }
50
+ function selectBackend(env) {
51
+ return env.WAYLAND_DISPLAY?.trim()
52
+ ? { kind: 'wayland', command: 'wl-paste' }
53
+ : { kind: 'x11', command: 'xclip' };
54
+ }
55
+ function versionArgs(backend) {
56
+ return backend.kind === 'wayland' ? ['--version'] : ['-version'];
57
+ }
58
+ function mimeArgs(backend) {
59
+ return backend.kind === 'wayland'
60
+ ? ['--list-types']
61
+ : ['-selection', 'clipboard', '-t', 'TARGETS', '-o'];
62
+ }
63
+ function imageArgs(backend) {
64
+ return backend.kind === 'wayland'
65
+ ? ['-t', 'image/png']
66
+ : ['-selection', 'clipboard', '-t', 'image/png', '-o'];
67
+ }
68
+ function unavailableReason(backend) {
69
+ return backend.kind === 'wayland'
70
+ ? 'Wayland clipboard tool wl-paste is unavailable; install wl-clipboard to enable image paste'
71
+ : 'X11 clipboard tool xclip is unavailable; install xclip to enable image paste';
72
+ }
73
+ function hasPngMime(output) {
74
+ return output.split(/\s+/u).some((type) => type === 'image/png');
75
+ }
76
+ function execToFile(cmd, args, outputPath, timeout) {
77
+ const fd = fs.openSync(outputPath, 'w', 0o600);
78
+ try {
79
+ execFileSync(cmd, args, {
80
+ timeout,
81
+ stdio: ['ignore', fd, 'pipe']
82
+ });
83
+ }
84
+ finally {
85
+ fs.closeSync(fd);
86
+ }
87
+ }
88
+ function isPng(buffer) {
89
+ return buffer.length >= PNG_MAGIC.length && PNG_MAGIC.every((byte, index) => buffer[index] === byte);
90
+ }
91
+ //# sourceMappingURL=linux.js.map
@@ -1,7 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { hostJoin } from "../engines/wsl2-paths.js";
4
+ import { hostJoin } from '../engines/wsl2-paths.js';
5
5
  export const CONTAINER_CLIPBOARD_MOUNT = '/clipboard';
6
6
  const DEFAULT_KEEP = 20;
7
7
  const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;