bit-office 1.0.4 → 1.1.1
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 +1757 -192
- package/dist/index.js.map +1 -1
- package/dist/web/404.html +2 -2
- package/dist/web/_next/static/chunks/368.cc1f599d57c41c84.js +1 -0
- package/dist/web/_next/static/chunks/app/office/page-3c20b887f5c4dc88.js +1 -0
- package/dist/web/_next/static/chunks/{webpack-2f6b11d148fba4ca.js → webpack-24a71fb73bc694fc.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/index.txt +1 -1
- package/dist/web/office.html +3 -3
- package/dist/web/office.txt +2 -2
- package/dist/web/pair.html +2 -2
- package/dist/web/pair.txt +1 -1
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/904.70db0cb7d0ebff73.js +0 -1
- package/dist/web/_next/static/chunks/app/office/page-40e6628ffa58377f.js +0 -1
- /package/dist/web/_next/static/{AB-Nx4k1uFFcprckH9Pym → pUJijMbzNc5YblbUZx7p7}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{AB-Nx4k1uFFcprckH9Pym → pUJijMbzNc5YblbUZx7p7}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -524,8 +524,8 @@ function getErrorMap() {
|
|
|
524
524
|
|
|
525
525
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
526
526
|
var makeIssue = (params) => {
|
|
527
|
-
const { data, path:
|
|
528
|
-
const fullPath = [...
|
|
527
|
+
const { data, path: path10, errorMaps, issueData } = params;
|
|
528
|
+
const fullPath = [...path10, ...issueData.path || []];
|
|
529
529
|
const fullIssue = {
|
|
530
530
|
...issueData,
|
|
531
531
|
path: fullPath
|
|
@@ -641,11 +641,11 @@ var errorUtil;
|
|
|
641
641
|
|
|
642
642
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
643
643
|
var ParseInputLazyPath = class {
|
|
644
|
-
constructor(parent, value,
|
|
644
|
+
constructor(parent, value, path10, key) {
|
|
645
645
|
this._cachedPath = [];
|
|
646
646
|
this.parent = parent;
|
|
647
647
|
this.data = value;
|
|
648
|
-
this._path =
|
|
648
|
+
this._path = path10;
|
|
649
649
|
this._key = key;
|
|
650
650
|
}
|
|
651
651
|
get path() {
|
|
@@ -4097,6 +4097,7 @@ var AgentStatusEnum = external_exports.enum([
|
|
|
4097
4097
|
]);
|
|
4098
4098
|
var RiskLevelEnum = external_exports.enum(["low", "med", "high"]);
|
|
4099
4099
|
var DecisionEnum = external_exports.enum(["yes", "no"]);
|
|
4100
|
+
var TeamPhaseEnum = external_exports.enum(["create", "design", "execute", "complete"]);
|
|
4100
4101
|
|
|
4101
4102
|
// ../../packages/shared/src/commands.ts
|
|
4102
4103
|
var RunTaskCommand = external_exports.object({
|
|
@@ -4142,13 +4143,16 @@ var OpenFileCommand = external_exports.object({
|
|
|
4142
4143
|
});
|
|
4143
4144
|
var CreateTeamCommand = external_exports.object({
|
|
4144
4145
|
type: external_exports.literal("CREATE_TEAM"),
|
|
4145
|
-
|
|
4146
|
-
|
|
4146
|
+
leadId: external_exports.string(),
|
|
4147
|
+
memberIds: external_exports.array(external_exports.string()),
|
|
4147
4148
|
backends: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
4148
4149
|
});
|
|
4149
4150
|
var ServePreviewCommand = external_exports.object({
|
|
4150
4151
|
type: external_exports.literal("SERVE_PREVIEW"),
|
|
4151
|
-
filePath: external_exports.string()
|
|
4152
|
+
filePath: external_exports.string().optional(),
|
|
4153
|
+
previewCmd: external_exports.string().optional(),
|
|
4154
|
+
previewPort: external_exports.number().optional(),
|
|
4155
|
+
cwd: external_exports.string().optional()
|
|
4152
4156
|
});
|
|
4153
4157
|
var StopTeamCommand = external_exports.object({
|
|
4154
4158
|
type: external_exports.literal("STOP_TEAM")
|
|
@@ -4156,6 +4160,35 @@ var StopTeamCommand = external_exports.object({
|
|
|
4156
4160
|
var FireTeamCommand = external_exports.object({
|
|
4157
4161
|
type: external_exports.literal("FIRE_TEAM")
|
|
4158
4162
|
});
|
|
4163
|
+
var KillExternalCommand = external_exports.object({
|
|
4164
|
+
type: external_exports.literal("KILL_EXTERNAL"),
|
|
4165
|
+
agentId: external_exports.string()
|
|
4166
|
+
});
|
|
4167
|
+
var ApprovePlanCommand = external_exports.object({
|
|
4168
|
+
type: external_exports.literal("APPROVE_PLAN"),
|
|
4169
|
+
agentId: external_exports.string()
|
|
4170
|
+
});
|
|
4171
|
+
var EndProjectCommand = external_exports.object({
|
|
4172
|
+
type: external_exports.literal("END_PROJECT"),
|
|
4173
|
+
agentId: external_exports.string()
|
|
4174
|
+
});
|
|
4175
|
+
var SaveAgentDefCommand = external_exports.object({
|
|
4176
|
+
type: external_exports.literal("SAVE_AGENT_DEF"),
|
|
4177
|
+
agent: external_exports.object({
|
|
4178
|
+
id: external_exports.string(),
|
|
4179
|
+
name: external_exports.string(),
|
|
4180
|
+
role: external_exports.string(),
|
|
4181
|
+
skills: external_exports.string(),
|
|
4182
|
+
personality: external_exports.string(),
|
|
4183
|
+
palette: external_exports.number(),
|
|
4184
|
+
isBuiltin: external_exports.boolean(),
|
|
4185
|
+
teamRole: external_exports.enum(["dev", "reviewer", "leader"])
|
|
4186
|
+
})
|
|
4187
|
+
});
|
|
4188
|
+
var DeleteAgentDefCommand = external_exports.object({
|
|
4189
|
+
type: external_exports.literal("DELETE_AGENT_DEF"),
|
|
4190
|
+
agentDefId: external_exports.string()
|
|
4191
|
+
});
|
|
4159
4192
|
var CommandSchema = external_exports.discriminatedUnion("type", [
|
|
4160
4193
|
RunTaskCommand,
|
|
4161
4194
|
ApprovalDecisionCommand,
|
|
@@ -4167,7 +4200,12 @@ var CommandSchema = external_exports.discriminatedUnion("type", [
|
|
|
4167
4200
|
CreateTeamCommand,
|
|
4168
4201
|
ServePreviewCommand,
|
|
4169
4202
|
StopTeamCommand,
|
|
4170
|
-
FireTeamCommand
|
|
4203
|
+
FireTeamCommand,
|
|
4204
|
+
KillExternalCommand,
|
|
4205
|
+
ApprovePlanCommand,
|
|
4206
|
+
EndProjectCommand,
|
|
4207
|
+
SaveAgentDefCommand,
|
|
4208
|
+
DeleteAgentDefCommand
|
|
4171
4209
|
]);
|
|
4172
4210
|
|
|
4173
4211
|
// ../../packages/shared/src/events.ts
|
|
@@ -4199,6 +4237,10 @@ var ApprovalNeededEvent = external_exports.object({
|
|
|
4199
4237
|
summary: external_exports.string(),
|
|
4200
4238
|
riskLevel: RiskLevelEnum
|
|
4201
4239
|
});
|
|
4240
|
+
var TokenUsage = external_exports.object({
|
|
4241
|
+
inputTokens: external_exports.number(),
|
|
4242
|
+
outputTokens: external_exports.number()
|
|
4243
|
+
});
|
|
4202
4244
|
var TaskResultPayload = external_exports.object({
|
|
4203
4245
|
summary: external_exports.string(),
|
|
4204
4246
|
fullOutput: external_exports.string().optional(),
|
|
@@ -4207,7 +4249,12 @@ var TaskResultPayload = external_exports.object({
|
|
|
4207
4249
|
testResult: external_exports.enum(["passed", "failed", "unknown"]),
|
|
4208
4250
|
nextSuggestion: external_exports.string().optional(),
|
|
4209
4251
|
previewUrl: external_exports.string().optional(),
|
|
4210
|
-
previewPath: external_exports.string().optional()
|
|
4252
|
+
previewPath: external_exports.string().optional(),
|
|
4253
|
+
entryFile: external_exports.string().optional(),
|
|
4254
|
+
projectDir: external_exports.string().optional(),
|
|
4255
|
+
previewCmd: external_exports.string().optional(),
|
|
4256
|
+
previewPort: external_exports.number().optional(),
|
|
4257
|
+
tokenUsage: TokenUsage.optional()
|
|
4211
4258
|
});
|
|
4212
4259
|
var TaskDoneEvent = external_exports.object({
|
|
4213
4260
|
type: external_exports.literal("TASK_DONE"),
|
|
@@ -4238,7 +4285,11 @@ var AgentCreatedEvent = external_exports.object({
|
|
|
4238
4285
|
personality: external_exports.string().optional(),
|
|
4239
4286
|
backend: external_exports.string().optional(),
|
|
4240
4287
|
isTeamLead: external_exports.boolean().optional(),
|
|
4241
|
-
teamId: external_exports.string().optional()
|
|
4288
|
+
teamId: external_exports.string().optional(),
|
|
4289
|
+
isExternal: external_exports.boolean().optional(),
|
|
4290
|
+
pid: external_exports.number().optional(),
|
|
4291
|
+
cwd: external_exports.string().optional(),
|
|
4292
|
+
startedAt: external_exports.number().optional()
|
|
4242
4293
|
});
|
|
4243
4294
|
var AgentFiredEvent = external_exports.object({
|
|
4244
4295
|
type: external_exports.literal("AGENT_FIRED"),
|
|
@@ -4268,6 +4319,31 @@ var TaskQueuedEvent = external_exports.object({
|
|
|
4268
4319
|
prompt: external_exports.string(),
|
|
4269
4320
|
position: external_exports.number()
|
|
4270
4321
|
});
|
|
4322
|
+
var TokenUpdateEvent = external_exports.object({
|
|
4323
|
+
type: external_exports.literal("TOKEN_UPDATE"),
|
|
4324
|
+
agentId: external_exports.string(),
|
|
4325
|
+
inputTokens: external_exports.number(),
|
|
4326
|
+
outputTokens: external_exports.number()
|
|
4327
|
+
});
|
|
4328
|
+
var TeamPhaseEvent = external_exports.object({
|
|
4329
|
+
type: external_exports.literal("TEAM_PHASE"),
|
|
4330
|
+
teamId: external_exports.string(),
|
|
4331
|
+
phase: TeamPhaseEnum,
|
|
4332
|
+
leadAgentId: external_exports.string()
|
|
4333
|
+
});
|
|
4334
|
+
var AgentDefsEvent = external_exports.object({
|
|
4335
|
+
type: external_exports.literal("AGENT_DEFS"),
|
|
4336
|
+
agents: external_exports.array(external_exports.object({
|
|
4337
|
+
id: external_exports.string(),
|
|
4338
|
+
name: external_exports.string(),
|
|
4339
|
+
role: external_exports.string(),
|
|
4340
|
+
skills: external_exports.string(),
|
|
4341
|
+
personality: external_exports.string(),
|
|
4342
|
+
palette: external_exports.number(),
|
|
4343
|
+
isBuiltin: external_exports.boolean(),
|
|
4344
|
+
teamRole: external_exports.enum(["dev", "reviewer", "leader"])
|
|
4345
|
+
}))
|
|
4346
|
+
});
|
|
4271
4347
|
var GatewayEventSchema = external_exports.discriminatedUnion("type", [
|
|
4272
4348
|
AgentStatusEvent,
|
|
4273
4349
|
TaskStartedEvent,
|
|
@@ -4280,7 +4356,10 @@ var GatewayEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
4280
4356
|
AgentFiredEvent,
|
|
4281
4357
|
TaskResultReturnedEvent,
|
|
4282
4358
|
TeamChatEvent,
|
|
4283
|
-
TaskQueuedEvent
|
|
4359
|
+
TaskQueuedEvent,
|
|
4360
|
+
TokenUpdateEvent,
|
|
4361
|
+
TeamPhaseEvent,
|
|
4362
|
+
AgentDefsEvent
|
|
4284
4363
|
]);
|
|
4285
4364
|
|
|
4286
4365
|
// ../../packages/shared/src/presets.ts
|
|
@@ -4289,8 +4368,19 @@ var AGENT_PRESETS = [
|
|
|
4289
4368
|
{ palette: 1, name: "Mia", role: "Backend Dev", description: "APIs, database, server logic", personality: "You speak formally, professionally, in an organized and concise manner." },
|
|
4290
4369
|
{ palette: 2, name: "Leo", role: "Fullstack Dev", description: "End-to-end, frontend + backend", personality: "You are aggressive, action-first, always pursuing speed and efficiency." },
|
|
4291
4370
|
{ palette: 3, name: "Sophie", role: "Code Reviewer", description: "Review PRs, find bugs, quality", personality: "You teach patiently, explain the reasoning, and guide like a mentor." },
|
|
4292
|
-
{ palette: 4, name: "
|
|
4293
|
-
{ palette: 5, name: "Marcus", role: "
|
|
4371
|
+
{ palette: 4, name: "Kai", role: "Game Dev", description: "Web games, PixiJS/Three.js/Canvas", personality: "You are enthusiastic, creative, and obsessive about game feel. You care deeply about smooth animations, tight controls, and the little details that make a game satisfying to play." },
|
|
4372
|
+
{ palette: 5, name: "Marcus", role: "Team Lead", description: "Creative direction, planning, delegation", personality: "You have strong product intuition and communicate with clarity and vision. You focus on the big picture, make decisive creative calls, and keep the team aligned.", isLeader: true }
|
|
4373
|
+
];
|
|
4374
|
+
var LEADER_PRESET_INDEX = AGENT_PRESETS.findIndex((p) => p.isLeader);
|
|
4375
|
+
|
|
4376
|
+
// ../../packages/shared/src/agent-defs.ts
|
|
4377
|
+
var DEFAULT_AGENT_DEFS = [
|
|
4378
|
+
{ id: "alex", name: "Alex", role: "Frontend Dev", skills: "UI components, React/Next.js/CSS", personality: "You speak in a friendly, casual, encouraging, and natural tone.", palette: 0, isBuiltin: true, teamRole: "dev" },
|
|
4379
|
+
{ id: "mia", name: "Mia", role: "Backend Dev", skills: "APIs, database, server logic", personality: "You speak formally, professionally, in an organized and concise manner.", palette: 1, isBuiltin: true, teamRole: "dev" },
|
|
4380
|
+
{ id: "leo", name: "Leo", role: "Fullstack Dev", skills: "End-to-end, frontend + backend", personality: "You are aggressive, action-first, always pursuing speed and efficiency.", palette: 2, isBuiltin: true, teamRole: "dev" },
|
|
4381
|
+
{ id: "sophie", name: "Sophie", role: "Code Reviewer", skills: "Review PRs, find bugs, quality", personality: "You teach patiently, explain the reasoning, and guide like a mentor.", palette: 3, isBuiltin: true, teamRole: "reviewer" },
|
|
4382
|
+
{ id: "kai", name: "Kai", role: "Game Dev", skills: "Web games, PixiJS/Three.js/Canvas", personality: "You are enthusiastic, creative, and obsessive about game feel. You care deeply about smooth animations, tight controls, and the little details that make a game satisfying to play.", palette: 4, isBuiltin: true, teamRole: "dev" },
|
|
4383
|
+
{ id: "marcus", name: "Marcus", role: "Team Lead", skills: "Creative direction, planning, delegation", personality: "You have strong product intuition and communicate with clarity and vision. You focus on the big picture, make decisive creative calls, and keep the team aligned.", palette: 5, isBuiltin: true, teamRole: "leader" }
|
|
4294
4384
|
];
|
|
4295
4385
|
|
|
4296
4386
|
// src/config.ts
|
|
@@ -4356,7 +4446,12 @@ function buildConfig() {
|
|
|
4356
4446
|
const saved = loadSavedConfig();
|
|
4357
4447
|
return {
|
|
4358
4448
|
machineId: getOrCreateMachineId(),
|
|
4359
|
-
defaultWorkspace:
|
|
4449
|
+
defaultWorkspace: (() => {
|
|
4450
|
+
const envWs = process.env.WORKSPACE;
|
|
4451
|
+
if (envWs && existsSync(envWs)) return envWs;
|
|
4452
|
+
if (envWs) console.log(`[Config] WORKSPACE="${envWs}" does not exist, using default`);
|
|
4453
|
+
return resolveDefaultWorkspace();
|
|
4454
|
+
})(),
|
|
4360
4455
|
wsPort: 9090,
|
|
4361
4456
|
ablyApiKey: process.env.ABLY_API_KEY || saved.ablyApiKey || void 0,
|
|
4362
4457
|
webDir: resolveWebDir(),
|
|
@@ -4647,8 +4742,8 @@ var PRESETS = [
|
|
|
4647
4742
|
{ name: "Mia", role: "Backend Dev", palette: 1, personality: "You speak formally, professionally, in an organized and concise manner." },
|
|
4648
4743
|
{ name: "Leo", role: "Fullstack Dev", palette: 2, personality: "You are aggressive, action-first, always pursuing speed and efficiency." },
|
|
4649
4744
|
{ name: "Sophie", role: "Code Reviewer", palette: 3, personality: "You teach patiently, explain the reasoning, and guide like a mentor." },
|
|
4650
|
-
{ name: "
|
|
4651
|
-
{ name: "Marcus", role: "
|
|
4745
|
+
{ name: "Kai", role: "Game Dev", palette: 4, personality: "You are enthusiastic, creative, and obsessive about game feel." },
|
|
4746
|
+
{ name: "Marcus", role: "Team Lead", palette: 5, personality: "You have strong product intuition and communicate with clarity and vision." }
|
|
4652
4747
|
];
|
|
4653
4748
|
var botAgents = [];
|
|
4654
4749
|
function formatEvent(event, agentId) {
|
|
@@ -4778,6 +4873,46 @@ import { createInterface } from "readline";
|
|
|
4778
4873
|
|
|
4779
4874
|
// src/backends.ts
|
|
4780
4875
|
import { execSync } from "child_process";
|
|
4876
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
4877
|
+
import { homedir as homedir2 } from "os";
|
|
4878
|
+
import path from "path";
|
|
4879
|
+
var isRoot = process.getuid?.() === 0;
|
|
4880
|
+
function ensureClaudeSettingsForRoot() {
|
|
4881
|
+
if (!isRoot) return;
|
|
4882
|
+
const claudeDir = path.join(homedir2(), ".claude");
|
|
4883
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
4884
|
+
const requiredAllow = [
|
|
4885
|
+
"Bash",
|
|
4886
|
+
"Read",
|
|
4887
|
+
"Write",
|
|
4888
|
+
"Edit",
|
|
4889
|
+
"MultiEdit",
|
|
4890
|
+
"Glob",
|
|
4891
|
+
"Grep",
|
|
4892
|
+
"WebFetch",
|
|
4893
|
+
"TodoRead",
|
|
4894
|
+
"TodoWrite",
|
|
4895
|
+
"Agent"
|
|
4896
|
+
];
|
|
4897
|
+
try {
|
|
4898
|
+
let settings = {};
|
|
4899
|
+
if (existsSync2(settingsPath)) {
|
|
4900
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
4901
|
+
}
|
|
4902
|
+
settings.defaultMode = "bypassPermissions";
|
|
4903
|
+
const perms = settings.permissions ?? {};
|
|
4904
|
+
const existing = Array.isArray(perms.allow) ? perms.allow : [];
|
|
4905
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...requiredAllow])];
|
|
4906
|
+
perms.allow = merged;
|
|
4907
|
+
settings.permissions = perms;
|
|
4908
|
+
if (!existsSync2(claudeDir)) mkdirSync2(claudeDir, { recursive: true });
|
|
4909
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
4910
|
+
console.log("[backends] Running as root \u2014 configured Claude Code settings.json to allow all permissions");
|
|
4911
|
+
} catch (err) {
|
|
4912
|
+
console.warn("[backends] Failed to configure Claude settings for root:", err);
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
ensureClaudeSettingsForRoot();
|
|
4781
4916
|
var backends = [
|
|
4782
4917
|
{
|
|
4783
4918
|
id: "claude",
|
|
@@ -4785,7 +4920,8 @@ var backends = [
|
|
|
4785
4920
|
command: "claude",
|
|
4786
4921
|
supportsStdin: true,
|
|
4787
4922
|
buildArgs(prompt, opts) {
|
|
4788
|
-
const args = ["-p", prompt, "--output-format", "stream-json", "--verbose"
|
|
4923
|
+
const args = ["-p", prompt, "--output-format", "stream-json", "--verbose"];
|
|
4924
|
+
if (!isRoot) args.push("--dangerously-skip-permissions");
|
|
4789
4925
|
if (!opts.skipResume) {
|
|
4790
4926
|
if (opts.resumeSessionId) {
|
|
4791
4927
|
args.push("--resume", opts.resumeSessionId);
|
|
@@ -4804,7 +4940,7 @@ var backends = [
|
|
|
4804
4940
|
name: "Codex CLI",
|
|
4805
4941
|
command: "codex",
|
|
4806
4942
|
buildArgs(prompt, opts) {
|
|
4807
|
-
if (opts.fullAccess) {
|
|
4943
|
+
if (opts.fullAccess && !isRoot) {
|
|
4808
4944
|
return ["exec", prompt, "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check"];
|
|
4809
4945
|
}
|
|
4810
4946
|
return ["exec", prompt, "--full-auto", "--skip-git-repo-check"];
|
|
@@ -4922,53 +5058,118 @@ async function runSetup() {
|
|
|
4922
5058
|
|
|
4923
5059
|
// ../../packages/orchestrator/src/orchestrator.ts
|
|
4924
5060
|
import { EventEmitter } from "events";
|
|
5061
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5062
|
+
import path7 from "path";
|
|
4925
5063
|
import { nanoid as nanoid4 } from "nanoid";
|
|
4926
5064
|
|
|
4927
5065
|
// ../../packages/orchestrator/src/agent-session.ts
|
|
4928
5066
|
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
4929
|
-
import
|
|
4930
|
-
import { existsSync as
|
|
4931
|
-
import { homedir as
|
|
5067
|
+
import path3 from "path";
|
|
5068
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5069
|
+
import { homedir as homedir3 } from "os";
|
|
4932
5070
|
|
|
4933
5071
|
// ../../packages/orchestrator/src/preview-server.ts
|
|
4934
5072
|
import { spawn } from "child_process";
|
|
4935
|
-
import
|
|
4936
|
-
var
|
|
5073
|
+
import path2 from "path";
|
|
5074
|
+
var STATIC_PORT = 9100;
|
|
4937
5075
|
var PreviewServer = class {
|
|
4938
5076
|
process = null;
|
|
4939
5077
|
currentDir = null;
|
|
5078
|
+
isDetached = false;
|
|
4940
5079
|
/**
|
|
4941
|
-
* Serve a directory on a fixed port.
|
|
5080
|
+
* Mode 1: Serve a static file directory on a fixed port.
|
|
4942
5081
|
* Returns the preview URL for the given file.
|
|
4943
5082
|
*/
|
|
4944
5083
|
serve(filePath) {
|
|
4945
|
-
const dir =
|
|
4946
|
-
const fileName =
|
|
5084
|
+
const dir = path2.dirname(filePath);
|
|
5085
|
+
const fileName = path2.basename(filePath);
|
|
4947
5086
|
this.stop();
|
|
4948
5087
|
try {
|
|
4949
|
-
this.process = spawn("npx", ["serve", dir, "-l", String(
|
|
5088
|
+
this.process = spawn("npx", ["serve", dir, "-l", String(STATIC_PORT), "--no-clipboard"], {
|
|
4950
5089
|
stdio: "ignore",
|
|
4951
5090
|
detached: true
|
|
4952
5091
|
});
|
|
4953
5092
|
this.process.unref();
|
|
4954
5093
|
this.currentDir = dir;
|
|
4955
|
-
|
|
4956
|
-
|
|
5094
|
+
this.isDetached = true;
|
|
5095
|
+
const url = `http://localhost:${STATIC_PORT}/${fileName}`;
|
|
5096
|
+
console.log(`[PreviewServer] Serving ${dir} on port ${STATIC_PORT}`);
|
|
5097
|
+
return url;
|
|
5098
|
+
} catch (e) {
|
|
5099
|
+
console.log(`[PreviewServer] Failed to start static serve: ${e}`);
|
|
5100
|
+
return void 0;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
/**
|
|
5104
|
+
* Mode 2: Run a command (e.g. "python app.py") and use the specified port.
|
|
5105
|
+
* The command is expected to start a server on the given port.
|
|
5106
|
+
* Returns the preview URL.
|
|
5107
|
+
*/
|
|
5108
|
+
runCommand(cmd, cwd, port) {
|
|
5109
|
+
this.stop();
|
|
5110
|
+
try {
|
|
5111
|
+
this.process = spawn(cmd, {
|
|
5112
|
+
shell: true,
|
|
5113
|
+
cwd,
|
|
5114
|
+
stdio: "ignore",
|
|
5115
|
+
detached: true
|
|
5116
|
+
});
|
|
5117
|
+
this.process.unref();
|
|
5118
|
+
this.currentDir = cwd;
|
|
5119
|
+
this.isDetached = true;
|
|
5120
|
+
const url = `http://localhost:${port}`;
|
|
5121
|
+
console.log(`[PreviewServer] Running "${cmd}" in ${cwd}, preview at port ${port}`);
|
|
4957
5122
|
return url;
|
|
4958
5123
|
} catch (e) {
|
|
4959
|
-
console.log(`[PreviewServer] Failed to
|
|
5124
|
+
console.log(`[PreviewServer] Failed to run command: ${e}`);
|
|
4960
5125
|
return void 0;
|
|
4961
5126
|
}
|
|
4962
5127
|
}
|
|
4963
|
-
/**
|
|
5128
|
+
/**
|
|
5129
|
+
* Mode 3: Launch a desktop/CLI process (no web preview URL).
|
|
5130
|
+
* Used for Pygame, Tkinter, Electron, terminal apps, etc.
|
|
5131
|
+
* NOT detached — GUI apps need the login session to access WindowServer (macOS).
|
|
5132
|
+
*/
|
|
5133
|
+
launchProcess(cmd, cwd) {
|
|
5134
|
+
this.stop();
|
|
5135
|
+
try {
|
|
5136
|
+
this.process = spawn(cmd, {
|
|
5137
|
+
shell: true,
|
|
5138
|
+
cwd,
|
|
5139
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5140
|
+
});
|
|
5141
|
+
this.currentDir = cwd;
|
|
5142
|
+
this.isDetached = false;
|
|
5143
|
+
console.log(`[PreviewServer] Launched "${cmd}" in ${cwd} (pid=${this.process.pid})`);
|
|
5144
|
+
this.process.stderr?.on("data", (data) => {
|
|
5145
|
+
const msg = data.toString().trim();
|
|
5146
|
+
if (msg) console.log(`[PreviewServer] stderr: ${msg.slice(0, 200)}`);
|
|
5147
|
+
});
|
|
5148
|
+
this.process.on("exit", (code) => {
|
|
5149
|
+
console.log(`[PreviewServer] Process exited with code ${code}`);
|
|
5150
|
+
});
|
|
5151
|
+
} catch (e) {
|
|
5152
|
+
console.log(`[PreviewServer] Failed to launch process: ${e}`);
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
/** Kill the current process */
|
|
4964
5156
|
stop() {
|
|
4965
5157
|
if (this.process) {
|
|
4966
5158
|
try {
|
|
4967
|
-
this.process.
|
|
5159
|
+
if (this.isDetached && this.process.pid) {
|
|
5160
|
+
process.kill(-this.process.pid, "SIGTERM");
|
|
5161
|
+
} else {
|
|
5162
|
+
this.process.kill("SIGTERM");
|
|
5163
|
+
}
|
|
4968
5164
|
} catch {
|
|
5165
|
+
try {
|
|
5166
|
+
this.process.kill("SIGTERM");
|
|
5167
|
+
} catch {
|
|
5168
|
+
}
|
|
4969
5169
|
}
|
|
4970
5170
|
this.process = null;
|
|
4971
5171
|
this.currentDir = null;
|
|
5172
|
+
this.isDetached = false;
|
|
4972
5173
|
console.log(`[PreviewServer] Stopped`);
|
|
4973
5174
|
}
|
|
4974
5175
|
}
|
|
@@ -4977,24 +5178,24 @@ var previewServer = new PreviewServer();
|
|
|
4977
5178
|
|
|
4978
5179
|
// ../../packages/orchestrator/src/agent-session.ts
|
|
4979
5180
|
import { nanoid as nanoid2 } from "nanoid";
|
|
4980
|
-
var SESSION_FILE =
|
|
5181
|
+
var SESSION_FILE = path3.join(homedir3(), ".bit-office", "agent-sessions.json");
|
|
4981
5182
|
function loadSessionMap() {
|
|
4982
5183
|
try {
|
|
4983
|
-
if (
|
|
5184
|
+
if (existsSync3(SESSION_FILE)) return JSON.parse(readFileSync3(SESSION_FILE, "utf-8"));
|
|
4984
5185
|
} catch {
|
|
4985
5186
|
}
|
|
4986
5187
|
return {};
|
|
4987
5188
|
}
|
|
4988
5189
|
function saveSessionId(agentId, sessionId) {
|
|
4989
|
-
const dir =
|
|
4990
|
-
if (!
|
|
5190
|
+
const dir = path3.dirname(SESSION_FILE);
|
|
5191
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
4991
5192
|
const map = loadSessionMap();
|
|
4992
5193
|
if (sessionId) {
|
|
4993
5194
|
map[agentId] = sessionId;
|
|
4994
5195
|
} else {
|
|
4995
5196
|
delete map[agentId];
|
|
4996
5197
|
}
|
|
4997
|
-
|
|
5198
|
+
writeFileSync3(SESSION_FILE, JSON.stringify(map), "utf-8");
|
|
4998
5199
|
}
|
|
4999
5200
|
var AgentSession = class {
|
|
5000
5201
|
agentId;
|
|
@@ -5017,6 +5218,8 @@ var AgentSession = class {
|
|
|
5017
5218
|
sandboxMode;
|
|
5018
5219
|
stdoutBuffer = "";
|
|
5019
5220
|
stderrBuffer = "";
|
|
5221
|
+
taskInputTokens = 0;
|
|
5222
|
+
taskOutputTokens = 0;
|
|
5020
5223
|
hasHistory;
|
|
5021
5224
|
sessionId;
|
|
5022
5225
|
taskQueue = [];
|
|
@@ -5024,6 +5227,8 @@ var AgentSession = class {
|
|
|
5024
5227
|
_renderPrompt;
|
|
5025
5228
|
timedOut = false;
|
|
5026
5229
|
_isTeamLead;
|
|
5230
|
+
/** Whether this leader has already been through execute phase at least once */
|
|
5231
|
+
_hasExecuted = false;
|
|
5027
5232
|
_lastResult = null;
|
|
5028
5233
|
/** Original user-facing task prompt (for leader state-summary mode) */
|
|
5029
5234
|
originalTask = null;
|
|
@@ -5041,13 +5246,28 @@ var AgentSession = class {
|
|
|
5041
5246
|
return this._lastResult;
|
|
5042
5247
|
}
|
|
5043
5248
|
_lastResultText = null;
|
|
5249
|
+
/** Full output from the last completed task (for plan capture). */
|
|
5250
|
+
_lastFullOutput = null;
|
|
5251
|
+
get lastFullOutput() {
|
|
5252
|
+
return this._lastFullOutput;
|
|
5253
|
+
}
|
|
5044
5254
|
set isTeamLead(v) {
|
|
5045
5255
|
this._isTeamLead = v;
|
|
5046
5256
|
}
|
|
5257
|
+
/** Current phase override for team collaboration phases */
|
|
5258
|
+
currentPhase = null;
|
|
5047
5259
|
/** Current working directory of the running task (used by worktree logic) */
|
|
5048
5260
|
get currentWorkingDir() {
|
|
5049
5261
|
return this.currentCwd;
|
|
5050
5262
|
}
|
|
5263
|
+
/** The configured workspace root directory */
|
|
5264
|
+
get workspaceDir() {
|
|
5265
|
+
return this.workspace;
|
|
5266
|
+
}
|
|
5267
|
+
/** PID of the running child process (null if not running) */
|
|
5268
|
+
get pid() {
|
|
5269
|
+
return this.process?.pid ?? null;
|
|
5270
|
+
}
|
|
5051
5271
|
/** Worktree path if task is running in one (set externally by orchestrator) */
|
|
5052
5272
|
worktreePath = null;
|
|
5053
5273
|
worktreeBranch = null;
|
|
@@ -5067,7 +5287,7 @@ var AgentSession = class {
|
|
|
5067
5287
|
this.onEvent = opts.onEvent;
|
|
5068
5288
|
this._renderPrompt = opts.renderPrompt;
|
|
5069
5289
|
}
|
|
5070
|
-
async runTask(taskId, prompt, repoPath, teamContext, isUserInitiated = false) {
|
|
5290
|
+
async runTask(taskId, prompt, repoPath, teamContext, isUserInitiated = false, phaseOverride) {
|
|
5071
5291
|
if (this._userCancelled && !isUserInitiated) {
|
|
5072
5292
|
console.log(`[Agent ${this.name}] Ignoring internal task restart \u2014 agent was cancelled by user`);
|
|
5073
5293
|
return;
|
|
@@ -5077,7 +5297,7 @@ var AgentSession = class {
|
|
|
5077
5297
|
}
|
|
5078
5298
|
if (this.process) {
|
|
5079
5299
|
const position = this.taskQueue.length + 1;
|
|
5080
|
-
this.taskQueue.push({ taskId, prompt, repoPath, teamContext });
|
|
5300
|
+
this.taskQueue.push({ taskId, prompt, repoPath, teamContext, phaseOverride });
|
|
5081
5301
|
this.onEvent({
|
|
5082
5302
|
type: "task:queued",
|
|
5083
5303
|
agentId: this.agentId,
|
|
@@ -5092,10 +5312,13 @@ var AgentSession = class {
|
|
|
5092
5312
|
this.idleTimer = null;
|
|
5093
5313
|
}
|
|
5094
5314
|
this.currentTaskId = taskId;
|
|
5315
|
+
this.currentPhase = phaseOverride ?? null;
|
|
5095
5316
|
const cwd = repoPath ?? this.workspace;
|
|
5096
5317
|
this.currentCwd = cwd;
|
|
5097
5318
|
this.stdoutBuffer = "";
|
|
5098
5319
|
this.stderrBuffer = "";
|
|
5320
|
+
this.taskInputTokens = 0;
|
|
5321
|
+
this.taskOutputTokens = 0;
|
|
5099
5322
|
this.onEvent({
|
|
5100
5323
|
type: "task:started",
|
|
5101
5324
|
agentId: this.agentId,
|
|
@@ -5108,19 +5331,30 @@ var AgentSession = class {
|
|
|
5108
5331
|
for (const key of this.backend.deleteEnv ?? []) {
|
|
5109
5332
|
delete cleanEnv[key];
|
|
5110
5333
|
}
|
|
5334
|
+
const rawOriginalTask = this._isTeamLead ? this.originalTask ?? prompt : "";
|
|
5335
|
+
const originalTask = rawOriginalTask.length > 1500 ? rawOriginalTask.slice(0, 1500) + "\n...(truncated)" : rawOriginalTask;
|
|
5111
5336
|
const templateVars = {
|
|
5112
5337
|
name: this.name,
|
|
5113
5338
|
role: this._isTeamLead ? "Team Lead" : this.role,
|
|
5114
5339
|
personality: this.personality ? `${this.personality}` : "",
|
|
5115
5340
|
teamRoster: teamContext ?? "",
|
|
5116
|
-
originalTask
|
|
5117
|
-
prompt
|
|
5341
|
+
originalTask,
|
|
5342
|
+
prompt,
|
|
5343
|
+
soloHint: this.teamId ? "" : `- You are a SOLO developer. Do NOT delegate, assign tasks, or mention other team members. Do ALL the work yourself.
|
|
5344
|
+
- 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.`
|
|
5118
5345
|
};
|
|
5346
|
+
const isFirstExecute = this._isTeamLead && phaseOverride === "execute" && !this._hasExecuted;
|
|
5119
5347
|
let fullPrompt;
|
|
5120
|
-
if (this._isTeamLead) {
|
|
5121
|
-
|
|
5348
|
+
if (this._isTeamLead && phaseOverride && ["create", "design", "complete"].includes(phaseOverride)) {
|
|
5349
|
+
const templateName = this.hasHistory ? `leader-${phaseOverride}-continue` : `leader-${phaseOverride}`;
|
|
5350
|
+
fullPrompt = this._renderPrompt(templateName, templateVars);
|
|
5351
|
+
} else if (this._isTeamLead) {
|
|
5352
|
+
const useInitial = isFirstExecute || !this.hasHistory;
|
|
5353
|
+
fullPrompt = this._renderPrompt(useInitial ? "leader-initial" : "leader-continue", templateVars);
|
|
5354
|
+
if (phaseOverride === "execute") this._hasExecuted = true;
|
|
5122
5355
|
} else {
|
|
5123
|
-
|
|
5356
|
+
const workerInitial = this.role.toLowerCase().includes("review") ? "worker-reviewer-initial" : "worker-initial";
|
|
5357
|
+
fullPrompt = this._renderPrompt(this.hasHistory ? "worker-continue" : workerInitial, templateVars);
|
|
5124
5358
|
}
|
|
5125
5359
|
const fullAccess = this.sandboxMode === "full";
|
|
5126
5360
|
const verbose = !!process.env.DEBUG;
|
|
@@ -5129,9 +5363,10 @@ var AgentSession = class {
|
|
|
5129
5363
|
resumeSessionId: this.sessionId ?? void 0,
|
|
5130
5364
|
fullAccess,
|
|
5131
5365
|
noTools: this._isTeamLead,
|
|
5132
|
-
model: this._isTeamLead ? "sonnet" : void 0,
|
|
5133
5366
|
verbose,
|
|
5134
|
-
|
|
5367
|
+
// Only skip resume on first execute (to shed conversational create/design context).
|
|
5368
|
+
// On subsequent runs (result forwarding, user feedback), resume so leader keeps context.
|
|
5369
|
+
skipResume: isFirstExecute && this.hasHistory
|
|
5135
5370
|
});
|
|
5136
5371
|
try {
|
|
5137
5372
|
const whichPath = execSync2(`which ${this.backend.command}`, { env: cleanEnv, encoding: "utf-8", timeout: 3e3 }).trim();
|
|
@@ -5168,22 +5403,34 @@ var AgentSession = class {
|
|
|
5168
5403
|
if (/^\s*[\w./\\-]+\.(ts|tsx|js|jsx|json|md|css|py)\s*$/.test(line)) return true;
|
|
5169
5404
|
return false;
|
|
5170
5405
|
};
|
|
5406
|
+
let pendingDelegation = null;
|
|
5407
|
+
const flushDelegation = () => {
|
|
5408
|
+
if (pendingDelegation && this.onDelegation) {
|
|
5409
|
+
const fullPrompt2 = pendingDelegation.lines.join("\n").replace(/\*\*$/, "").trim();
|
|
5410
|
+
console.log(`[Delegation detected] ${this.name} -> @${pendingDelegation.targetName}: ${fullPrompt2.slice(0, 120)}`);
|
|
5411
|
+
this.onDelegation(this.agentId, pendingDelegation.targetName, fullPrompt2);
|
|
5412
|
+
}
|
|
5413
|
+
pendingDelegation = null;
|
|
5414
|
+
};
|
|
5171
5415
|
const handleTextLine = (text) => {
|
|
5172
5416
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
5173
5417
|
const visibleLines = [];
|
|
5174
5418
|
for (const line of lines) {
|
|
5175
5419
|
const trimmed = line.trim();
|
|
5176
5420
|
console.log(`[Agent ${this.name}] ${trimmed.slice(0, 200)}`);
|
|
5177
|
-
const match = trimmed.match(DELEGATION_RE);
|
|
5178
|
-
if (match
|
|
5421
|
+
const match = this._isTeamLead ? trimmed.match(DELEGATION_RE) : null;
|
|
5422
|
+
if (match) {
|
|
5423
|
+
flushDelegation();
|
|
5179
5424
|
const [, targetName, delegatedPrompt] = match;
|
|
5180
|
-
|
|
5181
|
-
|
|
5425
|
+
pendingDelegation = { targetName, lines: [delegatedPrompt] };
|
|
5426
|
+
} else if (pendingDelegation) {
|
|
5427
|
+
pendingDelegation.lines.push(trimmed);
|
|
5182
5428
|
}
|
|
5183
5429
|
if (!isSystemNoise(line)) {
|
|
5184
5430
|
visibleLines.push(trimmed);
|
|
5185
5431
|
}
|
|
5186
5432
|
}
|
|
5433
|
+
flushDelegation();
|
|
5187
5434
|
if (visibleLines.length > 0) {
|
|
5188
5435
|
this.onEvent({
|
|
5189
5436
|
type: "log:append",
|
|
@@ -5218,6 +5465,11 @@ var AgentSession = class {
|
|
|
5218
5465
|
console.log(`[Agent ${this.name}] Session ID: ${msg.session_id}`);
|
|
5219
5466
|
}
|
|
5220
5467
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
5468
|
+
if (msg.message.usage) {
|
|
5469
|
+
const usage = msg.message.usage;
|
|
5470
|
+
if (typeof usage.input_tokens === "number") this.taskInputTokens += usage.input_tokens;
|
|
5471
|
+
if (typeof usage.output_tokens === "number") this.taskOutputTokens += usage.output_tokens;
|
|
5472
|
+
}
|
|
5221
5473
|
for (const block of msg.message.content) {
|
|
5222
5474
|
if (block.type === "text" && block.text) {
|
|
5223
5475
|
this.stdoutBuffer += block.text + "\n";
|
|
@@ -5302,15 +5554,18 @@ var AgentSession = class {
|
|
|
5302
5554
|
} else if (code === 0) {
|
|
5303
5555
|
this.hasHistory = true;
|
|
5304
5556
|
saveSessionId(this.agentId, this.sessionId);
|
|
5305
|
-
const { summary, fullOutput, changedFiles } = this.extractResult();
|
|
5306
|
-
|
|
5557
|
+
const { summary, fullOutput, changedFiles, entryFile, projectDir, previewCmd, previewPort } = this.extractResult();
|
|
5558
|
+
this._lastFullOutput = fullOutput;
|
|
5559
|
+
const hasWorkOutput = changedFiles.length > 0 || entryFile || previewCmd || projectDir;
|
|
5560
|
+
const { previewUrl, previewPath } = this._isTeamLead || !hasWorkOutput ? { previewUrl: void 0, previewPath: void 0 } : this.detectPreview();
|
|
5307
5561
|
this._lastResult = `done: ${summary.slice(0, 120)}`;
|
|
5308
5562
|
this.setStatus("done");
|
|
5563
|
+
const tokenUsage = this.taskInputTokens > 0 || this.taskOutputTokens > 0 ? { inputTokens: this.taskInputTokens, outputTokens: this.taskOutputTokens } : void 0;
|
|
5309
5564
|
this.onEvent({
|
|
5310
5565
|
type: "task:done",
|
|
5311
5566
|
agentId: this.agentId,
|
|
5312
5567
|
taskId: completedTaskId,
|
|
5313
|
-
result: { summary, fullOutput, changedFiles, diffStat: "", testResult: "unknown", previewUrl, previewPath }
|
|
5568
|
+
result: { summary, fullOutput, changedFiles, diffStat: "", testResult: "unknown", previewUrl, previewPath, entryFile, projectDir, previewCmd, previewPort, tokenUsage }
|
|
5314
5569
|
});
|
|
5315
5570
|
this.onTaskComplete?.(this.agentId, completedTaskId, summary, true);
|
|
5316
5571
|
this.idleTimer = setTimeout(() => {
|
|
@@ -5378,29 +5633,59 @@ var AgentSession = class {
|
|
|
5378
5633
|
* Called directly for workers; called by orchestrator for leader's final result.
|
|
5379
5634
|
*/
|
|
5380
5635
|
detectPreview() {
|
|
5381
|
-
const
|
|
5382
|
-
|
|
5636
|
+
const result = this.extractResult();
|
|
5637
|
+
const baseCwd = this.currentCwd ?? this.workspace;
|
|
5638
|
+
const cwd = result.projectDir ? path3.isAbsolute(result.projectDir) ? result.projectDir : path3.join(baseCwd, result.projectDir) : baseCwd;
|
|
5639
|
+
let previewUrl;
|
|
5383
5640
|
let previewPath;
|
|
5384
|
-
if (
|
|
5385
|
-
|
|
5386
|
-
|
|
5641
|
+
if (result.previewCmd) {
|
|
5642
|
+
if (result.previewPort) {
|
|
5643
|
+
previewUrl = previewServer.runCommand(result.previewCmd, cwd, result.previewPort);
|
|
5644
|
+
if (previewUrl) return { previewUrl, previewPath: void 0 };
|
|
5645
|
+
} else {
|
|
5646
|
+
return { previewUrl: void 0, previewPath: void 0 };
|
|
5647
|
+
}
|
|
5387
5648
|
}
|
|
5388
|
-
if (
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5649
|
+
if (result.entryFile) {
|
|
5650
|
+
if (/\.html?$/i.test(result.entryFile)) {
|
|
5651
|
+
previewPath = path3.isAbsolute(result.entryFile) ? result.entryFile : path3.join(cwd, result.entryFile);
|
|
5652
|
+
if (existsSync3(previewPath)) {
|
|
5653
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5654
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5655
|
+
}
|
|
5393
5656
|
}
|
|
5394
5657
|
}
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5658
|
+
const previewMatch = this.stdoutBuffer.match(/PREVIEW:\s*(https?:\/\/[^\s*)\]>]+)/i);
|
|
5659
|
+
if (previewMatch) {
|
|
5660
|
+
return { previewUrl: previewMatch[1].replace(/[*)\]>]+$/, ""), previewPath: void 0 };
|
|
5661
|
+
}
|
|
5662
|
+
const fileMatch = this.stdoutBuffer.match(/(?:open\s+)?((?:\/[\w./_-]+|[\w./_-]+)\.html?)\b/i);
|
|
5663
|
+
if (fileMatch) {
|
|
5664
|
+
previewPath = path3.isAbsolute(fileMatch[1]) ? fileMatch[1] : path3.join(cwd, fileMatch[1]);
|
|
5665
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5666
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5667
|
+
}
|
|
5668
|
+
const htmlFile = result.changedFiles.find((f) => /\.html?$/i.test(f));
|
|
5669
|
+
if (htmlFile) {
|
|
5670
|
+
previewPath = path3.isAbsolute(htmlFile) ? htmlFile : path3.join(cwd, htmlFile);
|
|
5671
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5672
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5673
|
+
}
|
|
5674
|
+
const candidates = [
|
|
5675
|
+
"dist/index.html",
|
|
5676
|
+
"build/index.html",
|
|
5677
|
+
"out/index.html",
|
|
5678
|
+
"index.html",
|
|
5679
|
+
"public/index.html"
|
|
5680
|
+
];
|
|
5681
|
+
for (const candidate of candidates) {
|
|
5682
|
+
const absPath = path3.join(cwd, candidate);
|
|
5683
|
+
if (existsSync3(absPath)) {
|
|
5684
|
+
previewUrl = previewServer.serve(absPath);
|
|
5685
|
+
if (previewUrl) return { previewUrl, previewPath: absPath };
|
|
5401
5686
|
}
|
|
5402
5687
|
}
|
|
5403
|
-
return { previewUrl, previewPath };
|
|
5688
|
+
return { previewUrl: void 0, previewPath: void 0 };
|
|
5404
5689
|
}
|
|
5405
5690
|
/**
|
|
5406
5691
|
* Parse stdoutBuffer for structured result (SUMMARY/STATUS/FILES_CHANGED).
|
|
@@ -5411,6 +5696,10 @@ var AgentSession = class {
|
|
|
5411
5696
|
const fullOutput = raw.slice(0, 3e3);
|
|
5412
5697
|
const summaryMatch = raw.match(/SUMMARY:\s*(.+)/i);
|
|
5413
5698
|
const filesMatch = raw.match(/FILES_CHANGED:\s*(.+)/i);
|
|
5699
|
+
const entryFileMatch = raw.match(/ENTRY_FILE:\s*(.+)/i);
|
|
5700
|
+
const projectDirMatch = raw.match(/PROJECT_DIR:\s*(.+)/i);
|
|
5701
|
+
const previewCmdMatch = raw.match(/PREVIEW_CMD:\s*(.+)/i);
|
|
5702
|
+
const previewPortMatch = raw.match(/PREVIEW_PORT:\s*(\d+)/i);
|
|
5414
5703
|
const changedFiles = [];
|
|
5415
5704
|
if (filesMatch) {
|
|
5416
5705
|
const fileList = filesMatch[1].trim();
|
|
@@ -5419,8 +5708,12 @@ var AgentSession = class {
|
|
|
5419
5708
|
if (cleaned) changedFiles.push(cleaned);
|
|
5420
5709
|
}
|
|
5421
5710
|
}
|
|
5711
|
+
const entryFile = entryFileMatch?.[1]?.trim();
|
|
5712
|
+
const projectDir = projectDirMatch?.[1]?.trim();
|
|
5713
|
+
const previewCmd = previewCmdMatch?.[1]?.trim();
|
|
5714
|
+
const previewPort = previewPortMatch ? parseInt(previewPortMatch[1], 10) : void 0;
|
|
5422
5715
|
if (summaryMatch) {
|
|
5423
|
-
return { summary: summaryMatch[1].trim(), fullOutput, changedFiles };
|
|
5716
|
+
return { summary: summaryMatch[1].trim(), fullOutput, changedFiles, entryFile, projectDir, previewCmd, previewPort };
|
|
5424
5717
|
}
|
|
5425
5718
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
5426
5719
|
const delegationRe = /^@(\w+):/;
|
|
@@ -5445,17 +5738,17 @@ var AgentSession = class {
|
|
|
5445
5738
|
}
|
|
5446
5739
|
}
|
|
5447
5740
|
if (meaningful.length === 0 && delegationTargets.length > 0) {
|
|
5448
|
-
return { summary: `Delegated tasks to ${delegationTargets.join(", ")}`, fullOutput, changedFiles };
|
|
5741
|
+
return { summary: `Delegated tasks to ${delegationTargets.join(", ")}`, fullOutput, changedFiles, entryFile, projectDir };
|
|
5449
5742
|
}
|
|
5450
5743
|
const lastChunk = meaningful.slice(-5).join("\n").trim();
|
|
5451
5744
|
const summary = lastChunk.slice(0, 500) || "Task completed";
|
|
5452
|
-
return { summary, fullOutput, changedFiles };
|
|
5745
|
+
return { summary, fullOutput, changedFiles, entryFile, projectDir };
|
|
5453
5746
|
}
|
|
5454
5747
|
dequeueNext() {
|
|
5455
5748
|
if (this.taskQueue.length === 0) return;
|
|
5456
5749
|
const next = this.taskQueue.shift();
|
|
5457
5750
|
setTimeout(() => {
|
|
5458
|
-
this.runTask(next.taskId, next.prompt, next.repoPath, next.teamContext);
|
|
5751
|
+
this.runTask(next.taskId, next.prompt, next.repoPath, next.teamContext, false, next.phaseOverride);
|
|
5459
5752
|
}, 100);
|
|
5460
5753
|
}
|
|
5461
5754
|
cancelled = false;
|
|
@@ -5511,16 +5804,33 @@ var AgentSession = class {
|
|
|
5511
5804
|
this.idleTimer = null;
|
|
5512
5805
|
}
|
|
5513
5806
|
if (this.process?.pid) {
|
|
5807
|
+
const pgid = this.process.pid;
|
|
5514
5808
|
try {
|
|
5515
|
-
process.kill(-
|
|
5809
|
+
process.kill(-pgid, "SIGKILL");
|
|
5516
5810
|
} catch {
|
|
5517
|
-
|
|
5811
|
+
try {
|
|
5812
|
+
this.process.kill("SIGKILL");
|
|
5813
|
+
} catch {
|
|
5814
|
+
}
|
|
5518
5815
|
}
|
|
5519
5816
|
this.process = null;
|
|
5520
5817
|
}
|
|
5521
5818
|
this.pendingApprovals.clear();
|
|
5522
5819
|
saveSessionId(this.agentId, null);
|
|
5523
5820
|
}
|
|
5821
|
+
/** Reset conversation history so the next task starts fresh (used by End Project). */
|
|
5822
|
+
clearHistory() {
|
|
5823
|
+
this.hasHistory = false;
|
|
5824
|
+
this.sessionId = null;
|
|
5825
|
+
this.originalTask = null;
|
|
5826
|
+
this.currentPhase = null;
|
|
5827
|
+
this._hasExecuted = false;
|
|
5828
|
+
this._lastResult = null;
|
|
5829
|
+
this._lastResultText = null;
|
|
5830
|
+
this._lastFullOutput = null;
|
|
5831
|
+
this.setStatus("idle");
|
|
5832
|
+
saveSessionId(this.agentId, null);
|
|
5833
|
+
}
|
|
5524
5834
|
resolveApproval(approvalId, decision) {
|
|
5525
5835
|
if (approvalId === "__all__") {
|
|
5526
5836
|
for (const [, pending2] of this.pendingApprovals) {
|
|
@@ -5580,7 +5890,8 @@ var AgentManager = class {
|
|
|
5580
5890
|
const lines = [];
|
|
5581
5891
|
for (const session of this.agents.values()) {
|
|
5582
5892
|
const lead = this.isTeamLead(session.agentId) ? " (Team Lead)" : "";
|
|
5583
|
-
const
|
|
5893
|
+
const raw = session.lastResult ?? "";
|
|
5894
|
+
const result = raw ? ` \u2014 ${raw.length > 100 ? raw.slice(0, 100) + "\u2026" : raw}` : "";
|
|
5584
5895
|
lines.push(`- ${session.name} (${session.role}) [${session.status}]${lead}${result}`);
|
|
5585
5896
|
}
|
|
5586
5897
|
return lines.join("\n");
|
|
@@ -5626,10 +5937,12 @@ var AgentManager = class {
|
|
|
5626
5937
|
|
|
5627
5938
|
// ../../packages/orchestrator/src/delegation.ts
|
|
5628
5939
|
import { nanoid as nanoid3 } from "nanoid";
|
|
5940
|
+
import path4 from "path";
|
|
5629
5941
|
var MAX_DELEGATION_DEPTH = 5;
|
|
5630
5942
|
var MAX_TOTAL_DELEGATIONS = 20;
|
|
5631
|
-
var DELEGATION_BUDGET_ROUNDS =
|
|
5943
|
+
var DELEGATION_BUDGET_ROUNDS = 7;
|
|
5632
5944
|
var HARD_CEILING_ROUNDS = 10;
|
|
5945
|
+
var MAX_REVIEW_ROUNDS = 3;
|
|
5633
5946
|
var RESULT_BATCH_WINDOW_MS = 2e4;
|
|
5634
5947
|
var DelegationRouter = class {
|
|
5635
5948
|
/** taskId → fromAgentId */
|
|
@@ -5642,6 +5955,8 @@ var DelegationRouter = class {
|
|
|
5642
5955
|
totalDelegations = 0;
|
|
5643
5956
|
/** How many times the leader has been invoked to process results */
|
|
5644
5957
|
leaderRounds = 0;
|
|
5958
|
+
/** How many times a Code Reviewer result has been forwarded to the leader */
|
|
5959
|
+
reviewCount = 0;
|
|
5645
5960
|
/** When true, all new delegations and result forwarding are blocked */
|
|
5646
5961
|
stopped = false;
|
|
5647
5962
|
/** TaskIds created by flushResults — only these can produce a final result */
|
|
@@ -5650,6 +5965,8 @@ var DelegationRouter = class {
|
|
|
5650
5965
|
delegationsAtResultStart = /* @__PURE__ */ new Map();
|
|
5651
5966
|
/** Batch result forwarding: originAgentId → pending results + timer */
|
|
5652
5967
|
pendingResults = /* @__PURE__ */ new Map();
|
|
5968
|
+
/** Team-wide project directory — all delegations use this as repoPath when set */
|
|
5969
|
+
teamProjectDir = null;
|
|
5653
5970
|
agentManager;
|
|
5654
5971
|
promptEngine;
|
|
5655
5972
|
emitEvent;
|
|
@@ -5683,7 +6000,7 @@ var DelegationRouter = class {
|
|
|
5683
6000
|
* if the current task is not a "resultTask" (safety net for convergence).
|
|
5684
6001
|
*/
|
|
5685
6002
|
isBudgetExhausted() {
|
|
5686
|
-
return this.leaderRounds >= DELEGATION_BUDGET_ROUNDS;
|
|
6003
|
+
return this.leaderRounds >= DELEGATION_BUDGET_ROUNDS || this.reviewCount >= MAX_REVIEW_ROUNDS;
|
|
5687
6004
|
}
|
|
5688
6005
|
/**
|
|
5689
6006
|
* True if the given resultTask completed WITHOUT creating any new delegations.
|
|
@@ -5724,6 +6041,16 @@ var DelegationRouter = class {
|
|
|
5724
6041
|
}
|
|
5725
6042
|
this.pendingResults.clear();
|
|
5726
6043
|
}
|
|
6044
|
+
/**
|
|
6045
|
+
* Set a team-wide project directory. All delegations will use this as repoPath.
|
|
6046
|
+
*/
|
|
6047
|
+
setTeamProjectDir(dir) {
|
|
6048
|
+
this.teamProjectDir = dir;
|
|
6049
|
+
if (dir) console.log(`[Delegation] Team project dir set: ${dir}`);
|
|
6050
|
+
}
|
|
6051
|
+
getTeamProjectDir() {
|
|
6052
|
+
return this.teamProjectDir;
|
|
6053
|
+
}
|
|
5727
6054
|
/**
|
|
5728
6055
|
* Reset all delegation state (on new team task).
|
|
5729
6056
|
*/
|
|
@@ -5735,7 +6062,9 @@ var DelegationRouter = class {
|
|
|
5735
6062
|
this.delegationsAtResultStart.clear();
|
|
5736
6063
|
this.totalDelegations = 0;
|
|
5737
6064
|
this.leaderRounds = 0;
|
|
6065
|
+
this.reviewCount = 0;
|
|
5738
6066
|
this.stopped = false;
|
|
6067
|
+
this.teamProjectDir = null;
|
|
5739
6068
|
for (const pending of this.pendingResults.values()) {
|
|
5740
6069
|
clearTimeout(pending.timer);
|
|
5741
6070
|
}
|
|
@@ -5744,8 +6073,13 @@ var DelegationRouter = class {
|
|
|
5744
6073
|
wireDelegation(session) {
|
|
5745
6074
|
session.onDelegation = (fromAgentId, targetName, prompt) => {
|
|
5746
6075
|
if (this.stopped) return;
|
|
5747
|
-
|
|
5748
|
-
|
|
6076
|
+
const phaseCheckSession = this.agentManager.get(fromAgentId);
|
|
6077
|
+
if (phaseCheckSession?.currentPhase && phaseCheckSession.currentPhase !== "execute") {
|
|
6078
|
+
console.log(`[Delegation] BLOCKED: agent ${fromAgentId} is in phase "${phaseCheckSession.currentPhase}", not "execute"`);
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
6081
|
+
if (this.isBudgetExhausted()) {
|
|
6082
|
+
console.log(`[Delegation] BLOCKED: budget exhausted (leaderRounds=${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS}, reviewCount=${this.reviewCount}/${MAX_REVIEW_ROUNDS})`);
|
|
5749
6083
|
return;
|
|
5750
6084
|
}
|
|
5751
6085
|
const target = this.agentManager.findByName(targetName);
|
|
@@ -5785,14 +6119,27 @@ var DelegationRouter = class {
|
|
|
5785
6119
|
const fromSession = this.agentManager.get(fromAgentId);
|
|
5786
6120
|
const fromName = fromSession?.name ?? fromAgentId;
|
|
5787
6121
|
const fromRole = fromSession?.role ?? "Team Lead";
|
|
5788
|
-
|
|
5789
|
-
|
|
6122
|
+
let repoPath = this.teamProjectDir ?? void 0;
|
|
6123
|
+
let cleanPrompt = prompt;
|
|
6124
|
+
const dirMatch = prompt.match(/^\s*\[([^\]]+)\]\s*/);
|
|
6125
|
+
if (dirMatch) {
|
|
6126
|
+
cleanPrompt = prompt.slice(dirMatch[0].length);
|
|
6127
|
+
if (!repoPath) {
|
|
6128
|
+
const dirPart = dirMatch[1].replace(/\/$/, "");
|
|
6129
|
+
const leaderSession = this.agentManager.get(fromAgentId);
|
|
6130
|
+
if (leaderSession) {
|
|
6131
|
+
repoPath = path4.resolve(leaderSession.workspaceDir, dirPart);
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6135
|
+
const fullPrompt = this.promptEngine.render("delegation-prefix", { fromName, fromRole, prompt: cleanPrompt });
|
|
6136
|
+
console.log(`[Delegation] ${fromAgentId} -> ${target.agentId} (${targetName}) depth=${newDepth} total=${this.totalDelegations} repoPath=${repoPath ?? "default"}: ${cleanPrompt.slice(0, 80)}`);
|
|
5790
6137
|
this.emitEvent({
|
|
5791
6138
|
type: "task:delegated",
|
|
5792
6139
|
fromAgentId,
|
|
5793
6140
|
toAgentId: target.agentId,
|
|
5794
6141
|
taskId,
|
|
5795
|
-
prompt
|
|
6142
|
+
prompt: cleanPrompt
|
|
5796
6143
|
});
|
|
5797
6144
|
this.emitEvent({
|
|
5798
6145
|
type: "team:chat",
|
|
@@ -5804,7 +6151,7 @@ var DelegationRouter = class {
|
|
|
5804
6151
|
timestamp: Date.now()
|
|
5805
6152
|
});
|
|
5806
6153
|
this.assignedTask.set(target.agentId, taskId);
|
|
5807
|
-
target.runTask(taskId, fullPrompt);
|
|
6154
|
+
target.runTask(taskId, fullPrompt, repoPath);
|
|
5808
6155
|
};
|
|
5809
6156
|
}
|
|
5810
6157
|
wireResultForwarding(session) {
|
|
@@ -5879,6 +6226,13 @@ var DelegationRouter = class {
|
|
|
5879
6226
|
const originSession = this.agentManager.get(originAgentId);
|
|
5880
6227
|
if (!originSession) return;
|
|
5881
6228
|
this.leaderRounds++;
|
|
6229
|
+
for (const r of pending.results) {
|
|
6230
|
+
const agent = this.agentManager.findByName(r.fromName);
|
|
6231
|
+
if (agent && agent.role.toLowerCase().includes("review")) {
|
|
6232
|
+
this.reviewCount++;
|
|
6233
|
+
console.log(`[ResultBatch] Reviewer result detected (reviewCount=${this.reviewCount})`);
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
5882
6236
|
if (this.leaderRounds > HARD_CEILING_ROUNDS) {
|
|
5883
6237
|
console.log(`[ResultBatch] Hard ceiling reached (${HARD_CEILING_ROUNDS} rounds). Force-completing.`);
|
|
5884
6238
|
const resultLines2 = pending.results.map(
|
|
@@ -5908,12 +6262,13 @@ ${resultLines2}`,
|
|
|
5908
6262
|
}
|
|
5909
6263
|
let roundInfo;
|
|
5910
6264
|
const budgetExhausted = this.leaderRounds >= DELEGATION_BUDGET_ROUNDS;
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6265
|
+
const reviewExhausted = this.reviewCount >= MAX_REVIEW_ROUNDS;
|
|
6266
|
+
if (budgetExhausted || reviewExhausted) {
|
|
6267
|
+
roundInfo = reviewExhausted ? `REVIEW LIMIT REACHED (${this.reviewCount}/${MAX_REVIEW_ROUNDS} reviews). No more fix iterations. Output your FINAL SUMMARY now \u2014 accept the work as-is.` : `BUDGET REACHED (round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS}). No more delegations allowed. Output your FINAL SUMMARY now.`;
|
|
6268
|
+
} else if (this.reviewCount > 0) {
|
|
6269
|
+
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS} | Review ${this.reviewCount}/${MAX_REVIEW_ROUNDS} (fix iteration ${this.reviewCount})`;
|
|
5915
6270
|
} else {
|
|
5916
|
-
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS}`;
|
|
6271
|
+
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS} | No reviews yet`;
|
|
5917
6272
|
}
|
|
5918
6273
|
const resultLines = pending.results.map(
|
|
5919
6274
|
(r) => `- ${r.fromName} (${r.statusWord}): ${r.summary}`
|
|
@@ -5935,8 +6290,8 @@ ${resultLines2}`,
|
|
|
5935
6290
|
};
|
|
5936
6291
|
|
|
5937
6292
|
// ../../packages/orchestrator/src/prompt-templates.ts
|
|
5938
|
-
import { readFileSync as
|
|
5939
|
-
import
|
|
6293
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
|
|
6294
|
+
import path5 from "path";
|
|
5940
6295
|
var PROMPT_DEFAULTS = {
|
|
5941
6296
|
"leader-initial": `You are {{name}}, the Team Lead. {{personality}}
|
|
5942
6297
|
You CANNOT write code, run commands, or use any tools. You can ONLY delegate.
|
|
@@ -5945,22 +6300,34 @@ Team:
|
|
|
5945
6300
|
{{teamRoster}}
|
|
5946
6301
|
|
|
5947
6302
|
Delegate using this exact format (one per line):
|
|
5948
|
-
@AgentName:
|
|
6303
|
+
@AgentName: task description
|
|
6304
|
+
|
|
6305
|
+
The system has already created a dedicated project directory for this team. All agents will automatically work there \u2014 do NOT specify directory paths in delegations.
|
|
6306
|
+
|
|
6307
|
+
===== DELEGATION RULES =====
|
|
6308
|
+
|
|
6309
|
+
CRITICAL \u2014 How to assign work to developers:
|
|
6310
|
+
- Give each developer ONE complete, end-to-end task that produces a RUNNABLE deliverable.
|
|
6311
|
+
- The developer is responsible for EVERYTHING: project setup, dependencies, all source files, build configuration, and verification.
|
|
6312
|
+
- NEVER split a project into module-level sub-tasks (e.g. "create AudioManager.ts", "create GameScene.ts"). That produces disconnected files with no project skeleton.
|
|
6313
|
+
- CORRECT example: "@Leo: Build a complete arcade game with PixiJS. Set up the project (package.json, entry HTML, config), implement gameplay (player movement, enemies, scoring, game states), add audio (SFX + BGM with mute toggle), and build a working deliverable. Output ENTRY_FILE when done."
|
|
6314
|
+
- WRONG example: "@Leo: Create src/audio/AudioManager.ts" then "@Leo: Create src/game/GameScene.ts" \u2014 this produces isolated modules that can't run.
|
|
6315
|
+
- If you have multiple developers, split by FEATURE AREA (each producing a runnable piece), not by FILE.
|
|
5949
6316
|
|
|
5950
|
-
|
|
6317
|
+
===== EXECUTION PHASES =====
|
|
5951
6318
|
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6319
|
+
1. BUILD (this round): Assign developers now. Each dev must deliver a working, verifiable result.
|
|
6320
|
+
2. REVIEW: When dev results come back, assign Code Reviewer to check the code.
|
|
6321
|
+
3. FIX (if needed): If Reviewer reports VERDICT=FAIL, collect ISSUES and delegate a fix to the developer. Remind dev to rebuild/re-verify. After fix, assign Reviewer again. Up to 3 review cycles.
|
|
6322
|
+
4. REPORT: When Reviewer reports VERDICT=PASS (or after 3 cycles), output FINAL SUMMARY with preview info. Copy the developer's preview fields (ENTRY_FILE, PREVIEW_CMD, PREVIEW_PORT) exactly as reported \u2014 only include fields the dev actually provided.
|
|
5956
6323
|
|
|
5957
6324
|
Rules:
|
|
5958
6325
|
- Never write code yourself. Only delegate.
|
|
5959
|
-
-
|
|
5960
|
-
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
6326
|
+
- Phase 1 (this round): Assign developers ONLY. Do NOT assign Code Reviewer yet \u2014 there is no code to review.
|
|
6327
|
+
- Skip review for trivial changes (config, typo, rename).
|
|
6328
|
+
|
|
6329
|
+
Approved plan:
|
|
6330
|
+
{{originalTask}}
|
|
5964
6331
|
|
|
5965
6332
|
Task: {{prompt}}`,
|
|
5966
6333
|
"leader-continue": `You are {{name}}, the Team Lead. {{personality}}
|
|
@@ -5974,7 +6341,7 @@ Team status:
|
|
|
5974
6341
|
Delegate using: @AgentName: task description
|
|
5975
6342
|
|
|
5976
6343
|
{{prompt}}`,
|
|
5977
|
-
"leader-result": `You are the Team Lead. You CANNOT write or fix code. You can ONLY delegate using @Name:
|
|
6344
|
+
"leader-result": `You are the Team Lead. You CANNOT write or fix code. You can ONLY delegate using @Name: <task>.
|
|
5978
6345
|
|
|
5979
6346
|
Original user task: {{originalTask}}
|
|
5980
6347
|
|
|
@@ -5986,21 +6353,47 @@ Team status:
|
|
|
5986
6353
|
New result from {{fromName}} ({{resultStatus}}):
|
|
5987
6354
|
{{resultSummary}}
|
|
5988
6355
|
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
6356
|
+
===== DECISION FLOW =====
|
|
6357
|
+
|
|
6358
|
+
Check WHO sent this result, then follow the matching branch:
|
|
6359
|
+
|
|
6360
|
+
\u2500\u2500 RESULT FROM A DEVELOPER \u2500\u2500
|
|
6361
|
+
If STATUS=done:
|
|
6362
|
+
\u2192 Assign Code Reviewer to check the code. In your delegation, include:
|
|
6363
|
+
1. Dev's ENTRY_FILE/PREVIEW_CMD so reviewer knows what was built.
|
|
6364
|
+
2. The KEY FEATURES from the approved plan (3-5 bullet points) so reviewer can verify feature completeness.
|
|
6365
|
+
\u2192 Exception: skip review for trivial changes (config, typo, rename) \u2014 go straight to FINAL SUMMARY.
|
|
6366
|
+
If STATUS=failed:
|
|
6367
|
+
\u2192 Delegate ONE targeted fix to the same developer. Be specific about what failed.
|
|
6368
|
+
|
|
6369
|
+
\u2500\u2500 RESULT FROM CODE REVIEWER \u2500\u2500
|
|
6370
|
+
Reviewer output format: VERDICT (PASS/FAIL), ISSUES (numbered list), SUGGESTIONS (optional).
|
|
6371
|
+
If VERDICT=PASS:
|
|
6372
|
+
\u2192 Output FINAL SUMMARY. Copy ENTRY_FILE/PREVIEW fields from the developer's last report. You are DONE.
|
|
6373
|
+
If VERDICT=FAIL:
|
|
6374
|
+
\u2192 Collect ALL issues into ONE fix delegation to the original developer.
|
|
6375
|
+
\u2192 Quote each issue verbatim. Remind dev: after fixing, rebuild and verify the deliverable works.
|
|
6376
|
+
\u2192 After dev returns with the fix, assign Code Reviewer again to re-check.
|
|
6377
|
+
|
|
6378
|
+
\u2500\u2500 SPECIAL CASES \u2500\u2500
|
|
6379
|
+
\u2022 If roundInfo says "REVIEW LIMIT REACHED" or "BUDGET REACHED" \u2192 Output FINAL SUMMARY immediately. Accept the work as-is.
|
|
6380
|
+
\u2022 Permanent blocker (auth error, missing API key, service down) \u2192 report to the user, do not retry.
|
|
6381
|
+
\u2022 Same error repeated twice \u2192 STOP and report to the user.
|
|
5997
6382
|
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6383
|
+
===== FINAL SUMMARY FORMAT =====
|
|
6384
|
+
(Copy preview fields EXACTLY from the developer's LAST successful report. Only include fields the dev actually provided \u2014 do NOT invent values.)
|
|
6385
|
+
|
|
6386
|
+
ENTRY_FILE: <from dev \u2014 e.g. index.html, dist/index.html. OMIT if dev didn't provide one>
|
|
6387
|
+
PREVIEW_CMD: <from dev \u2014 e.g. "python app.py", "node server.js". OMIT if dev didn't provide one>
|
|
6388
|
+
PREVIEW_PORT: <from dev \u2014 e.g. 5000, 3000. OMIT if dev didn't provide one>
|
|
6389
|
+
SUMMARY: <2-3 sentence description of what was built>
|
|
6390
|
+
|
|
6391
|
+
RULES:
|
|
6392
|
+
- VERDICT=PASS means done, even if SUGGESTIONS exist. Suggestions are non-blocking.
|
|
6393
|
+
- VERDICT=FAIL means real bugs \u2014 always delegate a fix before finalizing.
|
|
6394
|
+
- In every fix delegation, remind dev to rebuild and re-test before reporting.
|
|
6395
|
+
- You MUST include ENTRY_FILE or PREVIEW_CMD in your FINAL SUMMARY \u2014 the user needs this to preview.
|
|
6396
|
+
- Do NOT include PROJECT_DIR \u2014 the system manages project directories automatically.`,
|
|
6004
6397
|
"worker-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
|
|
6005
6398
|
|
|
6006
6399
|
CONVERGENCE RULES (follow strictly):
|
|
@@ -6008,24 +6401,197 @@ CONVERGENCE RULES (follow strictly):
|
|
|
6008
6401
|
- Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
|
|
6009
6402
|
- If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
|
|
6010
6403
|
- Do NOT add features, error handling, or improvements that were not explicitly asked for.
|
|
6011
|
-
- Sub-delegate (@AgentName:) when you need a teammate's specialized expertise (e.g. frontend dev asking backend dev for an API contract, or asking QA to verify a specific behavior). Natural peer collaboration is encouraged \u2014 just keep it focused.
|
|
6012
6404
|
|
|
6013
6405
|
HARD LIMITS:
|
|
6014
|
-
- Do NOT start any dev server
|
|
6015
|
-
-
|
|
6406
|
+
- Do NOT start any long-running dev server or file server. The system handles preview serving automatically.
|
|
6407
|
+
- You MAY install dependencies if the project needs them (npm install, pip install, etc.).
|
|
6408
|
+
{{soloHint}}
|
|
6409
|
+
|
|
6410
|
+
Start with one sentence describing your approach. Then do the work.
|
|
6411
|
+
|
|
6412
|
+
You are responsible for the COMPLETE deliverable \u2014 not just source files. This means:
|
|
6413
|
+
1. Project setup: create all config files needed (package.json, tsconfig, build config, requirements.txt, etc.)
|
|
6414
|
+
2. All source code
|
|
6415
|
+
3. Build & verify: if the project has a build step, RUN IT and fix errors until it passes
|
|
6416
|
+
4. Report how to run/preview the result (see deliverable types below)
|
|
6417
|
+
|
|
6418
|
+
VERIFICATION (MANDATORY before reporting STATUS: done):
|
|
6419
|
+
- If you created a package.json with a build script \u2192 run the build, fix errors until it succeeds, confirm the output file exists
|
|
6420
|
+
- If your deliverable is an HTML file \u2192 confirm it exists and references valid scripts/styles
|
|
6421
|
+
- 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)
|
|
6422
|
+
- If NONE of the above apply \u2192 at minimum list the files and confirm the entry point exists
|
|
6423
|
+
- IMPORTANT: Do NOT launch GUI/desktop applications (Pygame, Tkinter, Electron, etc.) to test them \u2014 they open windows that cannot be controlled. Use syntax checks and import checks only. The user will launch the app manually when ready.
|
|
6424
|
+
- 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.
|
|
6425
|
+
- Do NOT report STATUS: done unless verification passes. Fix problems yourself first.
|
|
6426
|
+
- STATUS: failed is ONLY for truly unsolvable problems (missing API keys, no network, system-level issues).
|
|
6427
|
+
|
|
6428
|
+
===== DELIVERABLE TYPES =====
|
|
6429
|
+
Your project falls into one of these categories. Report the matching fields:
|
|
6430
|
+
|
|
6431
|
+
A) STATIC WEB (HTML/CSS/JS \u2014 no server needed):
|
|
6432
|
+
ENTRY_FILE: index.html (the HTML file to open \u2014 e.g. index.html, dist/index.html, build/index.html)
|
|
6016
6433
|
|
|
6017
|
-
|
|
6434
|
+
B) WEB SERVER (Flask, Express, Sinatra, Rails, Go net/http, etc. \u2014 serves on a port):
|
|
6435
|
+
PREVIEW_CMD: python app.py (the command to start the server)
|
|
6436
|
+
PREVIEW_PORT: 5000 (the port the server listens on \u2014 REQUIRED for web servers)
|
|
6437
|
+
|
|
6438
|
+
C) DESKTOP/CLI APP (Pygame, Tkinter, Electron, JavaFX, terminal tool, native GUI, etc.):
|
|
6439
|
+
PREVIEW_CMD: python game.py (the command to launch the app \u2014 NO PREVIEW_PORT needed)
|
|
6440
|
+
|
|
6441
|
+
OUTPUT:
|
|
6018
6442
|
|
|
6019
|
-
When you finish, report your result in this exact format:
|
|
6020
6443
|
STATUS: done | failed
|
|
6021
|
-
FILES_CHANGED: (list
|
|
6022
|
-
|
|
6444
|
+
FILES_CHANGED: (list all files created or modified, one per line)
|
|
6445
|
+
ENTRY_FILE: (type A only \u2014 path to the HTML file)
|
|
6446
|
+
PREVIEW_CMD: (types B and C \u2014 command to start the app or server)
|
|
6447
|
+
PREVIEW_PORT: (type B only \u2014 the port the server listens on)
|
|
6448
|
+
SUMMARY: (one sentence: what you built + how to run/preview it)
|
|
6449
|
+
|
|
6450
|
+
You MUST provide at least ENTRY_FILE or PREVIEW_CMD.
|
|
6451
|
+
|
|
6452
|
+
{{prompt}}`,
|
|
6453
|
+
"worker-reviewer-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
|
|
6454
|
+
|
|
6455
|
+
CONVERGENCE RULES (follow strictly):
|
|
6456
|
+
- Do the MINIMUM needed to satisfy the task. Simple and working beats perfect and slow.
|
|
6457
|
+
- Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
|
|
6458
|
+
- If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
|
|
6459
|
+
- Do NOT add features, error handling, or improvements that were not explicitly asked for.
|
|
6460
|
+
|
|
6461
|
+
HARD LIMITS:
|
|
6462
|
+
- Do NOT start any long-running dev server or file server. The system handles preview serving automatically.
|
|
6463
|
+
- 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.
|
|
6464
|
+
|
|
6465
|
+
Code Quality (must check):
|
|
6466
|
+
- Correctness, security vulnerabilities, crashes, broken logic.
|
|
6467
|
+
- 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.
|
|
6468
|
+
|
|
6469
|
+
Feature Completeness (must check):
|
|
6470
|
+
- Compare the deliverable against the key features listed in your task assignment.
|
|
6471
|
+
- Flag CORE features that are completely missing or non-functional as ISSUES.
|
|
6472
|
+
- Do NOT fail for polish, extras, or stretch goals \u2014 this is a prototype. Focus on whether the main functionality works.
|
|
6473
|
+
|
|
6474
|
+
Do NOT nitpick style, naming, or formatting. This is a prototype, not production code.
|
|
6475
|
+
|
|
6476
|
+
VERDICT: PASS | FAIL
|
|
6477
|
+
- PASS = code runs without crashes AND core features are implemented (even if rough)
|
|
6478
|
+
- FAIL = crashes/bugs that prevent usage OR core features are missing/broken
|
|
6479
|
+
ISSUES: (numbered list \u2014 bugs, security problems, or missing core features)
|
|
6480
|
+
SUGGESTIONS: (optional \u2014 minor non-blocking observations, keep brief)
|
|
6481
|
+
SUMMARY: (one sentence overall assessment)
|
|
6023
6482
|
|
|
6024
6483
|
{{prompt}}`,
|
|
6025
6484
|
"worker-continue": `{{prompt}}`,
|
|
6026
6485
|
"delegation-prefix": `[Assigned by {{fromName}} ({{fromRole}})]
|
|
6027
6486
|
{{prompt}}`,
|
|
6028
|
-
"delegation-hint": `To delegate a task to another agent, output on its own line: @AgentName: <task description
|
|
6487
|
+
"delegation-hint": `To delegate a task to another agent, output on its own line: @AgentName: <task description>`,
|
|
6488
|
+
"leader-create": `You are {{name}}, the team's Creative Director and Product Consultant. {{personality}}
|
|
6489
|
+
You are starting a new project conversation with the user. Your dual role:
|
|
6490
|
+
1. CREATIVE DIRECTOR \u2014 design the product vision: theme, look & feel, user experience, what makes it unique
|
|
6491
|
+
2. PRODUCT CONSULTANT \u2014 turn that vision into a clear, buildable plan
|
|
6492
|
+
|
|
6493
|
+
Rules:
|
|
6494
|
+
- Be conversational, warm, and concise.
|
|
6495
|
+
- Ask at most 1-2 clarifying questions, then produce a plan. Do NOT over-question.
|
|
6496
|
+
- If the user gives a clear idea (even brief), that is ENOUGH \u2014 use your CREATIVITY to fill in the vision (theme, style, characters, mood, unique twist) and produce the plan immediately.
|
|
6497
|
+
- The goal is a WORKING PROTOTYPE, not a production system.
|
|
6498
|
+
- When ready, produce a project plan wrapped in [PLAN]...[/PLAN] tags.
|
|
6499
|
+
|
|
6500
|
+
===== PLAN FORMAT (strict \u2014 follow this structure) =====
|
|
6501
|
+
|
|
6502
|
+
[PLAN]
|
|
6503
|
+
CONCEPT: Short Name \u2014 one sentence describing what this product is and who it's for (e.g. "Shadow Dash \u2014 a fast-paced rooftop runner for casual gamers")
|
|
6504
|
+
|
|
6505
|
+
CREATIVE VISION:
|
|
6506
|
+
- Theme & setting (e.g. "pixel cityscape at night", "cozy forest caf\xE9")
|
|
6507
|
+
- Visual style (e.g. "retro pixel art", "flat minimalist", "hand-drawn sketch")
|
|
6508
|
+
- Core experience \u2014 what does the user SEE and FEEL when using it?
|
|
6509
|
+
|
|
6510
|
+
FEATURES:
|
|
6511
|
+
- (3-5 bullet points describing WHAT the product does from the user's perspective)
|
|
6512
|
+
- (focus on interactions, content, and behavior \u2014 NOT technical implementation)
|
|
6513
|
+
|
|
6514
|
+
TECH: (one line \u2014 e.g. "Vanilla JS + Canvas" or "React + Tailwind")
|
|
6515
|
+
|
|
6516
|
+
ASSIGNMENTS:
|
|
6517
|
+
- @DevName: (one-sentence summary of what they build)
|
|
6518
|
+
[/PLAN]
|
|
6519
|
+
|
|
6520
|
+
===== ANTI-PATTERNS (never do these) =====
|
|
6521
|
+
- Do NOT write technical implementation steps (e.g. "implement game loop with requestAnimationFrame") \u2014 that is the developer's job.
|
|
6522
|
+
- Do NOT list generic engineering tasks (e.g. "add collision detection", "implement scoring system") \u2014 describe WHAT the product does, not HOW to code it.
|
|
6523
|
+
- Do NOT produce a checklist of modules or files. The plan is a PRODUCT DESCRIPTION, not a technical spec.
|
|
6524
|
+
- Do NOT include milestones, risk analysis, acceptance criteria, or deployment plans.
|
|
6525
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax outside [PLAN] tags.
|
|
6526
|
+
|
|
6527
|
+
If the user hasn't described their project yet, greet them and ask what they'd like to build.
|
|
6528
|
+
|
|
6529
|
+
Team:
|
|
6530
|
+
{{teamRoster}}
|
|
6531
|
+
|
|
6532
|
+
{{prompt}}`,
|
|
6533
|
+
"leader-create-continue": `You are {{name}}, the team's Creative Director and Product Consultant. {{personality}}
|
|
6534
|
+
Do NOT greet or re-introduce yourself \u2014 the conversation is already underway.
|
|
6535
|
+
|
|
6536
|
+
The user replied: {{prompt}}
|
|
6537
|
+
|
|
6538
|
+
IMPORTANT: If the user is pushing you to move forward (e.g. "just do it", "make a plan", "you decide", "any is fine", "up to you"), STOP asking questions and use your CREATIVITY to fill in the vision \u2014 pick a theme, style, unique twist \u2014 and immediately produce a project plan in [PLAN]...[/PLAN] tags.
|
|
6539
|
+
|
|
6540
|
+
Remember: You are the Creative Director. The plan must describe the PRODUCT VISION (concept, creative vision, features from user's perspective), NOT technical implementation steps. Keep it short, actionable, no milestones or risk analysis. Otherwise, ask at most ONE more question, then produce the plan. Do NOT delegate or write code.`,
|
|
6541
|
+
"leader-design": `You are {{name}}, the team's Creative Director, refining the project vision with the user. {{personality}}
|
|
6542
|
+
The user has given feedback on your plan. Your job is to REVISE the existing plan, not start over.
|
|
6543
|
+
|
|
6544
|
+
===== CRITICAL: INCREMENTAL UPDATE =====
|
|
6545
|
+
- User feedback is usually a PARTIAL adjustment (e.g. "use PixiJS", "make it darker", "add multiplayer").
|
|
6546
|
+
- Apply the feedback to the EXISTING plan. Keep everything the user did NOT mention.
|
|
6547
|
+
- NEVER discard the original concept, story, characters, or gameplay just because the user asked for a tech or style change.
|
|
6548
|
+
- If the user says "use X engine" or "change to Y framework" \u2192 update ONLY the TECH line and any affected details. The product vision stays.
|
|
6549
|
+
- Think of it as editing a document, not writing a new one.
|
|
6550
|
+
|
|
6551
|
+
Rules:
|
|
6552
|
+
- Address the user's feedback directly and show what changed.
|
|
6553
|
+
- Always output the updated plan in [PLAN]...[/PLAN] tags using the standard format: CONCEPT, CREATIVE VISION, FEATURES, TECH, ASSIGNMENTS.
|
|
6554
|
+
- The plan describes the PRODUCT VISION \u2014 what users see, feel, and experience. NOT technical implementation steps.
|
|
6555
|
+
- Keep it prototype-focused. No milestones, risk analysis, or deployment plans.
|
|
6556
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax outside [PLAN] tags.
|
|
6557
|
+
|
|
6558
|
+
Team:
|
|
6559
|
+
{{teamRoster}}
|
|
6560
|
+
|
|
6561
|
+
Previous plan context: {{originalTask}}
|
|
6562
|
+
|
|
6563
|
+
User feedback: {{prompt}}`,
|
|
6564
|
+
"leader-design-continue": `You are {{name}}, the team's Creative Director, refining the project vision. {{personality}}
|
|
6565
|
+
|
|
6566
|
+
Your current plan:
|
|
6567
|
+
{{originalTask}}
|
|
6568
|
+
|
|
6569
|
+
The user replied: {{prompt}}
|
|
6570
|
+
|
|
6571
|
+
IMPORTANT: This is an INCREMENTAL update. Apply the user's feedback to the plan above \u2014 do NOT discard the original concept. If the user only mentions one aspect (tech, style, feature), change ONLY that part and keep everything else intact.
|
|
6572
|
+
|
|
6573
|
+
Output the revised plan in the standard format: CONCEPT, CREATIVE VISION, FEATURES, TECH, ASSIGNMENTS. Describe the product vision, NOT technical steps. Always output in [PLAN]...[/PLAN] tags. Do NOT delegate or write code.`,
|
|
6574
|
+
"leader-complete": `You are {{name}}, presenting completed work to the user. {{personality}}
|
|
6575
|
+
The team has finished executing the project. Summarize what was accomplished and ask if the user wants any changes.
|
|
6576
|
+
|
|
6577
|
+
Rules:
|
|
6578
|
+
- Be concise and highlight key outcomes.
|
|
6579
|
+
- If the user provides feedback, note it \u2014 the system will transition back to execute phase.
|
|
6580
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax.
|
|
6581
|
+
|
|
6582
|
+
Team:
|
|
6583
|
+
{{teamRoster}}
|
|
6584
|
+
|
|
6585
|
+
Original task: {{originalTask}}
|
|
6586
|
+
|
|
6587
|
+
{{prompt}}`,
|
|
6588
|
+
"leader-complete-continue": `You are {{name}}, discussing the completed project with the user. {{personality}}
|
|
6589
|
+
|
|
6590
|
+
Original task: {{originalTask}}
|
|
6591
|
+
|
|
6592
|
+
The user replied: {{prompt}}
|
|
6593
|
+
|
|
6594
|
+
Address their feedback. Do NOT delegate or write code.`
|
|
6029
6595
|
};
|
|
6030
6596
|
var PromptEngine = class {
|
|
6031
6597
|
templates = { ...PROMPT_DEFAULTS };
|
|
@@ -6035,21 +6601,24 @@ var PromptEngine = class {
|
|
|
6035
6601
|
}
|
|
6036
6602
|
/**
|
|
6037
6603
|
* Initialize prompt templates on startup.
|
|
6038
|
-
*
|
|
6039
|
-
*
|
|
6604
|
+
* Always writes built-in defaults to disk so new/updated templates take effect.
|
|
6605
|
+
* Users can still customize — edits are preserved until the next code update changes a template.
|
|
6040
6606
|
*/
|
|
6041
6607
|
init() {
|
|
6042
6608
|
if (!this.promptsDir) {
|
|
6043
6609
|
console.log(`[Prompts] No promptsDir configured, using ${Object.keys(PROMPT_DEFAULTS).length} default templates`);
|
|
6044
6610
|
return;
|
|
6045
6611
|
}
|
|
6046
|
-
if (!
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6612
|
+
if (!existsSync4(this.promptsDir)) {
|
|
6613
|
+
mkdirSync4(this.promptsDir, { recursive: true });
|
|
6614
|
+
}
|
|
6615
|
+
let written = 0;
|
|
6616
|
+
for (const [name, content] of Object.entries(PROMPT_DEFAULTS)) {
|
|
6617
|
+
const filePath = path5.join(this.promptsDir, `${name}.md`);
|
|
6618
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
6619
|
+
written++;
|
|
6052
6620
|
}
|
|
6621
|
+
console.log(`[Prompts] Synced ${written} default templates to ${this.promptsDir}`);
|
|
6053
6622
|
this.reload();
|
|
6054
6623
|
}
|
|
6055
6624
|
/**
|
|
@@ -6061,10 +6630,10 @@ var PromptEngine = class {
|
|
|
6061
6630
|
let defaulted = 0;
|
|
6062
6631
|
if (this.promptsDir) {
|
|
6063
6632
|
for (const name of Object.keys(PROMPT_DEFAULTS)) {
|
|
6064
|
-
const filePath =
|
|
6065
|
-
if (
|
|
6633
|
+
const filePath = path5.join(this.promptsDir, `${name}.md`);
|
|
6634
|
+
if (existsSync4(filePath)) {
|
|
6066
6635
|
try {
|
|
6067
|
-
merged[name] =
|
|
6636
|
+
merged[name] = readFileSync4(filePath, "utf-8");
|
|
6068
6637
|
loaded++;
|
|
6069
6638
|
} catch {
|
|
6070
6639
|
defaulted++;
|
|
@@ -6191,7 +6760,7 @@ IMPORTANT: If the same error keeps repeating, choose option 3. Do not waste reso
|
|
|
6191
6760
|
|
|
6192
6761
|
// ../../packages/orchestrator/src/worktree.ts
|
|
6193
6762
|
import { execSync as execSync3 } from "child_process";
|
|
6194
|
-
import
|
|
6763
|
+
import path6 from "path";
|
|
6195
6764
|
var TIMEOUT = 5e3;
|
|
6196
6765
|
function isGitRepo(cwd) {
|
|
6197
6766
|
try {
|
|
@@ -6203,9 +6772,9 @@ function isGitRepo(cwd) {
|
|
|
6203
6772
|
}
|
|
6204
6773
|
function createWorktree(workspace, agentId, taskId, agentName) {
|
|
6205
6774
|
if (!isGitRepo(workspace)) return null;
|
|
6206
|
-
const worktreeDir =
|
|
6775
|
+
const worktreeDir = path6.join(workspace, ".worktrees");
|
|
6207
6776
|
const worktreeName = `${agentId}-${taskId}`;
|
|
6208
|
-
const worktreePath =
|
|
6777
|
+
const worktreePath = path6.join(worktreeDir, worktreeName);
|
|
6209
6778
|
const branch = `agent/${agentName.toLowerCase().replace(/\s+/g, "-")}/${taskId}`;
|
|
6210
6779
|
try {
|
|
6211
6780
|
execSync3(`git worktree add "${worktreePath}" -b "${branch}"`, {
|
|
@@ -6251,7 +6820,7 @@ function mergeWorktree(workspace, worktreePath, branch) {
|
|
|
6251
6820
|
}
|
|
6252
6821
|
}
|
|
6253
6822
|
function removeWorktree(worktreePath, branch, workspace) {
|
|
6254
|
-
const cwd = workspace ??
|
|
6823
|
+
const cwd = workspace ?? path6.dirname(path6.dirname(worktreePath));
|
|
6255
6824
|
try {
|
|
6256
6825
|
execSync3(`git worktree remove --force "${worktreePath}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
|
|
6257
6826
|
} catch {
|
|
@@ -6274,8 +6843,12 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6274
6843
|
sandboxMode;
|
|
6275
6844
|
worktreeEnabled;
|
|
6276
6845
|
worktreeMerge;
|
|
6277
|
-
/** Preview captured from the first dev worker that produces one — not from QA/reviewer */
|
|
6846
|
+
/** Preview info captured from the first dev worker that produces one — not from QA/reviewer */
|
|
6278
6847
|
teamPreview = null;
|
|
6848
|
+
/** Accumulated changedFiles from all workers in the current team session */
|
|
6849
|
+
teamChangedFiles = /* @__PURE__ */ new Set();
|
|
6850
|
+
/** Guard against emitting isFinalResult more than once per execute cycle. */
|
|
6851
|
+
teamFinalized = false;
|
|
6279
6852
|
constructor(opts) {
|
|
6280
6853
|
super();
|
|
6281
6854
|
this.workspace = opts.workspace;
|
|
@@ -6402,9 +6975,15 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6402
6975
|
return;
|
|
6403
6976
|
}
|
|
6404
6977
|
if (this.agentManager.isTeamLead(agentId) && !this.delegationRouter.isDelegated(taskId)) {
|
|
6405
|
-
session.originalTask
|
|
6978
|
+
if (!session.originalTask || !opts?.phaseOverride || opts.phaseOverride !== "execute" && opts.phaseOverride !== "design" && opts.phaseOverride !== "complete") {
|
|
6979
|
+
session.originalTask = prompt;
|
|
6980
|
+
}
|
|
6981
|
+
const savedProjectDir = this.delegationRouter.getTeamProjectDir();
|
|
6406
6982
|
this.delegationRouter.clearAll();
|
|
6983
|
+
if (savedProjectDir) this.delegationRouter.setTeamProjectDir(savedProjectDir);
|
|
6407
6984
|
this.teamPreview = null;
|
|
6985
|
+
this.teamChangedFiles.clear();
|
|
6986
|
+
this.teamFinalized = false;
|
|
6408
6987
|
}
|
|
6409
6988
|
this.retryTracker?.track(taskId, prompt);
|
|
6410
6989
|
if (this.worktreeEnabled && !session.worktreePath) {
|
|
@@ -6424,14 +7003,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6424
7003
|
}
|
|
6425
7004
|
const repoPath = session.worktreePath ?? opts?.repoPath;
|
|
6426
7005
|
const teamContext = this.agentManager.isTeamLead(agentId) ? this.agentManager.getTeamRoster() : void 0;
|
|
6427
|
-
session.runTask(
|
|
6428
|
-
taskId,
|
|
6429
|
-
prompt,
|
|
6430
|
-
repoPath,
|
|
6431
|
-
teamContext,
|
|
6432
|
-
true
|
|
6433
|
-
/* isUserInitiated */
|
|
6434
|
-
);
|
|
7006
|
+
session.runTask(taskId, prompt, repoPath, teamContext, true, opts?.phaseOverride);
|
|
6435
7007
|
}
|
|
6436
7008
|
cancelTask(agentId) {
|
|
6437
7009
|
const session = this.agentManager.get(agentId);
|
|
@@ -6491,7 +7063,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6491
7063
|
getAgent(agentId) {
|
|
6492
7064
|
const s = this.agentManager.get(agentId);
|
|
6493
7065
|
if (!s) return void 0;
|
|
6494
|
-
return { agentId: s.agentId, name: s.name, role: s.role, status: s.status, palette: s.palette, backend: s.backend.id };
|
|
7066
|
+
return { agentId: s.agentId, name: s.name, role: s.role, status: s.status, palette: s.palette, backend: s.backend.id, pid: s.pid };
|
|
6495
7067
|
}
|
|
6496
7068
|
getAllAgents() {
|
|
6497
7069
|
return this.agentManager.getAll().map((s) => ({
|
|
@@ -6501,6 +7073,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6501
7073
|
status: s.status,
|
|
6502
7074
|
palette: s.palette,
|
|
6503
7075
|
backend: s.backend.id,
|
|
7076
|
+
pid: s.pid,
|
|
6504
7077
|
isTeamLead: this.agentManager.isTeamLead(s.agentId),
|
|
6505
7078
|
teamId: s.teamId
|
|
6506
7079
|
}));
|
|
@@ -6508,9 +7081,51 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6508
7081
|
getTeamRoster() {
|
|
6509
7082
|
return this.agentManager.getTeamRoster();
|
|
6510
7083
|
}
|
|
7084
|
+
/** Return PIDs of all managed (gateway-spawned) agent processes */
|
|
7085
|
+
getManagedPids() {
|
|
7086
|
+
const pids = [];
|
|
7087
|
+
for (const session of this.agentManager.getAll()) {
|
|
7088
|
+
const pid = session.pid;
|
|
7089
|
+
if (pid !== null) pids.push(pid);
|
|
7090
|
+
}
|
|
7091
|
+
return pids;
|
|
7092
|
+
}
|
|
6511
7093
|
isTeamLead(agentId) {
|
|
6512
7094
|
return this.agentManager.isTeamLead(agentId);
|
|
6513
7095
|
}
|
|
7096
|
+
/** Get the leader's last full output (used to capture the approved plan). */
|
|
7097
|
+
getLeaderLastOutput(agentId) {
|
|
7098
|
+
const session = this.agentManager.get(agentId);
|
|
7099
|
+
return session?.lastFullOutput ?? null;
|
|
7100
|
+
}
|
|
7101
|
+
/** Set team-wide project directory — all delegations will use this as cwd. */
|
|
7102
|
+
setTeamProjectDir(dir) {
|
|
7103
|
+
this.delegationRouter.setTeamProjectDir(dir);
|
|
7104
|
+
}
|
|
7105
|
+
getTeamProjectDir() {
|
|
7106
|
+
return this.delegationRouter.getTeamProjectDir();
|
|
7107
|
+
}
|
|
7108
|
+
/** Set the original task context for the leader (e.g. the approved plan). */
|
|
7109
|
+
setOriginalTask(agentId, task) {
|
|
7110
|
+
const session = this.agentManager.get(agentId);
|
|
7111
|
+
if (session) session.originalTask = task;
|
|
7112
|
+
}
|
|
7113
|
+
/** Clear ALL team members' conversation history for a fresh project cycle. */
|
|
7114
|
+
clearLeaderHistory(agentId) {
|
|
7115
|
+
const session = this.agentManager.get(agentId);
|
|
7116
|
+
if (session) {
|
|
7117
|
+
session.clearHistory();
|
|
7118
|
+
for (const agent of this.agentManager.getAll()) {
|
|
7119
|
+
if (agent.agentId !== agentId) {
|
|
7120
|
+
agent.clearHistory();
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
this.delegationRouter.clearAll();
|
|
7124
|
+
this.teamPreview = null;
|
|
7125
|
+
this.teamChangedFiles.clear();
|
|
7126
|
+
this.teamFinalized = false;
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
6514
7129
|
// ---------------------------------------------------------------------------
|
|
6515
7130
|
// Cleanup
|
|
6516
7131
|
// ---------------------------------------------------------------------------
|
|
@@ -6586,28 +7201,84 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6586
7201
|
session.worktreeBranch = null;
|
|
6587
7202
|
}
|
|
6588
7203
|
this.retryTracker?.clear(event.taskId);
|
|
6589
|
-
if (!this.agentManager.isTeamLead(agentId) &&
|
|
7204
|
+
if (!this.agentManager.isTeamLead(agentId) && event.result?.changedFiles) {
|
|
7205
|
+
for (const f of event.result.changedFiles) {
|
|
7206
|
+
this.teamChangedFiles.add(f);
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
if (!this.agentManager.isTeamLead(agentId)) {
|
|
6590
7210
|
const role = session?.role?.toLowerCase() ?? "";
|
|
6591
|
-
const isDevWorker = !role.includes("
|
|
6592
|
-
if (isDevWorker && event.result
|
|
7211
|
+
const isDevWorker = !role.includes("review");
|
|
7212
|
+
if (isDevWorker && event.result && (event.result.previewUrl || event.result.entryFile || event.result.previewCmd)) {
|
|
6593
7213
|
this.teamPreview = {
|
|
6594
7214
|
previewUrl: event.result.previewUrl,
|
|
6595
|
-
previewPath: event.result.previewPath
|
|
7215
|
+
previewPath: event.result.previewPath,
|
|
7216
|
+
entryFile: event.result.entryFile,
|
|
7217
|
+
previewCmd: event.result.previewCmd,
|
|
7218
|
+
previewPort: event.result.previewPort
|
|
6596
7219
|
};
|
|
6597
|
-
console.log(`[Orchestrator] Preview captured from ${session?.name}:
|
|
7220
|
+
console.log(`[Orchestrator] Preview captured from ${session?.name}: url=${this.teamPreview.previewUrl}, entry=${this.teamPreview.entryFile}, cmd=${this.teamPreview.previewCmd}`);
|
|
6598
7221
|
}
|
|
6599
7222
|
}
|
|
6600
7223
|
if (this.agentManager.isTeamLead(agentId)) {
|
|
6601
7224
|
const isResultTask = this.delegationRouter.isResultTask(event.taskId);
|
|
6602
7225
|
const leaderDidNotDelegateNewWork = isResultTask && this.delegationRouter.resultTaskDidNotDelegate(event.taskId);
|
|
6603
7226
|
const budgetForced = this.delegationRouter.isBudgetExhausted() && !this.delegationRouter.hasPendingFrom(agentId);
|
|
6604
|
-
const
|
|
7227
|
+
const hasWorkingWorkers = this.agentManager.getAll().some(
|
|
7228
|
+
(w) => w.agentId !== agentId && w.status === "working"
|
|
7229
|
+
);
|
|
7230
|
+
if (hasWorkingWorkers && !budgetForced) {
|
|
7231
|
+
console.log(`[Orchestrator] Deferring finalization \u2014 workers still running`);
|
|
7232
|
+
}
|
|
7233
|
+
const shouldFinalize = (leaderDidNotDelegateNewWork || budgetForced) && !this.teamFinalized && (!hasWorkingWorkers || budgetForced);
|
|
6605
7234
|
if (shouldFinalize) {
|
|
7235
|
+
this.teamFinalized = true;
|
|
6606
7236
|
event.isFinalResult = true;
|
|
6607
7237
|
this.delegationRouter.clearAgent(agentId);
|
|
6608
|
-
if (
|
|
6609
|
-
event.result.
|
|
6610
|
-
|
|
7238
|
+
if (event.result && this.teamChangedFiles.size > 0) {
|
|
7239
|
+
const merged = new Set(event.result.changedFiles ?? []);
|
|
7240
|
+
for (const f of this.teamChangedFiles) merged.add(f);
|
|
7241
|
+
event.result.changedFiles = Array.from(merged);
|
|
7242
|
+
}
|
|
7243
|
+
if (event.result) {
|
|
7244
|
+
const teamDir = this.delegationRouter.getTeamProjectDir();
|
|
7245
|
+
if (teamDir) {
|
|
7246
|
+
event.result.projectDir = teamDir;
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
if (this.teamPreview && event.result) {
|
|
7250
|
+
if (this.teamPreview.previewUrl) {
|
|
7251
|
+
event.result.previewUrl = this.teamPreview.previewUrl;
|
|
7252
|
+
event.result.previewPath = this.teamPreview.previewPath;
|
|
7253
|
+
}
|
|
7254
|
+
if (this.teamPreview.entryFile) event.result.entryFile = this.teamPreview.entryFile;
|
|
7255
|
+
if (this.teamPreview.previewCmd) event.result.previewCmd = this.teamPreview.previewCmd;
|
|
7256
|
+
if (this.teamPreview.previewPort) event.result.previewPort = this.teamPreview.previewPort;
|
|
7257
|
+
}
|
|
7258
|
+
if (event.result?.entryFile) {
|
|
7259
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7260
|
+
const absEntry = path7.isAbsolute(event.result.entryFile) ? event.result.entryFile : path7.join(projectDir, event.result.entryFile);
|
|
7261
|
+
if (!existsSync5(absEntry)) {
|
|
7262
|
+
const allFiles = event.result.changedFiles ?? [];
|
|
7263
|
+
const ext = path7.extname(event.result.entryFile).toLowerCase();
|
|
7264
|
+
const candidate = allFiles.map((f) => path7.basename(f)).find((f) => path7.extname(f).toLowerCase() === ext);
|
|
7265
|
+
if (candidate) {
|
|
7266
|
+
console.log(`[Orchestrator] entryFile "${event.result.entryFile}" not found on disk, using "${candidate}" from changedFiles`);
|
|
7267
|
+
event.result.entryFile = candidate;
|
|
7268
|
+
} else {
|
|
7269
|
+
console.log(`[Orchestrator] entryFile "${event.result.entryFile}" not found on disk, clearing`);
|
|
7270
|
+
event.result.entryFile = void 0;
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
if (event.result?.entryFile && !event.result.previewCmd && !/\.html?$/i.test(event.result.entryFile)) {
|
|
7275
|
+
const ext = path7.extname(event.result.entryFile).toLowerCase();
|
|
7276
|
+
const runners = { ".py": "python3", ".js": "node", ".rb": "ruby", ".sh": "bash" };
|
|
7277
|
+
const runner = runners[ext];
|
|
7278
|
+
if (runner) {
|
|
7279
|
+
event.result.previewCmd = `${runner} ${event.result.entryFile}`;
|
|
7280
|
+
console.log(`[Orchestrator] Auto-constructed previewCmd: ${event.result.previewCmd}`);
|
|
7281
|
+
}
|
|
6611
7282
|
}
|
|
6612
7283
|
if (!event.result?.previewUrl && event.result) {
|
|
6613
7284
|
for (const worker of this.agentManager.getAll()) {
|
|
@@ -6620,6 +7291,70 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6620
7291
|
}
|
|
6621
7292
|
}
|
|
6622
7293
|
}
|
|
7294
|
+
if (!event.result?.previewUrl && event.result?.previewCmd) {
|
|
7295
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7296
|
+
if (event.result.previewPort) {
|
|
7297
|
+
const url = previewServer.runCommand(event.result.previewCmd, projectDir, event.result.previewPort);
|
|
7298
|
+
if (url) {
|
|
7299
|
+
event.result.previewUrl = url;
|
|
7300
|
+
console.log(`[Orchestrator] Preview from leader PREVIEW_CMD (port ${event.result.previewPort}): ${url}`);
|
|
7301
|
+
}
|
|
7302
|
+
} else {
|
|
7303
|
+
console.log(`[Orchestrator] Desktop app ready (user can Launch): ${event.result.previewCmd}`);
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
if (!event.result?.previewUrl && event.result?.entryFile) {
|
|
7307
|
+
const entryFile = event.result.entryFile;
|
|
7308
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7309
|
+
if (/\.html?$/i.test(entryFile)) {
|
|
7310
|
+
const absPath = path7.isAbsolute(entryFile) ? entryFile : path7.join(projectDir, entryFile);
|
|
7311
|
+
const url = previewServer.serve(absPath);
|
|
7312
|
+
if (url) {
|
|
7313
|
+
event.result.previewUrl = url;
|
|
7314
|
+
event.result.previewPath = absPath;
|
|
7315
|
+
console.log(`[Orchestrator] Preview from leader ENTRY_FILE: ${url}`);
|
|
7316
|
+
}
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
if (!event.result?.previewUrl && event.result && this.teamChangedFiles.size > 0) {
|
|
7320
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7321
|
+
const htmlFile = Array.from(this.teamChangedFiles).find((f) => /\.html?$/i.test(f));
|
|
7322
|
+
if (htmlFile) {
|
|
7323
|
+
const absPath = path7.isAbsolute(htmlFile) ? htmlFile : path7.join(projectDir, htmlFile);
|
|
7324
|
+
const url = previewServer.serve(absPath);
|
|
7325
|
+
if (url) {
|
|
7326
|
+
event.result.previewUrl = url;
|
|
7327
|
+
event.result.previewPath = absPath;
|
|
7328
|
+
console.log(`[Orchestrator] Preview from teamChangedFiles: ${url}`);
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
if (!event.result?.previewUrl && event.result) {
|
|
7333
|
+
const projectDir = this.delegationRouter.getTeamProjectDir();
|
|
7334
|
+
if (projectDir) {
|
|
7335
|
+
const candidates = [
|
|
7336
|
+
"dist/index.html",
|
|
7337
|
+
"build/index.html",
|
|
7338
|
+
"out/index.html",
|
|
7339
|
+
// common build dirs
|
|
7340
|
+
"index.html",
|
|
7341
|
+
"public/index.html"
|
|
7342
|
+
// static projects
|
|
7343
|
+
];
|
|
7344
|
+
for (const candidate of candidates) {
|
|
7345
|
+
const absPath = path7.join(projectDir, candidate);
|
|
7346
|
+
if (existsSync5(absPath)) {
|
|
7347
|
+
const url = previewServer.serve(absPath);
|
|
7348
|
+
if (url) {
|
|
7349
|
+
event.result.previewUrl = url;
|
|
7350
|
+
event.result.previewPath = absPath;
|
|
7351
|
+
console.log(`[Orchestrator] Preview from project scan: ${absPath}`);
|
|
7352
|
+
break;
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
6623
7358
|
const summary = event.result?.summary?.slice(0, 200) ?? "All tasks completed.";
|
|
6624
7359
|
this.emitEvent({
|
|
6625
7360
|
type: "team:chat",
|
|
@@ -6655,14 +7390,481 @@ function createOrchestrator(options) {
|
|
|
6655
7390
|
|
|
6656
7391
|
// src/index.ts
|
|
6657
7392
|
import { nanoid as nanoid5 } from "nanoid";
|
|
7393
|
+
import { execFile as execFile3 } from "child_process";
|
|
7394
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
7395
|
+
import path9 from "path";
|
|
7396
|
+
import os2 from "os";
|
|
7397
|
+
|
|
7398
|
+
// src/process-scanner.ts
|
|
6658
7399
|
import { execFile } from "child_process";
|
|
6659
|
-
|
|
6660
|
-
|
|
7400
|
+
var KNOWN_COMMANDS = ["claude", "codex", "gemini", "aider", "opencode"];
|
|
7401
|
+
var COMMAND_TO_BACKEND = {
|
|
7402
|
+
claude: "claude",
|
|
7403
|
+
codex: "codex",
|
|
7404
|
+
gemini: "gemini",
|
|
7405
|
+
aider: "aider",
|
|
7406
|
+
opencode: "opencode"
|
|
7407
|
+
};
|
|
7408
|
+
function parseEtime(etime) {
|
|
7409
|
+
const now = Date.now();
|
|
7410
|
+
const parts = etime.trim().replace(/-/g, ":").split(":");
|
|
7411
|
+
let seconds = 0;
|
|
7412
|
+
if (parts.length === 4) {
|
|
7413
|
+
seconds = parseInt(parts[0]) * 86400 + parseInt(parts[1]) * 3600 + parseInt(parts[2]) * 60 + parseInt(parts[3]);
|
|
7414
|
+
} else if (parts.length === 3) {
|
|
7415
|
+
seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
|
|
7416
|
+
} else if (parts.length === 2) {
|
|
7417
|
+
seconds = parseInt(parts[0]) * 60 + parseInt(parts[1]);
|
|
7418
|
+
}
|
|
7419
|
+
return now - seconds * 1e3;
|
|
7420
|
+
}
|
|
7421
|
+
function exec(cmd, args) {
|
|
7422
|
+
return new Promise((resolve2) => {
|
|
7423
|
+
execFile(cmd, args, { timeout: 5e3, maxBuffer: 1024 * 512 }, (err, stdout) => {
|
|
7424
|
+
resolve2(err ? "" : stdout);
|
|
7425
|
+
});
|
|
7426
|
+
});
|
|
7427
|
+
}
|
|
7428
|
+
async function getCwds(pids) {
|
|
7429
|
+
const result = /* @__PURE__ */ new Map();
|
|
7430
|
+
if (pids.length === 0) return result;
|
|
7431
|
+
const output = await exec("lsof", ["-a", "-p", pids.join(","), "-d", "cwd", "-Fn"]);
|
|
7432
|
+
let currentPid = null;
|
|
7433
|
+
for (const line of output.split("\n")) {
|
|
7434
|
+
if (line.startsWith("p")) {
|
|
7435
|
+
currentPid = parseInt(line.slice(1));
|
|
7436
|
+
} else if (line.startsWith("n") && currentPid !== null) {
|
|
7437
|
+
result.set(currentPid, line.slice(1));
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
return result;
|
|
7441
|
+
}
|
|
7442
|
+
var ProcessScanner = class _ProcessScanner {
|
|
7443
|
+
timer = null;
|
|
7444
|
+
previous = /* @__PURE__ */ new Map();
|
|
7445
|
+
getManagedPids;
|
|
7446
|
+
callbacks;
|
|
7447
|
+
/** PIDs to ignore temporarily (recently killed — may still appear in ps) */
|
|
7448
|
+
graceList = /* @__PURE__ */ new Map();
|
|
7449
|
+
// pid → expiry timestamp
|
|
7450
|
+
static GRACE_MS = 15e3;
|
|
7451
|
+
// 15 seconds grace period
|
|
7452
|
+
constructor(getManagedPids, callbacks) {
|
|
7453
|
+
this.getManagedPids = getManagedPids;
|
|
7454
|
+
this.callbacks = callbacks;
|
|
7455
|
+
}
|
|
7456
|
+
/** Mark a PID as recently killed — scanner will ignore it for a grace period */
|
|
7457
|
+
addGracePid(pid) {
|
|
7458
|
+
this.graceList.set(pid, Date.now() + _ProcessScanner.GRACE_MS);
|
|
7459
|
+
}
|
|
7460
|
+
start(intervalMs = 7e3) {
|
|
7461
|
+
this.scan();
|
|
7462
|
+
this.timer = setInterval(() => this.scan(), intervalMs);
|
|
7463
|
+
}
|
|
7464
|
+
stop() {
|
|
7465
|
+
if (this.timer) {
|
|
7466
|
+
clearInterval(this.timer);
|
|
7467
|
+
this.timer = null;
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
async scan() {
|
|
7471
|
+
try {
|
|
7472
|
+
const psOutput = await exec("ps", ["-eo", "pid,ppid,pcpu,etime,args"]);
|
|
7473
|
+
const managed = new Set(this.getManagedPids());
|
|
7474
|
+
const now = Date.now();
|
|
7475
|
+
for (const [pid, expiry] of this.graceList) {
|
|
7476
|
+
if (now >= expiry) {
|
|
7477
|
+
this.graceList.delete(pid);
|
|
7478
|
+
} else {
|
|
7479
|
+
managed.add(pid);
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
const lines = psOutput.split("\n").slice(1);
|
|
7483
|
+
const candidates = [];
|
|
7484
|
+
for (const line of lines) {
|
|
7485
|
+
const trimmed = line.trim();
|
|
7486
|
+
if (!trimmed) continue;
|
|
7487
|
+
const match = trimmed.match(/^\s*(\d+)\s+(\d+)\s+([\d.]+)\s+([\w:-]+)\s+(.+)$/);
|
|
7488
|
+
if (!match) continue;
|
|
7489
|
+
const pid = parseInt(match[1]);
|
|
7490
|
+
const ppid = parseInt(match[2]);
|
|
7491
|
+
const cpu = parseFloat(match[3]);
|
|
7492
|
+
const etime = match[4];
|
|
7493
|
+
const args = match[5];
|
|
7494
|
+
if (managed.has(pid)) continue;
|
|
7495
|
+
const cmdName = this.matchCommand(args);
|
|
7496
|
+
if (!cmdName) continue;
|
|
7497
|
+
candidates.push({ pid, ppid, cpu, etime, command: cmdName });
|
|
7498
|
+
}
|
|
7499
|
+
const candidatePids = new Set(candidates.map((c) => c.pid));
|
|
7500
|
+
const filtered = candidates.filter((c) => !candidatePids.has(c.ppid));
|
|
7501
|
+
const cwds = await getCwds(filtered.map((c) => c.pid));
|
|
7502
|
+
const current = /* @__PURE__ */ new Map();
|
|
7503
|
+
for (const c of filtered) {
|
|
7504
|
+
const backendId = COMMAND_TO_BACKEND[c.command] ?? c.command;
|
|
7505
|
+
const agentId = `ext-${backendId}-${c.pid}`;
|
|
7506
|
+
const status = c.cpu >= 5 ? "working" : "idle";
|
|
7507
|
+
current.set(agentId, {
|
|
7508
|
+
pid: c.pid,
|
|
7509
|
+
ppid: c.ppid,
|
|
7510
|
+
cpu: c.cpu,
|
|
7511
|
+
command: c.command,
|
|
7512
|
+
backendId,
|
|
7513
|
+
cwd: cwds.get(c.pid) ?? null,
|
|
7514
|
+
startedAt: parseEtime(c.etime),
|
|
7515
|
+
agentId,
|
|
7516
|
+
status
|
|
7517
|
+
});
|
|
7518
|
+
}
|
|
7519
|
+
const added = [];
|
|
7520
|
+
const removed = [];
|
|
7521
|
+
const changed = [];
|
|
7522
|
+
for (const [id, agent] of current) {
|
|
7523
|
+
const prev = this.previous.get(id);
|
|
7524
|
+
if (!prev) {
|
|
7525
|
+
added.push(agent);
|
|
7526
|
+
} else if (prev.status !== agent.status) {
|
|
7527
|
+
changed.push(agent);
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
for (const id of this.previous.keys()) {
|
|
7531
|
+
if (!current.has(id)) {
|
|
7532
|
+
removed.push(id);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7535
|
+
this.previous = current;
|
|
7536
|
+
if (added.length > 0) this.callbacks.onAdded(added);
|
|
7537
|
+
if (removed.length > 0) this.callbacks.onRemoved(removed);
|
|
7538
|
+
if (changed.length > 0) this.callbacks.onChanged(changed);
|
|
7539
|
+
} catch (err) {
|
|
7540
|
+
console.error("[ProcessScanner] Scan error:", err);
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
matchCommand(args) {
|
|
7544
|
+
for (const cmd of KNOWN_COMMANDS) {
|
|
7545
|
+
const re = new RegExp(`(?:^|/)${cmd}(?:\\s|$)`);
|
|
7546
|
+
if (re.test(args)) return cmd;
|
|
7547
|
+
}
|
|
7548
|
+
return null;
|
|
7549
|
+
}
|
|
7550
|
+
};
|
|
7551
|
+
|
|
7552
|
+
// src/external-output-reader.ts
|
|
7553
|
+
import { watch, readdirSync, statSync, existsSync as existsSync6, openSync, readSync, closeSync } from "fs";
|
|
7554
|
+
import { execFile as execFile2 } from "child_process";
|
|
7555
|
+
import path8 from "path";
|
|
6661
7556
|
import os from "os";
|
|
7557
|
+
var SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
7558
|
+
".ts",
|
|
7559
|
+
".tsx",
|
|
7560
|
+
".js",
|
|
7561
|
+
".jsx",
|
|
7562
|
+
".py",
|
|
7563
|
+
".go",
|
|
7564
|
+
".rs",
|
|
7565
|
+
".java",
|
|
7566
|
+
".c",
|
|
7567
|
+
".cpp",
|
|
7568
|
+
".h",
|
|
7569
|
+
".css",
|
|
7570
|
+
".scss",
|
|
7571
|
+
".html",
|
|
7572
|
+
".json",
|
|
7573
|
+
".yaml",
|
|
7574
|
+
".yml",
|
|
7575
|
+
".toml",
|
|
7576
|
+
".md",
|
|
7577
|
+
".sql",
|
|
7578
|
+
".sh",
|
|
7579
|
+
".rb",
|
|
7580
|
+
".swift",
|
|
7581
|
+
".kt"
|
|
7582
|
+
]);
|
|
7583
|
+
var ExternalOutputReader = class {
|
|
7584
|
+
readers = /* @__PURE__ */ new Map();
|
|
7585
|
+
onStatus = null;
|
|
7586
|
+
onTokenUpdate = null;
|
|
7587
|
+
/** Set a callback to be notified when an external agent's status changes (driven by JSONL entries) */
|
|
7588
|
+
setOnStatus(cb) {
|
|
7589
|
+
this.onStatus = cb;
|
|
7590
|
+
}
|
|
7591
|
+
/** Set a callback to be notified when token usage is detected from JSONL entries */
|
|
7592
|
+
setOnTokenUpdate(cb) {
|
|
7593
|
+
this.onTokenUpdate = cb;
|
|
7594
|
+
}
|
|
7595
|
+
attach(agentId, pid, cwd, backendId, onOutput) {
|
|
7596
|
+
if (this.readers.has(agentId)) return;
|
|
7597
|
+
let cleanup2;
|
|
7598
|
+
if (backendId === "claude" && cwd) {
|
|
7599
|
+
cleanup2 = this.startClaudeReader(agentId, cwd, onOutput);
|
|
7600
|
+
} else {
|
|
7601
|
+
cleanup2 = this.startLsofReader(agentId, pid, onOutput);
|
|
7602
|
+
}
|
|
7603
|
+
this.readers.set(agentId, { agentId, pid, cwd, backendId, onOutput, cleanup: cleanup2, inputTokens: 0, outputTokens: 0 });
|
|
7604
|
+
}
|
|
7605
|
+
detach(agentId) {
|
|
7606
|
+
const reader = this.readers.get(agentId);
|
|
7607
|
+
if (reader) {
|
|
7608
|
+
reader.cleanup();
|
|
7609
|
+
this.readers.delete(agentId);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
detachAll() {
|
|
7613
|
+
for (const [id] of this.readers) {
|
|
7614
|
+
this.detach(id);
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
// ── Claude JSONL reader ───────────────────────────────────────
|
|
7618
|
+
startClaudeReader(agentId, cwd, onOutput) {
|
|
7619
|
+
const projectKey = cwd.replace(/\//g, "-");
|
|
7620
|
+
const projectDir = path8.join(os.homedir(), ".claude", "projects", projectKey);
|
|
7621
|
+
console.log(`[OutputReader] Claude reader for ${agentId}: watching ${projectDir}`);
|
|
7622
|
+
let lastPosition = 0;
|
|
7623
|
+
let watchedFile = null;
|
|
7624
|
+
let watcher = null;
|
|
7625
|
+
let lastEmitTime = 0;
|
|
7626
|
+
let pendingChunk = null;
|
|
7627
|
+
let throttleTimer = null;
|
|
7628
|
+
let pollTimer = null;
|
|
7629
|
+
let retryTimer = null;
|
|
7630
|
+
let stopped = false;
|
|
7631
|
+
let retryCount = 0;
|
|
7632
|
+
const THROTTLE_MS = 2e3;
|
|
7633
|
+
const POLL_MS = 3e3;
|
|
7634
|
+
const MAX_RETRIES = 5;
|
|
7635
|
+
const emitThrottled = (text) => {
|
|
7636
|
+
const now = Date.now();
|
|
7637
|
+
if (now - lastEmitTime >= THROTTLE_MS) {
|
|
7638
|
+
lastEmitTime = now;
|
|
7639
|
+
onOutput(text);
|
|
7640
|
+
pendingChunk = null;
|
|
7641
|
+
} else {
|
|
7642
|
+
pendingChunk = text;
|
|
7643
|
+
if (!throttleTimer) {
|
|
7644
|
+
throttleTimer = setTimeout(() => {
|
|
7645
|
+
throttleTimer = null;
|
|
7646
|
+
if (pendingChunk && !stopped) {
|
|
7647
|
+
lastEmitTime = Date.now();
|
|
7648
|
+
onOutput(pendingChunk);
|
|
7649
|
+
pendingChunk = null;
|
|
7650
|
+
}
|
|
7651
|
+
}, THROTTLE_MS - (now - lastEmitTime));
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
};
|
|
7655
|
+
const readNewLines = () => {
|
|
7656
|
+
if (!watchedFile || stopped) return;
|
|
7657
|
+
try {
|
|
7658
|
+
const stat2 = statSync(watchedFile);
|
|
7659
|
+
if (stat2.size <= lastPosition) return;
|
|
7660
|
+
const bytesToRead = stat2.size - lastPosition;
|
|
7661
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
7662
|
+
const fd = openSync(watchedFile, "r");
|
|
7663
|
+
try {
|
|
7664
|
+
readSync(fd, buf, 0, bytesToRead, lastPosition);
|
|
7665
|
+
} finally {
|
|
7666
|
+
closeSync(fd);
|
|
7667
|
+
}
|
|
7668
|
+
lastPosition = stat2.size;
|
|
7669
|
+
const newData = buf.toString("utf-8");
|
|
7670
|
+
for (const line of newData.split("\n")) {
|
|
7671
|
+
if (!line.trim()) continue;
|
|
7672
|
+
try {
|
|
7673
|
+
const entry = JSON.parse(line);
|
|
7674
|
+
this.extractClaudeOutput(entry, emitThrottled, agentId);
|
|
7675
|
+
} catch {
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
} catch (err) {
|
|
7679
|
+
console.error(`[OutputReader] Error reading JSONL for ${agentId}:`, err);
|
|
7680
|
+
}
|
|
7681
|
+
};
|
|
7682
|
+
const findAndWatch = () => {
|
|
7683
|
+
if (stopped) return;
|
|
7684
|
+
if (!existsSync6(projectDir)) {
|
|
7685
|
+
retryCount++;
|
|
7686
|
+
if (retryCount > MAX_RETRIES) {
|
|
7687
|
+
console.log(`[OutputReader] Project dir not found after ${MAX_RETRIES} retries, giving up: ${projectDir}`);
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
console.log(`[OutputReader] Project dir not found (${retryCount}/${MAX_RETRIES}), retrying: ${projectDir}`);
|
|
7691
|
+
retryTimer = setTimeout(findAndWatch, 5e3);
|
|
7692
|
+
return;
|
|
7693
|
+
}
|
|
7694
|
+
try {
|
|
7695
|
+
const files = readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
7696
|
+
name: f,
|
|
7697
|
+
mtime: statSync(path8.join(projectDir, f)).mtimeMs
|
|
7698
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
7699
|
+
if (files.length === 0) {
|
|
7700
|
+
retryCount++;
|
|
7701
|
+
if (retryCount > MAX_RETRIES) {
|
|
7702
|
+
console.log(`[OutputReader] No JSONL files after ${MAX_RETRIES} retries, giving up: ${projectDir}`);
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
console.log(`[OutputReader] No JSONL files yet in ${projectDir} (${retryCount}/${MAX_RETRIES}), retrying`);
|
|
7706
|
+
retryTimer = setTimeout(findAndWatch, 5e3);
|
|
7707
|
+
return;
|
|
7708
|
+
}
|
|
7709
|
+
watchedFile = path8.join(projectDir, files[0].name);
|
|
7710
|
+
lastPosition = statSync(watchedFile).size;
|
|
7711
|
+
console.log(`[OutputReader] Watching JSONL: ${watchedFile} (pos=${lastPosition})`);
|
|
7712
|
+
try {
|
|
7713
|
+
watcher = watch(projectDir, (_event, filename) => {
|
|
7714
|
+
if (stopped) return;
|
|
7715
|
+
if (filename && filename.endsWith(".jsonl")) {
|
|
7716
|
+
const fullPath = path8.join(projectDir, filename);
|
|
7717
|
+
if (fullPath !== watchedFile && existsSync6(fullPath)) {
|
|
7718
|
+
try {
|
|
7719
|
+
const newMtime = statSync(fullPath).mtimeMs;
|
|
7720
|
+
const curMtime = watchedFile ? statSync(watchedFile).mtimeMs : 0;
|
|
7721
|
+
if (newMtime > curMtime) {
|
|
7722
|
+
console.log(`[OutputReader] Switching to newer JSONL: ${fullPath}`);
|
|
7723
|
+
watchedFile = fullPath;
|
|
7724
|
+
lastPosition = 0;
|
|
7725
|
+
}
|
|
7726
|
+
} catch {
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
readNewLines();
|
|
7731
|
+
});
|
|
7732
|
+
} catch {
|
|
7733
|
+
console.log(`[OutputReader] fs.watch failed, relying on polling only`);
|
|
7734
|
+
}
|
|
7735
|
+
pollTimer = setInterval(readNewLines, POLL_MS);
|
|
7736
|
+
} catch (err) {
|
|
7737
|
+
console.error(`[OutputReader] Error setting up watcher for ${agentId}:`, err);
|
|
7738
|
+
}
|
|
7739
|
+
};
|
|
7740
|
+
findAndWatch();
|
|
7741
|
+
return () => {
|
|
7742
|
+
stopped = true;
|
|
7743
|
+
if (watcher) {
|
|
7744
|
+
watcher.close();
|
|
7745
|
+
watcher = null;
|
|
7746
|
+
}
|
|
7747
|
+
if (pollTimer) {
|
|
7748
|
+
clearInterval(pollTimer);
|
|
7749
|
+
pollTimer = null;
|
|
7750
|
+
}
|
|
7751
|
+
if (throttleTimer) {
|
|
7752
|
+
clearTimeout(throttleTimer);
|
|
7753
|
+
throttleTimer = null;
|
|
7754
|
+
}
|
|
7755
|
+
if (retryTimer) {
|
|
7756
|
+
clearTimeout(retryTimer);
|
|
7757
|
+
retryTimer = null;
|
|
7758
|
+
}
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
/**
|
|
7762
|
+
* Extract readable output from a Claude JSONL entry.
|
|
7763
|
+
* Also drives status: "human" → working, "result" → idle.
|
|
7764
|
+
*
|
|
7765
|
+
* Claude JSONL types:
|
|
7766
|
+
* - { type: "human" } → user sent message, agent starts working
|
|
7767
|
+
* - { type: "assistant", message: { content: [...] } } → agent responding
|
|
7768
|
+
* - { type: "result", result: "..." } → turn complete, waiting for input
|
|
7769
|
+
*/
|
|
7770
|
+
extractClaudeOutput(entry, emit, agentId) {
|
|
7771
|
+
if (entry.type === "user" || entry.type === "human") {
|
|
7772
|
+
this.onStatus?.(agentId, "working");
|
|
7773
|
+
}
|
|
7774
|
+
if (entry.type === "assistant") {
|
|
7775
|
+
const message = entry.message;
|
|
7776
|
+
const usage = message?.usage;
|
|
7777
|
+
if (usage) {
|
|
7778
|
+
const reader = this.readers.get(agentId);
|
|
7779
|
+
if (reader) {
|
|
7780
|
+
const inp = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
7781
|
+
const out = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
7782
|
+
if (inp > 0 || out > 0) {
|
|
7783
|
+
reader.inputTokens += inp;
|
|
7784
|
+
reader.outputTokens += out;
|
|
7785
|
+
this.onTokenUpdate?.(agentId, reader.inputTokens, reader.outputTokens);
|
|
7786
|
+
}
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
const stopReason = message?.stop_reason ?? entry.stop_reason;
|
|
7790
|
+
const content = message?.content;
|
|
7791
|
+
if (content && Array.isArray(content)) {
|
|
7792
|
+
for (const block of content) {
|
|
7793
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
7794
|
+
emit(block.text.slice(0, 200));
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
if (stopReason === "end_turn") {
|
|
7799
|
+
this.onStatus?.(agentId, "idle");
|
|
7800
|
+
} else {
|
|
7801
|
+
this.onStatus?.(agentId, "working");
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
if (entry.type === "result") {
|
|
7805
|
+
const result = entry.result;
|
|
7806
|
+
if (typeof result === "string" && result.trim()) {
|
|
7807
|
+
emit(result.slice(0, 200));
|
|
7808
|
+
}
|
|
7809
|
+
this.onStatus?.(agentId, "idle");
|
|
7810
|
+
}
|
|
7811
|
+
}
|
|
7812
|
+
// ── lsof fallback reader ──────────────────────────────────────
|
|
7813
|
+
startLsofReader(agentId, pid, onOutput) {
|
|
7814
|
+
const knownFiles = /* @__PURE__ */ new Set();
|
|
7815
|
+
let stopped = false;
|
|
7816
|
+
console.log(`[OutputReader] lsof reader for ${agentId}: pid=${pid}`);
|
|
7817
|
+
const poll = () => {
|
|
7818
|
+
if (stopped) return;
|
|
7819
|
+
execFile2("lsof", ["-p", String(pid)], { timeout: 5e3, maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
7820
|
+
if (err || stopped) return;
|
|
7821
|
+
const lines = stdout.split("\n");
|
|
7822
|
+
const newFiles = [];
|
|
7823
|
+
for (const line of lines) {
|
|
7824
|
+
const cols = line.trim().split(/\s+/);
|
|
7825
|
+
if (cols.length < 9) continue;
|
|
7826
|
+
const name = cols.slice(8).join(" ");
|
|
7827
|
+
if (!name || name.startsWith("/dev/") || name.startsWith("/System/")) continue;
|
|
7828
|
+
const ext = path8.extname(name);
|
|
7829
|
+
if (!SOURCE_EXTS.has(ext)) continue;
|
|
7830
|
+
if (!knownFiles.has(name)) {
|
|
7831
|
+
knownFiles.add(name);
|
|
7832
|
+
newFiles.push(name);
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
if (newFiles.length > 0) {
|
|
7836
|
+
const basename = path8.basename(newFiles[newFiles.length - 1]);
|
|
7837
|
+
onOutput(`Editing ${basename}`);
|
|
7838
|
+
}
|
|
7839
|
+
});
|
|
7840
|
+
};
|
|
7841
|
+
const timer = setInterval(poll, 5e3);
|
|
7842
|
+
poll();
|
|
7843
|
+
return () => {
|
|
7844
|
+
stopped = true;
|
|
7845
|
+
clearInterval(timer);
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
};
|
|
7849
|
+
|
|
7850
|
+
// src/index.ts
|
|
6662
7851
|
registerChannel(wsChannel);
|
|
6663
7852
|
registerChannel(ablyChannel);
|
|
6664
7853
|
registerChannel(telegramChannel);
|
|
6665
7854
|
var orc;
|
|
7855
|
+
var scanner = null;
|
|
7856
|
+
var outputReader = null;
|
|
7857
|
+
var externalAgents = /* @__PURE__ */ new Map();
|
|
7858
|
+
var teamPhases = /* @__PURE__ */ new Map();
|
|
7859
|
+
function publishTeamPhase(teamId, phase, leadAgentId) {
|
|
7860
|
+
teamPhases.set(teamId, { phase, leadAgentId });
|
|
7861
|
+
publishEvent({
|
|
7862
|
+
type: "TEAM_PHASE",
|
|
7863
|
+
teamId,
|
|
7864
|
+
phase,
|
|
7865
|
+
leadAgentId
|
|
7866
|
+
});
|
|
7867
|
+
}
|
|
6666
7868
|
function generatePairCode() {
|
|
6667
7869
|
return nanoid5(6).toUpperCase();
|
|
6668
7870
|
}
|
|
@@ -6677,12 +7879,109 @@ function showPairCode() {
|
|
|
6677
7879
|
console.log(`Open your phone \u2192 enter gateway address + code`);
|
|
6678
7880
|
console.log("");
|
|
6679
7881
|
}
|
|
7882
|
+
function extractProjectName(planText) {
|
|
7883
|
+
function toKebab(s) {
|
|
7884
|
+
return s.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
7885
|
+
}
|
|
7886
|
+
function trimKebab(s, maxLen) {
|
|
7887
|
+
if (s.length <= maxLen) return s;
|
|
7888
|
+
const cut = s.lastIndexOf("-", maxLen);
|
|
7889
|
+
return cut > 2 ? s.slice(0, cut) : s.slice(0, maxLen);
|
|
7890
|
+
}
|
|
7891
|
+
const namedConcept = planText.match(/CONCEPT\s*[::]\s*(?:A\s+|An\s+|The\s+)?(.+?)\s*[—–]\s/i);
|
|
7892
|
+
if (namedConcept) {
|
|
7893
|
+
const kebab = toKebab(namedConcept[1].trim());
|
|
7894
|
+
if (kebab.length >= 2 && kebab.length <= 30) return kebab;
|
|
7895
|
+
}
|
|
7896
|
+
const quoted = planText.match(/["""\u201c]([^"""\u201d]{2,25})["""\u201d]/);
|
|
7897
|
+
if (quoted) {
|
|
7898
|
+
const kebab = toKebab(quoted[1].trim());
|
|
7899
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7900
|
+
}
|
|
7901
|
+
const concept = planText.match(/CONCEPT\s*[::]\s*(?:A\s+|An\s+|The\s+)?(.+?)(?:\s+(?:for|that|which|where|with|featuring|aimed|designed|,|。)\b|[—–.\n])/i);
|
|
7902
|
+
if (concept) {
|
|
7903
|
+
const kebab = toKebab(concept[1].trim());
|
|
7904
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7905
|
+
}
|
|
7906
|
+
const fallbacks = [
|
|
7907
|
+
/(?:goal|project|目标|项目)\s*[::]\s*(.+)/i,
|
|
7908
|
+
/\[PLAN\][\s\S]*?(?:goal|project|目标)\s*[::]\s*(.+)/i,
|
|
7909
|
+
/(?:build|create|make|开发|做|构建)\s+(?:a\s+)?(.+?)(?:\s+(?:with|using|that|for|where|,|。)\b|[.\n])/i
|
|
7910
|
+
];
|
|
7911
|
+
for (const re of fallbacks) {
|
|
7912
|
+
const m = planText.match(re);
|
|
7913
|
+
if (m) {
|
|
7914
|
+
const kebab = toKebab(m[1].trim());
|
|
7915
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
return "project";
|
|
7919
|
+
}
|
|
7920
|
+
function createUniqueProjectDir(workspace, baseName) {
|
|
7921
|
+
let dirName = baseName;
|
|
7922
|
+
let counter = 1;
|
|
7923
|
+
while (existsSync7(path9.join(workspace, dirName))) {
|
|
7924
|
+
counter++;
|
|
7925
|
+
dirName = `${baseName}-${counter}`;
|
|
7926
|
+
}
|
|
7927
|
+
const fullPath = path9.join(workspace, dirName);
|
|
7928
|
+
mkdirSync5(fullPath, { recursive: true });
|
|
7929
|
+
console.log(`[Gateway] Created project directory: ${fullPath}`);
|
|
7930
|
+
return fullPath;
|
|
7931
|
+
}
|
|
7932
|
+
var AGENTS_FILE = path9.join(os2.homedir(), ".bit-office", "agents.json");
|
|
7933
|
+
function loadAgentDefs() {
|
|
7934
|
+
try {
|
|
7935
|
+
if (existsSync7(AGENTS_FILE)) {
|
|
7936
|
+
const raw = JSON.parse(readFileSync5(AGENTS_FILE, "utf-8"));
|
|
7937
|
+
if (Array.isArray(raw.agents)) return raw.agents;
|
|
7938
|
+
}
|
|
7939
|
+
} catch (e) {
|
|
7940
|
+
console.log(`[Gateway] Failed to read agents.json: ${e}`);
|
|
7941
|
+
}
|
|
7942
|
+
saveAgentDefs(DEFAULT_AGENT_DEFS);
|
|
7943
|
+
return [...DEFAULT_AGENT_DEFS];
|
|
7944
|
+
}
|
|
7945
|
+
function saveAgentDefs(agents) {
|
|
7946
|
+
try {
|
|
7947
|
+
const dir = path9.dirname(AGENTS_FILE);
|
|
7948
|
+
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
7949
|
+
writeFileSync5(AGENTS_FILE, JSON.stringify({ agents }, null, 2), "utf-8");
|
|
7950
|
+
console.log(`[Gateway] Saved ${agents.length} agent definitions to ${AGENTS_FILE}`);
|
|
7951
|
+
} catch (e) {
|
|
7952
|
+
console.log(`[Gateway] Failed to save agents.json: ${e}`);
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
var agentDefs = [];
|
|
6680
7956
|
function mapOrchestratorEvent(e) {
|
|
6681
7957
|
switch (e.type) {
|
|
6682
7958
|
case "task:started":
|
|
6683
7959
|
return { type: "TASK_STARTED", agentId: e.agentId, taskId: e.taskId, prompt: e.prompt };
|
|
6684
|
-
case "task:done":
|
|
7960
|
+
case "task:done": {
|
|
7961
|
+
const resultText = (e.result?.summary ?? "") + (e.result?.fullOutput ?? "");
|
|
7962
|
+
if (resultText && /\[PLAN\]/i.test(resultText)) {
|
|
7963
|
+
for (const [teamId, tp] of teamPhases) {
|
|
7964
|
+
if (tp.leadAgentId === e.agentId && tp.phase === "create") {
|
|
7965
|
+
const planOutput = e.result?.fullOutput ?? e.result?.summary ?? "";
|
|
7966
|
+
if (planOutput) {
|
|
7967
|
+
orc.setOriginalTask(e.agentId, planOutput);
|
|
7968
|
+
console.log(`[Gateway] Captured plan from create phase (${planOutput.length} chars) for design context`);
|
|
7969
|
+
}
|
|
7970
|
+
publishTeamPhase(teamId, "design", e.agentId);
|
|
7971
|
+
break;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
if (e.isFinalResult) {
|
|
7976
|
+
for (const [teamId, tp] of teamPhases) {
|
|
7977
|
+
if (tp.leadAgentId === e.agentId && tp.phase === "execute") {
|
|
7978
|
+
publishTeamPhase(teamId, "complete", e.agentId);
|
|
7979
|
+
break;
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
6685
7983
|
return { type: "TASK_DONE", agentId: e.agentId, taskId: e.taskId, result: e.result, isFinalResult: e.isFinalResult };
|
|
7984
|
+
}
|
|
6686
7985
|
case "task:failed":
|
|
6687
7986
|
return { type: "TASK_FAILED", agentId: e.agentId, taskId: e.taskId, error: e.error };
|
|
6688
7987
|
case "task:delegated":
|
|
@@ -6698,7 +7997,7 @@ function mapOrchestratorEvent(e) {
|
|
|
6698
7997
|
case "task:queued":
|
|
6699
7998
|
return { type: "TASK_QUEUED", agentId: e.agentId, taskId: e.taskId, prompt: e.prompt, position: e.position };
|
|
6700
7999
|
case "agent:created":
|
|
6701
|
-
return { type: "AGENT_CREATED", agentId: e.agentId, name: e.name, role: e.role, palette: e.palette, personality: e.personality, backend: e.backend, isTeamLead: e.isTeamLead, teamId: e.teamId };
|
|
8000
|
+
return { type: "AGENT_CREATED", agentId: e.agentId, name: e.name, role: e.role, palette: e.palette, personality: e.personality, backend: e.backend, isTeamLead: e.isTeamLead || void 0, teamId: e.teamId };
|
|
6702
8001
|
case "agent:fired":
|
|
6703
8002
|
return { type: "AGENT_FIRED", agentId: e.agentId };
|
|
6704
8003
|
case "task:result-returned":
|
|
@@ -6735,6 +8034,8 @@ function handleCommand(parsed) {
|
|
|
6735
8034
|
}
|
|
6736
8035
|
case "FIRE_AGENT": {
|
|
6737
8036
|
console.log(`[Gateway] Firing agent: ${parsed.agentId}`);
|
|
8037
|
+
const agentToFire = orc.getAgent(parsed.agentId);
|
|
8038
|
+
if (agentToFire?.pid) scanner?.addGracePid(agentToFire.pid);
|
|
6738
8039
|
orc.removeAgent(parsed.agentId);
|
|
6739
8040
|
break;
|
|
6740
8041
|
}
|
|
@@ -6755,7 +8056,23 @@ function handleCommand(parsed) {
|
|
|
6755
8056
|
}
|
|
6756
8057
|
if (agent) {
|
|
6757
8058
|
console.log(`[Gateway] RUN_TASK: agent=${parsed.agentId}, isLead=${orc.isTeamLead(parsed.agentId)}, hasTeam=${orc.getAllAgents().length > 1}`);
|
|
6758
|
-
|
|
8059
|
+
let phaseOverride;
|
|
8060
|
+
if (orc.isTeamLead(parsed.agentId)) {
|
|
8061
|
+
for (const [, tp] of teamPhases) {
|
|
8062
|
+
if (tp.leadAgentId === parsed.agentId) {
|
|
8063
|
+
phaseOverride = tp.phase;
|
|
8064
|
+
if (tp.phase === "complete") {
|
|
8065
|
+
const teamId = Array.from(teamPhases.entries()).find(([, v]) => v.leadAgentId === parsed.agentId)?.[0];
|
|
8066
|
+
if (teamId) {
|
|
8067
|
+
publishTeamPhase(teamId, "execute", parsed.agentId);
|
|
8068
|
+
phaseOverride = "execute";
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
break;
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
orc.runTask(parsed.agentId, parsed.taskId, parsed.prompt, { repoPath: parsed.repoPath, phaseOverride });
|
|
6759
8076
|
} else {
|
|
6760
8077
|
publishEvent({
|
|
6761
8078
|
type: "TASK_FAILED",
|
|
@@ -6775,63 +8092,78 @@ function handleCommand(parsed) {
|
|
|
6775
8092
|
break;
|
|
6776
8093
|
}
|
|
6777
8094
|
case "SERVE_PREVIEW": {
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
8095
|
+
if (parsed.previewCmd && parsed.previewPort) {
|
|
8096
|
+
const cwd = parsed.cwd ?? config.defaultWorkspace;
|
|
8097
|
+
console.log(`[Gateway] SERVE_PREVIEW (cmd): "${parsed.previewCmd}" port=${parsed.previewPort} cwd=${cwd}`);
|
|
8098
|
+
previewServer.runCommand(parsed.previewCmd, cwd, parsed.previewPort);
|
|
8099
|
+
} else if (parsed.previewCmd) {
|
|
8100
|
+
const cwd = parsed.cwd ?? config.defaultWorkspace;
|
|
8101
|
+
console.log(`[Gateway] SERVE_PREVIEW (launch): "${parsed.previewCmd}" cwd=${cwd}`);
|
|
8102
|
+
previewServer.launchProcess(parsed.previewCmd, cwd);
|
|
8103
|
+
} else if (parsed.filePath) {
|
|
8104
|
+
console.log(`[Gateway] SERVE_PREVIEW (static): ${parsed.filePath}`);
|
|
8105
|
+
previewServer.serve(parsed.filePath);
|
|
8106
|
+
}
|
|
6781
8107
|
break;
|
|
6782
8108
|
}
|
|
6783
8109
|
case "OPEN_FILE": {
|
|
6784
8110
|
const raw = parsed.path;
|
|
6785
|
-
const resolved =
|
|
6786
|
-
const normalized =
|
|
6787
|
-
if (!normalized.startsWith(config.defaultWorkspace +
|
|
8111
|
+
const resolved = path9.resolve(config.defaultWorkspace, raw);
|
|
8112
|
+
const normalized = path9.normalize(resolved);
|
|
8113
|
+
if (!normalized.startsWith(config.defaultWorkspace + path9.sep) && normalized !== config.defaultWorkspace) {
|
|
6788
8114
|
console.error(`[Gateway] Blocked OPEN_FILE: path "${raw}" resolves outside workspace`);
|
|
6789
8115
|
break;
|
|
6790
8116
|
}
|
|
6791
|
-
if (!
|
|
8117
|
+
if (!existsSync7(normalized)) {
|
|
6792
8118
|
console.error(`[Gateway] OPEN_FILE: path does not exist: ${normalized}`);
|
|
6793
8119
|
break;
|
|
6794
8120
|
}
|
|
6795
8121
|
console.log(`[Gateway] Opening file: ${normalized}`);
|
|
6796
|
-
|
|
8122
|
+
execFile3("open", [normalized], (err) => {
|
|
6797
8123
|
if (err) console.error(`[Gateway] Failed to open file: ${err.message}`);
|
|
6798
8124
|
});
|
|
6799
8125
|
break;
|
|
6800
8126
|
}
|
|
6801
8127
|
case "CREATE_TEAM": {
|
|
6802
|
-
const {
|
|
6803
|
-
const
|
|
6804
|
-
console.log(`[Gateway] Creating team: lead=${
|
|
8128
|
+
const { leadId, memberIds, backends: backends2 } = parsed;
|
|
8129
|
+
const allIds = [leadId, ...memberIds.filter((id) => id !== leadId)];
|
|
8130
|
+
console.log(`[Gateway] Creating team: lead=${leadId}, members=${memberIds.join(",")}`);
|
|
6805
8131
|
let leadAgentId = null;
|
|
6806
8132
|
const teamId = `team-${nanoid5(6)}`;
|
|
6807
|
-
for (const
|
|
6808
|
-
const
|
|
6809
|
-
if (!
|
|
8133
|
+
for (const defId of allIds) {
|
|
8134
|
+
const def = agentDefs.find((a) => a.id === defId);
|
|
8135
|
+
if (!def) {
|
|
8136
|
+
console.log(`[Gateway] Agent def not found: ${defId}`);
|
|
8137
|
+
continue;
|
|
8138
|
+
}
|
|
6810
8139
|
const agentId = `agent-${nanoid5(6)}`;
|
|
6811
|
-
const backendId = backends2?.[
|
|
6812
|
-
if (
|
|
8140
|
+
const backendId = backends2?.[defId] ?? config.defaultBackend;
|
|
8141
|
+
if (defId === leadId) {
|
|
6813
8142
|
leadAgentId = agentId;
|
|
6814
8143
|
orc.setTeamLead(agentId);
|
|
6815
8144
|
}
|
|
6816
8145
|
orc.createAgent({
|
|
6817
8146
|
agentId,
|
|
6818
|
-
name:
|
|
6819
|
-
role:
|
|
6820
|
-
personality:
|
|
8147
|
+
name: def.name,
|
|
8148
|
+
role: def.skills ? `${def.role} \u2014 ${def.skills}` : def.role,
|
|
8149
|
+
personality: def.personality,
|
|
6821
8150
|
backend: backendId,
|
|
6822
|
-
palette:
|
|
8151
|
+
palette: def.palette,
|
|
6823
8152
|
teamId
|
|
6824
8153
|
});
|
|
6825
8154
|
}
|
|
6826
8155
|
if (leadAgentId) {
|
|
6827
|
-
const
|
|
8156
|
+
const leadDef = agentDefs.find((a) => a.id === leadId);
|
|
6828
8157
|
publishEvent({
|
|
6829
8158
|
type: "TEAM_CHAT",
|
|
6830
8159
|
fromAgentId: leadAgentId,
|
|
6831
|
-
message: `Team created! ${
|
|
8160
|
+
message: `Team created! ${leadDef?.name ?? "Lead"} is the Team Lead with ${memberIds.length} team members.`,
|
|
6832
8161
|
messageType: "status",
|
|
6833
8162
|
timestamp: Date.now()
|
|
6834
8163
|
});
|
|
8164
|
+
publishTeamPhase(teamId, "create", leadAgentId);
|
|
8165
|
+
const greetTaskId = nanoid5();
|
|
8166
|
+
orc.runTask(leadAgentId, greetTaskId, "Greet the user and ask what they would like to build.", { phaseOverride: "create" });
|
|
6835
8167
|
}
|
|
6836
8168
|
break;
|
|
6837
8169
|
}
|
|
@@ -6842,7 +8174,83 @@ function handleCommand(parsed) {
|
|
|
6842
8174
|
}
|
|
6843
8175
|
case "FIRE_TEAM": {
|
|
6844
8176
|
console.log("[Gateway] Firing entire team");
|
|
8177
|
+
for (const agent of orc.getAllAgents()) {
|
|
8178
|
+
const pid = agent.pid;
|
|
8179
|
+
if (pid) scanner?.addGracePid(pid);
|
|
8180
|
+
}
|
|
6845
8181
|
orc.fireTeam();
|
|
8182
|
+
teamPhases.clear();
|
|
8183
|
+
break;
|
|
8184
|
+
}
|
|
8185
|
+
case "KILL_EXTERNAL": {
|
|
8186
|
+
const ext = externalAgents.get(parsed.agentId);
|
|
8187
|
+
if (ext) {
|
|
8188
|
+
console.log(`[Gateway] Killing external process: ${ext.name} (pid=${ext.pid})`);
|
|
8189
|
+
scanner?.addGracePid(ext.pid);
|
|
8190
|
+
try {
|
|
8191
|
+
process.kill(ext.pid, "SIGKILL");
|
|
8192
|
+
} catch (err) {
|
|
8193
|
+
console.error(`[Gateway] Failed to kill pid ${ext.pid}:`, err);
|
|
8194
|
+
}
|
|
8195
|
+
outputReader?.detach(ext.agentId);
|
|
8196
|
+
externalAgents.delete(ext.agentId);
|
|
8197
|
+
publishEvent({ type: "AGENT_FIRED", agentId: ext.agentId });
|
|
8198
|
+
} else {
|
|
8199
|
+
console.log(`[Gateway] KILL_EXTERNAL: agent ${parsed.agentId} not found`);
|
|
8200
|
+
}
|
|
8201
|
+
break;
|
|
8202
|
+
}
|
|
8203
|
+
case "APPROVE_PLAN": {
|
|
8204
|
+
const agentId = parsed.agentId;
|
|
8205
|
+
console.log(`[Gateway] APPROVE_PLAN: agent=${agentId}`);
|
|
8206
|
+
const approvedPlan = orc.getLeaderLastOutput(agentId);
|
|
8207
|
+
if (approvedPlan) {
|
|
8208
|
+
orc.setOriginalTask(agentId, approvedPlan);
|
|
8209
|
+
console.log(`[Gateway] Captured approved plan (${approvedPlan.length} chars) for leader ${agentId}`);
|
|
8210
|
+
}
|
|
8211
|
+
const projectName = extractProjectName(approvedPlan ?? "project");
|
|
8212
|
+
const projectDir = createUniqueProjectDir(config.defaultWorkspace, projectName);
|
|
8213
|
+
orc.setTeamProjectDir(projectDir);
|
|
8214
|
+
let approveTeamId;
|
|
8215
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8216
|
+
if (tp.leadAgentId === agentId) {
|
|
8217
|
+
approveTeamId = teamId;
|
|
8218
|
+
break;
|
|
8219
|
+
}
|
|
8220
|
+
}
|
|
8221
|
+
if (!approveTeamId) {
|
|
8222
|
+
const agentInfo = orc.getAllAgents().find((a) => a.agentId === agentId);
|
|
8223
|
+
if (agentInfo?.teamId) approveTeamId = agentInfo.teamId;
|
|
8224
|
+
}
|
|
8225
|
+
if (approveTeamId) {
|
|
8226
|
+
publishTeamPhase(approveTeamId, "execute", agentId);
|
|
8227
|
+
const taskId = nanoid5();
|
|
8228
|
+
orc.runTask(agentId, taskId, `The user approved your plan. Execute it now by delegating tasks to your team members. All work must go in the project directory: ${path9.basename(projectDir)}/`, { phaseOverride: "execute" });
|
|
8229
|
+
}
|
|
8230
|
+
break;
|
|
8231
|
+
}
|
|
8232
|
+
case "END_PROJECT": {
|
|
8233
|
+
const agentId = parsed.agentId;
|
|
8234
|
+
console.log(`[Gateway] END_PROJECT: agent=${agentId}`);
|
|
8235
|
+
orc.clearLeaderHistory(agentId);
|
|
8236
|
+
let foundTeamId;
|
|
8237
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8238
|
+
if (tp.leadAgentId === agentId) {
|
|
8239
|
+
foundTeamId = teamId;
|
|
8240
|
+
break;
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
if (!foundTeamId) {
|
|
8244
|
+
const agentInfo = orc.getAllAgents().find((a) => a.agentId === agentId);
|
|
8245
|
+
if (agentInfo?.teamId) foundTeamId = agentInfo.teamId;
|
|
8246
|
+
}
|
|
8247
|
+
if (foundTeamId) {
|
|
8248
|
+
publishTeamPhase(foundTeamId, "create", agentId);
|
|
8249
|
+
const greetTaskId = nanoid5();
|
|
8250
|
+
orc.runTask(agentId, greetTaskId, "Greet the user and ask what they would like to build next.", { phaseOverride: "create" });
|
|
8251
|
+
} else {
|
|
8252
|
+
console.log(`[Gateway] END_PROJECT: no team found for agent ${agentId}, ignoring`);
|
|
8253
|
+
}
|
|
6846
8254
|
break;
|
|
6847
8255
|
}
|
|
6848
8256
|
case "PING": {
|
|
@@ -6856,7 +8264,7 @@ function handleCommand(parsed) {
|
|
|
6856
8264
|
palette: agent.palette,
|
|
6857
8265
|
personality: void 0,
|
|
6858
8266
|
backend: agent.backend,
|
|
6859
|
-
isTeamLead: agent.isTeamLead,
|
|
8267
|
+
isTeamLead: agent.isTeamLead || void 0,
|
|
6860
8268
|
teamId: agent.teamId
|
|
6861
8269
|
});
|
|
6862
8270
|
publishEvent({
|
|
@@ -6864,7 +8272,67 @@ function handleCommand(parsed) {
|
|
|
6864
8272
|
agentId: agent.agentId,
|
|
6865
8273
|
status: agent.status
|
|
6866
8274
|
});
|
|
8275
|
+
if (agent.isTeamLead && agent.teamId && !teamPhases.has(agent.teamId)) {
|
|
8276
|
+
teamPhases.set(agent.teamId, { phase: "create", leadAgentId: agent.agentId });
|
|
8277
|
+
console.log(`[Gateway] Restored team phase for ${agent.teamId} (leader=${agent.agentId})`);
|
|
8278
|
+
}
|
|
8279
|
+
}
|
|
8280
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8281
|
+
publishEvent({
|
|
8282
|
+
type: "TEAM_PHASE",
|
|
8283
|
+
teamId,
|
|
8284
|
+
phase: tp.phase,
|
|
8285
|
+
leadAgentId: tp.leadAgentId
|
|
8286
|
+
});
|
|
8287
|
+
}
|
|
8288
|
+
for (const [, ext] of externalAgents) {
|
|
8289
|
+
publishEvent({
|
|
8290
|
+
type: "AGENT_CREATED",
|
|
8291
|
+
agentId: ext.agentId,
|
|
8292
|
+
name: ext.name,
|
|
8293
|
+
role: ext.cwd ? ext.cwd.split("/").pop() ?? ext.backendId : ext.backendId,
|
|
8294
|
+
isExternal: true,
|
|
8295
|
+
pid: ext.pid,
|
|
8296
|
+
cwd: ext.cwd ?? void 0,
|
|
8297
|
+
startedAt: ext.startedAt,
|
|
8298
|
+
backend: ext.backendId
|
|
8299
|
+
});
|
|
8300
|
+
publishEvent({
|
|
8301
|
+
type: "AGENT_STATUS",
|
|
8302
|
+
agentId: ext.agentId,
|
|
8303
|
+
status: ext.status
|
|
8304
|
+
});
|
|
8305
|
+
}
|
|
8306
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
8307
|
+
break;
|
|
8308
|
+
}
|
|
8309
|
+
case "SAVE_AGENT_DEF": {
|
|
8310
|
+
const def = parsed.agent;
|
|
8311
|
+
const idx = agentDefs.findIndex((a) => a.id === def.id);
|
|
8312
|
+
if (idx >= 0) {
|
|
8313
|
+
if (agentDefs[idx].isBuiltin) {
|
|
8314
|
+
def.isBuiltin = true;
|
|
8315
|
+
def.teamRole = agentDefs[idx].teamRole;
|
|
8316
|
+
}
|
|
8317
|
+
agentDefs[idx] = def;
|
|
8318
|
+
} else {
|
|
8319
|
+
def.isBuiltin = false;
|
|
8320
|
+
def.teamRole = "dev";
|
|
8321
|
+
agentDefs.push(def);
|
|
6867
8322
|
}
|
|
8323
|
+
saveAgentDefs(agentDefs);
|
|
8324
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
8325
|
+
break;
|
|
8326
|
+
}
|
|
8327
|
+
case "DELETE_AGENT_DEF": {
|
|
8328
|
+
const target = agentDefs.find((a) => a.id === parsed.agentDefId);
|
|
8329
|
+
if (target?.isBuiltin) {
|
|
8330
|
+
console.log(`[Gateway] Cannot delete built-in agent: ${parsed.agentDefId}`);
|
|
8331
|
+
break;
|
|
8332
|
+
}
|
|
8333
|
+
agentDefs = agentDefs.filter((a) => a.id !== parsed.agentDefId);
|
|
8334
|
+
saveAgentDefs(agentDefs);
|
|
8335
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
6868
8336
|
break;
|
|
6869
8337
|
}
|
|
6870
8338
|
}
|
|
@@ -6892,9 +8360,11 @@ async function main() {
|
|
|
6892
8360
|
worktree: false,
|
|
6893
8361
|
// disabled by default for now
|
|
6894
8362
|
retry: { maxRetries: 2, escalateToLeader: true },
|
|
6895
|
-
promptsDir:
|
|
8363
|
+
promptsDir: path9.join(os2.homedir(), ".bit-office", "prompts"),
|
|
6896
8364
|
sandboxMode: config.sandboxMode
|
|
6897
8365
|
});
|
|
8366
|
+
agentDefs = loadAgentDefs();
|
|
8367
|
+
console.log(`[Gateway] Loaded ${agentDefs.length} agent definitions (${agentDefs.filter((a) => !a.isBuiltin).length} custom)`);
|
|
6898
8368
|
const forwardEvent = (event) => {
|
|
6899
8369
|
const mapped = mapOrchestratorEvent(event);
|
|
6900
8370
|
if (mapped) publishEvent(mapped);
|
|
@@ -6914,6 +8384,99 @@ async function main() {
|
|
|
6914
8384
|
orc.on("agent:created", forwardEvent);
|
|
6915
8385
|
orc.on("agent:fired", forwardEvent);
|
|
6916
8386
|
orc.on("task:result-returned", forwardEvent);
|
|
8387
|
+
outputReader = new ExternalOutputReader();
|
|
8388
|
+
outputReader.setOnStatus((agentId, status) => {
|
|
8389
|
+
const ext = externalAgents.get(agentId);
|
|
8390
|
+
if (ext && ext.status !== status) {
|
|
8391
|
+
ext.status = status;
|
|
8392
|
+
publishEvent({
|
|
8393
|
+
type: "AGENT_STATUS",
|
|
8394
|
+
agentId,
|
|
8395
|
+
status
|
|
8396
|
+
});
|
|
8397
|
+
}
|
|
8398
|
+
});
|
|
8399
|
+
outputReader.setOnTokenUpdate((agentId, inputTokens, outputTokens) => {
|
|
8400
|
+
publishEvent({
|
|
8401
|
+
type: "TOKEN_UPDATE",
|
|
8402
|
+
agentId,
|
|
8403
|
+
inputTokens,
|
|
8404
|
+
outputTokens
|
|
8405
|
+
});
|
|
8406
|
+
});
|
|
8407
|
+
scanner = new ProcessScanner(
|
|
8408
|
+
() => orc.getManagedPids(),
|
|
8409
|
+
{
|
|
8410
|
+
onAdded: (agents) => {
|
|
8411
|
+
for (const agent of agents) {
|
|
8412
|
+
const name = agent.command.charAt(0).toUpperCase() + agent.command.slice(1);
|
|
8413
|
+
const displayName = `${name} (${agent.pid})`;
|
|
8414
|
+
externalAgents.set(agent.agentId, {
|
|
8415
|
+
agentId: agent.agentId,
|
|
8416
|
+
name: displayName,
|
|
8417
|
+
backendId: agent.backendId,
|
|
8418
|
+
pid: agent.pid,
|
|
8419
|
+
cwd: agent.cwd,
|
|
8420
|
+
startedAt: agent.startedAt,
|
|
8421
|
+
status: agent.status
|
|
8422
|
+
});
|
|
8423
|
+
console.log(`[ProcessScanner] External agent found: ${displayName} (pid=${agent.pid}, cwd=${agent.cwd})`);
|
|
8424
|
+
publishEvent({
|
|
8425
|
+
type: "AGENT_CREATED",
|
|
8426
|
+
agentId: agent.agentId,
|
|
8427
|
+
name: displayName,
|
|
8428
|
+
role: agent.cwd ? agent.cwd.split("/").pop() ?? agent.backendId : agent.backendId,
|
|
8429
|
+
isExternal: true,
|
|
8430
|
+
pid: agent.pid,
|
|
8431
|
+
cwd: agent.cwd ?? void 0,
|
|
8432
|
+
startedAt: agent.startedAt,
|
|
8433
|
+
backend: agent.backendId
|
|
8434
|
+
});
|
|
8435
|
+
publishEvent({
|
|
8436
|
+
type: "AGENT_STATUS",
|
|
8437
|
+
agentId: agent.agentId,
|
|
8438
|
+
status: agent.status
|
|
8439
|
+
});
|
|
8440
|
+
outputReader?.attach(agent.agentId, agent.pid, agent.cwd, agent.backendId, (chunk) => {
|
|
8441
|
+
publishEvent({
|
|
8442
|
+
type: "LOG_APPEND",
|
|
8443
|
+
agentId: agent.agentId,
|
|
8444
|
+
taskId: "external",
|
|
8445
|
+
stream: "stdout",
|
|
8446
|
+
chunk
|
|
8447
|
+
});
|
|
8448
|
+
});
|
|
8449
|
+
}
|
|
8450
|
+
},
|
|
8451
|
+
onRemoved: (agentIds) => {
|
|
8452
|
+
for (const agentId of agentIds) {
|
|
8453
|
+
const ext = externalAgents.get(agentId);
|
|
8454
|
+
console.log(`[ProcessScanner] External agent gone: ${ext?.name ?? agentId}`);
|
|
8455
|
+
outputReader?.detach(agentId);
|
|
8456
|
+
externalAgents.delete(agentId);
|
|
8457
|
+
publishEvent({
|
|
8458
|
+
type: "AGENT_FIRED",
|
|
8459
|
+
agentId
|
|
8460
|
+
});
|
|
8461
|
+
}
|
|
8462
|
+
},
|
|
8463
|
+
onChanged: (agents) => {
|
|
8464
|
+
for (const agent of agents) {
|
|
8465
|
+
const ext = externalAgents.get(agent.agentId);
|
|
8466
|
+
if (ext?.backendId === "claude") continue;
|
|
8467
|
+
if (ext) {
|
|
8468
|
+
ext.status = agent.status;
|
|
8469
|
+
}
|
|
8470
|
+
publishEvent({
|
|
8471
|
+
type: "AGENT_STATUS",
|
|
8472
|
+
agentId: agent.agentId,
|
|
8473
|
+
status: agent.status
|
|
8474
|
+
});
|
|
8475
|
+
}
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
);
|
|
8479
|
+
scanner.start();
|
|
6917
8480
|
const backendNames = config.detectedBackends.map((id) => getBackend(id)?.name ?? id).join(", ");
|
|
6918
8481
|
console.log(`[Gateway] AI backends: ${backendNames || "none detected"} (default: ${getBackend(config.defaultBackend)?.name ?? config.defaultBackend})`);
|
|
6919
8482
|
console.log(`[Gateway] Permissions: ${config.sandboxMode === "full" ? "Full access" : "Sandbox"}`);
|
|
@@ -6922,10 +8485,10 @@ async function main() {
|
|
|
6922
8485
|
await initTransports(handleCommand);
|
|
6923
8486
|
console.log("[Gateway] Listening for commands...");
|
|
6924
8487
|
console.log("[Gateway] Press 'p' + Enter to generate a new pair code");
|
|
6925
|
-
if (process.env.NODE_ENV !== "development" &&
|
|
8488
|
+
if (process.env.NODE_ENV !== "development" && existsSync7(config.webDir)) {
|
|
6926
8489
|
const url = `http://localhost:${config.wsPort}`;
|
|
6927
8490
|
console.log(`[Gateway] Opening ${url}`);
|
|
6928
|
-
|
|
8491
|
+
execFile3("open", [url]);
|
|
6929
8492
|
}
|
|
6930
8493
|
if (process.stdin.isTTY) {
|
|
6931
8494
|
process.stdin.setEncoding("utf-8");
|
|
@@ -6939,6 +8502,8 @@ async function main() {
|
|
|
6939
8502
|
}
|
|
6940
8503
|
function cleanup() {
|
|
6941
8504
|
console.log("[Gateway] Shutting down...");
|
|
8505
|
+
outputReader?.detachAll();
|
|
8506
|
+
scanner?.stop();
|
|
6942
8507
|
previewServer.stop();
|
|
6943
8508
|
orc?.destroy();
|
|
6944
8509
|
destroyTransports();
|