@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
|
@@ -1,40 +1,27 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
import * as p from '@clack/prompts';
|
|
4
3
|
import pc from 'picocolors';
|
|
5
4
|
import { loadConfig } from '../config.ts';
|
|
6
5
|
import type { SandboxConfig } from '../config.ts';
|
|
7
6
|
import { prepareDockerfile } from '../dockerfile.ts';
|
|
8
|
-
import {
|
|
7
|
+
import { sandboxImageRefreshLabel } from '../constants.ts';
|
|
9
8
|
import { detectEngine, ensureDocker } from '../engine.ts';
|
|
10
|
-
import { runEngine, runSafeEngine, runVerboseEngine } from '../shell.ts';
|
|
9
|
+
import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from '../shell.ts';
|
|
11
10
|
import { pruneSandboxDanglingImages } from '../image-prune.ts';
|
|
12
|
-
import {
|
|
13
|
-
imageSignatureFields,
|
|
14
|
-
resolveTools,
|
|
15
|
-
toolNpmPackagesArg,
|
|
16
|
-
toolShellInstallScriptBase64
|
|
17
|
-
} from '../tools.ts';
|
|
11
|
+
import { resolveTools } from '../tools.ts';
|
|
18
12
|
import type { SandboxTool } from '../tools.ts';
|
|
19
|
-
import {
|
|
20
|
-
|
|
13
|
+
import {
|
|
14
|
+
buildImageSignature,
|
|
15
|
+
buildSandboxImageArgs,
|
|
16
|
+
parseImageLabels,
|
|
17
|
+
parseRefreshTimestamp
|
|
18
|
+
} from '../image-build.ts';
|
|
21
19
|
|
|
22
20
|
const USAGE = `Usage: ai sandbox rebuild [--quiet] [--refresh]`;
|
|
23
21
|
|
|
24
|
-
type PreparedDockerfile = ReturnType<typeof prepareDockerfile>;
|
|
25
22
|
type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => string;
|
|
26
23
|
type EngineRunSafeFn = EngineRunFn;
|
|
27
24
|
|
|
28
|
-
function buildSignature(preparedDockerfile: PreparedDockerfile, tools: SandboxTool[]): string {
|
|
29
|
-
return createHash('sha256')
|
|
30
|
-
.update(JSON.stringify({
|
|
31
|
-
dockerfile: preparedDockerfile.signature,
|
|
32
|
-
tools: imageSignatureFields(tools)
|
|
33
|
-
}))
|
|
34
|
-
.digest('hex')
|
|
35
|
-
.slice(0, 12);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
export function buildArgs(
|
|
39
26
|
config: SandboxConfig,
|
|
40
27
|
tools: SandboxTool[],
|
|
@@ -45,49 +32,41 @@ export function buildArgs(
|
|
|
45
32
|
runFn = runEngine,
|
|
46
33
|
runSafeFn = runSafeEngine,
|
|
47
34
|
env = process.env,
|
|
48
|
-
refresh = false
|
|
35
|
+
refresh = false,
|
|
36
|
+
lastRefresh
|
|
49
37
|
}: {
|
|
50
38
|
engine?: string;
|
|
51
39
|
runFn?: EngineRunFn;
|
|
52
40
|
runSafeFn?: EngineRunSafeFn;
|
|
53
41
|
env?: NodeJS.ProcessEnv;
|
|
54
42
|
refresh?: boolean;
|
|
43
|
+
lastRefresh?: number;
|
|
55
44
|
} = {}
|
|
56
45
|
): string[] {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
engine: selectedEngine,
|
|
46
|
+
return buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
47
|
+
engine: engine ?? detectEngine(config),
|
|
60
48
|
runFn,
|
|
61
49
|
runSafeFn,
|
|
62
|
-
env
|
|
50
|
+
env,
|
|
51
|
+
refresh,
|
|
52
|
+
lastRefresh
|
|
63
53
|
});
|
|
54
|
+
}
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
config.imageName,
|
|
69
|
-
'--build-arg',
|
|
70
|
-
`HOST_UID=${hostUid}`,
|
|
71
|
-
'--build-arg',
|
|
72
|
-
`HOST_GID=${hostGid}`,
|
|
73
|
-
'--build-arg',
|
|
74
|
-
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
75
|
-
'--build-arg',
|
|
76
|
-
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
77
|
-
'--label',
|
|
78
|
-
sandboxLabel(config),
|
|
79
|
-
'--label',
|
|
80
|
-
`${sandboxImageConfigLabel(config)}=${imageSignature}`,
|
|
81
|
-
'-f',
|
|
82
|
-
toEnginePath(selectedEngine, dockerfilePath),
|
|
83
|
-
toEnginePath(selectedEngine, config.repoRoot)
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
if (refresh) {
|
|
87
|
-
args.splice(1, 0, '--no-cache', '--pull');
|
|
56
|
+
function readExistingLastRefresh(config: SandboxConfig, engine: string): number {
|
|
57
|
+
if (!runOkEngine(engine, 'docker', ['image', 'inspect', config.imageName])) {
|
|
58
|
+
return 0;
|
|
88
59
|
}
|
|
89
60
|
|
|
90
|
-
|
|
61
|
+
const labels = parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
62
|
+
'image',
|
|
63
|
+
'inspect',
|
|
64
|
+
'--format',
|
|
65
|
+
'{{ json .Config.Labels }}',
|
|
66
|
+
config.imageName
|
|
67
|
+
]));
|
|
68
|
+
|
|
69
|
+
return parseRefreshTimestamp(labels[sandboxImageRefreshLabel(config)] ?? '');
|
|
91
70
|
}
|
|
92
71
|
|
|
93
72
|
export async function rebuild(args: string[]): Promise<void> {
|
|
@@ -110,19 +89,24 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
110
89
|
const config = loadConfig();
|
|
111
90
|
const tools = resolveTools(config);
|
|
112
91
|
const preparedDockerfile = prepareDockerfile(config);
|
|
113
|
-
const imageSignature =
|
|
92
|
+
const imageSignature = buildImageSignature(preparedDockerfile, tools);
|
|
114
93
|
const quiet = values.quiet ?? false;
|
|
115
94
|
const refresh = values.refresh ?? false;
|
|
116
95
|
const engine = detectEngine(config);
|
|
117
96
|
|
|
118
97
|
await ensureDocker(config, undefined);
|
|
98
|
+
const lastRefresh = refresh ? Date.now() : readExistingLastRefresh(config, engine);
|
|
119
99
|
p.intro(pc.cyan('Rebuilding sandbox image'));
|
|
120
100
|
|
|
121
101
|
try {
|
|
122
102
|
if (quiet) {
|
|
123
103
|
const spinner = p.spinner();
|
|
124
104
|
spinner.start('Building image...');
|
|
125
|
-
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
105
|
+
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
106
|
+
engine,
|
|
107
|
+
refresh,
|
|
108
|
+
lastRefresh
|
|
109
|
+
}), {
|
|
126
110
|
cwd: config.repoRoot
|
|
127
111
|
});
|
|
128
112
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
@@ -131,7 +115,11 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
131
115
|
runVerboseEngine(
|
|
132
116
|
engine,
|
|
133
117
|
'docker',
|
|
134
|
-
buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
118
|
+
buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
119
|
+
engine,
|
|
120
|
+
refresh,
|
|
121
|
+
lastRefresh
|
|
122
|
+
}),
|
|
135
123
|
{ cwd: config.repoRoot }
|
|
136
124
|
);
|
|
137
125
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULTS = Object.freeze({
|
|
|
13
13
|
engine: null,
|
|
14
14
|
runtimes: ['node22'],
|
|
15
15
|
tools: ['agent-infra', 'claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
16
|
+
refreshIntervalDays: 7,
|
|
16
17
|
dockerfile: null,
|
|
17
18
|
vm: {
|
|
18
19
|
cpu: null,
|
|
@@ -29,6 +30,7 @@ type SandboxConfigInput = {
|
|
|
29
30
|
runtimes?: string[];
|
|
30
31
|
tools?: string[];
|
|
31
32
|
customTools?: unknown;
|
|
33
|
+
refreshIntervalDays?: unknown;
|
|
32
34
|
dockerfile?: string | null;
|
|
33
35
|
vm?: Record<string, unknown>;
|
|
34
36
|
};
|
|
@@ -55,6 +57,7 @@ export type SandboxConfig = {
|
|
|
55
57
|
runtimes: string[];
|
|
56
58
|
tools: string[];
|
|
57
59
|
customTools: SandboxTool[];
|
|
60
|
+
refreshIntervalDays: number;
|
|
58
61
|
dockerfile: string | null;
|
|
59
62
|
vm: SandboxVmConfig;
|
|
60
63
|
};
|
|
@@ -80,11 +83,16 @@ function asPositiveNumberOrNull(value: unknown): number | null {
|
|
|
80
83
|
return typeof value === 'number' ? value : null;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
function
|
|
86
|
+
function asNonNegativeIntegerOrDefault(value: unknown, fallback: number): number {
|
|
87
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cloneDefaults(): SandboxConfigInput & { vm: SandboxVmConfig; runtimes: string[]; tools: string[]; refreshIntervalDays: number } {
|
|
84
91
|
return {
|
|
85
92
|
engine: DEFAULTS.engine,
|
|
86
93
|
runtimes: [...DEFAULTS.runtimes],
|
|
87
94
|
tools: [...DEFAULTS.tools],
|
|
95
|
+
refreshIntervalDays: DEFAULTS.refreshIntervalDays,
|
|
88
96
|
dockerfile: DEFAULTS.dockerfile,
|
|
89
97
|
vm: { ...DEFAULTS.vm }
|
|
90
98
|
};
|
|
@@ -160,6 +168,10 @@ export function loadConfig({
|
|
|
160
168
|
? [...sandbox.tools]
|
|
161
169
|
: defaults.tools,
|
|
162
170
|
customTools,
|
|
171
|
+
refreshIntervalDays: asNonNegativeIntegerOrDefault(
|
|
172
|
+
sandbox.refreshIntervalDays,
|
|
173
|
+
defaults.refreshIntervalDays
|
|
174
|
+
),
|
|
163
175
|
dockerfile,
|
|
164
176
|
vm: {
|
|
165
177
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
package/lib/sandbox/constants.ts
CHANGED
|
@@ -107,6 +107,10 @@ export function sandboxImageConfigLabel(config: Pick<SandboxPathConfig, 'project
|
|
|
107
107
|
return `${sandboxLabel(config)}.image-config`;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
export function sandboxImageRefreshLabel(config: Pick<SandboxPathConfig, 'project'>): string {
|
|
111
|
+
return `${sandboxLabel(config)}.last-refresh`;
|
|
112
|
+
}
|
|
113
|
+
|
|
110
114
|
export function parsePositiveIntegerOption(value: unknown, optionName: string): number | undefined {
|
|
111
115
|
if (value === undefined || value === null) {
|
|
112
116
|
return undefined;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { detectEngine } from './engine.ts';
|
|
3
|
+
import { resolveBuildUid } from './engines/native.ts';
|
|
4
|
+
import { toEnginePath } from './engines/wsl2-paths.ts';
|
|
5
|
+
import { sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel } from './constants.ts';
|
|
6
|
+
import { runEngine, runSafeEngine } from './shell.ts';
|
|
7
|
+
import {
|
|
8
|
+
imageSignatureFields,
|
|
9
|
+
toolNpmPackagesArg,
|
|
10
|
+
toolShellInstallScriptBase64
|
|
11
|
+
} from './tools.ts';
|
|
12
|
+
import type { SandboxTool } from './tools.ts';
|
|
13
|
+
|
|
14
|
+
type PreparedDockerfileSignature = {
|
|
15
|
+
signature: unknown;
|
|
16
|
+
};
|
|
17
|
+
type BuildImageConfig = {
|
|
18
|
+
project: string;
|
|
19
|
+
imageName: string;
|
|
20
|
+
repoRoot: string;
|
|
21
|
+
engine?: string | null;
|
|
22
|
+
};
|
|
23
|
+
type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => string;
|
|
24
|
+
type EngineRunSafeFn = EngineRunFn;
|
|
25
|
+
|
|
26
|
+
export function buildImageSignature(preparedDockerfile: PreparedDockerfileSignature, tools: SandboxTool[]): string {
|
|
27
|
+
return createHash('sha256')
|
|
28
|
+
.update(JSON.stringify({
|
|
29
|
+
dockerfile: preparedDockerfile.signature,
|
|
30
|
+
tools: imageSignatureFields(tools)
|
|
31
|
+
}))
|
|
32
|
+
.digest('hex')
|
|
33
|
+
.slice(0, 12);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildSandboxImageArgs(
|
|
37
|
+
config: BuildImageConfig,
|
|
38
|
+
tools: SandboxTool[],
|
|
39
|
+
dockerfilePath: string,
|
|
40
|
+
imageSignature: string,
|
|
41
|
+
{
|
|
42
|
+
engine,
|
|
43
|
+
runFn = runEngine,
|
|
44
|
+
runSafeFn = runSafeEngine,
|
|
45
|
+
env = process.env,
|
|
46
|
+
refresh = false,
|
|
47
|
+
lastRefresh
|
|
48
|
+
}: {
|
|
49
|
+
engine?: string;
|
|
50
|
+
runFn?: EngineRunFn;
|
|
51
|
+
runSafeFn?: EngineRunSafeFn;
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
refresh?: boolean;
|
|
54
|
+
lastRefresh?: number;
|
|
55
|
+
} = {}
|
|
56
|
+
): string[] {
|
|
57
|
+
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
58
|
+
const { uid: hostUid, gid: hostGid } = resolveBuildUid({
|
|
59
|
+
engine: selectedEngine,
|
|
60
|
+
runFn,
|
|
61
|
+
runSafeFn,
|
|
62
|
+
env
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const args = [
|
|
66
|
+
'build',
|
|
67
|
+
'-t',
|
|
68
|
+
config.imageName,
|
|
69
|
+
'--build-arg',
|
|
70
|
+
`HOST_UID=${hostUid}`,
|
|
71
|
+
'--build-arg',
|
|
72
|
+
`HOST_GID=${hostGid}`,
|
|
73
|
+
'--build-arg',
|
|
74
|
+
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
75
|
+
'--build-arg',
|
|
76
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
77
|
+
'--label',
|
|
78
|
+
sandboxLabel(config),
|
|
79
|
+
'--label',
|
|
80
|
+
`${sandboxImageConfigLabel(config)}=${imageSignature}`
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
if (lastRefresh !== undefined) {
|
|
84
|
+
args.push('--label', `${sandboxImageRefreshLabel(config)}=${lastRefresh}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
args.push(
|
|
88
|
+
'-f',
|
|
89
|
+
toEnginePath(selectedEngine, dockerfilePath),
|
|
90
|
+
toEnginePath(selectedEngine, config.repoRoot)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (refresh) {
|
|
94
|
+
args.splice(1, 0, '--no-cache', '--pull');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return args;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function parseRefreshTimestamp(value: string): number {
|
|
101
|
+
const parsed = Number(value);
|
|
102
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function parseImageLabels(raw: string): Record<string, string> {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
108
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
return Object.fromEntries(
|
|
112
|
+
Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
|
113
|
+
);
|
|
114
|
+
} catch {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function isRefreshDisabled(env: NodeJS.ProcessEnv, noRefreshFlag: boolean): boolean {
|
|
120
|
+
if (noRefreshFlag) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const value = env.AI_SANDBOX_NO_REFRESH?.trim().toLowerCase();
|
|
125
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function isRefreshDue(lastRefresh: number, now: number, intervalDays: number): boolean {
|
|
129
|
+
if (intervalDays <= 0 || lastRefresh > now) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return now - lastRefresh >= intervalDays * 24 * 60 * 60 * 1000;
|
|
134
|
+
}
|
|
@@ -12,6 +12,9 @@ RUN if [ "${HOST_UID}" = "0" ]; then \
|
|
|
12
12
|
(groupadd -o -g ${HOST_GID} devuser || true) && \
|
|
13
13
|
useradd -o -u ${HOST_UID} -g ${HOST_GID} -m -s /bin/bash devuser; \
|
|
14
14
|
else \
|
|
15
|
+
if ubuntu_uid="$(id -u ubuntu 2>/dev/null)" && [ "${ubuntu_uid}" = "${HOST_UID}" ]; then \
|
|
16
|
+
userdel -r ubuntu || exit 1; \
|
|
17
|
+
fi; \
|
|
15
18
|
(groupadd -g ${HOST_GID} devuser || true) && \
|
|
16
19
|
useradd -u ${HOST_UID} -g ${HOST_GID} -m -s /bin/bash devuser; \
|
|
17
20
|
fi
|
package/lib/sandbox/shell.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { execFileSync, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import type { ExecFileSyncOptions, StdioOptions, SpawnSyncOptions, SpawnSyncReturns } from 'node:child_process';
|
|
5
|
+
import { getAdapter } from './engines/index.ts';
|
|
5
6
|
|
|
6
7
|
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
7
8
|
|
|
@@ -52,7 +53,7 @@ function resolveCommand(cmd: string): string {
|
|
|
52
53
|
return cmd;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
function commandOptions<T extends
|
|
56
|
+
function commandOptions<T extends object>(cmd: string, opts: T): T | (T & { shell: true }) {
|
|
56
57
|
if (process.platform === 'win32' && /\.(?:bat|cmd)$/i.test(cmd)) {
|
|
57
58
|
return { ...opts, shell: true };
|
|
58
59
|
}
|
|
@@ -143,6 +144,13 @@ export function commandForEngine(engine: string, cmd: string, args: string[] = [
|
|
|
143
144
|
return { cmd: resolvedWrapper, args: ['--', cmd, ...args] };
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
if (cmd === 'docker') {
|
|
148
|
+
const context = getAdapter(engine).dockerContext;
|
|
149
|
+
if (context) {
|
|
150
|
+
return { cmd, args: ['--context', context, ...args] };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
146
154
|
return { cmd, args };
|
|
147
155
|
}
|
|
148
156
|
|
|
@@ -153,7 +161,8 @@ export function runEngine(engine: string, cmd: string, args: string[], opts: Com
|
|
|
153
161
|
|
|
154
162
|
export function execEngine(engine: string, cmd: string, args: string[], opts: ExecFileSyncOptions = {}) {
|
|
155
163
|
const command = commandForEngine(engine, cmd, args);
|
|
156
|
-
|
|
164
|
+
const resolved = resolveCommand(command.cmd);
|
|
165
|
+
return execFileSync(resolved, command.args, commandOptions(resolved, opts));
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
export function runOkEngine(engine: string, cmd: string, args: string[], opts: CommandOptions = {}): boolean {
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -22,9 +22,9 @@ export type SandboxTool = {
|
|
|
22
22
|
// When set, containerMount is mounted as an in-container tmpfs (RAM) instead
|
|
23
23
|
// of bind-mounting the host config dir, keeping high-churn tool logs off the
|
|
24
24
|
// host disk. `seed` lists the host-dir entries (relative to the tool's config
|
|
25
|
-
// dir) to
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
// dir) to copy into the tmpfs when the container starts. It is an explicit
|
|
26
|
+
// allowlist so runtime files (e.g. logs_2.sqlite, sessions) left in the host
|
|
27
|
+
// dir stay out of the container and seeded config cannot write back.
|
|
28
28
|
tmpfs?: { size?: string; seed?: string[] };
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -80,8 +80,8 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
80
80
|
// codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
|
|
81
81
|
// a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
|
|
82
82
|
// codex home as tmpfs so those logs stay in RAM and die with the container.
|
|
83
|
-
//
|
|
84
|
-
//
|
|
83
|
+
// Seeded config (config.toml, model-catalogs) is copied into the tmpfs at
|
|
84
|
+
// startup; runtime files like logs_2.sqlite stay in RAM.
|
|
85
85
|
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
86
86
|
hostLiveMounts: [
|
|
87
87
|
{ hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ServerConfig } from '../config.ts';
|
|
2
|
+
import type { OutboundMessage } from '../display.ts';
|
|
2
3
|
import type { Logger } from '../logger.ts';
|
|
3
4
|
|
|
4
5
|
// Adapter contract for agent-infra-server.
|
|
@@ -19,6 +20,7 @@ export type InboundMessage = {
|
|
|
19
20
|
messageId: string;
|
|
20
21
|
raw: unknown;
|
|
21
22
|
reply: (text: string) => Promise<void>;
|
|
23
|
+
replyDisplay?: (message: OutboundMessage) => Promise<void>;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
// Runtime context passed to every adapter's start(). dispatch() is registered
|
|
@@ -36,6 +38,7 @@ export type Adapter = {
|
|
|
36
38
|
start: (ctx: AdapterCtx) => Promise<void>;
|
|
37
39
|
stop: () => Promise<void>;
|
|
38
40
|
sendMessage: (target: { chatId: string }, text: string) => Promise<void>;
|
|
41
|
+
sendDisplayMessage?: (target: { chatId: string }, message: OutboundMessage) => Promise<void>;
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
// The shape of an adapter module's default export (a factory).
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Adapter, AdapterCtx, AdapterFactory, InboundMessage } from '../_contract.ts';
|
|
2
|
-
import {
|
|
2
|
+
import type { OutboundMessage } from '../../display.ts';
|
|
3
|
+
import { renderFeishuMessage } from './renderer.ts';
|
|
4
|
+
import { createFeishuTransport, normalizeMessage } from './transport.ts';
|
|
3
5
|
import type { FeishuTransport } from './transport.ts';
|
|
4
6
|
|
|
5
7
|
// Assemble the feishu adapter. The transport is injectable so unit tests can
|
|
@@ -26,7 +28,10 @@ export function createFeishuAdapter(
|
|
|
26
28
|
messageId: normalized.messageId,
|
|
27
29
|
raw: normalized.raw,
|
|
28
30
|
reply: async (text) => {
|
|
29
|
-
await transport.send(normalized.chatId,
|
|
31
|
+
await transport.send(normalized.chatId, renderFeishuMessage(text));
|
|
32
|
+
},
|
|
33
|
+
replyDisplay: async (message: OutboundMessage) => {
|
|
34
|
+
await transport.send(normalized.chatId, renderFeishuMessage(message));
|
|
30
35
|
}
|
|
31
36
|
};
|
|
32
37
|
await adapterCtx.dispatch(message);
|
|
@@ -39,7 +44,10 @@ export function createFeishuAdapter(
|
|
|
39
44
|
await transport.stop();
|
|
40
45
|
},
|
|
41
46
|
async sendMessage(target, text) {
|
|
42
|
-
await transport.send(target.chatId,
|
|
47
|
+
await transport.send(target.chatId, renderFeishuMessage(text));
|
|
48
|
+
},
|
|
49
|
+
async sendDisplayMessage(target, message) {
|
|
50
|
+
await transport.send(target.chatId, renderFeishuMessage(message));
|
|
43
51
|
}
|
|
44
52
|
};
|
|
45
53
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
|
+
import { normalizeOutbound, outboundToText, type DisplayMessage, type OutboundMessage } from '../../display.ts';
|
|
3
|
+
import type { FeishuMessagePayload } from './transport.ts';
|
|
4
|
+
|
|
5
|
+
const FEISHU_LARK_MD_MAX_LENGTH = 20_000;
|
|
6
|
+
|
|
7
|
+
type FeishuElement =
|
|
8
|
+
| { tag: 'div'; text: { tag: 'lark_md'; content: string } }
|
|
9
|
+
| { tag: 'div'; fields: Array<{ is_short: boolean; text: { tag: 'lark_md'; content: string } }> };
|
|
10
|
+
|
|
11
|
+
function templateForTone(tone: 'info' | 'success' | 'warning' | 'danger' | 'running'): string {
|
|
12
|
+
if (tone === 'success') return 'green';
|
|
13
|
+
if (tone === 'warning') return 'yellow';
|
|
14
|
+
if (tone === 'danger') return 'red';
|
|
15
|
+
return 'blue';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function templateForResult(exitCode: number | null): string {
|
|
19
|
+
return exitCode === 0 ? 'green' : 'red';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function cleanFeishuText(text: string): string {
|
|
23
|
+
return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function clampLarkMarkdown(text: string): string {
|
|
27
|
+
const clean = cleanFeishuText(text);
|
|
28
|
+
if (clean.length <= FEISHU_LARK_MD_MAX_LENGTH) return clean;
|
|
29
|
+
return `${clean.slice(0, FEISHU_LARK_MD_MAX_LENGTH - 18)}\n\n...(truncated)`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function div(content: string): FeishuElement {
|
|
33
|
+
return { tag: 'div', text: { tag: 'lark_md', content: clampLarkMarkdown(content) } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fields(rows: [string, string][]): FeishuElement {
|
|
37
|
+
return {
|
|
38
|
+
tag: 'div',
|
|
39
|
+
fields: rows.map(([label, value]) => ({
|
|
40
|
+
is_short: true,
|
|
41
|
+
text: { tag: 'lark_md', content: clampLarkMarkdown(`**${label}**\n${value}`) }
|
|
42
|
+
}))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function card(title: string, template: string, elements: FeishuElement[]): FeishuMessagePayload {
|
|
47
|
+
return {
|
|
48
|
+
msg_type: 'interactive',
|
|
49
|
+
content: JSON.stringify({
|
|
50
|
+
config: { wide_screen_mode: true },
|
|
51
|
+
header: { title: { tag: 'plain_text', content: cleanFeishuText(title) }, template },
|
|
52
|
+
elements
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function tableMarkdown(message: Extract<DisplayMessage, { kind: 'table' }>): string {
|
|
58
|
+
return outboundToText(message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderStatus(message: Extract<DisplayMessage, { kind: 'status-card' }>): FeishuMessagePayload {
|
|
62
|
+
const elements: FeishuElement[] = [];
|
|
63
|
+
if (message.fields && message.fields.length > 0) elements.push(fields(message.fields));
|
|
64
|
+
if (message.body) elements.push(div(message.body));
|
|
65
|
+
if (elements.length === 0) elements.push(div(message.title));
|
|
66
|
+
return card(message.title, templateForTone(message.tone), elements);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderStream(message: Extract<DisplayMessage, { kind: 'stream-event' }>): FeishuMessagePayload {
|
|
70
|
+
const template = message.phase === 'finished' ? templateForResult(message.exitCode ?? null) : 'blue';
|
|
71
|
+
return card(message.title, template, [div(outboundToText(message))]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderCommandResult(message: Extract<DisplayMessage, { kind: 'command-result' }>): FeishuMessagePayload {
|
|
75
|
+
return card(message.title, templateForResult(message.exitCode), [div(outboundToText(message))]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderUnknownDisplayKind(message: never): FeishuMessagePayload {
|
|
79
|
+
return card('agent-infra', 'blue', [div(`[unknown display kind: ${String((message as { kind?: unknown }).kind)}]`)]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function renderFeishuMessage(message: OutboundMessage): FeishuMessagePayload {
|
|
83
|
+
const normalized = normalizeOutbound(message);
|
|
84
|
+
switch (normalized.kind) {
|
|
85
|
+
case 'text':
|
|
86
|
+
return card('agent-infra', 'blue', [div(normalized.text)]);
|
|
87
|
+
case 'markdown':
|
|
88
|
+
return card('agent-infra', 'blue', [div(normalized.markdown)]);
|
|
89
|
+
case 'table':
|
|
90
|
+
return card(normalized.title ?? 'agent-infra', 'blue', [div(tableMarkdown(normalized))]);
|
|
91
|
+
case 'status-card':
|
|
92
|
+
return renderStatus(normalized);
|
|
93
|
+
case 'stream-event':
|
|
94
|
+
return renderStream(normalized);
|
|
95
|
+
case 'command-result':
|
|
96
|
+
return renderCommandResult(normalized);
|
|
97
|
+
}
|
|
98
|
+
return renderUnknownDisplayKind(normalized);
|
|
99
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
|
-
import { stripVTControlCharacters } from 'node:util';
|
|
3
2
|
|
|
4
3
|
// Transport layer for the feishu adapter. All @larksuiteoapi/node-sdk surface is
|
|
5
4
|
// confined here so the adapter body (index.ts) depends only on this narrow
|
|
@@ -11,10 +10,10 @@ export type FeishuTransport = {
|
|
|
11
10
|
// each inbound im.message.receive_v1.
|
|
12
11
|
start: (onMessage: (raw: unknown) => Promise<void>) => Promise<void>;
|
|
13
12
|
stop: () => Promise<void>;
|
|
14
|
-
send: (chatId: string, message:
|
|
13
|
+
send: (chatId: string, message: FeishuMessagePayload) => Promise<void>;
|
|
15
14
|
};
|
|
16
15
|
|
|
17
|
-
export type
|
|
16
|
+
export type FeishuMessagePayload = { msg_type: 'interactive'; content: string };
|
|
18
17
|
|
|
19
18
|
type FeishuCreateData = {
|
|
20
19
|
receive_id: string;
|
|
@@ -78,23 +77,11 @@ function resolveDomain(value: unknown): number {
|
|
|
78
77
|
return value === 'lark' || value === 'Lark' ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
export function
|
|
82
|
-
return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function cardMessage(text: string): FeishuOutgoingMessage {
|
|
86
|
-
return { kind: 'interactive', title: 'agent-infra', text: cleanFeishuText(text) };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function toFeishuCreateData(chatId: string, message: FeishuOutgoingMessage): FeishuCreateData {
|
|
80
|
+
export function toFeishuCreateData(chatId: string, message: FeishuMessagePayload): FeishuCreateData {
|
|
90
81
|
return {
|
|
91
82
|
receive_id: chatId,
|
|
92
|
-
msg_type:
|
|
93
|
-
content:
|
|
94
|
-
config: { wide_screen_mode: true },
|
|
95
|
-
header: { title: { tag: 'plain_text', content: message.title }, template: 'blue' },
|
|
96
|
-
elements: [{ tag: 'div', text: { tag: 'lark_md', content: message.text } }]
|
|
97
|
-
})
|
|
83
|
+
msg_type: message.msg_type,
|
|
84
|
+
content: message.content
|
|
98
85
|
};
|
|
99
86
|
}
|
|
100
87
|
|