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.
Files changed (152) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +50 -13
  5. package/dist/error-report.js +32 -3
  6. package/dist/human-in-loop/approval-tasks.d.ts +1 -0
  7. package/dist/human-in-loop/approval-tasks.js +7 -5
  8. package/dist/human-in-loop/approvals-commands.js +51 -8
  9. package/dist/human-in-loop/runner.js +24 -19
  10. package/dist/human-in-loop/state-machine.d.ts +3 -3
  11. package/dist/human-in-loop/state-machine.js +13 -5
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +6 -1
  14. package/dist/mcp-proxy.js +85 -19
  15. package/dist/mcp.compile-check.js +1 -0
  16. package/dist/mcp.d.ts +1 -0
  17. package/dist/mcp.js +50 -8
  18. package/dist/renderer.js +119 -13
  19. package/dist/sdk.compile-check.js +1 -0
  20. package/dist/sdk.d.ts +1 -0
  21. package/dist/sdk.js +56 -11
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
  24. package/node_modules/@poe-code/agent-defs/package.json +1 -1
  25. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
  26. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
  27. package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
  28. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
  29. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
  30. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
  31. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
  32. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
  33. package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
  34. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
  35. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
  38. package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
  42. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
  43. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
  44. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
  45. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
  46. package/node_modules/@poe-code/config-mutations/package.json +1 -1
  47. package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
  48. package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
  49. package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
  50. package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
  51. package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
  52. package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
  53. package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
  54. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
  55. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
  56. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  57. package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
  58. package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
  59. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
  60. package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
  61. package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
  62. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
  63. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
  64. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
  65. package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
  66. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
  67. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
  68. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
  73. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
  75. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
  76. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  77. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
  78. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
  79. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
  80. package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
  81. package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
  82. package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
  83. package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
  84. package/node_modules/@poe-code/design-system/package.json +2 -1
  85. package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +1 -0
  86. package/node_modules/@poe-code/process-runner/dist/docker/args.js +11 -3
  87. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +377 -130
  88. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +78 -10
  89. package/node_modules/@poe-code/process-runner/dist/docker/env-file.d.ts +6 -0
  90. package/node_modules/@poe-code/process-runner/dist/docker/env-file.js +49 -0
  91. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  92. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +21 -5
  93. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  94. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  95. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  96. package/node_modules/@poe-code/process-runner/dist/types.d.ts +6 -0
  97. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +61 -0
  98. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +503 -0
  99. package/node_modules/@poe-code/process-runner/package.json +1 -1
  100. package/node_modules/@poe-code/task-list/README.md +0 -2
  101. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  102. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  103. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  104. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  105. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  106. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  107. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  108. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  109. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  110. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  111. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  112. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  113. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  114. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  115. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  116. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  117. package/node_modules/@poe-code/task-list/package.json +1 -2
  118. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  119. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +8 -0
  120. package/node_modules/auth-store/dist/encrypted-file-store.js +104 -8
  121. package/node_modules/auth-store/dist/index.d.ts +1 -1
  122. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  123. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  124. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  125. package/node_modules/auth-store/dist/provider-store.js +55 -7
  126. package/node_modules/auth-store/dist/types.d.ts +3 -1
  127. package/node_modules/auth-store/package.json +2 -1
  128. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  129. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  130. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  131. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  132. package/node_modules/mcp-oauth/package.json +1 -0
  133. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  134. package/node_modules/tiny-mcp-client/dist/internal.d.ts +9 -4
  135. package/node_modules/tiny-mcp-client/dist/internal.js +244 -66
  136. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  137. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  138. package/node_modules/tiny-mcp-client/package.json +2 -1
  139. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  140. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  141. package/node_modules/tiny-mcp-client/src/internal.ts +287 -76
  142. package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +32 -0
  143. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  144. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  145. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  146. package/package.json +10 -12
  147. package/node_modules/@poe-code/file-lock/README.md +0 -52
  148. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  149. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  150. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  151. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  152. package/node_modules/@poe-code/file-lock/package.json +0 -23
@@ -3,12 +3,25 @@ import { randomBytes } from "node:crypto";
3
3
  import { buildDockerRunArgs } from "./args.js";
4
4
  import { buildContextArgs, detectContext } from "./context.js";
5
5
  import { detectEngine } from "./engine.js";
6
+ import { createDockerEnvFile } from "./env-file.js";
7
+ const DOCKER_ABORT_GRACE_MS = 10_000;
8
+ const DOCKER_ABORT_FORCE_GRACE_MS = 5_000;
6
9
  export function createDockerRunner(options) {
7
10
  const engine = options.engine ?? detectEngine();
8
11
  const context = options.context ?? detectContext();
9
12
  return {
10
13
  name: "docker",
11
14
  exec(spec) {
15
+ if (spec.signal?.aborted === true) {
16
+ return {
17
+ pid: null,
18
+ stdin: null,
19
+ stdout: null,
20
+ stderr: null,
21
+ result: Promise.resolve({ exitCode: 1 }),
22
+ kill() { }
23
+ };
24
+ }
12
25
  const stdinMode = spec.stdin ?? "ignore";
13
26
  const stdoutMode = spec.stdout ?? "pipe";
14
27
  const stderrMode = spec.stderr ?? "pipe";
@@ -17,6 +30,7 @@ export function createDockerRunner(options) {
17
30
  stderrMode === "inherit" &&
18
31
  spec.tty === true;
19
32
  const containerName = buildContainerName(options.containerName ?? spec.command);
33
+ const envFile = createDockerEnvFile(spec.env);
20
34
  const runArgs = buildDockerRunArgs({
21
35
  engine,
22
36
  context,
@@ -25,6 +39,7 @@ export function createDockerRunner(options) {
25
39
  args: spec.args ?? [],
26
40
  cwd: spec.cwd,
27
41
  env: spec.env,
42
+ envFilePath: envFile?.path,
28
43
  mounts: options.mounts ?? [],
29
44
  ports: options.ports ?? [],
30
45
  network: options.network,
@@ -36,25 +51,56 @@ export function createDockerRunner(options) {
36
51
  extraArgs: options.extraArgs ?? []
37
52
  });
38
53
  const [command, ...args] = runArgs;
39
- const child = childProcess.spawn(command, args, {
40
- stdio: interactiveMode ? "inherit" : [stdinMode, stdoutMode, stderrMode]
41
- });
54
+ let child;
55
+ try {
56
+ child = childProcess.spawn(command, args, {
57
+ stdio: interactiveMode ? "inherit" : [stdinMode, stdoutMode, stderrMode]
58
+ });
59
+ }
60
+ catch (error) {
61
+ envFile?.cleanup();
62
+ throw error;
63
+ }
42
64
  let isResultSettled = false;
65
+ let exitCodeOverride = null;
43
66
  let resolveResult = null;
67
+ let abortEscalationTimers = [];
44
68
  const result = new Promise((resolve) => {
45
69
  resolveResult = resolve;
46
70
  });
47
- const cleanupAbort = bindAbortSignal(spec.signal, () => {
48
- spawnControlCommand(engine, context, ["stop", containerName]);
49
- });
71
+ const clearAbortEscalation = () => {
72
+ for (const timer of abortEscalationTimers) {
73
+ clearTimeout(timer);
74
+ }
75
+ abortEscalationTimers = [];
76
+ };
50
77
  const settleResult = (exitCode) => {
51
78
  if (isResultSettled) {
52
79
  return;
53
80
  }
54
81
  isResultSettled = true;
55
82
  cleanupAbort();
56
- resolveResult?.({ exitCode });
83
+ clearAbortEscalation();
84
+ envFile?.cleanup();
85
+ resolveResult?.({ exitCode: exitCodeOverride ?? exitCode });
86
+ };
87
+ const scheduleAbortEscalation = () => {
88
+ const terminateTimer = setTimeout(() => {
89
+ killHostDockerChild(child, "SIGTERM");
90
+ const forceTimer = setTimeout(() => {
91
+ killHostDockerChild(child, "SIGKILL");
92
+ }, DOCKER_ABORT_FORCE_GRACE_MS);
93
+ unrefTimer(forceTimer);
94
+ abortEscalationTimers.push(forceTimer);
95
+ }, DOCKER_ABORT_GRACE_MS);
96
+ unrefTimer(terminateTimer);
97
+ abortEscalationTimers.push(terminateTimer);
57
98
  };
99
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
100
+ exitCodeOverride = 1;
101
+ spawnControlCommand(engine, context, ["stop", containerName]);
102
+ scheduleAbortEscalation();
103
+ });
58
104
  child.once("error", () => {
59
105
  settleResult(1);
60
106
  });
@@ -82,6 +128,14 @@ export function createDockerRunner(options) {
82
128
  }
83
129
  };
84
130
  }
131
+ function killHostDockerChild(child, signal) {
132
+ try {
133
+ child.kill(signal);
134
+ }
135
+ catch {
136
+ return;
137
+ }
138
+ }
85
139
  function buildContainerName(name) {
86
140
  const suffix = randomBytes(3).toString("hex").slice(0, 6);
87
141
  const sanitizedName = sanitizeContainerName(name);
@@ -112,9 +166,15 @@ function isContainerNameCharacter(char) {
112
166
  return char === "." || char === "_" || char === "-";
113
167
  }
114
168
  function spawnControlCommand(engine, context, args) {
115
- childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
116
- stdio: "ignore"
117
- });
169
+ try {
170
+ const child = childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
171
+ stdio: "ignore"
172
+ });
173
+ child.once("error", () => undefined);
174
+ }
175
+ catch {
176
+ return;
177
+ }
118
178
  }
119
179
  function bindAbortSignal(signal, onAbort) {
120
180
  if (signal === undefined) {
@@ -129,3 +189,11 @@ function bindAbortSignal(signal, onAbort) {
129
189
  signal.removeEventListener("abort", onAbort);
130
190
  };
131
191
  }
192
+ function unrefTimer(timer) {
193
+ if (typeof timer === "object" &&
194
+ timer !== null &&
195
+ "unref" in timer &&
196
+ typeof timer.unref === "function") {
197
+ timer.unref();
198
+ }
199
+ }
@@ -0,0 +1,6 @@
1
+ export interface DockerEnvFile {
2
+ path: string;
3
+ cleanup(): void;
4
+ }
5
+ export declare function createDockerEnvFile(env: Record<string, string> | undefined): DockerEnvFile | null;
6
+ export declare function serializeDockerEnvFile(entries: Array<[string, string]>): string;
@@ -0,0 +1,49 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ export function createDockerEnvFile(env) {
5
+ const entries = Object.entries(env ?? {});
6
+ if (entries.length === 0) {
7
+ return null;
8
+ }
9
+ const directory = mkdtempSync(path.join(tmpdir(), "poe-docker-env-"));
10
+ const filePath = path.join(directory, "env");
11
+ let active = true;
12
+ try {
13
+ writeFileSync(filePath, serializeDockerEnvFile(entries), {
14
+ encoding: "utf8",
15
+ flag: "wx",
16
+ mode: 0o600
17
+ });
18
+ }
19
+ catch (error) {
20
+ rmSync(directory, { recursive: true, force: true });
21
+ throw error;
22
+ }
23
+ return {
24
+ path: filePath,
25
+ cleanup() {
26
+ if (!active) {
27
+ return;
28
+ }
29
+ active = false;
30
+ rmSync(directory, { recursive: true, force: true });
31
+ }
32
+ };
33
+ }
34
+ export function serializeDockerEnvFile(entries) {
35
+ return (entries.map(([key, value]) => `${formatDockerEnvKey(key)}=${formatDockerEnvValue(value)}`).join("\n") +
36
+ "\n");
37
+ }
38
+ function formatDockerEnvKey(key) {
39
+ if (key.length === 0 || key.includes("=") || key.includes("\n") || key.includes("\r")) {
40
+ throw new Error(`Invalid Docker environment variable name: ${JSON.stringify(key)}`);
41
+ }
42
+ return key;
43
+ }
44
+ function formatDockerEnvValue(value) {
45
+ if (value.includes("\n") || value.includes("\r")) {
46
+ throw new Error("Docker env-file values cannot contain newline characters.");
47
+ }
48
+ return value;
49
+ }
@@ -31,12 +31,13 @@ export const hostExecutionEnvFactory = {
31
31
  return createHostRunner().exec({
32
32
  command: shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
33
33
  ...(shellSpec?.args ? { args: shellSpec.args } : {}),
34
- cwd: openSpec.cwd,
34
+ cwd: shellSpec?.cwd ?? openSpec.cwd,
35
35
  env: shellSpec && "env" in shellSpec ? shellSpec.env : openSpec.env,
36
36
  stdin: "inherit",
37
37
  stdout: "inherit",
38
38
  stderr: "inherit",
39
- tty: true
39
+ tty: true,
40
+ signal: shellSpec?.signal
40
41
  });
41
42
  },
42
43
  async close() { }
@@ -1,12 +1,23 @@
1
1
  import { spawn as spawnChildProcess } from "node:child_process";
2
2
  export function createHostRunner(options = {}) {
3
- const detached = options.detached === true;
3
+ const detachedByDefault = options.detached === true;
4
4
  return {
5
5
  name: "host",
6
6
  exec(spec) {
7
+ if (spec.signal?.aborted === true) {
8
+ return {
9
+ pid: null,
10
+ stdin: null,
11
+ stdout: null,
12
+ stderr: null,
13
+ result: Promise.resolve({ exitCode: 1 }),
14
+ kill() { }
15
+ };
16
+ }
7
17
  const stdinMode = spec.stdin ?? "ignore";
8
18
  const stdoutMode = spec.stdout ?? "pipe";
9
19
  const stderrMode = spec.stderr ?? "pipe";
20
+ const killProcessGroup = detachedByDefault || spec.killProcessGroup === true;
10
21
  const stdio = stdinMode === "inherit" && stdoutMode === "inherit" && stderrMode === "inherit"
11
22
  ? "inherit"
12
23
  : [stdinMode, stdoutMode, stderrMode];
@@ -14,13 +25,13 @@ export function createHostRunner(options = {}) {
14
25
  cwd: spec.cwd,
15
26
  env: spec.env,
16
27
  stdio,
17
- ...(detached ? { detached: true } : {})
28
+ ...(killProcessGroup ? { detached: true } : {})
18
29
  });
19
- if (detached) {
30
+ if (killProcessGroup) {
20
31
  child.unref();
21
32
  }
22
33
  const kill = (signal) => {
23
- if (detached && process.platform !== "win32" && child.pid !== undefined) {
34
+ if (killProcessGroup && process.platform !== "win32" && child.pid !== undefined) {
24
35
  process.kill(-child.pid, signal);
25
36
  return;
26
37
  }
@@ -32,7 +43,12 @@ export function createHostRunner(options = {}) {
32
43
  resolveResult = resolve;
33
44
  });
34
45
  const cleanupAbort = bindAbortSignal(spec.signal, () => {
35
- kill("SIGTERM");
46
+ try {
47
+ kill("SIGTERM");
48
+ }
49
+ catch {
50
+ return;
51
+ }
36
52
  });
37
53
  child.once("close", (code) => {
38
54
  if (settled)
@@ -5,4 +5,5 @@ export { buildDockerRuntimeTemplate, dockerExecutionEnvFactory } from "./docker/
5
5
  export { hostExecutionEnvFactory } from "./host/host-execution-env.js";
6
6
  export { createHostRunner } from "./host/host-runner.js";
7
7
  export { createMockRunner, createMockRunnerByCommand } from "./testing/index.js";
8
+ export { downloadWorkspace, uploadWorkspace, type WorkspaceDownloadOptions, type WorkspaceTransferDirent, type WorkspaceTransferEnv, type WorkspaceTransferFileSystem, type WorkspaceTransferOptions, type WorkspaceTransferRunnerOptions, type WorkspaceTransferStats } from "./workspace-transfer.js";
8
9
  export type { DownloadResult, DockerMount, DockerPortMapping, DockerRunArgs, DockerRunnerOptions, Engine, ExecutionState, ExecutionEnvFactory, ExecutionEnvType, HostRunnerOptions, JobHandle, JobStatus, LogChunk, MockRunBehavior, OpenedEnv, OpenSpec, RunHandle, RunResult, Runner, RunSpec, TemplateEntry, UploadResult } from "./types.js";
@@ -5,3 +5,4 @@ export { buildDockerRuntimeTemplate, dockerExecutionEnvFactory } from "./docker/
5
5
  export { hostExecutionEnvFactory } from "./host/host-execution-env.js";
6
6
  export { createHostRunner } from "./host/host-runner.js";
7
7
  export { createMockRunner, createMockRunnerByCommand } from "./testing/index.js";
8
+ export { downloadWorkspace, uploadWorkspace } from "./workspace-transfer.js";
@@ -16,7 +16,9 @@ export function createMockRunnerByCommand(behaviorsByCommand) {
16
16
  return {
17
17
  name: "mock",
18
18
  exec(spec) {
19
- const behavior = behaviorsByCommand[spec.command];
19
+ const behavior = Object.prototype.hasOwnProperty.call(behaviorsByCommand, spec.command)
20
+ ? behaviorsByCommand[spec.command]
21
+ : undefined;
20
22
  if (behavior === undefined) {
21
23
  throw new Error(`No mock run behavior found for command "${spec.command}"`);
22
24
  }
@@ -29,12 +31,19 @@ function createRunHandle(spec, behavior) {
29
31
  const stderrMode = spec.stderr ?? "pipe";
30
32
  const stdinMode = spec.stdin ?? "ignore";
31
33
  const interval = behavior.stdoutInterval ?? 10;
34
+ if (behavior.exitAfterMs !== undefined && (!Number.isFinite(behavior.exitAfterMs) || behavior.exitAfterMs < 0)) {
35
+ throw new Error("Mock run exitAfterMs must be a finite non-negative number.");
36
+ }
32
37
  const stdoutController = stdoutMode === "pipe" && behavior.stdout !== undefined
33
38
  ? createReadableStream(behavior.stdout, interval)
34
39
  : null;
35
40
  const stderrController = stderrMode === "pipe" && behavior.stderr !== undefined
36
41
  ? createReadableStream(behavior.stderr, interval)
37
42
  : null;
43
+ const outputDone = Promise.all([
44
+ ...(stdoutController === null ? [] : [stdoutController.done]),
45
+ ...(stderrController === null ? [] : [stderrController.done])
46
+ ]);
38
47
  let resolveResult = null;
39
48
  const result = new Promise((resolve) => {
40
49
  resolveResult = resolve;
@@ -51,10 +60,18 @@ function createRunHandle(spec, behavior) {
51
60
  stdoutController?.stop();
52
61
  stderrController?.stop();
53
62
  };
54
- const exitAfterMs = behavior.exitAfterMs ?? 0;
55
- const exitTimer = exitAfterMs > 0
56
- ? setTimeout(complete, exitAfterMs)
57
- : queueMicrotask(complete);
63
+ const exitAfterMs = behavior.exitAfterMs;
64
+ const exitTimer = exitAfterMs === undefined
65
+ ? undefined
66
+ : exitAfterMs > 0
67
+ ? setTimeout(complete, exitAfterMs)
68
+ : undefined;
69
+ if (exitAfterMs === undefined) {
70
+ void outputDone.then(complete);
71
+ }
72
+ else if (exitAfterMs === 0) {
73
+ queueMicrotask(complete);
74
+ }
58
75
  return {
59
76
  pid: behavior.pid ?? null,
60
77
  stdout: stdoutController?.stream ?? null,
@@ -62,7 +79,7 @@ function createRunHandle(spec, behavior) {
62
79
  stdin: stdinMode === "pipe" ? createWritableStream() : null,
63
80
  result,
64
81
  kill() {
65
- if (typeof exitTimer === "object" && exitTimer !== null && "hasRef" in exitTimer) {
82
+ if (exitTimer !== undefined) {
66
83
  clearTimeout(exitTimer);
67
84
  }
68
85
  stopStreams();
@@ -76,6 +93,10 @@ function createReadableStream(lines, interval) {
76
93
  });
77
94
  const timers = new Set();
78
95
  let stopped = false;
96
+ let resolveDone;
97
+ const done = new Promise((resolve) => {
98
+ resolveDone = resolve;
99
+ });
79
100
  const stop = () => {
80
101
  if (stopped) {
81
102
  return;
@@ -86,10 +107,11 @@ function createReadableStream(lines, interval) {
86
107
  }
87
108
  timers.clear();
88
109
  stream.push(null);
110
+ resolveDone?.();
89
111
  };
90
112
  if (lines.length === 0) {
91
113
  queueMicrotask(stop);
92
- return { stream, stop };
114
+ return { done, stream, stop };
93
115
  }
94
116
  for (const [index, line] of lines.entries()) {
95
117
  const timer = setTimeout(() => {
@@ -104,7 +126,7 @@ function createReadableStream(lines, interval) {
104
126
  }, interval * (index + 1));
105
127
  timers.add(timer);
106
128
  }
107
- return { stream, stop };
129
+ return { done, stream, stop };
108
130
  }
109
131
  function createWritableStream() {
110
132
  return new Writable({
@@ -20,6 +20,8 @@ export interface RunSpec {
20
20
  stderr?: "pipe" | "inherit";
21
21
  tty?: boolean;
22
22
  signal?: AbortSignal;
23
+ /** Start in a separate process group so kill() can signal the full group where supported. */
24
+ killProcessGroup?: boolean;
23
25
  }
24
26
  export interface Runner {
25
27
  exec(spec: RunSpec): RunHandle;
@@ -103,10 +105,12 @@ export interface AttachedJobContext {
103
105
  tool: string;
104
106
  argv: string[];
105
107
  cwd: string;
108
+ reattachContext?: Record<string, unknown>;
106
109
  }
107
110
  export interface OpenedEnv {
108
111
  readonly id: string;
109
112
  readonly job: JobHandle | null;
113
+ readonly reattachContext?: Record<string, unknown>;
110
114
  uploadWorkspace(): Promise<UploadResult>;
111
115
  downloadWorkspace(opts: {
112
116
  conflictPolicy: "refuse" | "overwrite";
@@ -125,6 +129,7 @@ export interface JobHandle {
125
129
  stream(opts?: {
126
130
  sinceByte?: number;
127
131
  since?: Date;
132
+ follow?: boolean;
128
133
  }): AsyncIterable<LogChunk>;
129
134
  wait(): Promise<{
130
135
  exitCode: number;
@@ -160,6 +165,7 @@ export interface DockerRunArgs {
160
165
  args: string[];
161
166
  cwd?: string;
162
167
  env?: Record<string, string>;
168
+ envFilePath?: string;
163
169
  mounts: DockerMount[];
164
170
  ports: DockerPortMapping[];
165
171
  network?: string;
@@ -0,0 +1,61 @@
1
+ import type { DownloadResult, UploadResult } from "./types.js";
2
+ export type { DownloadResult, UploadResult } from "./types.js";
3
+ export interface WorkspaceTransferDirent {
4
+ name: string;
5
+ isFile(): boolean;
6
+ isDirectory(): boolean;
7
+ isSymbolicLink?(): boolean;
8
+ }
9
+ export interface WorkspaceTransferStats {
10
+ isFile(): boolean;
11
+ isDirectory(): boolean;
12
+ isSymbolicLink?(): boolean;
13
+ size: number;
14
+ }
15
+ export interface WorkspaceTransferFileSystem {
16
+ mkdir(path: string, options?: {
17
+ recursive?: boolean;
18
+ }): Promise<void>;
19
+ readdir(path: string, options: {
20
+ withFileTypes: true;
21
+ }): Promise<WorkspaceTransferDirent[]>;
22
+ readFile(path: string): Promise<Buffer>;
23
+ readFile(path: string, encoding: BufferEncoding): Promise<string>;
24
+ writeFile(path: string, data: string | Buffer, options?: {
25
+ flag?: string;
26
+ mode?: number;
27
+ }): Promise<void>;
28
+ stat(path: string): Promise<WorkspaceTransferStats>;
29
+ lstat?(path: string): Promise<WorkspaceTransferStats>;
30
+ rename?(oldPath: string, newPath: string): Promise<void>;
31
+ rm?(path: string, options?: {
32
+ recursive?: boolean;
33
+ force?: boolean;
34
+ }): Promise<void>;
35
+ unlink?(path: string): Promise<void>;
36
+ rmdir?(path: string): Promise<void>;
37
+ }
38
+ export interface WorkspaceTransferEnv {
39
+ cwd: string;
40
+ uploadDir: string;
41
+ workspaceDir?: string;
42
+ fs?: WorkspaceTransferFileSystem;
43
+ remoteFs?: WorkspaceTransferFileSystem;
44
+ }
45
+ export interface WorkspaceTransferOptions {
46
+ runner?: WorkspaceTransferRunnerOptions;
47
+ uploadMaxFileMb?: number;
48
+ workspaceExclude?: string[];
49
+ warn?: (message: string) => void;
50
+ }
51
+ export interface WorkspaceTransferRunnerOptions {
52
+ upload_max_file_mb?: number;
53
+ workspace?: {
54
+ exclude?: string[];
55
+ };
56
+ }
57
+ export interface WorkspaceDownloadOptions {
58
+ conflictPolicy: "refuse" | "overwrite";
59
+ }
60
+ export declare function uploadWorkspace(env: WorkspaceTransferEnv, opts: WorkspaceTransferOptions): Promise<UploadResult>;
61
+ export declare function downloadWorkspace(env: WorkspaceTransferEnv, opts: WorkspaceDownloadOptions): Promise<DownloadResult>;