the-citadel 0.9.5 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of the-citadel might be problematic. Click here for more details.
- package/dist/cli.js +448 -1544
- package/dist/config/schema.d.ts +1 -0
- package/dist/core/instruction.d.ts +21 -0
- package/dist/core/queue.d.ts +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +51756 -54874
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
2
|
import { createRequire } from "node:module";
|
|
4
3
|
var __create = Object.create;
|
|
5
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -45500,10 +45499,10 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
45500
45499
|
});
|
|
45501
45500
|
|
|
45502
45501
|
// src/cli.ts
|
|
45503
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
45504
|
-
import { access, mkdir, unlink, writeFile } from "fs/promises";
|
|
45505
|
-
import { dirname as
|
|
45506
|
-
import { fileURLToPath } from "url";
|
|
45502
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
45503
|
+
import { access, mkdir, unlink, writeFile } from "node:fs/promises";
|
|
45504
|
+
import { dirname as dirname4, join as join6, resolve as resolve9 } from "node:path";
|
|
45505
|
+
import { fileURLToPath } from "node:url";
|
|
45507
45506
|
|
|
45508
45507
|
// node_modules/commander/esm.mjs
|
|
45509
45508
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -65092,6 +65091,7 @@ var ConfigSchema = exports_external.object({
|
|
|
65092
65091
|
maxMessageSize: 1e5
|
|
65093
65092
|
}),
|
|
65094
65093
|
hooks: exports_external.object({
|
|
65094
|
+
onPearlStart: exports_external.any().optional(),
|
|
65095
65095
|
onPearlDone: exports_external.any().optional()
|
|
65096
65096
|
}).optional(),
|
|
65097
65097
|
logging: exports_external.object({
|
|
@@ -65149,6 +65149,11 @@ async function loadConfig() {
|
|
|
65149
65149
|
logger.debug("[Config] Loaded from file/env");
|
|
65150
65150
|
return config2;
|
|
65151
65151
|
}
|
|
65152
|
+
function setConfig(config2) {
|
|
65153
|
+
const parsed = ConfigSchema.parse(config2);
|
|
65154
|
+
logger.debug("[Config] Manually set config");
|
|
65155
|
+
setGlobalSingleton(CONFIG_KEY, parsed);
|
|
65156
|
+
}
|
|
65152
65157
|
function getConfig() {
|
|
65153
65158
|
const config2 = getGlobalSingleton(CONFIG_KEY, () => null);
|
|
65154
65159
|
if (!config2) {
|
|
@@ -85126,10 +85131,39 @@ ${results.join(`
|
|
|
85126
85131
|
}
|
|
85127
85132
|
|
|
85128
85133
|
// src/core/instruction.ts
|
|
85134
|
+
class CustomProtocolProvider {
|
|
85135
|
+
name = "custom-protocol";
|
|
85136
|
+
priority = 5;
|
|
85137
|
+
async getInstructions(_ctx) {
|
|
85138
|
+
const path2 = resolve7(process.cwd(), ".citadel/instructions/protocol.md");
|
|
85139
|
+
if (existsSync7(path2)) {
|
|
85140
|
+
try {
|
|
85141
|
+
const content = await readFile3(path2, "utf-8");
|
|
85142
|
+
return `# ⚠️ PROTOCOL (Highest Priority)
|
|
85143
|
+
${content}`;
|
|
85144
|
+
} catch (err) {
|
|
85145
|
+
logger.error(`[CustomProtocolProvider] Failed to read ${path2}:`, err);
|
|
85146
|
+
}
|
|
85147
|
+
}
|
|
85148
|
+
return null;
|
|
85149
|
+
}
|
|
85150
|
+
}
|
|
85151
|
+
|
|
85129
85152
|
class GlobalProvider {
|
|
85130
85153
|
name = "global";
|
|
85131
85154
|
priority = 10;
|
|
85132
85155
|
async getInstructions(ctx) {
|
|
85156
|
+
const parts = [];
|
|
85157
|
+
const projectContext = await getProjectContext().resolveContext(process.cwd(), process.cwd());
|
|
85158
|
+
if (projectContext) {
|
|
85159
|
+
parts.push(`# PROJECT RULES (AGENTS.md)
|
|
85160
|
+
You must follow these rules from the project configuration:
|
|
85161
|
+
|
|
85162
|
+
## Raw Configuration
|
|
85163
|
+
${projectContext.config.raw}
|
|
85164
|
+
|
|
85165
|
+
Always prioritize these project-specific instructions over general knowledge.`);
|
|
85166
|
+
}
|
|
85133
85167
|
if (ctx.pearlId) {
|
|
85134
85168
|
try {
|
|
85135
85169
|
const pearl = await getPearls().get(ctx.pearlId);
|
|
@@ -85137,62 +85171,65 @@ class GlobalProvider {
|
|
|
85137
85171
|
const agentPath = resolve7(process.cwd(), `agents/${pearl.assignee}.md`);
|
|
85138
85172
|
if (existsSync7(agentPath)) {
|
|
85139
85173
|
const content = await readFile3(agentPath, "utf-8");
|
|
85140
|
-
|
|
85141
|
-
|
|
85142
|
-
You are acting as ${pearl.assignee}. Follow these specific instructions and style guidelines:
|
|
85174
|
+
parts.push(`# AGENT PERSONA: ${pearl.assignee.toUpperCase()}
|
|
85175
|
+
You are embodying the persona of ${pearl.assignee}. Adopt the following style and voice, while still adhering to the project rules above:
|
|
85143
85176
|
|
|
85144
|
-
|
|
85145
|
-
${content}
|
|
85146
|
-
`;
|
|
85177
|
+
${content}`);
|
|
85147
85178
|
}
|
|
85148
85179
|
}
|
|
85149
85180
|
} catch (err) {
|
|
85150
|
-
logger.warn(`[GlobalProvider] Error fetching assignee
|
|
85181
|
+
logger.warn(`[GlobalProvider] Error fetching assignee persona for ${ctx.pearlId}: ${err}`);
|
|
85151
85182
|
}
|
|
85152
85183
|
}
|
|
85153
|
-
|
|
85154
|
-
if (!projectContext)
|
|
85155
|
-
return null;
|
|
85156
|
-
return `
|
|
85157
|
-
# PROJECT RULES (AGENTS.md)
|
|
85158
|
-
You must follow these rules from the project configuration:
|
|
85184
|
+
return parts.length > 0 ? parts.join(`
|
|
85159
85185
|
|
|
85160
|
-
|
|
85161
|
-
${projectContext.config.raw}
|
|
85186
|
+
---
|
|
85162
85187
|
|
|
85163
|
-
|
|
85164
|
-
`;
|
|
85188
|
+
`) : null;
|
|
85165
85189
|
}
|
|
85166
85190
|
}
|
|
85167
85191
|
|
|
85168
85192
|
class BuiltinProvider {
|
|
85169
85193
|
name = "builtin";
|
|
85170
85194
|
priority = 15;
|
|
85195
|
+
static WORKER_TYPE_ROLES = [
|
|
85196
|
+
"worker",
|
|
85197
|
+
"software_developer",
|
|
85198
|
+
"qa",
|
|
85199
|
+
"product",
|
|
85200
|
+
"research"
|
|
85201
|
+
];
|
|
85171
85202
|
async getInstructions(ctx) {
|
|
85172
|
-
|
|
85173
|
-
|
|
85174
|
-
|
|
85175
|
-
|
|
85176
|
-
|
|
85177
|
-
|
|
85178
|
-
|
|
85179
|
-
- Use \`
|
|
85180
|
-
|
|
85181
|
-
|
|
85182
|
-
|
|
85183
|
-
|
|
85184
|
-
-
|
|
85185
|
-
-
|
|
85186
|
-
|
|
85187
|
-
|
|
85203
|
+
const parts = [];
|
|
85204
|
+
if (BuiltinProvider.WORKER_TYPE_ROLES.includes(ctx.role)) {
|
|
85205
|
+
parts.push(`## Worker Base Protocol
|
|
85206
|
+
|
|
85207
|
+
### Skills-First Workflow
|
|
85208
|
+
Before reasoning from scratch, you MUST leverage the company's specialized skills library:
|
|
85209
|
+
- **Discovery**: Use \`skills:list_skills\` to see available methodologies.
|
|
85210
|
+
- **Utilization**: Use \`skills:get_skill\` to read the instructions for a relevant skill and follow them.
|
|
85211
|
+
|
|
85212
|
+
### Memory Retrieval
|
|
85213
|
+
If no skill exists, use the QMD system:
|
|
85214
|
+
- \`qmd:search\` for keyword lookup in \`memories/\` or \`docs/\`.
|
|
85215
|
+
- \`qmd:vector_search\` for conceptual / semantic search.
|
|
85216
|
+
- \`qmd:deep_search\` for highest quality historical context.
|
|
85217
|
+
|
|
85218
|
+
### Persistence & Git
|
|
85219
|
+
- Every successful task must result in an atomic Git commit.
|
|
85220
|
+
- Use descriptive messages: \`feat(role): summary [pearlId]\`.
|
|
85221
|
+
|
|
85222
|
+
### Escalation & Delegation
|
|
85223
|
+
- You are NOT authorized to call \`escalate_to_human\` directly.
|
|
85224
|
+
- Delegate to a C-level role (\`ceo\`, \`cto\`, or \`cfo\`) via \`delegate_task\` if human input is required.`);
|
|
85188
85225
|
}
|
|
85189
85226
|
if (ctx.role === "gatekeeper") {
|
|
85190
|
-
|
|
85191
|
-
|
|
85192
|
-
You are the Gatekeeper (Evaluator). Your purpose is to verify that the work meets the requirements.
|
|
85193
|
-
`;
|
|
85227
|
+
parts.push(`## Verification Mode
|
|
85228
|
+
You are the Gatekeeper (Evaluator). Your purpose is to verify that the work meets the requirements. You MUST finalize with \`approve_work\`, \`reject_work\`, or \`fail_work\`.`);
|
|
85194
85229
|
}
|
|
85195
|
-
return
|
|
85230
|
+
return parts.length > 0 ? parts.join(`
|
|
85231
|
+
|
|
85232
|
+
`) : null;
|
|
85196
85233
|
}
|
|
85197
85234
|
}
|
|
85198
85235
|
|
|
@@ -85279,17 +85316,32 @@ ${ctx.context.custom_instructions}`;
|
|
|
85279
85316
|
}
|
|
85280
85317
|
}
|
|
85281
85318
|
|
|
85319
|
+
class EnforcementProvider {
|
|
85320
|
+
name = "enforcement";
|
|
85321
|
+
priority = 100;
|
|
85322
|
+
async getInstructions(_ctx) {
|
|
85323
|
+
return `# MANDATORY COMPLETION PROTOCOL
|
|
85324
|
+
You are strictly prohibited from ending your response with text alone.
|
|
85325
|
+
To finalize your work, you MUST call exactly one of the completion tools: \`submit_work\`, \`approve_work\`, \`reject_work\`, or \`fail_work\`.
|
|
85326
|
+
|
|
85327
|
+
**Failure to call a completion tool will result in the task being discarded as incomplete.**
|
|
85328
|
+
Text-only summaries are NOT submissions. You must trigger the framework catalyst tools.`;
|
|
85329
|
+
}
|
|
85330
|
+
}
|
|
85331
|
+
|
|
85282
85332
|
class InstructionService {
|
|
85283
85333
|
providers = [];
|
|
85284
85334
|
constructor() {
|
|
85285
85335
|
this.providers = [
|
|
85336
|
+
new CustomProtocolProvider,
|
|
85286
85337
|
new GlobalProvider,
|
|
85287
85338
|
new BuiltinProvider,
|
|
85288
85339
|
new RoleProvider,
|
|
85289
85340
|
new MCPResourceProvider,
|
|
85290
85341
|
new FormulaProvider,
|
|
85291
85342
|
new TagProvider,
|
|
85292
|
-
new ContextProvider
|
|
85343
|
+
new ContextProvider,
|
|
85344
|
+
new EnforcementProvider
|
|
85293
85345
|
].sort((a, b) => a.priority - b.priority);
|
|
85294
85346
|
}
|
|
85295
85347
|
async buildPrompt(ctx, basePrompt) {
|
|
@@ -94293,7 +94345,7 @@ Request: ${prompt}`
|
|
|
94293
94345
|
];
|
|
94294
94346
|
let finalResult = "";
|
|
94295
94347
|
let completionRetryCount = 0;
|
|
94296
|
-
const maxCompletionRetries =
|
|
94348
|
+
const maxCompletionRetries = 5;
|
|
94297
94349
|
let completionToolCalled = false;
|
|
94298
94350
|
const totalUsage = {
|
|
94299
94351
|
inputTokens: 0,
|
|
@@ -94397,17 +94449,19 @@ Request: ${prompt}`
|
|
|
94397
94449
|
completionRetryCount++;
|
|
94398
94450
|
if (completionRetryCount <= maxCompletionRetries) {
|
|
94399
94451
|
logger.info(`[${this.role}] Agent exited without completion tool. Providing reminder ${completionRetryCount}/${maxCompletionRetries}.`);
|
|
94400
|
-
let hint =
|
|
94401
|
-
|
|
94402
|
-
|
|
94452
|
+
let hint = `# MANDATORY COMPLETION PROTOCOL
|
|
94453
|
+
You provided a response but did not call a completion tool.
|
|
94454
|
+
To finalize your work, you MUST call exactly one of: \`submit_work\`, \`approve_work\`, \`reject_work\`, or \`fail_work\`.
|
|
94455
|
+
|
|
94456
|
+
**Text-only responses are NOT accepted as task completion.**
|
|
94457
|
+
If you are finished, submit your work now. If you are still working, continue with your next tool call.`;
|
|
94403
94458
|
if (completionRetryCount > 1) {
|
|
94404
94459
|
const completionTools = Object.keys(this.tools).filter((t) => t.includes("submit_work") || t.includes("approve_work") || t.includes("reject_work"));
|
|
94405
94460
|
const primaryTool = completionTools[0];
|
|
94406
94461
|
if (primaryTool) {
|
|
94407
94462
|
hint += `
|
|
94408
94463
|
|
|
94409
|
-
|
|
94410
|
-
Here is the schema for the primary completion tool:
|
|
94464
|
+
### Tool Schema Reference: ${primaryTool}
|
|
94411
94465
|
\`\`\`json
|
|
94412
94466
|
${JSON.stringify(this.schemas[primaryTool], null, 2)}
|
|
94413
94467
|
\`\`\``;
|
|
@@ -94849,23 +94903,7 @@ class EvaluatorAgent extends CoreAgent {
|
|
|
94849
94903
|
};
|
|
94850
94904
|
}
|
|
94851
94905
|
getSystemPrompt(defaultPrompt) {
|
|
94852
|
-
return
|
|
94853
|
-
${defaultPrompt}
|
|
94854
|
-
|
|
94855
|
-
# Context
|
|
94856
|
-
You are the Gatekeeper (Evaluator). Your goal is to VERIFY that the work meets requirements.
|
|
94857
|
-
The work submitted by the agent is available in the 'submitted_work' context variable.
|
|
94858
|
-
|
|
94859
|
-
# Instructions
|
|
94860
|
-
- If the work is a PLAN (look for 'step:plan' or 'step:planning' labels), review 'submitted_work' for logic, completeness, and adherence to requirements.
|
|
94861
|
-
- If the work is an IMPLEMENTATION (look for 'step:impl' or 'step:code' labels), inspect the filesystem and run tests.
|
|
94862
|
-
- Note that planning steps may not result in filesystem changes.
|
|
94863
|
-
- Use 'approve_work' or 'reject_work' accordingly.
|
|
94864
|
-
- CRITICAL: When using 'reject_work', you MUST provide a clear 'reason' explaining why the work was rejected so the worker can fix it.
|
|
94865
|
-
- CRITICAL: When approving work, you MUST provide 'acceptance_test'. This must be a string describing the verification performed. DO NOT pass null.
|
|
94866
|
-
- If the work is a plan, extract the acceptance criteria from the plan text.
|
|
94867
|
-
- # CRITICAL: You MUST finalize your decision using approve_work, reject_work, or fail_work. Your task is NOT complete until one of these tools is successfully called.
|
|
94868
|
-
`;
|
|
94906
|
+
return defaultPrompt;
|
|
94869
94907
|
}
|
|
94870
94908
|
}
|
|
94871
94909
|
|
|
@@ -95143,7 +95181,11 @@ class WorkQueue {
|
|
|
95143
95181
|
resetPearl(pearlId) {
|
|
95144
95182
|
this.db.run("DELETE FROM tickets WHERE pearl_id = ?", [pearlId]);
|
|
95145
95183
|
}
|
|
95146
|
-
listActive() {
|
|
95184
|
+
listActive(timeoutMs) {
|
|
95185
|
+
if (timeoutMs) {
|
|
95186
|
+
const cutoff = Date.now() - timeoutMs;
|
|
95187
|
+
return this.db.query("SELECT * FROM tickets WHERE status = 'processing' AND heartbeat_at > ?").all(cutoff);
|
|
95188
|
+
}
|
|
95147
95189
|
return this.db.query("SELECT * FROM tickets WHERE status = 'processing'").all();
|
|
95148
95190
|
}
|
|
95149
95191
|
getTicketsByStatus(status) {
|
|
@@ -95411,15 +95453,7 @@ class WorkerAgent extends CoreAgent {
|
|
|
95411
95453
|
return super.run(prompt, context2);
|
|
95412
95454
|
}
|
|
95413
95455
|
getSystemPrompt(defaultPrompt) {
|
|
95414
|
-
return
|
|
95415
|
-
${defaultPrompt}
|
|
95416
|
-
|
|
95417
|
-
# Guidelines
|
|
95418
|
-
- Use filesystem tools to explore and write code.
|
|
95419
|
-
- Run tests with run_command if available.
|
|
95420
|
-
- Keep the user informed with report_progress.
|
|
95421
|
-
- # CRITICAL: You MUST submit your work when done with the submit_work tool. DO NOT simply state that you are finished in text. You are only considered finished once submit_work is successfully called.
|
|
95422
|
-
`;
|
|
95456
|
+
return defaultPrompt;
|
|
95423
95457
|
}
|
|
95424
95458
|
}
|
|
95425
95459
|
|
|
@@ -95748,1451 +95782,13 @@ class Conductor {
|
|
|
95748
95782
|
assignee: role
|
|
95749
95783
|
});
|
|
95750
95784
|
logger.info(`[${role}] Instantiating agent for role: ${role}`, { pearlId: ticket.pearl_id });
|
|
95751
|
-
|
|
95752
|
-
try {
|
|
95753
|
-
const result = await agent.run(`Process this task: ${pearl.title}`, { pearlId: ticket.pearl_id, pearl });
|
|
95754
|
-
const finalPearl = await this.pearls.get(ticket.pearl_id);
|
|
95755
|
-
if (finalPearl.status === "in_progress") {
|
|
95756
|
-
logger.warn(`[${role}] Agent exited without submitting work for ${ticket.pearl_id}`, { pearlId: ticket.pearl_id });
|
|
95757
|
-
await this.pearls.update(ticket.pearl_id, {
|
|
95758
|
-
status: "open",
|
|
95759
|
-
labels: [...finalPearl.labels || [], "agent-incomplete"]
|
|
95760
|
-
});
|
|
95761
|
-
}
|
|
95762
|
-
return result;
|
|
95763
|
-
} catch (error48) {
|
|
95764
|
-
logger.error(`[${role}] Agent failed for ${ticket.pearl_id}`, error48);
|
|
95765
|
-
const currentLabels = pearl?.labels || [];
|
|
95766
|
-
await this.pearls.update(ticket.pearl_id, {
|
|
95767
|
-
status: "open",
|
|
95768
|
-
labels: [...currentLabels, "failed", "agent-error"]
|
|
95769
|
-
});
|
|
95770
|
-
}
|
|
95771
|
-
}, this.queue, this.config.worker.maxRetries), this.config.worker.min_workers);
|
|
95772
|
-
this.pools.set(role, pool);
|
|
95773
|
-
}
|
|
95774
|
-
this.gatekeeperPool = new PoolClass("gatekeeper", (id) => new Hook(id, "gatekeeper", async (ticket) => {
|
|
95775
|
-
logger.info(`[Gatekeeper] Verifying ${ticket.pearl_id}`, {
|
|
95776
|
-
pearlId: ticket.pearl_id
|
|
95777
|
-
});
|
|
95778
|
-
const agent = new EvaluatorAgent;
|
|
95779
|
-
const pearl = await this.pearls.get(ticket.pearl_id);
|
|
95780
|
-
await this.pearls.update(ticket.pearl_id, { assignee: "gatekeeper" });
|
|
95781
|
-
const submittedWork = pearl.output || this.queue.getOutput(ticket.pearl_id);
|
|
95782
|
-
if (!submittedWork) {
|
|
95783
|
-
logger.warn(`[Gatekeeper] No submitted work found for ${ticket.pearl_id} (retrieved 'null' from queue). Evaluator may reject.`, { pearlId: ticket.pearl_id });
|
|
95784
|
-
}
|
|
95785
|
-
try {
|
|
95786
|
-
await agent.run(`Verify this work: ${pearl.title}`, {
|
|
95787
|
-
pearlId: ticket.pearl_id,
|
|
95788
|
-
pearl,
|
|
95789
|
-
submitted_work: submittedWork
|
|
95790
|
-
});
|
|
95791
|
-
const finalPearl = await this.pearls.get(ticket.pearl_id);
|
|
95792
|
-
if (finalPearl.status === "verify") {
|
|
95793
|
-
logger.warn(`[Gatekeeper] Agent exited without decision for ${ticket.pearl_id}`, { pearlId: ticket.pearl_id });
|
|
95794
|
-
await this.pearls.update(ticket.pearl_id, {
|
|
95795
|
-
status: "verify",
|
|
95796
|
-
labels: [...finalPearl.labels || [], "evaluator-incomplete"]
|
|
95797
|
-
});
|
|
95798
|
-
}
|
|
95799
|
-
} catch (error48) {
|
|
95800
|
-
logger.error(`[Gatekeeper] Agent failed for ${ticket.pearl_id}`, error48);
|
|
95801
|
-
await this.pearls.update(ticket.pearl_id, {
|
|
95802
|
-
status: "verify",
|
|
95803
|
-
labels: [...pearl.labels || [], "evaluator-error"]
|
|
95804
|
-
});
|
|
95805
|
-
}
|
|
95806
|
-
}, this.queue, 3), this.config.gatekeeper.min_workers);
|
|
95807
|
-
}
|
|
95808
|
-
async start() {
|
|
95809
|
-
if (this.isRunning)
|
|
95810
|
-
return;
|
|
95811
|
-
this.isRunning = true;
|
|
95812
|
-
logger.info("[Conductor] Starting...");
|
|
95813
|
-
await getMCPService().initialize();
|
|
95814
|
-
const healthy = await this.validateEnvironment();
|
|
95815
|
-
if (!healthy) {
|
|
95816
|
-
this.isRunning = false;
|
|
95817
|
-
await getMCPService().shutdown();
|
|
95818
|
-
return;
|
|
95819
|
-
}
|
|
95820
|
-
for (const pool of this.pools.values()) {
|
|
95821
|
-
pool.start();
|
|
95822
|
-
}
|
|
95823
|
-
this.gatekeeperPool.start();
|
|
95824
|
-
this.routerLoop();
|
|
95825
|
-
}
|
|
95826
|
-
async stop() {
|
|
95827
|
-
this.isRunning = false;
|
|
95828
|
-
logger.info("[Conductor] Stopping...");
|
|
95829
|
-
for (const pool of this.pools.values()) {
|
|
95830
|
-
pool.stop();
|
|
95831
|
-
}
|
|
95832
|
-
this.gatekeeperPool.stop();
|
|
95833
|
-
if (this.routerTimer) {
|
|
95834
|
-
clearTimeout(this.routerTimer);
|
|
95835
|
-
this.routerTimer = null;
|
|
95836
|
-
}
|
|
95837
|
-
await getMCPService().shutdown();
|
|
95838
|
-
}
|
|
95839
|
-
async validateEnvironment() {
|
|
95840
|
-
logger.info("[Conductor] Validating environment...");
|
|
95841
|
-
const healthy = await this.pearls.doctor();
|
|
95842
|
-
if (!healthy) {
|
|
95843
|
-
logger.error('[Conductor] Environment check failed! "bd doctor" reports issues.');
|
|
95844
|
-
logger.error('[Conductor] Please run "bd doctor" and "bd sync" manually to fix data integrity issues.');
|
|
95845
|
-
return false;
|
|
95846
|
-
}
|
|
95847
|
-
return true;
|
|
95848
|
-
}
|
|
95849
|
-
async routerLoop() {
|
|
95850
|
-
if (!this.isRunning)
|
|
95851
|
-
return;
|
|
95852
|
-
let nextDelay = 5000;
|
|
95853
|
-
try {
|
|
95854
|
-
await this.cycleRouter();
|
|
95855
|
-
await this.scalePools();
|
|
95856
|
-
this.consecutiveFailures = 0;
|
|
95857
|
-
} catch (error48) {
|
|
95858
|
-
this.consecutiveFailures++;
|
|
95859
|
-
const backoff = Math.min(5000 * 2 ** this.consecutiveFailures, 300000);
|
|
95860
|
-
nextDelay = backoff;
|
|
95861
|
-
logger.error(`[Conductor] Cycle failed (attempt ${this.consecutiveFailures}). Backing off for ${Math.round(nextDelay / 1000)}s:`, error48);
|
|
95862
|
-
}
|
|
95863
|
-
if (this.isRunning) {
|
|
95864
|
-
this.routerTimer = setTimeout(() => this.routerLoop(), nextDelay);
|
|
95865
|
-
}
|
|
95866
|
-
}
|
|
95867
|
-
async cycleRouter() {
|
|
95868
|
-
const pearlsClient = this.pearls;
|
|
95869
|
-
const queue = this.queue;
|
|
95870
|
-
try {
|
|
95871
|
-
const released = queue.releaseStalled(15 * 60 * 1000);
|
|
95872
|
-
if (released > 0) {
|
|
95873
|
-
logger.info(`[Router] Released ${released} stalled/zombie tickets`);
|
|
95874
|
-
}
|
|
95875
|
-
} catch (e) {
|
|
95876
|
-
logger.error("[Router] Failed to release stalled tickets", e);
|
|
95877
|
-
}
|
|
95878
|
-
const readyPearls = await pearlsClient.ready();
|
|
95879
|
-
if (!readyPearls) {
|
|
95880
|
-
logger.error("[Conductor] readyPearls is undefined!");
|
|
95881
|
-
return;
|
|
95882
|
-
}
|
|
95883
|
-
const inProgressPearls = await pearlsClient.list("in_progress");
|
|
95884
|
-
for (const pearl of inProgressPearls) {
|
|
95885
|
-
const active = queue.getActiveTicket(pearl.id);
|
|
95886
|
-
if (!active) {
|
|
95887
|
-
const latest = queue.getLatestTicket(pearl.id);
|
|
95888
|
-
const GRACE_PERIOD_MS = 5000;
|
|
95889
|
-
if (latest && latest.status === "completed" && latest.completed_at && Date.now() - latest.completed_at < GRACE_PERIOD_MS) {
|
|
95890
|
-
logger.debug(`[Router] Deferring reset of pearl ${pearl.id} (within 5s grace period of ticket completion)`, { pearlId: pearl.id });
|
|
95891
|
-
continue;
|
|
95892
|
-
}
|
|
95893
|
-
if (pearl.labels?.includes("hitl") || pearl.labels?.includes("escalation")) {
|
|
95894
|
-
continue;
|
|
95895
|
-
}
|
|
95896
|
-
logger.warn(`[Router] Resetting stuck pearl ${pearl.id} (in_progress with no active ticket)`, { pearlId: pearl.id });
|
|
95897
|
-
await pearlsClient.update(pearl.id, {
|
|
95898
|
-
status: "open",
|
|
95899
|
-
labels: [
|
|
95900
|
-
...(pearl.labels || []).filter((l) => l !== "auto-recovered"),
|
|
95901
|
-
"auto-recovered"
|
|
95902
|
-
]
|
|
95903
|
-
});
|
|
95904
|
-
} else if (pearl.labels?.includes("agent-error") || pearl.labels?.includes("failed")) {
|
|
95905
|
-
logger.debug(`[Router] Skipping reset of failed pearl ${pearl.id} - active ticket exists.`, { pearlId: pearl.id });
|
|
95906
|
-
}
|
|
95907
|
-
}
|
|
95908
|
-
for (const pearl of readyPearls) {
|
|
95909
|
-
const active = queue.getActiveTicket(pearl.id);
|
|
95910
|
-
if (!active) {
|
|
95911
|
-
const fresh = await pearlsClient.get(pearl.id);
|
|
95912
|
-
if (fresh.status !== "open") {
|
|
95913
|
-
logger.info(`[Router] Skipping ${pearl.id} (status changed to ${fresh.status})`, { pearlId: pearl.id });
|
|
95914
|
-
continue;
|
|
95915
|
-
}
|
|
95916
|
-
if (fresh.type === "epic") {
|
|
95917
|
-
if (this.config.gatekeeper.auto_close_epics) {
|
|
95918
|
-
const blockers = fresh.blockers || [];
|
|
95919
|
-
const allPearls = await pearlsClient.getAll();
|
|
95920
|
-
const children = allPearls.filter((p) => p.parent === pearl.id);
|
|
95921
|
-
const childrenIds = children.map((c) => c.id);
|
|
95922
|
-
const combinedBlockers = [...new Set([...blockers, ...childrenIds])];
|
|
95923
|
-
if (combinedBlockers.length > 0) {
|
|
95924
|
-
const blockerPearls = await Promise.all(combinedBlockers.map((id) => pearlsClient.get(id).catch(() => null)));
|
|
95925
|
-
const validBlockers = blockerPearls.filter((b) => b !== null);
|
|
95926
|
-
const allDone = validBlockers.every((b) => b.status === "done");
|
|
95927
|
-
if (allDone && validBlockers.length > 0) {
|
|
95928
|
-
logger.info(`[Router] Auto-closing Epic ${pearl.id} (all blockers and children completed)`, { pearlId: pearl.id });
|
|
95929
|
-
await pearlsClient.update(pearl.id, {
|
|
95930
|
-
status: "done",
|
|
95931
|
-
acceptance_test: "Auto-closed: All subtasks and children completed."
|
|
95932
|
-
});
|
|
95933
|
-
continue;
|
|
95934
|
-
}
|
|
95935
|
-
}
|
|
95936
|
-
}
|
|
95937
|
-
logger.info(`[Router] Skipping container/epic pearl ${pearl.id}`, {
|
|
95938
|
-
pearlId: pearl.id
|
|
95939
|
-
});
|
|
95940
|
-
continue;
|
|
95941
|
-
}
|
|
95942
|
-
if (fresh.labels?.includes("molecule:cooking")) {
|
|
95943
|
-
logger.debug(`[Router] Skipping cooking pearl ${pearl.id}`, {
|
|
95944
|
-
pearlId: pearl.id
|
|
95945
|
-
});
|
|
95946
|
-
continue;
|
|
95947
|
-
}
|
|
95948
|
-
if (fresh.blockers && fresh.blockers.length > 0) {
|
|
95949
|
-
const blockers = await Promise.all(fresh.blockers.map((id) => pearlsClient.get(id)));
|
|
95950
|
-
const activeBlockers = blockers.filter((b) => b.status !== "done");
|
|
95951
|
-
if (activeBlockers.length > 0) {
|
|
95952
|
-
logger.warn(`[Router] Skipping ${pearl.id} - incorrectly marked ready (blocked by ${activeBlockers.map((b) => b.id).join(", ")})`, { pearlId: pearl.id });
|
|
95953
|
-
continue;
|
|
95954
|
-
}
|
|
95955
|
-
}
|
|
95956
|
-
if (fresh.labels?.includes("recovery")) {
|
|
95957
|
-
const blockers = fresh.blockers || [];
|
|
95958
|
-
if (blockers.length > 0) {
|
|
95959
|
-
const blockerPearls = await Promise.all(blockers.map((id) => pearlsClient.get(id)));
|
|
95960
|
-
const anyFailed = blockerPearls.some((b) => b.labels?.includes("failed"));
|
|
95961
|
-
const allDone = blockerPearls.every((b) => b.status === "done");
|
|
95962
|
-
if (allDone && !anyFailed) {
|
|
95963
|
-
logger.info(`[Router] Skipping recovery pearl ${pearl.id} (all dependencies succeeded)`, { pearlId: pearl.id });
|
|
95964
|
-
await pearlsClient.update(pearl.id, {
|
|
95965
|
-
status: "done",
|
|
95966
|
-
acceptance_test: "Skipped: All dependencies succeeded without failure."
|
|
95967
|
-
});
|
|
95968
|
-
continue;
|
|
95969
|
-
}
|
|
95970
|
-
}
|
|
95971
|
-
}
|
|
95972
|
-
if (fresh.labels?.includes("hitl") || fresh.labels?.includes("escalation")) {
|
|
95973
|
-
logger.info(`[Router] Skipping HITL/Escalation pearl ${pearl.id} (awaiting human action)`, {
|
|
95974
|
-
pearlId: pearl.id
|
|
95975
|
-
});
|
|
95976
|
-
continue;
|
|
95977
|
-
}
|
|
95978
|
-
const piped = await getPiper().pipeData(pearl.id);
|
|
95979
|
-
if (piped) {
|
|
95980
|
-
logger.info(`[Router] Piped data for ${pearl.id}`);
|
|
95981
|
-
}
|
|
95982
|
-
const currentPearl = await pearlsClient.get(pearl.id);
|
|
95983
|
-
if (currentPearl.context) {
|
|
95984
|
-
const ctxString = JSON.stringify(currentPearl.context);
|
|
95985
|
-
if (ctxString.includes("{{steps.")) {
|
|
95986
|
-
logger.info(`[Router] Skipping ${pearl.id} (waiting for dependency data)`, { pearlId: pearl.id });
|
|
95987
|
-
continue;
|
|
95988
|
-
}
|
|
95989
|
-
}
|
|
95990
|
-
const stillNoTicket = queue.getActiveTicket(pearl.id);
|
|
95991
|
-
if (stillNoTicket) {
|
|
95992
|
-
logger.debug(`[Router] Pearl ${pearl.id} already has ticket (race avoided)`, { pearlId: pearl.id });
|
|
95993
|
-
continue;
|
|
95994
|
-
}
|
|
95995
|
-
let targetRole = currentPearl.assignee || currentPearl.context?.role || currentPearl.metadata?.role || "worker";
|
|
95996
|
-
if (!this.config.agents[targetRole]) {
|
|
95997
|
-
logger.warn(`[Router] Role '${targetRole}' not found in config. Falling back to 'worker' for ${pearl.id}`);
|
|
95998
|
-
targetRole = "worker";
|
|
95999
|
-
}
|
|
96000
|
-
logger.info(`[Router] Routing pearl ${pearl.id} to ${targetRole}`, {
|
|
96001
|
-
pearlId: pearl.id,
|
|
96002
|
-
targetRole
|
|
96003
|
-
});
|
|
96004
|
-
this.queue.enqueue(pearl.id, currentPearl.priority, targetRole);
|
|
96005
|
-
}
|
|
96006
|
-
}
|
|
96007
|
-
const verifyPearls = await pearlsClient.list("verify");
|
|
96008
|
-
for (const pearl of verifyPearls) {
|
|
96009
|
-
const active = queue.getActiveTicket(pearl.id);
|
|
96010
|
-
if (!active) {
|
|
96011
|
-
const fresh = await pearlsClient.get(pearl.id);
|
|
96012
|
-
if (fresh.status !== "verify") {
|
|
96013
|
-
continue;
|
|
96014
|
-
}
|
|
96015
|
-
const stillNoTicket = queue.getActiveTicket(pearl.id);
|
|
96016
|
-
if (stillNoTicket) {
|
|
96017
|
-
logger.debug(`[Router] Pearl ${pearl.id} already has ticket (race avoided)`, { pearlId: pearl.id });
|
|
96018
|
-
continue;
|
|
96019
|
-
}
|
|
96020
|
-
logger.info(`[Router] Routing pearl ${pearl.id} to gatekeeper`, {
|
|
96021
|
-
pearlId: pearl.id
|
|
96022
|
-
});
|
|
96023
|
-
queue.enqueue(pearl.id, fresh.priority, "gatekeeper");
|
|
96024
|
-
} else {
|
|
96025
|
-
if (active.target_role === "worker" && active.status !== "completed") {
|
|
96026
|
-
logger.warn(`[Router] Found zombie worker ticket for verify pearl ${pearl.id}. Cleaning up.`, { pearlId: pearl.id, ticketId: active.id });
|
|
95785
|
+
if (this.config.hooks?.onPearlStart) {
|
|
96027
95786
|
try {
|
|
96028
|
-
this.
|
|
96029
|
-
} catch (
|
|
96030
|
-
logger.error(`[
|
|
95787
|
+
await this.config.hooks.onPearlStart(pearl);
|
|
95788
|
+
} catch (err) {
|
|
95789
|
+
logger.error(`[${role}] Error in onPearlStart hook:`, err);
|
|
96031
95790
|
}
|
|
96032
95791
|
}
|
|
96033
|
-
}
|
|
96034
|
-
}
|
|
96035
|
-
}
|
|
96036
|
-
async scalePools() {
|
|
96037
|
-
for (const [role, pool] of this.pools.entries()) {
|
|
96038
|
-
const pending = this.queue.getPendingCount(role);
|
|
96039
|
-
let target = Math.ceil(pending * this.config.worker.load_factor);
|
|
96040
|
-
target = Math.max(this.config.worker.min_workers, Math.min(target, this.config.worker.max_workers));
|
|
96041
|
-
await pool.resize(target);
|
|
96042
|
-
}
|
|
96043
|
-
const gatekeeperPending = this.queue.getPendingCount("gatekeeper");
|
|
96044
|
-
let targetGatekeepers = Math.ceil(gatekeeperPending * this.config.gatekeeper.load_factor);
|
|
96045
|
-
targetGatekeepers = Math.max(this.config.gatekeeper.min_workers, Math.min(targetGatekeepers, this.config.gatekeeper.max_workers));
|
|
96046
|
-
await this.gatekeeperPool.resize(targetGatekeepers);
|
|
96047
|
-
}
|
|
96048
|
-
}
|
|
96049
|
-
|
|
96050
|
-
// src/bridge/components/Dashboard.tsx
|
|
96051
|
-
var import_react27 = __toESM(require_react(), 1);
|
|
96052
|
-
|
|
96053
|
-
// src/bridge/components/MoleculeTree.tsx
|
|
96054
|
-
var import_react26 = __toESM(require_react(), 1);
|
|
96055
|
-
var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
|
|
96056
|
-
var buildTree = (pearls) => {
|
|
96057
|
-
const map4 = new Map;
|
|
96058
|
-
const roots = [];
|
|
96059
|
-
for (const pearl of pearls) {
|
|
96060
|
-
map4.set(pearl.id, { pearl, children: [] });
|
|
96061
|
-
}
|
|
96062
|
-
for (const pearl of pearls) {
|
|
96063
|
-
const node = map4.get(pearl.id);
|
|
96064
|
-
if (!node)
|
|
96065
|
-
continue;
|
|
96066
|
-
if (pearl.parent && map4.has(pearl.parent)) {
|
|
96067
|
-
const parent = map4.get(pearl.parent);
|
|
96068
|
-
if (parent) {
|
|
96069
|
-
parent.children.push(node);
|
|
96070
|
-
}
|
|
96071
|
-
} else {
|
|
96072
|
-
roots.push(node);
|
|
96073
|
-
}
|
|
96074
|
-
}
|
|
96075
|
-
return roots;
|
|
96076
|
-
};
|
|
96077
|
-
var PearlNode = ({ node, depth }) => {
|
|
96078
|
-
const indent = " ".repeat(depth);
|
|
96079
|
-
const color = node.pearl.status === "done" ? "green" : node.pearl.status === "verify" ? "magenta" : node.pearl.status === "in_progress" ? "yellow" : "white";
|
|
96080
|
-
const icon = node.pearl.status === "done" ? "✓" : node.pearl.status === "verify" ? "?" : node.pearl.status === "in_progress" ? "▶" : "○";
|
|
96081
|
-
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
96082
|
-
flexDirection: "column",
|
|
96083
|
-
children: [
|
|
96084
|
-
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96085
|
-
color,
|
|
96086
|
-
children: [
|
|
96087
|
-
indent,
|
|
96088
|
-
icon,
|
|
96089
|
-
" ",
|
|
96090
|
-
node.pearl.title,
|
|
96091
|
-
" ",
|
|
96092
|
-
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96093
|
-
color: "gray",
|
|
96094
|
-
children: [
|
|
96095
|
-
"(",
|
|
96096
|
-
node.pearl.id,
|
|
96097
|
-
")"
|
|
96098
|
-
]
|
|
96099
|
-
}, undefined, true, undefined, this)
|
|
96100
|
-
]
|
|
96101
|
-
}, undefined, true, undefined, this),
|
|
96102
|
-
node.children.map((child) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
|
|
96103
|
-
node: child,
|
|
96104
|
-
depth: depth + 1
|
|
96105
|
-
}, child.pearl.id, false, undefined, this))
|
|
96106
|
-
]
|
|
96107
|
-
}, undefined, true, undefined, this);
|
|
96108
|
-
};
|
|
96109
|
-
var MoleculeTree = () => {
|
|
96110
|
-
const [tree, setTree] = import_react26.useState([]);
|
|
96111
|
-
import_react26.useEffect(() => {
|
|
96112
|
-
const refresh = async () => {
|
|
96113
|
-
try {
|
|
96114
|
-
const pearls = await getPearls().getAll();
|
|
96115
|
-
setTree(buildTree(pearls));
|
|
96116
|
-
} catch (_e) {}
|
|
96117
|
-
};
|
|
96118
|
-
refresh();
|
|
96119
|
-
const timer = setInterval(refresh, 2000);
|
|
96120
|
-
return () => clearInterval(timer);
|
|
96121
|
-
}, []);
|
|
96122
|
-
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
96123
|
-
flexDirection: "column",
|
|
96124
|
-
borderStyle: "round",
|
|
96125
|
-
borderColor: "magenta",
|
|
96126
|
-
width: "50%",
|
|
96127
|
-
height: 15,
|
|
96128
|
-
overflowY: "hidden",
|
|
96129
|
-
children: [
|
|
96130
|
-
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96131
|
-
bold: true,
|
|
96132
|
-
children: "Molecules"
|
|
96133
|
-
}, undefined, false, undefined, this),
|
|
96134
|
-
tree.length === 0 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96135
|
-
color: "gray",
|
|
96136
|
-
children: "No molecules found"
|
|
96137
|
-
}, undefined, false, undefined, this) : null,
|
|
96138
|
-
tree.map((node) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
|
|
96139
|
-
node,
|
|
96140
|
-
depth: 0
|
|
96141
|
-
}, node.pearl.id, false, undefined, this))
|
|
96142
|
-
]
|
|
96143
|
-
}, undefined, true, undefined, this);
|
|
96144
|
-
};
|
|
96145
|
-
|
|
96146
|
-
// src/bridge/components/Dashboard.tsx
|
|
96147
|
-
var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
|
|
96148
|
-
var LogStream = () => {
|
|
96149
|
-
const config2 = getConfig();
|
|
96150
|
-
const maxLogs = config2.bridge?.maxLogs || 1000;
|
|
96151
|
-
const [logs, setLogs] = import_react27.useState([]);
|
|
96152
|
-
const [scrollTop, setScrollTop] = import_react27.useState(0);
|
|
96153
|
-
const [viewHeight, _setViewHeight] = import_react27.useState(15);
|
|
96154
|
-
import_react27.useEffect(() => {
|
|
96155
|
-
const handler = (entry) => {
|
|
96156
|
-
setLogs((prev) => {
|
|
96157
|
-
const updated = [...prev, entry];
|
|
96158
|
-
if (updated.length > maxLogs) {
|
|
96159
|
-
return updated.slice(updated.length - maxLogs);
|
|
96160
|
-
}
|
|
96161
|
-
return updated;
|
|
96162
|
-
});
|
|
96163
|
-
};
|
|
96164
|
-
logger.on("log", handler);
|
|
96165
|
-
return () => {
|
|
96166
|
-
logger.off("log", handler);
|
|
96167
|
-
};
|
|
96168
|
-
}, [maxLogs]);
|
|
96169
|
-
use_input_default((_input, key) => {
|
|
96170
|
-
if (key.upArrow) {
|
|
96171
|
-
setScrollTop((prev) => Math.min(prev + 1, Math.max(0, logs.length - viewHeight)));
|
|
96172
|
-
}
|
|
96173
|
-
if (key.downArrow) {
|
|
96174
|
-
setScrollTop((prev) => Math.max(0, prev - 1));
|
|
96175
|
-
}
|
|
96176
|
-
if (key.pageUp) {
|
|
96177
|
-
setScrollTop((prev) => Math.min(prev + 10, Math.max(0, logs.length - viewHeight)));
|
|
96178
|
-
}
|
|
96179
|
-
if (key.pageDown) {
|
|
96180
|
-
setScrollTop((prev) => Math.max(0, prev - 10));
|
|
96181
|
-
}
|
|
96182
|
-
});
|
|
96183
|
-
const total = logs.length;
|
|
96184
|
-
const effectiveHeight = Math.min(total, viewHeight);
|
|
96185
|
-
let startIndex = total - effectiveHeight - scrollTop;
|
|
96186
|
-
if (startIndex < 0)
|
|
96187
|
-
startIndex = 0;
|
|
96188
|
-
const endIndex = startIndex + effectiveHeight;
|
|
96189
|
-
const viewLogs = logs.slice(startIndex, endIndex);
|
|
96190
|
-
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96191
|
-
flexDirection: "column",
|
|
96192
|
-
borderStyle: "round",
|
|
96193
|
-
borderColor: scrollTop > 0 ? "yellow" : "green",
|
|
96194
|
-
width: "100%",
|
|
96195
|
-
height: viewHeight + 2,
|
|
96196
|
-
children: [
|
|
96197
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96198
|
-
flexDirection: "row",
|
|
96199
|
-
justifyContent: "space-between",
|
|
96200
|
-
children: [
|
|
96201
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96202
|
-
bold: true,
|
|
96203
|
-
children: [
|
|
96204
|
-
"Live Logs ",
|
|
96205
|
-
scrollTop > 0 ? `(Scrolled: -${scrollTop})` : "(Live)"
|
|
96206
|
-
]
|
|
96207
|
-
}, undefined, true, undefined, this),
|
|
96208
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96209
|
-
color: "gray",
|
|
96210
|
-
children: [
|
|
96211
|
-
logs.length,
|
|
96212
|
-
"/",
|
|
96213
|
-
maxLogs
|
|
96214
|
-
]
|
|
96215
|
-
}, undefined, true, undefined, this)
|
|
96216
|
-
]
|
|
96217
|
-
}, undefined, true, undefined, this),
|
|
96218
|
-
viewLogs.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96219
|
-
wrap: "truncate",
|
|
96220
|
-
children: [
|
|
96221
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96222
|
-
color: "gray",
|
|
96223
|
-
children: [
|
|
96224
|
-
"[",
|
|
96225
|
-
l.timestamp.split("T")[1]?.split(".")[0] || "00:00:00",
|
|
96226
|
-
"]"
|
|
96227
|
-
]
|
|
96228
|
-
}, undefined, true, undefined, this),
|
|
96229
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96230
|
-
color: l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "white",
|
|
96231
|
-
children: [
|
|
96232
|
-
" ",
|
|
96233
|
-
"[",
|
|
96234
|
-
l.level.toUpperCase(),
|
|
96235
|
-
"]",
|
|
96236
|
-
" "
|
|
96237
|
-
]
|
|
96238
|
-
}, undefined, true, undefined, this),
|
|
96239
|
-
l.message
|
|
96240
|
-
]
|
|
96241
|
-
}, `${l.timestamp}-${startIndex + i2}`, true, undefined, this))
|
|
96242
|
-
]
|
|
96243
|
-
}, undefined, true, undefined, this);
|
|
96244
|
-
};
|
|
96245
|
-
var AgentMatrix = () => {
|
|
96246
|
-
const [activeTickets, setActiveTickets] = import_react27.useState([]);
|
|
96247
|
-
import_react27.useEffect(() => {
|
|
96248
|
-
const timer = setInterval(() => {
|
|
96249
|
-
const queue = getQueue();
|
|
96250
|
-
const rows = queue.getTicketsByStatus("processing");
|
|
96251
|
-
if (rows)
|
|
96252
|
-
setActiveTickets(rows);
|
|
96253
|
-
}, 1000);
|
|
96254
|
-
return () => clearInterval(timer);
|
|
96255
|
-
}, []);
|
|
96256
|
-
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96257
|
-
flexDirection: "column",
|
|
96258
|
-
borderStyle: "round",
|
|
96259
|
-
borderColor: "blue",
|
|
96260
|
-
width: "50%",
|
|
96261
|
-
children: [
|
|
96262
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96263
|
-
bold: true,
|
|
96264
|
-
children: "Active Agents"
|
|
96265
|
-
}, undefined, false, undefined, this),
|
|
96266
|
-
activeTickets.length === 0 ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96267
|
-
color: "gray",
|
|
96268
|
-
children: "No active agents"
|
|
96269
|
-
}, undefined, false, undefined, this) : null,
|
|
96270
|
-
activeTickets.map((t) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96271
|
-
flexDirection: "row",
|
|
96272
|
-
children: [
|
|
96273
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96274
|
-
color: "cyan",
|
|
96275
|
-
children: t.target_role
|
|
96276
|
-
}, undefined, false, undefined, this),
|
|
96277
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96278
|
-
children: " -> "
|
|
96279
|
-
}, undefined, false, undefined, this),
|
|
96280
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96281
|
-
children: t.pearl_id
|
|
96282
|
-
}, undefined, false, undefined, this),
|
|
96283
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96284
|
-
color: "yellow",
|
|
96285
|
-
children: [
|
|
96286
|
-
" (",
|
|
96287
|
-
t.status,
|
|
96288
|
-
")"
|
|
96289
|
-
]
|
|
96290
|
-
}, undefined, true, undefined, this)
|
|
96291
|
-
]
|
|
96292
|
-
}, t.id, true, undefined, this))
|
|
96293
|
-
]
|
|
96294
|
-
}, undefined, true, undefined, this);
|
|
96295
|
-
};
|
|
96296
|
-
var Dashboard = () => {
|
|
96297
|
-
const [status] = import_react27.useState("Online");
|
|
96298
|
-
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96299
|
-
flexDirection: "column",
|
|
96300
|
-
padding: 1,
|
|
96301
|
-
children: [
|
|
96302
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96303
|
-
children: [
|
|
96304
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96305
|
-
children: "The Citadel Bridge "
|
|
96306
|
-
}, undefined, false, undefined, this),
|
|
96307
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96308
|
-
color: "green",
|
|
96309
|
-
children: [
|
|
96310
|
-
"[",
|
|
96311
|
-
status,
|
|
96312
|
-
"]"
|
|
96313
|
-
]
|
|
96314
|
-
}, undefined, true, undefined, this)
|
|
96315
|
-
]
|
|
96316
|
-
}, undefined, true, undefined, this),
|
|
96317
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96318
|
-
flexDirection: "row",
|
|
96319
|
-
children: [
|
|
96320
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(AgentMatrix, {}, undefined, false, undefined, this),
|
|
96321
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MoleculeTree, {}, undefined, false, undefined, this)
|
|
96322
|
-
]
|
|
96323
|
-
}, undefined, true, undefined, this),
|
|
96324
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(LogStream, {}, undefined, false, undefined, this),
|
|
96325
|
-
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96326
|
-
color: "gray",
|
|
96327
|
-
children: "Press Ctrl+C to exit"
|
|
96328
|
-
}, undefined, false, undefined, this)
|
|
96329
|
-
]
|
|
96330
|
-
}, undefined, true, undefined, this);
|
|
96331
|
-
};
|
|
96332
|
-
|
|
96333
|
-
// src/bridge/index.tsx
|
|
96334
|
-
var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
|
|
96335
|
-
async function startBridge() {
|
|
96336
|
-
await loadConfig();
|
|
96337
|
-
logger.setConsoleEnabled(false);
|
|
96338
|
-
const conductor = new Conductor;
|
|
96339
|
-
conductor.start();
|
|
96340
|
-
const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Dashboard, {}, undefined, false, undefined, this));
|
|
96341
|
-
try {
|
|
96342
|
-
await waitUntilExit();
|
|
96343
|
-
} catch (error48) {
|
|
96344
|
-
console.error("Bridge crashed:", error48);
|
|
96345
|
-
} finally {
|
|
96346
|
-
conductor.stop();
|
|
96347
|
-
console.log("");
|
|
96348
|
-
}
|
|
96349
|
-
}
|
|
96350
|
-
|
|
96351
|
-
// src/config/index.ts
|
|
96352
|
-
var import_dotenv2 = __toESM(require_main(), 1);
|
|
96353
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
96354
|
-
import { resolve as resolve9 } from "node:path";
|
|
96355
|
-
import_dotenv2.default.config({ quiet: true });
|
|
96356
|
-
var CONFIG_KEY2 = "config_cache";
|
|
96357
|
-
async function loadConfig2() {
|
|
96358
|
-
const existing = getGlobalSingleton(CONFIG_KEY2, () => null);
|
|
96359
|
-
if (existing)
|
|
96360
|
-
return existing;
|
|
96361
|
-
const configPath = resolve9(process.cwd(), "citadel.config.ts");
|
|
96362
|
-
let userConfig = {};
|
|
96363
|
-
if (existsSync8(configPath)) {
|
|
96364
|
-
try {
|
|
96365
|
-
const mod = await import(configPath);
|
|
96366
|
-
userConfig = mod.default || {};
|
|
96367
|
-
} catch (error48) {
|
|
96368
|
-
console.warn("Failed to load citadel.config.ts:", error48);
|
|
96369
|
-
}
|
|
96370
|
-
}
|
|
96371
|
-
const rawConfig = {
|
|
96372
|
-
...userConfig,
|
|
96373
|
-
env: process.env.CITADEL_ENV || userConfig.env,
|
|
96374
|
-
providers: {
|
|
96375
|
-
ollama: {
|
|
96376
|
-
baseURL: process.env.CITADEL_OLLAMA_BASE_URL || userConfig.providers?.ollama?.baseURL,
|
|
96377
|
-
apiKey: process.env.CITADEL_OLLAMA_API_KEY || userConfig.providers?.ollama?.apiKey,
|
|
96378
|
-
...userConfig.providers?.ollama
|
|
96379
|
-
},
|
|
96380
|
-
openai: {
|
|
96381
|
-
apiKey: process.env.OPENAI_API_KEY || userConfig.providers?.openai?.apiKey,
|
|
96382
|
-
...userConfig.providers?.openai
|
|
96383
|
-
},
|
|
96384
|
-
anthropic: {
|
|
96385
|
-
apiKey: process.env.ANTHROPIC_API_KEY || userConfig.providers?.anthropic?.apiKey,
|
|
96386
|
-
...userConfig.providers?.anthropic
|
|
96387
|
-
},
|
|
96388
|
-
...userConfig.providers
|
|
96389
|
-
}
|
|
96390
|
-
};
|
|
96391
|
-
const parsed = ConfigSchema.safeParse(rawConfig);
|
|
96392
|
-
if (!parsed.success) {
|
|
96393
|
-
console.error("Configuration Error:", parsed.error.format());
|
|
96394
|
-
throw new Error("Invalid Citadel Configuration");
|
|
96395
|
-
}
|
|
96396
|
-
const config2 = parsed.data;
|
|
96397
|
-
setGlobalSingleton(CONFIG_KEY2, config2);
|
|
96398
|
-
logger.debug("[Config] Loaded from file/env");
|
|
96399
|
-
return config2;
|
|
96400
|
-
}
|
|
96401
|
-
|
|
96402
|
-
// src/core/pearls.ts
|
|
96403
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
96404
|
-
import { resolve as resolve10 } from "node:path";
|
|
96405
|
-
var PearlStatusSchema2 = exports_external.enum([
|
|
96406
|
-
"open",
|
|
96407
|
-
"in_progress",
|
|
96408
|
-
"verify",
|
|
96409
|
-
"done"
|
|
96410
|
-
]);
|
|
96411
|
-
var PearlPrioritySchema2 = exports_external.any().transform((val) => {
|
|
96412
|
-
if (typeof val === "number")
|
|
96413
|
-
return val;
|
|
96414
|
-
if (typeof val === "string") {
|
|
96415
|
-
const s = val.replace(/^P/, "");
|
|
96416
|
-
const n = parseInt(s, 10);
|
|
96417
|
-
if (!Number.isNaN(n))
|
|
96418
|
-
return n;
|
|
96419
|
-
}
|
|
96420
|
-
return 2;
|
|
96421
|
-
});
|
|
96422
|
-
var RawPearlSchema2 = exports_external.object({
|
|
96423
|
-
id: exports_external.string(),
|
|
96424
|
-
title: exports_external.string(),
|
|
96425
|
-
status: exports_external.string(),
|
|
96426
|
-
priority: PearlPrioritySchema2,
|
|
96427
|
-
author: exports_external.string().optional(),
|
|
96428
|
-
labels: exports_external.array(exports_external.string()).optional(),
|
|
96429
|
-
links: exports_external.array(exports_external.object({
|
|
96430
|
-
target_id: exports_external.string(),
|
|
96431
|
-
link_type: exports_external.string()
|
|
96432
|
-
})).optional(),
|
|
96433
|
-
deps: exports_external.array(exports_external.object({
|
|
96434
|
-
target_id: exports_external.string(),
|
|
96435
|
-
dep_type: exports_external.string()
|
|
96436
|
-
}).or(exports_external.string())).optional(),
|
|
96437
|
-
metadata: exports_external.record(exports_external.string(), exports_external.any()).optional(),
|
|
96438
|
-
description: exports_external.string().optional(),
|
|
96439
|
-
created_at: exports_external.any(),
|
|
96440
|
-
updated_at: exports_external.any()
|
|
96441
|
-
});
|
|
96442
|
-
var PearlSchema2 = exports_external.object({
|
|
96443
|
-
id: exports_external.string(),
|
|
96444
|
-
title: exports_external.string(),
|
|
96445
|
-
status: PearlStatusSchema2,
|
|
96446
|
-
priority: PearlPrioritySchema2,
|
|
96447
|
-
assignee: exports_external.string().optional(),
|
|
96448
|
-
labels: exports_external.array(exports_external.string()).optional(),
|
|
96449
|
-
blockers: exports_external.array(exports_external.string()).optional(),
|
|
96450
|
-
acceptance_test: exports_external.string().optional(),
|
|
96451
|
-
parent: exports_external.string().optional(),
|
|
96452
|
-
type: exports_external.string().optional(),
|
|
96453
|
-
description: exports_external.string().optional(),
|
|
96454
|
-
context: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
96455
|
-
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
96456
|
-
output: exports_external.unknown().optional(),
|
|
96457
|
-
created_at: exports_external.string(),
|
|
96458
|
-
updated_at: exports_external.string()
|
|
96459
|
-
});
|
|
96460
|
-
|
|
96461
|
-
class PearlsClient2 {
|
|
96462
|
-
basePath;
|
|
96463
|
-
binary;
|
|
96464
|
-
constructor(basePath, binary2) {
|
|
96465
|
-
let config2 = null;
|
|
96466
|
-
try {
|
|
96467
|
-
config2 = getConfig();
|
|
96468
|
-
} catch {}
|
|
96469
|
-
this.basePath = basePath || config2?.pearls?.path || ".pearls";
|
|
96470
|
-
this.binary = binary2 || config2?.pearls?.binary || "prl";
|
|
96471
|
-
}
|
|
96472
|
-
async runCommand(args) {
|
|
96473
|
-
const cwd2 = this.basePath.endsWith(".pearls") ? resolve10(this.basePath, "..") : this.basePath;
|
|
96474
|
-
try {
|
|
96475
|
-
const { stdout, stderr } = await this.execute(this.binary, args, cwd2);
|
|
96476
|
-
if (stderr && !stdout && !stderr.includes("warning")) {}
|
|
96477
|
-
return stdout.trim();
|
|
96478
|
-
} catch (error48) {
|
|
96479
|
-
const err = error48;
|
|
96480
|
-
throw new Error(`Pearls command failed: ${this.binary} ${args.join(" ")}
|
|
96481
|
-
${err.message}`);
|
|
96482
|
-
}
|
|
96483
|
-
}
|
|
96484
|
-
async init() {
|
|
96485
|
-
await this.runCommand(["init"]);
|
|
96486
|
-
}
|
|
96487
|
-
async execute(command, args, cwd2) {
|
|
96488
|
-
return new Promise((resolve11, reject) => {
|
|
96489
|
-
const child = spawn4(command, args, { cwd: cwd2 });
|
|
96490
|
-
let stdout = "";
|
|
96491
|
-
let stderr = "";
|
|
96492
|
-
child.stdout.on("data", (data) => {
|
|
96493
|
-
stdout += data.toString();
|
|
96494
|
-
});
|
|
96495
|
-
child.stderr.on("data", (data) => {
|
|
96496
|
-
stderr += data.toString();
|
|
96497
|
-
});
|
|
96498
|
-
child.on("close", (code) => {
|
|
96499
|
-
if (code === 0) {
|
|
96500
|
-
resolve11({ stdout, stderr });
|
|
96501
|
-
} else {
|
|
96502
|
-
reject(new Error(`Process exited with code ${code}
|
|
96503
|
-
${stderr}`));
|
|
96504
|
-
}
|
|
96505
|
-
});
|
|
96506
|
-
child.on("error", (err) => {
|
|
96507
|
-
reject(err);
|
|
96508
|
-
});
|
|
96509
|
-
});
|
|
96510
|
-
}
|
|
96511
|
-
async sync() {
|
|
96512
|
-
await this.runCommand(["sync"]);
|
|
96513
|
-
logger.info(`[Pearls] Database synchronized`);
|
|
96514
|
-
}
|
|
96515
|
-
async doctor() {
|
|
96516
|
-
try {
|
|
96517
|
-
const output = await this.runCommand(["doctor", "--format", "json"]);
|
|
96518
|
-
return !output.toLowerCase().includes("error");
|
|
96519
|
-
} catch (_error) {
|
|
96520
|
-
return false;
|
|
96521
|
-
}
|
|
96522
|
-
}
|
|
96523
|
-
parseRaw(output) {
|
|
96524
|
-
if (!output)
|
|
96525
|
-
throw new Error("Empty output from prl");
|
|
96526
|
-
let json4;
|
|
96527
|
-
try {
|
|
96528
|
-
json4 = JSON.parse(output);
|
|
96529
|
-
} catch (_e) {
|
|
96530
|
-
const start = output.indexOf("{");
|
|
96531
|
-
if (start !== -1) {
|
|
96532
|
-
let braceCount = 0;
|
|
96533
|
-
let end = -1;
|
|
96534
|
-
for (let i2 = start;i2 < output.length; i2++) {
|
|
96535
|
-
if (output[i2] === "{")
|
|
96536
|
-
braceCount++;
|
|
96537
|
-
else if (output[i2] === "}") {
|
|
96538
|
-
braceCount--;
|
|
96539
|
-
if (braceCount === 0) {
|
|
96540
|
-
end = i2;
|
|
96541
|
-
break;
|
|
96542
|
-
}
|
|
96543
|
-
}
|
|
96544
|
-
}
|
|
96545
|
-
if (end !== -1) {
|
|
96546
|
-
try {
|
|
96547
|
-
json4 = JSON.parse(output.substring(start, end + 1));
|
|
96548
|
-
} catch {
|
|
96549
|
-
throw new Error(`Failed to parse Pearls JSON (with extraction): ${output}`);
|
|
96550
|
-
}
|
|
96551
|
-
} else {
|
|
96552
|
-
throw new Error(`Failed to parse Pearls JSON (no end brace): ${output}`);
|
|
96553
|
-
}
|
|
96554
|
-
} else {
|
|
96555
|
-
throw new Error(`Failed to parse Pearls JSON: ${output}`);
|
|
96556
|
-
}
|
|
96557
|
-
}
|
|
96558
|
-
const pearl = json4.pearl || json4.issue || (Array.isArray(json4) ? json4[0] : json4);
|
|
96559
|
-
if (!pearl || typeof pearl !== "object") {
|
|
96560
|
-
if (json4.pearls && Array.isArray(json4.pearls) && json4.pearls.length > 0) {
|
|
96561
|
-
return this.mapToDomain(RawPearlSchema2.parse(json4.pearls[0]));
|
|
96562
|
-
}
|
|
96563
|
-
throw new Error(`Unexpected Pearls output format: ${output}`);
|
|
96564
|
-
}
|
|
96565
|
-
const raw = RawPearlSchema2.parse(pearl);
|
|
96566
|
-
return this.mapToDomain(raw);
|
|
96567
|
-
}
|
|
96568
|
-
parseRawList(output) {
|
|
96569
|
-
if (!output)
|
|
96570
|
-
return [];
|
|
96571
|
-
let json4;
|
|
96572
|
-
try {
|
|
96573
|
-
json4 = JSON.parse(output);
|
|
96574
|
-
} catch (_e) {
|
|
96575
|
-
const start = output.indexOf("[") !== -1 ? output.indexOf("[") : output.indexOf("{");
|
|
96576
|
-
if (start !== -1) {
|
|
96577
|
-
const opener = output[start];
|
|
96578
|
-
const closer = opener === "[" ? "]" : "}";
|
|
96579
|
-
let count = 0;
|
|
96580
|
-
let end = -1;
|
|
96581
|
-
for (let i2 = start;i2 < output.length; i2++) {
|
|
96582
|
-
if (output[i2] === opener)
|
|
96583
|
-
count++;
|
|
96584
|
-
else if (output[i2] === closer) {
|
|
96585
|
-
count--;
|
|
96586
|
-
if (count === 0) {
|
|
96587
|
-
end = i2;
|
|
96588
|
-
break;
|
|
96589
|
-
}
|
|
96590
|
-
}
|
|
96591
|
-
}
|
|
96592
|
-
if (end !== -1) {
|
|
96593
|
-
try {
|
|
96594
|
-
json4 = JSON.parse(output.substring(start, end + 1));
|
|
96595
|
-
} catch {
|
|
96596
|
-
return [];
|
|
96597
|
-
}
|
|
96598
|
-
} else {
|
|
96599
|
-
return [];
|
|
96600
|
-
}
|
|
96601
|
-
} else {
|
|
96602
|
-
return [];
|
|
96603
|
-
}
|
|
96604
|
-
}
|
|
96605
|
-
const list = json4.pearls || json4.ready || json4.issues || (Array.isArray(json4) ? json4 : [json4]);
|
|
96606
|
-
if (Array.isArray(list)) {
|
|
96607
|
-
return list.map((item) => {
|
|
96608
|
-
try {
|
|
96609
|
-
return this.mapToDomain(RawPearlSchema2.parse(item));
|
|
96610
|
-
} catch (e) {
|
|
96611
|
-
console.warn(`[Pearls] Failed to parse pearl item:`, e, item);
|
|
96612
|
-
return null;
|
|
96613
|
-
}
|
|
96614
|
-
}).filter((b) => !!b);
|
|
96615
|
-
}
|
|
96616
|
-
return [];
|
|
96617
|
-
}
|
|
96618
|
-
mapToDomain(raw) {
|
|
96619
|
-
let status = "open";
|
|
96620
|
-
const rawStatus = raw.status.toLowerCase();
|
|
96621
|
-
if (rawStatus === "closed") {
|
|
96622
|
-
status = "done";
|
|
96623
|
-
} else if (rawStatus === "inprogress" || rawStatus === "in_progress") {
|
|
96624
|
-
if (raw.labels?.includes("verify")) {
|
|
96625
|
-
status = "verify";
|
|
96626
|
-
} else {
|
|
96627
|
-
status = "in_progress";
|
|
96628
|
-
}
|
|
96629
|
-
} else {
|
|
96630
|
-
status = "open";
|
|
96631
|
-
}
|
|
96632
|
-
const blockers = [];
|
|
96633
|
-
let parent;
|
|
96634
|
-
if (raw.links) {
|
|
96635
|
-
for (const link2 of raw.links) {
|
|
96636
|
-
if (link2.link_type === "blocks") {
|
|
96637
|
-
blockers.push(link2.target_id);
|
|
96638
|
-
} else if (link2.link_type === "parent_child") {
|
|
96639
|
-
parent = link2.target_id;
|
|
96640
|
-
}
|
|
96641
|
-
}
|
|
96642
|
-
}
|
|
96643
|
-
if (raw.deps && Array.isArray(raw.deps)) {
|
|
96644
|
-
for (const dep of raw.deps) {
|
|
96645
|
-
if (typeof dep === "string") {
|
|
96646
|
-
blockers.push(dep);
|
|
96647
|
-
} else {
|
|
96648
|
-
if (dep.dep_type === "blocks") {
|
|
96649
|
-
blockers.push(dep.target_id);
|
|
96650
|
-
} else if (dep.dep_type === "parent_child") {
|
|
96651
|
-
parent = dep.target_id;
|
|
96652
|
-
}
|
|
96653
|
-
}
|
|
96654
|
-
}
|
|
96655
|
-
}
|
|
96656
|
-
const metadata = raw.metadata || {};
|
|
96657
|
-
const acceptance_test = metadata.acceptance_test;
|
|
96658
|
-
const type2 = metadata.type || metadata.issue_type;
|
|
96659
|
-
let context2 = metadata.context;
|
|
96660
|
-
const output = metadata.output;
|
|
96661
|
-
const assignee = metadata.assignee || raw.author;
|
|
96662
|
-
let description = raw.description || undefined;
|
|
96663
|
-
if (description && !context2) {
|
|
96664
|
-
const match2 = description.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
96665
|
-
if (match2?.[1] && match2[2]) {
|
|
96666
|
-
try {
|
|
96667
|
-
const parsed = JSON.parse(match2[1]);
|
|
96668
|
-
if (parsed && typeof parsed === "object") {
|
|
96669
|
-
context2 = parsed;
|
|
96670
|
-
description = match2[2];
|
|
96671
|
-
}
|
|
96672
|
-
} catch {}
|
|
96673
|
-
}
|
|
96674
|
-
}
|
|
96675
|
-
return {
|
|
96676
|
-
id: raw.id,
|
|
96677
|
-
title: raw.title,
|
|
96678
|
-
status,
|
|
96679
|
-
priority: raw.priority,
|
|
96680
|
-
assignee,
|
|
96681
|
-
labels: raw.labels,
|
|
96682
|
-
blockers,
|
|
96683
|
-
acceptance_test,
|
|
96684
|
-
parent,
|
|
96685
|
-
type: type2,
|
|
96686
|
-
description,
|
|
96687
|
-
context: context2,
|
|
96688
|
-
metadata,
|
|
96689
|
-
output,
|
|
96690
|
-
created_at: typeof raw.created_at === "number" ? new Date(raw.created_at * 1000).toISOString() : raw.created_at,
|
|
96691
|
-
updated_at: typeof raw.updated_at === "number" ? new Date(raw.updated_at * 1000).toISOString() : raw.updated_at
|
|
96692
|
-
};
|
|
96693
|
-
}
|
|
96694
|
-
async list(status) {
|
|
96695
|
-
const args = ["list"];
|
|
96696
|
-
if (status === "done")
|
|
96697
|
-
args.push("--status", "closed");
|
|
96698
|
-
else if (status === "verify")
|
|
96699
|
-
args.push("--status", "in_progress");
|
|
96700
|
-
else if (status === "in_progress")
|
|
96701
|
-
args.push("--status", "in_progress");
|
|
96702
|
-
else if (status === "open")
|
|
96703
|
-
args.push("--status", "open");
|
|
96704
|
-
args.push("--format", "json");
|
|
96705
|
-
const output = await this.runCommand(args);
|
|
96706
|
-
const pearls = this.parseRawList(output);
|
|
96707
|
-
if (status) {
|
|
96708
|
-
return pearls.filter((b) => b.status === status);
|
|
96709
|
-
}
|
|
96710
|
-
return pearls;
|
|
96711
|
-
}
|
|
96712
|
-
async ready() {
|
|
96713
|
-
const output = await this.runCommand(["ready", "--format", "json"]);
|
|
96714
|
-
return this.parseRawList(output);
|
|
96715
|
-
}
|
|
96716
|
-
async getAll() {
|
|
96717
|
-
const output = await this.runCommand(["list", "--format", "json"]);
|
|
96718
|
-
return this.parseRawList(output);
|
|
96719
|
-
}
|
|
96720
|
-
async get(id) {
|
|
96721
|
-
const output = await this.runCommand(["show", id, "--format", "json"]);
|
|
96722
|
-
return this.parseRaw(output);
|
|
96723
|
-
}
|
|
96724
|
-
async create(title, options = {}) {
|
|
96725
|
-
const args = ["create", title];
|
|
96726
|
-
if (options.priority !== undefined)
|
|
96727
|
-
args.push("--priority", options.priority.toString());
|
|
96728
|
-
if (options.description) {
|
|
96729
|
-
args.push("--description", options.description);
|
|
96730
|
-
}
|
|
96731
|
-
if (options.assignee) {
|
|
96732
|
-
args.push("--author", options.assignee);
|
|
96733
|
-
}
|
|
96734
|
-
args.push("--format", "json");
|
|
96735
|
-
const output = await this.runCommand(args);
|
|
96736
|
-
const pearl = this.parseRaw(output);
|
|
96737
|
-
const updates = {};
|
|
96738
|
-
if (options.acceptance_test)
|
|
96739
|
-
updates.acceptance_test = options.acceptance_test;
|
|
96740
|
-
if (options.type)
|
|
96741
|
-
updates.type = options.type;
|
|
96742
|
-
if (options.context)
|
|
96743
|
-
updates.context = options.context;
|
|
96744
|
-
if (options.assignee)
|
|
96745
|
-
updates.assignee = options.assignee;
|
|
96746
|
-
for (const [key, val] of Object.entries(updates)) {
|
|
96747
|
-
const valStr = JSON.stringify(val);
|
|
96748
|
-
await this.runCommand(["meta", "set", pearl.id, key, valStr, "--format", "json"]);
|
|
96749
|
-
}
|
|
96750
|
-
if (options.labels?.length) {
|
|
96751
|
-
for (const label of options.labels) {
|
|
96752
|
-
await this.runCommand(["update", pearl.id, "--add-label", label, "--format", "json"]);
|
|
96753
|
-
}
|
|
96754
|
-
}
|
|
96755
|
-
if (options.blockers?.length) {
|
|
96756
|
-
for (const blockerId of options.blockers) {
|
|
96757
|
-
await this.runCommand(["link", pearl.id, blockerId, "blocks", "--format", "json"]);
|
|
96758
|
-
}
|
|
96759
|
-
}
|
|
96760
|
-
if (options.parent) {
|
|
96761
|
-
await this.runCommand(["link", pearl.id, options.parent, "parent_child", "--format", "json"]);
|
|
96762
|
-
}
|
|
96763
|
-
return this.get(pearl.id);
|
|
96764
|
-
}
|
|
96765
|
-
async update(id, changes) {
|
|
96766
|
-
let current;
|
|
96767
|
-
if (changes.status) {
|
|
96768
|
-
current = await this.get(id);
|
|
96769
|
-
this.validateTransition(current, changes.status);
|
|
96770
|
-
const isFailed = changes.labels?.includes("failed") || !changes.labels && current.labels?.includes("failed");
|
|
96771
|
-
if (changes.status === "done" && !isFailed && !current.acceptance_test && !changes.acceptance_test) {
|
|
96772
|
-
throw new Error(`Cannot transition ${id} to 'done': missing acceptance_test`);
|
|
96773
|
-
}
|
|
96774
|
-
}
|
|
96775
|
-
const args = ["update", id];
|
|
96776
|
-
if (changes.assignee) {
|
|
96777
|
-
await this.runCommand(["meta", "set", id, "assignee", JSON.stringify(changes.assignee), "--format", "json"]);
|
|
96778
|
-
}
|
|
96779
|
-
if (changes.status) {
|
|
96780
|
-
if (!current)
|
|
96781
|
-
current = await this.get(id);
|
|
96782
|
-
let targetCliStatus = "";
|
|
96783
|
-
if (changes.status === "done")
|
|
96784
|
-
targetCliStatus = "closed";
|
|
96785
|
-
else if (changes.status === "verify")
|
|
96786
|
-
targetCliStatus = "in_progress";
|
|
96787
|
-
else if (changes.status === "in_progress")
|
|
96788
|
-
targetCliStatus = "in_progress";
|
|
96789
|
-
else if (changes.status === "open")
|
|
96790
|
-
targetCliStatus = "open";
|
|
96791
|
-
let currentCliStatus = "";
|
|
96792
|
-
if (current.status === "done")
|
|
96793
|
-
currentCliStatus = "closed";
|
|
96794
|
-
else if (current.status === "verify")
|
|
96795
|
-
currentCliStatus = "in_progress";
|
|
96796
|
-
else if (current.status === "in_progress")
|
|
96797
|
-
currentCliStatus = "in_progress";
|
|
96798
|
-
else if (current.status === "open")
|
|
96799
|
-
currentCliStatus = "open";
|
|
96800
|
-
if (targetCliStatus && targetCliStatus !== currentCliStatus) {
|
|
96801
|
-
args.push("--status", targetCliStatus);
|
|
96802
|
-
}
|
|
96803
|
-
if (changes.status === "verify") {
|
|
96804
|
-
args.push("--add-label", "verify");
|
|
96805
|
-
} else if (changes.status === "in_progress") {
|
|
96806
|
-
args.push("--remove-label", "verify");
|
|
96807
|
-
} else if (changes.status === "open") {
|
|
96808
|
-
args.push("--remove-label", "verify");
|
|
96809
|
-
}
|
|
96810
|
-
}
|
|
96811
|
-
if (changes.title) {
|
|
96812
|
-
args.push("--title", changes.title);
|
|
96813
|
-
}
|
|
96814
|
-
if (changes.description) {
|
|
96815
|
-
args.push("--description", changes.description);
|
|
96816
|
-
}
|
|
96817
|
-
if (changes.labels) {
|
|
96818
|
-
for (const label of changes.labels) {
|
|
96819
|
-
args.push("--add-label", label);
|
|
96820
|
-
}
|
|
96821
|
-
}
|
|
96822
|
-
if (changes.remove_labels) {
|
|
96823
|
-
for (const label of changes.remove_labels) {
|
|
96824
|
-
args.push("--remove-label", label);
|
|
96825
|
-
}
|
|
96826
|
-
}
|
|
96827
|
-
args.push("--format", "json");
|
|
96828
|
-
const output = await this.runCommand(args);
|
|
96829
|
-
if (changes.acceptance_test) {
|
|
96830
|
-
const valStr = JSON.stringify(changes.acceptance_test);
|
|
96831
|
-
await this.runCommand(["meta", "set", id, "acceptance_test", valStr, "--format", "json"]);
|
|
96832
|
-
}
|
|
96833
|
-
if (changes.type) {
|
|
96834
|
-
const valStr = JSON.stringify(changes.type);
|
|
96835
|
-
await this.runCommand(["meta", "set", id, "type", valStr, "--format", "json"]);
|
|
96836
|
-
}
|
|
96837
|
-
if (changes.context) {
|
|
96838
|
-
const valStr = JSON.stringify(changes.context);
|
|
96839
|
-
await this.runCommand(["meta", "set", id, "context", valStr, "--format", "json"]);
|
|
96840
|
-
}
|
|
96841
|
-
if (changes.output !== undefined) {
|
|
96842
|
-
const valStr = JSON.stringify(changes.output);
|
|
96843
|
-
await this.runCommand(["meta", "set", id, "output", valStr, "--format", "json"]);
|
|
96844
|
-
}
|
|
96845
|
-
let finalPearl;
|
|
96846
|
-
if (!output) {
|
|
96847
|
-
finalPearl = await this.get(id);
|
|
96848
|
-
} else {
|
|
96849
|
-
finalPearl = this.parseRaw(output);
|
|
96850
|
-
}
|
|
96851
|
-
if (changes.status === "done" && current?.status !== "done") {
|
|
96852
|
-
try {
|
|
96853
|
-
const config2 = getConfig();
|
|
96854
|
-
if (config2.hooks?.onPearlDone) {
|
|
96855
|
-
await config2.hooks.onPearlDone(finalPearl);
|
|
96856
|
-
}
|
|
96857
|
-
} catch (err) {
|
|
96858
|
-
logger.error(`[Pearls] Error executing onPearlDone hook for ${id}:`, err);
|
|
96859
|
-
}
|
|
96860
|
-
}
|
|
96861
|
-
return finalPearl;
|
|
96862
|
-
}
|
|
96863
|
-
validateTransition(current, next) {
|
|
96864
|
-
const validTransitions = {
|
|
96865
|
-
open: ["in_progress", "done"],
|
|
96866
|
-
in_progress: ["verify", "open"],
|
|
96867
|
-
verify: ["done", "in_progress", "open"],
|
|
96868
|
-
done: ["in_progress", "open"]
|
|
96869
|
-
};
|
|
96870
|
-
if (current.status === next)
|
|
96871
|
-
return;
|
|
96872
|
-
const allowed = validTransitions[current.status];
|
|
96873
|
-
if (!allowed.includes(next)) {
|
|
96874
|
-
throw new Error(`Invalid state transition for ${current.id}: ${current.status} -> ${next}`);
|
|
96875
|
-
}
|
|
96876
|
-
}
|
|
96877
|
-
async addDependency(childId, parentId) {
|
|
96878
|
-
await this.runCommand(["link", childId, parentId, "blocks"]);
|
|
96879
|
-
}
|
|
96880
|
-
async addComment(id, comment) {
|
|
96881
|
-
return this.runCommand(["comments", "add", id, comment]);
|
|
96882
|
-
}
|
|
96883
|
-
async listComments(id) {
|
|
96884
|
-
const output = await this.runCommand(["comments", "list", id, "--format", "json"]);
|
|
96885
|
-
if (!output)
|
|
96886
|
-
return [];
|
|
96887
|
-
try {
|
|
96888
|
-
const json4 = JSON.parse(output);
|
|
96889
|
-
const comments = json4.comments || (Array.isArray(json4) ? json4 : []);
|
|
96890
|
-
return comments.map((c) => ({
|
|
96891
|
-
author: c.author || "unknown",
|
|
96892
|
-
content: c.content || c.body || "",
|
|
96893
|
-
created_at: typeof c.created_at === "number" ? new Date(c.created_at * 1000).toISOString() : c.created_at
|
|
96894
|
-
}));
|
|
96895
|
-
} catch (error48) {
|
|
96896
|
-
logger.error(`[Pearls] Failed to parse comments for ${id}:`, error48);
|
|
96897
|
-
return [];
|
|
96898
|
-
}
|
|
96899
|
-
}
|
|
96900
|
-
}
|
|
96901
|
-
var PEARLS_KEY2 = "pearls_client";
|
|
96902
|
-
function getPearls2(basePath) {
|
|
96903
|
-
return getGlobalSingleton(PEARLS_KEY2, () => {
|
|
96904
|
-
let path2 = basePath;
|
|
96905
|
-
if (!path2) {
|
|
96906
|
-
try {
|
|
96907
|
-
const config2 = getConfig();
|
|
96908
|
-
path2 = config2.pearls.path;
|
|
96909
|
-
} catch {
|
|
96910
|
-
path2 = ".pearls";
|
|
96911
|
-
}
|
|
96912
|
-
}
|
|
96913
|
-
return new PearlsClient2(path2);
|
|
96914
|
-
});
|
|
96915
|
-
}
|
|
96916
|
-
|
|
96917
|
-
// src/core/queue.ts
|
|
96918
|
-
import { Database as Database2 } from "bun:sqlite";
|
|
96919
|
-
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
96920
|
-
import { dirname as dirname4, resolve as resolve11 } from "node:path";
|
|
96921
|
-
var TicketStatusSchema2 = exports_external.enum([
|
|
96922
|
-
"queued",
|
|
96923
|
-
"processing",
|
|
96924
|
-
"completed",
|
|
96925
|
-
"failed"
|
|
96926
|
-
]);
|
|
96927
|
-
var TicketSchema2 = exports_external.object({
|
|
96928
|
-
id: exports_external.string(),
|
|
96929
|
-
pearl_id: exports_external.string(),
|
|
96930
|
-
status: TicketStatusSchema2,
|
|
96931
|
-
priority: exports_external.number().min(0).max(3),
|
|
96932
|
-
target_role: exports_external.enum(["worker", "gatekeeper"]),
|
|
96933
|
-
assignee_id: exports_external.string().nullable(),
|
|
96934
|
-
created_at: exports_external.number(),
|
|
96935
|
-
started_at: exports_external.number().nullable(),
|
|
96936
|
-
completed_at: exports_external.number().nullable(),
|
|
96937
|
-
heartbeat_at: exports_external.number().nullable(),
|
|
96938
|
-
retry_count: exports_external.number(),
|
|
96939
|
-
next_attempt_at: exports_external.number().nullable(),
|
|
96940
|
-
output: exports_external.unknown().optional()
|
|
96941
|
-
});
|
|
96942
|
-
|
|
96943
|
-
class WorkQueue2 {
|
|
96944
|
-
db;
|
|
96945
|
-
constructor(dbPath) {
|
|
96946
|
-
const finalPath = dbPath || resolve11(process.cwd(), ".citadel", "queue.sqlite");
|
|
96947
|
-
mkdirSync4(dirname4(finalPath), { recursive: true });
|
|
96948
|
-
this.db = new Database2(finalPath);
|
|
96949
|
-
this.init();
|
|
96950
|
-
}
|
|
96951
|
-
init() {
|
|
96952
|
-
this.db.run(`
|
|
96953
|
-
CREATE TABLE IF NOT EXISTS tickets (
|
|
96954
|
-
id TEXT PRIMARY KEY,
|
|
96955
|
-
pearl_id TEXT NOT NULL,
|
|
96956
|
-
status TEXT NOT NULL,
|
|
96957
|
-
priority INTEGER NOT NULL,
|
|
96958
|
-
target_role TEXT NOT NULL,
|
|
96959
|
-
assignee_id TEXT,
|
|
96960
|
-
created_at INTEGER NOT NULL,
|
|
96961
|
-
started_at INTEGER,
|
|
96962
|
-
completed_at INTEGER,
|
|
96963
|
-
heartbeat_at INTEGER,
|
|
96964
|
-
retry_count INTEGER DEFAULT 0,
|
|
96965
|
-
next_attempt_at INTEGER
|
|
96966
|
-
)
|
|
96967
|
-
`);
|
|
96968
|
-
this.db.run(`CREATE INDEX IF NOT EXISTS idx_status_priority ON tickets(status, priority ASC, created_at ASC)`);
|
|
96969
|
-
this.db.run(`CREATE INDEX IF NOT EXISTS idx_pearl_id ON tickets(pearl_id)`);
|
|
96970
|
-
try {
|
|
96971
|
-
this.db.run(`ALTER TABLE tickets ADD COLUMN output TEXT`);
|
|
96972
|
-
} catch {}
|
|
96973
|
-
try {
|
|
96974
|
-
this.db.run(`ALTER TABLE tickets ADD COLUMN next_attempt_at INTEGER`);
|
|
96975
|
-
} catch {}
|
|
96976
|
-
}
|
|
96977
|
-
enqueue(pearlId, priority, targetRole) {
|
|
96978
|
-
const id = crypto.randomUUID();
|
|
96979
|
-
const now = Date.now();
|
|
96980
|
-
const finalPriority = priority ?? 1;
|
|
96981
|
-
const finalRole = targetRole ?? "worker";
|
|
96982
|
-
this.db.run(`
|
|
96983
|
-
INSERT INTO tickets (id, pearl_id, status, priority, target_role, created_at, retry_count)
|
|
96984
|
-
VALUES (?, ?, 'queued', ?, ?, ?, 0)
|
|
96985
|
-
`, [id, pearlId, finalPriority, finalRole, now]);
|
|
96986
|
-
}
|
|
96987
|
-
claim(assigneeId, role) {
|
|
96988
|
-
const transaction = this.db.transaction(() => {
|
|
96989
|
-
const now = Date.now();
|
|
96990
|
-
const candidate = this.db.query(`
|
|
96991
|
-
SELECT * FROM tickets
|
|
96992
|
-
WHERE status = 'queued' AND target_role = ?
|
|
96993
|
-
AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
|
|
96994
|
-
ORDER BY priority ASC, created_at ASC
|
|
96995
|
-
LIMIT 1
|
|
96996
|
-
`).get(role, now);
|
|
96997
|
-
if (!candidate)
|
|
96998
|
-
return null;
|
|
96999
|
-
logger.info(`[Queue] Claiming ticket ${candidate.id} for ${assigneeId} (role: ${role})`);
|
|
97000
|
-
this.db.run(`
|
|
97001
|
-
UPDATE tickets
|
|
97002
|
-
SET status = 'processing', assignee_id = ?, started_at = ?, heartbeat_at = ?
|
|
97003
|
-
WHERE id = ?
|
|
97004
|
-
`, [assigneeId, now, now, candidate.id]);
|
|
97005
|
-
return this.db.query(`SELECT * FROM tickets WHERE id = ?`).get(candidate.id);
|
|
97006
|
-
});
|
|
97007
|
-
return transaction();
|
|
97008
|
-
}
|
|
97009
|
-
heartbeat(ticketId) {
|
|
97010
|
-
this.db.run(`
|
|
97011
|
-
UPDATE tickets
|
|
97012
|
-
SET heartbeat_at = ?
|
|
97013
|
-
WHERE id = ? AND status = 'processing'
|
|
97014
|
-
`, [Date.now(), ticketId]);
|
|
97015
|
-
}
|
|
97016
|
-
complete(ticketId, output) {
|
|
97017
|
-
logger.info(`[Queue] Completing ticket ${ticketId}`);
|
|
97018
|
-
const now = Date.now();
|
|
97019
|
-
let result;
|
|
97020
|
-
if (output !== undefined && output !== null) {
|
|
97021
|
-
const outputJson = JSON.stringify(output);
|
|
97022
|
-
result = this.db.run(`
|
|
97023
|
-
UPDATE tickets
|
|
97024
|
-
SET status = 'completed', completed_at = ?, output = ?
|
|
97025
|
-
WHERE id = ? AND status = 'processing'
|
|
97026
|
-
`, [now, outputJson, ticketId]);
|
|
97027
|
-
} else {
|
|
97028
|
-
result = this.db.run(`
|
|
97029
|
-
UPDATE tickets
|
|
97030
|
-
SET status = 'completed', completed_at = ?
|
|
97031
|
-
WHERE id = ? AND status = 'processing'
|
|
97032
|
-
`, [now, ticketId]);
|
|
97033
|
-
}
|
|
97034
|
-
if (result.changes === 0) {
|
|
97035
|
-
const current = this.db.query(`SELECT status FROM tickets WHERE id = ?`).get(ticketId);
|
|
97036
|
-
if (current && current.status === "completed") {
|
|
97037
|
-
logger.debug(`[Queue] Ticket ${ticketId} already completed (idempotent).`);
|
|
97038
|
-
return;
|
|
97039
|
-
}
|
|
97040
|
-
const status = current?.status || "unknown";
|
|
97041
|
-
logger.warn(`[Queue] Failed to complete ticket ${ticketId}: Expected 'processing', found '${status}'.`);
|
|
97042
|
-
if (status === "queued") {
|
|
97043
|
-
logger.warn(`[Queue] Ticket ${ticketId} was reset to 'queued' while active. Ignoring completion to avoid state corruption.`);
|
|
97044
|
-
return;
|
|
97045
|
-
}
|
|
97046
|
-
throw new Error(`Failed to complete ticket ${ticketId}: Ticket is not in 'processing' state (current: ${status}).`);
|
|
97047
|
-
}
|
|
97048
|
-
}
|
|
97049
|
-
getOutput(pearlId) {
|
|
97050
|
-
const result = this.db.query(`
|
|
97051
|
-
SELECT output FROM tickets
|
|
97052
|
-
WHERE pearl_id = ? AND status = 'completed'
|
|
97053
|
-
ORDER BY completed_at DESC
|
|
97054
|
-
LIMIT 1
|
|
97055
|
-
`).get(pearlId);
|
|
97056
|
-
if (result?.output) {
|
|
97057
|
-
return JSON.parse(result.output);
|
|
97058
|
-
}
|
|
97059
|
-
return null;
|
|
97060
|
-
}
|
|
97061
|
-
fail(ticketId, permanent = false, maxRetries = 10) {
|
|
97062
|
-
if (permanent) {
|
|
97063
|
-
this.db.run(`
|
|
97064
|
-
UPDATE tickets
|
|
97065
|
-
SET status = 'failed'
|
|
97066
|
-
WHERE id = ? AND status = 'processing'
|
|
97067
|
-
`, [ticketId]);
|
|
97068
|
-
} else {
|
|
97069
|
-
const now = Date.now();
|
|
97070
|
-
const current = this.db.query(`SELECT retry_count FROM tickets WHERE id = ?`).get(ticketId);
|
|
97071
|
-
const nextRetry = (current?.retry_count || 0) + 1;
|
|
97072
|
-
if (nextRetry > maxRetries) {
|
|
97073
|
-
logger.warn(`[Queue] Ticket ${ticketId} exceeded max retries (${maxRetries}). Failing permanently.`);
|
|
97074
|
-
this.db.run(`
|
|
97075
|
-
UPDATE tickets
|
|
97076
|
-
SET status = 'failed'
|
|
97077
|
-
WHERE id = ? AND status = 'processing'
|
|
97078
|
-
`, [ticketId]);
|
|
97079
|
-
return;
|
|
97080
|
-
}
|
|
97081
|
-
const nextAttempt = now + Math.min(1000 * 2 ** nextRetry, 300000);
|
|
97082
|
-
logger.info(`[Queue] Failing ticket ${ticketId} (retry ${nextRetry}/${maxRetries}, next attempt at ${new Date(nextAttempt).toISOString()})`);
|
|
97083
|
-
this.db.run(`
|
|
97084
|
-
UPDATE tickets
|
|
97085
|
-
SET status = 'queued', assignee_id = NULL, started_at = NULL, heartbeat_at = NULL,
|
|
97086
|
-
retry_count = ?, next_attempt_at = ?
|
|
97087
|
-
WHERE id = ? AND status = 'processing'
|
|
97088
|
-
`, [nextRetry, nextAttempt, ticketId]);
|
|
97089
|
-
}
|
|
97090
|
-
}
|
|
97091
|
-
releaseStalled(timeoutMs) {
|
|
97092
|
-
const cutoff = Date.now() - timeoutMs;
|
|
97093
|
-
const stalled = this.db.query(`
|
|
97094
|
-
SELECT id FROM tickets
|
|
97095
|
-
WHERE status = 'processing' AND heartbeat_at < ?
|
|
97096
|
-
`).all(cutoff);
|
|
97097
|
-
if (stalled.length === 0)
|
|
97098
|
-
return 0;
|
|
97099
|
-
const releaseStmt = this.db.prepare(`
|
|
97100
|
-
UPDATE tickets
|
|
97101
|
-
SET status = 'queued', assignee_id = NULL, started_at = NULL, heartbeat_at = NULL,
|
|
97102
|
-
retry_count = retry_count + 1, next_attempt_at = ?
|
|
97103
|
-
WHERE id = ?
|
|
97104
|
-
`);
|
|
97105
|
-
const transaction = this.db.transaction(() => {
|
|
97106
|
-
const now = Date.now();
|
|
97107
|
-
for (const ticket of stalled) {
|
|
97108
|
-
const t = this.db.query(`SELECT retry_count FROM tickets WHERE id = ?`).get(ticket.id);
|
|
97109
|
-
const nextRetry = (t?.retry_count || 0) + 1;
|
|
97110
|
-
const nextAttempt = now + Math.min(1000 * 2 ** nextRetry, 300000);
|
|
97111
|
-
releaseStmt.run(nextAttempt, ticket.id);
|
|
97112
|
-
}
|
|
97113
|
-
});
|
|
97114
|
-
transaction();
|
|
97115
|
-
return stalled.length;
|
|
97116
|
-
}
|
|
97117
|
-
getActiveTicket(pearlId) {
|
|
97118
|
-
return this.db.query(`
|
|
97119
|
-
SELECT * FROM tickets
|
|
97120
|
-
WHERE pearl_id = ? AND status IN ('queued', 'processing')
|
|
97121
|
-
`).get(pearlId);
|
|
97122
|
-
}
|
|
97123
|
-
getLatestTicket(pearlId) {
|
|
97124
|
-
return this.db.query(`
|
|
97125
|
-
SELECT * FROM tickets
|
|
97126
|
-
WHERE pearl_id = ?
|
|
97127
|
-
ORDER BY created_at DESC
|
|
97128
|
-
LIMIT 1
|
|
97129
|
-
`).get(pearlId);
|
|
97130
|
-
}
|
|
97131
|
-
resetPearl(pearlId) {
|
|
97132
|
-
this.db.run("DELETE FROM tickets WHERE pearl_id = ?", [pearlId]);
|
|
97133
|
-
}
|
|
97134
|
-
listActive() {
|
|
97135
|
-
return this.db.query("SELECT * FROM tickets WHERE status = 'processing'").all();
|
|
97136
|
-
}
|
|
97137
|
-
getTicketsByStatus(status) {
|
|
97138
|
-
return this.db.query("SELECT * FROM tickets WHERE status = ?").all(status);
|
|
97139
|
-
}
|
|
97140
|
-
getAllTickets(status) {
|
|
97141
|
-
if (status) {
|
|
97142
|
-
return this.db.query("SELECT * FROM tickets WHERE status = ? ORDER BY created_at DESC").all(status);
|
|
97143
|
-
}
|
|
97144
|
-
return this.db.query("SELECT * FROM tickets ORDER BY created_at DESC").all();
|
|
97145
|
-
}
|
|
97146
|
-
getPendingCount(role) {
|
|
97147
|
-
const result = this.db.query(`
|
|
97148
|
-
SELECT COUNT(*) as count
|
|
97149
|
-
FROM tickets
|
|
97150
|
-
WHERE status = 'queued' AND target_role = ?
|
|
97151
|
-
`).get(role);
|
|
97152
|
-
return result.count;
|
|
97153
|
-
}
|
|
97154
|
-
close() {
|
|
97155
|
-
this.db.close();
|
|
97156
|
-
}
|
|
97157
|
-
}
|
|
97158
|
-
var QUEUE_KEY2 = "work_queue";
|
|
97159
|
-
function getQueue2() {
|
|
97160
|
-
return getGlobalSingleton(QUEUE_KEY2, () => new WorkQueue2);
|
|
97161
|
-
}
|
|
97162
|
-
|
|
97163
|
-
// src/services/conductor.ts
|
|
97164
|
-
class Conductor2 {
|
|
97165
|
-
isRunning = false;
|
|
97166
|
-
routerTimer = null;
|
|
97167
|
-
consecutiveFailures = 0;
|
|
97168
|
-
config;
|
|
97169
|
-
pools = new Map;
|
|
97170
|
-
gatekeeperPool;
|
|
97171
|
-
pearls;
|
|
97172
|
-
queue;
|
|
97173
|
-
constructor(pearls, queue, config2, PoolClass = WorkerPool) {
|
|
97174
|
-
this.pearls = pearls || getPearls();
|
|
97175
|
-
this.queue = queue || getQueue();
|
|
97176
|
-
this.config = config2 || getConfig();
|
|
97177
|
-
logger.info(`[Conductor] Queue DB: ${this.queue.db?.filename}`);
|
|
97178
|
-
logger.info(`[Conductor] Config: min_workers=${this.config.worker.min_workers}`);
|
|
97179
|
-
const roles = Object.keys(this.config.agents).filter((r) => r !== "gatekeeper");
|
|
97180
|
-
for (const role of roles) {
|
|
97181
|
-
const pool = new PoolClass(role, (id) => new Hook(id, role, async (ticket) => {
|
|
97182
|
-
logger.info(`[${role}] Processing ${ticket.pearl_id}`, {
|
|
97183
|
-
pearlId: ticket.pearl_id
|
|
97184
|
-
});
|
|
97185
|
-
const pearl = await this.pearls.get(ticket.pearl_id).catch(() => null);
|
|
97186
|
-
if (!pearl) {
|
|
97187
|
-
logger.error(`[${role}] Failed to retrieve pearl ${ticket.pearl_id} for processing`, { pearlId: ticket.pearl_id });
|
|
97188
|
-
this.queue.fail(ticket.id, true);
|
|
97189
|
-
return;
|
|
97190
|
-
}
|
|
97191
|
-
await this.pearls.update(ticket.pearl_id, {
|
|
97192
|
-
status: "in_progress",
|
|
97193
|
-
assignee: role
|
|
97194
|
-
});
|
|
97195
|
-
logger.info(`[${role}] Instantiating agent for role: ${role}`, { pearlId: ticket.pearl_id });
|
|
97196
95792
|
const agent = new WorkerAgent(role);
|
|
97197
95793
|
try {
|
|
97198
95794
|
const result = await agent.run(`Process this task: ${pearl.title}`, { pearlId: ticket.pearl_id, pearl });
|
|
@@ -97227,6 +95823,13 @@ class Conductor2 {
|
|
|
97227
95823
|
if (!submittedWork) {
|
|
97228
95824
|
logger.warn(`[Gatekeeper] No submitted work found for ${ticket.pearl_id} (retrieved 'null' from queue). Evaluator may reject.`, { pearlId: ticket.pearl_id });
|
|
97229
95825
|
}
|
|
95826
|
+
if (this.config.hooks?.onPearlStart) {
|
|
95827
|
+
try {
|
|
95828
|
+
await this.config.hooks.onPearlStart(pearl);
|
|
95829
|
+
} catch (err) {
|
|
95830
|
+
logger.error(`[Gatekeeper] Error in onPearlStart hook:`, err);
|
|
95831
|
+
}
|
|
95832
|
+
}
|
|
97230
95833
|
try {
|
|
97231
95834
|
await agent.run(`Verify this work: ${pearl.title}`, {
|
|
97232
95835
|
pearlId: ticket.pearl_id,
|
|
@@ -97492,6 +96095,307 @@ class Conductor2 {
|
|
|
97492
96095
|
}
|
|
97493
96096
|
}
|
|
97494
96097
|
|
|
96098
|
+
// src/bridge/components/Dashboard.tsx
|
|
96099
|
+
var import_react27 = __toESM(require_react(), 1);
|
|
96100
|
+
|
|
96101
|
+
// src/bridge/components/MoleculeTree.tsx
|
|
96102
|
+
var import_react26 = __toESM(require_react(), 1);
|
|
96103
|
+
var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
|
|
96104
|
+
var buildTree = (pearls) => {
|
|
96105
|
+
const map4 = new Map;
|
|
96106
|
+
const roots = [];
|
|
96107
|
+
for (const pearl of pearls) {
|
|
96108
|
+
map4.set(pearl.id, { pearl, children: [] });
|
|
96109
|
+
}
|
|
96110
|
+
for (const pearl of pearls) {
|
|
96111
|
+
const node = map4.get(pearl.id);
|
|
96112
|
+
if (!node)
|
|
96113
|
+
continue;
|
|
96114
|
+
if (pearl.parent && map4.has(pearl.parent)) {
|
|
96115
|
+
const parent = map4.get(pearl.parent);
|
|
96116
|
+
if (parent) {
|
|
96117
|
+
parent.children.push(node);
|
|
96118
|
+
}
|
|
96119
|
+
} else {
|
|
96120
|
+
roots.push(node);
|
|
96121
|
+
}
|
|
96122
|
+
}
|
|
96123
|
+
return roots;
|
|
96124
|
+
};
|
|
96125
|
+
var PearlNode = ({ node, depth }) => {
|
|
96126
|
+
const indent = " ".repeat(depth);
|
|
96127
|
+
const color = node.pearl.status === "done" ? "green" : node.pearl.status === "verify" ? "magenta" : node.pearl.status === "in_progress" ? "yellow" : "white";
|
|
96128
|
+
const icon = node.pearl.status === "done" ? "✓" : node.pearl.status === "verify" ? "?" : node.pearl.status === "in_progress" ? "▶" : "○";
|
|
96129
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
96130
|
+
flexDirection: "column",
|
|
96131
|
+
children: [
|
|
96132
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96133
|
+
color,
|
|
96134
|
+
children: [
|
|
96135
|
+
indent,
|
|
96136
|
+
icon,
|
|
96137
|
+
" ",
|
|
96138
|
+
node.pearl.title,
|
|
96139
|
+
" ",
|
|
96140
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96141
|
+
color: "gray",
|
|
96142
|
+
children: [
|
|
96143
|
+
"(",
|
|
96144
|
+
node.pearl.id,
|
|
96145
|
+
")"
|
|
96146
|
+
]
|
|
96147
|
+
}, undefined, true, undefined, this)
|
|
96148
|
+
]
|
|
96149
|
+
}, undefined, true, undefined, this),
|
|
96150
|
+
node.children.map((child) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
|
|
96151
|
+
node: child,
|
|
96152
|
+
depth: depth + 1
|
|
96153
|
+
}, child.pearl.id, false, undefined, this))
|
|
96154
|
+
]
|
|
96155
|
+
}, undefined, true, undefined, this);
|
|
96156
|
+
};
|
|
96157
|
+
var MoleculeTree = () => {
|
|
96158
|
+
const [tree, setTree] = import_react26.useState([]);
|
|
96159
|
+
import_react26.useEffect(() => {
|
|
96160
|
+
const refresh = async () => {
|
|
96161
|
+
try {
|
|
96162
|
+
const pearls = await getPearls().getAll();
|
|
96163
|
+
setTree(buildTree(pearls));
|
|
96164
|
+
} catch (_e) {}
|
|
96165
|
+
};
|
|
96166
|
+
refresh();
|
|
96167
|
+
const timer = setInterval(refresh, 2000);
|
|
96168
|
+
return () => clearInterval(timer);
|
|
96169
|
+
}, []);
|
|
96170
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
96171
|
+
flexDirection: "column",
|
|
96172
|
+
borderStyle: "round",
|
|
96173
|
+
borderColor: "magenta",
|
|
96174
|
+
width: "50%",
|
|
96175
|
+
height: 15,
|
|
96176
|
+
overflowY: "hidden",
|
|
96177
|
+
children: [
|
|
96178
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96179
|
+
bold: true,
|
|
96180
|
+
children: "Molecules"
|
|
96181
|
+
}, undefined, false, undefined, this),
|
|
96182
|
+
tree.length === 0 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
96183
|
+
color: "gray",
|
|
96184
|
+
children: "No molecules found"
|
|
96185
|
+
}, undefined, false, undefined, this) : null,
|
|
96186
|
+
tree.map((node) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
|
|
96187
|
+
node,
|
|
96188
|
+
depth: 0
|
|
96189
|
+
}, node.pearl.id, false, undefined, this))
|
|
96190
|
+
]
|
|
96191
|
+
}, undefined, true, undefined, this);
|
|
96192
|
+
};
|
|
96193
|
+
|
|
96194
|
+
// src/bridge/components/Dashboard.tsx
|
|
96195
|
+
var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
|
|
96196
|
+
var LogStream = () => {
|
|
96197
|
+
const config2 = getConfig();
|
|
96198
|
+
const maxLogs = config2.bridge?.maxLogs || 1000;
|
|
96199
|
+
const [logs, setLogs] = import_react27.useState([]);
|
|
96200
|
+
const [scrollTop, setScrollTop] = import_react27.useState(0);
|
|
96201
|
+
const [viewHeight, _setViewHeight] = import_react27.useState(15);
|
|
96202
|
+
import_react27.useEffect(() => {
|
|
96203
|
+
const handler = (entry) => {
|
|
96204
|
+
setLogs((prev) => {
|
|
96205
|
+
const updated = [...prev, entry];
|
|
96206
|
+
if (updated.length > maxLogs) {
|
|
96207
|
+
return updated.slice(updated.length - maxLogs);
|
|
96208
|
+
}
|
|
96209
|
+
return updated;
|
|
96210
|
+
});
|
|
96211
|
+
};
|
|
96212
|
+
logger.on("log", handler);
|
|
96213
|
+
return () => {
|
|
96214
|
+
logger.off("log", handler);
|
|
96215
|
+
};
|
|
96216
|
+
}, [maxLogs]);
|
|
96217
|
+
use_input_default((_input, key) => {
|
|
96218
|
+
if (key.upArrow) {
|
|
96219
|
+
setScrollTop((prev) => Math.min(prev + 1, Math.max(0, logs.length - viewHeight)));
|
|
96220
|
+
}
|
|
96221
|
+
if (key.downArrow) {
|
|
96222
|
+
setScrollTop((prev) => Math.max(0, prev - 1));
|
|
96223
|
+
}
|
|
96224
|
+
if (key.pageUp) {
|
|
96225
|
+
setScrollTop((prev) => Math.min(prev + 10, Math.max(0, logs.length - viewHeight)));
|
|
96226
|
+
}
|
|
96227
|
+
if (key.pageDown) {
|
|
96228
|
+
setScrollTop((prev) => Math.max(0, prev - 10));
|
|
96229
|
+
}
|
|
96230
|
+
});
|
|
96231
|
+
const total = logs.length;
|
|
96232
|
+
const effectiveHeight = Math.min(total, viewHeight);
|
|
96233
|
+
let startIndex = total - effectiveHeight - scrollTop;
|
|
96234
|
+
if (startIndex < 0)
|
|
96235
|
+
startIndex = 0;
|
|
96236
|
+
const endIndex = startIndex + effectiveHeight;
|
|
96237
|
+
const viewLogs = logs.slice(startIndex, endIndex);
|
|
96238
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96239
|
+
flexDirection: "column",
|
|
96240
|
+
borderStyle: "round",
|
|
96241
|
+
borderColor: scrollTop > 0 ? "yellow" : "green",
|
|
96242
|
+
width: "100%",
|
|
96243
|
+
height: viewHeight + 2,
|
|
96244
|
+
children: [
|
|
96245
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96246
|
+
flexDirection: "row",
|
|
96247
|
+
justifyContent: "space-between",
|
|
96248
|
+
children: [
|
|
96249
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96250
|
+
bold: true,
|
|
96251
|
+
children: [
|
|
96252
|
+
"Live Logs ",
|
|
96253
|
+
scrollTop > 0 ? `(Scrolled: -${scrollTop})` : "(Live)"
|
|
96254
|
+
]
|
|
96255
|
+
}, undefined, true, undefined, this),
|
|
96256
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96257
|
+
color: "gray",
|
|
96258
|
+
children: [
|
|
96259
|
+
logs.length,
|
|
96260
|
+
"/",
|
|
96261
|
+
maxLogs
|
|
96262
|
+
]
|
|
96263
|
+
}, undefined, true, undefined, this)
|
|
96264
|
+
]
|
|
96265
|
+
}, undefined, true, undefined, this),
|
|
96266
|
+
viewLogs.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96267
|
+
wrap: "truncate",
|
|
96268
|
+
children: [
|
|
96269
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96270
|
+
color: "gray",
|
|
96271
|
+
children: [
|
|
96272
|
+
"[",
|
|
96273
|
+
l.timestamp.split("T")[1]?.split(".")[0] || "00:00:00",
|
|
96274
|
+
"]"
|
|
96275
|
+
]
|
|
96276
|
+
}, undefined, true, undefined, this),
|
|
96277
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96278
|
+
color: l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "white",
|
|
96279
|
+
children: [
|
|
96280
|
+
" ",
|
|
96281
|
+
"[",
|
|
96282
|
+
l.level.toUpperCase(),
|
|
96283
|
+
"]",
|
|
96284
|
+
" "
|
|
96285
|
+
]
|
|
96286
|
+
}, undefined, true, undefined, this),
|
|
96287
|
+
l.message
|
|
96288
|
+
]
|
|
96289
|
+
}, `${l.timestamp}-${startIndex + i2}`, true, undefined, this))
|
|
96290
|
+
]
|
|
96291
|
+
}, undefined, true, undefined, this);
|
|
96292
|
+
};
|
|
96293
|
+
var AgentMatrix = () => {
|
|
96294
|
+
const [activeTickets, setActiveTickets] = import_react27.useState([]);
|
|
96295
|
+
import_react27.useEffect(() => {
|
|
96296
|
+
const timer = setInterval(() => {
|
|
96297
|
+
const queue = getQueue();
|
|
96298
|
+
const rows = queue.getTicketsByStatus("processing");
|
|
96299
|
+
if (rows)
|
|
96300
|
+
setActiveTickets(rows);
|
|
96301
|
+
}, 1000);
|
|
96302
|
+
return () => clearInterval(timer);
|
|
96303
|
+
}, []);
|
|
96304
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96305
|
+
flexDirection: "column",
|
|
96306
|
+
borderStyle: "round",
|
|
96307
|
+
borderColor: "blue",
|
|
96308
|
+
width: "50%",
|
|
96309
|
+
children: [
|
|
96310
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96311
|
+
bold: true,
|
|
96312
|
+
children: "Active Agents"
|
|
96313
|
+
}, undefined, false, undefined, this),
|
|
96314
|
+
activeTickets.length === 0 ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96315
|
+
color: "gray",
|
|
96316
|
+
children: "No active agents"
|
|
96317
|
+
}, undefined, false, undefined, this) : null,
|
|
96318
|
+
activeTickets.map((t) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96319
|
+
flexDirection: "row",
|
|
96320
|
+
children: [
|
|
96321
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96322
|
+
color: "cyan",
|
|
96323
|
+
children: t.target_role
|
|
96324
|
+
}, undefined, false, undefined, this),
|
|
96325
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96326
|
+
children: " -> "
|
|
96327
|
+
}, undefined, false, undefined, this),
|
|
96328
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96329
|
+
children: t.pearl_id
|
|
96330
|
+
}, undefined, false, undefined, this),
|
|
96331
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96332
|
+
color: "yellow",
|
|
96333
|
+
children: [
|
|
96334
|
+
" (",
|
|
96335
|
+
t.status,
|
|
96336
|
+
")"
|
|
96337
|
+
]
|
|
96338
|
+
}, undefined, true, undefined, this)
|
|
96339
|
+
]
|
|
96340
|
+
}, t.id, true, undefined, this))
|
|
96341
|
+
]
|
|
96342
|
+
}, undefined, true, undefined, this);
|
|
96343
|
+
};
|
|
96344
|
+
var Dashboard = () => {
|
|
96345
|
+
const [status] = import_react27.useState("Online");
|
|
96346
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96347
|
+
flexDirection: "column",
|
|
96348
|
+
padding: 1,
|
|
96349
|
+
children: [
|
|
96350
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96351
|
+
children: [
|
|
96352
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96353
|
+
children: "The Citadel Bridge "
|
|
96354
|
+
}, undefined, false, undefined, this),
|
|
96355
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96356
|
+
color: "green",
|
|
96357
|
+
children: [
|
|
96358
|
+
"[",
|
|
96359
|
+
status,
|
|
96360
|
+
"]"
|
|
96361
|
+
]
|
|
96362
|
+
}, undefined, true, undefined, this)
|
|
96363
|
+
]
|
|
96364
|
+
}, undefined, true, undefined, this),
|
|
96365
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
96366
|
+
flexDirection: "row",
|
|
96367
|
+
children: [
|
|
96368
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(AgentMatrix, {}, undefined, false, undefined, this),
|
|
96369
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MoleculeTree, {}, undefined, false, undefined, this)
|
|
96370
|
+
]
|
|
96371
|
+
}, undefined, true, undefined, this),
|
|
96372
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(LogStream, {}, undefined, false, undefined, this),
|
|
96373
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
96374
|
+
color: "gray",
|
|
96375
|
+
children: "Press Ctrl+C to exit"
|
|
96376
|
+
}, undefined, false, undefined, this)
|
|
96377
|
+
]
|
|
96378
|
+
}, undefined, true, undefined, this);
|
|
96379
|
+
};
|
|
96380
|
+
|
|
96381
|
+
// src/bridge/index.tsx
|
|
96382
|
+
var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
|
|
96383
|
+
async function startBridge() {
|
|
96384
|
+
await loadConfig();
|
|
96385
|
+
logger.setConsoleEnabled(false);
|
|
96386
|
+
const conductor = new Conductor;
|
|
96387
|
+
conductor.start();
|
|
96388
|
+
const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Dashboard, {}, undefined, false, undefined, this));
|
|
96389
|
+
try {
|
|
96390
|
+
await waitUntilExit();
|
|
96391
|
+
} catch (error48) {
|
|
96392
|
+
console.error("Bridge crashed:", error48);
|
|
96393
|
+
} finally {
|
|
96394
|
+
conductor.stop();
|
|
96395
|
+
console.log("");
|
|
96396
|
+
}
|
|
96397
|
+
}
|
|
96398
|
+
|
|
97495
96399
|
// src/services/workflow-engine.ts
|
|
97496
96400
|
class WorkflowEngine {
|
|
97497
96401
|
registry;
|
|
@@ -97655,7 +96559,7 @@ function getWorkflowEngine() {
|
|
|
97655
96559
|
}
|
|
97656
96560
|
|
|
97657
96561
|
// src/cli.ts
|
|
97658
|
-
var __dirname2 =
|
|
96562
|
+
var __dirname2 = dirname4(fileURLToPath(import.meta.url));
|
|
97659
96563
|
var packageJson = JSON.parse(readFileSync3(join6(__dirname2, "../package.json"), "utf-8"));
|
|
97660
96564
|
var version2 = packageJson.version;
|
|
97661
96565
|
var program2 = new Command;
|
|
@@ -97663,16 +96567,16 @@ program2.name("citadel").description("The Citadel: A deterministic agent orchest
|
|
|
97663
96567
|
function runCLI() {
|
|
97664
96568
|
program2.command("init").description("Initialize a new Citadel project (Foundry Mode)").action(async () => {
|
|
97665
96569
|
try {
|
|
97666
|
-
console.log("\uD83C\uDFD7
|
|
96570
|
+
console.log("\uD83C\uDFD7️ Initializing The Citadel...");
|
|
97667
96571
|
const cwd2 = process.cwd();
|
|
97668
96572
|
const citadelDir = join6(cwd2, ".citadel");
|
|
97669
96573
|
const formulasDir = join6(citadelDir, "formulas");
|
|
97670
96574
|
await mkdir(formulasDir, { recursive: true });
|
|
97671
|
-
console.log("
|
|
96575
|
+
console.log("✅ Created .citadel/ structure");
|
|
97672
96576
|
const configPath = join6(cwd2, "citadel.config.ts");
|
|
97673
96577
|
try {
|
|
97674
96578
|
await access(configPath);
|
|
97675
|
-
console.log("
|
|
96579
|
+
console.log("ℹ️ citadel.config.ts already exists");
|
|
97676
96580
|
} catch {
|
|
97677
96581
|
const configTemplate = `
|
|
97678
96582
|
export default {
|
|
@@ -97723,12 +96627,12 @@ export default {
|
|
|
97723
96627
|
};
|
|
97724
96628
|
`;
|
|
97725
96629
|
await writeFile(configPath, configTemplate.trim());
|
|
97726
|
-
console.log("
|
|
96630
|
+
console.log("✅ Created citadel.config.ts (Ollama default)");
|
|
97727
96631
|
}
|
|
97728
96632
|
const agentsPath = join6(cwd2, "AGENTS.md");
|
|
97729
96633
|
try {
|
|
97730
96634
|
await access(agentsPath);
|
|
97731
|
-
console.log("
|
|
96635
|
+
console.log("ℹ️ AGENTS.md already exists");
|
|
97732
96636
|
} catch {
|
|
97733
96637
|
const agentsTemplate = `
|
|
97734
96638
|
# Project Rules
|
|
@@ -97746,7 +96650,7 @@ export default {
|
|
|
97746
96650
|
- Always write a plan before coding.
|
|
97747
96651
|
`;
|
|
97748
96652
|
await writeFile(agentsPath, agentsTemplate.trim());
|
|
97749
|
-
console.log("
|
|
96653
|
+
console.log("✅ Created AGENTS.md");
|
|
97750
96654
|
}
|
|
97751
96655
|
const formulaPath = join6(formulasDir, "hello_world.toml");
|
|
97752
96656
|
try {
|
|
@@ -97767,26 +96671,26 @@ title = "Say Hello to {{name}}"
|
|
|
97767
96671
|
description = "Create a file named hello_{{name}}.txt with a greeting."
|
|
97768
96672
|
`;
|
|
97769
96673
|
await writeFile(formulaPath, formulaTemplate.trim());
|
|
97770
|
-
console.log("
|
|
96674
|
+
console.log("✅ Created .citadel/formulas/hello_world.toml");
|
|
97771
96675
|
}
|
|
97772
96676
|
console.log("\uD83D\uDD04 Initializing Pearls DB...");
|
|
97773
|
-
const pearls =
|
|
96677
|
+
const pearls = getPearls(join6(cwd2, ".pearls"));
|
|
97774
96678
|
await pearls.init();
|
|
97775
|
-
console.log("
|
|
96679
|
+
console.log("✅ Pearls initialized");
|
|
97776
96680
|
console.log(`
|
|
97777
96681
|
\uD83D\uDE80 Citadel initialized successfully!`);
|
|
97778
96682
|
console.log("Try running:");
|
|
97779
96683
|
console.log(' citadel create "My First Run" -f hello_world -v name=Developer');
|
|
97780
96684
|
console.log(" citadel start");
|
|
97781
96685
|
} catch (error48) {
|
|
97782
|
-
console.error("
|
|
96686
|
+
console.error("❌ Init failed:", error48);
|
|
97783
96687
|
process.exit(1);
|
|
97784
96688
|
}
|
|
97785
96689
|
});
|
|
97786
96690
|
program2.command("start").description("Start the Citadel Conductor service").action(async () => {
|
|
97787
96691
|
try {
|
|
97788
|
-
await
|
|
97789
|
-
const conductor = new
|
|
96692
|
+
await loadConfig();
|
|
96693
|
+
const conductor = new Conductor;
|
|
97790
96694
|
process.on("SIGINT", () => {
|
|
97791
96695
|
console.log(`
|
|
97792
96696
|
Received SIGINT. Stopping...`);
|
|
@@ -97804,13 +96708,13 @@ Received SIGINT. Stopping...`);
|
|
|
97804
96708
|
program2.command("reset-queue [pearlId]").description("Reset the Work Queue (Deletes persistence file or specific pearl tickets)").action(async (pearlId) => {
|
|
97805
96709
|
try {
|
|
97806
96710
|
if (pearlId) {
|
|
97807
|
-
await
|
|
97808
|
-
const queue =
|
|
96711
|
+
await loadConfig();
|
|
96712
|
+
const queue = getQueue();
|
|
97809
96713
|
console.log(`Resetting tickets for pearl: ${pearlId}...`);
|
|
97810
96714
|
queue.resetPearl(pearlId);
|
|
97811
96715
|
console.log(`Tickets for ${pearlId} have been cleared.`);
|
|
97812
96716
|
} else {
|
|
97813
|
-
const dbPath =
|
|
96717
|
+
const dbPath = resolve9(process.cwd(), ".citadel", "queue.sqlite");
|
|
97814
96718
|
console.log(`Resetting entire queue at ${dbPath}...`);
|
|
97815
96719
|
await unlink(dbPath);
|
|
97816
96720
|
console.log("Queue reset successfully.");
|
|
@@ -97824,8 +96728,8 @@ Received SIGINT. Stopping...`);
|
|
|
97824
96728
|
}
|
|
97825
96729
|
});
|
|
97826
96730
|
program2.command("inspect <pearlId>").description("Inspect the active ticket for a pearl").action(async (pearlId) => {
|
|
97827
|
-
await
|
|
97828
|
-
const ticket =
|
|
96731
|
+
await loadConfig();
|
|
96732
|
+
const ticket = getQueue().getActiveTicket(pearlId);
|
|
97829
96733
|
if (ticket) {
|
|
97830
96734
|
console.log(JSON.stringify(ticket, null, 2));
|
|
97831
96735
|
} else {
|
|
@@ -97833,8 +96737,8 @@ Received SIGINT. Stopping...`);
|
|
|
97833
96737
|
}
|
|
97834
96738
|
});
|
|
97835
96739
|
program2.command("queue").description("List all tickets in the work queue").option("-s, --status <status>", "Filter by status (queued, processing, completed, failed)").option("-j, --json", "Output in JSON format").action(async (options) => {
|
|
97836
|
-
await
|
|
97837
|
-
const queue =
|
|
96740
|
+
await loadConfig();
|
|
96741
|
+
const queue = getQueue();
|
|
97838
96742
|
const tickets = queue.getAllTickets(options.status);
|
|
97839
96743
|
if (options.json) {
|
|
97840
96744
|
console.log(JSON.stringify(tickets, null, 2));
|
|
@@ -97862,7 +96766,7 @@ Received SIGINT. Stopping...`);
|
|
|
97862
96766
|
console.error("Error: --formula is required for citadel create");
|
|
97863
96767
|
process.exit(1);
|
|
97864
96768
|
}
|
|
97865
|
-
await
|
|
96769
|
+
await loadConfig();
|
|
97866
96770
|
const engine = getWorkflowEngine();
|
|
97867
96771
|
await engine.init();
|
|
97868
96772
|
const variables = {};
|
|
@@ -97892,9 +96796,9 @@ if (__require.main == __require.module) {
|
|
|
97892
96796
|
export {
|
|
97893
96797
|
runCLI,
|
|
97894
96798
|
program2 as program,
|
|
97895
|
-
|
|
96799
|
+
loadConfig,
|
|
97896
96800
|
getWorkflowEngine,
|
|
97897
|
-
|
|
97898
|
-
|
|
97899
|
-
|
|
96801
|
+
getQueue,
|
|
96802
|
+
getPearls,
|
|
96803
|
+
Conductor
|
|
97900
96804
|
};
|