bit-office 1.2.0 → 1.2.2

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/index.js CHANGED
@@ -4201,6 +4201,11 @@ var SuggestCommand = external_exports.object({
4201
4201
  text: external_exports.string().max(500),
4202
4202
  author: external_exports.string().max(30).optional()
4203
4203
  });
4204
+ var RateProjectCommand = external_exports.object({
4205
+ type: external_exports.literal("RATE_PROJECT"),
4206
+ projectId: external_exports.string().optional(),
4207
+ ratings: external_exports.record(external_exports.string(), external_exports.number().min(1).max(5))
4208
+ });
4204
4209
  var ListProjectsCommand = external_exports.object({
4205
4210
  type: external_exports.literal("LIST_PROJECTS")
4206
4211
  });
@@ -4226,6 +4231,7 @@ var CommandSchema = external_exports.discriminatedUnion("type", [
4226
4231
  SaveAgentDefCommand,
4227
4232
  DeleteAgentDefCommand,
4228
4233
  SuggestCommand,
4234
+ RateProjectCommand,
4229
4235
  ListProjectsCommand,
4230
4236
  LoadProjectCommand
4231
4237
  ]);
@@ -4391,7 +4397,9 @@ var ProjectListEvent = external_exports.object({
4391
4397
  endedAt: external_exports.number(),
4392
4398
  agentNames: external_exports.array(external_exports.string()),
4393
4399
  eventCount: external_exports.number(),
4394
- preview: ProjectPreviewSchema
4400
+ preview: ProjectPreviewSchema,
4401
+ tokenUsage: external_exports.object({ inputTokens: external_exports.number(), outputTokens: external_exports.number() }).optional(),
4402
+ ratings: external_exports.record(external_exports.string(), external_exports.number()).optional()
4395
4403
  }))
4396
4404
  });
4397
4405
  var ProjectDataEvent = external_exports.object({
@@ -4402,6 +4410,10 @@ var ProjectDataEvent = external_exports.object({
4402
4410
  endedAt: external_exports.number(),
4403
4411
  events: external_exports.array(external_exports.any())
4404
4412
  });
4413
+ var PreviewReadyEvent = external_exports.object({
4414
+ type: external_exports.literal("PREVIEW_READY"),
4415
+ url: external_exports.string()
4416
+ });
4405
4417
  var GatewayEventSchema = external_exports.discriminatedUnion("type", [
4406
4418
  AgentsSyncEvent,
4407
4419
  AgentStatusEvent,
@@ -4421,7 +4433,8 @@ var GatewayEventSchema = external_exports.discriminatedUnion("type", [
4421
4433
  AgentDefsEvent,
4422
4434
  SuggestionEvent,
4423
4435
  ProjectListEvent,
4424
- ProjectDataEvent
4436
+ ProjectDataEvent,
4437
+ PreviewReadyEvent
4425
4438
  ]);
4426
4439
 
4427
4440
  // ../../packages/shared/src/presets.ts
@@ -5335,14 +5348,14 @@ var CONFIG = {
5335
5348
  };
5336
5349
 
5337
5350
  // ../../packages/orchestrator/src/agent-session.ts
5338
- import { spawn as spawn2, execSync as execSync3 } from "child_process";
5351
+ import { spawn, execSync as execSync2 } from "child_process";
5339
5352
  import path5 from "path";
5340
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
5341
- import { homedir as homedir4 } from "os";
5353
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
5354
+ import { homedir as homedir5 } from "os";
5342
5355
 
5343
5356
  // ../../packages/orchestrator/src/preview-resolver.ts
5344
- import { existsSync as existsSync6 } from "fs";
5345
- import path4 from "path";
5357
+ import { existsSync as existsSync5 } from "fs";
5358
+ import path3 from "path";
5346
5359
 
5347
5360
  // ../../packages/orchestrator/src/resolve-path.ts
5348
5361
  import path2 from "path";
@@ -5362,157 +5375,19 @@ function resolveAgentPath(filePath, projectDir, workspace) {
5362
5375
  return void 0;
5363
5376
  }
5364
5377
 
5365
- // ../../packages/orchestrator/src/preview-server.ts
5366
- import { spawn, execSync as execSync2 } from "child_process";
5367
- import { existsSync as existsSync5 } from "fs";
5368
- import path3 from "path";
5369
- var STATIC_PORT = 9100;
5370
- var PreviewServer = class {
5371
- process = null;
5372
- currentDir = null;
5373
- isDetached = false;
5374
- /**
5375
- * Mode 1: Serve a static file directory on a fixed port.
5376
- * Returns the preview URL for the given file.
5377
- */
5378
- serve(filePath) {
5379
- if (!existsSync5(filePath)) {
5380
- console.log(`[PreviewServer] File not found: ${filePath}`);
5381
- return void 0;
5382
- }
5383
- const dir = path3.dirname(filePath);
5384
- const fileName = path3.basename(filePath);
5385
- this.stop();
5386
- try {
5387
- this.process = spawn("npx", ["serve", dir, "-l", String(STATIC_PORT), "--no-clipboard"], {
5388
- stdio: "ignore",
5389
- detached: true
5390
- });
5391
- this.process.unref();
5392
- this.currentDir = dir;
5393
- this.isDetached = true;
5394
- const url = `http://localhost:${STATIC_PORT}/${fileName}`;
5395
- console.log(`[PreviewServer] Serving ${dir} on port ${STATIC_PORT}`);
5396
- return url;
5397
- } catch (e) {
5398
- console.log(`[PreviewServer] Failed to start static serve: ${e}`);
5399
- return void 0;
5400
- }
5401
- }
5402
- /**
5403
- * Mode 2: Run a command (e.g. "python app.py") and use the specified port.
5404
- * The command is expected to start a server on the given port.
5405
- * Returns the preview URL.
5406
- */
5407
- runCommand(cmd, cwd, port) {
5408
- this.stop();
5409
- try {
5410
- this.process = spawn(cmd, {
5411
- shell: true,
5412
- cwd,
5413
- stdio: "ignore",
5414
- detached: true
5415
- });
5416
- this.process.unref();
5417
- this.currentDir = cwd;
5418
- this.isDetached = true;
5419
- const url = `http://localhost:${port}`;
5420
- console.log(`[PreviewServer] Running "${cmd}" in ${cwd}, preview at port ${port}`);
5421
- return url;
5422
- } catch (e) {
5423
- console.log(`[PreviewServer] Failed to run command: ${e}`);
5424
- return void 0;
5425
- }
5426
- }
5427
- /**
5428
- * Mode 3: Launch a desktop/CLI process (no web preview URL).
5429
- * Used for Pygame, Tkinter, Electron, terminal apps, etc.
5430
- * NOT detached — GUI apps need the login session to access WindowServer (macOS).
5431
- */
5432
- launchProcess(cmd, cwd) {
5433
- this.stop();
5434
- try {
5435
- this.process = spawn(cmd, {
5436
- shell: true,
5437
- cwd,
5438
- stdio: ["ignore", "ignore", "pipe"]
5439
- });
5440
- this.currentDir = cwd;
5441
- this.isDetached = false;
5442
- console.log(`[PreviewServer] Launched "${cmd}" in ${cwd} (pid=${this.process.pid})`);
5443
- this.process.stderr?.on("data", (data) => {
5444
- const msg = data.toString().trim();
5445
- if (msg) console.log(`[PreviewServer] stderr: ${msg.slice(0, 200)}`);
5446
- });
5447
- this.process.on("exit", (code) => {
5448
- console.log(`[PreviewServer] Process exited with code ${code}`);
5449
- });
5450
- } catch (e) {
5451
- console.log(`[PreviewServer] Failed to launch process: ${e}`);
5452
- }
5453
- }
5454
- /** Kill the current process and any orphan process on the static port */
5455
- stop() {
5456
- if (this.process) {
5457
- try {
5458
- if (this.isDetached && this.process.pid) {
5459
- process.kill(-this.process.pid, "SIGTERM");
5460
- } else {
5461
- this.process.kill("SIGTERM");
5462
- }
5463
- } catch {
5464
- try {
5465
- this.process.kill("SIGTERM");
5466
- } catch {
5467
- }
5468
- }
5469
- this.process = null;
5470
- this.currentDir = null;
5471
- this.isDetached = false;
5472
- console.log(`[PreviewServer] Stopped`);
5473
- }
5474
- this.killPortHolder(STATIC_PORT);
5475
- }
5476
- /** Kill whatever process is listening on the given port (best-effort). */
5477
- killPortHolder(port) {
5478
- try {
5479
- const out = execSync2(`lsof -ti :${port}`, { encoding: "utf-8", timeout: 3e3 }).trim();
5480
- if (out) {
5481
- for (const pid of out.split("\n")) {
5482
- const n = parseInt(pid, 10);
5483
- if (n > 0) {
5484
- try {
5485
- process.kill(n, "SIGKILL");
5486
- } catch {
5487
- }
5488
- }
5489
- }
5490
- console.log(`[PreviewServer] Killed orphan process(es) on port ${port}: ${out.replace(/\n/g, ", ")}`);
5491
- }
5492
- } catch {
5493
- }
5494
- }
5495
- };
5496
- var previewServer = new PreviewServer();
5497
-
5498
5378
  // ../../packages/orchestrator/src/preview-resolver.ts
5499
5379
  var EMPTY = { previewUrl: void 0, previewPath: void 0 };
5500
5380
  function resolvePreview(input) {
5501
5381
  const { cwd, workspace } = input;
5502
5382
  if (input.previewCmd && input.previewPort) {
5503
- const url = previewServer.runCommand(input.previewCmd, cwd, input.previewPort);
5504
- if (url) return { previewUrl: url, previewPath: void 0 };
5383
+ return EMPTY;
5505
5384
  }
5506
5385
  if (input.previewCmd && !input.previewPort) {
5507
- console.log(`[PreviewResolver] Desktop app ready (user can Launch): ${input.previewCmd}`);
5508
5386
  return EMPTY;
5509
5387
  }
5510
5388
  if (input.entryFile && /\.html?$/i.test(input.entryFile)) {
5511
5389
  const absPath = resolveAgentPath(input.entryFile, cwd, workspace);
5512
- if (absPath) {
5513
- const url = previewServer.serve(absPath);
5514
- if (url) return { previewUrl: url, previewPath: absPath };
5515
- }
5390
+ if (absPath) return { previewUrl: void 0, previewPath: absPath };
5516
5391
  }
5517
5392
  if (input.stdout) {
5518
5393
  const match = input.stdout.match(/PREVIEW:\s*(https?:\/\/[^\s*)\]>]+)/i);
@@ -5524,28 +5399,19 @@ function resolvePreview(input) {
5524
5399
  const fileMatch = input.stdout.match(/(?:open\s+)?((?:\/[\w./_-]+|[\w./_-]+)\.html?)\b/i);
5525
5400
  if (fileMatch) {
5526
5401
  const absPath = resolveAgentPath(fileMatch[1], cwd, workspace);
5527
- if (absPath) {
5528
- const url = previewServer.serve(absPath);
5529
- if (url) return { previewUrl: url, previewPath: absPath };
5530
- }
5402
+ if (absPath) return { previewUrl: void 0, previewPath: absPath };
5531
5403
  }
5532
5404
  }
5533
5405
  if (input.changedFiles) {
5534
5406
  for (const f of input.changedFiles) {
5535
5407
  if (!/\.html?$/i.test(f)) continue;
5536
5408
  const absPath = resolveAgentPath(f, cwd, workspace);
5537
- if (absPath) {
5538
- const url = previewServer.serve(absPath);
5539
- if (url) return { previewUrl: url, previewPath: absPath };
5540
- }
5409
+ if (absPath) return { previewUrl: void 0, previewPath: absPath };
5541
5410
  }
5542
5411
  }
5543
5412
  for (const candidate of CONFIG.preview.buildOutputCandidates) {
5544
- const absPath = path4.join(cwd, candidate);
5545
- if (existsSync6(absPath)) {
5546
- const url = previewServer.serve(absPath);
5547
- if (url) return { previewUrl: url, previewPath: absPath };
5548
- }
5413
+ const absPath = path3.join(cwd, candidate);
5414
+ if (existsSync5(absPath)) return { previewUrl: void 0, previewPath: absPath };
5549
5415
  }
5550
5416
  return EMPTY;
5551
5417
  }
@@ -5559,19 +5425,23 @@ function parseAgentOutput(raw, fallbackText) {
5559
5425
  const entryFileMatch = text.match(/ENTRY_FILE:\s*(.+)/i);
5560
5426
  const projectDirMatch = text.match(/PROJECT_DIR:\s*(.+)/i);
5561
5427
  const previewCmdMatch = text.match(/PREVIEW_CMD:\s*(.+)/i);
5562
- const previewPortMatch = text.match(/PREVIEW_PORT:\s*(\d+)/i);
5428
+ const previewPortMatch = text.match(/PREVIEW_PORT:\s*[*`_]*(\d+)/i);
5429
+ const stripMarkdown = (v) => v.replace(/\*\*/g, "").replace(/`/g, "").replace(/^_+|_+$/g, "").trim();
5563
5430
  const changedFiles = [];
5564
5431
  if (filesMatch) {
5565
5432
  const fileList = filesMatch[1].trim();
5566
5433
  for (const f of fileList.split(/[,\n]+/)) {
5567
- const cleaned = f.trim().replace(/^[-*]\s*/, "");
5434
+ const cleaned = stripMarkdown(f.trim().replace(/^[-*]\s*/, ""));
5568
5435
  if (cleaned) changedFiles.push(cleaned);
5569
5436
  }
5570
5437
  }
5571
5438
  const isPlaceholder = (v) => !v || /^[\[(].*not provided.*[\])]$/i.test(v) || /^[\[(].*n\/?a.*[\])]$/i.test(v) || /^none$/i.test(v);
5572
- const entryFile = isPlaceholder(entryFileMatch?.[1]?.trim()) ? void 0 : entryFileMatch[1].trim();
5573
- const projectDir = isPlaceholder(projectDirMatch?.[1]?.trim()) ? void 0 : projectDirMatch[1].trim();
5574
- const previewCmd = isPlaceholder(previewCmdMatch?.[1]?.trim()) ? void 0 : previewCmdMatch[1].trim();
5439
+ const rawEntry = entryFileMatch?.[1]?.trim();
5440
+ const rawDir = projectDirMatch?.[1]?.trim();
5441
+ const rawCmd = previewCmdMatch?.[1]?.trim();
5442
+ const entryFile = isPlaceholder(rawEntry) ? void 0 : stripMarkdown(rawEntry);
5443
+ const projectDir = isPlaceholder(rawDir) ? void 0 : stripMarkdown(rawDir);
5444
+ const previewCmd = isPlaceholder(rawCmd) ? void 0 : stripMarkdown(rawCmd);
5575
5445
  const previewPort = previewPortMatch ? parseInt(previewPortMatch[1], 10) : void 0;
5576
5446
  if (summaryMatch) {
5577
5447
  return { summary: summaryMatch[1].trim(), fullOutput, changedFiles, entryFile, projectDir, previewCmd, previewPort };
@@ -5611,10 +5481,139 @@ function extractFallbackSummary(raw, _hasFiles, _entryFile, _projectDir) {
5611
5481
 
5612
5482
  // ../../packages/orchestrator/src/agent-session.ts
5613
5483
  import { nanoid as nanoid3 } from "nanoid";
5614
- var SESSION_FILE = path5.join(homedir4(), ".bit-office", "agent-sessions.json");
5484
+
5485
+ // ../../packages/orchestrator/src/memory.ts
5486
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
5487
+ import path4 from "path";
5488
+ import { homedir as homedir4 } from "os";
5489
+ var MEMORY_DIR = path4.join(homedir4(), ".bit-office", "memory");
5490
+ function ensureDir() {
5491
+ if (!existsSync6(MEMORY_DIR)) {
5492
+ mkdirSync4(MEMORY_DIR, { recursive: true });
5493
+ }
5494
+ }
5495
+ function loadStore() {
5496
+ const filePath = path4.join(MEMORY_DIR, "memory.json");
5497
+ try {
5498
+ if (existsSync6(filePath)) {
5499
+ return JSON.parse(readFileSync4(filePath, "utf-8"));
5500
+ }
5501
+ } catch {
5502
+ }
5503
+ return { reviewPatterns: [], techPreferences: [], projectHistory: [] };
5504
+ }
5505
+ function saveStore(store) {
5506
+ ensureDir();
5507
+ const filePath = path4.join(MEMORY_DIR, "memory.json");
5508
+ writeFileSync4(filePath, JSON.stringify(store, null, 2), "utf-8");
5509
+ }
5510
+ function recordReviewFeedback(reviewOutput) {
5511
+ const verdictMatch = reviewOutput.match(/VERDICT[:\s]*(\w+)/i);
5512
+ if (!verdictMatch || verdictMatch[1].toUpperCase() !== "FAIL") return;
5513
+ const issueLines = [];
5514
+ const issueRe = /^\s*\d+[.)]\s*(.+)/gm;
5515
+ let match;
5516
+ while ((match = issueRe.exec(reviewOutput)) !== null) {
5517
+ const issue = match[1].trim();
5518
+ if (issue.length > 10 && issue.length < 200) {
5519
+ issueLines.push(issue);
5520
+ }
5521
+ }
5522
+ if (issueLines.length === 0) return;
5523
+ const store = loadStore();
5524
+ const now = Date.now();
5525
+ for (const issue of issueLines) {
5526
+ const normalized = normalizeIssue(issue);
5527
+ const existing = store.reviewPatterns.find((p) => normalizeIssue(p.pattern) === normalized);
5528
+ if (existing) {
5529
+ existing.count++;
5530
+ existing.lastSeen = now;
5531
+ } else {
5532
+ store.reviewPatterns.push({ pattern: issue, count: 1, lastSeen: now });
5533
+ }
5534
+ }
5535
+ store.reviewPatterns.sort((a, b) => b.count - a.count);
5536
+ store.reviewPatterns = store.reviewPatterns.slice(0, 20);
5537
+ saveStore(store);
5538
+ console.log(`[Memory] Recorded ${issueLines.length} review pattern(s), total=${store.reviewPatterns.length}`);
5539
+ }
5540
+ function recordProjectCompletion(summary, tech, reviewPassed) {
5541
+ const store = loadStore();
5542
+ store.projectHistory.push({
5543
+ summary: summary.slice(0, 300),
5544
+ tech: tech.slice(0, 100),
5545
+ completedAt: Date.now(),
5546
+ reviewPassed
5547
+ });
5548
+ if (store.projectHistory.length > 50) {
5549
+ store.projectHistory = store.projectHistory.slice(-50);
5550
+ }
5551
+ saveStore(store);
5552
+ console.log(`[Memory] Recorded project completion: ${summary.slice(0, 80)}`);
5553
+ }
5554
+ function recordTechPreference(tech) {
5555
+ const store = loadStore();
5556
+ const normalized = tech.trim().toLowerCase();
5557
+ if (!store.techPreferences.some((t) => t.toLowerCase() === normalized)) {
5558
+ store.techPreferences.push(tech.trim());
5559
+ if (store.techPreferences.length > 10) {
5560
+ store.techPreferences = store.techPreferences.slice(-10);
5561
+ }
5562
+ saveStore(store);
5563
+ console.log(`[Memory] Recorded tech preference: ${tech}`);
5564
+ }
5565
+ }
5566
+ function recordProjectRatings(ratings) {
5567
+ const store = loadStore();
5568
+ if (store.projectHistory.length === 0) return;
5569
+ store.projectHistory[store.projectHistory.length - 1].ratings = ratings;
5570
+ saveStore(store);
5571
+ const avg = Object.values(ratings);
5572
+ const mean = avg.length > 0 ? (avg.reduce((a, b) => a + b, 0) / avg.length).toFixed(1) : "?";
5573
+ console.log(`[Memory] Updated latest project ratings (avg ${mean}/5)`);
5574
+ }
5575
+ function getMemoryContext() {
5576
+ const store = loadStore();
5577
+ const sections = [];
5578
+ const recurring = store.reviewPatterns.filter((p) => p.count >= 2);
5579
+ if (recurring.length > 0) {
5580
+ const lines = recurring.slice(0, 5).map((p) => `- ${p.pattern} (flagged ${p.count}x)`);
5581
+ sections.push(`COMMON REVIEW ISSUES (avoid these):
5582
+ ${lines.join("\n")}`);
5583
+ }
5584
+ if (store.techPreferences.length > 0) {
5585
+ const recent = store.techPreferences.slice(-3);
5586
+ sections.push(`USER'S PREFERRED TECH: ${recent.join(", ")}`);
5587
+ }
5588
+ const rated = store.projectHistory.filter((p) => p.ratings && Object.keys(p.ratings).length > 0).slice(-3);
5589
+ if (rated.length > 0) {
5590
+ const lines = rated.map((p) => {
5591
+ const r = p.ratings;
5592
+ const scores = Object.entries(r).map(([k, v]) => `${k}:${v}/5`).join(", ");
5593
+ const avg = Object.values(r).reduce((a, b) => a + b, 0) / Object.values(r).length;
5594
+ const weak = Object.entries(r).filter(([, v]) => v <= 2).map(([k]) => k);
5595
+ let line = `- "${p.summary.slice(0, 60)}" [${scores}] avg=${avg.toFixed(1)}`;
5596
+ if (weak.length > 0) line += ` \u2192 improve: ${weak.join(", ")}`;
5597
+ return line;
5598
+ });
5599
+ sections.push(`PAST PROJECT RATINGS (learn from user feedback):
5600
+ ${lines.join("\n")}`);
5601
+ }
5602
+ if (sections.length === 0) return "";
5603
+ return `
5604
+ ===== LEARNED FROM PREVIOUS PROJECTS =====
5605
+ ${sections.join("\n\n")}
5606
+ `;
5607
+ }
5608
+ function normalizeIssue(issue) {
5609
+ return issue.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
5610
+ }
5611
+
5612
+ // ../../packages/orchestrator/src/agent-session.ts
5613
+ var SESSION_FILE = path5.join(homedir5(), ".bit-office", "agent-sessions.json");
5615
5614
  function loadSessionMap() {
5616
5615
  try {
5617
- if (existsSync7(SESSION_FILE)) return JSON.parse(readFileSync4(SESSION_FILE, "utf-8"));
5616
+ if (existsSync7(SESSION_FILE)) return JSON.parse(readFileSync5(SESSION_FILE, "utf-8"));
5618
5617
  } catch {
5619
5618
  }
5620
5619
  return {};
@@ -5624,14 +5623,14 @@ function clearSessionId(agentId) {
5624
5623
  }
5625
5624
  function saveSessionId(agentId, sessionId) {
5626
5625
  const dir = path5.dirname(SESSION_FILE);
5627
- if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
5626
+ if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
5628
5627
  const map = loadSessionMap();
5629
5628
  if (sessionId) {
5630
5629
  map[agentId] = sessionId;
5631
5630
  } else {
5632
5631
  delete map[agentId];
5633
5632
  }
5634
- writeFileSync4(SESSION_FILE, JSON.stringify(map), "utf-8");
5633
+ writeFileSync5(SESSION_FILE, JSON.stringify(map), "utf-8");
5635
5634
  }
5636
5635
  var AgentSession = class {
5637
5636
  agentId;
@@ -5656,6 +5655,8 @@ var AgentSession = class {
5656
5655
  stderrBuffer = "";
5657
5656
  taskInputTokens = 0;
5658
5657
  taskOutputTokens = 0;
5658
+ /** Dedup same-turn repeated usage in assistant messages */
5659
+ lastUsageSignature = "";
5659
5660
  hasHistory;
5660
5661
  sessionId;
5661
5662
  taskQueue = [];
@@ -5761,6 +5762,7 @@ var AgentSession = class {
5761
5762
  this.stderrBuffer = "";
5762
5763
  this.taskInputTokens = 0;
5763
5764
  this.taskOutputTokens = 0;
5765
+ this.lastUsageSignature = "";
5764
5766
  this.onEvent({
5765
5767
  type: "task:started",
5766
5768
  agentId: this.agentId,
@@ -5782,7 +5784,7 @@ var AgentSession = class {
5782
5784
  teamRoster: teamContext ?? "",
5783
5785
  originalTask,
5784
5786
  prompt,
5785
- memory: this._memoryContext,
5787
+ memory: this._memoryContext || getMemoryContext(),
5786
5788
  soloHint: this.teamId ? "" : `- You are a SOLO developer. Do NOT delegate, assign tasks, or mention other team members. Do ALL the work yourself.
5787
5789
  - PROJECT DIRECTORY: When creating files, first create a dedicated project directory (short kebab-case name, e.g. "snake-game"). Do ALL work inside it. Report it as PROJECT_DIR: <directory-name> in your output. If the user is just chatting (no code needed), skip this.`
5788
5790
  };
@@ -5812,12 +5814,12 @@ var AgentSession = class {
5812
5814
  skipResume: isFirstExecute && this.hasHistory
5813
5815
  });
5814
5816
  try {
5815
- const whichPath = execSync3(`which ${this.backend.command}`, { env: cleanEnv, encoding: "utf-8", timeout: 3e3 }).trim();
5817
+ const whichPath = execSync2(`which ${this.backend.command}`, { env: cleanEnv, encoding: "utf-8", timeout: 3e3 }).trim();
5816
5818
  console.log(`[Agent ${this.name}] Binary: ${whichPath}, CLAUDECODE=${cleanEnv.CLAUDECODE ?? "unset"}, ENTRYPOINT=${cleanEnv.CLAUDE_CODE_ENTRYPOINT ?? "unset"}`);
5817
5819
  } catch {
5818
5820
  }
5819
5821
  console.log(`[Agent ${this.name}] Spawning: ${this.backend.command} ${args.map((a) => a.length > 80 ? a.slice(0, 80) + "..." : a).join(" ")}`);
5820
- this.process = spawn2(this.backend.command, args, {
5822
+ this.process = spawn(this.backend.command, args, {
5821
5823
  cwd,
5822
5824
  env: cleanEnv,
5823
5825
  stdio: ["ignore", "pipe", "pipe"],
@@ -5910,8 +5912,20 @@ var AgentSession = class {
5910
5912
  if (msg.type === "assistant" && msg.message?.content) {
5911
5913
  if (msg.message.usage) {
5912
5914
  const usage = msg.message.usage;
5913
- if (typeof usage.input_tokens === "number") this.taskInputTokens += usage.input_tokens;
5914
- if (typeof usage.output_tokens === "number") this.taskOutputTokens += usage.output_tokens;
5915
+ const turnIn = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
5916
+ const turnOut = usage.output_tokens ?? 0;
5917
+ const sig = `${turnIn}:${turnOut}`;
5918
+ if (sig !== this.lastUsageSignature) {
5919
+ this.lastUsageSignature = sig;
5920
+ this.taskInputTokens += turnIn;
5921
+ this.taskOutputTokens += turnOut;
5922
+ this.onEvent({
5923
+ type: "token:update",
5924
+ agentId: this.agentId,
5925
+ inputTokens: this.taskInputTokens,
5926
+ outputTokens: this.taskOutputTokens
5927
+ });
5928
+ }
5915
5929
  }
5916
5930
  for (const block of msg.message.content) {
5917
5931
  if (block.type === "text" && block.text) {
@@ -5922,12 +5936,27 @@ var AgentSession = class {
5922
5936
  console.log(`[Agent ${this.name} thinking] ${block.thinking.slice(0, 120)}...`);
5923
5937
  }
5924
5938
  }
5925
- } else if (msg.type === "result" && msg.result) {
5926
- if (!this.stdoutBuffer) {
5927
- this.stdoutBuffer = msg.result;
5928
- handleTextLine(msg.result);
5939
+ } else if (msg.type === "result") {
5940
+ if (msg.usage) {
5941
+ const usage = msg.usage;
5942
+ const totalIn = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
5943
+ const totalOut = usage.output_tokens ?? 0;
5944
+ this.taskInputTokens = totalIn;
5945
+ this.taskOutputTokens = totalOut;
5946
+ this.onEvent({
5947
+ type: "token:update",
5948
+ agentId: this.agentId,
5949
+ inputTokens: this.taskInputTokens,
5950
+ outputTokens: this.taskOutputTokens
5951
+ });
5952
+ }
5953
+ if (msg.result) {
5954
+ if (!this.stdoutBuffer) {
5955
+ this.stdoutBuffer = msg.result;
5956
+ handleTextLine(msg.result);
5957
+ }
5958
+ this._lastResultText = msg.result;
5929
5959
  }
5930
- this._lastResultText = msg.result;
5931
5960
  }
5932
5961
  continue;
5933
5962
  } catch {
@@ -5948,11 +5977,18 @@ var AgentSession = class {
5948
5977
  }
5949
5978
  });
5950
5979
  this.process.on("close", (code) => {
5980
+ const agentPid = this.process?.pid;
5951
5981
  this.process = null;
5952
5982
  if (this.taskTimeout) {
5953
5983
  clearTimeout(this.taskTimeout);
5954
5984
  this.taskTimeout = null;
5955
5985
  }
5986
+ if (agentPid) {
5987
+ try {
5988
+ process.kill(-agentPid, "SIGTERM");
5989
+ } catch {
5990
+ }
5991
+ }
5956
5992
  const remaining = jsonLineBuf.trim();
5957
5993
  if (remaining) {
5958
5994
  jsonLineBuf = "";
@@ -6315,6 +6351,8 @@ var DelegationRouter = class {
6315
6351
  devFixAttempts = /* @__PURE__ */ new Map();
6316
6352
  /** Tracks which dev agent was last assigned to work (for reviewer → dev routing) */
6317
6353
  lastDevAgentId = null;
6354
+ /** Last known preview fields from developer output (survives across rounds for leader context) */
6355
+ lastDevPreview = "";
6318
6356
  agentManager;
6319
6357
  promptEngine;
6320
6358
  emitEvent;
@@ -6418,6 +6456,7 @@ var DelegationRouter = class {
6418
6456
  this.teamProjectDir = null;
6419
6457
  this.devFixAttempts.clear();
6420
6458
  this.lastDevAgentId = null;
6459
+ this.lastDevPreview = "";
6421
6460
  for (const pending of this.pendingResults.values()) {
6422
6461
  clearTimeout(pending.timer);
6423
6462
  }
@@ -6596,6 +6635,17 @@ SUMMARY: (one sentence overall assessment)`
6596
6635
  if (this.tryDirectFix(agentId, fromSession, fullOutput ?? summary, originAgentId)) {
6597
6636
  return;
6598
6637
  }
6638
+ const fromRole = fromSession?.role?.toLowerCase() ?? "";
6639
+ if (!fromRole.includes("review") && fullOutput) {
6640
+ const lines = [];
6641
+ const em = fullOutput.match(/ENTRY_FILE:\s*(.+)/i);
6642
+ const cm = fullOutput.match(/PREVIEW_CMD:\s*(.+)/i);
6643
+ const pm = fullOutput.match(/PREVIEW_PORT:\s*[*`_]*(\d+)/i);
6644
+ if (em) lines.push(`ENTRY_FILE: ${em[1].trim()}`);
6645
+ if (cm) lines.push(`PREVIEW_CMD: ${cm[1].trim()}`);
6646
+ if (pm) lines.push(`PREVIEW_PORT: ${pm[1]}`);
6647
+ if (lines.length > 0) this.lastDevPreview = lines.join("\n");
6648
+ }
6599
6649
  this.enqueueResult(originAgentId, { fromName, statusWord, summary: summary.slice(0, 400) });
6600
6650
  };
6601
6651
  }
@@ -6759,7 +6809,8 @@ ${resultLines2}`,
6759
6809
  resultStatus: pending.results.every((r) => r.statusWord.includes("success")) ? "completed successfully" : "mixed results",
6760
6810
  resultSummary: resultLines,
6761
6811
  originalTask: originSession.originalTask ?? "",
6762
- roundInfo
6812
+ roundInfo,
6813
+ devPreview: this.lastDevPreview
6763
6814
  });
6764
6815
  console.log(`[ResultBatch] Flushing ${pending.results.length} result(s) to ${originAgentId} (round ${this.leaderRounds}, budget=${CONFIG.delegation.budgetRounds}, ceiling=${CONFIG.delegation.hardCeilingRounds})`);
6765
6816
  originSession.runTask(followUpTaskId, batchPrompt, void 0, teamContext);
@@ -6767,7 +6818,7 @@ ${resultLines2}`,
6767
6818
  };
6768
6819
 
6769
6820
  // ../../packages/orchestrator/src/prompt-templates.ts
6770
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
6821
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync8 } from "fs";
6771
6822
  import path7 from "path";
6772
6823
  var PROMPT_DEFAULTS = {
6773
6824
  "leader-initial": `You are {{name}}, the Team Lead. {{personality}}
@@ -6817,6 +6868,12 @@ Team status:
6817
6868
 
6818
6869
  Delegate using: @AgentName: task description
6819
6870
 
6871
+ ===== RULES =====
6872
+ - ONE task at a time. Delegate to the developer FIRST. Wait for their result before assigning Code Reviewer.
6873
+ - Do NOT assign Code Reviewer and Developer simultaneously \u2014 there is nothing to review until the dev is done.
6874
+ - Keep fixes MINIMAL. If the user reports a bug, fix THAT bug only. Do NOT add new features, tests, or process changes in the same round.
6875
+ - Do NOT redefine the reviewer's methodology or add new review requirements \u2014 just ask them to review the code.
6876
+
6820
6877
  {{prompt}}`,
6821
6878
  "leader-result": `You are the Team Lead. You CANNOT write or fix code. You can ONLY delegate using @Name: <task>.
6822
6879
 
@@ -6857,12 +6914,15 @@ Check WHO sent this result, then follow the matching branch:
6857
6914
  \u2022 Permanent blocker (auth error, missing API key, service down) \u2192 report to the user, do not retry.
6858
6915
  \u2022 Same error repeated twice \u2192 STOP and report to the user.
6859
6916
 
6917
+ ===== DEVELOPER'S LAST KNOWN PREVIEW FIELDS =====
6918
+ {{devPreview}}
6919
+
6860
6920
  ===== FINAL SUMMARY FORMAT =====
6861
- (Copy preview fields EXACTLY from the developer's LAST successful report. Only include fields the dev actually provided \u2014 do NOT invent values.)
6921
+ (Copy preview fields from DEVELOPER'S LAST KNOWN PREVIEW FIELDS above. Do NOT invent values.)
6862
6922
 
6863
- ENTRY_FILE: <from dev \u2014 e.g. index.html, dist/index.html. OMIT if dev didn't provide one>
6864
- PREVIEW_CMD: <from dev \u2014 e.g. "python app.py". OMIT if dev didn't provide one. NEVER use "npm run dev" or "npm start"!>
6865
- PREVIEW_PORT: <from dev \u2014 e.g. 5000, 3000. OMIT if dev didn't provide one>
6923
+ ENTRY_FILE: <copy from above if available, otherwise OMIT>
6924
+ PREVIEW_CMD: <copy from above if available, otherwise OMIT. NEVER use "npm run dev" or "npm start"!>
6925
+ PREVIEW_PORT: <copy from above if available, otherwise OMIT>
6866
6926
  SUMMARY: <2-3 sentence description of what was built>
6867
6927
 
6868
6928
  RULES:
@@ -6873,95 +6933,71 @@ RULES:
6873
6933
  - Do NOT include PROJECT_DIR \u2014 the system manages project directories automatically.`,
6874
6934
  "worker-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
6875
6935
 
6876
- CONVERGENCE RULES (follow strictly):
6877
- - Do the MINIMUM needed to satisfy the task. Simple and working beats perfect and slow.
6878
- - Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
6879
- - If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
6880
- - Do NOT add features, error handling, or improvements that were not explicitly asked for.
6881
-
6882
- HARD LIMITS:
6883
- - NEVER run "npm run dev", "npm start", "npx vite", "python -m http.server", or ANY command that starts a long-running server. These will hang forever and waste your budget. The system handles preview serving automatically.
6884
- - Do NOT create backend servers, WebSocket servers, or any server-side code UNLESS the task explicitly requires one. Default to static HTML/CSS/JS.
6885
- - You MAY install dependencies (npm install, pip install) and run ONE-SHOT build commands (npm run build, npx tsc). Never run watch/serve/dev commands.
6936
+ RULES:
6937
+ - Do the MINIMUM needed. Simple and working beats perfect.
6938
+ - NEVER run long-running commands (npm run dev, npm start, npx vite, live-server, python -m http.server). They hang forever and you will be killed. The system serves previews automatically.
6939
+ - Do NOT launch GUI apps (Pygame, Tkinter, Electron) or dev servers. You CANNOT see UI.
6940
+ - You MAY run one-shot commands: npm install, npm run build, npx tsc, syntax checks.
6941
+ - Default to static HTML/CSS/JS unless a backend is explicitly required.
6886
6942
  {{soloHint}}
6887
6943
  {{memory}}
6888
- Start with one sentence describing your approach. Then do the work.
6889
-
6890
- You are responsible for the COMPLETE deliverable \u2014 not just source files. This means:
6891
- 1. Project setup: create all config files needed (package.json, tsconfig, build config, requirements.txt, etc.)
6892
- 2. All source code
6893
- 3. Build & verify: if the project has a build step, RUN IT and fix errors until it passes
6894
- 4. Report how to run/preview the result (see deliverable types below)
6895
-
6896
- VERIFICATION (MANDATORY before reporting STATUS: done):
6897
- - If you created a package.json with a build script \u2192 run "npm run build" (ONE-SHOT), fix errors until it succeeds, confirm the output file exists. NEVER run "npm run dev" or "npm start" \u2014 these hang forever.
6898
- - If your deliverable is an HTML file \u2192 confirm it exists and references valid scripts/styles
6899
- - If your deliverable is a script (Python, Node, etc.) \u2192 run a syntax check (python -c "import ast; ast.parse(open('app.py').read())" or node --check app.js)
6900
- - If NONE of the above apply \u2192 at minimum list the files and confirm the entry point exists
6901
- - IMPORTANT: Do NOT launch GUI/desktop applications (Pygame, Tkinter, Electron, etc.) \u2014 they open windows that cannot be controlled. Do NOT start dev servers (vite, webpack-dev-server, live-server) \u2014 they never exit.
6902
- - FINAL CHECK: confirm you can fill in at least ENTRY_FILE or PREVIEW_CMD (see deliverable types). If you cannot, your deliverable is incomplete \u2014 fix it before reporting.
6903
- - Do NOT report STATUS: done unless verification passes. Fix problems yourself first.
6904
- - STATUS: failed is ONLY for truly unsolvable problems (missing API keys, no network, system-level issues).
6905
6944
 
6906
- ===== DELIVERABLE TYPES =====
6907
- ALWAYS prefer type A (static web) unless the task EXPLICITLY requires a server or desktop app.
6908
- Games, interactive demos, visualizations, and web pages should ALL be static HTML.
6945
+ OUTPUT STYLE:
6946
+ - While working, output a SHORT status line (\u22648 words) at each major step, prefixed with \u2192. Example: "\u2192 Setting up project" or "\u2192 Building game logic". No other prose or narration. Do NOT write "Let me...", "I'll now...", "Looking at..." \u2014 just do the work.
6947
+ - After all work is done, output ONLY the structured result block below.
6909
6948
 
6910
- A) STATIC WEB (HTML/CSS/JS \u2014 no server needed) \u2014 DEFAULT CHOICE:
6911
- ENTRY_FILE: index.html (the HTML file to open \u2014 e.g. index.html, dist/index.html, build/index.html)
6912
- This is the preferred approach. Put everything in a single HTML file or a small set of static files.
6949
+ DELIVERABLE:
6950
+ - You own the COMPLETE deliverable: project setup, all source code, build & verify.
6951
+ - STATUS: failed is ONLY for truly unsolvable problems (missing API keys, system issues).
6913
6952
 
6914
- B) WEB SERVER \u2014 ONLY if the task explicitly requires a backend (database, API proxy, user auth, etc.):
6915
- PREVIEW_CMD: python app.py (the command to start the server)
6916
- PREVIEW_PORT: 5000 (the port the server listens on \u2014 REQUIRED for web servers)
6953
+ VERIFY BEFORE REPORTING DONE (mandatory):
6954
+ - If package.json has a build script \u2192 run "npm run build" (one-shot), fix errors until it passes.
6955
+ - If HTML deliverable \u2192 confirm the file exists and references valid scripts/styles.
6956
+ - If script (Python/Node) \u2192 run syntax check (node --check / python -c "import ast; ...").
6957
+ - FINAL CHECK: you MUST be able to fill in ENTRY_FILE or PREVIEW_CMD below. If not, your deliverable is incomplete \u2014 fix it first.
6917
6958
 
6918
- C) DESKTOP/CLI APP (Pygame, Tkinter, Electron, JavaFX, terminal tool, native GUI, etc.):
6919
- PREVIEW_CMD: python game.py (the command to launch the app \u2014 NO PREVIEW_PORT needed)
6959
+ DELIVERABLE TYPES (prefer A):
6960
+ A) STATIC WEB \u2192 ENTRY_FILE: index.html
6961
+ B) WEB SERVER (only if backend needed) \u2192 PREVIEW_CMD + PREVIEW_PORT
6962
+ C) DESKTOP/CLI \u2192 PREVIEW_CMD only
6920
6963
 
6921
- OUTPUT:
6964
+ PORT RULES FOR WEB SERVERS (type B):
6965
+ - The system overrides your port. Your app MUST read port from the PORT environment variable.
6966
+ - Python: use int(os.environ.get("PORT", 5000)) \u2014 NOT a hardcoded port.
6967
+ - Node/JS: use process.env.PORT || 3000
6968
+ - Always output PREVIEW_CMD even for Vite/webpack/bundler projects (e.g. PREVIEW_CMD: npx vite).
6922
6969
 
6970
+ RESULT FORMAT:
6923
6971
  STATUS: done | failed
6924
- FILES_CHANGED: (list all files created or modified, one per line)
6925
- ENTRY_FILE: (type A only \u2014 path to the HTML file)
6926
- PREVIEW_CMD: (types B and C ONLY \u2014 OMIT this field entirely for static web projects)
6927
- PREVIEW_PORT: (type B only \u2014 the port the server listens on)
6928
- SUMMARY: (one sentence: what you built + how to run/preview it)
6929
-
6930
- You MUST provide at least ENTRY_FILE or PREVIEW_CMD. For games and interactive projects, ENTRY_FILE is almost always correct.
6972
+ FILES_CHANGED: (one per line)
6973
+ ENTRY_FILE: (type A)
6974
+ PREVIEW_CMD: (types B/C only)
6975
+ PREVIEW_PORT: (type B only)
6976
+ SUMMARY: (one sentence)
6931
6977
 
6932
6978
  {{prompt}}`,
6933
6979
  "worker-reviewer-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
6934
6980
 
6935
- CONVERGENCE RULES (follow strictly):
6936
- - Do the MINIMUM needed to satisfy the task. Simple and working beats perfect and slow.
6937
- - Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
6938
- - If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
6939
- - Do NOT add features, error handling, or improvements that were not explicitly asked for.
6940
-
6941
- HARD LIMITS:
6942
- - NEVER run "npm run dev", "npm start", "npx vite", or ANY long-running server command. These hang forever. Only use one-shot commands like "npm run build" or "node --check".
6943
- - Do NOT launch GUI/desktop applications (Pygame, Tkinter, Electron, etc.) to test them \u2014 they open windows that cannot be controlled. Use syntax checks, import checks, and code reading only.
6944
-
6945
- Code Quality (must check):
6946
- - Correctness: crashes, broken logic, missing files, syntax errors.
6947
- - Verify the deliverable can actually run: check that entry point exists, dependencies are declared, build output is present. For GUI/desktop apps, verify via code review and syntax checks \u2014 do NOT run them.
6948
- - VERIFY WITH TOOLS, not just the developer's summary. Run "ls" to confirm reported files exist. If ENTRY_FILE is claimed, check the file is there and references valid scripts/styles. Do not trust STATUS: done at face value.
6949
- - Do NOT flag security issues in prototypes \u2014 this is a demo, not production code.
6981
+ RULES:
6982
+ - NEVER run servers, dev commands, or GUI apps. You CANNOT see UI.
6983
+ - ONLY use: code reading, "ls" to check files, "npm run build" (one-shot), syntax checks.
6984
+ - This is a prototype \u2014 do NOT nitpick style, naming, formatting, or security.
6950
6985
 
6951
- Feature Completeness (must check):
6952
- - Compare the deliverable against the key features listed in your task assignment.
6953
- - Flag CORE features that are completely missing or non-functional as ISSUES.
6954
- - Do NOT fail for polish, extras, or stretch goals \u2014 this is a prototype. Focus on whether the main functionality works.
6986
+ OUTPUT STYLE:
6987
+ - While reviewing, output a SHORT status line (\u22648 words) at each step, prefixed with \u2192. Example: "\u2192 Checking file structure" or "\u2192 Reading game logic". No other prose.
6988
+ - After review, output ONLY the verdict block below.
6955
6989
 
6956
- Do NOT nitpick style, naming, formatting, or security hardening. This is a prototype, not production code.
6957
- Focus ONLY on: does it run? Does it do what was asked?
6990
+ REVIEW CHECKLIST:
6991
+ 1. VERIFY files exist with "ls" \u2014 do NOT trust the developer's summary at face value. Check ENTRY_FILE is real and references valid scripts/styles.
6992
+ 2. READ the code to verify logic. Check for crashes, broken logic, missing files, syntax errors.
6993
+ 3. Feature completeness: compare against key features in your task. Flag CORE features missing/broken as ISSUES. Ignore polish/extras.
6958
6994
 
6959
6995
  VERDICT: PASS | FAIL
6960
- - PASS = code runs without crashes AND core features are implemented (even if rough)
6961
- - FAIL = crashes/bugs that prevent usage OR core features are missing/broken
6962
- ISSUES: (numbered list \u2014 bugs, security problems, or missing core features)
6963
- SUGGESTIONS: (optional \u2014 minor non-blocking observations, keep brief)
6964
- SUMMARY: (one sentence overall assessment)
6996
+ - PASS = runs without crashes AND core features implemented
6997
+ - FAIL = crashes/bugs prevent usage OR core features missing
6998
+ ISSUES: (numbered list)
6999
+ SUGGESTIONS: (optional, brief)
7000
+ SUMMARY: (one sentence)
6965
7001
 
6966
7002
  {{prompt}}`,
6967
7003
  "worker-continue": `{{prompt}}`,
@@ -7028,7 +7064,7 @@ ASSIGNMENTS:
7028
7064
  - Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax outside [PLAN] tags.
7029
7065
 
7030
7066
  If the user hasn't described their project yet, greet them and ask what they'd like to build.
7031
-
7067
+ {{memory}}
7032
7068
  Team:
7033
7069
  {{teamRoster}}
7034
7070
 
@@ -7113,12 +7149,12 @@ var PromptEngine = class {
7113
7149
  return;
7114
7150
  }
7115
7151
  if (!existsSync8(this.promptsDir)) {
7116
- mkdirSync5(this.promptsDir, { recursive: true });
7152
+ mkdirSync6(this.promptsDir, { recursive: true });
7117
7153
  }
7118
7154
  let written = 0;
7119
7155
  for (const [name, content] of Object.entries(PROMPT_DEFAULTS)) {
7120
7156
  const filePath = path7.join(this.promptsDir, `${name}.md`);
7121
- writeFileSync5(filePath, content, "utf-8");
7157
+ writeFileSync6(filePath, content, "utf-8");
7122
7158
  written++;
7123
7159
  }
7124
7160
  console.log(`[Prompts] Synced ${written} default templates to ${this.promptsDir}`);
@@ -7136,7 +7172,7 @@ var PromptEngine = class {
7136
7172
  const filePath = path7.join(this.promptsDir, `${name}.md`);
7137
7173
  if (existsSync8(filePath)) {
7138
7174
  try {
7139
- merged[name] = readFileSync5(filePath, "utf-8");
7175
+ merged[name] = readFileSync6(filePath, "utf-8");
7140
7176
  loaded++;
7141
7177
  } catch {
7142
7178
  defaulted++;
@@ -7419,7 +7455,7 @@ function finalizeTeamResult(ctx) {
7419
7455
  }
7420
7456
  validateEntryFile(result, projectDir ?? workspace, workspace);
7421
7457
  autoConstructPreviewCmd(result);
7422
- if (!result.previewUrl) {
7458
+ if (!result.previewUrl && !result.previewPath) {
7423
7459
  resolvePreviewUrlFromTeam(result, ctx);
7424
7460
  }
7425
7461
  }
@@ -7454,9 +7490,9 @@ function resolvePreviewUrlFromTeam(result, ctx) {
7454
7490
  const { projectDir, workspace } = ctx;
7455
7491
  const resolveDir = projectDir ?? workspace;
7456
7492
  const workerPreview = ctx.detectWorkerPreview();
7457
- if (workerPreview?.previewUrl) {
7458
- result.previewUrl = workerPreview.previewUrl;
7459
- result.previewPath = workerPreview.previewPath;
7493
+ if (workerPreview?.previewUrl || workerPreview?.previewPath) {
7494
+ if (workerPreview.previewUrl) result.previewUrl = workerPreview.previewUrl;
7495
+ if (workerPreview.previewPath) result.previewPath = workerPreview.previewPath;
7460
7496
  return;
7461
7497
  }
7462
7498
  const allChangedFiles = result.changedFiles ?? [];
@@ -7468,19 +7504,17 @@ function resolvePreviewUrlFromTeam(result, ctx) {
7468
7504
  cwd: resolveDir,
7469
7505
  workspace
7470
7506
  });
7471
- if (preview.previewUrl) {
7472
- result.previewUrl = preview.previewUrl;
7473
- result.previewPath = preview.previewPath;
7474
- }
7507
+ if (preview.previewUrl) result.previewUrl = preview.previewUrl;
7508
+ if (preview.previewPath) result.previewPath = preview.previewPath;
7475
7509
  }
7476
7510
 
7477
7511
  // ../../packages/orchestrator/src/worktree.ts
7478
- import { execSync as execSync4 } from "child_process";
7512
+ import { execSync as execSync3 } from "child_process";
7479
7513
  import path9 from "path";
7480
7514
  var TIMEOUT = 5e3;
7481
7515
  function isGitRepo(cwd) {
7482
7516
  try {
7483
- execSync4("git rev-parse --is-inside-work-tree", { cwd, stdio: "ignore", timeout: TIMEOUT });
7517
+ execSync3("git rev-parse --is-inside-work-tree", { cwd, stdio: "ignore", timeout: TIMEOUT });
7484
7518
  return true;
7485
7519
  } catch {
7486
7520
  return false;
@@ -7493,7 +7527,7 @@ function createWorktree(workspace, agentId, taskId, agentName) {
7493
7527
  const worktreePath = path9.join(worktreeDir, worktreeName);
7494
7528
  const branch = `agent/${agentName.toLowerCase().replace(/\s+/g, "-")}/${taskId}`;
7495
7529
  try {
7496
- execSync4(`git worktree add "${worktreePath}" -b "${branch}"`, {
7530
+ execSync3(`git worktree add "${worktreePath}" -b "${branch}"`, {
7497
7531
  cwd: workspace,
7498
7532
  stdio: "pipe",
7499
7533
  timeout: TIMEOUT
@@ -7506,30 +7540,30 @@ function createWorktree(workspace, agentId, taskId, agentName) {
7506
7540
  }
7507
7541
  function mergeWorktree(workspace, worktreePath, branch) {
7508
7542
  try {
7509
- execSync4(`git merge --no-ff "${branch}"`, {
7543
+ execSync3(`git merge --no-ff "${branch}"`, {
7510
7544
  cwd: workspace,
7511
7545
  stdio: "pipe",
7512
7546
  timeout: TIMEOUT
7513
7547
  });
7514
7548
  try {
7515
- execSync4(`git worktree remove "${worktreePath}"`, { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7549
+ execSync3(`git worktree remove "${worktreePath}"`, { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7516
7550
  } catch {
7517
7551
  }
7518
7552
  try {
7519
- execSync4(`git branch -d "${branch}"`, { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7553
+ execSync3(`git branch -d "${branch}"`, { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7520
7554
  } catch {
7521
7555
  }
7522
7556
  return { success: true };
7523
7557
  } catch (err) {
7524
7558
  let conflictFiles = [];
7525
7559
  try {
7526
- const output = execSync4("git diff --name-only --diff-filter=U", {
7560
+ const output = execSync3("git diff --name-only --diff-filter=U", {
7527
7561
  cwd: workspace,
7528
7562
  encoding: "utf-8",
7529
7563
  timeout: TIMEOUT
7530
7564
  }).trim();
7531
7565
  conflictFiles = output ? output.split("\n") : [];
7532
- execSync4("git merge --abort", { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7566
+ execSync3("git merge --abort", { cwd: workspace, stdio: "pipe", timeout: TIMEOUT });
7533
7567
  } catch {
7534
7568
  }
7535
7569
  return { success: false, conflictFiles };
@@ -7538,119 +7572,15 @@ function mergeWorktree(workspace, worktreePath, branch) {
7538
7572
  function removeWorktree(worktreePath, branch, workspace) {
7539
7573
  const cwd = workspace ?? path9.dirname(path9.dirname(worktreePath));
7540
7574
  try {
7541
- execSync4(`git worktree remove --force "${worktreePath}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
7575
+ execSync3(`git worktree remove --force "${worktreePath}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
7542
7576
  } catch {
7543
7577
  }
7544
7578
  try {
7545
- execSync4(`git branch -D "${branch}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
7579
+ execSync3(`git branch -D "${branch}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
7546
7580
  } catch {
7547
7581
  }
7548
7582
  }
7549
7583
 
7550
- // ../../packages/orchestrator/src/memory.ts
7551
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync9 } from "fs";
7552
- import path10 from "path";
7553
- import { homedir as homedir5 } from "os";
7554
- var MEMORY_DIR = path10.join(homedir5(), ".bit-office", "memory");
7555
- function ensureDir() {
7556
- if (!existsSync9(MEMORY_DIR)) {
7557
- mkdirSync6(MEMORY_DIR, { recursive: true });
7558
- }
7559
- }
7560
- function loadStore() {
7561
- const filePath = path10.join(MEMORY_DIR, "memory.json");
7562
- try {
7563
- if (existsSync9(filePath)) {
7564
- return JSON.parse(readFileSync6(filePath, "utf-8"));
7565
- }
7566
- } catch {
7567
- }
7568
- return { reviewPatterns: [], techPreferences: [], projectHistory: [] };
7569
- }
7570
- function saveStore(store) {
7571
- ensureDir();
7572
- const filePath = path10.join(MEMORY_DIR, "memory.json");
7573
- writeFileSync6(filePath, JSON.stringify(store, null, 2), "utf-8");
7574
- }
7575
- function recordReviewFeedback(reviewOutput) {
7576
- const verdictMatch = reviewOutput.match(/VERDICT[:\s]*(\w+)/i);
7577
- if (!verdictMatch || verdictMatch[1].toUpperCase() !== "FAIL") return;
7578
- const issueLines = [];
7579
- const issueRe = /^\s*\d+[.)]\s*(.+)/gm;
7580
- let match;
7581
- while ((match = issueRe.exec(reviewOutput)) !== null) {
7582
- const issue = match[1].trim();
7583
- if (issue.length > 10 && issue.length < 200) {
7584
- issueLines.push(issue);
7585
- }
7586
- }
7587
- if (issueLines.length === 0) return;
7588
- const store = loadStore();
7589
- const now = Date.now();
7590
- for (const issue of issueLines) {
7591
- const normalized = normalizeIssue(issue);
7592
- const existing = store.reviewPatterns.find((p) => normalizeIssue(p.pattern) === normalized);
7593
- if (existing) {
7594
- existing.count++;
7595
- existing.lastSeen = now;
7596
- } else {
7597
- store.reviewPatterns.push({ pattern: issue, count: 1, lastSeen: now });
7598
- }
7599
- }
7600
- store.reviewPatterns.sort((a, b) => b.count - a.count);
7601
- store.reviewPatterns = store.reviewPatterns.slice(0, 20);
7602
- saveStore(store);
7603
- console.log(`[Memory] Recorded ${issueLines.length} review pattern(s), total=${store.reviewPatterns.length}`);
7604
- }
7605
- function recordProjectCompletion(summary, tech, reviewPassed) {
7606
- const store = loadStore();
7607
- store.projectHistory.push({
7608
- summary: summary.slice(0, 300),
7609
- tech: tech.slice(0, 100),
7610
- completedAt: Date.now(),
7611
- reviewPassed
7612
- });
7613
- if (store.projectHistory.length > 50) {
7614
- store.projectHistory = store.projectHistory.slice(-50);
7615
- }
7616
- saveStore(store);
7617
- console.log(`[Memory] Recorded project completion: ${summary.slice(0, 80)}`);
7618
- }
7619
- function recordTechPreference(tech) {
7620
- const store = loadStore();
7621
- const normalized = tech.trim().toLowerCase();
7622
- if (!store.techPreferences.some((t) => t.toLowerCase() === normalized)) {
7623
- store.techPreferences.push(tech.trim());
7624
- if (store.techPreferences.length > 10) {
7625
- store.techPreferences = store.techPreferences.slice(-10);
7626
- }
7627
- saveStore(store);
7628
- console.log(`[Memory] Recorded tech preference: ${tech}`);
7629
- }
7630
- }
7631
- function getMemoryContext() {
7632
- const store = loadStore();
7633
- const sections = [];
7634
- const recurring = store.reviewPatterns.filter((p) => p.count >= 2);
7635
- if (recurring.length > 0) {
7636
- const lines = recurring.slice(0, 5).map((p) => `- ${p.pattern} (flagged ${p.count}x)`);
7637
- sections.push(`COMMON REVIEW ISSUES (avoid these):
7638
- ${lines.join("\n")}`);
7639
- }
7640
- if (store.techPreferences.length > 0) {
7641
- const recent = store.techPreferences.slice(-3);
7642
- sections.push(`USER'S PREFERRED TECH: ${recent.join(", ")}`);
7643
- }
7644
- if (sections.length === 0) return "";
7645
- return `
7646
- ===== LEARNED FROM PREVIOUS PROJECTS =====
7647
- ${sections.join("\n\n")}
7648
- `;
7649
- }
7650
- function normalizeIssue(issue) {
7651
- return issue.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
7652
- }
7653
-
7654
7584
  // ../../packages/orchestrator/src/orchestrator.ts
7655
7585
  var Orchestrator = class extends EventEmitter {
7656
7586
  agentManager = new AgentManager();
@@ -7705,8 +7635,8 @@ var Orchestrator = class extends EventEmitter {
7705
7635
  createAgent(opts) {
7706
7636
  const backend = this.backends.get(opts.backend ?? this.defaultBackendId) ?? this.backends.get(this.defaultBackendId);
7707
7637
  const roleLower = opts.role.toLowerCase();
7708
- const isDevWorker = !roleLower.includes("review") && !roleLower.includes("lead");
7709
- const memoryContext = isDevWorker ? getMemoryContext() : "";
7638
+ const isReviewer = roleLower.includes("review");
7639
+ const memoryContext = !isReviewer ? getMemoryContext() : "";
7710
7640
  const session = new AgentSession({
7711
7641
  agentId: opts.agentId,
7712
7642
  name: opts.name,
@@ -8210,6 +8140,152 @@ var Orchestrator = class extends EventEmitter {
8210
8140
  }
8211
8141
  };
8212
8142
 
8143
+ // ../../packages/orchestrator/src/preview-server.ts
8144
+ import { spawn as spawn2, execSync as execSync4 } from "child_process";
8145
+ import { existsSync as existsSync9 } from "fs";
8146
+ import path10 from "path";
8147
+ var STATIC_PORT = 9100;
8148
+ var COMMAND_PORT = 9101;
8149
+ var PreviewServer = class {
8150
+ process = null;
8151
+ currentDir = null;
8152
+ isDetached = false;
8153
+ /**
8154
+ * Mode 1: Serve a static file directory on a fixed port.
8155
+ * Returns the preview URL for the given file.
8156
+ */
8157
+ serve(filePath) {
8158
+ if (!existsSync9(filePath)) {
8159
+ console.log(`[PreviewServer] File not found: ${filePath}`);
8160
+ return void 0;
8161
+ }
8162
+ const dir = path10.dirname(filePath);
8163
+ const fileName = path10.basename(filePath);
8164
+ this.stop();
8165
+ try {
8166
+ this.process = spawn2("npx", ["serve", dir, "-l", String(STATIC_PORT), "--no-clipboard"], {
8167
+ stdio: "ignore",
8168
+ detached: true
8169
+ });
8170
+ this.process.unref();
8171
+ this.currentDir = dir;
8172
+ this.isDetached = true;
8173
+ const url = `http://localhost:${STATIC_PORT}/${fileName}`;
8174
+ console.log(`[PreviewServer] Serving ${dir} on port ${STATIC_PORT}`);
8175
+ return url;
8176
+ } catch (e) {
8177
+ console.log(`[PreviewServer] Failed to start static serve: ${e}`);
8178
+ return void 0;
8179
+ }
8180
+ }
8181
+ /**
8182
+ * Mode 2: Run a command (e.g. "python app.py") and use a controlled port.
8183
+ * The agent-specified port is ALWAYS replaced with COMMAND_PORT to prevent
8184
+ * conflicts with the host system (e.g. Next.js on 3000).
8185
+ * Returns the preview URL.
8186
+ */
8187
+ runCommand(cmd, cwd, agentPort) {
8188
+ this.stop();
8189
+ const port = COMMAND_PORT;
8190
+ cmd = cmd.replace(/\s+(?:--port|-p)\s+\d+/gi, "");
8191
+ if (agentPort) cmd = cmd.replace(new RegExp(`\\b${agentPort}\\b`, "g"), String(port));
8192
+ const isPython = /^python\b|^python3\b/i.test(cmd.trim());
8193
+ if (!isPython) {
8194
+ cmd = `${cmd} --port ${port}`;
8195
+ }
8196
+ console.log(`[PreviewServer] Command: "${cmd}" (forced port ${port})`);
8197
+ this.killPortHolder(port);
8198
+ try {
8199
+ this.process = spawn2(cmd, {
8200
+ shell: true,
8201
+ cwd,
8202
+ stdio: "ignore",
8203
+ detached: true,
8204
+ env: { ...process.env, PORT: String(port) }
8205
+ });
8206
+ this.process.unref();
8207
+ this.currentDir = cwd;
8208
+ this.isDetached = true;
8209
+ const url = `http://localhost:${port}`;
8210
+ console.log(`[PreviewServer] Running "${cmd}" in ${cwd}, preview at port ${port}`);
8211
+ return url;
8212
+ } catch (e) {
8213
+ console.log(`[PreviewServer] Failed to run command: ${e}`);
8214
+ return void 0;
8215
+ }
8216
+ }
8217
+ /**
8218
+ * Mode 3: Launch a desktop/CLI process (no web preview URL).
8219
+ * Used for Pygame, Tkinter, Electron, terminal apps, etc.
8220
+ * NOT detached — GUI apps need the login session to access WindowServer (macOS).
8221
+ */
8222
+ launchProcess(cmd, cwd) {
8223
+ this.stop();
8224
+ try {
8225
+ this.process = spawn2(cmd, {
8226
+ shell: true,
8227
+ cwd,
8228
+ stdio: ["ignore", "ignore", "pipe"]
8229
+ });
8230
+ this.currentDir = cwd;
8231
+ this.isDetached = false;
8232
+ console.log(`[PreviewServer] Launched "${cmd}" in ${cwd} (pid=${this.process.pid})`);
8233
+ this.process.stderr?.on("data", (data) => {
8234
+ const msg = data.toString().trim();
8235
+ if (msg) console.log(`[PreviewServer] stderr: ${msg.slice(0, 200)}`);
8236
+ });
8237
+ this.process.on("exit", (code) => {
8238
+ console.log(`[PreviewServer] Process exited with code ${code}`);
8239
+ });
8240
+ } catch (e) {
8241
+ console.log(`[PreviewServer] Failed to launch process: ${e}`);
8242
+ }
8243
+ }
8244
+ /** Kill the current process and any orphan process on managed ports */
8245
+ stop() {
8246
+ if (this.process) {
8247
+ try {
8248
+ if (this.isDetached && this.process.pid) {
8249
+ process.kill(-this.process.pid, "SIGTERM");
8250
+ } else {
8251
+ this.process.kill("SIGTERM");
8252
+ }
8253
+ } catch {
8254
+ try {
8255
+ this.process.kill("SIGTERM");
8256
+ } catch {
8257
+ }
8258
+ }
8259
+ this.process = null;
8260
+ this.currentDir = null;
8261
+ this.isDetached = false;
8262
+ console.log(`[PreviewServer] Stopped`);
8263
+ }
8264
+ this.killPortHolder(STATIC_PORT);
8265
+ this.killPortHolder(COMMAND_PORT);
8266
+ }
8267
+ /** Kill whatever process is listening on the given port (best-effort). */
8268
+ killPortHolder(port) {
8269
+ try {
8270
+ const out = execSync4(`lsof -ti :${port}`, { encoding: "utf-8", timeout: 3e3 }).trim();
8271
+ if (out) {
8272
+ for (const pid of out.split("\n")) {
8273
+ const n = parseInt(pid, 10);
8274
+ if (n > 0) {
8275
+ try {
8276
+ process.kill(n, "SIGKILL");
8277
+ } catch {
8278
+ }
8279
+ }
8280
+ }
8281
+ console.log(`[PreviewServer] Killed orphan process(es) on port ${port}: ${out.replace(/\n/g, ", ")}`);
8282
+ }
8283
+ } catch {
8284
+ }
8285
+ }
8286
+ };
8287
+ var previewServer = new PreviewServer();
8288
+
8213
8289
  // ../../packages/orchestrator/src/index.ts
8214
8290
  function createOrchestrator(options) {
8215
8291
  return new Orchestrator(options);
@@ -8799,7 +8875,25 @@ function archiveProject(agents, team) {
8799
8875
  }
8800
8876
  }
8801
8877
  }
8878
+ let totalInputTokens = 0;
8879
+ let totalOutputTokens = 0;
8880
+ for (const e of projectEvents) {
8881
+ if (e.type === "TASK_DONE" && e.result?.tokenUsage) {
8882
+ totalInputTokens += e.result.tokenUsage.inputTokens ?? 0;
8883
+ totalOutputTokens += e.result.tokenUsage.outputTokens ?? 0;
8884
+ }
8885
+ }
8886
+ const tokenUsage = totalInputTokens > 0 || totalOutputTokens > 0 ? { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } : void 0;
8802
8887
  const id = `${projectStartedAt}-${projectName || "project"}`;
8888
+ const filePath = path12.join(PROJECTS_DIR, `${id}.json`);
8889
+ let existingRatings;
8890
+ try {
8891
+ if (existsSync11(filePath)) {
8892
+ const existing = JSON.parse(readFileSync7(filePath, "utf-8"));
8893
+ existingRatings = existing.ratings;
8894
+ }
8895
+ } catch {
8896
+ }
8803
8897
  const archive = {
8804
8898
  id,
8805
8899
  name: projectName || "Untitled Project",
@@ -8808,10 +8902,11 @@ function archiveProject(agents, team) {
8808
8902
  agents,
8809
8903
  team,
8810
8904
  events: projectEvents,
8811
- preview
8905
+ preview,
8906
+ tokenUsage,
8907
+ ratings: existingRatings
8812
8908
  };
8813
8909
  try {
8814
- const filePath = path12.join(PROJECTS_DIR, `${id}.json`);
8815
8910
  writeFileSync7(filePath, JSON.stringify(archive), "utf-8");
8816
8911
  console.log(`[TeamState] Archived project "${archive.name}" (${projectEvents.length} events) \u2192 ${filePath}`);
8817
8912
  return id;
@@ -8836,7 +8931,9 @@ function listProjects() {
8836
8931
  endedAt: raw.endedAt,
8837
8932
  agentNames: raw.agents.map((a) => a.name),
8838
8933
  eventCount: raw.events.length,
8839
- preview: raw.preview
8934
+ preview: raw.preview,
8935
+ tokenUsage: raw.tokenUsage,
8936
+ ratings: raw.ratings
8840
8937
  });
8841
8938
  } catch {
8842
8939
  }
@@ -8859,6 +8956,30 @@ function loadProject(id) {
8859
8956
  }
8860
8957
  return null;
8861
8958
  }
8959
+ function rateProject(ratings, projectId) {
8960
+ if (!existsSync11(PROJECTS_DIR)) return false;
8961
+ try {
8962
+ let filePath;
8963
+ if (projectId) {
8964
+ const safeId = projectId.replace(/[/\\]/g, "");
8965
+ filePath = path12.join(PROJECTS_DIR, `${safeId}.json`);
8966
+ if (!path12.resolve(filePath).startsWith(path12.resolve(PROJECTS_DIR))) return false;
8967
+ } else {
8968
+ const files = readdirSync2(PROJECTS_DIR).filter((f) => f.endsWith(".json")).sort().reverse();
8969
+ if (files.length === 0) return false;
8970
+ filePath = path12.join(PROJECTS_DIR, files[0]);
8971
+ }
8972
+ if (!existsSync11(filePath)) return false;
8973
+ const archive = JSON.parse(readFileSync7(filePath, "utf-8"));
8974
+ archive.ratings = ratings;
8975
+ writeFileSync7(filePath, JSON.stringify(archive), "utf-8");
8976
+ console.log(`[TeamState] Rated project "${archive.name}":`, ratings);
8977
+ return true;
8978
+ } catch (e) {
8979
+ console.log(`[TeamState] Failed to rate project: ${e}`);
8980
+ return false;
8981
+ }
8982
+ }
8862
8983
 
8863
8984
  // src/index.ts
8864
8985
  registerChannel(wsChannel);
@@ -8981,6 +9102,40 @@ function saveAgentDefs(agents) {
8981
9102
  }
8982
9103
  }
8983
9104
  var agentDefs = [];
9105
+ function detectDevServer(projectDir) {
9106
+ try {
9107
+ const pkgPath = path13.join(projectDir, "package.json");
9108
+ if (!existsSync12(pkgPath)) return null;
9109
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
9110
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
9111
+ if (allDeps["vite"]) return { cmd: "npx vite", port: 5173 };
9112
+ if (allDeps["webpack-dev-server"]) return { cmd: "npx webpack serve", port: 8080 };
9113
+ if (allDeps["parcel"]) return { cmd: "npx parcel index.html", port: 1234 };
9114
+ if (allDeps["next"]) return { cmd: "npx next dev", port: 3e3 };
9115
+ if (allDeps["react-scripts"]) return { cmd: "npx react-scripts start", port: 3e3 };
9116
+ return null;
9117
+ } catch {
9118
+ return null;
9119
+ }
9120
+ }
9121
+ function buildArchiveAgents() {
9122
+ return orc.getAllAgents().map((a) => ({
9123
+ agentId: a.agentId,
9124
+ name: a.name,
9125
+ role: a.role,
9126
+ personality: a.personality,
9127
+ backend: a.backend,
9128
+ palette: a.palette,
9129
+ teamId: a.teamId,
9130
+ isTeamLead: orc.isTeamLead(a.agentId)
9131
+ }));
9132
+ }
9133
+ function buildArchiveTeam() {
9134
+ const phases = orc.getAllTeamPhases();
9135
+ if (phases.length === 0) return null;
9136
+ const tp = phases[0];
9137
+ return { teamId: tp.teamId, leadAgentId: tp.leadAgentId, phase: tp.phase, projectDir: orc.getTeamProjectDir() };
9138
+ }
8984
9139
  function mapOrchestratorEvent(e) {
8985
9140
  switch (e.type) {
8986
9141
  case "task:started":
@@ -9013,8 +9168,13 @@ function mapOrchestratorEvent(e) {
9013
9168
  bufferEvent(phaseEvt);
9014
9169
  publishEvent(phaseEvt);
9015
9170
  persistTeamState();
9171
+ if (e.phase === "complete") {
9172
+ archiveProject(buildArchiveAgents(), buildArchiveTeam());
9173
+ }
9016
9174
  return null;
9017
9175
  }
9176
+ case "token:update":
9177
+ return { type: "TOKEN_UPDATE", agentId: e.agentId, inputTokens: e.inputTokens, outputTokens: e.outputTokens };
9018
9178
  // New events (worktree, retry) — log only, no wire protocol equivalent yet
9019
9179
  case "task:retrying":
9020
9180
  console.log(`[Retry] Agent ${e.agentId} retrying task ${e.taskId} (attempt ${e.attempt}/${e.maxRetries})`);
@@ -9125,18 +9285,28 @@ ${text}]`;
9125
9285
  break;
9126
9286
  }
9127
9287
  case "SERVE_PREVIEW": {
9128
- const cmdLooksValid = parsed.previewCmd && !/^[\[(].*[\])]$/.test(parsed.previewCmd) && !/^none$/i.test(parsed.previewCmd);
9288
+ const cleanCmd = parsed.previewCmd?.replace(/\*\*/g, "").replace(/`/g, "").replace(/^_+|_+$/g, "").trim();
9289
+ const cleanPath = parsed.filePath?.replace(/\*\*/g, "").replace(/`/g, "").replace(/^_+|_+$/g, "").trim();
9290
+ const cmdLooksValid = cleanCmd && !/^[\[(].*[\])]$/.test(cleanCmd) && !/^none$/i.test(cleanCmd);
9129
9291
  if (cmdLooksValid && parsed.previewPort) {
9130
9292
  const cwd = parsed.cwd ?? config.defaultWorkspace;
9131
- console.log(`[Gateway] SERVE_PREVIEW (cmd): "${parsed.previewCmd}" port=${parsed.previewPort} cwd=${cwd}`);
9132
- previewServer.runCommand(parsed.previewCmd, cwd, parsed.previewPort);
9293
+ console.log(`[Gateway] SERVE_PREVIEW (cmd): "${cleanCmd}" port=${parsed.previewPort} cwd=${cwd}`);
9294
+ previewServer.runCommand(cleanCmd, cwd, parsed.previewPort);
9133
9295
  } else if (cmdLooksValid) {
9134
9296
  const cwd = parsed.cwd ?? config.defaultWorkspace;
9135
- console.log(`[Gateway] SERVE_PREVIEW (launch): "${parsed.previewCmd}" cwd=${cwd}`);
9136
- previewServer.launchProcess(parsed.previewCmd, cwd);
9137
- } else if (parsed.filePath) {
9138
- console.log(`[Gateway] SERVE_PREVIEW (static): ${parsed.filePath}`);
9139
- previewServer.serve(parsed.filePath);
9297
+ console.log(`[Gateway] SERVE_PREVIEW (launch): "${cleanCmd}" cwd=${cwd}`);
9298
+ previewServer.launchProcess(cleanCmd, cwd);
9299
+ } else if (cleanPath) {
9300
+ const projectDir = parsed.cwd ?? (cleanPath.includes("/") ? path13.dirname(cleanPath) : config.defaultWorkspace);
9301
+ const detected = detectDevServer(projectDir);
9302
+ if (detected) {
9303
+ console.log(`[Gateway] SERVE_PREVIEW (auto-detected ${detected.cmd}): cwd=${projectDir}`);
9304
+ previewServer.runCommand(detected.cmd, projectDir, detected.port);
9305
+ publishEvent({ type: "PREVIEW_READY", url: "http://localhost:9101" });
9306
+ } else {
9307
+ console.log(`[Gateway] SERVE_PREVIEW (static): ${cleanPath}`);
9308
+ previewServer.serve(cleanPath);
9309
+ }
9140
9310
  }
9141
9311
  break;
9142
9312
  }
@@ -9261,23 +9431,7 @@ ${text}]`;
9261
9431
  case "END_PROJECT": {
9262
9432
  const agentId = parsed.agentId;
9263
9433
  console.log(`[Gateway] END_PROJECT: agent=${agentId}`);
9264
- const archiveAgents = orc.getAllAgents().map((a) => ({
9265
- agentId: a.agentId,
9266
- name: a.name,
9267
- role: a.role,
9268
- personality: a.personality,
9269
- backend: a.backend,
9270
- palette: a.palette,
9271
- teamId: a.teamId,
9272
- isTeamLead: orc.isTeamLead(a.agentId)
9273
- }));
9274
- let archiveTeam = null;
9275
- const archivePhases = orc.getAllTeamPhases();
9276
- if (archivePhases.length > 0) {
9277
- const tp = archivePhases[0];
9278
- archiveTeam = { teamId: tp.teamId, leadAgentId: tp.leadAgentId, phase: tp.phase, projectDir: orc.getTeamProjectDir() };
9279
- }
9280
- archiveProject(archiveAgents, archiveTeam);
9434
+ archiveProject(buildArchiveAgents(), buildArchiveTeam());
9281
9435
  resetProjectBuffer();
9282
9436
  orc.clearLeaderHistory(agentId);
9283
9437
  if (!orc.getAgent(agentId) && parsed.name) {
@@ -9411,6 +9565,11 @@ ${text}]`;
9411
9565
  });
9412
9566
  break;
9413
9567
  }
9568
+ case "RATE_PROJECT": {
9569
+ rateProject(parsed.ratings, parsed.projectId);
9570
+ recordProjectRatings(parsed.ratings);
9571
+ break;
9572
+ }
9414
9573
  case "LIST_PROJECTS": {
9415
9574
  const projects = listProjects();
9416
9575
  publishEvent({ type: "PROJECT_LIST", projects });
@@ -9541,6 +9700,7 @@ async function main() {
9541
9700
  orc.on("task:queued", forwardEvent);
9542
9701
  orc.on("worktree:created", forwardEvent);
9543
9702
  orc.on("worktree:merged", forwardEvent);
9703
+ orc.on("token:update", forwardEvent);
9544
9704
  orc.on("agent:created", forwardEvent);
9545
9705
  orc.on("agent:fired", forwardEvent);
9546
9706
  orc.on("task:result-returned", forwardEvent);