@wrongstack/webui 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-DjDDhHNu.js → index-B5qzSV8A.js} +25 -25
- package/dist/assets/index-BTevO8Vz.css +1 -0
- package/dist/index.css +117 -0
- package/dist/index.css.map +1 -1
- package/dist/index.html +2 -2
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +183 -9
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +183 -9
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/assets/index-aTQFIbqW.css +0 -1
package/dist/server/entry.js
CHANGED
|
@@ -136,22 +136,36 @@ function patchConfig(config, updates) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// src/server/autophase-ws-handler.ts
|
|
139
|
+
import { spawnSync } from "child_process";
|
|
139
140
|
import {
|
|
140
141
|
AutoPhasePlanner,
|
|
141
142
|
PhaseGraphBuilder,
|
|
142
143
|
PhaseOrchestrator,
|
|
143
|
-
PhaseStore
|
|
144
|
+
PhaseStore,
|
|
145
|
+
WorktreeManager
|
|
144
146
|
} from "@wrongstack/core";
|
|
147
|
+
function isGitRepo(cwd) {
|
|
148
|
+
try {
|
|
149
|
+
const r = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8" });
|
|
150
|
+
return r.status === 0 && r.stdout.trim() === "true";
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
145
155
|
var AutoPhaseWebSocketHandler = class {
|
|
146
|
-
constructor(agent, context, logger, storeDir) {
|
|
156
|
+
constructor(agent, context, logger, storeDir, events, projectRoot) {
|
|
147
157
|
this.agent = agent;
|
|
148
158
|
this.context = context;
|
|
149
159
|
this.logger = logger;
|
|
160
|
+
this.events = events;
|
|
161
|
+
this.projectRoot = projectRoot;
|
|
150
162
|
this.store = new PhaseStore({ baseDir: storeDir });
|
|
151
163
|
}
|
|
152
164
|
agent;
|
|
153
165
|
context;
|
|
154
166
|
logger;
|
|
167
|
+
events;
|
|
168
|
+
projectRoot;
|
|
155
169
|
orchestrator = null;
|
|
156
170
|
graph = null;
|
|
157
171
|
store;
|
|
@@ -159,6 +173,8 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
159
173
|
broadcastInterval = null;
|
|
160
174
|
/** Aborts in-flight task agents when the run is stopped. */
|
|
161
175
|
abort = null;
|
|
176
|
+
/** Optional per-phase git-worktree isolation (lazily created at start). */
|
|
177
|
+
worktrees = null;
|
|
162
178
|
addClient(ws) {
|
|
163
179
|
const client = { ws, id: crypto.randomUUID() };
|
|
164
180
|
this.clients.add(client);
|
|
@@ -246,12 +262,15 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
246
262
|
this.graph = graph;
|
|
247
263
|
this.abort = new AbortController();
|
|
248
264
|
await this.store.save(graph);
|
|
265
|
+
if (!this.worktrees && this.events && this.projectRoot && process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0" && isGitRepo(this.projectRoot)) {
|
|
266
|
+
this.worktrees = new WorktreeManager({ projectRoot: this.projectRoot, events: this.events });
|
|
267
|
+
}
|
|
249
268
|
this.orchestrator = new PhaseOrchestrator({
|
|
250
269
|
graph,
|
|
251
270
|
ctx: {
|
|
252
|
-
executeTask: async (task, phaseId) => {
|
|
271
|
+
executeTask: async (task, phaseId, env) => {
|
|
253
272
|
this.logger.info(`[AutoPhase] [${phaseId}] Executing: ${task.title}`);
|
|
254
|
-
const result = await this.executeTaskWithAgent(task, phaseId);
|
|
273
|
+
const result = await this.executeTaskWithAgent(task, phaseId, env);
|
|
255
274
|
this.logger.info(`[AutoPhase] [${phaseId}] Completed: ${task.title}`);
|
|
256
275
|
return result;
|
|
257
276
|
},
|
|
@@ -266,10 +285,13 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
266
285
|
this.broadcastState();
|
|
267
286
|
}
|
|
268
287
|
},
|
|
288
|
+
worktrees: this.worktrees ?? void 0,
|
|
269
289
|
autonomous,
|
|
290
|
+
// Must stay 1: phase tasks run on the single shared context whose cwd we
|
|
291
|
+
// swap per phase, so parallel phases would race on context.cwd.
|
|
270
292
|
maxConcurrentPhases: 1,
|
|
271
293
|
// Sequential within a phase: each todo is a full-tool agent editing the
|
|
272
|
-
//
|
|
294
|
+
// phase worktree, so running two at once risks concurrent writes.
|
|
273
295
|
maxConcurrentTasks: 1
|
|
274
296
|
});
|
|
275
297
|
this.startBroadcast();
|
|
@@ -321,7 +343,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
321
343
|
}
|
|
322
344
|
return this.defaultPhases();
|
|
323
345
|
}
|
|
324
|
-
async executeTaskWithAgent(task, phaseId) {
|
|
346
|
+
async executeTaskWithAgent(task, phaseId, env) {
|
|
325
347
|
const prompt = `Execute task: ${task.title}
|
|
326
348
|
|
|
327
349
|
Description: ${task.description}
|
|
@@ -329,8 +351,13 @@ Phase: ${phaseId}
|
|
|
329
351
|
Priority: ${task.priority}
|
|
330
352
|
Type: ${task.type}`;
|
|
331
353
|
const signal = this.abort?.signal ?? new AbortController().signal;
|
|
332
|
-
const
|
|
333
|
-
|
|
354
|
+
const prevCwd = this.context.cwd;
|
|
355
|
+
if (env?.cwd) this.context.cwd = env.cwd;
|
|
356
|
+
try {
|
|
357
|
+
return await this.agent.run(prompt, { signal });
|
|
358
|
+
} finally {
|
|
359
|
+
this.context.cwd = prevCwd;
|
|
360
|
+
}
|
|
334
361
|
}
|
|
335
362
|
async handleTaskStatusChange(taskId, status) {
|
|
336
363
|
if (!this.graph) return;
|
|
@@ -437,6 +464,144 @@ Type: ${task.type}`;
|
|
|
437
464
|
}
|
|
438
465
|
};
|
|
439
466
|
|
|
467
|
+
// src/server/worktree-ws-handler.ts
|
|
468
|
+
var MAX_ACTIVITY = 6;
|
|
469
|
+
var WorktreeWebSocketHandler = class {
|
|
470
|
+
constructor(events, logger) {
|
|
471
|
+
this.events = events;
|
|
472
|
+
this.logger = logger;
|
|
473
|
+
this.subscribe();
|
|
474
|
+
}
|
|
475
|
+
events;
|
|
476
|
+
logger;
|
|
477
|
+
clients = /* @__PURE__ */ new Set();
|
|
478
|
+
handles = /* @__PURE__ */ new Map();
|
|
479
|
+
baseBranch = "";
|
|
480
|
+
broadcastInterval = null;
|
|
481
|
+
offs = [];
|
|
482
|
+
addClient(ws) {
|
|
483
|
+
this.clients.add(ws);
|
|
484
|
+
ws.on("close", () => this.clients.delete(ws));
|
|
485
|
+
ws.on("error", () => this.clients.delete(ws));
|
|
486
|
+
this.send(ws, this.stateMessage());
|
|
487
|
+
}
|
|
488
|
+
dispose() {
|
|
489
|
+
for (const off of this.offs) off();
|
|
490
|
+
this.offs.length = 0;
|
|
491
|
+
this.stopBroadcast();
|
|
492
|
+
}
|
|
493
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
494
|
+
subscribe() {
|
|
495
|
+
const on = this.events.on.bind(this.events);
|
|
496
|
+
this.offs.push(
|
|
497
|
+
on("worktree.allocated", (p) => {
|
|
498
|
+
const e = p;
|
|
499
|
+
this.baseBranch = e.baseBranch || this.baseBranch;
|
|
500
|
+
this.upsert(e.handleId, {
|
|
501
|
+
handleId: e.handleId,
|
|
502
|
+
ownerId: e.ownerId,
|
|
503
|
+
ownerLabel: e.ownerLabel,
|
|
504
|
+
branch: e.branch,
|
|
505
|
+
baseBranch: e.baseBranch,
|
|
506
|
+
status: "active",
|
|
507
|
+
insertions: 0,
|
|
508
|
+
deletions: 0,
|
|
509
|
+
files: 0,
|
|
510
|
+
allocatedAt: Date.now(),
|
|
511
|
+
lastEventAt: Date.now(),
|
|
512
|
+
recentActivity: []
|
|
513
|
+
});
|
|
514
|
+
this.activity(e.handleId, "allocated", `branch ${e.branch}`);
|
|
515
|
+
this.ensureBroadcast();
|
|
516
|
+
}),
|
|
517
|
+
on("worktree.committed", (p) => {
|
|
518
|
+
const e = p;
|
|
519
|
+
this.patch(e.handleId, { status: "committing", insertions: e.insertions, deletions: e.deletions, files: e.files });
|
|
520
|
+
if (e.committed) this.activity(e.handleId, "committed", `+${e.insertions}/-${e.deletions} (${e.files}f)`);
|
|
521
|
+
this.broadcastState();
|
|
522
|
+
}),
|
|
523
|
+
on("worktree.merged", (p) => {
|
|
524
|
+
const e = p;
|
|
525
|
+
this.patch(e.handleId, { status: "merged" });
|
|
526
|
+
this.activity(e.handleId, "merged", `\u2192 ${e.baseBranch}`);
|
|
527
|
+
this.broadcastState();
|
|
528
|
+
}),
|
|
529
|
+
on("worktree.conflict", (p) => {
|
|
530
|
+
const e = p;
|
|
531
|
+
this.patch(e.handleId, { status: "needs-review", conflictFiles: e.conflictFiles });
|
|
532
|
+
this.activity(e.handleId, "conflict", e.conflictFiles.join(", "));
|
|
533
|
+
this.broadcastState();
|
|
534
|
+
}),
|
|
535
|
+
on("worktree.failed", (p) => {
|
|
536
|
+
const e = p;
|
|
537
|
+
this.patch(e.handleId, { status: "failed" });
|
|
538
|
+
this.activity(e.handleId, "failed", e.error);
|
|
539
|
+
this.broadcastState();
|
|
540
|
+
}),
|
|
541
|
+
on("worktree.released", (p) => {
|
|
542
|
+
const e = p;
|
|
543
|
+
if (!e.kept) this.handles.delete(e.handleId);
|
|
544
|
+
this.activity(e.handleId, "released", e.kept ? "kept for review" : "removed");
|
|
545
|
+
if (this.handles.size === 0) this.stopBroadcast();
|
|
546
|
+
else this.broadcastState();
|
|
547
|
+
})
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
upsert(id, view) {
|
|
551
|
+
this.handles.set(id, view);
|
|
552
|
+
}
|
|
553
|
+
patch(id, patch) {
|
|
554
|
+
const cur = this.handles.get(id);
|
|
555
|
+
if (!cur) return;
|
|
556
|
+
this.handles.set(id, { ...cur, ...patch, lastEventAt: Date.now() });
|
|
557
|
+
}
|
|
558
|
+
activity(id, kind, text) {
|
|
559
|
+
const cur = this.handles.get(id);
|
|
560
|
+
if (cur) {
|
|
561
|
+
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-MAX_ACTIVITY);
|
|
562
|
+
this.handles.set(id, { ...cur, recentActivity });
|
|
563
|
+
}
|
|
564
|
+
this.broadcast({ type: "worktree.event", payload: { kind, handleId: id, text, at: Date.now() } });
|
|
565
|
+
}
|
|
566
|
+
stateMessage() {
|
|
567
|
+
return {
|
|
568
|
+
type: "worktree.state",
|
|
569
|
+
payload: { worktrees: [...this.handles.values()], baseBranch: this.baseBranch }
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
broadcastState() {
|
|
573
|
+
this.broadcast(this.stateMessage());
|
|
574
|
+
}
|
|
575
|
+
ensureBroadcast() {
|
|
576
|
+
this.broadcast(this.stateMessage());
|
|
577
|
+
if (this.broadcastInterval) return;
|
|
578
|
+
this.broadcastInterval = setInterval(() => this.broadcast(this.stateMessage()), 2e3);
|
|
579
|
+
}
|
|
580
|
+
stopBroadcast() {
|
|
581
|
+
this.broadcast(this.stateMessage());
|
|
582
|
+
if (this.broadcastInterval) {
|
|
583
|
+
clearInterval(this.broadcastInterval);
|
|
584
|
+
this.broadcastInterval = null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
broadcast(msg) {
|
|
588
|
+
const data = JSON.stringify(msg);
|
|
589
|
+
for (const ws of this.clients) {
|
|
590
|
+
try {
|
|
591
|
+
if (ws.readyState === 1) ws.send(data);
|
|
592
|
+
} catch (err) {
|
|
593
|
+
this.logger.debug?.(`worktree broadcast failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
send(ws, msg) {
|
|
598
|
+
try {
|
|
599
|
+
if (ws.readyState === 1) ws.send(JSON.stringify(msg));
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
440
605
|
// src/server/index.ts
|
|
441
606
|
var HTML_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'";
|
|
442
607
|
async function startWebUI(opts = {}) {
|
|
@@ -640,7 +805,15 @@ async function startWebUI(opts = {}) {
|
|
|
640
805
|
toolExecutor
|
|
641
806
|
});
|
|
642
807
|
console.log("[WebUI] Agent initialized");
|
|
643
|
-
const autoPhaseHandler = new AutoPhaseWebSocketHandler(
|
|
808
|
+
const autoPhaseHandler = new AutoPhaseWebSocketHandler(
|
|
809
|
+
agent,
|
|
810
|
+
context,
|
|
811
|
+
logger,
|
|
812
|
+
wpaths.projectAutophase,
|
|
813
|
+
events,
|
|
814
|
+
projectRoot
|
|
815
|
+
);
|
|
816
|
+
const worktreeHandler = new WorktreeWebSocketHandler(events, logger);
|
|
644
817
|
async function sessionStartPayload() {
|
|
645
818
|
let maxContext = 0;
|
|
646
819
|
let inputCost = 0;
|
|
@@ -862,6 +1035,7 @@ async function startWebUI(opts = {}) {
|
|
|
862
1035
|
send(ws, { type: "session.start", payload });
|
|
863
1036
|
});
|
|
864
1037
|
autoPhaseHandler.addClient(ws);
|
|
1038
|
+
worktreeHandler.addClient(ws);
|
|
865
1039
|
ws.on("message", async (data) => {
|
|
866
1040
|
if (!checkRateLimit(ws)) {
|
|
867
1041
|
send(ws, {
|