toolcraft 0.0.22 → 0.0.24

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 (147) 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 +57 -14
  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/docker-execution-env.js +244 -110
  86. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +16 -4
  87. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  88. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +16 -1
  89. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  90. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  91. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  92. package/node_modules/@poe-code/process-runner/dist/types.d.ts +3 -0
  93. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +57 -0
  94. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +484 -0
  95. package/node_modules/@poe-code/process-runner/package.json +1 -1
  96. package/node_modules/@poe-code/task-list/README.md +0 -2
  97. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  98. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  99. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  100. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  101. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  102. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  103. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  104. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  105. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  106. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  107. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  108. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  109. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  110. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  111. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  112. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  113. package/node_modules/@poe-code/task-list/package.json +1 -2
  114. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  115. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
  116. package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
  117. package/node_modules/auth-store/dist/index.d.ts +1 -1
  118. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  119. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  120. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  121. package/node_modules/auth-store/dist/provider-store.js +55 -7
  122. package/node_modules/auth-store/dist/types.d.ts +3 -1
  123. package/node_modules/auth-store/package.json +2 -1
  124. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  125. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  126. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  127. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  128. package/node_modules/mcp-oauth/package.json +1 -0
  129. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  130. package/node_modules/tiny-mcp-client/dist/internal.d.ts +8 -4
  131. package/node_modules/tiny-mcp-client/dist/internal.js +237 -67
  132. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  133. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  134. package/node_modules/tiny-mcp-client/package.json +2 -1
  135. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  136. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  137. package/node_modules/tiny-mcp-client/src/internal.ts +279 -77
  138. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  139. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  140. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  141. package/package.json +10 -12
  142. package/node_modules/@poe-code/file-lock/README.md +0 -52
  143. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  144. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  145. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  146. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  147. package/node_modules/@poe-code/file-lock/package.json +0 -23
@@ -0,0 +1,484 @@
1
+ import { createHash } from "node:crypto";
2
+ import { promises as nodeFs } from "node:fs";
3
+ import path from "node:path";
4
+ const uploadState = new WeakMap();
5
+ export async function uploadWorkspace(env, opts) {
6
+ const localFs = env.fs ?? nodeFs;
7
+ const remoteFs = env.remoteFs ?? localFs;
8
+ const workspaceDir = env.workspaceDir ?? "/workspace";
9
+ const maxBytes = (opts.uploadMaxFileMb ?? opts.runner?.upload_max_file_mb ?? 100) * 1024 * 1024;
10
+ const warn = opts.warn ?? console.warn;
11
+ const allFiles = await listFiles(localFs, env.cwd);
12
+ const state = new Map();
13
+ const gitignore = await readGitignoreRules(localFs, env.cwd, allFiles);
14
+ const poeCodeIgnore = await readIgnoreFile(localFs, env.cwd, ".poe-code-ignore", false);
15
+ const workspaceExclude = parseIgnoreLines([...(opts.runner?.workspace?.exclude ?? []), ...(opts.workspaceExclude ?? [])], false);
16
+ const entries = [];
17
+ const skipped = [];
18
+ for (const file of allFiles) {
19
+ if (isIgnoredByGit(file.path, gitignore) ||
20
+ isIgnoredAdditively(file.path, poeCodeIgnore) ||
21
+ isIgnoredAdditively(file.path, workspaceExclude)) {
22
+ continue;
23
+ }
24
+ const content = await localFs.readFile(file.absolutePath);
25
+ const bytes = content.byteLength;
26
+ state.set(file.path, {
27
+ hash: hashBuffer(content),
28
+ uploaded: false
29
+ });
30
+ if (bytes > maxBytes) {
31
+ skipped.push({ path: file.path, bytes, reason: "max_size" });
32
+ warn(`Skipping ${file.path}: ${bytes} bytes exceeds upload_max_file_mb.`);
33
+ continue;
34
+ }
35
+ entries.push({ ...file, bytes, content });
36
+ state.set(file.path, {
37
+ hash: hashBuffer(content),
38
+ uploaded: true
39
+ });
40
+ }
41
+ const stagedWorkspaceDir = `${workspaceDir}.upload-tmp`;
42
+ const priorWorkspaceDir = `${workspaceDir}.upload-backup`;
43
+ const archivePath = path.join(env.uploadDir, "workspace.tar");
44
+ const stagedArchivePath = `${archivePath}.upload-tmp`;
45
+ await removeTree(remoteFs, stagedWorkspaceDir);
46
+ await removeTree(remoteFs, priorWorkspaceDir);
47
+ let hadWorkspace = false;
48
+ try {
49
+ await remoteFs.mkdir(stagedWorkspaceDir, { recursive: true });
50
+ await remoteFs.mkdir(env.uploadDir, { recursive: true });
51
+ await remoteFs.writeFile(stagedArchivePath, createTar(entries));
52
+ for (const entry of entries) {
53
+ const remotePath = path.join(stagedWorkspaceDir, entry.path);
54
+ await remoteFs.mkdir(path.dirname(remotePath), { recursive: true });
55
+ await remoteFs.writeFile(remotePath, entry.content);
56
+ }
57
+ hadWorkspace = (await statIfExists(remoteFs, workspaceDir)) !== null;
58
+ if (hadWorkspace) {
59
+ await renamePath(remoteFs, workspaceDir, priorWorkspaceDir);
60
+ }
61
+ await renamePath(remoteFs, stagedWorkspaceDir, workspaceDir);
62
+ await renamePath(remoteFs, stagedArchivePath, archivePath);
63
+ await removeTree(remoteFs, priorWorkspaceDir);
64
+ }
65
+ catch (error) {
66
+ if ((await statIfExists(remoteFs, workspaceDir)) === null && hadWorkspace) {
67
+ await renamePath(remoteFs, priorWorkspaceDir, workspaceDir).catch(() => undefined);
68
+ }
69
+ await removeTree(remoteFs, stagedWorkspaceDir).catch(() => undefined);
70
+ await removeFile(remoteFs, stagedArchivePath).catch(() => undefined);
71
+ throw error;
72
+ }
73
+ uploadState.set(env, state);
74
+ return {
75
+ files: entries.length,
76
+ bytes: entries.reduce((sum, entry) => sum + entry.bytes, 0),
77
+ skipped
78
+ };
79
+ }
80
+ export async function downloadWorkspace(env, opts) {
81
+ const localFs = env.fs ?? nodeFs;
82
+ const remoteFs = env.remoteFs ?? localFs;
83
+ const workspaceDir = env.workspaceDir ?? "/workspace";
84
+ const state = uploadState.get(env) ?? new Map();
85
+ const remoteFiles = await listFilesIfExists(remoteFs, workspaceDir);
86
+ const remotePaths = new Set(remoteFiles.map((file) => file.path));
87
+ const conflicts = [];
88
+ let files = 0;
89
+ let bytes = 0;
90
+ for (const remoteFile of remoteFiles) {
91
+ const remoteContent = await remoteFs.readFile(remoteFile.absolutePath);
92
+ const localPath = path.join(env.cwd, remoteFile.path);
93
+ await assertSafeLocalDownloadPath(localFs, env.cwd, localPath);
94
+ const conflict = await isDownloadConflict(localFs, localPath, remoteFile.path, remoteContent, state);
95
+ if (conflict && opts.conflictPolicy === "refuse") {
96
+ conflicts.push({ path: remoteFile.path, reason: "local_modified" });
97
+ continue;
98
+ }
99
+ await writeFileAtomically(localFs, localPath, remoteContent, ".download-tmp");
100
+ state.set(remoteFile.path, {
101
+ hash: hashBuffer(remoteContent),
102
+ uploaded: true
103
+ });
104
+ files += 1;
105
+ bytes += remoteContent.length;
106
+ }
107
+ for (const [relativePath, fileState] of state) {
108
+ if (!fileState.uploaded || remotePaths.has(relativePath)) {
109
+ continue;
110
+ }
111
+ const localPath = path.join(env.cwd, relativePath);
112
+ await assertSafeLocalDownloadPath(localFs, env.cwd, localPath);
113
+ const localContent = await readFileIfExists(localFs, localPath);
114
+ if (localContent === null) {
115
+ continue;
116
+ }
117
+ if (opts.conflictPolicy === "refuse" && hashBuffer(localContent) !== fileState.hash) {
118
+ conflicts.push({ path: relativePath, reason: "local_modified" });
119
+ continue;
120
+ }
121
+ await removeFile(localFs, localPath);
122
+ }
123
+ return { files, bytes, conflicts };
124
+ }
125
+ async function listFilesIfExists(fs, root) {
126
+ try {
127
+ return await listFiles(fs, root);
128
+ }
129
+ catch (error) {
130
+ if (isNotFoundError(error)) {
131
+ return [];
132
+ }
133
+ throw error;
134
+ }
135
+ }
136
+ async function listFiles(fs, root) {
137
+ const result = [];
138
+ async function visit(dir) {
139
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
140
+ for (const dirent of dirents.sort((left, right) => left.name.localeCompare(right.name))) {
141
+ const absolutePath = path.join(dir, dirent.name);
142
+ if (dirent.isDirectory()) {
143
+ await visit(absolutePath);
144
+ continue;
145
+ }
146
+ if (!dirent.isFile()) {
147
+ continue;
148
+ }
149
+ const stats = await fs.stat(absolutePath);
150
+ result.push({
151
+ path: toRelativePath(root, absolutePath),
152
+ absolutePath,
153
+ bytes: stats.size
154
+ });
155
+ }
156
+ }
157
+ await visit(root);
158
+ return result;
159
+ }
160
+ async function readIgnoreFile(fs, cwd, fileName, allowNegation) {
161
+ const content = await readFileIfExists(fs, path.join(cwd, fileName));
162
+ if (content === null) {
163
+ return [];
164
+ }
165
+ return parseIgnoreLines(content.toString("utf8").split("\n"), allowNegation, "");
166
+ }
167
+ async function readGitignoreRules(fs, cwd, files) {
168
+ const rules = [];
169
+ for (const file of files) {
170
+ if (path.basename(file.path) !== ".gitignore") {
171
+ continue;
172
+ }
173
+ const content = await fs.readFile(file.absolutePath, "utf8");
174
+ const containingDirectory = normalizeRelativePath(path.dirname(file.path));
175
+ rules.push(...parseIgnoreLines(content.split("\n"), true, containingDirectory === "." ? "" : containingDirectory));
176
+ }
177
+ return rules;
178
+ }
179
+ function parseIgnoreLines(lines, allowNegation, basePath = "") {
180
+ const rules = [];
181
+ for (const rawLine of lines) {
182
+ let line = rawLine.trimEnd();
183
+ if (line.length === 0 || line.startsWith("#")) {
184
+ continue;
185
+ }
186
+ const escapedMarker = line.startsWith("\\#") || line.startsWith("\\!");
187
+ if (escapedMarker) {
188
+ line = line.slice(1);
189
+ }
190
+ const negate = !escapedMarker && allowNegation && line.startsWith("!");
191
+ const patternWithMarker = negate ? line.slice(1) : line;
192
+ if (patternWithMarker.length === 0) {
193
+ continue;
194
+ }
195
+ const anchored = patternWithMarker.startsWith("/");
196
+ const directoryOnly = patternWithMarker.endsWith("/");
197
+ const pattern = stripSlashes(directoryOnly ? patternWithMarker.slice(0, -1) : patternWithMarker);
198
+ if (pattern.length > 0) {
199
+ rules.push({ pattern, negate, directoryOnly, anchored, basePath });
200
+ }
201
+ }
202
+ return rules;
203
+ }
204
+ function isIgnoredByGit(relativePath, rules) {
205
+ let ignored = false;
206
+ for (const rule of rules) {
207
+ if (matchesRule(relativePath, rule)) {
208
+ ignored = !rule.negate;
209
+ }
210
+ }
211
+ return ignored;
212
+ }
213
+ function isIgnoredAdditively(relativePath, rules) {
214
+ return rules.some((rule) => matchesRule(relativePath, rule));
215
+ }
216
+ function matchesRule(relativePath, rule) {
217
+ const normalizedPath = normalizeRelativePath(relativePath);
218
+ const scopedPath = pathWithinRuleScope(normalizedPath, rule.basePath);
219
+ if (scopedPath === null) {
220
+ return false;
221
+ }
222
+ const normalizedPattern = normalizeRelativePath(rule.pattern);
223
+ if (rule.directoryOnly) {
224
+ return pathMatchesDirectory(scopedPath, normalizedPattern, rule.anchored);
225
+ }
226
+ if (rule.anchored || normalizedPattern.includes("/")) {
227
+ return matchPathSegments(scopedPath.split("/"), normalizedPattern.split("/"));
228
+ }
229
+ return scopedPath.split("/").some((segment) => matchSegment(segment, normalizedPattern));
230
+ }
231
+ function pathWithinRuleScope(relativePath, basePath) {
232
+ if (basePath.length === 0) {
233
+ return relativePath;
234
+ }
235
+ if (relativePath === basePath) {
236
+ return "";
237
+ }
238
+ const prefix = `${basePath}/`;
239
+ return relativePath.startsWith(prefix) ? relativePath.slice(prefix.length) : null;
240
+ }
241
+ function pathMatchesDirectory(relativePath, pattern, anchored) {
242
+ if (anchored || pattern.includes("/")) {
243
+ return relativePath === pattern || relativePath.startsWith(`${pattern}/`);
244
+ }
245
+ const segments = relativePath.split("/");
246
+ return segments.some((segment, index) => {
247
+ return matchSegment(segment, pattern) && index < segments.length - 1;
248
+ });
249
+ }
250
+ function matchPathSegments(pathSegments, patternSegments) {
251
+ return matchPathFrom(0, 0);
252
+ function matchPathFrom(pathIndex, patternIndex) {
253
+ if (patternIndex === patternSegments.length) {
254
+ return pathIndex === pathSegments.length;
255
+ }
256
+ const patternSegment = patternSegments[patternIndex] ?? "";
257
+ if (patternSegment === "**") {
258
+ for (let nextPathIndex = pathIndex; nextPathIndex <= pathSegments.length; nextPathIndex += 1) {
259
+ if (matchPathFrom(nextPathIndex, patternIndex + 1)) {
260
+ return true;
261
+ }
262
+ }
263
+ return false;
264
+ }
265
+ return (pathIndex < pathSegments.length &&
266
+ matchSegment(pathSegments[pathIndex] ?? "", patternSegment) &&
267
+ matchPathFrom(pathIndex + 1, patternIndex + 1));
268
+ }
269
+ }
270
+ function matchSegment(value, pattern) {
271
+ const patternParts = pattern.split("*");
272
+ if (patternParts.length === 1) {
273
+ return value === pattern;
274
+ }
275
+ let offset = 0;
276
+ for (const [index, part] of patternParts.entries()) {
277
+ if (part.length === 0) {
278
+ continue;
279
+ }
280
+ const foundAt = value.indexOf(part, offset);
281
+ if (foundAt === -1) {
282
+ return false;
283
+ }
284
+ if (index === 0 && foundAt !== 0) {
285
+ return false;
286
+ }
287
+ offset = foundAt + part.length;
288
+ }
289
+ const lastPart = patternParts.at(-1) ?? "";
290
+ return lastPart.length === 0 || value.endsWith(lastPart);
291
+ }
292
+ async function isDownloadConflict(fs, localPath, relativePath, remoteContent, state) {
293
+ const localContent = await readFileIfExists(fs, localPath);
294
+ if (localContent === null) {
295
+ return false;
296
+ }
297
+ const localHash = hashBuffer(localContent);
298
+ const remoteHash = hashBuffer(remoteContent);
299
+ const uploadedHash = state.get(relativePath)?.hash;
300
+ const localChanged = uploadedHash === undefined || localHash !== uploadedHash;
301
+ return localChanged && localHash !== remoteHash;
302
+ }
303
+ async function readFileIfExists(fs, filePath) {
304
+ try {
305
+ return await fs.readFile(filePath);
306
+ }
307
+ catch (error) {
308
+ if (isNotFoundError(error)) {
309
+ return null;
310
+ }
311
+ throw error;
312
+ }
313
+ }
314
+ async function removeTree(fs, targetPath) {
315
+ if (fs.rm) {
316
+ await fs.rm(targetPath, { recursive: true, force: true });
317
+ return;
318
+ }
319
+ const stats = await statIfExists(fs, targetPath);
320
+ if (stats === null) {
321
+ return;
322
+ }
323
+ if (stats.isFile()) {
324
+ await removeFile(fs, targetPath);
325
+ return;
326
+ }
327
+ const dirents = await fs.readdir(targetPath, { withFileTypes: true });
328
+ for (const dirent of dirents) {
329
+ await removeTree(fs, path.join(targetPath, dirent.name));
330
+ }
331
+ if (fs.rmdir) {
332
+ await fs.rmdir(targetPath);
333
+ }
334
+ }
335
+ async function removeFile(fs, filePath) {
336
+ if (fs.rm) {
337
+ await fs.rm(filePath, { force: true });
338
+ return;
339
+ }
340
+ if (fs.unlink) {
341
+ await fs.unlink(filePath);
342
+ }
343
+ }
344
+ async function renamePath(fs, sourcePath, destinationPath) {
345
+ if (!fs.rename) {
346
+ throw new Error("Workspace transfer filesystem must support atomic rename.");
347
+ }
348
+ await fs.rename(sourcePath, destinationPath);
349
+ }
350
+ async function writeFileAtomically(fs, destinationPath, data, temporarySuffix) {
351
+ const temporaryPath = `${destinationPath}${temporarySuffix}`;
352
+ await fs.mkdir(path.dirname(destinationPath), { recursive: true });
353
+ try {
354
+ await fs.writeFile(temporaryPath, data);
355
+ await renamePath(fs, temporaryPath, destinationPath);
356
+ }
357
+ catch (error) {
358
+ await removeFile(fs, temporaryPath).catch(() => undefined);
359
+ throw error;
360
+ }
361
+ }
362
+ async function statIfExists(fs, targetPath) {
363
+ try {
364
+ return await fs.stat(targetPath);
365
+ }
366
+ catch (error) {
367
+ if (isNotFoundError(error)) {
368
+ return null;
369
+ }
370
+ throw error;
371
+ }
372
+ }
373
+ async function assertSafeLocalDownloadPath(fs, workspacePath, targetPath) {
374
+ const resolvedWorkspacePath = path.resolve(workspacePath);
375
+ const resolvedTargetPath = path.resolve(targetPath);
376
+ const relativePath = path.relative(resolvedWorkspacePath, resolvedTargetPath);
377
+ if (relativePath === ".." ||
378
+ relativePath.startsWith(`..${path.sep}`) ||
379
+ path.isAbsolute(relativePath)) {
380
+ throw new Error("Workspace download must remain inside the local workspace.");
381
+ }
382
+ if (fs.lstat === undefined) {
383
+ throw new Error("Workspace transfer filesystem must support symbolic link checks.");
384
+ }
385
+ let currentPath = resolvedTargetPath;
386
+ while (true) {
387
+ try {
388
+ const stats = await fs.lstat(currentPath);
389
+ if (stats.isSymbolicLink?.() === true) {
390
+ throw new Error("Workspace download must remain inside the local workspace.");
391
+ }
392
+ }
393
+ catch (error) {
394
+ if (!isNotFoundError(error)) {
395
+ throw error;
396
+ }
397
+ }
398
+ if (currentPath === resolvedWorkspacePath) {
399
+ return;
400
+ }
401
+ currentPath = path.dirname(currentPath);
402
+ }
403
+ }
404
+ function createTar(entries) {
405
+ const chunks = [];
406
+ for (const entry of entries) {
407
+ chunks.push(createTarHeader(entry.path, entry.bytes));
408
+ chunks.push(entry.content);
409
+ chunks.push(Buffer.alloc(paddingFor(entry.bytes)));
410
+ }
411
+ chunks.push(Buffer.alloc(1024));
412
+ return Buffer.concat(chunks);
413
+ }
414
+ function createTarHeader(name, size) {
415
+ const header = Buffer.alloc(512);
416
+ const { entryName, prefix } = splitTarPath(name);
417
+ writeString(header, entryName, 0, 100);
418
+ writeOctal(header, 0o644, 100, 8);
419
+ writeOctal(header, 0, 108, 8);
420
+ writeOctal(header, 0, 116, 8);
421
+ writeOctal(header, size, 124, 12);
422
+ writeOctal(header, 0, 136, 12);
423
+ header.fill(32, 148, 156);
424
+ header.write("0", 156, 1, "ascii");
425
+ header.write("ustar", 257, 5, "ascii");
426
+ header.write("00", 263, 2, "ascii");
427
+ writeString(header, prefix, 345, 155);
428
+ writeOctal(header, checksum(header), 148, 8);
429
+ return header;
430
+ }
431
+ function splitTarPath(name) {
432
+ if (Buffer.byteLength(name) <= 100) {
433
+ return { entryName: name, prefix: "" };
434
+ }
435
+ let separatorIndex = name.lastIndexOf("/");
436
+ while (separatorIndex !== -1) {
437
+ const prefix = name.slice(0, separatorIndex);
438
+ const entryName = name.slice(separatorIndex + 1);
439
+ if (Buffer.byteLength(entryName) <= 100 && Buffer.byteLength(prefix) <= 155) {
440
+ return { entryName, prefix };
441
+ }
442
+ separatorIndex = name.lastIndexOf("/", separatorIndex - 1);
443
+ }
444
+ throw new Error(`Workspace tar path is too long to represent: ${name}`);
445
+ }
446
+ function writeString(buffer, value, offset, length) {
447
+ const text = Buffer.from(value);
448
+ text.copy(buffer, offset, 0, Math.min(text.length, length));
449
+ }
450
+ function writeOctal(buffer, value, offset, length) {
451
+ const text = value.toString(8).padStart(length - 1, "0");
452
+ buffer.write(text.slice(0, length - 1), offset, length - 1, "ascii");
453
+ buffer[offset + length - 1] = 0;
454
+ }
455
+ function checksum(buffer) {
456
+ return buffer.reduce((sum, value) => sum + value, 0);
457
+ }
458
+ function paddingFor(size) {
459
+ const remainder = size % 512;
460
+ return remainder === 0 ? 0 : 512 - remainder;
461
+ }
462
+ function hashBuffer(buffer) {
463
+ return createHash("sha256").update(buffer).digest("hex");
464
+ }
465
+ function toRelativePath(root, absolutePath) {
466
+ return normalizeRelativePath(path.relative(root, absolutePath));
467
+ }
468
+ function normalizeRelativePath(value) {
469
+ return stripSlashes(value.split(path.sep).join("/"));
470
+ }
471
+ function stripSlashes(value) {
472
+ let start = 0;
473
+ let end = value.length;
474
+ while (value[start] === "/") {
475
+ start += 1;
476
+ }
477
+ while (value[end - 1] === "/") {
478
+ end -= 1;
479
+ }
480
+ return value.slice(start, end);
481
+ }
482
+ function isNotFoundError(error) {
483
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
484
+ }
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "scripts": {
19
- "build": "tsc",
19
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc",
20
20
  "test": "cd ../.. && vitest run --config vitest.config.ts packages/process-runner/src/**/*.test.ts",
21
21
  "test:unit": "cd ../.. && vitest run --config vitest.config.ts packages/process-runner/src/**/*.test.ts",
22
22
  "lint": "cd ../.. && eslint packages/process-runner/src --ext ts && tsc -p packages/process-runner/tsconfig.json --noEmit"
@@ -56,8 +56,6 @@ Pass a custom machine with `openTaskList({ stateMachine })`. If omitted, the pac
56
56
  | `create` | `boolean` | `false` | Creates missing storage for the selected backend when enabled. |
57
57
  | `singleList` | `string` | unset | `markdown-dir` only. Treats `path` as one list with this name instead of one subdirectory per list. |
58
58
  | `frontmatterMode` | `"strict" \| "passthrough"` | `"strict"` | `markdown-dir` only. `passthrough` preserves non-task frontmatter and allows files without a frontmatter block. |
59
- | `lockStaleMs` | `number` | `30_000` | Stale threshold passed to backend file locking. |
60
- | `lockRetries` | `number` | `20` | Retry count passed to backend file locking. |
61
59
  | `fs` | `TaskListFs` | `node:fs/promises` adapter | Injectable filesystem, primarily for tests. |
62
60
  | `stateMachine` | `StateMachineDef` | `defaultStateMachine` | Overrides the task lifecycle used by `create`, `fire`, `canFire`, and `events`. |
63
61
 
@@ -22,6 +22,9 @@ export function createGhClient(options) {
22
22
  throw new Error(`GitHub GraphQL request failed with status ${response.status}: ${body}`);
23
23
  }
24
24
  const parsed = JSON.parse(body);
25
+ if (parsed.data !== undefined && parsed.data !== null) {
26
+ return parsed.data;
27
+ }
25
28
  const firstError = parsed.errors?.[0];
26
29
  if (firstError !== undefined) {
27
30
  throw new Error(firstError.message ?? "GitHub GraphQL request failed");