@elie-laloum/outpost 1.1.0

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 (128) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.fr.md +251 -0
  4. package/README.md +226 -0
  5. package/ROADMAP.md +34 -0
  6. package/SECURITY.md +15 -0
  7. package/dist/application/campaign.d.ts +33 -0
  8. package/dist/application/campaign.js +195 -0
  9. package/dist/application/campaign.js.map +1 -0
  10. package/dist/application/execution.d.ts +48 -0
  11. package/dist/application/execution.js +322 -0
  12. package/dist/application/execution.js.map +1 -0
  13. package/dist/application/interactive-brief.d.ts +3 -0
  14. package/dist/application/interactive-brief.js +35 -0
  15. package/dist/application/interactive-brief.js.map +1 -0
  16. package/dist/application/outpost.d.ts +91 -0
  17. package/dist/application/outpost.js +629 -0
  18. package/dist/application/outpost.js.map +1 -0
  19. package/dist/application/remote-workspace.d.ts +11 -0
  20. package/dist/application/remote-workspace.js +264 -0
  21. package/dist/application/remote-workspace.js.map +1 -0
  22. package/dist/application/tasks.d.ts +17 -0
  23. package/dist/application/tasks.js +36 -0
  24. package/dist/application/tasks.js.map +1 -0
  25. package/dist/cli/main.d.ts +2 -0
  26. package/dist/cli/main.js +94 -0
  27. package/dist/cli/main.js.map +1 -0
  28. package/dist/cli/scaffold.d.ts +28 -0
  29. package/dist/cli/scaffold.js +169 -0
  30. package/dist/cli/scaffold.js.map +1 -0
  31. package/dist/cli/starters.d.ts +3 -0
  32. package/dist/cli/starters.js +34 -0
  33. package/dist/cli/starters.js.map +1 -0
  34. package/dist/domain/backlog.d.ts +16 -0
  35. package/dist/domain/backlog.js +23 -0
  36. package/dist/domain/backlog.js.map +1 -0
  37. package/dist/domain/errors.d.ts +11 -0
  38. package/dist/domain/errors.js +37 -0
  39. package/dist/domain/errors.js.map +1 -0
  40. package/dist/domain/ports.d.ts +183 -0
  41. package/dist/domain/ports.js +2 -0
  42. package/dist/domain/ports.js.map +1 -0
  43. package/dist/domain/prompts.d.ts +22 -0
  44. package/dist/domain/prompts.js +45 -0
  45. package/dist/domain/prompts.js.map +1 -0
  46. package/dist/domain/response.d.ts +37 -0
  47. package/dist/domain/response.js +50 -0
  48. package/dist/domain/response.js.map +1 -0
  49. package/dist/domain/workflow.d.ts +68 -0
  50. package/dist/domain/workflow.js +246 -0
  51. package/dist/domain/workflow.js.map +1 -0
  52. package/dist/index.d.ts +27 -0
  53. package/dist/index.js +13 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/infrastructure/abort.d.ts +1 -0
  56. package/dist/infrastructure/abort.js +16 -0
  57. package/dist/infrastructure/abort.js.map +1 -0
  58. package/dist/infrastructure/conversations.d.ts +30 -0
  59. package/dist/infrastructure/conversations.js +222 -0
  60. package/dist/infrastructure/conversations.js.map +1 -0
  61. package/dist/infrastructure/files.d.ts +5 -0
  62. package/dist/infrastructure/files.js +85 -0
  63. package/dist/infrastructure/files.js.map +1 -0
  64. package/dist/infrastructure/git.d.ts +14 -0
  65. package/dist/infrastructure/git.js +304 -0
  66. package/dist/infrastructure/git.js.map +1 -0
  67. package/dist/infrastructure/journal.d.ts +10 -0
  68. package/dist/infrastructure/journal.js +53 -0
  69. package/dist/infrastructure/journal.js.map +1 -0
  70. package/dist/infrastructure/process.d.ts +6 -0
  71. package/dist/infrastructure/process.js +133 -0
  72. package/dist/infrastructure/process.js.map +1 -0
  73. package/dist/infrastructure/reporter.d.ts +10 -0
  74. package/dist/infrastructure/reporter.js +47 -0
  75. package/dist/infrastructure/reporter.js.map +1 -0
  76. package/dist/infrastructure/settings.d.ts +3 -0
  77. package/dist/infrastructure/settings.js +41 -0
  78. package/dist/infrastructure/settings.js.map +1 -0
  79. package/dist/infrastructure/shutdown.d.ts +1 -0
  80. package/dist/infrastructure/shutdown.js +67 -0
  81. package/dist/infrastructure/shutdown.js.map +1 -0
  82. package/dist/infrastructure/terminal.d.ts +11 -0
  83. package/dist/infrastructure/terminal.js +19 -0
  84. package/dist/infrastructure/terminal.js.map +1 -0
  85. package/dist/infrastructure/transfer.d.ts +3 -0
  86. package/dist/infrastructure/transfer.js +29 -0
  87. package/dist/infrastructure/transfer.js.map +1 -0
  88. package/dist/providers/agents.d.ts +17 -0
  89. package/dist/providers/agents.js +204 -0
  90. package/dist/providers/agents.js.map +1 -0
  91. package/dist/providers/backlogs.d.ts +9 -0
  92. package/dist/providers/backlogs.js +82 -0
  93. package/dist/providers/backlogs.js.map +1 -0
  94. package/dist/providers/cloud-files.d.ts +4 -0
  95. package/dist/providers/cloud-files.js +66 -0
  96. package/dist/providers/cloud-files.js.map +1 -0
  97. package/dist/providers/container.d.ts +21 -0
  98. package/dist/providers/container.js +310 -0
  99. package/dist/providers/container.js.map +1 -0
  100. package/dist/providers/daytona.d.ts +10 -0
  101. package/dist/providers/daytona.js +153 -0
  102. package/dist/providers/daytona.js.map +1 -0
  103. package/dist/providers/docker.d.ts +3 -0
  104. package/dist/providers/docker.js +3 -0
  105. package/dist/providers/docker.js.map +1 -0
  106. package/dist/providers/factories.d.ts +9 -0
  107. package/dist/providers/factories.js +12 -0
  108. package/dist/providers/factories.js.map +1 -0
  109. package/dist/providers/local.d.ts +4 -0
  110. package/dist/providers/local.js +64 -0
  111. package/dist/providers/local.js.map +1 -0
  112. package/dist/providers/podman.d.ts +3 -0
  113. package/dist/providers/podman.js +3 -0
  114. package/dist/providers/podman.js.map +1 -0
  115. package/dist/providers/vercel.d.ts +9 -0
  116. package/dist/providers/vercel.js +158 -0
  117. package/dist/providers/vercel.js.map +1 -0
  118. package/dist/providers/versions.d.ts +4 -0
  119. package/dist/providers/versions.js +5 -0
  120. package/dist/providers/versions.js.map +1 -0
  121. package/docs/api.md +147 -0
  122. package/docs/architecture.md +21 -0
  123. package/docs/migration-1.1.fr.md +44 -0
  124. package/docs/migration-1.1.md +38 -0
  125. package/docs/operations.md +101 -0
  126. package/docs/providers.md +119 -0
  127. package/docs/workflows.md +101 -0
  128. package/package.json +95 -0
@@ -0,0 +1,304 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { appendFile, mkdir, open, readFile, rm, stat, rename, } from "node:fs/promises";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import { OutpostError, recordRecovery } from "../domain/errors.js";
5
+ import { copySelected, directory, inside } from "./files.js";
6
+ import { executeProcess, requireSuccess } from "./process.js";
7
+ export async function git(cwd, args, deadlineMs = 30_000, stdin) {
8
+ const result = await requireSuccess({
9
+ executable: "git",
10
+ arguments: [
11
+ "-c",
12
+ "core.hooksPath=" + (process.platform === "win32" ? "NUL" : "/dev/null"),
13
+ ...args,
14
+ ],
15
+ directory: cwd,
16
+ variables: { LC_ALL: "C", GIT_TERMINAL_PROMPT: "0" },
17
+ deadlineMs,
18
+ retain: 16_777_216,
19
+ ...(stdin === undefined ? {} : { stdin }),
20
+ });
21
+ return result.stdout;
22
+ }
23
+ export async function commits(cwd, baseline, deadlineMs) {
24
+ const lines = await git(cwd, ["log", "--format=%H%x00%s", `${baseline}..HEAD`], deadlineMs);
25
+ return lines
26
+ .trim()
27
+ .split("\n")
28
+ .filter(Boolean)
29
+ .map((line) => {
30
+ const [oid = "", subject = ""] = line.split("\0");
31
+ return { oid, subject };
32
+ });
33
+ }
34
+ async function lock(root, key) {
35
+ const folder = join(root, ".outpost", "locks");
36
+ await mkdir(folder, { recursive: true });
37
+ const path = join(folder, `${createHash("sha256").update(key).digest("hex").slice(0, 20)}.json`);
38
+ for (let attempt = 0; attempt < 2; attempt++) {
39
+ try {
40
+ const file = await open(path, "wx", 0o600);
41
+ await file.writeFile(JSON.stringify({ pid: process.pid, nonce: randomUUID() }));
42
+ await file.close();
43
+ return () => rm(path, { force: true });
44
+ }
45
+ catch (cause) {
46
+ if (cause.code !== "EEXIST")
47
+ throw cause;
48
+ const owner = JSON.parse(await readFile(path, "utf8"));
49
+ let alive = true;
50
+ try {
51
+ if (!owner.pid)
52
+ throw new Error();
53
+ process.kill(owner.pid, 0);
54
+ }
55
+ catch (error) {
56
+ alive = error.code === "EPERM";
57
+ }
58
+ if (alive)
59
+ throw new OutpostError("conflict", `Workspace is already in use: ${key}`, { lock: path, pid: owner.pid });
60
+ await rm(path, { force: true });
61
+ }
62
+ }
63
+ throw new OutpostError("conflict", "Unable to acquire workspace lock");
64
+ }
65
+ export async function acquireWorkspace(options) {
66
+ const requested = await directory(options.repository);
67
+ const root = (await git(requested, ["rev-parse", "--show-toplevel"], options.limits?.gitMs)).trim();
68
+ const repository = await directory(root);
69
+ const exclude = (await git(repository, [
70
+ "rev-parse",
71
+ "--path-format=absolute",
72
+ "--git-path",
73
+ "info/exclude",
74
+ ])).trim();
75
+ const exclusions = await readFile(exclude, "utf8").catch(() => "");
76
+ const patterns = [
77
+ "/.outpost/workspaces/",
78
+ "/.outpost/locks/",
79
+ "/.outpost/recovery/",
80
+ "/.outpost/logs/",
81
+ ];
82
+ const missing = patterns.filter((pattern) => !exclusions.split(/\r?\n/).includes(pattern));
83
+ if (missing.length) {
84
+ await mkdir(dirname(exclude), { recursive: true });
85
+ await appendFile(exclude, `\n${missing.join("\n")}\n`);
86
+ }
87
+ const policy = options.branch ?? { mode: "current" };
88
+ const baseBranch = (await git(repository, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "")).trim();
89
+ if (policy.mode === "integrate" && !baseBranch)
90
+ throw new OutpostError("workspace", "Automatic integration requires an attached host branch");
91
+ if (policy.mode === "current" && options.copies?.length)
92
+ throw new OutpostError("configuration", "Copied inputs require a separate workspace");
93
+ const branch = policy.mode === "current"
94
+ ? baseBranch || "HEAD"
95
+ : policy.mode === "named"
96
+ ? policy.name
97
+ : `outpost/${options.label
98
+ ? options.label
99
+ .toLowerCase()
100
+ .replace(/[^a-z0-9]+/g, "-")
101
+ .slice(0, 48) + "-"
102
+ : "job-"}${randomUUID()}`;
103
+ if (policy.mode !== "current")
104
+ await git(repository, ["check-ref-format", "--branch", branch]);
105
+ const unlock = await lock(repository, policy.mode === "current" ? repository : branch);
106
+ let workdir = repository;
107
+ let created = false;
108
+ let disposal;
109
+ try {
110
+ if (policy.mode !== "current") {
111
+ const hash = createHash("sha256")
112
+ .update(branch)
113
+ .digest("hex")
114
+ .slice(0, 12);
115
+ const label = options.label
116
+ ?.toLowerCase()
117
+ .replace(/[^a-z0-9]+/g, "-")
118
+ .slice(0, 48);
119
+ const managed = join(repository, ".outpost", "workspaces");
120
+ const expected = join(managed, `${label ? label + "-" : ""}${hash}`);
121
+ await git(repository, ["worktree", "prune", "--expire", "now"]);
122
+ const list = await git(repository, [
123
+ "worktree",
124
+ "list",
125
+ "--porcelain",
126
+ "-z",
127
+ ]);
128
+ const entries = list.split("\0\0").map((item) => item.split("\0"));
129
+ const existing = entries.find((fields) => fields.includes(`branch refs/heads/${branch}`) ||
130
+ (fields.includes("detached") &&
131
+ fields.some((field) => field.startsWith("worktree ") &&
132
+ inside(managed, field.slice(9)) &&
133
+ (basename(field.slice(9)) === hash ||
134
+ basename(field.slice(9)).endsWith("-" + hash)))));
135
+ if (existing) {
136
+ workdir = existing
137
+ .find((field) => field.startsWith("worktree "))
138
+ .slice(9);
139
+ if (!inside(managed, workdir) || resolve(workdir) === resolve(managed))
140
+ throw new OutpostError("conflict", `Branch ${branch} is checked out elsewhere`, { path: workdir });
141
+ workdir = await directory(workdir);
142
+ const attached = (await git(workdir, [
143
+ "symbolic-ref",
144
+ "--quiet",
145
+ "--short",
146
+ "HEAD",
147
+ ]).catch(() => "")).trim();
148
+ const dirty = (await git(workdir, ["status", "--porcelain"])).trim();
149
+ if (attached === branch && !dirty) {
150
+ const remote = (await git(repository, ["remote"]))
151
+ .split(/\s+/)
152
+ .includes("origin");
153
+ if (remote) {
154
+ const fetched = await git(repository, [
155
+ "fetch",
156
+ "origin",
157
+ `refs/heads/${branch}:refs/remotes/origin/${branch}`,
158
+ ], options.limits?.gitMs).then(() => true, () => false);
159
+ if (fetched) {
160
+ const head = (await git(workdir, ["rev-parse", "HEAD"])).trim();
161
+ const target = `refs/remotes/origin/${branch}`;
162
+ const forward = await git(repository, [
163
+ "merge-base",
164
+ "--is-ancestor",
165
+ head,
166
+ target,
167
+ ]).then(() => true, () => false);
168
+ if (forward)
169
+ await git(workdir, ["merge", "--ff-only", target], options.limits?.gitMs);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ else {
175
+ if (await stat(expected).catch((error) => {
176
+ if (error.code === "ENOENT")
177
+ return undefined;
178
+ throw error;
179
+ })) {
180
+ const managed = join(repository, ".outpost", "workspaces");
181
+ if (!inside(managed, expected) ||
182
+ resolve(expected) === resolve(managed))
183
+ throw new OutpostError("workspace", "Refusing to relocate an unmanaged directory");
184
+ const recovery = join(repository, ".outpost", "recovery", `orphan-${hash}-${randomUUID()}`);
185
+ await mkdir(dirname(recovery), { recursive: true });
186
+ await rename(expected, recovery);
187
+ }
188
+ await mkdir(join(repository, ".outpost", "workspaces"), {
189
+ recursive: true,
190
+ });
191
+ const branchExists = await executeProcess({
192
+ executable: "git",
193
+ arguments: [
194
+ "show-ref",
195
+ "--verify",
196
+ "--quiet",
197
+ `refs/heads/${branch}`,
198
+ ],
199
+ directory: repository,
200
+ });
201
+ const from = policy.from ?? "HEAD";
202
+ const oid = (await git(repository, [
203
+ "rev-parse",
204
+ "--verify",
205
+ "--end-of-options",
206
+ `${from}^{commit}`,
207
+ ])).trim();
208
+ await git(repository, [
209
+ "-c",
210
+ "branch.autoSetupMerge=false",
211
+ "worktree",
212
+ "add",
213
+ ...(branchExists.status === 0 ? [] : ["-b", branch]),
214
+ expected,
215
+ branchExists.status === 0 ? branch : oid,
216
+ ], options.limits?.gitMs);
217
+ workdir = expected;
218
+ created = true;
219
+ }
220
+ }
221
+ await copySelected(repository, workdir, options.copies ?? [], options.limits?.copyMs);
222
+ const baseline = (await git(workdir, ["rev-parse", "HEAD"])).trim();
223
+ const gitDir = (await git(workdir, ["rev-parse", "--absolute-git-dir"])).trim();
224
+ const common = (await git(workdir, [
225
+ "rev-parse",
226
+ "--path-format=absolute",
227
+ "--git-common-dir",
228
+ ])).trim();
229
+ return {
230
+ repository,
231
+ directory: workdir,
232
+ branch,
233
+ baseBranch,
234
+ baseline,
235
+ policy,
236
+ gitDirectories: [...new Set([gitDir, common])],
237
+ async integrate() {
238
+ if (policy.mode !== "integrate")
239
+ return;
240
+ const current = (await git(repository, ["symbolic-ref", "--short", "HEAD"])).trim();
241
+ if (current !== baseBranch)
242
+ throw new OutpostError("conflict", "Host branch changed during the job", { expected: baseBranch, current, directory: workdir });
243
+ const releaseMerge = await lock(repository, `merge:${baseBranch}`);
244
+ try {
245
+ await git(repository, ["merge", "--no-edit", branch], options.limits?.mergeMs);
246
+ }
247
+ catch (cause) {
248
+ throw new OutpostError("conflict", "Automatic integration failed; workspace retained", { directory: workdir, branch }, cause);
249
+ }
250
+ finally {
251
+ await releaseMerge();
252
+ }
253
+ },
254
+ dispose(preserve = false) {
255
+ if (disposal)
256
+ return disposal;
257
+ disposal = (async () => {
258
+ try {
259
+ if (policy.mode === "current")
260
+ return {};
261
+ if (preserve)
262
+ return { retainedDirectory: workdir };
263
+ const attached = await git(workdir, [
264
+ "symbolic-ref",
265
+ "--quiet",
266
+ "HEAD",
267
+ ]).catch(() => "");
268
+ if (!attached.trim())
269
+ return { retainedDirectory: workdir };
270
+ const dirty = await git(workdir, [
271
+ "status",
272
+ "--porcelain",
273
+ "--untracked-files=normal",
274
+ ]);
275
+ if (dirty.trim())
276
+ return { retainedDirectory: workdir };
277
+ if (!inside(join(repository, ".outpost", "workspaces"), workdir))
278
+ throw new OutpostError("workspace", "Refusing to remove an unmanaged workspace");
279
+ await git(repository, ["worktree", "remove", workdir]);
280
+ if (policy.mode === "integrate")
281
+ await git(repository, ["branch", "-d", branch]).catch(() => undefined);
282
+ return {};
283
+ }
284
+ finally {
285
+ await unlock();
286
+ }
287
+ })();
288
+ return disposal;
289
+ },
290
+ };
291
+ }
292
+ catch (error) {
293
+ if (created) {
294
+ const clean = await git(workdir, ["status", "--porcelain"]).then((output) => !output.trim(), () => false);
295
+ if (clean && inside(join(repository, ".outpost", "workspaces"), workdir))
296
+ await git(repository, ["worktree", "remove", workdir]).catch(() => undefined);
297
+ else
298
+ recordRecovery(error, { branch, directory: workdir });
299
+ }
300
+ await unlock();
301
+ throw error;
302
+ }
303
+ }
304
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/infrastructure/git.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n appendFile,\n mkdir,\n open,\n readFile,\n rm,\n stat,\n rename,\n} from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { OutpostError, recordRecovery } from \"../domain/errors.ts\";\nimport type {\n BranchPolicy,\n Commit,\n Disposal,\n StageLimits,\n WorkspaceRecord,\n} from \"../domain/ports.ts\";\nimport { copySelected, directory, inside } from \"./files.ts\";\nimport { executeProcess, requireSuccess } from \"./process.ts\";\n\nexport async function git(\n cwd: string,\n args: readonly string[],\n deadlineMs = 30_000,\n stdin?: string,\n): Promise<string> {\n const result = await requireSuccess({\n executable: \"git\",\n arguments: [\n \"-c\",\n \"core.hooksPath=\" + (process.platform === \"win32\" ? \"NUL\" : \"/dev/null\"),\n ...args,\n ],\n directory: cwd,\n variables: { LC_ALL: \"C\", GIT_TERMINAL_PROMPT: \"0\" },\n deadlineMs,\n retain: 16_777_216,\n ...(stdin === undefined ? {} : { stdin }),\n });\n return result.stdout;\n}\n\nexport async function commits(\n cwd: string,\n baseline: string,\n deadlineMs?: number,\n): Promise<Commit[]> {\n const lines = await git(\n cwd,\n [\"log\", \"--format=%H%x00%s\", `${baseline}..HEAD`],\n deadlineMs,\n );\n return lines\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => {\n const [oid = \"\", subject = \"\"] = line.split(\"\\0\");\n return { oid, subject };\n });\n}\n\nasync function lock(root: string, key: string): Promise<() => Promise<void>> {\n const folder = join(root, \".outpost\", \"locks\");\n await mkdir(folder, { recursive: true });\n const path = join(\n folder,\n `${createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 20)}.json`,\n );\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const file = await open(path, \"wx\", 0o600);\n await file.writeFile(\n JSON.stringify({ pid: process.pid, nonce: randomUUID() }),\n );\n await file.close();\n return () => rm(path, { force: true });\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code !== \"EEXIST\") throw cause;\n const owner = JSON.parse(await readFile(path, \"utf8\")) as {\n pid?: number;\n };\n let alive = true;\n try {\n if (!owner.pid) throw new Error();\n process.kill(owner.pid, 0);\n } catch (error) {\n alive = (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n if (alive)\n throw new OutpostError(\n \"conflict\",\n `Workspace is already in use: ${key}`,\n { lock: path, pid: owner.pid },\n );\n await rm(path, { force: true });\n }\n }\n throw new OutpostError(\"conflict\", \"Unable to acquire workspace lock\");\n}\n\nexport interface WorkspaceLease extends WorkspaceRecord {\n integrate(): Promise<void>;\n dispose(preserve?: boolean): Promise<Disposal>;\n}\n\nexport async function acquireWorkspace(options: {\n repository?: string;\n branch?: BranchPolicy;\n copies?: readonly string[];\n limits?: StageLimits;\n label?: string;\n}): Promise<WorkspaceLease> {\n const requested = await directory(options.repository);\n const root = (\n await git(\n requested,\n [\"rev-parse\", \"--show-toplevel\"],\n options.limits?.gitMs,\n )\n ).trim();\n const repository = await directory(root);\n const exclude = (\n await git(repository, [\n \"rev-parse\",\n \"--path-format=absolute\",\n \"--git-path\",\n \"info/exclude\",\n ])\n ).trim();\n const exclusions = await readFile(exclude, \"utf8\").catch(() => \"\");\n const patterns = [\n \"/.outpost/workspaces/\",\n \"/.outpost/locks/\",\n \"/.outpost/recovery/\",\n \"/.outpost/logs/\",\n ];\n const missing = patterns.filter(\n (pattern) => !exclusions.split(/\\r?\\n/).includes(pattern),\n );\n if (missing.length) {\n await mkdir(dirname(exclude), { recursive: true });\n await appendFile(exclude, `\\n${missing.join(\"\\n\")}\\n`);\n }\n const policy = options.branch ?? { mode: \"current\" };\n const baseBranch = (\n await git(repository, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]).catch(\n () => \"\",\n )\n ).trim();\n if (policy.mode === \"integrate\" && !baseBranch)\n throw new OutpostError(\n \"workspace\",\n \"Automatic integration requires an attached host branch\",\n );\n if (policy.mode === \"current\" && options.copies?.length)\n throw new OutpostError(\n \"configuration\",\n \"Copied inputs require a separate workspace\",\n );\n const branch =\n policy.mode === \"current\"\n ? baseBranch || \"HEAD\"\n : policy.mode === \"named\"\n ? policy.name\n : `outpost/${\n options.label\n ? options.label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .slice(0, 48) + \"-\"\n : \"job-\"\n }${randomUUID()}`;\n if (policy.mode !== \"current\")\n await git(repository, [\"check-ref-format\", \"--branch\", branch]);\n const unlock = await lock(\n repository,\n policy.mode === \"current\" ? repository : branch,\n );\n let workdir = repository;\n let created = false;\n let disposal: Promise<Disposal> | undefined;\n try {\n if (policy.mode !== \"current\") {\n const hash = createHash(\"sha256\")\n .update(branch)\n .digest(\"hex\")\n .slice(0, 12);\n const label = options.label\n ?.toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .slice(0, 48);\n const managed = join(repository, \".outpost\", \"workspaces\");\n const expected = join(managed, `${label ? label + \"-\" : \"\"}${hash}`);\n await git(repository, [\"worktree\", \"prune\", \"--expire\", \"now\"]);\n const list = await git(repository, [\n \"worktree\",\n \"list\",\n \"--porcelain\",\n \"-z\",\n ]);\n const entries = list.split(\"\\0\\0\").map((item) => item.split(\"\\0\"));\n const existing = entries.find(\n (fields) =>\n fields.includes(`branch refs/heads/${branch}`) ||\n (fields.includes(\"detached\") &&\n fields.some(\n (field) =>\n field.startsWith(\"worktree \") &&\n inside(managed, field.slice(9)) &&\n (basename(field.slice(9)) === hash ||\n basename(field.slice(9)).endsWith(\"-\" + hash)),\n )),\n );\n if (existing) {\n workdir = existing\n .find((field) => field.startsWith(\"worktree \"))!\n .slice(9);\n if (!inside(managed, workdir) || resolve(workdir) === resolve(managed))\n throw new OutpostError(\n \"conflict\",\n `Branch ${branch} is checked out elsewhere`,\n { path: workdir },\n );\n workdir = await directory(workdir);\n const attached = (\n await git(workdir, [\n \"symbolic-ref\",\n \"--quiet\",\n \"--short\",\n \"HEAD\",\n ]).catch(() => \"\")\n ).trim();\n const dirty = (await git(workdir, [\"status\", \"--porcelain\"])).trim();\n if (attached === branch && !dirty) {\n const remote = (await git(repository, [\"remote\"]))\n .split(/\\s+/)\n .includes(\"origin\");\n if (remote) {\n const fetched = await git(\n repository,\n [\n \"fetch\",\n \"origin\",\n `refs/heads/${branch}:refs/remotes/origin/${branch}`,\n ],\n options.limits?.gitMs,\n ).then(\n () => true,\n () => false,\n );\n if (fetched) {\n const head = (await git(workdir, [\"rev-parse\", \"HEAD\"])).trim();\n const target = `refs/remotes/origin/${branch}`;\n const forward = await git(repository, [\n \"merge-base\",\n \"--is-ancestor\",\n head,\n target,\n ]).then(\n () => true,\n () => false,\n );\n if (forward)\n await git(\n workdir,\n [\"merge\", \"--ff-only\", target],\n options.limits?.gitMs,\n );\n }\n }\n }\n } else {\n if (\n await stat(expected).catch((error) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\")\n return undefined;\n throw error;\n })\n ) {\n const managed = join(repository, \".outpost\", \"workspaces\");\n if (\n !inside(managed, expected) ||\n resolve(expected) === resolve(managed)\n )\n throw new OutpostError(\n \"workspace\",\n \"Refusing to relocate an unmanaged directory\",\n );\n const recovery = join(\n repository,\n \".outpost\",\n \"recovery\",\n `orphan-${hash}-${randomUUID()}`,\n );\n await mkdir(dirname(recovery), { recursive: true });\n await rename(expected, recovery);\n }\n await mkdir(join(repository, \".outpost\", \"workspaces\"), {\n recursive: true,\n });\n const branchExists = await executeProcess({\n executable: \"git\",\n arguments: [\n \"show-ref\",\n \"--verify\",\n \"--quiet\",\n `refs/heads/${branch}`,\n ],\n directory: repository,\n });\n const from = policy.from ?? \"HEAD\";\n const oid = (\n await git(repository, [\n \"rev-parse\",\n \"--verify\",\n \"--end-of-options\",\n `${from}^{commit}`,\n ])\n ).trim();\n await git(\n repository,\n [\n \"-c\",\n \"branch.autoSetupMerge=false\",\n \"worktree\",\n \"add\",\n ...(branchExists.status === 0 ? [] : [\"-b\", branch]),\n expected,\n branchExists.status === 0 ? branch : oid,\n ],\n options.limits?.gitMs,\n );\n workdir = expected;\n created = true;\n }\n }\n await copySelected(\n repository,\n workdir,\n options.copies ?? [],\n options.limits?.copyMs,\n );\n const baseline = (await git(workdir, [\"rev-parse\", \"HEAD\"])).trim();\n const gitDir = (\n await git(workdir, [\"rev-parse\", \"--absolute-git-dir\"])\n ).trim();\n const common = (\n await git(workdir, [\n \"rev-parse\",\n \"--path-format=absolute\",\n \"--git-common-dir\",\n ])\n ).trim();\n return {\n repository,\n directory: workdir,\n branch,\n baseBranch,\n baseline,\n policy,\n gitDirectories: [...new Set([gitDir, common])],\n async integrate() {\n if (policy.mode !== \"integrate\") return;\n const current = (\n await git(repository, [\"symbolic-ref\", \"--short\", \"HEAD\"])\n ).trim();\n if (current !== baseBranch)\n throw new OutpostError(\n \"conflict\",\n \"Host branch changed during the job\",\n { expected: baseBranch, current, directory: workdir },\n );\n const releaseMerge = await lock(repository, `merge:${baseBranch}`);\n try {\n await git(\n repository,\n [\"merge\", \"--no-edit\", branch],\n options.limits?.mergeMs,\n );\n } catch (cause) {\n throw new OutpostError(\n \"conflict\",\n \"Automatic integration failed; workspace retained\",\n { directory: workdir, branch },\n cause,\n );\n } finally {\n await releaseMerge();\n }\n },\n dispose(preserve = false) {\n if (disposal) return disposal;\n disposal = (async () => {\n try {\n if (policy.mode === \"current\") return {};\n if (preserve) return { retainedDirectory: workdir };\n const attached = await git(workdir, [\n \"symbolic-ref\",\n \"--quiet\",\n \"HEAD\",\n ]).catch(() => \"\");\n if (!attached.trim()) return { retainedDirectory: workdir };\n const dirty = await git(workdir, [\n \"status\",\n \"--porcelain\",\n \"--untracked-files=normal\",\n ]);\n if (dirty.trim()) return { retainedDirectory: workdir };\n if (!inside(join(repository, \".outpost\", \"workspaces\"), workdir))\n throw new OutpostError(\n \"workspace\",\n \"Refusing to remove an unmanaged workspace\",\n );\n await git(repository, [\"worktree\", \"remove\", workdir]);\n if (policy.mode === \"integrate\")\n await git(repository, [\"branch\", \"-d\", branch]).catch(\n () => undefined,\n );\n return {};\n } finally {\n await unlock();\n }\n })();\n return disposal;\n },\n };\n } catch (error) {\n if (created) {\n const clean = await git(workdir, [\"status\", \"--porcelain\"]).then(\n (output) => !output.trim(),\n () => false,\n );\n if (clean && inside(join(repository, \".outpost\", \"workspaces\"), workdir))\n await git(repository, [\"worktree\", \"remove\", workdir]).catch(\n () => undefined,\n );\n else recordRecovery(error, { branch, directory: workdir });\n }\n await unlock();\n throw error;\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { AgentEvent } from "../domain/ports.ts";
2
+ export type Logging = false | "stdout" | {
3
+ readonly file?: string;
4
+ readonly verbose?: boolean;
5
+ };
6
+ export declare function journal(repository: string, logging?: Logging, label?: string): Promise<{
7
+ file?: string;
8
+ record(event: AgentEvent): void;
9
+ close(): Promise<void>;
10
+ }>;
@@ -0,0 +1,53 @@
1
+ import { mkdir, open } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { reporter } from "./reporter.js";
5
+ export async function journal(repository, logging = {}, label) {
6
+ const file = logging && logging !== "stdout"
7
+ ? resolve(repository, logging.file ??
8
+ join(".outpost", "logs", `${label ? label.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 64) + "-" : ""}${new Date().toISOString().replaceAll(":", "-")}-${randomUUID()}.jsonl`))
9
+ : undefined;
10
+ let handle, pending = Promise.resolve();
11
+ let failure;
12
+ const display = reporter({ ...(label ? { label } : {}) });
13
+ if (file) {
14
+ await mkdir(dirname(file), { recursive: true });
15
+ handle = await open(file, "a", 0o600);
16
+ await handle.write(JSON.stringify({
17
+ kind: "dispatch-start",
18
+ at: new Date().toISOString(),
19
+ label,
20
+ }) + "\n");
21
+ }
22
+ return {
23
+ ...(file ? { file } : {}),
24
+ record(event) {
25
+ if (!logging)
26
+ return;
27
+ if (event.kind === "raw" && logging !== "stdout" && !logging.verbose)
28
+ return;
29
+ const line = JSON.stringify({
30
+ at: new Date().toISOString(),
31
+ ...(label ? { label } : {}),
32
+ ...event,
33
+ }) + "\n";
34
+ if (logging === "stdout")
35
+ display(event);
36
+ else
37
+ pending = pending
38
+ .then(async () => {
39
+ await handle?.write(line);
40
+ })
41
+ .catch((error) => {
42
+ failure = error;
43
+ });
44
+ },
45
+ async close() {
46
+ await pending;
47
+ await handle?.close();
48
+ if (failure)
49
+ throw failure;
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/infrastructure/journal.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, open, type FileHandle } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport type { AgentEvent } from \"../domain/ports.ts\";\nimport { reporter } from \"./reporter.ts\";\n\nexport type Logging =\n false | \"stdout\" | { readonly file?: string; readonly verbose?: boolean };\n\nexport async function journal(\n repository: string,\n logging: Logging = {},\n label?: string,\n): Promise<{\n file?: string;\n record(event: AgentEvent): void;\n close(): Promise<void>;\n}> {\n const file =\n logging && logging !== \"stdout\"\n ? resolve(\n repository,\n logging.file ??\n join(\n \".outpost\",\n \"logs\",\n `${label ? label.replace(/[^A-Za-z0-9_-]/g, \"-\").slice(0, 64) + \"-\" : \"\"}${new Date().toISOString().replaceAll(\":\", \"-\")}-${randomUUID()}.jsonl`,\n ),\n )\n : undefined;\n let handle: FileHandle | undefined,\n pending = Promise.resolve();\n let failure: unknown;\n const display = reporter({ ...(label ? { label } : {}) });\n if (file) {\n await mkdir(dirname(file), { recursive: true });\n handle = await open(file, \"a\", 0o600);\n await handle.write(\n JSON.stringify({\n kind: \"dispatch-start\",\n at: new Date().toISOString(),\n label,\n }) + \"\\n\",\n );\n }\n return {\n ...(file ? { file } : {}),\n record(event) {\n if (!logging) return;\n if (event.kind === \"raw\" && logging !== \"stdout\" && !logging.verbose)\n return;\n const line =\n JSON.stringify({\n at: new Date().toISOString(),\n ...(label ? { label } : {}),\n ...event,\n }) + \"\\n\";\n if (logging === \"stdout\") display(event);\n else\n pending = pending\n .then(async () => {\n await handle?.write(line);\n })\n .catch((error) => {\n failure = error;\n });\n },\n async close() {\n await pending;\n await handle?.close();\n if (failure) throw failure;\n },\n };\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { Command, CommandResult } from "../domain/ports.ts";
2
+ export type Executor = (command: Command) => Promise<CommandResult>;
3
+ export declare const executeProcess: Executor;
4
+ export declare function requireSuccess(command: Command, executor?: Executor): Promise<CommandResult>;
5
+ export declare function shell(script: string): Pick<Command, "executable" | "arguments">;
6
+ export declare function quote(value: string): string;
@@ -0,0 +1,133 @@
1
+ import { spawn } from "node:child_process";
2
+ import { StringDecoder } from "node:string_decoder";
3
+ import { OutpostError, positive } from "../domain/errors.js";
4
+ import { restoreTerminal } from "./terminal.js";
5
+ export const executeProcess = (command) => {
6
+ command.signal?.throwIfAborted();
7
+ const deadline = command.deadlineMs ?? 600_000;
8
+ positive(deadline, "deadlineMs");
9
+ const limit = command.retain ?? 65_536;
10
+ positive(limit, "retain");
11
+ return new Promise((resolve, reject) => {
12
+ const interactive = command.interactive === true;
13
+ const child = spawn(command.executable, [...(command.arguments ?? [])], {
14
+ cwd: command.directory,
15
+ env: { ...process.env, ...command.variables },
16
+ shell: false,
17
+ windowsHide: true,
18
+ detached: !interactive && process.platform !== "win32",
19
+ stdio: interactive && !command.terminal ? "inherit" : ["pipe", "pipe", "pipe"],
20
+ });
21
+ const decoder = {
22
+ stdout: new StringDecoder("utf8"),
23
+ stderr: new StringDecoder("utf8"),
24
+ };
25
+ const output = { stdout: "", stderr: "" };
26
+ let failed = false, reason, escalation;
27
+ function kill(hard) {
28
+ if (!child.pid)
29
+ return;
30
+ if (process.platform === "win32") {
31
+ const killer = spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
32
+ killer.on("error", () => {
33
+ child.kill();
34
+ });
35
+ killer.on("exit", (status) => {
36
+ if (status !== 0)
37
+ child.kill();
38
+ });
39
+ if (hard)
40
+ child.kill();
41
+ }
42
+ else {
43
+ try {
44
+ process.kill(interactive ? child.pid : -child.pid, hard ? "SIGKILL" : "SIGTERM");
45
+ }
46
+ catch {
47
+ child.kill(hard ? "SIGKILL" : "SIGTERM");
48
+ }
49
+ }
50
+ }
51
+ function interrupt(error) {
52
+ if (failed)
53
+ return;
54
+ failed = true;
55
+ reason = error;
56
+ kill(false);
57
+ escalation = setTimeout(() => kill(true), 500);
58
+ }
59
+ const abort = () => interrupt(command.signal?.reason ??
60
+ new OutpostError("aborted", "Command cancelled"));
61
+ const timer = setTimeout(() => interrupt(new OutpostError("timeout", `Command exceeded ${deadline} ms`, {
62
+ deadlineMs: deadline,
63
+ })), deadline);
64
+ const consume = (channel, text) => {
65
+ output[channel] = (output[channel] + text).slice(-limit);
66
+ try {
67
+ command.observe?.(channel, text);
68
+ }
69
+ catch (error) {
70
+ interrupt(error);
71
+ }
72
+ };
73
+ child.stdout?.on("data", (data) => consume("stdout", decoder.stdout.write(data)));
74
+ child.stderr?.on("data", (data) => consume("stderr", decoder.stderr.write(data)));
75
+ child.stdin?.on("error", (error) => {
76
+ if (error.code !== "EPIPE")
77
+ interrupt(error);
78
+ });
79
+ child.on("error", (error) => {
80
+ failed = true;
81
+ reason = error;
82
+ });
83
+ command.signal?.addEventListener("abort", abort, { once: true });
84
+ if (command.signal?.aborted)
85
+ abort();
86
+ child.on("close", (status) => {
87
+ if (interactive)
88
+ restoreTerminal();
89
+ if (child.stdin)
90
+ command.terminal?.input?.unpipe(child.stdin);
91
+ clearTimeout(timer);
92
+ clearTimeout(escalation);
93
+ command.signal?.removeEventListener("abort", abort);
94
+ for (const channel of ["stdout", "stderr"]) {
95
+ const tail = decoder[channel].end();
96
+ if (tail)
97
+ consume(channel, tail);
98
+ }
99
+ if (failed)
100
+ reject(reason);
101
+ else
102
+ resolve({ status: status ?? 1, ...output });
103
+ });
104
+ if (child.stdin && command.terminal?.input)
105
+ command.terminal.input.pipe(child.stdin);
106
+ else
107
+ child.stdin?.end(command.stdin);
108
+ if (child.stdout && command.terminal?.output)
109
+ child.stdout.pipe(command.terminal.output, { end: false });
110
+ if (child.stderr && command.terminal?.error)
111
+ child.stderr.pipe(command.terminal.error, { end: false });
112
+ });
113
+ };
114
+ export async function requireSuccess(command, executor = executeProcess) {
115
+ const result = await executor(command);
116
+ if (result.status !== 0)
117
+ throw new OutpostError("process", `${command.executable} exited with status ${result.status}`, { ...result });
118
+ return result;
119
+ }
120
+ export function shell(script) {
121
+ return process.platform === "win32"
122
+ ? {
123
+ executable: "powershell.exe",
124
+ arguments: ["-NoProfile", "-NonInteractive", "-Command", script],
125
+ }
126
+ : { executable: "sh", arguments: ["-c", script] };
127
+ }
128
+ export function quote(value) {
129
+ if (value.includes("\0"))
130
+ throw new OutpostError("configuration", "A command argument cannot contain NUL");
131
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
132
+ }
133
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/infrastructure/process.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport { OutpostError, positive } from \"../domain/errors.ts\";\nimport type { Channel, Command, CommandResult } from \"../domain/ports.ts\";\nimport { restoreTerminal } from \"./terminal.ts\";\n\nexport type Executor = (command: Command) => Promise<CommandResult>;\n\nexport const executeProcess: Executor = (command) => {\n command.signal?.throwIfAborted();\n const deadline = command.deadlineMs ?? 600_000;\n positive(deadline, \"deadlineMs\");\n const limit = command.retain ?? 65_536;\n positive(limit, \"retain\");\n return new Promise((resolve, reject) => {\n const interactive = command.interactive === true;\n const child = spawn(command.executable, [...(command.arguments ?? [])], {\n cwd: command.directory,\n env: { ...process.env, ...command.variables },\n shell: false,\n windowsHide: true,\n detached: !interactive && process.platform !== \"win32\",\n stdio:\n interactive && !command.terminal ? \"inherit\" : [\"pipe\", \"pipe\", \"pipe\"],\n });\n const decoder = {\n stdout: new StringDecoder(\"utf8\"),\n stderr: new StringDecoder(\"utf8\"),\n };\n const output = { stdout: \"\", stderr: \"\" };\n let failed = false,\n reason: unknown,\n escalation: ReturnType<typeof setTimeout> | undefined;\n function kill(hard: boolean): void {\n if (!child.pid) return;\n if (process.platform === \"win32\") {\n const killer = spawn(\n \"taskkill\",\n [\"/PID\", String(child.pid), \"/T\", \"/F\"],\n { windowsHide: true, stdio: \"ignore\" },\n );\n killer.on(\"error\", () => {\n child.kill();\n });\n killer.on(\"exit\", (status) => {\n if (status !== 0) child.kill();\n });\n if (hard) child.kill();\n } else {\n try {\n process.kill(\n interactive ? child.pid : -child.pid,\n hard ? \"SIGKILL\" : \"SIGTERM\",\n );\n } catch {\n child.kill(hard ? \"SIGKILL\" : \"SIGTERM\");\n }\n }\n }\n function interrupt(error: unknown): void {\n if (failed) return;\n failed = true;\n reason = error;\n kill(false);\n escalation = setTimeout(() => kill(true), 500);\n }\n const abort = () =>\n interrupt(\n command.signal?.reason ??\n new OutpostError(\"aborted\", \"Command cancelled\"),\n );\n const timer = setTimeout(\n () =>\n interrupt(\n new OutpostError(\"timeout\", `Command exceeded ${deadline} ms`, {\n deadlineMs: deadline,\n }),\n ),\n deadline,\n );\n const consume = (channel: Channel, text: string) => {\n output[channel] = (output[channel] + text).slice(-limit);\n try {\n command.observe?.(channel, text);\n } catch (error) {\n interrupt(error);\n }\n };\n child.stdout?.on(\"data\", (data: Buffer) =>\n consume(\"stdout\", decoder.stdout.write(data)),\n );\n child.stderr?.on(\"data\", (data: Buffer) =>\n consume(\"stderr\", decoder.stderr.write(data)),\n );\n child.stdin?.on(\"error\", (error) => {\n if ((error as NodeJS.ErrnoException).code !== \"EPIPE\") interrupt(error);\n });\n child.on(\"error\", (error) => {\n failed = true;\n reason = error;\n });\n command.signal?.addEventListener(\"abort\", abort, { once: true });\n if (command.signal?.aborted) abort();\n child.on(\"close\", (status) => {\n if (interactive) restoreTerminal();\n if (child.stdin) command.terminal?.input?.unpipe(child.stdin);\n clearTimeout(timer);\n clearTimeout(escalation);\n command.signal?.removeEventListener(\"abort\", abort);\n for (const channel of [\"stdout\", \"stderr\"] as const) {\n const tail = decoder[channel].end();\n if (tail) consume(channel, tail);\n }\n if (failed) reject(reason);\n else resolve({ status: status ?? 1, ...output });\n });\n if (child.stdin && command.terminal?.input)\n command.terminal.input.pipe(child.stdin);\n else child.stdin?.end(command.stdin);\n if (child.stdout && command.terminal?.output)\n child.stdout.pipe(command.terminal.output, { end: false });\n if (child.stderr && command.terminal?.error)\n child.stderr.pipe(command.terminal.error, { end: false });\n });\n};\n\nexport async function requireSuccess(\n command: Command,\n executor = executeProcess,\n): Promise<CommandResult> {\n const result = await executor(command);\n if (result.status !== 0)\n throw new OutpostError(\n \"process\",\n `${command.executable} exited with status ${result.status}`,\n { ...result },\n );\n return result;\n}\n\nexport function shell(\n script: string,\n): Pick<Command, \"executable\" | \"arguments\"> {\n return process.platform === \"win32\"\n ? {\n executable: \"powershell.exe\",\n arguments: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script],\n }\n : { executable: \"sh\", arguments: [\"-c\", script] };\n}\n\nexport function quote(value: string): string {\n if (value.includes(\"\\0\"))\n throw new OutpostError(\n \"configuration\",\n \"A command argument cannot contain NUL\",\n );\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { AgentEvent } from "../domain/ports.ts";
2
+ export interface ReporterOptions {
3
+ readonly label?: string;
4
+ readonly verbose?: boolean;
5
+ readonly quiet?: boolean;
6
+ readonly write?: (text: string) => void;
7
+ }
8
+ export declare function reporter(options?: ReporterOptions): (event: AgentEvent & {
9
+ readonly pass?: number;
10
+ }) => void;