toolcraft 0.0.23 → 0.0.25
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.md +2 -2
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +50 -13
- package/dist/error-report.js +32 -3
- package/dist/human-in-loop/approval-tasks.d.ts +1 -0
- package/dist/human-in-loop/approval-tasks.js +7 -5
- package/dist/human-in-loop/approvals-commands.js +51 -8
- package/dist/human-in-loop/runner.js +24 -19
- package/dist/human-in-loop/state-machine.d.ts +3 -3
- package/dist/human-in-loop/state-machine.js +13 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/mcp-proxy.js +85 -19
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +50 -8
- package/dist/renderer.js +119 -13
- package/dist/sdk.compile-check.js +1 -0
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +56 -11
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
- package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
- package/node_modules/@poe-code/agent-defs/package.json +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
- package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
- package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
- package/node_modules/@poe-code/config-mutations/package.json +1 -1
- package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
- package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
- package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
- package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
- package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/design-system/dist/index.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
- package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
- package/node_modules/@poe-code/design-system/package.json +2 -1
- package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/docker/args.js +11 -3
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +377 -130
- package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +78 -10
- package/node_modules/@poe-code/process-runner/dist/docker/env-file.d.ts +6 -0
- package/node_modules/@poe-code/process-runner/dist/docker/env-file.js +49 -0
- package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
- package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +21 -5
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +6 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +61 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +503 -0
- package/node_modules/@poe-code/process-runner/package.json +1 -1
- package/node_modules/@poe-code/task-list/README.md +0 -2
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
- package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/task-list/dist/index.js +2 -0
- package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/move.js +215 -0
- package/node_modules/@poe-code/task-list/dist/open.js +3 -4
- package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
- package/node_modules/@poe-code/task-list/dist/state.js +9 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
- package/node_modules/@poe-code/task-list/package.json +1 -2
- package/node_modules/auth-store/dist/create-secret-store.js +4 -1
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +8 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +104 -8
- package/node_modules/auth-store/dist/index.d.ts +1 -1
- package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
- package/node_modules/auth-store/dist/keychain-store.js +18 -16
- package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
- package/node_modules/auth-store/dist/provider-store.js +55 -7
- package/node_modules/auth-store/dist/types.d.ts +3 -1
- package/node_modules/auth-store/package.json +2 -1
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
- package/node_modules/mcp-oauth/package.json +1 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +9 -4
- package/node_modules/tiny-mcp-client/dist/internal.js +244 -66
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +287 -76
- package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +32 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
- package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
- package/package.json +10 -12
- package/node_modules/@poe-code/file-lock/README.md +0 -52
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
- package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
- package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
- package/node_modules/@poe-code/file-lock/package.json +0 -23
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { buildDockerRunArgs } from "./args.js";
|
|
6
|
+
import { buildDockerEnvArgs, buildDockerRunArgs } from "./args.js";
|
|
7
7
|
import { buildContextArgs, detectContext } from "./context.js";
|
|
8
8
|
import { detectEngine } from "./engine.js";
|
|
9
|
+
import { createDockerEnvFile } from "./env-file.js";
|
|
9
10
|
import { createHostRunner } from "../host/host-runner.js";
|
|
11
|
+
import { downloadWorkspace as downloadTransferredWorkspace, uploadWorkspace as uploadTransferredWorkspace } from "../workspace-transfer.js";
|
|
10
12
|
const containerCommand = ["sh", "-c", "while :; do sleep 3600; done"];
|
|
11
13
|
export const dockerExecutionEnvFactory = {
|
|
12
14
|
type: "docker",
|
|
@@ -53,144 +55,98 @@ export const dockerExecutionEnvFactory = {
|
|
|
53
55
|
});
|
|
54
56
|
},
|
|
55
57
|
async attach(envId, context) {
|
|
56
|
-
const
|
|
58
|
+
const reattachContext = parseDockerReattachContext(context?.reattachContext);
|
|
59
|
+
const engine = reattachContext?.engine ?? detectEngine();
|
|
57
60
|
return createDockerEnv({
|
|
58
61
|
id: envId,
|
|
59
62
|
spec: createAttachedSpec(context?.cwd),
|
|
60
63
|
runner: createHostRunner(),
|
|
61
64
|
engine,
|
|
62
|
-
context: detectContext(),
|
|
65
|
+
context: reattachContext === undefined ? detectContext() : reattachContext.context,
|
|
63
66
|
attachedJobId: context?.jobId
|
|
64
67
|
});
|
|
65
68
|
}
|
|
66
69
|
};
|
|
67
70
|
function createDockerEnv(input) {
|
|
68
71
|
const containerRef = input.id;
|
|
72
|
+
const workspaceTransferEnv = {
|
|
73
|
+
cwd: input.spec.cwd,
|
|
74
|
+
uploadDir: "/tmp/poe-workspace-transfer",
|
|
75
|
+
workspaceDir: input.spec.cwd,
|
|
76
|
+
remoteFs: createContainerWorkspaceFileSystem(input)
|
|
77
|
+
};
|
|
78
|
+
let detachedJobContext = input.attachedJobId === undefined
|
|
79
|
+
? null
|
|
80
|
+
: { id: input.attachedJobId, tool: input.spec.jobLabel.tool, argv: input.spec.jobLabel.argv };
|
|
69
81
|
return {
|
|
70
82
|
id: containerRef,
|
|
83
|
+
reattachContext: { engine: input.engine, context: input.context },
|
|
71
84
|
job: input.attachedJobId === undefined
|
|
72
85
|
? null
|
|
73
|
-
: createContainerJob(containerRef, input.runner, input.engine, input.context,
|
|
86
|
+
: createContainerJob(containerRef, input.runner, input.engine, input.context, detachedJobContext),
|
|
87
|
+
setDetachedJobContext(context) {
|
|
88
|
+
detachedJobContext = context;
|
|
89
|
+
},
|
|
74
90
|
async uploadWorkspace() {
|
|
75
|
-
|
|
76
|
-
const archivePath = path.join(tempDir, "workspace.tar");
|
|
77
|
-
try {
|
|
78
|
-
const excludeArgs = input.spec.uploadIgnoreFiles.flatMap((ignored) => [
|
|
79
|
-
"--exclude",
|
|
80
|
-
ignored
|
|
81
|
-
]);
|
|
82
|
-
const tarArgs = [...excludeArgs, "-cf", archivePath, "-C", input.spec.cwd, "."];
|
|
83
|
-
await runOrThrow(input.runner, {
|
|
84
|
-
command: "tar",
|
|
85
|
-
args: tarArgs,
|
|
86
|
-
stdout: "pipe",
|
|
87
|
-
stderr: "pipe"
|
|
88
|
-
});
|
|
89
|
-
await runOrThrow(input.runner, {
|
|
90
|
-
command: input.engine,
|
|
91
|
-
args: [
|
|
92
|
-
...buildContextArgs(input.engine, input.context),
|
|
93
|
-
"cp",
|
|
94
|
-
archivePath,
|
|
95
|
-
`${containerRef}:/tmp/poe-workspace-upload.tar`
|
|
96
|
-
],
|
|
97
|
-
stdout: "pipe",
|
|
98
|
-
stderr: "pipe"
|
|
99
|
-
});
|
|
100
|
-
await runOrThrow(input.runner, {
|
|
101
|
-
command: input.engine,
|
|
102
|
-
args: [
|
|
103
|
-
...buildContextArgs(input.engine, input.context),
|
|
104
|
-
"exec",
|
|
105
|
-
containerRef,
|
|
106
|
-
"sh",
|
|
107
|
-
"-c",
|
|
108
|
-
`mkdir -p ${shellQuote(input.spec.cwd)} && tar -xf /tmp/poe-workspace-upload.tar -C ${shellQuote(input.spec.cwd)}`
|
|
109
|
-
],
|
|
110
|
-
stdout: "pipe",
|
|
111
|
-
stderr: "pipe"
|
|
112
|
-
});
|
|
91
|
+
if (readRunnerSync(input.spec.runner) === "none") {
|
|
113
92
|
return { files: 0, bytes: 0, skipped: [] };
|
|
114
93
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
return uploadTransferredWorkspace(workspaceTransferEnv, {
|
|
95
|
+
runner: readWorkspaceTransferRunner(input.spec.runner),
|
|
96
|
+
workspaceExclude: input.spec.uploadIgnoreFiles
|
|
97
|
+
});
|
|
118
98
|
},
|
|
119
99
|
async downloadWorkspace(opts) {
|
|
120
|
-
const
|
|
121
|
-
|
|
100
|
+
const sync = readRunnerSync(input.spec.runner);
|
|
101
|
+
if (sync === "upload" || sync === "none") {
|
|
102
|
+
return { files: 0, bytes: 0, conflicts: [] };
|
|
103
|
+
}
|
|
104
|
+
return downloadTransferredWorkspace(workspaceTransferEnv, opts);
|
|
105
|
+
},
|
|
106
|
+
exec(spec) {
|
|
107
|
+
const envFile = createDockerEnvFile(spec.env);
|
|
122
108
|
try {
|
|
123
|
-
|
|
109
|
+
return cleanUpEnvFileAfterRun(input.runner.exec({
|
|
124
110
|
command: input.engine,
|
|
125
111
|
args: [
|
|
126
112
|
...buildContextArgs(input.engine, input.context),
|
|
127
113
|
"exec",
|
|
114
|
+
...(spec.stdin === "pipe" || spec.stdin === "inherit" ? ["-i"] : []),
|
|
115
|
+
...(spec.tty === true ? ["-t"] : []),
|
|
116
|
+
...(spec.cwd !== undefined ? ["-w", spec.cwd] : []),
|
|
117
|
+
...buildDockerEnvArgs({ env: spec.env, envFilePath: envFile?.path }),
|
|
128
118
|
containerRef,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
`tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote(input.spec.cwd)} .`
|
|
132
|
-
],
|
|
133
|
-
stdout: "pipe",
|
|
134
|
-
stderr: "pipe"
|
|
135
|
-
});
|
|
136
|
-
await runOrThrow(input.runner, {
|
|
137
|
-
command: input.engine,
|
|
138
|
-
args: [
|
|
139
|
-
...buildContextArgs(input.engine, input.context),
|
|
140
|
-
"cp",
|
|
141
|
-
`${containerRef}:/tmp/poe-workspace-download.tar`,
|
|
142
|
-
archivePath
|
|
119
|
+
spec.command,
|
|
120
|
+
...(spec.args ?? [])
|
|
143
121
|
],
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
stdout: "pipe",
|
|
152
|
-
stderr: "pipe"
|
|
153
|
-
});
|
|
154
|
-
return { files: 0, bytes: 0, conflicts: [] };
|
|
122
|
+
stdin: spec.stdin,
|
|
123
|
+
stdout: spec.stdout,
|
|
124
|
+
stderr: spec.stderr,
|
|
125
|
+
tty: spec.tty,
|
|
126
|
+
signal: spec.signal,
|
|
127
|
+
killProcessGroup: spec.killProcessGroup
|
|
128
|
+
}), envFile?.cleanup);
|
|
155
129
|
}
|
|
156
|
-
|
|
157
|
-
|
|
130
|
+
catch (error) {
|
|
131
|
+
envFile?.cleanup();
|
|
132
|
+
throw error;
|
|
158
133
|
}
|
|
159
134
|
},
|
|
160
|
-
exec(spec) {
|
|
161
|
-
return input.runner.exec({
|
|
162
|
-
command: input.engine,
|
|
163
|
-
args: [
|
|
164
|
-
...buildContextArgs(input.engine, input.context),
|
|
165
|
-
"exec",
|
|
166
|
-
...(spec.stdin === "pipe" || spec.stdin === "inherit" ? ["-i"] : []),
|
|
167
|
-
...(spec.tty === true ? ["-t"] : []),
|
|
168
|
-
...(spec.cwd !== undefined ? ["-w", spec.cwd] : []),
|
|
169
|
-
...buildEnvArgs(spec.env),
|
|
170
|
-
containerRef,
|
|
171
|
-
spec.command,
|
|
172
|
-
...(spec.args ?? [])
|
|
173
|
-
],
|
|
174
|
-
stdin: spec.stdin,
|
|
175
|
-
stdout: spec.stdout,
|
|
176
|
-
stderr: spec.stderr,
|
|
177
|
-
tty: spec.tty
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
135
|
async detach() {
|
|
181
|
-
return createContainerJob(containerRef, input.runner, input.engine, input.context);
|
|
136
|
+
return createContainerJob(containerRef, input.runner, input.engine, input.context, detachedJobContext);
|
|
182
137
|
},
|
|
183
138
|
shell() {
|
|
184
139
|
const shellSpec = input.spec.shellSpec;
|
|
185
140
|
return this.exec({
|
|
186
141
|
command: shellSpec?.command ?? input.spec.env.SHELL ?? "sh",
|
|
187
142
|
...(shellSpec?.args ? { args: shellSpec.args } : {}),
|
|
188
|
-
cwd: input.spec.cwd,
|
|
143
|
+
cwd: shellSpec?.cwd ?? input.spec.cwd,
|
|
189
144
|
env: shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env,
|
|
190
145
|
stdin: "inherit",
|
|
191
146
|
stdout: "inherit",
|
|
192
147
|
stderr: "inherit",
|
|
193
|
-
tty: true
|
|
148
|
+
tty: true,
|
|
149
|
+
signal: shellSpec?.signal
|
|
194
150
|
});
|
|
195
151
|
},
|
|
196
152
|
async close() {
|
|
@@ -203,6 +159,14 @@ function createDockerEnv(input) {
|
|
|
203
159
|
}
|
|
204
160
|
};
|
|
205
161
|
}
|
|
162
|
+
function parseDockerReattachContext(value) {
|
|
163
|
+
if (value !== undefined &&
|
|
164
|
+
(value.engine === "docker" || value.engine === "podman") &&
|
|
165
|
+
(value.context === null || typeof value.context === "string")) {
|
|
166
|
+
return { engine: value.engine, context: value.context };
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
206
170
|
async function resolveImage(input) {
|
|
207
171
|
if (input.runtime.image !== undefined) {
|
|
208
172
|
return input.runtime.image;
|
|
@@ -222,9 +186,10 @@ export async function buildDockerRuntimeTemplate(input) {
|
|
|
222
186
|
const dockerfilePath = path.resolve(input.cwd, input.runtime.dockerfile ?? path.join(".poe-code", "Dockerfile"));
|
|
223
187
|
const buildContext = path.resolve(input.cwd, input.runtime.build_context ?? ".");
|
|
224
188
|
const dockerfileBytes = await readFile(dockerfilePath);
|
|
225
|
-
const
|
|
189
|
+
const buildContextFiles = await readBuildContextFiles(buildContext);
|
|
190
|
+
const hash = hashDockerTemplate(dockerfileBytes, buildContextFiles, input.runtime.build_args ?? {}, engine);
|
|
226
191
|
const cached = input.force ? null : await input.state?.templates.get("docker", hash);
|
|
227
|
-
if (cached?.image !== undefined) {
|
|
192
|
+
if (cached?.image !== undefined && (await imageExists(runner, engine, context, cached.image))) {
|
|
228
193
|
return {
|
|
229
194
|
backend: "docker",
|
|
230
195
|
hash,
|
|
@@ -256,10 +221,18 @@ export async function buildDockerRuntimeTemplate(input) {
|
|
|
256
221
|
cached: false
|
|
257
222
|
};
|
|
258
223
|
}
|
|
259
|
-
function hashDockerTemplate(dockerfileBytes, buildArgs) {
|
|
224
|
+
function hashDockerTemplate(dockerfileBytes, buildContextFiles, buildArgs, engine) {
|
|
260
225
|
const hash = createHash("sha256");
|
|
261
226
|
hash.update(dockerfileBytes);
|
|
262
227
|
hash.update("\0");
|
|
228
|
+
hash.update(engine);
|
|
229
|
+
hash.update("\0");
|
|
230
|
+
for (const file of buildContextFiles) {
|
|
231
|
+
hash.update(file.relativePath);
|
|
232
|
+
hash.update("\0");
|
|
233
|
+
hash.update(file.bytes);
|
|
234
|
+
hash.update("\0");
|
|
235
|
+
}
|
|
263
236
|
for (const [key, value] of sortedBuildArgs(buildArgs)) {
|
|
264
237
|
hash.update(key);
|
|
265
238
|
hash.update("=");
|
|
@@ -268,6 +241,39 @@ function hashDockerTemplate(dockerfileBytes, buildArgs) {
|
|
|
268
241
|
}
|
|
269
242
|
return hash.digest("hex");
|
|
270
243
|
}
|
|
244
|
+
async function readBuildContextFiles(buildContext) {
|
|
245
|
+
const files = [];
|
|
246
|
+
await collectBuildContextFiles(buildContext, "", files);
|
|
247
|
+
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
248
|
+
}
|
|
249
|
+
async function collectBuildContextFiles(buildContext, relativeDir, files) {
|
|
250
|
+
const absoluteDir = path.join(buildContext, relativeDir);
|
|
251
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
const relativePath = path.join(relativeDir, entry.name);
|
|
254
|
+
if (entry.isDirectory()) {
|
|
255
|
+
await collectBuildContextFiles(buildContext, relativePath, files);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (!entry.isFile()) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
files.push({
|
|
262
|
+
relativePath: relativePath.split(path.sep).join("/"),
|
|
263
|
+
bytes: await readFile(path.join(buildContext, relativePath))
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function imageExists(runner, engine, context, image) {
|
|
268
|
+
const handle = runner.exec({
|
|
269
|
+
command: engine,
|
|
270
|
+
args: [...buildContextArgs(engine, context), "image", "inspect", image],
|
|
271
|
+
stdout: "pipe",
|
|
272
|
+
stderr: "pipe"
|
|
273
|
+
});
|
|
274
|
+
const result = await handle.result;
|
|
275
|
+
return result.exitCode === 0;
|
|
276
|
+
}
|
|
271
277
|
async function buildImage(input) {
|
|
272
278
|
await runOrThrow(input.runner, {
|
|
273
279
|
command: input.engine,
|
|
@@ -310,9 +316,49 @@ async function runAndRead(runner, spec) {
|
|
|
310
316
|
}
|
|
311
317
|
return output;
|
|
312
318
|
}
|
|
319
|
+
async function runAndReadBytes(runner, spec) {
|
|
320
|
+
const handle = runner.exec(spec);
|
|
321
|
+
const stdout = readStreamBytes(handle.stdout);
|
|
322
|
+
const stderr = readStream(handle.stderr);
|
|
323
|
+
const result = await handle.result;
|
|
324
|
+
const output = await stdout;
|
|
325
|
+
if (result.exitCode !== 0) {
|
|
326
|
+
const errorOutput = await stderr;
|
|
327
|
+
throw new Error(`Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}${errorOutput ? `\n${errorOutput}` : ""}`);
|
|
328
|
+
}
|
|
329
|
+
return output;
|
|
330
|
+
}
|
|
313
331
|
async function runOrThrow(runner, spec) {
|
|
314
332
|
await runAndRead(runner, spec);
|
|
315
333
|
}
|
|
334
|
+
function cleanUpEnvFileAfterRun(handle, cleanup) {
|
|
335
|
+
if (cleanup === undefined) {
|
|
336
|
+
return handle;
|
|
337
|
+
}
|
|
338
|
+
let cleanedUp = false;
|
|
339
|
+
const cleanupOnce = () => {
|
|
340
|
+
if (cleanedUp) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
cleanedUp = true;
|
|
344
|
+
try {
|
|
345
|
+
cleanup();
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Cleanup is best effort; preserve the command result.
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
pid: handle.pid,
|
|
353
|
+
stdin: handle.stdin,
|
|
354
|
+
stdout: handle.stdout,
|
|
355
|
+
stderr: handle.stderr,
|
|
356
|
+
result: handle.result.finally(cleanupOnce),
|
|
357
|
+
kill(signal) {
|
|
358
|
+
handle.kill(signal);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
316
362
|
async function readStream(stream) {
|
|
317
363
|
if (stream === null) {
|
|
318
364
|
return "";
|
|
@@ -324,25 +370,139 @@ async function readStream(stream) {
|
|
|
324
370
|
}
|
|
325
371
|
return chunks.join("");
|
|
326
372
|
}
|
|
373
|
+
async function readStreamBytes(stream) {
|
|
374
|
+
if (stream === null) {
|
|
375
|
+
return Buffer.alloc(0);
|
|
376
|
+
}
|
|
377
|
+
const chunks = [];
|
|
378
|
+
for await (const chunk of stream) {
|
|
379
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : Buffer.from(chunk));
|
|
380
|
+
}
|
|
381
|
+
return Buffer.concat(chunks);
|
|
382
|
+
}
|
|
327
383
|
function sortedBuildArgs(buildArgs) {
|
|
328
384
|
return Object.entries(buildArgs).sort(([left], [right]) => left.localeCompare(right));
|
|
329
385
|
}
|
|
330
|
-
function buildEnvArgs(env) {
|
|
331
|
-
if (env === undefined) {
|
|
332
|
-
return [];
|
|
333
|
-
}
|
|
334
|
-
return Object.entries(env).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
|
|
335
|
-
}
|
|
336
386
|
function createContainerName() {
|
|
337
387
|
return `poe-env-${randomBytes(6).toString("hex")}`;
|
|
338
388
|
}
|
|
339
|
-
function
|
|
389
|
+
function readRunnerSync(runner) {
|
|
390
|
+
if (typeof runner !== "object" || runner === null || !("sync" in runner)) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
const sync = runner.sync;
|
|
394
|
+
return sync === "both" || sync === "upload" || sync === "none" ? sync : undefined;
|
|
395
|
+
}
|
|
396
|
+
function readWorkspaceTransferRunner(runner) {
|
|
397
|
+
if (typeof runner !== "object" || runner === null) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
const record = runner;
|
|
401
|
+
const uploadMaxFileMb = typeof record.upload_max_file_mb === "number" ? record.upload_max_file_mb : undefined;
|
|
402
|
+
const workspace = typeof record.workspace === "object" && record.workspace !== null
|
|
403
|
+
&& Array.isArray(record.workspace.exclude)
|
|
404
|
+
? { exclude: record.workspace.exclude.filter((value) => typeof value === "string") }
|
|
405
|
+
: undefined;
|
|
406
|
+
return { ...(uploadMaxFileMb === undefined ? {} : { upload_max_file_mb: uploadMaxFileMb }), ...(workspace === undefined ? {} : { workspace }) };
|
|
407
|
+
}
|
|
408
|
+
function createContainerWorkspaceFileSystem(input) {
|
|
409
|
+
const execShell = (command) => runAndRead(input.runner, {
|
|
410
|
+
command: input.engine,
|
|
411
|
+
args: [...buildContextArgs(input.engine, input.context), "exec", input.id, "sh", "-c", command],
|
|
412
|
+
stdout: "pipe",
|
|
413
|
+
stderr: "pipe"
|
|
414
|
+
});
|
|
415
|
+
async function readRemoteFile(targetPath) {
|
|
416
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-read-"));
|
|
417
|
+
const destinationPath = path.join(tempDir, "content");
|
|
418
|
+
try {
|
|
419
|
+
await runOrThrow(input.runner, {
|
|
420
|
+
command: input.engine,
|
|
421
|
+
args: [...buildContextArgs(input.engine, input.context), "cp", `${input.id}:${targetPath}`, destinationPath],
|
|
422
|
+
stdout: "pipe",
|
|
423
|
+
stderr: "pipe"
|
|
424
|
+
});
|
|
425
|
+
return await readFile(destinationPath);
|
|
426
|
+
}
|
|
427
|
+
finally {
|
|
428
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function readFileFromContainer(targetPath, encoding) {
|
|
432
|
+
const contents = await readRemoteFile(targetPath);
|
|
433
|
+
return encoding === undefined ? contents : contents.toString(encoding);
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
async mkdir(targetPath) {
|
|
437
|
+
await execShell(`mkdir -p ${shellQuote(targetPath)}`);
|
|
438
|
+
},
|
|
439
|
+
async readdir(targetPath) {
|
|
440
|
+
const quotedTargetPath = shellQuote(targetPath);
|
|
441
|
+
const output = await execShell([
|
|
442
|
+
`for item in ${quotedTargetPath}/* ${quotedTargetPath}/.[!.]* ${quotedTargetPath}/..?*; do`,
|
|
443
|
+
`[ -e "$item" ] || [ -L "$item" ] || continue;`,
|
|
444
|
+
`if [ -L "$item" ]; then kind=l; size=0;`,
|
|
445
|
+
`elif [ -d "$item" ]; then kind=d; size=0;`,
|
|
446
|
+
`elif [ -f "$item" ]; then kind=f; size=$(wc -c < "$item");`,
|
|
447
|
+
`else continue; fi;`,
|
|
448
|
+
`printf '%s\\t%s\\t%s\\n' "\${item##*/}" "$kind" "$size";`,
|
|
449
|
+
`done`
|
|
450
|
+
].join(" "));
|
|
451
|
+
return output.split("\n").filter(Boolean).map((line) => {
|
|
452
|
+
const [name = "", kind = "f"] = line.split("\t");
|
|
453
|
+
return {
|
|
454
|
+
name,
|
|
455
|
+
isFile: () => kind === "f",
|
|
456
|
+
isDirectory: () => kind === "d",
|
|
457
|
+
isSymbolicLink: () => kind === "l"
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
},
|
|
461
|
+
readFile: readFileFromContainer,
|
|
462
|
+
async writeFile(targetPath, data) {
|
|
463
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-write-"));
|
|
464
|
+
const sourcePath = path.join(tempDir, "content");
|
|
465
|
+
try {
|
|
466
|
+
await writeFile(sourcePath, data);
|
|
467
|
+
await runOrThrow(input.runner, {
|
|
468
|
+
command: input.engine,
|
|
469
|
+
args: [...buildContextArgs(input.engine, input.context), "cp", sourcePath, `${input.id}:${targetPath}`],
|
|
470
|
+
stdout: "pipe",
|
|
471
|
+
stderr: "pipe"
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
async stat(targetPath) {
|
|
479
|
+
const output = await execShell(`if [ -d ${shellQuote(targetPath)} ]; then printf 'd\\t0'; elif [ -f ${shellQuote(targetPath)} ]; then printf 'f\\t'; wc -c < ${shellQuote(targetPath)}; else printf 'missing'; fi`);
|
|
480
|
+
if (output.trim() === "missing") {
|
|
481
|
+
throw Object.assign(new Error(`ENOENT: ${targetPath}`), { code: "ENOENT" });
|
|
482
|
+
}
|
|
483
|
+
const [kind = "f", rawSize = "0"] = output.trim().split("\t");
|
|
484
|
+
return { size: Number(rawSize.trim()), isFile: () => kind === "f", isDirectory: () => kind === "d" };
|
|
485
|
+
},
|
|
486
|
+
async rename(oldPath, newPath) {
|
|
487
|
+
await execShell(`mv ${shellQuote(oldPath)} ${shellQuote(newPath)}`);
|
|
488
|
+
},
|
|
489
|
+
async rm(targetPath) {
|
|
490
|
+
await execShell(`rm -rf ${shellQuote(targetPath)}`);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function createContainerJob(containerId, runner, engine, context, detachedJobContext = null) {
|
|
495
|
+
const jobId = detachedJobContext?.id ?? containerId;
|
|
340
496
|
return {
|
|
341
497
|
id: jobId,
|
|
342
498
|
envId: containerId,
|
|
343
|
-
tool: "docker",
|
|
344
|
-
argv: ["attach", containerId],
|
|
499
|
+
tool: detachedJobContext?.tool ?? "docker",
|
|
500
|
+
argv: detachedJobContext?.argv ?? ["attach", containerId],
|
|
345
501
|
async status() {
|
|
502
|
+
if (detachedJobContext !== null) {
|
|
503
|
+
const exitCode = await readDetachedExitCode(containerId, jobId, runner, engine, context);
|
|
504
|
+
return exitCode === null ? "running" : "exited";
|
|
505
|
+
}
|
|
346
506
|
const handle = runner.exec({
|
|
347
507
|
command: engine,
|
|
348
508
|
args: [
|
|
@@ -360,29 +520,59 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
360
520
|
if (result.exitCode !== 0) {
|
|
361
521
|
return "lost";
|
|
362
522
|
}
|
|
363
|
-
return stdout.trim() === "
|
|
523
|
+
return stdout.trim() === "exited" ? "exited" : "running";
|
|
364
524
|
},
|
|
365
525
|
async *stream(opts) {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
526
|
+
const logFile = shellQuote(`/tmp/poe-jobs/${jobId}.log`);
|
|
527
|
+
const sinceCondition = opts?.since === undefined
|
|
528
|
+
? ""
|
|
529
|
+
: ` && test $(stat -c %Y ${logFile} 2>/dev/null || stat -f %m ${logFile}) -ge ${Math.ceil(opts.since.getTime() / 1000)}`;
|
|
530
|
+
let byteOffset = opts?.sinceByte ?? 0;
|
|
531
|
+
let pendingBytes = Buffer.alloc(0);
|
|
532
|
+
let pendingByteOffset = byteOffset;
|
|
533
|
+
while (true) {
|
|
534
|
+
const stdout = await runAndReadBytes(runner, {
|
|
535
|
+
command: engine,
|
|
536
|
+
args: [
|
|
537
|
+
...buildContextArgs(engine, context),
|
|
538
|
+
"exec",
|
|
539
|
+
containerId,
|
|
540
|
+
"sh",
|
|
541
|
+
"-c",
|
|
542
|
+
`test -f ${logFile}${sinceCondition} && tail -c +${byteOffset + 1} ${logFile} || true`
|
|
543
|
+
],
|
|
544
|
+
stdout: "pipe",
|
|
545
|
+
stderr: "pipe"
|
|
546
|
+
});
|
|
547
|
+
if (stdout.byteLength > 0) {
|
|
548
|
+
const combined = pendingBytes.byteLength === 0
|
|
549
|
+
? stdout
|
|
550
|
+
: Buffer.concat([pendingBytes, stdout]);
|
|
551
|
+
const completeLength = completeUtf8PrefixLength(combined);
|
|
552
|
+
byteOffset += stdout.byteLength;
|
|
553
|
+
pendingBytes = combined.subarray(completeLength);
|
|
554
|
+
const data = combined.subarray(0, completeLength).toString("utf8");
|
|
555
|
+
if (data.length > 0) {
|
|
556
|
+
yield { byteOffset: pendingByteOffset, data };
|
|
557
|
+
pendingByteOffset += completeLength;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (opts?.follow !== true || (await this.status()) !== "running") {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
383
564
|
}
|
|
384
565
|
},
|
|
385
566
|
async wait() {
|
|
567
|
+
if (detachedJobContext !== null) {
|
|
568
|
+
while (true) {
|
|
569
|
+
const exitCode = await readDetachedExitCode(containerId, jobId, runner, engine, context);
|
|
570
|
+
if (exitCode !== null) {
|
|
571
|
+
return { exitCode };
|
|
572
|
+
}
|
|
573
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
386
576
|
const handle = runner.exec({
|
|
387
577
|
command: engine,
|
|
388
578
|
args: [...buildContextArgs(engine, context), "wait", containerId],
|
|
@@ -391,7 +581,8 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
391
581
|
});
|
|
392
582
|
const stdout = await readStream(handle.stdout);
|
|
393
583
|
const result = await handle.result;
|
|
394
|
-
|
|
584
|
+
const exitCode = Number.parseInt(stdout.trim(), 10);
|
|
585
|
+
return { exitCode: Number.isNaN(exitCode) ? result.exitCode : exitCode };
|
|
395
586
|
},
|
|
396
587
|
async kill(signal) {
|
|
397
588
|
const args = signal === undefined || signal === "SIGTERM"
|
|
@@ -406,6 +597,62 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
406
597
|
}
|
|
407
598
|
};
|
|
408
599
|
}
|
|
600
|
+
function completeUtf8PrefixLength(contents) {
|
|
601
|
+
if (contents.length === 0) {
|
|
602
|
+
return 0;
|
|
603
|
+
}
|
|
604
|
+
let leadIndex = contents.length - 1;
|
|
605
|
+
while (leadIndex >= 0 && isUtf8ContinuationByte(contents[leadIndex])) {
|
|
606
|
+
leadIndex -= 1;
|
|
607
|
+
}
|
|
608
|
+
if (leadIndex < 0) {
|
|
609
|
+
return contents.length;
|
|
610
|
+
}
|
|
611
|
+
const expectedLength = utf8SequenceLength(contents[leadIndex]);
|
|
612
|
+
if (expectedLength === 0) {
|
|
613
|
+
return contents.length;
|
|
614
|
+
}
|
|
615
|
+
const availableLength = contents.length - leadIndex;
|
|
616
|
+
return availableLength < expectedLength ? leadIndex : contents.length;
|
|
617
|
+
}
|
|
618
|
+
function isUtf8ContinuationByte(byte) {
|
|
619
|
+
return byte >= 0x80 && byte <= 0xbf;
|
|
620
|
+
}
|
|
621
|
+
function utf8SequenceLength(byte) {
|
|
622
|
+
if (byte >= 0xc2 && byte <= 0xdf) {
|
|
623
|
+
return 2;
|
|
624
|
+
}
|
|
625
|
+
if (byte >= 0xe0 && byte <= 0xef) {
|
|
626
|
+
return 3;
|
|
627
|
+
}
|
|
628
|
+
if (byte >= 0xf0 && byte <= 0xf4) {
|
|
629
|
+
return 4;
|
|
630
|
+
}
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
async function readDetachedExitCode(containerId, jobId, runner, engine, context) {
|
|
634
|
+
const exitFile = shellQuote(`/tmp/poe-jobs/${jobId}.exit`);
|
|
635
|
+
const handle = runner.exec({
|
|
636
|
+
command: engine,
|
|
637
|
+
args: [
|
|
638
|
+
...buildContextArgs(engine, context),
|
|
639
|
+
"exec",
|
|
640
|
+
containerId,
|
|
641
|
+
"sh",
|
|
642
|
+
"-c",
|
|
643
|
+
`test -f ${exitFile} && cat ${exitFile} || true`
|
|
644
|
+
],
|
|
645
|
+
stdout: "pipe",
|
|
646
|
+
stderr: "pipe"
|
|
647
|
+
});
|
|
648
|
+
const stdout = await readStream(handle.stdout);
|
|
649
|
+
const result = await handle.result;
|
|
650
|
+
if (result.exitCode !== 0) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
const exitCode = Number.parseInt(stdout.trim(), 10);
|
|
654
|
+
return Number.isNaN(exitCode) ? null : exitCode;
|
|
655
|
+
}
|
|
409
656
|
function createAttachedSpec(cwd = "/workspace") {
|
|
410
657
|
return {
|
|
411
658
|
cwd,
|