@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.
- package/README.zh-CN.md +10 -0
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +2 -2
- package/dist/lib/cp.js +57 -16
- package/dist/lib/decide.js +2 -2
- package/dist/lib/defaults.json +2 -0
- package/dist/lib/init.js +7 -7
- package/dist/lib/merge.js +1 -1
- package/dist/lib/prompt.js +1 -1
- package/dist/lib/run/index.js +7 -7
- package/dist/lib/run/prompt.js +1 -1
- package/dist/lib/sandbox/capture.js +10 -8
- package/dist/lib/sandbox/clipboard/bridge.js +59 -21
- package/dist/lib/sandbox/clipboard/inbox.js +82 -0
- package/dist/lib/sandbox/clipboard/index.js +5 -6
- package/dist/lib/sandbox/clipboard/linux.js +91 -0
- package/dist/lib/sandbox/clipboard/paths.js +1 -1
- package/dist/lib/sandbox/clipboard/win32.js +144 -0
- package/dist/lib/sandbox/commands/create.js +106 -88
- package/dist/lib/sandbox/commands/enter.js +9 -9
- package/dist/lib/sandbox/commands/list-running.js +2 -2
- package/dist/lib/sandbox/commands/ls.js +7 -7
- package/dist/lib/sandbox/commands/prune.js +8 -8
- package/dist/lib/sandbox/commands/rebuild.js +38 -50
- package/dist/lib/sandbox/commands/refresh.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +11 -11
- package/dist/lib/sandbox/commands/show.js +4 -4
- package/dist/lib/sandbox/commands/start.js +4 -4
- package/dist/lib/sandbox/commands/vm.js +4 -4
- package/dist/lib/sandbox/config.js +10 -4
- package/dist/lib/sandbox/constants.js +4 -1
- package/dist/lib/sandbox/credentials.js +1 -1
- package/dist/lib/sandbox/dotfiles.js +1 -1
- package/dist/lib/sandbox/engine.js +3 -3
- package/dist/lib/sandbox/engines/index.js +5 -5
- package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
- package/dist/lib/sandbox/image-build.js +80 -0
- package/dist/lib/sandbox/image-prune.js +2 -2
- package/dist/lib/sandbox/index.js +10 -10
- package/dist/lib/sandbox/managed-fs.js +1 -1
- package/dist/lib/sandbox/readme-scaffold.js +1 -1
- package/dist/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/dist/lib/sandbox/shell.js +9 -1
- package/dist/lib/sandbox/tools.js +4 -4
- package/dist/lib/server/adapters/feishu/index.js +10 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +2 -13
- package/dist/lib/server/daemon.js +64 -43
- package/dist/lib/server/display.js +83 -0
- package/dist/lib/server/index.js +2 -2
- package/dist/lib/server/process-control.js +2 -2
- package/dist/lib/server/protocol.js +2 -2
- package/dist/lib/server/streamer.js +5 -4
- package/dist/lib/task/commands/cat.js +2 -2
- package/dist/lib/task/commands/decisions.js +4 -4
- package/dist/lib/task/commands/files.js +3 -3
- package/dist/lib/task/commands/grep.js +3 -3
- package/dist/lib/task/commands/issue-body.js +4 -4
- package/dist/lib/task/commands/log.js +5 -5
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/commands/show.js +1 -1
- package/dist/lib/task/commands/status.js +71 -25
- package/dist/lib/task/index.js +9 -9
- package/dist/lib/task/resolve-ref.js +1 -1
- package/dist/lib/task/workflow-warnings.js +94 -0
- package/dist/lib/update.js +4 -4
- package/lib/cp.ts +56 -15
- package/lib/defaults.json +2 -0
- package/lib/sandbox/capture.ts +5 -3
- package/lib/sandbox/clipboard/bridge.ts +62 -18
- package/lib/sandbox/clipboard/inbox.ts +83 -0
- package/lib/sandbox/clipboard/index.ts +5 -6
- package/lib/sandbox/clipboard/linux.ts +124 -0
- package/lib/sandbox/clipboard/win32.ts +173 -0
- package/lib/sandbox/commands/create.ts +132 -88
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +13 -1
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/lib/sandbox/shell.ts +11 -2
- package/lib/sandbox/tools.ts +5 -5
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +11 -3
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +5 -18
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- package/lib/server/streamer.ts +5 -4
- package/lib/task/commands/log.ts +3 -3
- package/lib/task/commands/status.ts +102 -21
- package/lib/task/workflow-warnings.ts +121 -0
- package/package.json +2 -2
- package/templates/.agents/README.en.md +3 -0
- package/templates/.agents/README.zh-CN.md +3 -0
- package/templates/.agents/rules/create-issue.github.en.md +4 -4
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
- package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
- package/templates/.agents/rules/issue-sync.github.en.md +13 -0
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
- package/templates/.agents/rules/next-step-output.en.md +15 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
- package/templates/.agents/rules/pr-sync.github.en.md +17 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
- package/templates/.agents/rules/review-handshake.en.md +2 -2
- package/templates/.agents/rules/review-handshake.zh-CN.md +2 -2
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/rules/testing-discipline.en.md +3 -41
- package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +213 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
- package/templates/.agents/templates/task.en.md +7 -0
- package/templates/.agents/templates/task.zh-CN.md +7 -0
- package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
- package/templates/.github/workflows/metadata-sync.yml +5 -0
- package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
package/dist/lib/task/index.js
CHANGED
|
@@ -41,48 +41,48 @@ export async function runTask(args) {
|
|
|
41
41
|
}
|
|
42
42
|
switch (subcommand) {
|
|
43
43
|
case 'cat': {
|
|
44
|
-
const { cat } = await import(
|
|
44
|
+
const { cat } = await import('./commands/cat.js');
|
|
45
45
|
cat(rest);
|
|
46
46
|
break;
|
|
47
47
|
}
|
|
48
48
|
case 'decisions':
|
|
49
49
|
case 'd': {
|
|
50
|
-
const { decisions } = await import(
|
|
50
|
+
const { decisions } = await import('./commands/decisions.js');
|
|
51
51
|
decisions(rest);
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
54
54
|
case 'files': {
|
|
55
|
-
const { files } = await import(
|
|
55
|
+
const { files } = await import('./commands/files.js');
|
|
56
56
|
files(rest);
|
|
57
57
|
break;
|
|
58
58
|
}
|
|
59
59
|
case 'grep': {
|
|
60
|
-
const { grep } = await import(
|
|
60
|
+
const { grep } = await import('./commands/grep.js');
|
|
61
61
|
grep(rest);
|
|
62
62
|
break;
|
|
63
63
|
}
|
|
64
64
|
case 'issue-body': {
|
|
65
|
-
const { issueBody } = await import(
|
|
65
|
+
const { issueBody } = await import('./commands/issue-body.js');
|
|
66
66
|
issueBody(rest);
|
|
67
67
|
break;
|
|
68
68
|
}
|
|
69
69
|
case 'log': {
|
|
70
|
-
const { log } = await import(
|
|
70
|
+
const { log } = await import('./commands/log.js');
|
|
71
71
|
log(rest);
|
|
72
72
|
break;
|
|
73
73
|
}
|
|
74
74
|
case 'ls': {
|
|
75
|
-
const { ls } = await import(
|
|
75
|
+
const { ls } = await import('./commands/ls.js');
|
|
76
76
|
ls(rest);
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
79
79
|
case 'show': {
|
|
80
|
-
const { show } = await import(
|
|
80
|
+
const { show } = await import('./commands/show.js');
|
|
81
81
|
show(rest);
|
|
82
82
|
break;
|
|
83
83
|
}
|
|
84
84
|
case 'status': {
|
|
85
|
-
const { status } = await import(
|
|
85
|
+
const { status } = await import('./commands/status.js');
|
|
86
86
|
status(rest);
|
|
87
87
|
break;
|
|
88
88
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
-
import { normalizeShortIdInput } from
|
|
4
|
+
import { normalizeShortIdInput } from './short-id.js';
|
|
5
5
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
6
|
// Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
|
|
7
7
|
// Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { extractSection } from './sections.js';
|
|
2
|
+
const WORKFLOW_WARNING_HEADINGS = ['工作流告警', 'Workflow Warnings'];
|
|
3
|
+
const WORKFLOW_WARNING_STATUSES = new Set(['open', 'resolved', 'ignored']);
|
|
4
|
+
const WORKFLOW_WARNING_SEVERITIES = new Set(['IMPORTANT', 'ACTION_REQUIRED']);
|
|
5
|
+
function splitTableRow(line) {
|
|
6
|
+
let value = line.trim();
|
|
7
|
+
if (!value.startsWith('|'))
|
|
8
|
+
return [];
|
|
9
|
+
value = value.replace(/^\|/, '').replace(/\|$/, '');
|
|
10
|
+
const cells = [];
|
|
11
|
+
let cell = '';
|
|
12
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
13
|
+
const char = value[index];
|
|
14
|
+
if (char === '|' && !isEscapedAt(value, index)) {
|
|
15
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
16
|
+
cell = '';
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
cell += char;
|
|
20
|
+
}
|
|
21
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
22
|
+
return cells;
|
|
23
|
+
}
|
|
24
|
+
function unescapeTableCell(value) {
|
|
25
|
+
let output = '';
|
|
26
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
27
|
+
const char = value[index];
|
|
28
|
+
const next = value[index + 1];
|
|
29
|
+
if (char === '\\' && (next === '\\' || next === '|')) {
|
|
30
|
+
output += next;
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
output += char;
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
function isEscapedAt(value, index) {
|
|
39
|
+
let backslashes = 0;
|
|
40
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor -= 1) {
|
|
41
|
+
backslashes += 1;
|
|
42
|
+
}
|
|
43
|
+
return backslashes % 2 === 1;
|
|
44
|
+
}
|
|
45
|
+
function isSeparatorRow(cells) {
|
|
46
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parses well-formed workflow warning rows. Malformed rows are intentionally
|
|
50
|
+
* ignored here; validate-artifact is responsible for reporting structure errors.
|
|
51
|
+
*/
|
|
52
|
+
function parseWorkflowWarnings(content) {
|
|
53
|
+
const section = extractSection(content, WORKFLOW_WARNING_HEADINGS);
|
|
54
|
+
if (!section)
|
|
55
|
+
return [];
|
|
56
|
+
const rows = [];
|
|
57
|
+
for (const rawLine of section.split(/\r?\n/)) {
|
|
58
|
+
const cells = splitTableRow(rawLine);
|
|
59
|
+
if (cells.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
if ((cells[0] ?? '').toLowerCase() === 'id')
|
|
62
|
+
continue;
|
|
63
|
+
if (isSeparatorRow(cells))
|
|
64
|
+
continue;
|
|
65
|
+
if (cells.length < 11)
|
|
66
|
+
continue;
|
|
67
|
+
rows.push({
|
|
68
|
+
id: cells[0] ?? '',
|
|
69
|
+
time: cells[1] ?? '',
|
|
70
|
+
step: cells[2] ?? '',
|
|
71
|
+
severity: cells[3] ?? '',
|
|
72
|
+
code: cells[4] ?? '',
|
|
73
|
+
status: cells[5] ?? '',
|
|
74
|
+
target: cells[6] ?? '',
|
|
75
|
+
message: cells[7] ?? '',
|
|
76
|
+
action: cells[8] ?? '',
|
|
77
|
+
resolvedAt: cells[9] ?? '',
|
|
78
|
+
resolution: cells[10] ?? ''
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
function getOpenWorkflowWarnings(content) {
|
|
84
|
+
return parseWorkflowWarnings(content).filter((warning) => warning.status === 'open');
|
|
85
|
+
}
|
|
86
|
+
function formatWorkflowWarningSummary(warnings) {
|
|
87
|
+
return warnings.map((warning) => {
|
|
88
|
+
const target = warning.target ? ` ${warning.target}` : '';
|
|
89
|
+
const action = warning.action ? ` - ${warning.action}` : '';
|
|
90
|
+
return `${warning.id} [${warning.severity}] ${warning.code}${target}${action}`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export { WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_STATUSES, WORKFLOW_WARNING_SEVERITIES, parseWorkflowWarnings, getOpenWorkflowWarnings, formatWorkflowWarningSummary };
|
|
94
|
+
//# sourceMappingURL=workflow-warnings.js.map
|
package/dist/lib/update.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { info, ok, err } from
|
|
4
|
-
import { resolveTemplateDir } from
|
|
5
|
-
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from
|
|
6
|
-
import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from
|
|
3
|
+
import { info, ok, err } from './log.js';
|
|
4
|
+
import { resolveTemplateDir } from './paths.js';
|
|
5
|
+
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.js';
|
|
6
|
+
import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from './builtin-tuis.js';
|
|
7
7
|
const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
|
|
8
8
|
const CONFIG_DIR = '.agents';
|
|
9
9
|
const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
|
package/lib/cp.ts
CHANGED
|
@@ -5,8 +5,9 @@ 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
7
|
import { createClipboardAdapter, type ClipboardAdapter } from './sandbox/clipboard/index.ts';
|
|
8
|
+
import { RECEIVER_CAPABILITY, receiveRemoteClipboardPng } from './sandbox/clipboard/inbox.ts';
|
|
8
9
|
|
|
9
|
-
const USAGE = 'Usage: ai cp <ssh-alias>\n\nCopy the local clipboard image (PNG) to a remote macOS
|
|
10
|
+
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';
|
|
10
11
|
const COMMAND_TIMEOUT_MS = 30_000;
|
|
11
12
|
|
|
12
13
|
export type SpawnResult = {
|
|
@@ -65,6 +66,21 @@ export async function cmdCp(args: string[], deps: CpDeps = {}): Promise<number>
|
|
|
65
66
|
writeStderr = (chunk: string) => process.stderr.write(chunk)
|
|
66
67
|
} = deps;
|
|
67
68
|
|
|
69
|
+
if (args.length === 1 && args[0] === '--remote-receive-capability') {
|
|
70
|
+
if (platform === 'linux') writeStdout(`${RECEIVER_CAPABILITY}\n`);
|
|
71
|
+
return platform === 'linux' ? 0 : 1;
|
|
72
|
+
}
|
|
73
|
+
if (args.length === 2 && args[0] === '--remote-receive-v1') {
|
|
74
|
+
try {
|
|
75
|
+
const result = receiveRemoteClipboardPng(process.env.HOME || process.env.USERPROFILE || '', args[1]!, platform);
|
|
76
|
+
writeStdout(`${result}\n`);
|
|
77
|
+
return 0;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
writeStderr(`${error instanceof Error ? error.message : 'clipboard receive failed'}\n`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
69
85
|
writeStdout(USAGE);
|
|
70
86
|
return 0;
|
|
@@ -110,6 +126,35 @@ export async function cmdCp(args: string[], deps: CpDeps = {}): Promise<number>
|
|
|
110
126
|
writeFileFn(localPng, png);
|
|
111
127
|
|
|
112
128
|
remotePath = `/tmp/agent-infra-cp-${randomId()}.png`;
|
|
129
|
+
const remoteSystem = spawnFn('ssh', [
|
|
130
|
+
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'uname', '-s'
|
|
131
|
+
]);
|
|
132
|
+
if (remoteSystem.status !== 0) {
|
|
133
|
+
writeStderr(`failed to detect remote platform on ${alias}:\n${commandDetail(remoteSystem)}\n`);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
const remotePlatform = remoteSystem.stdout.trim();
|
|
137
|
+
if (remotePlatform !== 'Darwin' && remotePlatform !== 'Linux') {
|
|
138
|
+
writeStderr(`unsupported remote platform on ${alias}: ${remotePlatform || 'unknown'}\n`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
if (remotePlatform === 'Linux') {
|
|
142
|
+
const homeCheck = spawnFn('ssh', [
|
|
143
|
+
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'test', '-d', '$HOME/.agent-infra'
|
|
144
|
+
]);
|
|
145
|
+
if (homeCheck.status !== 0) {
|
|
146
|
+
writeStderr(`remote agent-infra installation is missing on ${alias}\n`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
const capability = spawnFn('ssh', [
|
|
150
|
+
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'ai', 'cp', '--remote-receive-capability'
|
|
151
|
+
]);
|
|
152
|
+
if (capability.status !== 0 || capability.stderr !== '' || capability.stdout.replace(/\n$/u, '') !== RECEIVER_CAPABILITY) {
|
|
153
|
+
writeStderr(`remote agent-infra clipboard receiver is unavailable or incompatible on ${alias}; update agent-infra\n`);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
const upload = spawnFn('scp', [
|
|
114
159
|
'-o',
|
|
115
160
|
'BatchMode=yes',
|
|
@@ -124,25 +169,21 @@ export async function cmdCp(args: string[], deps: CpDeps = {}): Promise<number>
|
|
|
124
169
|
}
|
|
125
170
|
uploaded = true;
|
|
126
171
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'-o',
|
|
135
|
-
'ConnectTimeout=10',
|
|
136
|
-
alias,
|
|
137
|
-
'osascript',
|
|
138
|
-
'-'
|
|
139
|
-
], remoteSetScript(remotePath));
|
|
172
|
+
const setRemote = remotePlatform === 'Darwin'
|
|
173
|
+
? spawnFn('ssh', [
|
|
174
|
+
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'osascript', '-'
|
|
175
|
+
], remoteSetScript(remotePath))
|
|
176
|
+
: spawnFn('ssh', [
|
|
177
|
+
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', alias, 'ai', 'cp', '--remote-receive-v1', remotePath
|
|
178
|
+
]);
|
|
140
179
|
if (setRemote.status !== 0) {
|
|
141
180
|
writeStderr(`failed to set remote clipboard on ${alias}:\n${commandDetail(setRemote)}\n`);
|
|
142
181
|
return 1;
|
|
143
182
|
}
|
|
144
183
|
|
|
145
|
-
writeStdout(
|
|
184
|
+
writeStdout(remotePlatform === 'Linux'
|
|
185
|
+
? `uploaded clipboard image to ${alias}; return to the SSH session and press Ctrl+V\n`
|
|
186
|
+
: `copied clipboard image to ${alias}\n`);
|
|
146
187
|
return 0;
|
|
147
188
|
} finally {
|
|
148
189
|
if (uploaded && remotePath) {
|
package/lib/defaults.json
CHANGED
|
@@ -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/lib/sandbox/capture.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { loadConfig } from './config.ts';
|
|
3
3
|
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.ts';
|
|
4
4
|
import { detectEngine } from './engine.ts';
|
|
5
|
+
import { commandForEngine } from './shell.ts';
|
|
5
6
|
import { hostTimezoneEnvFlags, terminalEnvFlags } from './commands/enter.ts';
|
|
6
7
|
import {
|
|
7
8
|
fetchSandboxRows,
|
|
@@ -95,12 +96,12 @@ function buildRunScript(params: {
|
|
|
95
96
|
return `#!/bin/sh
|
|
96
97
|
set -u
|
|
97
98
|
cd /workspace
|
|
98
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
99
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)$/\\1:\\2/' > ${runDir}/started_at
|
|
99
100
|
printf '%s\\n' running > ${runDir}/status
|
|
100
101
|
${command}
|
|
101
102
|
code=$?
|
|
102
103
|
printf '%s\\n' "$code" > ${runDir}/exit_code
|
|
103
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
104
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)$/\\1:\\2/' > ${runDir}/finished_at
|
|
104
105
|
if [ "$code" -eq 0 ]; then
|
|
105
106
|
printf '%s\\n' completed > ${runDir}/status
|
|
106
107
|
else
|
|
@@ -202,7 +203,8 @@ export async function runInSandbox(
|
|
|
202
203
|
'-lc',
|
|
203
204
|
buildTmuxLauncher({ request, runId })
|
|
204
205
|
];
|
|
205
|
-
const
|
|
206
|
+
const command = commandForEngine(engine, 'docker', dockerArgs);
|
|
207
|
+
const result = await (options.spawn ?? spawnCapture)(command.cmd, command.args);
|
|
206
208
|
return {
|
|
207
209
|
...result,
|
|
208
210
|
run: {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
pruneClipboardDir,
|
|
10
10
|
writeClipboardPngAtomic
|
|
11
11
|
} from './paths.ts';
|
|
12
|
+
import { isClipboardInboxReadable, readPendingClipboardPath } from './inbox.ts';
|
|
12
13
|
import { commandForEngine, restoreTerminal, runInteractiveEngine, runOkEngine } from '../shell.ts';
|
|
13
14
|
import { loadNodePty, type NodePty, type PtyProcess } from './node-pty.ts';
|
|
14
15
|
|
|
@@ -30,8 +31,13 @@ type BridgeOptions = {
|
|
|
30
31
|
createDetector?: () => CtrlVDetector;
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
type TextImageClipboardAdapter = ClipboardAdapter & {
|
|
35
|
+
readImageFromText?: (text: string) => Buffer | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
33
38
|
const FALLBACK_PREFIX = 'Warning: clipboard image paste bridge disabled';
|
|
34
39
|
const PARTIAL_ESCAPE_FLUSH_MS = 30;
|
|
40
|
+
const BRACKETED_PASTE_RE = /^\x1b\[200~([\s\S]*)\x1b\[201~$/u;
|
|
35
41
|
|
|
36
42
|
// Node's stdin.setRawMode(true) uses libuv's RAW mode, which (unlike the
|
|
37
43
|
// cfmakeraw that `docker exec -it` applies on the non-bridge path) keeps ONLCR
|
|
@@ -80,13 +86,9 @@ export async function runInteractiveWithClipboardBridge(options: BridgeOptions):
|
|
|
80
86
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
81
87
|
return fallback('host stdin/stdout is not a TTY');
|
|
82
88
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const available = adapter.available();
|
|
87
|
-
if (!available.ok) {
|
|
88
|
-
return fallback(available.reason);
|
|
89
|
-
}
|
|
89
|
+
const available = adapter?.available() ?? { ok: false as const, reason: 'no clipboard adapter available on this platform' };
|
|
90
|
+
const inboxReady = isClipboardInboxReadable(home);
|
|
91
|
+
if (!available.ok && !inboxReady) return fallback(available.reason);
|
|
90
92
|
if (!runOk(engine, 'docker', ['exec', container, 'sh', '-c', '[ -d /clipboard ] && [ -r /clipboard ]'])) {
|
|
91
93
|
return fallback('container /clipboard mount is missing; rebuild the sandbox to enable image paste');
|
|
92
94
|
}
|
|
@@ -114,7 +116,7 @@ export async function runInteractiveWithClipboardBridge(options: BridgeOptions):
|
|
|
114
116
|
return runBridge({
|
|
115
117
|
child,
|
|
116
118
|
home,
|
|
117
|
-
adapter,
|
|
119
|
+
adapter: available.ok ? adapter : null,
|
|
118
120
|
writeStderr,
|
|
119
121
|
stdin,
|
|
120
122
|
stdout,
|
|
@@ -133,7 +135,7 @@ async function runBridge({
|
|
|
133
135
|
}: {
|
|
134
136
|
child: PtyProcess;
|
|
135
137
|
home: string;
|
|
136
|
-
adapter: ClipboardAdapter;
|
|
138
|
+
adapter: ClipboardAdapter | null;
|
|
137
139
|
writeStderr: (chunk: string) => unknown;
|
|
138
140
|
stdin: NodeJS.ReadStream;
|
|
139
141
|
stdout: NodeJS.WriteStream;
|
|
@@ -147,7 +149,7 @@ async function runBridge({
|
|
|
147
149
|
clearFlushTimer();
|
|
148
150
|
for (const token of detector.feed(inputDecoder.write(chunk))) {
|
|
149
151
|
if (token.kind === 'text') {
|
|
150
|
-
|
|
152
|
+
handleText(token.raw, child);
|
|
151
153
|
} else {
|
|
152
154
|
handleCtrlV(token, child);
|
|
153
155
|
}
|
|
@@ -157,7 +159,7 @@ async function runBridge({
|
|
|
157
159
|
flushTimer = null;
|
|
158
160
|
for (const token of detector.flush()) {
|
|
159
161
|
if (token.kind === 'text') {
|
|
160
|
-
|
|
162
|
+
handleText(token.raw, child);
|
|
161
163
|
} else {
|
|
162
164
|
handleCtrlV(token, child);
|
|
163
165
|
}
|
|
@@ -169,22 +171,55 @@ async function runBridge({
|
|
|
169
171
|
const onSigint = () => child.kill('SIGINT');
|
|
170
172
|
const onSigterm = () => child.kill('SIGTERM');
|
|
171
173
|
|
|
174
|
+
function writeImagePaste(png: Buffer, target: PtyProcess): void {
|
|
175
|
+
const filename = pngClipboardFilename(png);
|
|
176
|
+
writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
|
|
177
|
+
pruneClipboardDir(clipboardHostDir(home));
|
|
178
|
+
target.write(buildBracketedPaste(containerClipboardPath(filename)));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleText(raw: string, target: PtyProcess): void {
|
|
182
|
+
const textAdapter = adapter as TextImageClipboardAdapter | null;
|
|
183
|
+
const pastedText = extractSinglePathPaste(raw);
|
|
184
|
+
if (!textAdapter || !textAdapter.readImageFromText || pastedText === null) {
|
|
185
|
+
target.write(raw);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const png = textAdapter.readImageFromText(pastedText);
|
|
191
|
+
if (!png) {
|
|
192
|
+
target.write(raw);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
writeImagePaste(png, target);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
target.write(raw);
|
|
198
|
+
if (!warnedPasteFailure) {
|
|
199
|
+
warnedPasteFailure = true;
|
|
200
|
+
writeStderr(`Warning: clipboard image paste failed; forwarded pasted text (${error instanceof Error ? error.message : 'unknown error'})\n`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
172
205
|
function handleCtrlV(match: CtrlVMatch, target: PtyProcess): void {
|
|
173
206
|
try {
|
|
207
|
+
const pendingPath = readPendingClipboardPath(home);
|
|
208
|
+
if (pendingPath) {
|
|
209
|
+
target.write(buildBracketedPaste(pendingPath));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
174
212
|
// readImagePng returns null both for "no image on clipboard" and for
|
|
175
213
|
// unexpected read failures; both cases forward the original Ctrl+V so
|
|
176
214
|
// the container app handles it as a regular keystroke. The throw branch
|
|
177
215
|
// below only fires on truly unexpected exceptions (e.g. fs write
|
|
178
216
|
// errors writing to the host clipboard dir).
|
|
179
|
-
const png = adapter
|
|
217
|
+
const png = adapter?.readImagePng() ?? null;
|
|
180
218
|
if (!png) {
|
|
181
219
|
target.write(match.raw);
|
|
182
220
|
return;
|
|
183
221
|
}
|
|
184
|
-
|
|
185
|
-
writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
|
|
186
|
-
pruneClipboardDir(clipboardHostDir(home));
|
|
187
|
-
target.write(buildBracketedPaste(containerClipboardPath(filename)));
|
|
222
|
+
writeImagePaste(png, target);
|
|
188
223
|
} catch (error) {
|
|
189
224
|
target.write(match.raw);
|
|
190
225
|
if (!warnedPasteFailure) {
|
|
@@ -219,14 +254,14 @@ async function runBridge({
|
|
|
219
254
|
try {
|
|
220
255
|
for (const token of detector.feed(inputDecoder.end())) {
|
|
221
256
|
if (token.kind === 'text') {
|
|
222
|
-
|
|
257
|
+
handleText(token.raw, child);
|
|
223
258
|
} else {
|
|
224
259
|
handleCtrlV(token, child);
|
|
225
260
|
}
|
|
226
261
|
}
|
|
227
262
|
for (const token of detector.flush()) {
|
|
228
263
|
if (token.kind === 'text') {
|
|
229
|
-
|
|
264
|
+
handleText(token.raw, child);
|
|
230
265
|
}
|
|
231
266
|
}
|
|
232
267
|
} catch {
|
|
@@ -244,6 +279,15 @@ async function runBridge({
|
|
|
244
279
|
}
|
|
245
280
|
}
|
|
246
281
|
|
|
282
|
+
function extractSinglePathPaste(raw: string): string | null {
|
|
283
|
+
const match = raw.match(BRACKETED_PASTE_RE);
|
|
284
|
+
const text = (match ? match[1] : raw)?.trim();
|
|
285
|
+
if (!text || /[\r\n]/u.test(text)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return text;
|
|
289
|
+
}
|
|
290
|
+
|
|
247
291
|
function onceExit(
|
|
248
292
|
child: PtyProcess,
|
|
249
293
|
stdin: NodeJS.ReadStream
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
clipboardHostDir,
|
|
5
|
+
containerClipboardPath,
|
|
6
|
+
pngClipboardFilename,
|
|
7
|
+
pruneClipboardDir,
|
|
8
|
+
writeClipboardPngAtomic
|
|
9
|
+
} from './paths.ts';
|
|
10
|
+
|
|
11
|
+
export const RECEIVER_CAPABILITY = 'AGENT_INFRA_CLIPBOARD_RECEIVER:v1';
|
|
12
|
+
const MARKER = '.pending.json';
|
|
13
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
14
|
+
const HASHED_PNG = /^[a-f0-9]{16}\.png$/u;
|
|
15
|
+
const REMOTE_TEMP = /^agent-infra-cp-[0-9a-f-]{36}\.png$/u;
|
|
16
|
+
|
|
17
|
+
export function isClipboardInboxReadable(home: string): boolean {
|
|
18
|
+
const dir = clipboardHostDir(home);
|
|
19
|
+
try {
|
|
20
|
+
fs.accessSync(dir, fs.constants.R_OK);
|
|
21
|
+
return fs.statSync(dir).isDirectory();
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function receiveRemoteClipboardPng(home: string, tempPath: string, platformName: NodeJS.Platform = process.platform): string {
|
|
28
|
+
if (platformName !== 'linux') throw new Error('remote clipboard receiver requires Linux');
|
|
29
|
+
if (path.dirname(tempPath) !== '/tmp' || !REMOTE_TEMP.test(path.basename(tempPath))) {
|
|
30
|
+
throw new Error('invalid clipboard upload path');
|
|
31
|
+
}
|
|
32
|
+
const signature = Buffer.alloc(PNG_SIGNATURE.length);
|
|
33
|
+
const fd = fs.openSync(tempPath, 'r');
|
|
34
|
+
let signatureBytes: number;
|
|
35
|
+
try {
|
|
36
|
+
signatureBytes = fs.readSync(fd, signature, 0, signature.length, 0);
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
if (signatureBytes !== PNG_SIGNATURE.length || !signature.equals(PNG_SIGNATURE)) {
|
|
41
|
+
throw new Error('uploaded clipboard file is not a PNG');
|
|
42
|
+
}
|
|
43
|
+
const png = fs.readFileSync(tempPath);
|
|
44
|
+
const dir = clipboardHostDir(home);
|
|
45
|
+
if (!fs.existsSync(path.dirname(dir))) throw new Error('agent-infra is not installed on this host');
|
|
46
|
+
const filename = pngClipboardFilename(png);
|
|
47
|
+
writeClipboardPngAtomic(dir, filename, png);
|
|
48
|
+
pruneClipboardDir(dir);
|
|
49
|
+
const markerTmp = path.join(dir, `.${MARKER}.${process.pid}.tmp`);
|
|
50
|
+
fs.writeFileSync(markerTmp, JSON.stringify({ version: 1, filename, createdAt: new Date().toISOString() }) + '\n', { mode: 0o600 });
|
|
51
|
+
fs.renameSync(markerTmp, path.join(dir, MARKER));
|
|
52
|
+
return containerClipboardPath(filename);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function readPendingClipboardPath(home: string): string | null {
|
|
56
|
+
const dir = clipboardHostDir(home);
|
|
57
|
+
const markerPath = path.join(dir, MARKER);
|
|
58
|
+
let markerText: string;
|
|
59
|
+
try {
|
|
60
|
+
markerText = fs.readFileSync(markerPath, 'utf8');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
63
|
+
throw new Error('invalid clipboard pending marker', { cause: error });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let marker: { version?: unknown; filename?: unknown };
|
|
67
|
+
try {
|
|
68
|
+
marker = JSON.parse(markerText) as { version?: unknown; filename?: unknown };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new Error('invalid clipboard pending marker', { cause: error });
|
|
71
|
+
}
|
|
72
|
+
if (marker.version !== 1 || typeof marker.filename !== 'string' || !HASHED_PNG.test(marker.filename)) {
|
|
73
|
+
throw new Error('invalid clipboard pending marker');
|
|
74
|
+
}
|
|
75
|
+
const filePath = path.join(dir, marker.filename);
|
|
76
|
+
try {
|
|
77
|
+
const stat = fs.lstatSync(filePath);
|
|
78
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('pending clipboard image is not a regular file');
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error('invalid clipboard pending marker', { cause: error });
|
|
81
|
+
}
|
|
82
|
+
return containerClipboardPath(marker.filename);
|
|
83
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { platform } from 'node:os';
|
|
2
2
|
import { createDarwinClipboardAdapter, type DarwinClipboardAdapter } from './darwin.ts';
|
|
3
|
+
import { createLinuxClipboardAdapter, type LinuxClipboardAdapter } from './linux.ts';
|
|
4
|
+
import { createWin32ClipboardAdapter, type Win32ClipboardAdapter } from './win32.ts';
|
|
3
5
|
|
|
4
|
-
export type ClipboardAdapter = DarwinClipboardAdapter;
|
|
6
|
+
export type ClipboardAdapter = DarwinClipboardAdapter | LinuxClipboardAdapter | Win32ClipboardAdapter;
|
|
5
7
|
|
|
6
8
|
export function createClipboardAdapter({
|
|
7
9
|
platformName = platform()
|
|
@@ -10,12 +12,9 @@ export function createClipboardAdapter({
|
|
|
10
12
|
case 'darwin':
|
|
11
13
|
return createDarwinClipboardAdapter();
|
|
12
14
|
case 'linux':
|
|
13
|
-
|
|
14
|
-
// see Issue #386 follow-up. Returning null disables the bridge for now.
|
|
15
|
-
return null;
|
|
15
|
+
return createLinuxClipboardAdapter();
|
|
16
16
|
case 'win32':
|
|
17
|
-
|
|
18
|
-
return null;
|
|
17
|
+
return createWin32ClipboardAdapter();
|
|
19
18
|
default:
|
|
20
19
|
return null;
|
|
21
20
|
}
|