bit-office 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1755 -193
- package/dist/index.js.map +1 -1
- package/dist/web/{out/out/404.html → 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/{out/out/_next/static/chunks/webpack-2f6b11d148fba4ca.js → _next/static/chunks/webpack-24a71fb73bc694fc.js} +1 -1
- package/dist/web/{out/out/index.html → index.html} +2 -2
- package/dist/web/{out/out/index.txt → index.txt} +1 -1
- package/dist/web/{out/out/office.html → office.html} +3 -3
- package/dist/web/{out/out/office.txt → office.txt} +2 -2
- package/dist/web/{out/out/pair.html → pair.html} +2 -2
- package/dist/web/{out/out/pair.txt → pair.txt} +1 -1
- package/dist/web/{out/out/sw.js → sw.js} +1 -1
- package/package.json +2 -2
- package/dist/web/out/out/_next/static/chunks/904.70db0cb7d0ebff73.js +0 -1
- package/dist/web/out/out/_next/static/chunks/app/office/page-40e6628ffa58377f.js +0 -1
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/LICENSE.txt +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 1.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 2.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 3.5.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 3.jpeg +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 3.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Designs/Office Level 4.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Palette/Office Palette Large.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Palette/Office Palette Medium.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Palette/Office Palette Small.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 16x16 no shadow.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 16x16.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 32x32 no shadow.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 32x32.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 48x48 no shadow.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office Tileset All 48x48.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/A2 Office Floors.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/A4 Office Walls.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/A5 Office Floors & Walls.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/B-C-D-E Office 1 No Shadows.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/B-C-D-E Office 1.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/B-C-D-E Office 2 No Shadows.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/B-C-D-E Office 2.png +0 -0
- /package/dist/web/{out/out/Office Tileset → Office Tileset}/Office VX Ace/README.txt +0 -0
- /package/dist/web/{out/out/_next/static/0Dv8UrlW-3l4rmSUSmwsV → _next/static/2DlUdS8198vtEVFBCTvcr}/_buildManifest.js +0 -0
- /package/dist/web/{out/out/_next/static/0Dv8UrlW-3l4rmSUSmwsV → _next/static/2DlUdS8198vtEVFBCTvcr}/_ssgManifest.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/29.2a4210219e06b537.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/290.d0fe5fb72a5068b2.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/325ca9c7-f84e7e4973790fdf.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/388-5a04e93e2531cfb7.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/4d67e943-e6a0fd3b5c397505.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/757.6d61de28074ba8d4.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/810.44213c6355518b0b.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/880.614e72604eb723a3.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/989-4c2c2b3a6c2f8a4a.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/app/_not-found/page-182ed2c105839b2a.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/app/layout-72c65340fbaef4c0.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/app/page-9721074d4b94e149.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/app/pair/page-ffb2dcb5753f3161.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/framework-bec628f2b7cc8f6d.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/main-314e1c8a2c4316bd.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/main-app-baad86574bd560af.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/pages/_app-f4d560c9dbc22e4e.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/pages/_error-492bd157be5771d5.js +0 -0
- /package/dist/web/{out/out/_next → _next}/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_0.png +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_1.png +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_2.png +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_3.png +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_4.png +0 -0
- /package/dist/web/{out/out/assets → assets}/characters/char_5.png +0 -0
- /package/dist/web/{out/out/assets → assets}/walls.png +0 -0
- /package/dist/web/{out/out/icons → icons}/icon-192.png +0 -0
- /package/dist/web/{out/out/icons → icons}/icon-512.png +0 -0
- /package/dist/web/{out/out/manifest.json → manifest.json} +0 -0
- /package/dist/web/{out/out/pixel-agents-layout.json → pixel-agents-layout.json} +0 -0
- /package/dist/web/{out/out/workbox-5194662c.js → workbox-5194662c.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
|
|
@@ -4336,8 +4426,8 @@ function getOrCreateMachineId() {
|
|
|
4336
4426
|
}
|
|
4337
4427
|
function resolveWebDir() {
|
|
4338
4428
|
if (process.env.WEB_DIR) return process.env.WEB_DIR;
|
|
4339
|
-
const bundled = resolve(__dirname, "web
|
|
4340
|
-
if (existsSync(bundled)) return bundled;
|
|
4429
|
+
const bundled = resolve(__dirname, "web");
|
|
4430
|
+
if (existsSync(resolve(bundled, "index.html"))) return bundled;
|
|
4341
4431
|
return resolve(__dirname, "../../web/out");
|
|
4342
4432
|
}
|
|
4343
4433
|
function resolveDefaultWorkspace() {
|
|
@@ -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,29 @@ 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."
|
|
5118
5344
|
};
|
|
5345
|
+
const isFirstExecute = this._isTeamLead && phaseOverride === "execute" && !this._hasExecuted;
|
|
5119
5346
|
let fullPrompt;
|
|
5120
|
-
if (this._isTeamLead) {
|
|
5121
|
-
|
|
5347
|
+
if (this._isTeamLead && phaseOverride && ["create", "design", "complete"].includes(phaseOverride)) {
|
|
5348
|
+
const templateName = this.hasHistory ? `leader-${phaseOverride}-continue` : `leader-${phaseOverride}`;
|
|
5349
|
+
fullPrompt = this._renderPrompt(templateName, templateVars);
|
|
5350
|
+
} else if (this._isTeamLead) {
|
|
5351
|
+
const useInitial = isFirstExecute || !this.hasHistory;
|
|
5352
|
+
fullPrompt = this._renderPrompt(useInitial ? "leader-initial" : "leader-continue", templateVars);
|
|
5353
|
+
if (phaseOverride === "execute") this._hasExecuted = true;
|
|
5122
5354
|
} else {
|
|
5123
|
-
|
|
5355
|
+
const workerInitial = this.role.toLowerCase().includes("review") ? "worker-reviewer-initial" : "worker-initial";
|
|
5356
|
+
fullPrompt = this._renderPrompt(this.hasHistory ? "worker-continue" : workerInitial, templateVars);
|
|
5124
5357
|
}
|
|
5125
5358
|
const fullAccess = this.sandboxMode === "full";
|
|
5126
5359
|
const verbose = !!process.env.DEBUG;
|
|
@@ -5129,9 +5362,10 @@ var AgentSession = class {
|
|
|
5129
5362
|
resumeSessionId: this.sessionId ?? void 0,
|
|
5130
5363
|
fullAccess,
|
|
5131
5364
|
noTools: this._isTeamLead,
|
|
5132
|
-
model: this._isTeamLead ? "sonnet" : void 0,
|
|
5133
5365
|
verbose,
|
|
5134
|
-
|
|
5366
|
+
// Only skip resume on first execute (to shed conversational create/design context).
|
|
5367
|
+
// On subsequent runs (result forwarding, user feedback), resume so leader keeps context.
|
|
5368
|
+
skipResume: isFirstExecute && this.hasHistory
|
|
5135
5369
|
});
|
|
5136
5370
|
try {
|
|
5137
5371
|
const whichPath = execSync2(`which ${this.backend.command}`, { env: cleanEnv, encoding: "utf-8", timeout: 3e3 }).trim();
|
|
@@ -5168,22 +5402,34 @@ var AgentSession = class {
|
|
|
5168
5402
|
if (/^\s*[\w./\\-]+\.(ts|tsx|js|jsx|json|md|css|py)\s*$/.test(line)) return true;
|
|
5169
5403
|
return false;
|
|
5170
5404
|
};
|
|
5405
|
+
let pendingDelegation = null;
|
|
5406
|
+
const flushDelegation = () => {
|
|
5407
|
+
if (pendingDelegation && this.onDelegation) {
|
|
5408
|
+
const fullPrompt2 = pendingDelegation.lines.join("\n").replace(/\*\*$/, "").trim();
|
|
5409
|
+
console.log(`[Delegation detected] ${this.name} -> @${pendingDelegation.targetName}: ${fullPrompt2.slice(0, 120)}`);
|
|
5410
|
+
this.onDelegation(this.agentId, pendingDelegation.targetName, fullPrompt2);
|
|
5411
|
+
}
|
|
5412
|
+
pendingDelegation = null;
|
|
5413
|
+
};
|
|
5171
5414
|
const handleTextLine = (text) => {
|
|
5172
5415
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
5173
5416
|
const visibleLines = [];
|
|
5174
5417
|
for (const line of lines) {
|
|
5175
5418
|
const trimmed = line.trim();
|
|
5176
5419
|
console.log(`[Agent ${this.name}] ${trimmed.slice(0, 200)}`);
|
|
5177
|
-
const match = trimmed.match(DELEGATION_RE);
|
|
5178
|
-
if (match
|
|
5420
|
+
const match = this._isTeamLead ? trimmed.match(DELEGATION_RE) : null;
|
|
5421
|
+
if (match) {
|
|
5422
|
+
flushDelegation();
|
|
5179
5423
|
const [, targetName, delegatedPrompt] = match;
|
|
5180
|
-
|
|
5181
|
-
|
|
5424
|
+
pendingDelegation = { targetName, lines: [delegatedPrompt] };
|
|
5425
|
+
} else if (pendingDelegation) {
|
|
5426
|
+
pendingDelegation.lines.push(trimmed);
|
|
5182
5427
|
}
|
|
5183
5428
|
if (!isSystemNoise(line)) {
|
|
5184
5429
|
visibleLines.push(trimmed);
|
|
5185
5430
|
}
|
|
5186
5431
|
}
|
|
5432
|
+
flushDelegation();
|
|
5187
5433
|
if (visibleLines.length > 0) {
|
|
5188
5434
|
this.onEvent({
|
|
5189
5435
|
type: "log:append",
|
|
@@ -5218,6 +5464,11 @@ var AgentSession = class {
|
|
|
5218
5464
|
console.log(`[Agent ${this.name}] Session ID: ${msg.session_id}`);
|
|
5219
5465
|
}
|
|
5220
5466
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
5467
|
+
if (msg.message.usage) {
|
|
5468
|
+
const usage = msg.message.usage;
|
|
5469
|
+
if (typeof usage.input_tokens === "number") this.taskInputTokens += usage.input_tokens;
|
|
5470
|
+
if (typeof usage.output_tokens === "number") this.taskOutputTokens += usage.output_tokens;
|
|
5471
|
+
}
|
|
5221
5472
|
for (const block of msg.message.content) {
|
|
5222
5473
|
if (block.type === "text" && block.text) {
|
|
5223
5474
|
this.stdoutBuffer += block.text + "\n";
|
|
@@ -5302,15 +5553,17 @@ var AgentSession = class {
|
|
|
5302
5553
|
} else if (code === 0) {
|
|
5303
5554
|
this.hasHistory = true;
|
|
5304
5555
|
saveSessionId(this.agentId, this.sessionId);
|
|
5305
|
-
const { summary, fullOutput, changedFiles } = this.extractResult();
|
|
5556
|
+
const { summary, fullOutput, changedFiles, entryFile, projectDir, previewCmd, previewPort } = this.extractResult();
|
|
5557
|
+
this._lastFullOutput = fullOutput;
|
|
5306
5558
|
const { previewUrl, previewPath } = this._isTeamLead ? { previewUrl: void 0, previewPath: void 0 } : this.detectPreview();
|
|
5307
5559
|
this._lastResult = `done: ${summary.slice(0, 120)}`;
|
|
5308
5560
|
this.setStatus("done");
|
|
5561
|
+
const tokenUsage = this.taskInputTokens > 0 || this.taskOutputTokens > 0 ? { inputTokens: this.taskInputTokens, outputTokens: this.taskOutputTokens } : void 0;
|
|
5309
5562
|
this.onEvent({
|
|
5310
5563
|
type: "task:done",
|
|
5311
5564
|
agentId: this.agentId,
|
|
5312
5565
|
taskId: completedTaskId,
|
|
5313
|
-
result: { summary, fullOutput, changedFiles, diffStat: "", testResult: "unknown", previewUrl, previewPath }
|
|
5566
|
+
result: { summary, fullOutput, changedFiles, diffStat: "", testResult: "unknown", previewUrl, previewPath, entryFile, projectDir, previewCmd, previewPort, tokenUsage }
|
|
5314
5567
|
});
|
|
5315
5568
|
this.onTaskComplete?.(this.agentId, completedTaskId, summary, true);
|
|
5316
5569
|
this.idleTimer = setTimeout(() => {
|
|
@@ -5378,29 +5631,58 @@ var AgentSession = class {
|
|
|
5378
5631
|
* Called directly for workers; called by orchestrator for leader's final result.
|
|
5379
5632
|
*/
|
|
5380
5633
|
detectPreview() {
|
|
5381
|
-
const
|
|
5382
|
-
|
|
5634
|
+
const result = this.extractResult();
|
|
5635
|
+
const cwd = this.currentCwd ?? this.workspace;
|
|
5636
|
+
let previewUrl;
|
|
5383
5637
|
let previewPath;
|
|
5384
|
-
if (
|
|
5385
|
-
|
|
5386
|
-
|
|
5638
|
+
if (result.previewCmd) {
|
|
5639
|
+
if (result.previewPort) {
|
|
5640
|
+
previewUrl = previewServer.runCommand(result.previewCmd, cwd, result.previewPort);
|
|
5641
|
+
if (previewUrl) return { previewUrl, previewPath: void 0 };
|
|
5642
|
+
} else {
|
|
5643
|
+
return { previewUrl: void 0, previewPath: void 0 };
|
|
5644
|
+
}
|
|
5387
5645
|
}
|
|
5388
|
-
if (
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5646
|
+
if (result.entryFile) {
|
|
5647
|
+
if (/\.html?$/i.test(result.entryFile)) {
|
|
5648
|
+
previewPath = path3.isAbsolute(result.entryFile) ? result.entryFile : path3.join(cwd, result.entryFile);
|
|
5649
|
+
if (existsSync3(previewPath)) {
|
|
5650
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5651
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5652
|
+
}
|
|
5393
5653
|
}
|
|
5394
5654
|
}
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5655
|
+
const previewMatch = this.stdoutBuffer.match(/PREVIEW:\s*(https?:\/\/[^\s*)\]>]+)/i);
|
|
5656
|
+
if (previewMatch) {
|
|
5657
|
+
return { previewUrl: previewMatch[1].replace(/[*)\]>]+$/, ""), previewPath: void 0 };
|
|
5658
|
+
}
|
|
5659
|
+
const fileMatch = this.stdoutBuffer.match(/(?:open\s+)?((?:\/[\w./_-]+|[\w./_-]+)\.html?)\b/i);
|
|
5660
|
+
if (fileMatch) {
|
|
5661
|
+
previewPath = path3.isAbsolute(fileMatch[1]) ? fileMatch[1] : path3.join(cwd, fileMatch[1]);
|
|
5662
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5663
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5664
|
+
}
|
|
5665
|
+
const htmlFile = result.changedFiles.find((f) => /\.html?$/i.test(f));
|
|
5666
|
+
if (htmlFile) {
|
|
5667
|
+
previewPath = path3.isAbsolute(htmlFile) ? htmlFile : path3.join(cwd, htmlFile);
|
|
5668
|
+
previewUrl = previewServer.serve(previewPath);
|
|
5669
|
+
if (previewUrl) return { previewUrl, previewPath };
|
|
5670
|
+
}
|
|
5671
|
+
const candidates = [
|
|
5672
|
+
"dist/index.html",
|
|
5673
|
+
"build/index.html",
|
|
5674
|
+
"out/index.html",
|
|
5675
|
+
"index.html",
|
|
5676
|
+
"public/index.html"
|
|
5677
|
+
];
|
|
5678
|
+
for (const candidate of candidates) {
|
|
5679
|
+
const absPath = path3.join(cwd, candidate);
|
|
5680
|
+
if (existsSync3(absPath)) {
|
|
5681
|
+
previewUrl = previewServer.serve(absPath);
|
|
5682
|
+
if (previewUrl) return { previewUrl, previewPath: absPath };
|
|
5401
5683
|
}
|
|
5402
5684
|
}
|
|
5403
|
-
return { previewUrl, previewPath };
|
|
5685
|
+
return { previewUrl: void 0, previewPath: void 0 };
|
|
5404
5686
|
}
|
|
5405
5687
|
/**
|
|
5406
5688
|
* Parse stdoutBuffer for structured result (SUMMARY/STATUS/FILES_CHANGED).
|
|
@@ -5411,6 +5693,10 @@ var AgentSession = class {
|
|
|
5411
5693
|
const fullOutput = raw.slice(0, 3e3);
|
|
5412
5694
|
const summaryMatch = raw.match(/SUMMARY:\s*(.+)/i);
|
|
5413
5695
|
const filesMatch = raw.match(/FILES_CHANGED:\s*(.+)/i);
|
|
5696
|
+
const entryFileMatch = raw.match(/ENTRY_FILE:\s*(.+)/i);
|
|
5697
|
+
const projectDirMatch = raw.match(/PROJECT_DIR:\s*(.+)/i);
|
|
5698
|
+
const previewCmdMatch = raw.match(/PREVIEW_CMD:\s*(.+)/i);
|
|
5699
|
+
const previewPortMatch = raw.match(/PREVIEW_PORT:\s*(\d+)/i);
|
|
5414
5700
|
const changedFiles = [];
|
|
5415
5701
|
if (filesMatch) {
|
|
5416
5702
|
const fileList = filesMatch[1].trim();
|
|
@@ -5419,8 +5705,12 @@ var AgentSession = class {
|
|
|
5419
5705
|
if (cleaned) changedFiles.push(cleaned);
|
|
5420
5706
|
}
|
|
5421
5707
|
}
|
|
5708
|
+
const entryFile = entryFileMatch?.[1]?.trim();
|
|
5709
|
+
const projectDir = projectDirMatch?.[1]?.trim();
|
|
5710
|
+
const previewCmd = previewCmdMatch?.[1]?.trim();
|
|
5711
|
+
const previewPort = previewPortMatch ? parseInt(previewPortMatch[1], 10) : void 0;
|
|
5422
5712
|
if (summaryMatch) {
|
|
5423
|
-
return { summary: summaryMatch[1].trim(), fullOutput, changedFiles };
|
|
5713
|
+
return { summary: summaryMatch[1].trim(), fullOutput, changedFiles, entryFile, projectDir, previewCmd, previewPort };
|
|
5424
5714
|
}
|
|
5425
5715
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
5426
5716
|
const delegationRe = /^@(\w+):/;
|
|
@@ -5445,17 +5735,17 @@ var AgentSession = class {
|
|
|
5445
5735
|
}
|
|
5446
5736
|
}
|
|
5447
5737
|
if (meaningful.length === 0 && delegationTargets.length > 0) {
|
|
5448
|
-
return { summary: `Delegated tasks to ${delegationTargets.join(", ")}`, fullOutput, changedFiles };
|
|
5738
|
+
return { summary: `Delegated tasks to ${delegationTargets.join(", ")}`, fullOutput, changedFiles, entryFile, projectDir };
|
|
5449
5739
|
}
|
|
5450
5740
|
const lastChunk = meaningful.slice(-5).join("\n").trim();
|
|
5451
5741
|
const summary = lastChunk.slice(0, 500) || "Task completed";
|
|
5452
|
-
return { summary, fullOutput, changedFiles };
|
|
5742
|
+
return { summary, fullOutput, changedFiles, entryFile, projectDir };
|
|
5453
5743
|
}
|
|
5454
5744
|
dequeueNext() {
|
|
5455
5745
|
if (this.taskQueue.length === 0) return;
|
|
5456
5746
|
const next = this.taskQueue.shift();
|
|
5457
5747
|
setTimeout(() => {
|
|
5458
|
-
this.runTask(next.taskId, next.prompt, next.repoPath, next.teamContext);
|
|
5748
|
+
this.runTask(next.taskId, next.prompt, next.repoPath, next.teamContext, false, next.phaseOverride);
|
|
5459
5749
|
}, 100);
|
|
5460
5750
|
}
|
|
5461
5751
|
cancelled = false;
|
|
@@ -5511,16 +5801,33 @@ var AgentSession = class {
|
|
|
5511
5801
|
this.idleTimer = null;
|
|
5512
5802
|
}
|
|
5513
5803
|
if (this.process?.pid) {
|
|
5804
|
+
const pgid = this.process.pid;
|
|
5514
5805
|
try {
|
|
5515
|
-
process.kill(-
|
|
5806
|
+
process.kill(-pgid, "SIGKILL");
|
|
5516
5807
|
} catch {
|
|
5517
|
-
|
|
5808
|
+
try {
|
|
5809
|
+
this.process.kill("SIGKILL");
|
|
5810
|
+
} catch {
|
|
5811
|
+
}
|
|
5518
5812
|
}
|
|
5519
5813
|
this.process = null;
|
|
5520
5814
|
}
|
|
5521
5815
|
this.pendingApprovals.clear();
|
|
5522
5816
|
saveSessionId(this.agentId, null);
|
|
5523
5817
|
}
|
|
5818
|
+
/** Reset conversation history so the next task starts fresh (used by End Project). */
|
|
5819
|
+
clearHistory() {
|
|
5820
|
+
this.hasHistory = false;
|
|
5821
|
+
this.sessionId = null;
|
|
5822
|
+
this.originalTask = null;
|
|
5823
|
+
this.currentPhase = null;
|
|
5824
|
+
this._hasExecuted = false;
|
|
5825
|
+
this._lastResult = null;
|
|
5826
|
+
this._lastResultText = null;
|
|
5827
|
+
this._lastFullOutput = null;
|
|
5828
|
+
this.setStatus("idle");
|
|
5829
|
+
saveSessionId(this.agentId, null);
|
|
5830
|
+
}
|
|
5524
5831
|
resolveApproval(approvalId, decision) {
|
|
5525
5832
|
if (approvalId === "__all__") {
|
|
5526
5833
|
for (const [, pending2] of this.pendingApprovals) {
|
|
@@ -5580,7 +5887,8 @@ var AgentManager = class {
|
|
|
5580
5887
|
const lines = [];
|
|
5581
5888
|
for (const session of this.agents.values()) {
|
|
5582
5889
|
const lead = this.isTeamLead(session.agentId) ? " (Team Lead)" : "";
|
|
5583
|
-
const
|
|
5890
|
+
const raw = session.lastResult ?? "";
|
|
5891
|
+
const result = raw ? ` \u2014 ${raw.length > 100 ? raw.slice(0, 100) + "\u2026" : raw}` : "";
|
|
5584
5892
|
lines.push(`- ${session.name} (${session.role}) [${session.status}]${lead}${result}`);
|
|
5585
5893
|
}
|
|
5586
5894
|
return lines.join("\n");
|
|
@@ -5626,10 +5934,12 @@ var AgentManager = class {
|
|
|
5626
5934
|
|
|
5627
5935
|
// ../../packages/orchestrator/src/delegation.ts
|
|
5628
5936
|
import { nanoid as nanoid3 } from "nanoid";
|
|
5937
|
+
import path4 from "path";
|
|
5629
5938
|
var MAX_DELEGATION_DEPTH = 5;
|
|
5630
5939
|
var MAX_TOTAL_DELEGATIONS = 20;
|
|
5631
|
-
var DELEGATION_BUDGET_ROUNDS =
|
|
5940
|
+
var DELEGATION_BUDGET_ROUNDS = 7;
|
|
5632
5941
|
var HARD_CEILING_ROUNDS = 10;
|
|
5942
|
+
var MAX_REVIEW_ROUNDS = 3;
|
|
5633
5943
|
var RESULT_BATCH_WINDOW_MS = 2e4;
|
|
5634
5944
|
var DelegationRouter = class {
|
|
5635
5945
|
/** taskId → fromAgentId */
|
|
@@ -5642,6 +5952,8 @@ var DelegationRouter = class {
|
|
|
5642
5952
|
totalDelegations = 0;
|
|
5643
5953
|
/** How many times the leader has been invoked to process results */
|
|
5644
5954
|
leaderRounds = 0;
|
|
5955
|
+
/** How many times a Code Reviewer result has been forwarded to the leader */
|
|
5956
|
+
reviewCount = 0;
|
|
5645
5957
|
/** When true, all new delegations and result forwarding are blocked */
|
|
5646
5958
|
stopped = false;
|
|
5647
5959
|
/** TaskIds created by flushResults — only these can produce a final result */
|
|
@@ -5650,6 +5962,8 @@ var DelegationRouter = class {
|
|
|
5650
5962
|
delegationsAtResultStart = /* @__PURE__ */ new Map();
|
|
5651
5963
|
/** Batch result forwarding: originAgentId → pending results + timer */
|
|
5652
5964
|
pendingResults = /* @__PURE__ */ new Map();
|
|
5965
|
+
/** Team-wide project directory — all delegations use this as repoPath when set */
|
|
5966
|
+
teamProjectDir = null;
|
|
5653
5967
|
agentManager;
|
|
5654
5968
|
promptEngine;
|
|
5655
5969
|
emitEvent;
|
|
@@ -5683,7 +5997,7 @@ var DelegationRouter = class {
|
|
|
5683
5997
|
* if the current task is not a "resultTask" (safety net for convergence).
|
|
5684
5998
|
*/
|
|
5685
5999
|
isBudgetExhausted() {
|
|
5686
|
-
return this.leaderRounds >= DELEGATION_BUDGET_ROUNDS;
|
|
6000
|
+
return this.leaderRounds >= DELEGATION_BUDGET_ROUNDS || this.reviewCount >= MAX_REVIEW_ROUNDS;
|
|
5687
6001
|
}
|
|
5688
6002
|
/**
|
|
5689
6003
|
* True if the given resultTask completed WITHOUT creating any new delegations.
|
|
@@ -5724,6 +6038,16 @@ var DelegationRouter = class {
|
|
|
5724
6038
|
}
|
|
5725
6039
|
this.pendingResults.clear();
|
|
5726
6040
|
}
|
|
6041
|
+
/**
|
|
6042
|
+
* Set a team-wide project directory. All delegations will use this as repoPath.
|
|
6043
|
+
*/
|
|
6044
|
+
setTeamProjectDir(dir) {
|
|
6045
|
+
this.teamProjectDir = dir;
|
|
6046
|
+
if (dir) console.log(`[Delegation] Team project dir set: ${dir}`);
|
|
6047
|
+
}
|
|
6048
|
+
getTeamProjectDir() {
|
|
6049
|
+
return this.teamProjectDir;
|
|
6050
|
+
}
|
|
5727
6051
|
/**
|
|
5728
6052
|
* Reset all delegation state (on new team task).
|
|
5729
6053
|
*/
|
|
@@ -5735,7 +6059,9 @@ var DelegationRouter = class {
|
|
|
5735
6059
|
this.delegationsAtResultStart.clear();
|
|
5736
6060
|
this.totalDelegations = 0;
|
|
5737
6061
|
this.leaderRounds = 0;
|
|
6062
|
+
this.reviewCount = 0;
|
|
5738
6063
|
this.stopped = false;
|
|
6064
|
+
this.teamProjectDir = null;
|
|
5739
6065
|
for (const pending of this.pendingResults.values()) {
|
|
5740
6066
|
clearTimeout(pending.timer);
|
|
5741
6067
|
}
|
|
@@ -5744,8 +6070,13 @@ var DelegationRouter = class {
|
|
|
5744
6070
|
wireDelegation(session) {
|
|
5745
6071
|
session.onDelegation = (fromAgentId, targetName, prompt) => {
|
|
5746
6072
|
if (this.stopped) return;
|
|
5747
|
-
|
|
5748
|
-
|
|
6073
|
+
const phaseCheckSession = this.agentManager.get(fromAgentId);
|
|
6074
|
+
if (phaseCheckSession?.currentPhase && phaseCheckSession.currentPhase !== "execute") {
|
|
6075
|
+
console.log(`[Delegation] BLOCKED: agent ${fromAgentId} is in phase "${phaseCheckSession.currentPhase}", not "execute"`);
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
if (this.isBudgetExhausted()) {
|
|
6079
|
+
console.log(`[Delegation] BLOCKED: budget exhausted (leaderRounds=${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS}, reviewCount=${this.reviewCount}/${MAX_REVIEW_ROUNDS})`);
|
|
5749
6080
|
return;
|
|
5750
6081
|
}
|
|
5751
6082
|
const target = this.agentManager.findByName(targetName);
|
|
@@ -5785,14 +6116,27 @@ var DelegationRouter = class {
|
|
|
5785
6116
|
const fromSession = this.agentManager.get(fromAgentId);
|
|
5786
6117
|
const fromName = fromSession?.name ?? fromAgentId;
|
|
5787
6118
|
const fromRole = fromSession?.role ?? "Team Lead";
|
|
5788
|
-
|
|
5789
|
-
|
|
6119
|
+
let repoPath = this.teamProjectDir ?? void 0;
|
|
6120
|
+
let cleanPrompt = prompt;
|
|
6121
|
+
const dirMatch = prompt.match(/^\s*\[([^\]]+)\]\s*/);
|
|
6122
|
+
if (dirMatch) {
|
|
6123
|
+
cleanPrompt = prompt.slice(dirMatch[0].length);
|
|
6124
|
+
if (!repoPath) {
|
|
6125
|
+
const dirPart = dirMatch[1].replace(/\/$/, "");
|
|
6126
|
+
const leaderSession = this.agentManager.get(fromAgentId);
|
|
6127
|
+
if (leaderSession) {
|
|
6128
|
+
repoPath = path4.resolve(leaderSession.workspaceDir, dirPart);
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
const fullPrompt = this.promptEngine.render("delegation-prefix", { fromName, fromRole, prompt: cleanPrompt });
|
|
6133
|
+
console.log(`[Delegation] ${fromAgentId} -> ${target.agentId} (${targetName}) depth=${newDepth} total=${this.totalDelegations} repoPath=${repoPath ?? "default"}: ${cleanPrompt.slice(0, 80)}`);
|
|
5790
6134
|
this.emitEvent({
|
|
5791
6135
|
type: "task:delegated",
|
|
5792
6136
|
fromAgentId,
|
|
5793
6137
|
toAgentId: target.agentId,
|
|
5794
6138
|
taskId,
|
|
5795
|
-
prompt
|
|
6139
|
+
prompt: cleanPrompt
|
|
5796
6140
|
});
|
|
5797
6141
|
this.emitEvent({
|
|
5798
6142
|
type: "team:chat",
|
|
@@ -5804,7 +6148,7 @@ var DelegationRouter = class {
|
|
|
5804
6148
|
timestamp: Date.now()
|
|
5805
6149
|
});
|
|
5806
6150
|
this.assignedTask.set(target.agentId, taskId);
|
|
5807
|
-
target.runTask(taskId, fullPrompt);
|
|
6151
|
+
target.runTask(taskId, fullPrompt, repoPath);
|
|
5808
6152
|
};
|
|
5809
6153
|
}
|
|
5810
6154
|
wireResultForwarding(session) {
|
|
@@ -5879,6 +6223,13 @@ var DelegationRouter = class {
|
|
|
5879
6223
|
const originSession = this.agentManager.get(originAgentId);
|
|
5880
6224
|
if (!originSession) return;
|
|
5881
6225
|
this.leaderRounds++;
|
|
6226
|
+
for (const r of pending.results) {
|
|
6227
|
+
const agent = this.agentManager.findByName(r.fromName);
|
|
6228
|
+
if (agent && agent.role.toLowerCase().includes("review")) {
|
|
6229
|
+
this.reviewCount++;
|
|
6230
|
+
console.log(`[ResultBatch] Reviewer result detected (reviewCount=${this.reviewCount})`);
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
5882
6233
|
if (this.leaderRounds > HARD_CEILING_ROUNDS) {
|
|
5883
6234
|
console.log(`[ResultBatch] Hard ceiling reached (${HARD_CEILING_ROUNDS} rounds). Force-completing.`);
|
|
5884
6235
|
const resultLines2 = pending.results.map(
|
|
@@ -5908,12 +6259,13 @@ ${resultLines2}`,
|
|
|
5908
6259
|
}
|
|
5909
6260
|
let roundInfo;
|
|
5910
6261
|
const budgetExhausted = this.leaderRounds >= DELEGATION_BUDGET_ROUNDS;
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6262
|
+
const reviewExhausted = this.reviewCount >= MAX_REVIEW_ROUNDS;
|
|
6263
|
+
if (budgetExhausted || reviewExhausted) {
|
|
6264
|
+
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.`;
|
|
6265
|
+
} else if (this.reviewCount > 0) {
|
|
6266
|
+
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS} | Review ${this.reviewCount}/${MAX_REVIEW_ROUNDS} (fix iteration ${this.reviewCount})`;
|
|
5915
6267
|
} else {
|
|
5916
|
-
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS}`;
|
|
6268
|
+
roundInfo = `Round ${this.leaderRounds}/${DELEGATION_BUDGET_ROUNDS} | No reviews yet`;
|
|
5917
6269
|
}
|
|
5918
6270
|
const resultLines = pending.results.map(
|
|
5919
6271
|
(r) => `- ${r.fromName} (${r.statusWord}): ${r.summary}`
|
|
@@ -5935,8 +6287,8 @@ ${resultLines2}`,
|
|
|
5935
6287
|
};
|
|
5936
6288
|
|
|
5937
6289
|
// ../../packages/orchestrator/src/prompt-templates.ts
|
|
5938
|
-
import { readFileSync as
|
|
5939
|
-
import
|
|
6290
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
|
|
6291
|
+
import path5 from "path";
|
|
5940
6292
|
var PROMPT_DEFAULTS = {
|
|
5941
6293
|
"leader-initial": `You are {{name}}, the Team Lead. {{personality}}
|
|
5942
6294
|
You CANNOT write code, run commands, or use any tools. You can ONLY delegate.
|
|
@@ -5945,22 +6297,34 @@ Team:
|
|
|
5945
6297
|
{{teamRoster}}
|
|
5946
6298
|
|
|
5947
6299
|
Delegate using this exact format (one per line):
|
|
5948
|
-
@AgentName:
|
|
6300
|
+
@AgentName: task description
|
|
6301
|
+
|
|
6302
|
+
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.
|
|
6303
|
+
|
|
6304
|
+
===== DELEGATION RULES =====
|
|
6305
|
+
|
|
6306
|
+
CRITICAL \u2014 How to assign work to developers:
|
|
6307
|
+
- Give each developer ONE complete, end-to-end task that produces a RUNNABLE deliverable.
|
|
6308
|
+
- The developer is responsible for EVERYTHING: project setup, dependencies, all source files, build configuration, and verification.
|
|
6309
|
+
- 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.
|
|
6310
|
+
- 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."
|
|
6311
|
+
- WRONG example: "@Leo: Create src/audio/AudioManager.ts" then "@Leo: Create src/game/GameScene.ts" \u2014 this produces isolated modules that can't run.
|
|
6312
|
+
- If you have multiple developers, split by FEATURE AREA (each producing a runnable piece), not by FILE.
|
|
5949
6313
|
|
|
5950
|
-
|
|
6314
|
+
===== EXECUTION PHASES =====
|
|
5951
6315
|
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6316
|
+
1. BUILD (this round): Assign developers now. Each dev must deliver a working, verifiable result.
|
|
6317
|
+
2. REVIEW: When dev results come back, assign Code Reviewer to check the code.
|
|
6318
|
+
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.
|
|
6319
|
+
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
6320
|
|
|
5957
6321
|
Rules:
|
|
5958
6322
|
- Never write code yourself. Only delegate.
|
|
5959
|
-
-
|
|
5960
|
-
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
6323
|
+
- Phase 1 (this round): Assign developers ONLY. Do NOT assign Code Reviewer yet \u2014 there is no code to review.
|
|
6324
|
+
- Skip review for trivial changes (config, typo, rename).
|
|
6325
|
+
|
|
6326
|
+
Approved plan:
|
|
6327
|
+
{{originalTask}}
|
|
5964
6328
|
|
|
5965
6329
|
Task: {{prompt}}`,
|
|
5966
6330
|
"leader-continue": `You are {{name}}, the Team Lead. {{personality}}
|
|
@@ -5974,7 +6338,7 @@ Team status:
|
|
|
5974
6338
|
Delegate using: @AgentName: task description
|
|
5975
6339
|
|
|
5976
6340
|
{{prompt}}`,
|
|
5977
|
-
"leader-result": `You are the Team Lead. You CANNOT write or fix code. You can ONLY delegate using @Name:
|
|
6341
|
+
"leader-result": `You are the Team Lead. You CANNOT write or fix code. You can ONLY delegate using @Name: <task>.
|
|
5978
6342
|
|
|
5979
6343
|
Original user task: {{originalTask}}
|
|
5980
6344
|
|
|
@@ -5986,21 +6350,47 @@ Team status:
|
|
|
5986
6350
|
New result from {{fromName}} ({{resultStatus}}):
|
|
5987
6351
|
{{resultSummary}}
|
|
5988
6352
|
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
6353
|
+
===== DECISION FLOW =====
|
|
6354
|
+
|
|
6355
|
+
Check WHO sent this result, then follow the matching branch:
|
|
6356
|
+
|
|
6357
|
+
\u2500\u2500 RESULT FROM A DEVELOPER \u2500\u2500
|
|
6358
|
+
If STATUS=done:
|
|
6359
|
+
\u2192 Assign Code Reviewer to check the code. In your delegation, include:
|
|
6360
|
+
1. Dev's ENTRY_FILE/PREVIEW_CMD so reviewer knows what was built.
|
|
6361
|
+
2. The KEY FEATURES from the approved plan (3-5 bullet points) so reviewer can verify feature completeness.
|
|
6362
|
+
\u2192 Exception: skip review for trivial changes (config, typo, rename) \u2014 go straight to FINAL SUMMARY.
|
|
6363
|
+
If STATUS=failed:
|
|
6364
|
+
\u2192 Delegate ONE targeted fix to the same developer. Be specific about what failed.
|
|
6365
|
+
|
|
6366
|
+
\u2500\u2500 RESULT FROM CODE REVIEWER \u2500\u2500
|
|
6367
|
+
Reviewer output format: VERDICT (PASS/FAIL), ISSUES (numbered list), SUGGESTIONS (optional).
|
|
6368
|
+
If VERDICT=PASS:
|
|
6369
|
+
\u2192 Output FINAL SUMMARY. Copy ENTRY_FILE/PREVIEW fields from the developer's last report. You are DONE.
|
|
6370
|
+
If VERDICT=FAIL:
|
|
6371
|
+
\u2192 Collect ALL issues into ONE fix delegation to the original developer.
|
|
6372
|
+
\u2192 Quote each issue verbatim. Remind dev: after fixing, rebuild and verify the deliverable works.
|
|
6373
|
+
\u2192 After dev returns with the fix, assign Code Reviewer again to re-check.
|
|
6374
|
+
|
|
6375
|
+
\u2500\u2500 SPECIAL CASES \u2500\u2500
|
|
6376
|
+
\u2022 If roundInfo says "REVIEW LIMIT REACHED" or "BUDGET REACHED" \u2192 Output FINAL SUMMARY immediately. Accept the work as-is.
|
|
6377
|
+
\u2022 Permanent blocker (auth error, missing API key, service down) \u2192 report to the user, do not retry.
|
|
6378
|
+
\u2022 Same error repeated twice \u2192 STOP and report to the user.
|
|
5997
6379
|
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6380
|
+
===== FINAL SUMMARY FORMAT =====
|
|
6381
|
+
(Copy preview fields EXACTLY from the developer's LAST successful report. Only include fields the dev actually provided \u2014 do NOT invent values.)
|
|
6382
|
+
|
|
6383
|
+
ENTRY_FILE: <from dev \u2014 e.g. index.html, dist/index.html. OMIT if dev didn't provide one>
|
|
6384
|
+
PREVIEW_CMD: <from dev \u2014 e.g. "python app.py", "node server.js". OMIT if dev didn't provide one>
|
|
6385
|
+
PREVIEW_PORT: <from dev \u2014 e.g. 5000, 3000. OMIT if dev didn't provide one>
|
|
6386
|
+
SUMMARY: <2-3 sentence description of what was built>
|
|
6387
|
+
|
|
6388
|
+
RULES:
|
|
6389
|
+
- VERDICT=PASS means done, even if SUGGESTIONS exist. Suggestions are non-blocking.
|
|
6390
|
+
- VERDICT=FAIL means real bugs \u2014 always delegate a fix before finalizing.
|
|
6391
|
+
- In every fix delegation, remind dev to rebuild and re-test before reporting.
|
|
6392
|
+
- You MUST include ENTRY_FILE or PREVIEW_CMD in your FINAL SUMMARY \u2014 the user needs this to preview.
|
|
6393
|
+
- Do NOT include PROJECT_DIR \u2014 the system manages project directories automatically.`,
|
|
6004
6394
|
"worker-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
|
|
6005
6395
|
|
|
6006
6396
|
CONVERGENCE RULES (follow strictly):
|
|
@@ -6008,24 +6398,197 @@ CONVERGENCE RULES (follow strictly):
|
|
|
6008
6398
|
- Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
|
|
6009
6399
|
- If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
|
|
6010
6400
|
- 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
6401
|
|
|
6013
6402
|
HARD LIMITS:
|
|
6014
|
-
- Do NOT start any dev server
|
|
6015
|
-
-
|
|
6403
|
+
- Do NOT start any long-running dev server or file server. The system handles preview serving automatically.
|
|
6404
|
+
- You MAY install dependencies if the project needs them (npm install, pip install, etc.).
|
|
6405
|
+
{{soloHint}}
|
|
6406
|
+
|
|
6407
|
+
Start with one sentence describing your approach. Then do the work.
|
|
6408
|
+
|
|
6409
|
+
You are responsible for the COMPLETE deliverable \u2014 not just source files. This means:
|
|
6410
|
+
1. Project setup: create all config files needed (package.json, tsconfig, build config, requirements.txt, etc.)
|
|
6411
|
+
2. All source code
|
|
6412
|
+
3. Build & verify: if the project has a build step, RUN IT and fix errors until it passes
|
|
6413
|
+
4. Report how to run/preview the result (see deliverable types below)
|
|
6414
|
+
|
|
6415
|
+
VERIFICATION (MANDATORY before reporting STATUS: done):
|
|
6416
|
+
- If you created a package.json with a build script \u2192 run the build, fix errors until it succeeds, confirm the output file exists
|
|
6417
|
+
- If your deliverable is an HTML file \u2192 confirm it exists and references valid scripts/styles
|
|
6418
|
+
- 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)
|
|
6419
|
+
- If NONE of the above apply \u2192 at minimum list the files and confirm the entry point exists
|
|
6420
|
+
- 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.
|
|
6421
|
+
- 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.
|
|
6422
|
+
- Do NOT report STATUS: done unless verification passes. Fix problems yourself first.
|
|
6423
|
+
- STATUS: failed is ONLY for truly unsolvable problems (missing API keys, no network, system-level issues).
|
|
6424
|
+
|
|
6425
|
+
===== DELIVERABLE TYPES =====
|
|
6426
|
+
Your project falls into one of these categories. Report the matching fields:
|
|
6427
|
+
|
|
6428
|
+
A) STATIC WEB (HTML/CSS/JS \u2014 no server needed):
|
|
6429
|
+
ENTRY_FILE: index.html (the HTML file to open \u2014 e.g. index.html, dist/index.html, build/index.html)
|
|
6016
6430
|
|
|
6017
|
-
|
|
6431
|
+
B) WEB SERVER (Flask, Express, Sinatra, Rails, Go net/http, etc. \u2014 serves on a port):
|
|
6432
|
+
PREVIEW_CMD: python app.py (the command to start the server)
|
|
6433
|
+
PREVIEW_PORT: 5000 (the port the server listens on \u2014 REQUIRED for web servers)
|
|
6434
|
+
|
|
6435
|
+
C) DESKTOP/CLI APP (Pygame, Tkinter, Electron, JavaFX, terminal tool, native GUI, etc.):
|
|
6436
|
+
PREVIEW_CMD: python game.py (the command to launch the app \u2014 NO PREVIEW_PORT needed)
|
|
6437
|
+
|
|
6438
|
+
OUTPUT:
|
|
6018
6439
|
|
|
6019
|
-
When you finish, report your result in this exact format:
|
|
6020
6440
|
STATUS: done | failed
|
|
6021
|
-
FILES_CHANGED: (list
|
|
6022
|
-
|
|
6441
|
+
FILES_CHANGED: (list all files created or modified, one per line)
|
|
6442
|
+
ENTRY_FILE: (type A only \u2014 path to the HTML file)
|
|
6443
|
+
PREVIEW_CMD: (types B and C \u2014 command to start the app or server)
|
|
6444
|
+
PREVIEW_PORT: (type B only \u2014 the port the server listens on)
|
|
6445
|
+
SUMMARY: (one sentence: what you built + how to run/preview it)
|
|
6446
|
+
|
|
6447
|
+
You MUST provide at least ENTRY_FILE or PREVIEW_CMD.
|
|
6448
|
+
|
|
6449
|
+
{{prompt}}`,
|
|
6450
|
+
"worker-reviewer-initial": `Your name is {{name}}, your role is {{role}}. {{personality}}
|
|
6451
|
+
|
|
6452
|
+
CONVERGENCE RULES (follow strictly):
|
|
6453
|
+
- Do the MINIMUM needed to satisfy the task. Simple and working beats perfect and slow.
|
|
6454
|
+
- Only touch files directly required by this task. Do NOT refactor, clean up, or "improve" unrelated code.
|
|
6455
|
+
- If you are uncertain between two approaches, choose the simpler one and note it in SUMMARY.
|
|
6456
|
+
- Do NOT add features, error handling, or improvements that were not explicitly asked for.
|
|
6457
|
+
|
|
6458
|
+
HARD LIMITS:
|
|
6459
|
+
- Do NOT start any long-running dev server or file server. The system handles preview serving automatically.
|
|
6460
|
+
- 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.
|
|
6461
|
+
|
|
6462
|
+
Code Quality (must check):
|
|
6463
|
+
- Correctness, security vulnerabilities, crashes, broken logic.
|
|
6464
|
+
- 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.
|
|
6465
|
+
|
|
6466
|
+
Feature Completeness (must check):
|
|
6467
|
+
- Compare the deliverable against the key features listed in your task assignment.
|
|
6468
|
+
- Flag CORE features that are completely missing or non-functional as ISSUES.
|
|
6469
|
+
- Do NOT fail for polish, extras, or stretch goals \u2014 this is a prototype. Focus on whether the main functionality works.
|
|
6470
|
+
|
|
6471
|
+
Do NOT nitpick style, naming, or formatting. This is a prototype, not production code.
|
|
6472
|
+
|
|
6473
|
+
VERDICT: PASS | FAIL
|
|
6474
|
+
- PASS = code runs without crashes AND core features are implemented (even if rough)
|
|
6475
|
+
- FAIL = crashes/bugs that prevent usage OR core features are missing/broken
|
|
6476
|
+
ISSUES: (numbered list \u2014 bugs, security problems, or missing core features)
|
|
6477
|
+
SUGGESTIONS: (optional \u2014 minor non-blocking observations, keep brief)
|
|
6478
|
+
SUMMARY: (one sentence overall assessment)
|
|
6023
6479
|
|
|
6024
6480
|
{{prompt}}`,
|
|
6025
6481
|
"worker-continue": `{{prompt}}`,
|
|
6026
6482
|
"delegation-prefix": `[Assigned by {{fromName}} ({{fromRole}})]
|
|
6027
6483
|
{{prompt}}`,
|
|
6028
|
-
"delegation-hint": `To delegate a task to another agent, output on its own line: @AgentName: <task description
|
|
6484
|
+
"delegation-hint": `To delegate a task to another agent, output on its own line: @AgentName: <task description>`,
|
|
6485
|
+
"leader-create": `You are {{name}}, the team's Creative Director and Product Consultant. {{personality}}
|
|
6486
|
+
You are starting a new project conversation with the user. Your dual role:
|
|
6487
|
+
1. CREATIVE DIRECTOR \u2014 design the product vision: theme, look & feel, user experience, what makes it unique
|
|
6488
|
+
2. PRODUCT CONSULTANT \u2014 turn that vision into a clear, buildable plan
|
|
6489
|
+
|
|
6490
|
+
Rules:
|
|
6491
|
+
- Be conversational, warm, and concise.
|
|
6492
|
+
- Ask at most 1-2 clarifying questions, then produce a plan. Do NOT over-question.
|
|
6493
|
+
- 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.
|
|
6494
|
+
- The goal is a WORKING PROTOTYPE, not a production system.
|
|
6495
|
+
- When ready, produce a project plan wrapped in [PLAN]...[/PLAN] tags.
|
|
6496
|
+
|
|
6497
|
+
===== PLAN FORMAT (strict \u2014 follow this structure) =====
|
|
6498
|
+
|
|
6499
|
+
[PLAN]
|
|
6500
|
+
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")
|
|
6501
|
+
|
|
6502
|
+
CREATIVE VISION:
|
|
6503
|
+
- Theme & setting (e.g. "pixel cityscape at night", "cozy forest caf\xE9")
|
|
6504
|
+
- Visual style (e.g. "retro pixel art", "flat minimalist", "hand-drawn sketch")
|
|
6505
|
+
- Core experience \u2014 what does the user SEE and FEEL when using it?
|
|
6506
|
+
|
|
6507
|
+
FEATURES:
|
|
6508
|
+
- (3-5 bullet points describing WHAT the product does from the user's perspective)
|
|
6509
|
+
- (focus on interactions, content, and behavior \u2014 NOT technical implementation)
|
|
6510
|
+
|
|
6511
|
+
TECH: (one line \u2014 e.g. "Vanilla JS + Canvas" or "React + Tailwind")
|
|
6512
|
+
|
|
6513
|
+
ASSIGNMENTS:
|
|
6514
|
+
- @DevName: (one-sentence summary of what they build)
|
|
6515
|
+
[/PLAN]
|
|
6516
|
+
|
|
6517
|
+
===== ANTI-PATTERNS (never do these) =====
|
|
6518
|
+
- Do NOT write technical implementation steps (e.g. "implement game loop with requestAnimationFrame") \u2014 that is the developer's job.
|
|
6519
|
+
- 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.
|
|
6520
|
+
- Do NOT produce a checklist of modules or files. The plan is a PRODUCT DESCRIPTION, not a technical spec.
|
|
6521
|
+
- Do NOT include milestones, risk analysis, acceptance criteria, or deployment plans.
|
|
6522
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax outside [PLAN] tags.
|
|
6523
|
+
|
|
6524
|
+
If the user hasn't described their project yet, greet them and ask what they'd like to build.
|
|
6525
|
+
|
|
6526
|
+
Team:
|
|
6527
|
+
{{teamRoster}}
|
|
6528
|
+
|
|
6529
|
+
{{prompt}}`,
|
|
6530
|
+
"leader-create-continue": `You are {{name}}, the team's Creative Director and Product Consultant. {{personality}}
|
|
6531
|
+
Do NOT greet or re-introduce yourself \u2014 the conversation is already underway.
|
|
6532
|
+
|
|
6533
|
+
The user replied: {{prompt}}
|
|
6534
|
+
|
|
6535
|
+
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.
|
|
6536
|
+
|
|
6537
|
+
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.`,
|
|
6538
|
+
"leader-design": `You are {{name}}, the team's Creative Director, refining the project vision with the user. {{personality}}
|
|
6539
|
+
The user has given feedback on your plan. Your job is to REVISE the existing plan, not start over.
|
|
6540
|
+
|
|
6541
|
+
===== CRITICAL: INCREMENTAL UPDATE =====
|
|
6542
|
+
- User feedback is usually a PARTIAL adjustment (e.g. "use PixiJS", "make it darker", "add multiplayer").
|
|
6543
|
+
- Apply the feedback to the EXISTING plan. Keep everything the user did NOT mention.
|
|
6544
|
+
- NEVER discard the original concept, story, characters, or gameplay just because the user asked for a tech or style change.
|
|
6545
|
+
- 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.
|
|
6546
|
+
- Think of it as editing a document, not writing a new one.
|
|
6547
|
+
|
|
6548
|
+
Rules:
|
|
6549
|
+
- Address the user's feedback directly and show what changed.
|
|
6550
|
+
- Always output the updated plan in [PLAN]...[/PLAN] tags using the standard format: CONCEPT, CREATIVE VISION, FEATURES, TECH, ASSIGNMENTS.
|
|
6551
|
+
- The plan describes the PRODUCT VISION \u2014 what users see, feel, and experience. NOT technical implementation steps.
|
|
6552
|
+
- Keep it prototype-focused. No milestones, risk analysis, or deployment plans.
|
|
6553
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax outside [PLAN] tags.
|
|
6554
|
+
|
|
6555
|
+
Team:
|
|
6556
|
+
{{teamRoster}}
|
|
6557
|
+
|
|
6558
|
+
Previous plan context: {{originalTask}}
|
|
6559
|
+
|
|
6560
|
+
User feedback: {{prompt}}`,
|
|
6561
|
+
"leader-design-continue": `You are {{name}}, the team's Creative Director, refining the project vision. {{personality}}
|
|
6562
|
+
|
|
6563
|
+
Your current plan:
|
|
6564
|
+
{{originalTask}}
|
|
6565
|
+
|
|
6566
|
+
The user replied: {{prompt}}
|
|
6567
|
+
|
|
6568
|
+
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.
|
|
6569
|
+
|
|
6570
|
+
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.`,
|
|
6571
|
+
"leader-complete": `You are {{name}}, presenting completed work to the user. {{personality}}
|
|
6572
|
+
The team has finished executing the project. Summarize what was accomplished and ask if the user wants any changes.
|
|
6573
|
+
|
|
6574
|
+
Rules:
|
|
6575
|
+
- Be concise and highlight key outcomes.
|
|
6576
|
+
- If the user provides feedback, note it \u2014 the system will transition back to execute phase.
|
|
6577
|
+
- Do NOT delegate. Do NOT write code. Do NOT use @AgentName: syntax.
|
|
6578
|
+
|
|
6579
|
+
Team:
|
|
6580
|
+
{{teamRoster}}
|
|
6581
|
+
|
|
6582
|
+
Original task: {{originalTask}}
|
|
6583
|
+
|
|
6584
|
+
{{prompt}}`,
|
|
6585
|
+
"leader-complete-continue": `You are {{name}}, discussing the completed project with the user. {{personality}}
|
|
6586
|
+
|
|
6587
|
+
Original task: {{originalTask}}
|
|
6588
|
+
|
|
6589
|
+
The user replied: {{prompt}}
|
|
6590
|
+
|
|
6591
|
+
Address their feedback. Do NOT delegate or write code.`
|
|
6029
6592
|
};
|
|
6030
6593
|
var PromptEngine = class {
|
|
6031
6594
|
templates = { ...PROMPT_DEFAULTS };
|
|
@@ -6035,21 +6598,24 @@ var PromptEngine = class {
|
|
|
6035
6598
|
}
|
|
6036
6599
|
/**
|
|
6037
6600
|
* Initialize prompt templates on startup.
|
|
6038
|
-
*
|
|
6039
|
-
*
|
|
6601
|
+
* Always writes built-in defaults to disk so new/updated templates take effect.
|
|
6602
|
+
* Users can still customize — edits are preserved until the next code update changes a template.
|
|
6040
6603
|
*/
|
|
6041
6604
|
init() {
|
|
6042
6605
|
if (!this.promptsDir) {
|
|
6043
6606
|
console.log(`[Prompts] No promptsDir configured, using ${Object.keys(PROMPT_DEFAULTS).length} default templates`);
|
|
6044
6607
|
return;
|
|
6045
6608
|
}
|
|
6046
|
-
if (!
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6609
|
+
if (!existsSync4(this.promptsDir)) {
|
|
6610
|
+
mkdirSync4(this.promptsDir, { recursive: true });
|
|
6611
|
+
}
|
|
6612
|
+
let written = 0;
|
|
6613
|
+
for (const [name, content] of Object.entries(PROMPT_DEFAULTS)) {
|
|
6614
|
+
const filePath = path5.join(this.promptsDir, `${name}.md`);
|
|
6615
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
6616
|
+
written++;
|
|
6052
6617
|
}
|
|
6618
|
+
console.log(`[Prompts] Synced ${written} default templates to ${this.promptsDir}`);
|
|
6053
6619
|
this.reload();
|
|
6054
6620
|
}
|
|
6055
6621
|
/**
|
|
@@ -6061,10 +6627,10 @@ var PromptEngine = class {
|
|
|
6061
6627
|
let defaulted = 0;
|
|
6062
6628
|
if (this.promptsDir) {
|
|
6063
6629
|
for (const name of Object.keys(PROMPT_DEFAULTS)) {
|
|
6064
|
-
const filePath =
|
|
6065
|
-
if (
|
|
6630
|
+
const filePath = path5.join(this.promptsDir, `${name}.md`);
|
|
6631
|
+
if (existsSync4(filePath)) {
|
|
6066
6632
|
try {
|
|
6067
|
-
merged[name] =
|
|
6633
|
+
merged[name] = readFileSync4(filePath, "utf-8");
|
|
6068
6634
|
loaded++;
|
|
6069
6635
|
} catch {
|
|
6070
6636
|
defaulted++;
|
|
@@ -6191,7 +6757,7 @@ IMPORTANT: If the same error keeps repeating, choose option 3. Do not waste reso
|
|
|
6191
6757
|
|
|
6192
6758
|
// ../../packages/orchestrator/src/worktree.ts
|
|
6193
6759
|
import { execSync as execSync3 } from "child_process";
|
|
6194
|
-
import
|
|
6760
|
+
import path6 from "path";
|
|
6195
6761
|
var TIMEOUT = 5e3;
|
|
6196
6762
|
function isGitRepo(cwd) {
|
|
6197
6763
|
try {
|
|
@@ -6203,9 +6769,9 @@ function isGitRepo(cwd) {
|
|
|
6203
6769
|
}
|
|
6204
6770
|
function createWorktree(workspace, agentId, taskId, agentName) {
|
|
6205
6771
|
if (!isGitRepo(workspace)) return null;
|
|
6206
|
-
const worktreeDir =
|
|
6772
|
+
const worktreeDir = path6.join(workspace, ".worktrees");
|
|
6207
6773
|
const worktreeName = `${agentId}-${taskId}`;
|
|
6208
|
-
const worktreePath =
|
|
6774
|
+
const worktreePath = path6.join(worktreeDir, worktreeName);
|
|
6209
6775
|
const branch = `agent/${agentName.toLowerCase().replace(/\s+/g, "-")}/${taskId}`;
|
|
6210
6776
|
try {
|
|
6211
6777
|
execSync3(`git worktree add "${worktreePath}" -b "${branch}"`, {
|
|
@@ -6251,7 +6817,7 @@ function mergeWorktree(workspace, worktreePath, branch) {
|
|
|
6251
6817
|
}
|
|
6252
6818
|
}
|
|
6253
6819
|
function removeWorktree(worktreePath, branch, workspace) {
|
|
6254
|
-
const cwd = workspace ??
|
|
6820
|
+
const cwd = workspace ?? path6.dirname(path6.dirname(worktreePath));
|
|
6255
6821
|
try {
|
|
6256
6822
|
execSync3(`git worktree remove --force "${worktreePath}"`, { cwd, stdio: "pipe", timeout: TIMEOUT });
|
|
6257
6823
|
} catch {
|
|
@@ -6274,8 +6840,12 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6274
6840
|
sandboxMode;
|
|
6275
6841
|
worktreeEnabled;
|
|
6276
6842
|
worktreeMerge;
|
|
6277
|
-
/** Preview captured from the first dev worker that produces one — not from QA/reviewer */
|
|
6843
|
+
/** Preview info captured from the first dev worker that produces one — not from QA/reviewer */
|
|
6278
6844
|
teamPreview = null;
|
|
6845
|
+
/** Accumulated changedFiles from all workers in the current team session */
|
|
6846
|
+
teamChangedFiles = /* @__PURE__ */ new Set();
|
|
6847
|
+
/** Guard against emitting isFinalResult more than once per execute cycle. */
|
|
6848
|
+
teamFinalized = false;
|
|
6279
6849
|
constructor(opts) {
|
|
6280
6850
|
super();
|
|
6281
6851
|
this.workspace = opts.workspace;
|
|
@@ -6402,9 +6972,15 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6402
6972
|
return;
|
|
6403
6973
|
}
|
|
6404
6974
|
if (this.agentManager.isTeamLead(agentId) && !this.delegationRouter.isDelegated(taskId)) {
|
|
6405
|
-
session.originalTask
|
|
6975
|
+
if (!session.originalTask || !opts?.phaseOverride || opts.phaseOverride !== "execute" && opts.phaseOverride !== "design" && opts.phaseOverride !== "complete") {
|
|
6976
|
+
session.originalTask = prompt;
|
|
6977
|
+
}
|
|
6978
|
+
const savedProjectDir = this.delegationRouter.getTeamProjectDir();
|
|
6406
6979
|
this.delegationRouter.clearAll();
|
|
6980
|
+
if (savedProjectDir) this.delegationRouter.setTeamProjectDir(savedProjectDir);
|
|
6407
6981
|
this.teamPreview = null;
|
|
6982
|
+
this.teamChangedFiles.clear();
|
|
6983
|
+
this.teamFinalized = false;
|
|
6408
6984
|
}
|
|
6409
6985
|
this.retryTracker?.track(taskId, prompt);
|
|
6410
6986
|
if (this.worktreeEnabled && !session.worktreePath) {
|
|
@@ -6424,14 +7000,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6424
7000
|
}
|
|
6425
7001
|
const repoPath = session.worktreePath ?? opts?.repoPath;
|
|
6426
7002
|
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
|
-
);
|
|
7003
|
+
session.runTask(taskId, prompt, repoPath, teamContext, true, opts?.phaseOverride);
|
|
6435
7004
|
}
|
|
6436
7005
|
cancelTask(agentId) {
|
|
6437
7006
|
const session = this.agentManager.get(agentId);
|
|
@@ -6491,7 +7060,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6491
7060
|
getAgent(agentId) {
|
|
6492
7061
|
const s = this.agentManager.get(agentId);
|
|
6493
7062
|
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 };
|
|
7063
|
+
return { agentId: s.agentId, name: s.name, role: s.role, status: s.status, palette: s.palette, backend: s.backend.id, pid: s.pid };
|
|
6495
7064
|
}
|
|
6496
7065
|
getAllAgents() {
|
|
6497
7066
|
return this.agentManager.getAll().map((s) => ({
|
|
@@ -6501,6 +7070,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6501
7070
|
status: s.status,
|
|
6502
7071
|
palette: s.palette,
|
|
6503
7072
|
backend: s.backend.id,
|
|
7073
|
+
pid: s.pid,
|
|
6504
7074
|
isTeamLead: this.agentManager.isTeamLead(s.agentId),
|
|
6505
7075
|
teamId: s.teamId
|
|
6506
7076
|
}));
|
|
@@ -6508,9 +7078,51 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6508
7078
|
getTeamRoster() {
|
|
6509
7079
|
return this.agentManager.getTeamRoster();
|
|
6510
7080
|
}
|
|
7081
|
+
/** Return PIDs of all managed (gateway-spawned) agent processes */
|
|
7082
|
+
getManagedPids() {
|
|
7083
|
+
const pids = [];
|
|
7084
|
+
for (const session of this.agentManager.getAll()) {
|
|
7085
|
+
const pid = session.pid;
|
|
7086
|
+
if (pid !== null) pids.push(pid);
|
|
7087
|
+
}
|
|
7088
|
+
return pids;
|
|
7089
|
+
}
|
|
6511
7090
|
isTeamLead(agentId) {
|
|
6512
7091
|
return this.agentManager.isTeamLead(agentId);
|
|
6513
7092
|
}
|
|
7093
|
+
/** Get the leader's last full output (used to capture the approved plan). */
|
|
7094
|
+
getLeaderLastOutput(agentId) {
|
|
7095
|
+
const session = this.agentManager.get(agentId);
|
|
7096
|
+
return session?.lastFullOutput ?? null;
|
|
7097
|
+
}
|
|
7098
|
+
/** Set team-wide project directory — all delegations will use this as cwd. */
|
|
7099
|
+
setTeamProjectDir(dir) {
|
|
7100
|
+
this.delegationRouter.setTeamProjectDir(dir);
|
|
7101
|
+
}
|
|
7102
|
+
getTeamProjectDir() {
|
|
7103
|
+
return this.delegationRouter.getTeamProjectDir();
|
|
7104
|
+
}
|
|
7105
|
+
/** Set the original task context for the leader (e.g. the approved plan). */
|
|
7106
|
+
setOriginalTask(agentId, task) {
|
|
7107
|
+
const session = this.agentManager.get(agentId);
|
|
7108
|
+
if (session) session.originalTask = task;
|
|
7109
|
+
}
|
|
7110
|
+
/** Clear ALL team members' conversation history for a fresh project cycle. */
|
|
7111
|
+
clearLeaderHistory(agentId) {
|
|
7112
|
+
const session = this.agentManager.get(agentId);
|
|
7113
|
+
if (session) {
|
|
7114
|
+
session.clearHistory();
|
|
7115
|
+
for (const agent of this.agentManager.getAll()) {
|
|
7116
|
+
if (agent.agentId !== agentId) {
|
|
7117
|
+
agent.clearHistory();
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
this.delegationRouter.clearAll();
|
|
7121
|
+
this.teamPreview = null;
|
|
7122
|
+
this.teamChangedFiles.clear();
|
|
7123
|
+
this.teamFinalized = false;
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
6514
7126
|
// ---------------------------------------------------------------------------
|
|
6515
7127
|
// Cleanup
|
|
6516
7128
|
// ---------------------------------------------------------------------------
|
|
@@ -6586,28 +7198,84 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6586
7198
|
session.worktreeBranch = null;
|
|
6587
7199
|
}
|
|
6588
7200
|
this.retryTracker?.clear(event.taskId);
|
|
6589
|
-
if (!this.agentManager.isTeamLead(agentId) &&
|
|
7201
|
+
if (!this.agentManager.isTeamLead(agentId) && event.result?.changedFiles) {
|
|
7202
|
+
for (const f of event.result.changedFiles) {
|
|
7203
|
+
this.teamChangedFiles.add(f);
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
if (!this.agentManager.isTeamLead(agentId)) {
|
|
6590
7207
|
const role = session?.role?.toLowerCase() ?? "";
|
|
6591
|
-
const isDevWorker = !role.includes("
|
|
6592
|
-
if (isDevWorker && event.result
|
|
7208
|
+
const isDevWorker = !role.includes("review");
|
|
7209
|
+
if (isDevWorker && event.result && (event.result.previewUrl || event.result.entryFile || event.result.previewCmd)) {
|
|
6593
7210
|
this.teamPreview = {
|
|
6594
7211
|
previewUrl: event.result.previewUrl,
|
|
6595
|
-
previewPath: event.result.previewPath
|
|
7212
|
+
previewPath: event.result.previewPath,
|
|
7213
|
+
entryFile: event.result.entryFile,
|
|
7214
|
+
previewCmd: event.result.previewCmd,
|
|
7215
|
+
previewPort: event.result.previewPort
|
|
6596
7216
|
};
|
|
6597
|
-
console.log(`[Orchestrator] Preview captured from ${session?.name}:
|
|
7217
|
+
console.log(`[Orchestrator] Preview captured from ${session?.name}: url=${this.teamPreview.previewUrl}, entry=${this.teamPreview.entryFile}, cmd=${this.teamPreview.previewCmd}`);
|
|
6598
7218
|
}
|
|
6599
7219
|
}
|
|
6600
7220
|
if (this.agentManager.isTeamLead(agentId)) {
|
|
6601
7221
|
const isResultTask = this.delegationRouter.isResultTask(event.taskId);
|
|
6602
7222
|
const leaderDidNotDelegateNewWork = isResultTask && this.delegationRouter.resultTaskDidNotDelegate(event.taskId);
|
|
6603
7223
|
const budgetForced = this.delegationRouter.isBudgetExhausted() && !this.delegationRouter.hasPendingFrom(agentId);
|
|
6604
|
-
const
|
|
7224
|
+
const hasWorkingWorkers = this.agentManager.getAll().some(
|
|
7225
|
+
(w) => w.agentId !== agentId && w.status === "working"
|
|
7226
|
+
);
|
|
7227
|
+
if (hasWorkingWorkers && !budgetForced) {
|
|
7228
|
+
console.log(`[Orchestrator] Deferring finalization \u2014 workers still running`);
|
|
7229
|
+
}
|
|
7230
|
+
const shouldFinalize = (leaderDidNotDelegateNewWork || budgetForced) && !this.teamFinalized && (!hasWorkingWorkers || budgetForced);
|
|
6605
7231
|
if (shouldFinalize) {
|
|
7232
|
+
this.teamFinalized = true;
|
|
6606
7233
|
event.isFinalResult = true;
|
|
6607
7234
|
this.delegationRouter.clearAgent(agentId);
|
|
6608
|
-
if (
|
|
6609
|
-
event.result.
|
|
6610
|
-
|
|
7235
|
+
if (event.result && this.teamChangedFiles.size > 0) {
|
|
7236
|
+
const merged = new Set(event.result.changedFiles ?? []);
|
|
7237
|
+
for (const f of this.teamChangedFiles) merged.add(f);
|
|
7238
|
+
event.result.changedFiles = Array.from(merged);
|
|
7239
|
+
}
|
|
7240
|
+
if (event.result) {
|
|
7241
|
+
const teamDir = this.delegationRouter.getTeamProjectDir();
|
|
7242
|
+
if (teamDir) {
|
|
7243
|
+
event.result.projectDir = teamDir;
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
if (this.teamPreview && event.result) {
|
|
7247
|
+
if (this.teamPreview.previewUrl) {
|
|
7248
|
+
event.result.previewUrl = this.teamPreview.previewUrl;
|
|
7249
|
+
event.result.previewPath = this.teamPreview.previewPath;
|
|
7250
|
+
}
|
|
7251
|
+
if (this.teamPreview.entryFile) event.result.entryFile = this.teamPreview.entryFile;
|
|
7252
|
+
if (this.teamPreview.previewCmd) event.result.previewCmd = this.teamPreview.previewCmd;
|
|
7253
|
+
if (this.teamPreview.previewPort) event.result.previewPort = this.teamPreview.previewPort;
|
|
7254
|
+
}
|
|
7255
|
+
if (event.result?.entryFile) {
|
|
7256
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7257
|
+
const absEntry = path7.isAbsolute(event.result.entryFile) ? event.result.entryFile : path7.join(projectDir, event.result.entryFile);
|
|
7258
|
+
if (!existsSync5(absEntry)) {
|
|
7259
|
+
const allFiles = event.result.changedFiles ?? [];
|
|
7260
|
+
const ext = path7.extname(event.result.entryFile).toLowerCase();
|
|
7261
|
+
const candidate = allFiles.map((f) => path7.basename(f)).find((f) => path7.extname(f).toLowerCase() === ext);
|
|
7262
|
+
if (candidate) {
|
|
7263
|
+
console.log(`[Orchestrator] entryFile "${event.result.entryFile}" not found on disk, using "${candidate}" from changedFiles`);
|
|
7264
|
+
event.result.entryFile = candidate;
|
|
7265
|
+
} else {
|
|
7266
|
+
console.log(`[Orchestrator] entryFile "${event.result.entryFile}" not found on disk, clearing`);
|
|
7267
|
+
event.result.entryFile = void 0;
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
if (event.result?.entryFile && !event.result.previewCmd && !/\.html?$/i.test(event.result.entryFile)) {
|
|
7272
|
+
const ext = path7.extname(event.result.entryFile).toLowerCase();
|
|
7273
|
+
const runners = { ".py": "python3", ".js": "node", ".rb": "ruby", ".sh": "bash" };
|
|
7274
|
+
const runner = runners[ext];
|
|
7275
|
+
if (runner) {
|
|
7276
|
+
event.result.previewCmd = `${runner} ${event.result.entryFile}`;
|
|
7277
|
+
console.log(`[Orchestrator] Auto-constructed previewCmd: ${event.result.previewCmd}`);
|
|
7278
|
+
}
|
|
6611
7279
|
}
|
|
6612
7280
|
if (!event.result?.previewUrl && event.result) {
|
|
6613
7281
|
for (const worker of this.agentManager.getAll()) {
|
|
@@ -6620,6 +7288,70 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6620
7288
|
}
|
|
6621
7289
|
}
|
|
6622
7290
|
}
|
|
7291
|
+
if (!event.result?.previewUrl && event.result?.previewCmd) {
|
|
7292
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7293
|
+
if (event.result.previewPort) {
|
|
7294
|
+
const url = previewServer.runCommand(event.result.previewCmd, projectDir, event.result.previewPort);
|
|
7295
|
+
if (url) {
|
|
7296
|
+
event.result.previewUrl = url;
|
|
7297
|
+
console.log(`[Orchestrator] Preview from leader PREVIEW_CMD (port ${event.result.previewPort}): ${url}`);
|
|
7298
|
+
}
|
|
7299
|
+
} else {
|
|
7300
|
+
console.log(`[Orchestrator] Desktop app ready (user can Launch): ${event.result.previewCmd}`);
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
if (!event.result?.previewUrl && event.result?.entryFile) {
|
|
7304
|
+
const entryFile = event.result.entryFile;
|
|
7305
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7306
|
+
if (/\.html?$/i.test(entryFile)) {
|
|
7307
|
+
const absPath = path7.isAbsolute(entryFile) ? entryFile : path7.join(projectDir, entryFile);
|
|
7308
|
+
const url = previewServer.serve(absPath);
|
|
7309
|
+
if (url) {
|
|
7310
|
+
event.result.previewUrl = url;
|
|
7311
|
+
event.result.previewPath = absPath;
|
|
7312
|
+
console.log(`[Orchestrator] Preview from leader ENTRY_FILE: ${url}`);
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
if (!event.result?.previewUrl && event.result && this.teamChangedFiles.size > 0) {
|
|
7317
|
+
const projectDir = this.delegationRouter.getTeamProjectDir() ?? this.workspace;
|
|
7318
|
+
const htmlFile = Array.from(this.teamChangedFiles).find((f) => /\.html?$/i.test(f));
|
|
7319
|
+
if (htmlFile) {
|
|
7320
|
+
const absPath = path7.isAbsolute(htmlFile) ? htmlFile : path7.join(projectDir, htmlFile);
|
|
7321
|
+
const url = previewServer.serve(absPath);
|
|
7322
|
+
if (url) {
|
|
7323
|
+
event.result.previewUrl = url;
|
|
7324
|
+
event.result.previewPath = absPath;
|
|
7325
|
+
console.log(`[Orchestrator] Preview from teamChangedFiles: ${url}`);
|
|
7326
|
+
}
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
if (!event.result?.previewUrl && event.result) {
|
|
7330
|
+
const projectDir = this.delegationRouter.getTeamProjectDir();
|
|
7331
|
+
if (projectDir) {
|
|
7332
|
+
const candidates = [
|
|
7333
|
+
"dist/index.html",
|
|
7334
|
+
"build/index.html",
|
|
7335
|
+
"out/index.html",
|
|
7336
|
+
// common build dirs
|
|
7337
|
+
"index.html",
|
|
7338
|
+
"public/index.html"
|
|
7339
|
+
// static projects
|
|
7340
|
+
];
|
|
7341
|
+
for (const candidate of candidates) {
|
|
7342
|
+
const absPath = path7.join(projectDir, candidate);
|
|
7343
|
+
if (existsSync5(absPath)) {
|
|
7344
|
+
const url = previewServer.serve(absPath);
|
|
7345
|
+
if (url) {
|
|
7346
|
+
event.result.previewUrl = url;
|
|
7347
|
+
event.result.previewPath = absPath;
|
|
7348
|
+
console.log(`[Orchestrator] Preview from project scan: ${absPath}`);
|
|
7349
|
+
break;
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
6623
7355
|
const summary = event.result?.summary?.slice(0, 200) ?? "All tasks completed.";
|
|
6624
7356
|
this.emitEvent({
|
|
6625
7357
|
type: "team:chat",
|
|
@@ -6655,14 +7387,481 @@ function createOrchestrator(options) {
|
|
|
6655
7387
|
|
|
6656
7388
|
// src/index.ts
|
|
6657
7389
|
import { nanoid as nanoid5 } from "nanoid";
|
|
7390
|
+
import { execFile as execFile3 } from "child_process";
|
|
7391
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
7392
|
+
import path9 from "path";
|
|
7393
|
+
import os2 from "os";
|
|
7394
|
+
|
|
7395
|
+
// src/process-scanner.ts
|
|
6658
7396
|
import { execFile } from "child_process";
|
|
6659
|
-
|
|
6660
|
-
|
|
7397
|
+
var KNOWN_COMMANDS = ["claude", "codex", "gemini", "aider", "opencode"];
|
|
7398
|
+
var COMMAND_TO_BACKEND = {
|
|
7399
|
+
claude: "claude",
|
|
7400
|
+
codex: "codex",
|
|
7401
|
+
gemini: "gemini",
|
|
7402
|
+
aider: "aider",
|
|
7403
|
+
opencode: "opencode"
|
|
7404
|
+
};
|
|
7405
|
+
function parseEtime(etime) {
|
|
7406
|
+
const now = Date.now();
|
|
7407
|
+
const parts = etime.trim().replace(/-/g, ":").split(":");
|
|
7408
|
+
let seconds = 0;
|
|
7409
|
+
if (parts.length === 4) {
|
|
7410
|
+
seconds = parseInt(parts[0]) * 86400 + parseInt(parts[1]) * 3600 + parseInt(parts[2]) * 60 + parseInt(parts[3]);
|
|
7411
|
+
} else if (parts.length === 3) {
|
|
7412
|
+
seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
|
|
7413
|
+
} else if (parts.length === 2) {
|
|
7414
|
+
seconds = parseInt(parts[0]) * 60 + parseInt(parts[1]);
|
|
7415
|
+
}
|
|
7416
|
+
return now - seconds * 1e3;
|
|
7417
|
+
}
|
|
7418
|
+
function exec(cmd, args) {
|
|
7419
|
+
return new Promise((resolve2) => {
|
|
7420
|
+
execFile(cmd, args, { timeout: 5e3, maxBuffer: 1024 * 512 }, (err, stdout) => {
|
|
7421
|
+
resolve2(err ? "" : stdout);
|
|
7422
|
+
});
|
|
7423
|
+
});
|
|
7424
|
+
}
|
|
7425
|
+
async function getCwds(pids) {
|
|
7426
|
+
const result = /* @__PURE__ */ new Map();
|
|
7427
|
+
if (pids.length === 0) return result;
|
|
7428
|
+
const output = await exec("lsof", ["-a", "-p", pids.join(","), "-d", "cwd", "-Fn"]);
|
|
7429
|
+
let currentPid = null;
|
|
7430
|
+
for (const line of output.split("\n")) {
|
|
7431
|
+
if (line.startsWith("p")) {
|
|
7432
|
+
currentPid = parseInt(line.slice(1));
|
|
7433
|
+
} else if (line.startsWith("n") && currentPid !== null) {
|
|
7434
|
+
result.set(currentPid, line.slice(1));
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
return result;
|
|
7438
|
+
}
|
|
7439
|
+
var ProcessScanner = class _ProcessScanner {
|
|
7440
|
+
timer = null;
|
|
7441
|
+
previous = /* @__PURE__ */ new Map();
|
|
7442
|
+
getManagedPids;
|
|
7443
|
+
callbacks;
|
|
7444
|
+
/** PIDs to ignore temporarily (recently killed — may still appear in ps) */
|
|
7445
|
+
graceList = /* @__PURE__ */ new Map();
|
|
7446
|
+
// pid → expiry timestamp
|
|
7447
|
+
static GRACE_MS = 15e3;
|
|
7448
|
+
// 15 seconds grace period
|
|
7449
|
+
constructor(getManagedPids, callbacks) {
|
|
7450
|
+
this.getManagedPids = getManagedPids;
|
|
7451
|
+
this.callbacks = callbacks;
|
|
7452
|
+
}
|
|
7453
|
+
/** Mark a PID as recently killed — scanner will ignore it for a grace period */
|
|
7454
|
+
addGracePid(pid) {
|
|
7455
|
+
this.graceList.set(pid, Date.now() + _ProcessScanner.GRACE_MS);
|
|
7456
|
+
}
|
|
7457
|
+
start(intervalMs = 7e3) {
|
|
7458
|
+
this.scan();
|
|
7459
|
+
this.timer = setInterval(() => this.scan(), intervalMs);
|
|
7460
|
+
}
|
|
7461
|
+
stop() {
|
|
7462
|
+
if (this.timer) {
|
|
7463
|
+
clearInterval(this.timer);
|
|
7464
|
+
this.timer = null;
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
async scan() {
|
|
7468
|
+
try {
|
|
7469
|
+
const psOutput = await exec("ps", ["-eo", "pid,ppid,pcpu,etime,args"]);
|
|
7470
|
+
const managed = new Set(this.getManagedPids());
|
|
7471
|
+
const now = Date.now();
|
|
7472
|
+
for (const [pid, expiry] of this.graceList) {
|
|
7473
|
+
if (now >= expiry) {
|
|
7474
|
+
this.graceList.delete(pid);
|
|
7475
|
+
} else {
|
|
7476
|
+
managed.add(pid);
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
const lines = psOutput.split("\n").slice(1);
|
|
7480
|
+
const candidates = [];
|
|
7481
|
+
for (const line of lines) {
|
|
7482
|
+
const trimmed = line.trim();
|
|
7483
|
+
if (!trimmed) continue;
|
|
7484
|
+
const match = trimmed.match(/^\s*(\d+)\s+(\d+)\s+([\d.]+)\s+([\w:-]+)\s+(.+)$/);
|
|
7485
|
+
if (!match) continue;
|
|
7486
|
+
const pid = parseInt(match[1]);
|
|
7487
|
+
const ppid = parseInt(match[2]);
|
|
7488
|
+
const cpu = parseFloat(match[3]);
|
|
7489
|
+
const etime = match[4];
|
|
7490
|
+
const args = match[5];
|
|
7491
|
+
if (managed.has(pid)) continue;
|
|
7492
|
+
const cmdName = this.matchCommand(args);
|
|
7493
|
+
if (!cmdName) continue;
|
|
7494
|
+
candidates.push({ pid, ppid, cpu, etime, command: cmdName });
|
|
7495
|
+
}
|
|
7496
|
+
const candidatePids = new Set(candidates.map((c) => c.pid));
|
|
7497
|
+
const filtered = candidates.filter((c) => !candidatePids.has(c.ppid));
|
|
7498
|
+
const cwds = await getCwds(filtered.map((c) => c.pid));
|
|
7499
|
+
const current = /* @__PURE__ */ new Map();
|
|
7500
|
+
for (const c of filtered) {
|
|
7501
|
+
const backendId = COMMAND_TO_BACKEND[c.command] ?? c.command;
|
|
7502
|
+
const agentId = `ext-${backendId}-${c.pid}`;
|
|
7503
|
+
const status = c.cpu >= 5 ? "working" : "idle";
|
|
7504
|
+
current.set(agentId, {
|
|
7505
|
+
pid: c.pid,
|
|
7506
|
+
ppid: c.ppid,
|
|
7507
|
+
cpu: c.cpu,
|
|
7508
|
+
command: c.command,
|
|
7509
|
+
backendId,
|
|
7510
|
+
cwd: cwds.get(c.pid) ?? null,
|
|
7511
|
+
startedAt: parseEtime(c.etime),
|
|
7512
|
+
agentId,
|
|
7513
|
+
status
|
|
7514
|
+
});
|
|
7515
|
+
}
|
|
7516
|
+
const added = [];
|
|
7517
|
+
const removed = [];
|
|
7518
|
+
const changed = [];
|
|
7519
|
+
for (const [id, agent] of current) {
|
|
7520
|
+
const prev = this.previous.get(id);
|
|
7521
|
+
if (!prev) {
|
|
7522
|
+
added.push(agent);
|
|
7523
|
+
} else if (prev.status !== agent.status) {
|
|
7524
|
+
changed.push(agent);
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
for (const id of this.previous.keys()) {
|
|
7528
|
+
if (!current.has(id)) {
|
|
7529
|
+
removed.push(id);
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
this.previous = current;
|
|
7533
|
+
if (added.length > 0) this.callbacks.onAdded(added);
|
|
7534
|
+
if (removed.length > 0) this.callbacks.onRemoved(removed);
|
|
7535
|
+
if (changed.length > 0) this.callbacks.onChanged(changed);
|
|
7536
|
+
} catch (err) {
|
|
7537
|
+
console.error("[ProcessScanner] Scan error:", err);
|
|
7538
|
+
}
|
|
7539
|
+
}
|
|
7540
|
+
matchCommand(args) {
|
|
7541
|
+
for (const cmd of KNOWN_COMMANDS) {
|
|
7542
|
+
const re = new RegExp(`(?:^|/)${cmd}(?:\\s|$)`);
|
|
7543
|
+
if (re.test(args)) return cmd;
|
|
7544
|
+
}
|
|
7545
|
+
return null;
|
|
7546
|
+
}
|
|
7547
|
+
};
|
|
7548
|
+
|
|
7549
|
+
// src/external-output-reader.ts
|
|
7550
|
+
import { watch, readdirSync, statSync, existsSync as existsSync6, openSync, readSync, closeSync } from "fs";
|
|
7551
|
+
import { execFile as execFile2 } from "child_process";
|
|
7552
|
+
import path8 from "path";
|
|
6661
7553
|
import os from "os";
|
|
7554
|
+
var SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
7555
|
+
".ts",
|
|
7556
|
+
".tsx",
|
|
7557
|
+
".js",
|
|
7558
|
+
".jsx",
|
|
7559
|
+
".py",
|
|
7560
|
+
".go",
|
|
7561
|
+
".rs",
|
|
7562
|
+
".java",
|
|
7563
|
+
".c",
|
|
7564
|
+
".cpp",
|
|
7565
|
+
".h",
|
|
7566
|
+
".css",
|
|
7567
|
+
".scss",
|
|
7568
|
+
".html",
|
|
7569
|
+
".json",
|
|
7570
|
+
".yaml",
|
|
7571
|
+
".yml",
|
|
7572
|
+
".toml",
|
|
7573
|
+
".md",
|
|
7574
|
+
".sql",
|
|
7575
|
+
".sh",
|
|
7576
|
+
".rb",
|
|
7577
|
+
".swift",
|
|
7578
|
+
".kt"
|
|
7579
|
+
]);
|
|
7580
|
+
var ExternalOutputReader = class {
|
|
7581
|
+
readers = /* @__PURE__ */ new Map();
|
|
7582
|
+
onStatus = null;
|
|
7583
|
+
onTokenUpdate = null;
|
|
7584
|
+
/** Set a callback to be notified when an external agent's status changes (driven by JSONL entries) */
|
|
7585
|
+
setOnStatus(cb) {
|
|
7586
|
+
this.onStatus = cb;
|
|
7587
|
+
}
|
|
7588
|
+
/** Set a callback to be notified when token usage is detected from JSONL entries */
|
|
7589
|
+
setOnTokenUpdate(cb) {
|
|
7590
|
+
this.onTokenUpdate = cb;
|
|
7591
|
+
}
|
|
7592
|
+
attach(agentId, pid, cwd, backendId, onOutput) {
|
|
7593
|
+
if (this.readers.has(agentId)) return;
|
|
7594
|
+
let cleanup2;
|
|
7595
|
+
if (backendId === "claude" && cwd) {
|
|
7596
|
+
cleanup2 = this.startClaudeReader(agentId, cwd, onOutput);
|
|
7597
|
+
} else {
|
|
7598
|
+
cleanup2 = this.startLsofReader(agentId, pid, onOutput);
|
|
7599
|
+
}
|
|
7600
|
+
this.readers.set(agentId, { agentId, pid, cwd, backendId, onOutput, cleanup: cleanup2, inputTokens: 0, outputTokens: 0 });
|
|
7601
|
+
}
|
|
7602
|
+
detach(agentId) {
|
|
7603
|
+
const reader = this.readers.get(agentId);
|
|
7604
|
+
if (reader) {
|
|
7605
|
+
reader.cleanup();
|
|
7606
|
+
this.readers.delete(agentId);
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
detachAll() {
|
|
7610
|
+
for (const [id] of this.readers) {
|
|
7611
|
+
this.detach(id);
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
// ── Claude JSONL reader ───────────────────────────────────────
|
|
7615
|
+
startClaudeReader(agentId, cwd, onOutput) {
|
|
7616
|
+
const projectKey = cwd.replace(/\//g, "-");
|
|
7617
|
+
const projectDir = path8.join(os.homedir(), ".claude", "projects", projectKey);
|
|
7618
|
+
console.log(`[OutputReader] Claude reader for ${agentId}: watching ${projectDir}`);
|
|
7619
|
+
let lastPosition = 0;
|
|
7620
|
+
let watchedFile = null;
|
|
7621
|
+
let watcher = null;
|
|
7622
|
+
let lastEmitTime = 0;
|
|
7623
|
+
let pendingChunk = null;
|
|
7624
|
+
let throttleTimer = null;
|
|
7625
|
+
let pollTimer = null;
|
|
7626
|
+
let retryTimer = null;
|
|
7627
|
+
let stopped = false;
|
|
7628
|
+
let retryCount = 0;
|
|
7629
|
+
const THROTTLE_MS = 2e3;
|
|
7630
|
+
const POLL_MS = 3e3;
|
|
7631
|
+
const MAX_RETRIES = 5;
|
|
7632
|
+
const emitThrottled = (text) => {
|
|
7633
|
+
const now = Date.now();
|
|
7634
|
+
if (now - lastEmitTime >= THROTTLE_MS) {
|
|
7635
|
+
lastEmitTime = now;
|
|
7636
|
+
onOutput(text);
|
|
7637
|
+
pendingChunk = null;
|
|
7638
|
+
} else {
|
|
7639
|
+
pendingChunk = text;
|
|
7640
|
+
if (!throttleTimer) {
|
|
7641
|
+
throttleTimer = setTimeout(() => {
|
|
7642
|
+
throttleTimer = null;
|
|
7643
|
+
if (pendingChunk && !stopped) {
|
|
7644
|
+
lastEmitTime = Date.now();
|
|
7645
|
+
onOutput(pendingChunk);
|
|
7646
|
+
pendingChunk = null;
|
|
7647
|
+
}
|
|
7648
|
+
}, THROTTLE_MS - (now - lastEmitTime));
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
};
|
|
7652
|
+
const readNewLines = () => {
|
|
7653
|
+
if (!watchedFile || stopped) return;
|
|
7654
|
+
try {
|
|
7655
|
+
const stat2 = statSync(watchedFile);
|
|
7656
|
+
if (stat2.size <= lastPosition) return;
|
|
7657
|
+
const bytesToRead = stat2.size - lastPosition;
|
|
7658
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
7659
|
+
const fd = openSync(watchedFile, "r");
|
|
7660
|
+
try {
|
|
7661
|
+
readSync(fd, buf, 0, bytesToRead, lastPosition);
|
|
7662
|
+
} finally {
|
|
7663
|
+
closeSync(fd);
|
|
7664
|
+
}
|
|
7665
|
+
lastPosition = stat2.size;
|
|
7666
|
+
const newData = buf.toString("utf-8");
|
|
7667
|
+
for (const line of newData.split("\n")) {
|
|
7668
|
+
if (!line.trim()) continue;
|
|
7669
|
+
try {
|
|
7670
|
+
const entry = JSON.parse(line);
|
|
7671
|
+
this.extractClaudeOutput(entry, emitThrottled, agentId);
|
|
7672
|
+
} catch {
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
} catch (err) {
|
|
7676
|
+
console.error(`[OutputReader] Error reading JSONL for ${agentId}:`, err);
|
|
7677
|
+
}
|
|
7678
|
+
};
|
|
7679
|
+
const findAndWatch = () => {
|
|
7680
|
+
if (stopped) return;
|
|
7681
|
+
if (!existsSync6(projectDir)) {
|
|
7682
|
+
retryCount++;
|
|
7683
|
+
if (retryCount > MAX_RETRIES) {
|
|
7684
|
+
console.log(`[OutputReader] Project dir not found after ${MAX_RETRIES} retries, giving up: ${projectDir}`);
|
|
7685
|
+
return;
|
|
7686
|
+
}
|
|
7687
|
+
console.log(`[OutputReader] Project dir not found (${retryCount}/${MAX_RETRIES}), retrying: ${projectDir}`);
|
|
7688
|
+
retryTimer = setTimeout(findAndWatch, 5e3);
|
|
7689
|
+
return;
|
|
7690
|
+
}
|
|
7691
|
+
try {
|
|
7692
|
+
const files = readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
7693
|
+
name: f,
|
|
7694
|
+
mtime: statSync(path8.join(projectDir, f)).mtimeMs
|
|
7695
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
7696
|
+
if (files.length === 0) {
|
|
7697
|
+
retryCount++;
|
|
7698
|
+
if (retryCount > MAX_RETRIES) {
|
|
7699
|
+
console.log(`[OutputReader] No JSONL files after ${MAX_RETRIES} retries, giving up: ${projectDir}`);
|
|
7700
|
+
return;
|
|
7701
|
+
}
|
|
7702
|
+
console.log(`[OutputReader] No JSONL files yet in ${projectDir} (${retryCount}/${MAX_RETRIES}), retrying`);
|
|
7703
|
+
retryTimer = setTimeout(findAndWatch, 5e3);
|
|
7704
|
+
return;
|
|
7705
|
+
}
|
|
7706
|
+
watchedFile = path8.join(projectDir, files[0].name);
|
|
7707
|
+
lastPosition = statSync(watchedFile).size;
|
|
7708
|
+
console.log(`[OutputReader] Watching JSONL: ${watchedFile} (pos=${lastPosition})`);
|
|
7709
|
+
try {
|
|
7710
|
+
watcher = watch(projectDir, (_event, filename) => {
|
|
7711
|
+
if (stopped) return;
|
|
7712
|
+
if (filename && filename.endsWith(".jsonl")) {
|
|
7713
|
+
const fullPath = path8.join(projectDir, filename);
|
|
7714
|
+
if (fullPath !== watchedFile && existsSync6(fullPath)) {
|
|
7715
|
+
try {
|
|
7716
|
+
const newMtime = statSync(fullPath).mtimeMs;
|
|
7717
|
+
const curMtime = watchedFile ? statSync(watchedFile).mtimeMs : 0;
|
|
7718
|
+
if (newMtime > curMtime) {
|
|
7719
|
+
console.log(`[OutputReader] Switching to newer JSONL: ${fullPath}`);
|
|
7720
|
+
watchedFile = fullPath;
|
|
7721
|
+
lastPosition = 0;
|
|
7722
|
+
}
|
|
7723
|
+
} catch {
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
readNewLines();
|
|
7728
|
+
});
|
|
7729
|
+
} catch {
|
|
7730
|
+
console.log(`[OutputReader] fs.watch failed, relying on polling only`);
|
|
7731
|
+
}
|
|
7732
|
+
pollTimer = setInterval(readNewLines, POLL_MS);
|
|
7733
|
+
} catch (err) {
|
|
7734
|
+
console.error(`[OutputReader] Error setting up watcher for ${agentId}:`, err);
|
|
7735
|
+
}
|
|
7736
|
+
};
|
|
7737
|
+
findAndWatch();
|
|
7738
|
+
return () => {
|
|
7739
|
+
stopped = true;
|
|
7740
|
+
if (watcher) {
|
|
7741
|
+
watcher.close();
|
|
7742
|
+
watcher = null;
|
|
7743
|
+
}
|
|
7744
|
+
if (pollTimer) {
|
|
7745
|
+
clearInterval(pollTimer);
|
|
7746
|
+
pollTimer = null;
|
|
7747
|
+
}
|
|
7748
|
+
if (throttleTimer) {
|
|
7749
|
+
clearTimeout(throttleTimer);
|
|
7750
|
+
throttleTimer = null;
|
|
7751
|
+
}
|
|
7752
|
+
if (retryTimer) {
|
|
7753
|
+
clearTimeout(retryTimer);
|
|
7754
|
+
retryTimer = null;
|
|
7755
|
+
}
|
|
7756
|
+
};
|
|
7757
|
+
}
|
|
7758
|
+
/**
|
|
7759
|
+
* Extract readable output from a Claude JSONL entry.
|
|
7760
|
+
* Also drives status: "human" → working, "result" → idle.
|
|
7761
|
+
*
|
|
7762
|
+
* Claude JSONL types:
|
|
7763
|
+
* - { type: "human" } → user sent message, agent starts working
|
|
7764
|
+
* - { type: "assistant", message: { content: [...] } } → agent responding
|
|
7765
|
+
* - { type: "result", result: "..." } → turn complete, waiting for input
|
|
7766
|
+
*/
|
|
7767
|
+
extractClaudeOutput(entry, emit, agentId) {
|
|
7768
|
+
if (entry.type === "user" || entry.type === "human") {
|
|
7769
|
+
this.onStatus?.(agentId, "working");
|
|
7770
|
+
}
|
|
7771
|
+
if (entry.type === "assistant") {
|
|
7772
|
+
const message = entry.message;
|
|
7773
|
+
const usage = message?.usage;
|
|
7774
|
+
if (usage) {
|
|
7775
|
+
const reader = this.readers.get(agentId);
|
|
7776
|
+
if (reader) {
|
|
7777
|
+
const inp = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
7778
|
+
const out = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
7779
|
+
if (inp > 0 || out > 0) {
|
|
7780
|
+
reader.inputTokens += inp;
|
|
7781
|
+
reader.outputTokens += out;
|
|
7782
|
+
this.onTokenUpdate?.(agentId, reader.inputTokens, reader.outputTokens);
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
const stopReason = message?.stop_reason ?? entry.stop_reason;
|
|
7787
|
+
const content = message?.content;
|
|
7788
|
+
if (content && Array.isArray(content)) {
|
|
7789
|
+
for (const block of content) {
|
|
7790
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
7791
|
+
emit(block.text.slice(0, 200));
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
}
|
|
7795
|
+
if (stopReason === "end_turn") {
|
|
7796
|
+
this.onStatus?.(agentId, "idle");
|
|
7797
|
+
} else {
|
|
7798
|
+
this.onStatus?.(agentId, "working");
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
if (entry.type === "result") {
|
|
7802
|
+
const result = entry.result;
|
|
7803
|
+
if (typeof result === "string" && result.trim()) {
|
|
7804
|
+
emit(result.slice(0, 200));
|
|
7805
|
+
}
|
|
7806
|
+
this.onStatus?.(agentId, "idle");
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
// ── lsof fallback reader ──────────────────────────────────────
|
|
7810
|
+
startLsofReader(agentId, pid, onOutput) {
|
|
7811
|
+
const knownFiles = /* @__PURE__ */ new Set();
|
|
7812
|
+
let stopped = false;
|
|
7813
|
+
console.log(`[OutputReader] lsof reader for ${agentId}: pid=${pid}`);
|
|
7814
|
+
const poll = () => {
|
|
7815
|
+
if (stopped) return;
|
|
7816
|
+
execFile2("lsof", ["-p", String(pid)], { timeout: 5e3, maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
7817
|
+
if (err || stopped) return;
|
|
7818
|
+
const lines = stdout.split("\n");
|
|
7819
|
+
const newFiles = [];
|
|
7820
|
+
for (const line of lines) {
|
|
7821
|
+
const cols = line.trim().split(/\s+/);
|
|
7822
|
+
if (cols.length < 9) continue;
|
|
7823
|
+
const name = cols.slice(8).join(" ");
|
|
7824
|
+
if (!name || name.startsWith("/dev/") || name.startsWith("/System/")) continue;
|
|
7825
|
+
const ext = path8.extname(name);
|
|
7826
|
+
if (!SOURCE_EXTS.has(ext)) continue;
|
|
7827
|
+
if (!knownFiles.has(name)) {
|
|
7828
|
+
knownFiles.add(name);
|
|
7829
|
+
newFiles.push(name);
|
|
7830
|
+
}
|
|
7831
|
+
}
|
|
7832
|
+
if (newFiles.length > 0) {
|
|
7833
|
+
const basename = path8.basename(newFiles[newFiles.length - 1]);
|
|
7834
|
+
onOutput(`Editing ${basename}`);
|
|
7835
|
+
}
|
|
7836
|
+
});
|
|
7837
|
+
};
|
|
7838
|
+
const timer = setInterval(poll, 5e3);
|
|
7839
|
+
poll();
|
|
7840
|
+
return () => {
|
|
7841
|
+
stopped = true;
|
|
7842
|
+
clearInterval(timer);
|
|
7843
|
+
};
|
|
7844
|
+
}
|
|
7845
|
+
};
|
|
7846
|
+
|
|
7847
|
+
// src/index.ts
|
|
6662
7848
|
registerChannel(wsChannel);
|
|
6663
7849
|
registerChannel(ablyChannel);
|
|
6664
7850
|
registerChannel(telegramChannel);
|
|
6665
7851
|
var orc;
|
|
7852
|
+
var scanner = null;
|
|
7853
|
+
var outputReader = null;
|
|
7854
|
+
var externalAgents = /* @__PURE__ */ new Map();
|
|
7855
|
+
var teamPhases = /* @__PURE__ */ new Map();
|
|
7856
|
+
function publishTeamPhase(teamId, phase, leadAgentId) {
|
|
7857
|
+
teamPhases.set(teamId, { phase, leadAgentId });
|
|
7858
|
+
publishEvent({
|
|
7859
|
+
type: "TEAM_PHASE",
|
|
7860
|
+
teamId,
|
|
7861
|
+
phase,
|
|
7862
|
+
leadAgentId
|
|
7863
|
+
});
|
|
7864
|
+
}
|
|
6666
7865
|
function generatePairCode() {
|
|
6667
7866
|
return nanoid5(6).toUpperCase();
|
|
6668
7867
|
}
|
|
@@ -6677,12 +7876,109 @@ function showPairCode() {
|
|
|
6677
7876
|
console.log(`Open your phone \u2192 enter gateway address + code`);
|
|
6678
7877
|
console.log("");
|
|
6679
7878
|
}
|
|
7879
|
+
function extractProjectName(planText) {
|
|
7880
|
+
function toKebab(s) {
|
|
7881
|
+
return s.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
7882
|
+
}
|
|
7883
|
+
function trimKebab(s, maxLen) {
|
|
7884
|
+
if (s.length <= maxLen) return s;
|
|
7885
|
+
const cut = s.lastIndexOf("-", maxLen);
|
|
7886
|
+
return cut > 2 ? s.slice(0, cut) : s.slice(0, maxLen);
|
|
7887
|
+
}
|
|
7888
|
+
const namedConcept = planText.match(/CONCEPT\s*[::]\s*(?:A\s+|An\s+|The\s+)?(.+?)\s*[—–]\s/i);
|
|
7889
|
+
if (namedConcept) {
|
|
7890
|
+
const kebab = toKebab(namedConcept[1].trim());
|
|
7891
|
+
if (kebab.length >= 2 && kebab.length <= 30) return kebab;
|
|
7892
|
+
}
|
|
7893
|
+
const quoted = planText.match(/["""\u201c]([^"""\u201d]{2,25})["""\u201d]/);
|
|
7894
|
+
if (quoted) {
|
|
7895
|
+
const kebab = toKebab(quoted[1].trim());
|
|
7896
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7897
|
+
}
|
|
7898
|
+
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);
|
|
7899
|
+
if (concept) {
|
|
7900
|
+
const kebab = toKebab(concept[1].trim());
|
|
7901
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7902
|
+
}
|
|
7903
|
+
const fallbacks = [
|
|
7904
|
+
/(?:goal|project|目标|项目)\s*[::]\s*(.+)/i,
|
|
7905
|
+
/\[PLAN\][\s\S]*?(?:goal|project|目标)\s*[::]\s*(.+)/i,
|
|
7906
|
+
/(?:build|create|make|开发|做|构建)\s+(?:a\s+)?(.+?)(?:\s+(?:with|using|that|for|where|,|。)\b|[.\n])/i
|
|
7907
|
+
];
|
|
7908
|
+
for (const re of fallbacks) {
|
|
7909
|
+
const m = planText.match(re);
|
|
7910
|
+
if (m) {
|
|
7911
|
+
const kebab = toKebab(m[1].trim());
|
|
7912
|
+
if (kebab.length >= 2) return trimKebab(kebab, 25);
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7915
|
+
return "project";
|
|
7916
|
+
}
|
|
7917
|
+
function createUniqueProjectDir(workspace, baseName) {
|
|
7918
|
+
let dirName = baseName;
|
|
7919
|
+
let counter = 1;
|
|
7920
|
+
while (existsSync7(path9.join(workspace, dirName))) {
|
|
7921
|
+
counter++;
|
|
7922
|
+
dirName = `${baseName}-${counter}`;
|
|
7923
|
+
}
|
|
7924
|
+
const fullPath = path9.join(workspace, dirName);
|
|
7925
|
+
mkdirSync5(fullPath, { recursive: true });
|
|
7926
|
+
console.log(`[Gateway] Created project directory: ${fullPath}`);
|
|
7927
|
+
return fullPath;
|
|
7928
|
+
}
|
|
7929
|
+
var AGENTS_FILE = path9.join(os2.homedir(), ".bit-office", "agents.json");
|
|
7930
|
+
function loadAgentDefs() {
|
|
7931
|
+
try {
|
|
7932
|
+
if (existsSync7(AGENTS_FILE)) {
|
|
7933
|
+
const raw = JSON.parse(readFileSync5(AGENTS_FILE, "utf-8"));
|
|
7934
|
+
if (Array.isArray(raw.agents)) return raw.agents;
|
|
7935
|
+
}
|
|
7936
|
+
} catch (e) {
|
|
7937
|
+
console.log(`[Gateway] Failed to read agents.json: ${e}`);
|
|
7938
|
+
}
|
|
7939
|
+
saveAgentDefs(DEFAULT_AGENT_DEFS);
|
|
7940
|
+
return [...DEFAULT_AGENT_DEFS];
|
|
7941
|
+
}
|
|
7942
|
+
function saveAgentDefs(agents) {
|
|
7943
|
+
try {
|
|
7944
|
+
const dir = path9.dirname(AGENTS_FILE);
|
|
7945
|
+
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
7946
|
+
writeFileSync5(AGENTS_FILE, JSON.stringify({ agents }, null, 2), "utf-8");
|
|
7947
|
+
console.log(`[Gateway] Saved ${agents.length} agent definitions to ${AGENTS_FILE}`);
|
|
7948
|
+
} catch (e) {
|
|
7949
|
+
console.log(`[Gateway] Failed to save agents.json: ${e}`);
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
var agentDefs = [];
|
|
6680
7953
|
function mapOrchestratorEvent(e) {
|
|
6681
7954
|
switch (e.type) {
|
|
6682
7955
|
case "task:started":
|
|
6683
7956
|
return { type: "TASK_STARTED", agentId: e.agentId, taskId: e.taskId, prompt: e.prompt };
|
|
6684
|
-
case "task:done":
|
|
7957
|
+
case "task:done": {
|
|
7958
|
+
const resultText = (e.result?.summary ?? "") + (e.result?.fullOutput ?? "");
|
|
7959
|
+
if (resultText && /\[PLAN\]/i.test(resultText)) {
|
|
7960
|
+
for (const [teamId, tp] of teamPhases) {
|
|
7961
|
+
if (tp.leadAgentId === e.agentId && tp.phase === "create") {
|
|
7962
|
+
const planOutput = e.result?.fullOutput ?? e.result?.summary ?? "";
|
|
7963
|
+
if (planOutput) {
|
|
7964
|
+
orc.setOriginalTask(e.agentId, planOutput);
|
|
7965
|
+
console.log(`[Gateway] Captured plan from create phase (${planOutput.length} chars) for design context`);
|
|
7966
|
+
}
|
|
7967
|
+
publishTeamPhase(teamId, "design", e.agentId);
|
|
7968
|
+
break;
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
if (e.isFinalResult) {
|
|
7973
|
+
for (const [teamId, tp] of teamPhases) {
|
|
7974
|
+
if (tp.leadAgentId === e.agentId && tp.phase === "execute") {
|
|
7975
|
+
publishTeamPhase(teamId, "complete", e.agentId);
|
|
7976
|
+
break;
|
|
7977
|
+
}
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
6685
7980
|
return { type: "TASK_DONE", agentId: e.agentId, taskId: e.taskId, result: e.result, isFinalResult: e.isFinalResult };
|
|
7981
|
+
}
|
|
6686
7982
|
case "task:failed":
|
|
6687
7983
|
return { type: "TASK_FAILED", agentId: e.agentId, taskId: e.taskId, error: e.error };
|
|
6688
7984
|
case "task:delegated":
|
|
@@ -6698,7 +7994,7 @@ function mapOrchestratorEvent(e) {
|
|
|
6698
7994
|
case "task:queued":
|
|
6699
7995
|
return { type: "TASK_QUEUED", agentId: e.agentId, taskId: e.taskId, prompt: e.prompt, position: e.position };
|
|
6700
7996
|
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 };
|
|
7997
|
+
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
7998
|
case "agent:fired":
|
|
6703
7999
|
return { type: "AGENT_FIRED", agentId: e.agentId };
|
|
6704
8000
|
case "task:result-returned":
|
|
@@ -6735,6 +8031,8 @@ function handleCommand(parsed) {
|
|
|
6735
8031
|
}
|
|
6736
8032
|
case "FIRE_AGENT": {
|
|
6737
8033
|
console.log(`[Gateway] Firing agent: ${parsed.agentId}`);
|
|
8034
|
+
const agentToFire = orc.getAgent(parsed.agentId);
|
|
8035
|
+
if (agentToFire?.pid) scanner?.addGracePid(agentToFire.pid);
|
|
6738
8036
|
orc.removeAgent(parsed.agentId);
|
|
6739
8037
|
break;
|
|
6740
8038
|
}
|
|
@@ -6755,7 +8053,23 @@ function handleCommand(parsed) {
|
|
|
6755
8053
|
}
|
|
6756
8054
|
if (agent) {
|
|
6757
8055
|
console.log(`[Gateway] RUN_TASK: agent=${parsed.agentId}, isLead=${orc.isTeamLead(parsed.agentId)}, hasTeam=${orc.getAllAgents().length > 1}`);
|
|
6758
|
-
|
|
8056
|
+
let phaseOverride;
|
|
8057
|
+
if (orc.isTeamLead(parsed.agentId)) {
|
|
8058
|
+
for (const [, tp] of teamPhases) {
|
|
8059
|
+
if (tp.leadAgentId === parsed.agentId) {
|
|
8060
|
+
phaseOverride = tp.phase;
|
|
8061
|
+
if (tp.phase === "complete") {
|
|
8062
|
+
const teamId = Array.from(teamPhases.entries()).find(([, v]) => v.leadAgentId === parsed.agentId)?.[0];
|
|
8063
|
+
if (teamId) {
|
|
8064
|
+
publishTeamPhase(teamId, "execute", parsed.agentId);
|
|
8065
|
+
phaseOverride = "execute";
|
|
8066
|
+
}
|
|
8067
|
+
}
|
|
8068
|
+
break;
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
orc.runTask(parsed.agentId, parsed.taskId, parsed.prompt, { repoPath: parsed.repoPath, phaseOverride });
|
|
6759
8073
|
} else {
|
|
6760
8074
|
publishEvent({
|
|
6761
8075
|
type: "TASK_FAILED",
|
|
@@ -6775,63 +8089,78 @@ function handleCommand(parsed) {
|
|
|
6775
8089
|
break;
|
|
6776
8090
|
}
|
|
6777
8091
|
case "SERVE_PREVIEW": {
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
8092
|
+
if (parsed.previewCmd && parsed.previewPort) {
|
|
8093
|
+
const cwd = parsed.cwd ?? config.defaultWorkspace;
|
|
8094
|
+
console.log(`[Gateway] SERVE_PREVIEW (cmd): "${parsed.previewCmd}" port=${parsed.previewPort} cwd=${cwd}`);
|
|
8095
|
+
previewServer.runCommand(parsed.previewCmd, cwd, parsed.previewPort);
|
|
8096
|
+
} else if (parsed.previewCmd) {
|
|
8097
|
+
const cwd = parsed.cwd ?? config.defaultWorkspace;
|
|
8098
|
+
console.log(`[Gateway] SERVE_PREVIEW (launch): "${parsed.previewCmd}" cwd=${cwd}`);
|
|
8099
|
+
previewServer.launchProcess(parsed.previewCmd, cwd);
|
|
8100
|
+
} else if (parsed.filePath) {
|
|
8101
|
+
console.log(`[Gateway] SERVE_PREVIEW (static): ${parsed.filePath}`);
|
|
8102
|
+
previewServer.serve(parsed.filePath);
|
|
8103
|
+
}
|
|
6781
8104
|
break;
|
|
6782
8105
|
}
|
|
6783
8106
|
case "OPEN_FILE": {
|
|
6784
8107
|
const raw = parsed.path;
|
|
6785
|
-
const resolved =
|
|
6786
|
-
const normalized =
|
|
6787
|
-
if (!normalized.startsWith(config.defaultWorkspace +
|
|
8108
|
+
const resolved = path9.resolve(config.defaultWorkspace, raw);
|
|
8109
|
+
const normalized = path9.normalize(resolved);
|
|
8110
|
+
if (!normalized.startsWith(config.defaultWorkspace + path9.sep) && normalized !== config.defaultWorkspace) {
|
|
6788
8111
|
console.error(`[Gateway] Blocked OPEN_FILE: path "${raw}" resolves outside workspace`);
|
|
6789
8112
|
break;
|
|
6790
8113
|
}
|
|
6791
|
-
if (!
|
|
8114
|
+
if (!existsSync7(normalized)) {
|
|
6792
8115
|
console.error(`[Gateway] OPEN_FILE: path does not exist: ${normalized}`);
|
|
6793
8116
|
break;
|
|
6794
8117
|
}
|
|
6795
8118
|
console.log(`[Gateway] Opening file: ${normalized}`);
|
|
6796
|
-
|
|
8119
|
+
execFile3("open", [normalized], (err) => {
|
|
6797
8120
|
if (err) console.error(`[Gateway] Failed to open file: ${err.message}`);
|
|
6798
8121
|
});
|
|
6799
8122
|
break;
|
|
6800
8123
|
}
|
|
6801
8124
|
case "CREATE_TEAM": {
|
|
6802
|
-
const {
|
|
6803
|
-
const
|
|
6804
|
-
console.log(`[Gateway] Creating team: lead=${
|
|
8125
|
+
const { leadId, memberIds, backends: backends2 } = parsed;
|
|
8126
|
+
const allIds = [leadId, ...memberIds.filter((id) => id !== leadId)];
|
|
8127
|
+
console.log(`[Gateway] Creating team: lead=${leadId}, members=${memberIds.join(",")}`);
|
|
6805
8128
|
let leadAgentId = null;
|
|
6806
8129
|
const teamId = `team-${nanoid5(6)}`;
|
|
6807
|
-
for (const
|
|
6808
|
-
const
|
|
6809
|
-
if (!
|
|
8130
|
+
for (const defId of allIds) {
|
|
8131
|
+
const def = agentDefs.find((a) => a.id === defId);
|
|
8132
|
+
if (!def) {
|
|
8133
|
+
console.log(`[Gateway] Agent def not found: ${defId}`);
|
|
8134
|
+
continue;
|
|
8135
|
+
}
|
|
6810
8136
|
const agentId = `agent-${nanoid5(6)}`;
|
|
6811
|
-
const backendId = backends2?.[
|
|
6812
|
-
if (
|
|
8137
|
+
const backendId = backends2?.[defId] ?? config.defaultBackend;
|
|
8138
|
+
if (defId === leadId) {
|
|
6813
8139
|
leadAgentId = agentId;
|
|
6814
8140
|
orc.setTeamLead(agentId);
|
|
6815
8141
|
}
|
|
6816
8142
|
orc.createAgent({
|
|
6817
8143
|
agentId,
|
|
6818
|
-
name:
|
|
6819
|
-
role:
|
|
6820
|
-
personality:
|
|
8144
|
+
name: def.name,
|
|
8145
|
+
role: def.skills ? `${def.role} \u2014 ${def.skills}` : def.role,
|
|
8146
|
+
personality: def.personality,
|
|
6821
8147
|
backend: backendId,
|
|
6822
|
-
palette:
|
|
8148
|
+
palette: def.palette,
|
|
6823
8149
|
teamId
|
|
6824
8150
|
});
|
|
6825
8151
|
}
|
|
6826
8152
|
if (leadAgentId) {
|
|
6827
|
-
const
|
|
8153
|
+
const leadDef = agentDefs.find((a) => a.id === leadId);
|
|
6828
8154
|
publishEvent({
|
|
6829
8155
|
type: "TEAM_CHAT",
|
|
6830
8156
|
fromAgentId: leadAgentId,
|
|
6831
|
-
message: `Team created! ${
|
|
8157
|
+
message: `Team created! ${leadDef?.name ?? "Lead"} is the Team Lead with ${memberIds.length} team members.`,
|
|
6832
8158
|
messageType: "status",
|
|
6833
8159
|
timestamp: Date.now()
|
|
6834
8160
|
});
|
|
8161
|
+
publishTeamPhase(teamId, "create", leadAgentId);
|
|
8162
|
+
const greetTaskId = nanoid5();
|
|
8163
|
+
orc.runTask(leadAgentId, greetTaskId, "Greet the user and ask what they would like to build.", { phaseOverride: "create" });
|
|
6835
8164
|
}
|
|
6836
8165
|
break;
|
|
6837
8166
|
}
|
|
@@ -6842,7 +8171,83 @@ function handleCommand(parsed) {
|
|
|
6842
8171
|
}
|
|
6843
8172
|
case "FIRE_TEAM": {
|
|
6844
8173
|
console.log("[Gateway] Firing entire team");
|
|
8174
|
+
for (const agent of orc.getAllAgents()) {
|
|
8175
|
+
const pid = agent.pid;
|
|
8176
|
+
if (pid) scanner?.addGracePid(pid);
|
|
8177
|
+
}
|
|
6845
8178
|
orc.fireTeam();
|
|
8179
|
+
teamPhases.clear();
|
|
8180
|
+
break;
|
|
8181
|
+
}
|
|
8182
|
+
case "KILL_EXTERNAL": {
|
|
8183
|
+
const ext = externalAgents.get(parsed.agentId);
|
|
8184
|
+
if (ext) {
|
|
8185
|
+
console.log(`[Gateway] Killing external process: ${ext.name} (pid=${ext.pid})`);
|
|
8186
|
+
scanner?.addGracePid(ext.pid);
|
|
8187
|
+
try {
|
|
8188
|
+
process.kill(ext.pid, "SIGKILL");
|
|
8189
|
+
} catch (err) {
|
|
8190
|
+
console.error(`[Gateway] Failed to kill pid ${ext.pid}:`, err);
|
|
8191
|
+
}
|
|
8192
|
+
outputReader?.detach(ext.agentId);
|
|
8193
|
+
externalAgents.delete(ext.agentId);
|
|
8194
|
+
publishEvent({ type: "AGENT_FIRED", agentId: ext.agentId });
|
|
8195
|
+
} else {
|
|
8196
|
+
console.log(`[Gateway] KILL_EXTERNAL: agent ${parsed.agentId} not found`);
|
|
8197
|
+
}
|
|
8198
|
+
break;
|
|
8199
|
+
}
|
|
8200
|
+
case "APPROVE_PLAN": {
|
|
8201
|
+
const agentId = parsed.agentId;
|
|
8202
|
+
console.log(`[Gateway] APPROVE_PLAN: agent=${agentId}`);
|
|
8203
|
+
const approvedPlan = orc.getLeaderLastOutput(agentId);
|
|
8204
|
+
if (approvedPlan) {
|
|
8205
|
+
orc.setOriginalTask(agentId, approvedPlan);
|
|
8206
|
+
console.log(`[Gateway] Captured approved plan (${approvedPlan.length} chars) for leader ${agentId}`);
|
|
8207
|
+
}
|
|
8208
|
+
const projectName = extractProjectName(approvedPlan ?? "project");
|
|
8209
|
+
const projectDir = createUniqueProjectDir(config.defaultWorkspace, projectName);
|
|
8210
|
+
orc.setTeamProjectDir(projectDir);
|
|
8211
|
+
let approveTeamId;
|
|
8212
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8213
|
+
if (tp.leadAgentId === agentId) {
|
|
8214
|
+
approveTeamId = teamId;
|
|
8215
|
+
break;
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
if (!approveTeamId) {
|
|
8219
|
+
const agentInfo = orc.getAllAgents().find((a) => a.agentId === agentId);
|
|
8220
|
+
if (agentInfo?.teamId) approveTeamId = agentInfo.teamId;
|
|
8221
|
+
}
|
|
8222
|
+
if (approveTeamId) {
|
|
8223
|
+
publishTeamPhase(approveTeamId, "execute", agentId);
|
|
8224
|
+
const taskId = nanoid5();
|
|
8225
|
+
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" });
|
|
8226
|
+
}
|
|
8227
|
+
break;
|
|
8228
|
+
}
|
|
8229
|
+
case "END_PROJECT": {
|
|
8230
|
+
const agentId = parsed.agentId;
|
|
8231
|
+
console.log(`[Gateway] END_PROJECT: agent=${agentId}`);
|
|
8232
|
+
orc.clearLeaderHistory(agentId);
|
|
8233
|
+
let foundTeamId;
|
|
8234
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8235
|
+
if (tp.leadAgentId === agentId) {
|
|
8236
|
+
foundTeamId = teamId;
|
|
8237
|
+
break;
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
if (!foundTeamId) {
|
|
8241
|
+
const agentInfo = orc.getAllAgents().find((a) => a.agentId === agentId);
|
|
8242
|
+
if (agentInfo?.teamId) foundTeamId = agentInfo.teamId;
|
|
8243
|
+
}
|
|
8244
|
+
if (foundTeamId) {
|
|
8245
|
+
publishTeamPhase(foundTeamId, "create", agentId);
|
|
8246
|
+
const greetTaskId = nanoid5();
|
|
8247
|
+
orc.runTask(agentId, greetTaskId, "Greet the user and ask what they would like to build next.", { phaseOverride: "create" });
|
|
8248
|
+
} else {
|
|
8249
|
+
console.log(`[Gateway] END_PROJECT: no team found for agent ${agentId}, ignoring`);
|
|
8250
|
+
}
|
|
6846
8251
|
break;
|
|
6847
8252
|
}
|
|
6848
8253
|
case "PING": {
|
|
@@ -6856,7 +8261,7 @@ function handleCommand(parsed) {
|
|
|
6856
8261
|
palette: agent.palette,
|
|
6857
8262
|
personality: void 0,
|
|
6858
8263
|
backend: agent.backend,
|
|
6859
|
-
isTeamLead: agent.isTeamLead,
|
|
8264
|
+
isTeamLead: agent.isTeamLead || void 0,
|
|
6860
8265
|
teamId: agent.teamId
|
|
6861
8266
|
});
|
|
6862
8267
|
publishEvent({
|
|
@@ -6864,7 +8269,67 @@ function handleCommand(parsed) {
|
|
|
6864
8269
|
agentId: agent.agentId,
|
|
6865
8270
|
status: agent.status
|
|
6866
8271
|
});
|
|
8272
|
+
if (agent.isTeamLead && agent.teamId && !teamPhases.has(agent.teamId)) {
|
|
8273
|
+
teamPhases.set(agent.teamId, { phase: "create", leadAgentId: agent.agentId });
|
|
8274
|
+
console.log(`[Gateway] Restored team phase for ${agent.teamId} (leader=${agent.agentId})`);
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
for (const [teamId, tp] of teamPhases) {
|
|
8278
|
+
publishEvent({
|
|
8279
|
+
type: "TEAM_PHASE",
|
|
8280
|
+
teamId,
|
|
8281
|
+
phase: tp.phase,
|
|
8282
|
+
leadAgentId: tp.leadAgentId
|
|
8283
|
+
});
|
|
8284
|
+
}
|
|
8285
|
+
for (const [, ext] of externalAgents) {
|
|
8286
|
+
publishEvent({
|
|
8287
|
+
type: "AGENT_CREATED",
|
|
8288
|
+
agentId: ext.agentId,
|
|
8289
|
+
name: ext.name,
|
|
8290
|
+
role: ext.cwd ? ext.cwd.split("/").pop() ?? ext.backendId : ext.backendId,
|
|
8291
|
+
isExternal: true,
|
|
8292
|
+
pid: ext.pid,
|
|
8293
|
+
cwd: ext.cwd ?? void 0,
|
|
8294
|
+
startedAt: ext.startedAt,
|
|
8295
|
+
backend: ext.backendId
|
|
8296
|
+
});
|
|
8297
|
+
publishEvent({
|
|
8298
|
+
type: "AGENT_STATUS",
|
|
8299
|
+
agentId: ext.agentId,
|
|
8300
|
+
status: ext.status
|
|
8301
|
+
});
|
|
8302
|
+
}
|
|
8303
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
8304
|
+
break;
|
|
8305
|
+
}
|
|
8306
|
+
case "SAVE_AGENT_DEF": {
|
|
8307
|
+
const def = parsed.agent;
|
|
8308
|
+
const idx = agentDefs.findIndex((a) => a.id === def.id);
|
|
8309
|
+
if (idx >= 0) {
|
|
8310
|
+
if (agentDefs[idx].isBuiltin) {
|
|
8311
|
+
def.isBuiltin = true;
|
|
8312
|
+
def.teamRole = agentDefs[idx].teamRole;
|
|
8313
|
+
}
|
|
8314
|
+
agentDefs[idx] = def;
|
|
8315
|
+
} else {
|
|
8316
|
+
def.isBuiltin = false;
|
|
8317
|
+
def.teamRole = "dev";
|
|
8318
|
+
agentDefs.push(def);
|
|
6867
8319
|
}
|
|
8320
|
+
saveAgentDefs(agentDefs);
|
|
8321
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
8322
|
+
break;
|
|
8323
|
+
}
|
|
8324
|
+
case "DELETE_AGENT_DEF": {
|
|
8325
|
+
const target = agentDefs.find((a) => a.id === parsed.agentDefId);
|
|
8326
|
+
if (target?.isBuiltin) {
|
|
8327
|
+
console.log(`[Gateway] Cannot delete built-in agent: ${parsed.agentDefId}`);
|
|
8328
|
+
break;
|
|
8329
|
+
}
|
|
8330
|
+
agentDefs = agentDefs.filter((a) => a.id !== parsed.agentDefId);
|
|
8331
|
+
saveAgentDefs(agentDefs);
|
|
8332
|
+
publishEvent({ type: "AGENT_DEFS", agents: agentDefs });
|
|
6868
8333
|
break;
|
|
6869
8334
|
}
|
|
6870
8335
|
}
|
|
@@ -6892,9 +8357,11 @@ async function main() {
|
|
|
6892
8357
|
worktree: false,
|
|
6893
8358
|
// disabled by default for now
|
|
6894
8359
|
retry: { maxRetries: 2, escalateToLeader: true },
|
|
6895
|
-
promptsDir:
|
|
8360
|
+
promptsDir: path9.join(os2.homedir(), ".bit-office", "prompts"),
|
|
6896
8361
|
sandboxMode: config.sandboxMode
|
|
6897
8362
|
});
|
|
8363
|
+
agentDefs = loadAgentDefs();
|
|
8364
|
+
console.log(`[Gateway] Loaded ${agentDefs.length} agent definitions (${agentDefs.filter((a) => !a.isBuiltin).length} custom)`);
|
|
6898
8365
|
const forwardEvent = (event) => {
|
|
6899
8366
|
const mapped = mapOrchestratorEvent(event);
|
|
6900
8367
|
if (mapped) publishEvent(mapped);
|
|
@@ -6914,6 +8381,99 @@ async function main() {
|
|
|
6914
8381
|
orc.on("agent:created", forwardEvent);
|
|
6915
8382
|
orc.on("agent:fired", forwardEvent);
|
|
6916
8383
|
orc.on("task:result-returned", forwardEvent);
|
|
8384
|
+
outputReader = new ExternalOutputReader();
|
|
8385
|
+
outputReader.setOnStatus((agentId, status) => {
|
|
8386
|
+
const ext = externalAgents.get(agentId);
|
|
8387
|
+
if (ext && ext.status !== status) {
|
|
8388
|
+
ext.status = status;
|
|
8389
|
+
publishEvent({
|
|
8390
|
+
type: "AGENT_STATUS",
|
|
8391
|
+
agentId,
|
|
8392
|
+
status
|
|
8393
|
+
});
|
|
8394
|
+
}
|
|
8395
|
+
});
|
|
8396
|
+
outputReader.setOnTokenUpdate((agentId, inputTokens, outputTokens) => {
|
|
8397
|
+
publishEvent({
|
|
8398
|
+
type: "TOKEN_UPDATE",
|
|
8399
|
+
agentId,
|
|
8400
|
+
inputTokens,
|
|
8401
|
+
outputTokens
|
|
8402
|
+
});
|
|
8403
|
+
});
|
|
8404
|
+
scanner = new ProcessScanner(
|
|
8405
|
+
() => orc.getManagedPids(),
|
|
8406
|
+
{
|
|
8407
|
+
onAdded: (agents) => {
|
|
8408
|
+
for (const agent of agents) {
|
|
8409
|
+
const name = agent.command.charAt(0).toUpperCase() + agent.command.slice(1);
|
|
8410
|
+
const displayName = `${name} (${agent.pid})`;
|
|
8411
|
+
externalAgents.set(agent.agentId, {
|
|
8412
|
+
agentId: agent.agentId,
|
|
8413
|
+
name: displayName,
|
|
8414
|
+
backendId: agent.backendId,
|
|
8415
|
+
pid: agent.pid,
|
|
8416
|
+
cwd: agent.cwd,
|
|
8417
|
+
startedAt: agent.startedAt,
|
|
8418
|
+
status: agent.status
|
|
8419
|
+
});
|
|
8420
|
+
console.log(`[ProcessScanner] External agent found: ${displayName} (pid=${agent.pid}, cwd=${agent.cwd})`);
|
|
8421
|
+
publishEvent({
|
|
8422
|
+
type: "AGENT_CREATED",
|
|
8423
|
+
agentId: agent.agentId,
|
|
8424
|
+
name: displayName,
|
|
8425
|
+
role: agent.cwd ? agent.cwd.split("/").pop() ?? agent.backendId : agent.backendId,
|
|
8426
|
+
isExternal: true,
|
|
8427
|
+
pid: agent.pid,
|
|
8428
|
+
cwd: agent.cwd ?? void 0,
|
|
8429
|
+
startedAt: agent.startedAt,
|
|
8430
|
+
backend: agent.backendId
|
|
8431
|
+
});
|
|
8432
|
+
publishEvent({
|
|
8433
|
+
type: "AGENT_STATUS",
|
|
8434
|
+
agentId: agent.agentId,
|
|
8435
|
+
status: agent.status
|
|
8436
|
+
});
|
|
8437
|
+
outputReader?.attach(agent.agentId, agent.pid, agent.cwd, agent.backendId, (chunk) => {
|
|
8438
|
+
publishEvent({
|
|
8439
|
+
type: "LOG_APPEND",
|
|
8440
|
+
agentId: agent.agentId,
|
|
8441
|
+
taskId: "external",
|
|
8442
|
+
stream: "stdout",
|
|
8443
|
+
chunk
|
|
8444
|
+
});
|
|
8445
|
+
});
|
|
8446
|
+
}
|
|
8447
|
+
},
|
|
8448
|
+
onRemoved: (agentIds) => {
|
|
8449
|
+
for (const agentId of agentIds) {
|
|
8450
|
+
const ext = externalAgents.get(agentId);
|
|
8451
|
+
console.log(`[ProcessScanner] External agent gone: ${ext?.name ?? agentId}`);
|
|
8452
|
+
outputReader?.detach(agentId);
|
|
8453
|
+
externalAgents.delete(agentId);
|
|
8454
|
+
publishEvent({
|
|
8455
|
+
type: "AGENT_FIRED",
|
|
8456
|
+
agentId
|
|
8457
|
+
});
|
|
8458
|
+
}
|
|
8459
|
+
},
|
|
8460
|
+
onChanged: (agents) => {
|
|
8461
|
+
for (const agent of agents) {
|
|
8462
|
+
const ext = externalAgents.get(agent.agentId);
|
|
8463
|
+
if (ext?.backendId === "claude") continue;
|
|
8464
|
+
if (ext) {
|
|
8465
|
+
ext.status = agent.status;
|
|
8466
|
+
}
|
|
8467
|
+
publishEvent({
|
|
8468
|
+
type: "AGENT_STATUS",
|
|
8469
|
+
agentId: agent.agentId,
|
|
8470
|
+
status: agent.status
|
|
8471
|
+
});
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
);
|
|
8476
|
+
scanner.start();
|
|
6917
8477
|
const backendNames = config.detectedBackends.map((id) => getBackend(id)?.name ?? id).join(", ");
|
|
6918
8478
|
console.log(`[Gateway] AI backends: ${backendNames || "none detected"} (default: ${getBackend(config.defaultBackend)?.name ?? config.defaultBackend})`);
|
|
6919
8479
|
console.log(`[Gateway] Permissions: ${config.sandboxMode === "full" ? "Full access" : "Sandbox"}`);
|
|
@@ -6922,10 +8482,10 @@ async function main() {
|
|
|
6922
8482
|
await initTransports(handleCommand);
|
|
6923
8483
|
console.log("[Gateway] Listening for commands...");
|
|
6924
8484
|
console.log("[Gateway] Press 'p' + Enter to generate a new pair code");
|
|
6925
|
-
if (process.env.NODE_ENV !== "development" &&
|
|
8485
|
+
if (process.env.NODE_ENV !== "development" && existsSync7(config.webDir)) {
|
|
6926
8486
|
const url = `http://localhost:${config.wsPort}`;
|
|
6927
8487
|
console.log(`[Gateway] Opening ${url}`);
|
|
6928
|
-
|
|
8488
|
+
execFile3("open", [url]);
|
|
6929
8489
|
}
|
|
6930
8490
|
if (process.stdin.isTTY) {
|
|
6931
8491
|
process.stdin.setEncoding("utf-8");
|
|
@@ -6939,6 +8499,8 @@ async function main() {
|
|
|
6939
8499
|
}
|
|
6940
8500
|
function cleanup() {
|
|
6941
8501
|
console.log("[Gateway] Shutting down...");
|
|
8502
|
+
outputReader?.detachAll();
|
|
8503
|
+
scanner?.stop();
|
|
6942
8504
|
previewServer.stop();
|
|
6943
8505
|
orc?.destroy();
|
|
6944
8506
|
destroyTransports();
|