alchemylab-opencode 1.0.10 → 1.0.12

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.
@@ -0,0 +1,728 @@
1
+ // Built by esbuild from opencode-server source
2
+ if (typeof globalThis.Bun === 'undefined') {
3
+ globalThis.Bun = {
4
+ version: '0.0.0-node-compat',
5
+ stdin: { async text() { const chunks = []; for await (const c of process.stdin) chunks.push(c); return Buffer.concat(chunks).toString(); } },
6
+ which(cmd) { try { return require('child_process').execFileSync('which', [cmd], { encoding: 'utf8' }).trim(); } catch { return null; } },
7
+ };
8
+ }
9
+ if (typeof globalThis.__name === 'undefined') { globalThis.__name = (t, n) => { Object.defineProperty(t, 'name', { value: n, configurable: true }); return t; }; }
10
+ if (typeof globalThis.__export === 'undefined') { globalThis.__export = (t, a) => { for (const k in a) Object.defineProperty(t, k, { get: a[k], enumerable: true }); }; }
11
+
12
+ import {
13
+ ShareNext
14
+ } from "./chunk-MHT2AKV6.js";
15
+ import {
16
+ Command,
17
+ File,
18
+ FileWatcher,
19
+ Format,
20
+ Git,
21
+ LSP,
22
+ Plugin,
23
+ Slug,
24
+ Snapshot,
25
+ WorkspaceID
26
+ } from "./chunk-W7LNDS5C.js";
27
+ import {
28
+ Bus,
29
+ errorMessage
30
+ } from "./chunk-IMDQIMHW.js";
31
+ import {
32
+ AppFileSystem,
33
+ BusEvent,
34
+ Database,
35
+ Global,
36
+ GlobalBus,
37
+ Instance,
38
+ InstanceState,
39
+ Log,
40
+ NamedError,
41
+ Project,
42
+ ProjectID,
43
+ ProjectTable,
44
+ db_exports,
45
+ defaultLayer,
46
+ makeRuntime
47
+ } from "./chunk-3PZZ2D23.js";
48
+
49
+ // packages/opencode/src/worktree/index.ts
50
+ import z2 from "zod";
51
+
52
+ // packages/opencode/src/project/vcs.ts
53
+ import { Effect, Layer, ServiceMap, Stream } from "effect";
54
+ import path from "path";
55
+ import z from "zod";
56
+ var Vcs;
57
+ ((Vcs2) => {
58
+ const log = Log.create({ service: "vcs" });
59
+ const count = (text) => {
60
+ if (!text) return 0;
61
+ if (!text.endsWith("\n")) return text.split("\n").length;
62
+ return text.slice(0, -1).split("\n").length;
63
+ };
64
+ const work = Effect.fnUntraced(function* (fs, cwd, file) {
65
+ const full = path.join(cwd, file);
66
+ if (!(yield* fs.exists(full).pipe(Effect.orDie))) return "";
67
+ const buf = yield* fs.readFile(full).pipe(Effect.catch(() => Effect.succeed(new Uint8Array())));
68
+ if (Buffer.from(buf).includes(0)) return "";
69
+ return Buffer.from(buf).toString("utf8");
70
+ });
71
+ const nums = (list) => new Map(list.map((item) => [item.file, { additions: item.additions, deletions: item.deletions }]));
72
+ const merge = (...lists) => {
73
+ const out = /* @__PURE__ */ new Map();
74
+ lists.flat().forEach((item) => {
75
+ if (!out.has(item.file)) out.set(item.file, item);
76
+ });
77
+ return [...out.values()];
78
+ };
79
+ const files = Effect.fnUntraced(function* (fs, git, cwd, ref, list, map) {
80
+ const base = ref ? yield* git.prefix(cwd) : "";
81
+ const next = yield* Effect.forEach(
82
+ list,
83
+ (item) => Effect.gen(function* () {
84
+ const before = item.status === "added" || !ref ? "" : yield* git.show(cwd, ref, item.file, base);
85
+ const after = item.status === "deleted" ? "" : yield* work(fs, cwd, item.file);
86
+ const stat = map.get(item.file);
87
+ return {
88
+ file: item.file,
89
+ before,
90
+ after,
91
+ additions: stat?.additions ?? (item.status === "added" ? count(after) : 0),
92
+ deletions: stat?.deletions ?? (item.status === "deleted" ? count(before) : 0),
93
+ status: item.status
94
+ };
95
+ }),
96
+ { concurrency: 8 }
97
+ );
98
+ return next.toSorted((a, b) => a.file.localeCompare(b.file));
99
+ });
100
+ const track = Effect.fnUntraced(function* (fs, git, cwd, ref) {
101
+ if (!ref) return yield* files(fs, git, cwd, ref, yield* git.status(cwd), /* @__PURE__ */ new Map());
102
+ const [list, stats] = yield* Effect.all([git.status(cwd), git.stats(cwd, ref)], { concurrency: 2 });
103
+ return yield* files(fs, git, cwd, ref, list, nums(stats));
104
+ });
105
+ const compare = Effect.fnUntraced(function* (fs, git, cwd, ref) {
106
+ const [list, stats, extra] = yield* Effect.all([git.diff(cwd, ref), git.stats(cwd, ref), git.status(cwd)], {
107
+ concurrency: 3
108
+ });
109
+ return yield* files(
110
+ fs,
111
+ git,
112
+ cwd,
113
+ ref,
114
+ merge(
115
+ list,
116
+ extra.filter((item) => item.code === "??")
117
+ ),
118
+ nums(stats)
119
+ );
120
+ });
121
+ Vcs2.Mode = z.enum(["git", "branch"]);
122
+ Vcs2.Event = {
123
+ BranchUpdated: BusEvent.define(
124
+ "vcs.branch.updated",
125
+ z.object({
126
+ branch: z.string().optional()
127
+ })
128
+ )
129
+ };
130
+ Vcs2.Info = z.object({
131
+ branch: z.string().optional(),
132
+ default_branch: z.string().optional()
133
+ }).meta({
134
+ ref: "VcsInfo"
135
+ });
136
+ class Service extends ServiceMap.Service()("@opencode/Vcs") {
137
+ }
138
+ Vcs2.Service = Service;
139
+ Vcs2.layer = Layer.effect(
140
+ Service,
141
+ Effect.gen(function* () {
142
+ const fs = yield* AppFileSystem.Service;
143
+ const git = yield* Git.Service;
144
+ const bus = yield* Bus.Service;
145
+ const state = yield* InstanceState.make(
146
+ Effect.fn("Vcs.state")(
147
+ (ctx) => Effect.gen(function* () {
148
+ if (ctx.project.vcs !== "git") {
149
+ return { current: void 0, root: void 0 };
150
+ }
151
+ const get = Effect.fnUntraced(function* () {
152
+ return yield* git.branch(ctx.directory);
153
+ });
154
+ const [current, root] = yield* Effect.all([git.branch(ctx.directory), git.defaultBranch(ctx.directory)], {
155
+ concurrency: 2
156
+ });
157
+ const value = { current, root };
158
+ log.info("initialized", { branch: value.current, default_branch: value.root?.name });
159
+ yield* bus.subscribe(FileWatcher.Event.Updated).pipe(
160
+ Stream.filter((evt) => evt.properties.file.endsWith("HEAD")),
161
+ Stream.runForEach(
162
+ (_evt) => Effect.gen(function* () {
163
+ const next = yield* get();
164
+ if (next !== value.current) {
165
+ log.info("branch changed", { from: value.current, to: next });
166
+ value.current = next;
167
+ yield* bus.publish(Vcs2.Event.BranchUpdated, { branch: next });
168
+ }
169
+ })
170
+ ),
171
+ Effect.forkScoped
172
+ );
173
+ return value;
174
+ })
175
+ )
176
+ );
177
+ return Service.of({
178
+ init: Effect.fn("Vcs.init")(function* () {
179
+ yield* InstanceState.get(state);
180
+ }),
181
+ branch: Effect.fn("Vcs.branch")(function* () {
182
+ return yield* InstanceState.use(state, (x) => x.current);
183
+ }),
184
+ defaultBranch: Effect.fn("Vcs.defaultBranch")(function* () {
185
+ return yield* InstanceState.use(state, (x) => x.root?.name);
186
+ }),
187
+ diff: Effect.fn("Vcs.diff")(function* (mode) {
188
+ const value = yield* InstanceState.get(state);
189
+ if (Instance.project.vcs !== "git") return [];
190
+ if (mode === "git") {
191
+ return yield* track(
192
+ fs,
193
+ git,
194
+ Instance.directory,
195
+ (yield* git.hasHead(Instance.directory)) ? "HEAD" : void 0
196
+ );
197
+ }
198
+ if (!value.root) return [];
199
+ if (value.current && value.current === value.root.name) return [];
200
+ const ref = yield* git.mergeBase(Instance.directory, value.root.ref);
201
+ if (!ref) return [];
202
+ return yield* compare(fs, git, Instance.directory, ref);
203
+ })
204
+ });
205
+ })
206
+ );
207
+ const defaultLayer2 = Vcs2.layer.pipe(
208
+ Layer.provide(Git.defaultLayer),
209
+ Layer.provide(AppFileSystem.defaultLayer),
210
+ Layer.provide(Bus.layer)
211
+ );
212
+ const { runPromise } = makeRuntime(Service, defaultLayer2);
213
+ async function init() {
214
+ return runPromise((svc) => svc.init());
215
+ }
216
+ Vcs2.init = init;
217
+ async function branch() {
218
+ return runPromise((svc) => svc.branch());
219
+ }
220
+ Vcs2.branch = branch;
221
+ async function defaultBranch() {
222
+ return runPromise((svc) => svc.defaultBranch());
223
+ }
224
+ Vcs2.defaultBranch = defaultBranch;
225
+ async function diff(mode) {
226
+ return runPromise((svc) => svc.diff(mode));
227
+ }
228
+ Vcs2.diff = diff;
229
+ })(Vcs || (Vcs = {}));
230
+
231
+ // packages/opencode/src/project/bootstrap.ts
232
+ async function InstanceBootstrap() {
233
+ Log.Default.info("bootstrapping", { directory: Instance.directory });
234
+ await Plugin.init();
235
+ ShareNext.init();
236
+ Format.init();
237
+ await LSP.init();
238
+ File.init();
239
+ FileWatcher.init();
240
+ Vcs.init();
241
+ Snapshot.init();
242
+ Bus.subscribe(Command.Event.Executed, async (payload) => {
243
+ if (payload.properties.name === Command.Default.INIT) {
244
+ Project.setInitialized(Instance.project.id);
245
+ }
246
+ });
247
+ }
248
+
249
+ // packages/opencode/src/worktree/index.ts
250
+ import { Effect as Effect2, Layer as Layer2, Path, Scope, ServiceMap as ServiceMap2, Stream as Stream2 } from "effect";
251
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
252
+ import { NodePath } from "@effect/platform-node";
253
+ var Worktree;
254
+ ((Worktree2) => {
255
+ const log = Log.create({ service: "worktree" });
256
+ Worktree2.Event = {
257
+ Ready: BusEvent.define(
258
+ "worktree.ready",
259
+ z2.object({
260
+ name: z2.string(),
261
+ branch: z2.string()
262
+ })
263
+ ),
264
+ Failed: BusEvent.define(
265
+ "worktree.failed",
266
+ z2.object({
267
+ message: z2.string()
268
+ })
269
+ )
270
+ };
271
+ Worktree2.Info = z2.object({
272
+ name: z2.string(),
273
+ branch: z2.string(),
274
+ directory: z2.string()
275
+ }).meta({
276
+ ref: "Worktree"
277
+ });
278
+ Worktree2.CreateInput = z2.object({
279
+ name: z2.string().optional(),
280
+ startCommand: z2.string().optional().describe("Additional startup script to run after the project's start command")
281
+ }).meta({
282
+ ref: "WorktreeCreateInput"
283
+ });
284
+ Worktree2.RemoveInput = z2.object({
285
+ directory: z2.string()
286
+ }).meta({
287
+ ref: "WorktreeRemoveInput"
288
+ });
289
+ Worktree2.ResetInput = z2.object({
290
+ directory: z2.string()
291
+ }).meta({
292
+ ref: "WorktreeResetInput"
293
+ });
294
+ Worktree2.NotGitError = NamedError.create(
295
+ "WorktreeNotGitError",
296
+ z2.object({
297
+ message: z2.string()
298
+ })
299
+ );
300
+ Worktree2.NameGenerationFailedError = NamedError.create(
301
+ "WorktreeNameGenerationFailedError",
302
+ z2.object({
303
+ message: z2.string()
304
+ })
305
+ );
306
+ Worktree2.CreateFailedError = NamedError.create(
307
+ "WorktreeCreateFailedError",
308
+ z2.object({
309
+ message: z2.string()
310
+ })
311
+ );
312
+ Worktree2.StartCommandFailedError = NamedError.create(
313
+ "WorktreeStartCommandFailedError",
314
+ z2.object({
315
+ message: z2.string()
316
+ })
317
+ );
318
+ Worktree2.RemoveFailedError = NamedError.create(
319
+ "WorktreeRemoveFailedError",
320
+ z2.object({
321
+ message: z2.string()
322
+ })
323
+ );
324
+ Worktree2.ResetFailedError = NamedError.create(
325
+ "WorktreeResetFailedError",
326
+ z2.object({
327
+ message: z2.string()
328
+ })
329
+ );
330
+ function slugify(input) {
331
+ return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
332
+ }
333
+ function failedRemoves(...chunks) {
334
+ return chunks.filter(Boolean).flatMap(
335
+ (chunk) => chunk.split("\n").map((line) => line.trim()).flatMap((line) => {
336
+ const match = line.match(/^warning:\s+failed to remove\s+(.+):\s+/i);
337
+ if (!match) return [];
338
+ const value = match[1]?.trim().replace(/^['"]|['"]$/g, "");
339
+ if (!value) return [];
340
+ return [value];
341
+ })
342
+ );
343
+ }
344
+ class Service extends ServiceMap2.Service()("@opencode/Worktree") {
345
+ }
346
+ Worktree2.Service = Service;
347
+ Worktree2.layer = Layer2.effect(
348
+ Service,
349
+ Effect2.gen(function* () {
350
+ const scope = yield* Scope.Scope;
351
+ const fs = yield* AppFileSystem.Service;
352
+ const pathSvc = yield* Path.Path;
353
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
354
+ const project = yield* Project.Service;
355
+ const git = Effect2.fnUntraced(
356
+ function* (args, opts) {
357
+ const handle = yield* spawner.spawn(
358
+ ChildProcess.make("git", args, { cwd: opts?.cwd, extendEnv: true, stdin: "ignore" })
359
+ );
360
+ const [text, stderr] = yield* Effect2.all(
361
+ [Stream2.mkString(Stream2.decodeText(handle.stdout)), Stream2.mkString(Stream2.decodeText(handle.stderr))],
362
+ { concurrency: 2 }
363
+ );
364
+ const code = yield* handle.exitCode;
365
+ return { code, text, stderr };
366
+ },
367
+ Effect2.scoped,
368
+ Effect2.catch(
369
+ (e) => Effect2.succeed({ code: 1, text: "", stderr: e instanceof Error ? e.message : String(e) })
370
+ )
371
+ );
372
+ const MAX_NAME_ATTEMPTS = 26;
373
+ const candidate = Effect2.fn("Worktree.candidate")(function* (root, base) {
374
+ const ctx = yield* InstanceState.context;
375
+ for (const attempt of Array.from({ length: MAX_NAME_ATTEMPTS }, (_, i) => i)) {
376
+ const name = base ? attempt === 0 ? base : `${base}-${Slug.create()}` : Slug.create();
377
+ const branch = `opencode/${name}`;
378
+ const directory = pathSvc.join(root, name);
379
+ if (yield* fs.exists(directory).pipe(Effect2.orDie)) continue;
380
+ const ref = `refs/heads/${branch}`;
381
+ const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree });
382
+ if (branchCheck.code === 0) continue;
383
+ return Worktree2.Info.parse({ name, branch, directory });
384
+ }
385
+ throw new Worktree2.NameGenerationFailedError({ message: "Failed to generate a unique worktree name" });
386
+ });
387
+ const makeWorktreeInfo2 = Effect2.fn("Worktree.makeWorktreeInfo")(function* (name) {
388
+ const ctx = yield* InstanceState.context;
389
+ if (ctx.project.vcs !== "git") {
390
+ throw new Worktree2.NotGitError({ message: "Worktrees are only supported for git projects" });
391
+ }
392
+ const root = pathSvc.join(Global.Path.data, "worktree", ctx.project.id);
393
+ yield* fs.makeDirectory(root, { recursive: true }).pipe(Effect2.orDie);
394
+ const base = name ? slugify(name) : "";
395
+ return yield* candidate(root, base || void 0);
396
+ });
397
+ const setup = Effect2.fnUntraced(function* (info) {
398
+ const ctx = yield* InstanceState.context;
399
+ const created = yield* git(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], {
400
+ cwd: ctx.worktree
401
+ });
402
+ if (created.code !== 0) {
403
+ throw new Worktree2.CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" });
404
+ }
405
+ yield* project.addSandbox(ctx.project.id, info.directory).pipe(Effect2.catch(() => Effect2.void));
406
+ });
407
+ const boot = Effect2.fnUntraced(function* (info, startCommand) {
408
+ const ctx = yield* InstanceState.context;
409
+ const projectID = ctx.project.id;
410
+ const extra = startCommand?.trim();
411
+ const populated = yield* git(["reset", "--hard"], { cwd: info.directory });
412
+ if (populated.code !== 0) {
413
+ const message = populated.stderr || populated.text || "Failed to populate worktree";
414
+ log.error("worktree checkout failed", { directory: info.directory, message });
415
+ GlobalBus.emit("event", {
416
+ directory: info.directory,
417
+ payload: { type: Worktree2.Event.Failed.type, properties: { message } }
418
+ });
419
+ return;
420
+ }
421
+ const booted = yield* Effect2.promise(
422
+ () => Instance.provide({
423
+ directory: info.directory,
424
+ init: InstanceBootstrap,
425
+ fn: () => void 0
426
+ }).then(() => true).catch((error) => {
427
+ const message = errorMessage(error);
428
+ log.error("worktree bootstrap failed", { directory: info.directory, message });
429
+ GlobalBus.emit("event", {
430
+ directory: info.directory,
431
+ payload: { type: Worktree2.Event.Failed.type, properties: { message } }
432
+ });
433
+ return false;
434
+ })
435
+ );
436
+ if (!booted) return;
437
+ GlobalBus.emit("event", {
438
+ directory: info.directory,
439
+ payload: {
440
+ type: Worktree2.Event.Ready.type,
441
+ properties: { name: info.name, branch: info.branch }
442
+ }
443
+ });
444
+ yield* runStartScripts(info.directory, { projectID, extra });
445
+ });
446
+ const createFromInfo2 = Effect2.fn("Worktree.createFromInfo")(function* (info, startCommand) {
447
+ yield* setup(info);
448
+ yield* boot(info, startCommand);
449
+ });
450
+ const create2 = Effect2.fn("Worktree.create")(function* (input) {
451
+ const info = yield* makeWorktreeInfo2(input?.name);
452
+ yield* setup(info);
453
+ yield* boot(info, input?.startCommand).pipe(
454
+ Effect2.catchCause((cause) => Effect2.sync(() => log.error("worktree bootstrap failed", { cause }))),
455
+ Effect2.forkIn(scope)
456
+ );
457
+ return info;
458
+ });
459
+ const canonical = Effect2.fnUntraced(function* (input) {
460
+ const abs = pathSvc.resolve(input);
461
+ const real = yield* fs.realPath(abs).pipe(Effect2.catch(() => Effect2.succeed(abs)));
462
+ const normalized = pathSvc.normalize(real);
463
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
464
+ });
465
+ function parseWorktreeList(text) {
466
+ return text.split("\n").map((line) => line.trim()).reduce((acc, line) => {
467
+ if (!line) return acc;
468
+ if (line.startsWith("worktree ")) {
469
+ acc.push({ path: line.slice("worktree ".length).trim() });
470
+ return acc;
471
+ }
472
+ const current = acc[acc.length - 1];
473
+ if (!current) return acc;
474
+ if (line.startsWith("branch ")) {
475
+ current.branch = line.slice("branch ".length).trim();
476
+ }
477
+ return acc;
478
+ }, []);
479
+ }
480
+ const locateWorktree = Effect2.fnUntraced(function* (entries, directory) {
481
+ for (const item of entries) {
482
+ if (!item.path) continue;
483
+ const key = yield* canonical(item.path);
484
+ if (key === directory) return item;
485
+ }
486
+ return void 0;
487
+ });
488
+ function stopFsmonitor(target) {
489
+ return fs.exists(target).pipe(
490
+ Effect2.orDie,
491
+ Effect2.flatMap((exists) => exists ? git(["fsmonitor--daemon", "stop"], { cwd: target }) : Effect2.void)
492
+ );
493
+ }
494
+ function cleanDirectory(target) {
495
+ return Effect2.promise(
496
+ () => import("fs/promises").then((fsp) => fsp.rm(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })).catch((error) => {
497
+ const message = errorMessage(error);
498
+ throw new Worktree2.RemoveFailedError({ message: message || "Failed to remove git worktree directory" });
499
+ })
500
+ );
501
+ }
502
+ const remove2 = Effect2.fn("Worktree.remove")(function* (input) {
503
+ if (Instance.project.vcs !== "git") {
504
+ throw new Worktree2.NotGitError({ message: "Worktrees are only supported for git projects" });
505
+ }
506
+ const directory = yield* canonical(input.directory);
507
+ const list = yield* git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree });
508
+ if (list.code !== 0) {
509
+ throw new Worktree2.RemoveFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" });
510
+ }
511
+ const entries = parseWorktreeList(list.text);
512
+ const entry = yield* locateWorktree(entries, directory);
513
+ if (!entry?.path) {
514
+ const directoryExists = yield* fs.exists(directory).pipe(Effect2.orDie);
515
+ if (directoryExists) {
516
+ yield* stopFsmonitor(directory);
517
+ yield* cleanDirectory(directory);
518
+ }
519
+ return true;
520
+ }
521
+ yield* stopFsmonitor(entry.path);
522
+ const removed = yield* git(["worktree", "remove", "--force", entry.path], { cwd: Instance.worktree });
523
+ if (removed.code !== 0) {
524
+ const next = yield* git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree });
525
+ if (next.code !== 0) {
526
+ throw new Worktree2.RemoveFailedError({
527
+ message: removed.stderr || removed.text || next.stderr || next.text || "Failed to remove git worktree"
528
+ });
529
+ }
530
+ const stale = yield* locateWorktree(parseWorktreeList(next.text), directory);
531
+ if (stale?.path) {
532
+ throw new Worktree2.RemoveFailedError({ message: removed.stderr || removed.text || "Failed to remove git worktree" });
533
+ }
534
+ }
535
+ yield* cleanDirectory(entry.path);
536
+ const branch = entry.branch?.replace(/^refs\/heads\//, "");
537
+ if (branch) {
538
+ const deleted = yield* git(["branch", "-D", branch], { cwd: Instance.worktree });
539
+ if (deleted.code !== 0) {
540
+ throw new Worktree2.RemoveFailedError({
541
+ message: deleted.stderr || deleted.text || "Failed to delete worktree branch"
542
+ });
543
+ }
544
+ }
545
+ return true;
546
+ });
547
+ const gitExpect = Effect2.fnUntraced(function* (args, opts, error) {
548
+ const result = yield* git(args, opts);
549
+ if (result.code !== 0) throw error(result);
550
+ return result;
551
+ });
552
+ const runStartCommand = Effect2.fnUntraced(
553
+ function* (directory, cmd) {
554
+ const [shell, args] = process.platform === "win32" ? ["cmd", ["/c", cmd]] : ["bash", ["-lc", cmd]];
555
+ const handle = yield* spawner.spawn(
556
+ ChildProcess.make(shell, args, { cwd: directory, extendEnv: true, stdin: "ignore" })
557
+ );
558
+ const [, stderr] = yield* Effect2.all(
559
+ [Stream2.runDrain(handle.stdout), Stream2.mkString(Stream2.decodeText(handle.stderr))],
560
+ { concurrency: 2 }
561
+ ).pipe(Effect2.orDie);
562
+ const code = yield* handle.exitCode;
563
+ return { code, stderr };
564
+ },
565
+ Effect2.scoped,
566
+ Effect2.catch(() => Effect2.succeed({ code: 1, stderr: "" }))
567
+ );
568
+ const runStartScript = Effect2.fnUntraced(function* (directory, cmd, kind) {
569
+ const text = cmd.trim();
570
+ if (!text) return true;
571
+ const result = yield* runStartCommand(directory, text);
572
+ if (result.code === 0) return true;
573
+ log.error("worktree start command failed", { kind, directory, message: result.stderr });
574
+ return false;
575
+ });
576
+ const runStartScripts = Effect2.fnUntraced(function* (directory, input) {
577
+ const row = yield* Effect2.sync(
578
+ () => Database.use((db) => db.select().from(ProjectTable).where((0, db_exports.eq)(ProjectTable.id, input.projectID)).get())
579
+ );
580
+ const project2 = row ? Project.fromRow(row) : void 0;
581
+ const startup = project2?.commands?.start?.trim() ?? "";
582
+ const ok = yield* runStartScript(directory, startup, "project");
583
+ if (!ok) return false;
584
+ yield* runStartScript(directory, input.extra ?? "", "worktree");
585
+ return true;
586
+ });
587
+ const prune = Effect2.fnUntraced(function* (root, entries) {
588
+ const base = yield* canonical(root);
589
+ yield* Effect2.forEach(
590
+ entries,
591
+ (entry) => Effect2.gen(function* () {
592
+ const target = yield* canonical(pathSvc.resolve(root, entry));
593
+ if (target === base) return;
594
+ if (!target.startsWith(`${base}${pathSvc.sep}`)) return;
595
+ yield* fs.remove(target, { recursive: true }).pipe(Effect2.ignore);
596
+ }),
597
+ { concurrency: "unbounded" }
598
+ );
599
+ });
600
+ const sweep = Effect2.fnUntraced(function* (root) {
601
+ const first = yield* git(["clean", "-ffdx"], { cwd: root });
602
+ if (first.code === 0) return first;
603
+ const entries = failedRemoves(first.stderr, first.text);
604
+ if (!entries.length) return first;
605
+ yield* prune(root, entries);
606
+ return yield* git(["clean", "-ffdx"], { cwd: root });
607
+ });
608
+ const reset2 = Effect2.fn("Worktree.reset")(function* (input) {
609
+ if (Instance.project.vcs !== "git") {
610
+ throw new Worktree2.NotGitError({ message: "Worktrees are only supported for git projects" });
611
+ }
612
+ const directory = yield* canonical(input.directory);
613
+ const primary = yield* canonical(Instance.worktree);
614
+ if (directory === primary) {
615
+ throw new Worktree2.ResetFailedError({ message: "Cannot reset the primary workspace" });
616
+ }
617
+ const list = yield* git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree });
618
+ if (list.code !== 0) {
619
+ throw new Worktree2.ResetFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" });
620
+ }
621
+ const entry = yield* locateWorktree(parseWorktreeList(list.text), directory);
622
+ if (!entry?.path) {
623
+ throw new Worktree2.ResetFailedError({ message: "Worktree not found" });
624
+ }
625
+ const worktreePath = entry.path;
626
+ const base = yield* Effect2.promise(() => Git.defaultBranch(Instance.worktree));
627
+ if (!base) {
628
+ throw new Worktree2.ResetFailedError({ message: "Default branch not found" });
629
+ }
630
+ const sep = base.ref.indexOf("/");
631
+ if (base.ref !== base.name && sep > 0) {
632
+ const remote = base.ref.slice(0, sep);
633
+ const branch = base.ref.slice(sep + 1);
634
+ yield* gitExpect(
635
+ ["fetch", remote, branch],
636
+ { cwd: Instance.worktree },
637
+ (r) => new Worktree2.ResetFailedError({ message: r.stderr || r.text || `Failed to fetch ${base.ref}` })
638
+ );
639
+ }
640
+ yield* gitExpect(
641
+ ["reset", "--hard", base.ref],
642
+ { cwd: worktreePath },
643
+ (r) => new Worktree2.ResetFailedError({ message: r.stderr || r.text || "Failed to reset worktree to target" })
644
+ );
645
+ const cleanResult = yield* sweep(worktreePath);
646
+ if (cleanResult.code !== 0) {
647
+ throw new Worktree2.ResetFailedError({ message: cleanResult.stderr || cleanResult.text || "Failed to clean worktree" });
648
+ }
649
+ yield* gitExpect(
650
+ ["submodule", "update", "--init", "--recursive", "--force"],
651
+ { cwd: worktreePath },
652
+ (r) => new Worktree2.ResetFailedError({ message: r.stderr || r.text || "Failed to update submodules" })
653
+ );
654
+ yield* gitExpect(
655
+ ["submodule", "foreach", "--recursive", "git", "reset", "--hard"],
656
+ { cwd: worktreePath },
657
+ (r) => new Worktree2.ResetFailedError({ message: r.stderr || r.text || "Failed to reset submodules" })
658
+ );
659
+ yield* gitExpect(
660
+ ["submodule", "foreach", "--recursive", "git", "clean", "-fdx"],
661
+ { cwd: worktreePath },
662
+ (r) => new Worktree2.ResetFailedError({ message: r.stderr || r.text || "Failed to clean submodules" })
663
+ );
664
+ const status = yield* git(["-c", "core.fsmonitor=false", "status", "--porcelain=v1"], { cwd: worktreePath });
665
+ if (status.code !== 0) {
666
+ throw new Worktree2.ResetFailedError({ message: status.stderr || status.text || "Failed to read git status" });
667
+ }
668
+ if (status.text.trim()) {
669
+ throw new Worktree2.ResetFailedError({ message: `Worktree reset left local changes:
670
+ ${status.text.trim()}` });
671
+ }
672
+ yield* runStartScripts(worktreePath, { projectID: Instance.project.id }).pipe(
673
+ Effect2.catchCause((cause) => Effect2.sync(() => log.error("worktree start task failed", { cause }))),
674
+ Effect2.forkIn(scope)
675
+ );
676
+ return true;
677
+ });
678
+ return Service.of({ makeWorktreeInfo: makeWorktreeInfo2, createFromInfo: createFromInfo2, create: create2, remove: remove2, reset: reset2 });
679
+ })
680
+ );
681
+ const defaultLayer2 = Worktree2.layer.pipe(
682
+ Layer2.provide(defaultLayer),
683
+ Layer2.provide(Project.defaultLayer),
684
+ Layer2.provide(AppFileSystem.defaultLayer),
685
+ Layer2.provide(NodePath.layer)
686
+ );
687
+ const { runPromise } = makeRuntime(Service, defaultLayer2);
688
+ async function makeWorktreeInfo(name) {
689
+ return runPromise((svc) => svc.makeWorktreeInfo(name));
690
+ }
691
+ Worktree2.makeWorktreeInfo = makeWorktreeInfo;
692
+ async function createFromInfo(info, startCommand) {
693
+ return runPromise((svc) => svc.createFromInfo(info, startCommand));
694
+ }
695
+ Worktree2.createFromInfo = createFromInfo;
696
+ async function create(input) {
697
+ return runPromise((svc) => svc.create(input));
698
+ }
699
+ Worktree2.create = create;
700
+ async function remove(input) {
701
+ return runPromise((svc) => svc.remove(input));
702
+ }
703
+ Worktree2.remove = remove;
704
+ async function reset(input) {
705
+ return runPromise((svc) => svc.reset(input));
706
+ }
707
+ Worktree2.reset = reset;
708
+ })(Worktree || (Worktree = {}));
709
+
710
+ // packages/opencode/src/control-plane/types.ts
711
+ import z3 from "zod";
712
+ var WorkspaceInfo = z3.object({
713
+ id: WorkspaceID.zod,
714
+ type: z3.string(),
715
+ branch: z3.string().nullable(),
716
+ name: z3.string().nullable(),
717
+ directory: z3.string().nullable(),
718
+ extra: z3.unknown().nullable(),
719
+ projectID: ProjectID.zod
720
+ });
721
+
722
+ export {
723
+ Vcs,
724
+ InstanceBootstrap,
725
+ Worktree,
726
+ WorkspaceInfo
727
+ };
728
+ //# sourceMappingURL=chunk-EPJAWJV7.js.map