opencode-swarm 6.22.3 → 6.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -90
- package/dist/hooks/guardrails.d.ts +19 -0
- package/dist/index.js +971 -818
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47321,406 +47321,32 @@ function maskToolOutput(msg, _threshold) {
|
|
|
47321
47321
|
}
|
|
47322
47322
|
// src/hooks/delegation-gate.ts
|
|
47323
47323
|
init_schema();
|
|
47324
|
-
|
|
47325
|
-
const match = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
47326
|
-
return match ? match[1].trim() : null;
|
|
47327
|
-
}
|
|
47328
|
-
function createDelegationGateHook(config3) {
|
|
47329
|
-
const enabled = config3.hooks?.delegation_gate !== false;
|
|
47330
|
-
const delegationMaxChars = config3.hooks?.delegation_max_chars ?? 4000;
|
|
47331
|
-
if (!enabled) {
|
|
47332
|
-
return {
|
|
47333
|
-
messagesTransform: async (_input, _output) => {},
|
|
47334
|
-
toolAfter: async () => {}
|
|
47335
|
-
};
|
|
47336
|
-
}
|
|
47337
|
-
const toolAfter = async (input, _output) => {
|
|
47338
|
-
if (!input.sessionID)
|
|
47339
|
-
return;
|
|
47340
|
-
const session = swarmState.agentSessions.get(input.sessionID);
|
|
47341
|
-
if (!session)
|
|
47342
|
-
return;
|
|
47343
|
-
const normalized = input.tool.replace(/^[^:]+[:.]/, "");
|
|
47344
|
-
if (normalized === "Task" || normalized === "task") {
|
|
47345
|
-
const delegationChain = swarmState.delegationChains.get(input.sessionID);
|
|
47346
|
-
if (delegationChain && delegationChain.length > 0) {
|
|
47347
|
-
let lastCoderIndex = -1;
|
|
47348
|
-
for (let i2 = delegationChain.length - 1;i2 >= 0; i2--) {
|
|
47349
|
-
const target = stripKnownSwarmPrefix(delegationChain[i2].to);
|
|
47350
|
-
if (target.includes("coder")) {
|
|
47351
|
-
lastCoderIndex = i2;
|
|
47352
|
-
break;
|
|
47353
|
-
}
|
|
47354
|
-
}
|
|
47355
|
-
if (lastCoderIndex === -1)
|
|
47356
|
-
return;
|
|
47357
|
-
const afterCoder = delegationChain.slice(lastCoderIndex);
|
|
47358
|
-
let hasReviewer = false;
|
|
47359
|
-
let hasTestEngineer = false;
|
|
47360
|
-
for (const delegation of afterCoder) {
|
|
47361
|
-
const target = stripKnownSwarmPrefix(delegation.to);
|
|
47362
|
-
if (target === "reviewer")
|
|
47363
|
-
hasReviewer = true;
|
|
47364
|
-
if (target === "test_engineer")
|
|
47365
|
-
hasTestEngineer = true;
|
|
47366
|
-
}
|
|
47367
|
-
if (hasReviewer && hasTestEngineer) {
|
|
47368
|
-
session.qaSkipCount = 0;
|
|
47369
|
-
session.qaSkipTaskIds = [];
|
|
47370
|
-
}
|
|
47371
|
-
if (hasReviewer && session.taskWorkflowStates) {
|
|
47372
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
47373
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
47374
|
-
try {
|
|
47375
|
-
advanceTaskState(session, taskId, "reviewer_run");
|
|
47376
|
-
} catch {}
|
|
47377
|
-
}
|
|
47378
|
-
}
|
|
47379
|
-
}
|
|
47380
|
-
if (hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
|
|
47381
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
47382
|
-
if (state === "reviewer_run") {
|
|
47383
|
-
try {
|
|
47384
|
-
advanceTaskState(session, taskId, "tests_run");
|
|
47385
|
-
} catch {}
|
|
47386
|
-
}
|
|
47387
|
-
}
|
|
47388
|
-
}
|
|
47389
|
-
}
|
|
47390
|
-
}
|
|
47391
|
-
};
|
|
47392
|
-
return {
|
|
47393
|
-
messagesTransform: async (_input, output) => {
|
|
47394
|
-
const messages = output.messages;
|
|
47395
|
-
if (!messages || messages.length === 0)
|
|
47396
|
-
return;
|
|
47397
|
-
let lastUserMessageIndex = -1;
|
|
47398
|
-
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
47399
|
-
if (messages[i2]?.info?.role === "user") {
|
|
47400
|
-
lastUserMessageIndex = i2;
|
|
47401
|
-
break;
|
|
47402
|
-
}
|
|
47403
|
-
}
|
|
47404
|
-
if (lastUserMessageIndex === -1)
|
|
47405
|
-
return;
|
|
47406
|
-
const lastUserMessage = messages[lastUserMessageIndex];
|
|
47407
|
-
if (!lastUserMessage?.parts)
|
|
47408
|
-
return;
|
|
47409
|
-
const agent = lastUserMessage.info?.agent;
|
|
47410
|
-
const strippedAgent = agent ? stripKnownSwarmPrefix(agent) : undefined;
|
|
47411
|
-
if (strippedAgent && strippedAgent !== "architect")
|
|
47412
|
-
return;
|
|
47413
|
-
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
47414
|
-
if (textPartIndex === -1)
|
|
47415
|
-
return;
|
|
47416
|
-
const textPart = lastUserMessage.parts[textPartIndex];
|
|
47417
|
-
const text = textPart.text ?? "";
|
|
47418
|
-
const taskDisclosureSessionID = lastUserMessage.info?.sessionID;
|
|
47419
|
-
if (taskDisclosureSessionID) {
|
|
47420
|
-
const taskSession = ensureAgentSession(taskDisclosureSessionID);
|
|
47421
|
-
const currentTaskIdForWindow = taskSession.currentTaskId;
|
|
47422
|
-
if (currentTaskIdForWindow) {
|
|
47423
|
-
const taskLineRegex = /^[ \t]*-[ \t]*(?:\[[ x]\][ \t]+)?(\d+\.\d+(?:\.\d+)*)[:. ].*/gm;
|
|
47424
|
-
const taskLines = [];
|
|
47425
|
-
taskLineRegex.lastIndex = 0;
|
|
47426
|
-
let regexMatch = taskLineRegex.exec(text);
|
|
47427
|
-
while (regexMatch !== null) {
|
|
47428
|
-
taskLines.push({
|
|
47429
|
-
line: regexMatch[0],
|
|
47430
|
-
taskId: regexMatch[1],
|
|
47431
|
-
index: regexMatch.index
|
|
47432
|
-
});
|
|
47433
|
-
regexMatch = taskLineRegex.exec(text);
|
|
47434
|
-
}
|
|
47435
|
-
if (taskLines.length > 5) {
|
|
47436
|
-
const currentIdx = taskLines.findIndex((t) => t.taskId === currentTaskIdForWindow);
|
|
47437
|
-
const windowStart = Math.max(0, currentIdx - 2);
|
|
47438
|
-
const windowEnd = Math.min(taskLines.length - 1, currentIdx + 3);
|
|
47439
|
-
const visibleTasks = taskLines.slice(windowStart, windowEnd + 1);
|
|
47440
|
-
const hiddenBefore = windowStart;
|
|
47441
|
-
const hiddenAfter = taskLines.length - 1 - windowEnd;
|
|
47442
|
-
const totalTasks = taskLines.length;
|
|
47443
|
-
const visibleCount = visibleTasks.length;
|
|
47444
|
-
const firstTaskIndex = taskLines[0].index;
|
|
47445
|
-
const lastTask = taskLines[taskLines.length - 1];
|
|
47446
|
-
const lastTaskEnd = lastTask.index + lastTask.line.length;
|
|
47447
|
-
const before = text.slice(0, firstTaskIndex);
|
|
47448
|
-
const after = text.slice(lastTaskEnd);
|
|
47449
|
-
const visibleLines = visibleTasks.map((t) => t.line).join(`
|
|
47450
|
-
`);
|
|
47451
|
-
const trimComment = `[Task window: showing ${visibleCount} of ${totalTasks} tasks]`;
|
|
47452
|
-
const trimmedMiddle = (hiddenBefore > 0 ? `[...${hiddenBefore} tasks hidden...]
|
|
47453
|
-
` : "") + visibleLines + (hiddenAfter > 0 ? `
|
|
47454
|
-
[...${hiddenAfter} tasks hidden...]` : "");
|
|
47455
|
-
textPart.text = `${before}${trimmedMiddle}
|
|
47456
|
-
${trimComment}${after}`;
|
|
47457
|
-
}
|
|
47458
|
-
}
|
|
47459
|
-
}
|
|
47460
|
-
const sessionID = lastUserMessage.info?.sessionID;
|
|
47461
|
-
const taskIdMatch = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
47462
|
-
const currentTaskId = taskIdMatch ? taskIdMatch[1].trim() : null;
|
|
47463
|
-
const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
|
|
47464
|
-
const isCoderDelegation = coderDelegationPattern.test(text);
|
|
47465
|
-
const priorCoderTaskId = sessionID ? ensureAgentSession(sessionID).lastCoderDelegationTaskId ?? null : null;
|
|
47466
|
-
if (sessionID && isCoderDelegation && currentTaskId) {
|
|
47467
|
-
const session = ensureAgentSession(sessionID);
|
|
47468
|
-
session.lastCoderDelegationTaskId = currentTaskId;
|
|
47469
|
-
const fileDirPattern = /^FILE:\s*(.+)$/gm;
|
|
47470
|
-
const declaredFiles = [];
|
|
47471
|
-
for (const match of text.matchAll(fileDirPattern)) {
|
|
47472
|
-
const filePath = match[1].trim();
|
|
47473
|
-
if (filePath.length > 0 && !declaredFiles.includes(filePath)) {
|
|
47474
|
-
declaredFiles.push(filePath);
|
|
47475
|
-
}
|
|
47476
|
-
}
|
|
47477
|
-
session.declaredCoderScope = declaredFiles.length > 0 ? declaredFiles : null;
|
|
47478
|
-
try {
|
|
47479
|
-
advanceTaskState(session, currentTaskId, "coder_delegated");
|
|
47480
|
-
} catch (err2) {
|
|
47481
|
-
console.warn(`[delegation-gate] state machine warn: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
47482
|
-
}
|
|
47483
|
-
}
|
|
47484
|
-
if (sessionID && !isCoderDelegation && currentTaskId) {
|
|
47485
|
-
const session = ensureAgentSession(sessionID);
|
|
47486
|
-
if (session.architectWriteCount > 0 && session.lastCoderDelegationTaskId !== currentTaskId) {
|
|
47487
|
-
const warningText2 = `\u26A0\uFE0F DELEGATION VIOLATION: Code modifications detected for task ${currentTaskId} with zero coder delegations.
|
|
47488
|
-
Rule 1: DELEGATE all coding to coder. You do NOT write code.`;
|
|
47489
|
-
textPart.text = `${warningText2}
|
|
47490
|
-
|
|
47491
|
-
${text}`;
|
|
47492
|
-
}
|
|
47493
|
-
}
|
|
47494
|
-
{
|
|
47495
|
-
const deliberationSessionID = lastUserMessage.info?.sessionID;
|
|
47496
|
-
if (deliberationSessionID) {
|
|
47497
|
-
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(deliberationSessionID)) {} else {
|
|
47498
|
-
const deliberationSession = ensureAgentSession(deliberationSessionID);
|
|
47499
|
-
const lastGate = deliberationSession.lastGateOutcome;
|
|
47500
|
-
let preamble;
|
|
47501
|
-
if (lastGate) {
|
|
47502
|
-
const gateResult = lastGate.passed ? "PASSED" : "FAILED";
|
|
47503
|
-
const sanitizedGate = lastGate.gate.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 64);
|
|
47504
|
-
const sanitizedTaskId = lastGate.taskId.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 32);
|
|
47505
|
-
preamble = `[Last gate: ${sanitizedGate} ${gateResult} for task ${sanitizedTaskId}]
|
|
47506
|
-
[DELIBERATE: Before proceeding \u2014 what is the SINGLE next task? What gates must it pass?]`;
|
|
47507
|
-
} else {
|
|
47508
|
-
preamble = `[DELIBERATE: Identify the first task from the plan. What gates must it pass before marking complete?]`;
|
|
47509
|
-
}
|
|
47510
|
-
const currentText = textPart.text ?? "";
|
|
47511
|
-
textPart.text = `${preamble}
|
|
47512
|
-
|
|
47513
|
-
${currentText}`;
|
|
47514
|
-
}
|
|
47515
|
-
}
|
|
47516
|
-
}
|
|
47517
|
-
if (!isCoderDelegation)
|
|
47518
|
-
return;
|
|
47519
|
-
const warnings = [];
|
|
47520
|
-
if (text.length > delegationMaxChars) {
|
|
47521
|
-
warnings.push(`Delegation exceeds recommended size (${text.length} chars, limit ${delegationMaxChars}). Consider splitting into smaller tasks.`);
|
|
47522
|
-
}
|
|
47523
|
-
const fileMatches = text.match(/^FILE:/gm);
|
|
47524
|
-
if (fileMatches && fileMatches.length > 1) {
|
|
47525
|
-
warnings.push(`Multiple FILE: directives detected (${fileMatches.length}). Each coder task should target ONE file.`);
|
|
47526
|
-
}
|
|
47527
|
-
const taskMatches = text.match(/^TASK:/gm);
|
|
47528
|
-
if (taskMatches && taskMatches.length > 1) {
|
|
47529
|
-
warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
|
|
47530
|
-
}
|
|
47531
|
-
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with|while you'?re at it)[.,]?\b/gi;
|
|
47532
|
-
const batchingMatches = text.match(batchingPattern);
|
|
47533
|
-
if (batchingMatches && batchingMatches.length > 0) {
|
|
47534
|
-
warnings.push(`Batching language detected (${batchingMatches.join(", ")}). Break compound objectives into separate coder calls.`);
|
|
47535
|
-
}
|
|
47536
|
-
const taskLine = extractTaskLine(text);
|
|
47537
|
-
if (taskLine) {
|
|
47538
|
-
const andPattern = /\s+and\s+(update|add|remove|modify|refactor|implement|create|delete|fix|change|build|deploy|write|test|move|rename|extend|extract|convert|migrate|upgrade|replace)\b/i;
|
|
47539
|
-
if (andPattern.test(taskLine)) {
|
|
47540
|
-
warnings.push('TASK line contains "and" connecting separate actions');
|
|
47541
|
-
}
|
|
47542
|
-
}
|
|
47543
|
-
if (sessionID) {
|
|
47544
|
-
const delegationChain = swarmState.delegationChains.get(sessionID);
|
|
47545
|
-
if (delegationChain && delegationChain.length >= 2) {
|
|
47546
|
-
const coderIndices = [];
|
|
47547
|
-
for (let i2 = delegationChain.length - 1;i2 >= 0; i2--) {
|
|
47548
|
-
if (stripKnownSwarmPrefix(delegationChain[i2].to).includes("coder")) {
|
|
47549
|
-
coderIndices.unshift(i2);
|
|
47550
|
-
if (coderIndices.length === 2)
|
|
47551
|
-
break;
|
|
47552
|
-
}
|
|
47553
|
-
}
|
|
47554
|
-
if (coderIndices.length === 2) {
|
|
47555
|
-
const prevCoderIndex = coderIndices[0];
|
|
47556
|
-
const betweenCoders = delegationChain.slice(prevCoderIndex + 1);
|
|
47557
|
-
const hasReviewer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "reviewer");
|
|
47558
|
-
const hasTestEngineer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "test_engineer");
|
|
47559
|
-
const session = ensureAgentSession(sessionID);
|
|
47560
|
-
const priorTaskStuckAtCoder = priorCoderTaskId !== null && getTaskState(session, priorCoderTaskId) === "coder_delegated";
|
|
47561
|
-
if (!hasReviewer || !hasTestEngineer || priorTaskStuckAtCoder) {
|
|
47562
|
-
if (session.qaSkipCount >= 1) {
|
|
47563
|
-
const skippedTasks = session.qaSkipTaskIds.join(", ");
|
|
47564
|
-
throw new Error(`\uD83D\uDED1 QA GATE ENFORCEMENT: ${session.qaSkipCount + 1} consecutive coder delegations without reviewer/test_engineer. ` + `Skipped tasks: [${skippedTasks}]. ` + `DELEGATE to reviewer and test_engineer NOW before any further coder work.`);
|
|
47565
|
-
}
|
|
47566
|
-
session.qaSkipCount++;
|
|
47567
|
-
session.qaSkipTaskIds.push(currentTaskId ?? "unknown");
|
|
47568
|
-
warnings.push(`\u26A0\uFE0F PROTOCOL VIOLATION: Previous coder task completed, but QA gate was skipped. ` + `You MUST delegate to reviewer (code review) and test_engineer (test execution) ` + `before starting a new coder task. Review RULES 7-8 in your system prompt.`);
|
|
47569
|
-
}
|
|
47570
|
-
}
|
|
47571
|
-
}
|
|
47572
|
-
}
|
|
47573
|
-
if (warnings.length === 0)
|
|
47574
|
-
return;
|
|
47575
|
-
const warningLines = warnings.map((w) => `Detected signal: ${w}`);
|
|
47576
|
-
const warningText = `\u26A0\uFE0F BATCH DETECTED: Your coder delegation appears to contain multiple tasks.
|
|
47577
|
-
Rule 3: ONE task per coder call. Split this into separate delegations.
|
|
47578
|
-
${warningLines.join(`
|
|
47579
|
-
`)}`;
|
|
47580
|
-
const originalText = textPart.text ?? "";
|
|
47581
|
-
textPart.text = `${warningText}
|
|
47582
|
-
|
|
47583
|
-
${originalText}`;
|
|
47584
|
-
},
|
|
47585
|
-
toolAfter
|
|
47586
|
-
};
|
|
47587
|
-
}
|
|
47588
|
-
// src/hooks/delegation-sanitizer.ts
|
|
47589
|
-
init_utils2();
|
|
47590
|
-
import * as fs13 from "fs";
|
|
47591
|
-
var SANITIZATION_PATTERNS = [
|
|
47592
|
-
/\b\d+(st|nd|rd|th)\s+(attempt|try|time)\b/gi,
|
|
47593
|
-
/\b(5th|fifth|final|last)\s+attempt\b/gi,
|
|
47594
|
-
/attempt\s+\d+\s*\/\s*\d+/gi,
|
|
47595
|
-
/\bthis\s+is\s+(the\s+)?(5th|fifth|final|last)\b/gi,
|
|
47596
|
-
/\bwe('re|\s+are)\s+(behind|late)\b/gi,
|
|
47597
|
-
/\buser\s+is\s+waiting\b/gi,
|
|
47598
|
-
/\bship\s+(this|it)\s+now\b/gi,
|
|
47599
|
-
/\bor\s+I('ll|\s+will)\s+(stop|halt|alert)\b/gi,
|
|
47600
|
-
/\bor\s+all\s+work\s+stops\b/gi,
|
|
47601
|
-
/\bthis\s+will\s+(delay|block)\s+everything\b/gi,
|
|
47602
|
-
/\b(I'm|I\s+am)\s+(frustrated|disappointed)\b/gi
|
|
47603
|
-
];
|
|
47604
|
-
function sanitizeMessage(text, patterns = SANITIZATION_PATTERNS) {
|
|
47605
|
-
let sanitized = text;
|
|
47606
|
-
const stripped = [];
|
|
47607
|
-
for (const pattern of patterns) {
|
|
47608
|
-
const matches = sanitized.match(pattern);
|
|
47609
|
-
if (matches) {
|
|
47610
|
-
stripped.push(...matches);
|
|
47611
|
-
sanitized = sanitized.replace(pattern, "");
|
|
47612
|
-
}
|
|
47613
|
-
}
|
|
47614
|
-
sanitized = sanitized.replace(/\s+/g, " ").trim();
|
|
47615
|
-
return {
|
|
47616
|
-
sanitized,
|
|
47617
|
-
modified: stripped.length > 0,
|
|
47618
|
-
stripped
|
|
47619
|
-
};
|
|
47620
|
-
}
|
|
47621
|
-
function isGateAgentMessage(agentName) {
|
|
47622
|
-
const gateAgents = ["reviewer", "test_engineer", "critic", "test-engineer"];
|
|
47623
|
-
const normalized = agentName.toLowerCase().replace(/-/g, "_");
|
|
47624
|
-
return gateAgents.includes(normalized);
|
|
47625
|
-
}
|
|
47626
|
-
function createDelegationSanitizerHook(directory) {
|
|
47627
|
-
const hook = async (_input, output) => {
|
|
47628
|
-
const messages = output?.messages;
|
|
47629
|
-
if (!messages || !Array.isArray(messages)) {
|
|
47630
|
-
return;
|
|
47631
|
-
}
|
|
47632
|
-
for (const message of messages) {
|
|
47633
|
-
const info2 = message?.info;
|
|
47634
|
-
if (!info2)
|
|
47635
|
-
continue;
|
|
47636
|
-
const agent = info2.agent;
|
|
47637
|
-
if (!agent || !isGateAgentMessage(agent)) {
|
|
47638
|
-
continue;
|
|
47639
|
-
}
|
|
47640
|
-
if (!message.parts || !Array.isArray(message.parts)) {
|
|
47641
|
-
continue;
|
|
47642
|
-
}
|
|
47643
|
-
for (const part of message.parts) {
|
|
47644
|
-
if (part?.type !== "text" || !part.text) {
|
|
47645
|
-
continue;
|
|
47646
|
-
}
|
|
47647
|
-
const originalText = part.text;
|
|
47648
|
-
const result = sanitizeMessage(originalText);
|
|
47649
|
-
if (result.modified) {
|
|
47650
|
-
part.text = result.sanitized;
|
|
47651
|
-
try {
|
|
47652
|
-
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
47653
|
-
const event = {
|
|
47654
|
-
event: "message_sanitized",
|
|
47655
|
-
agent,
|
|
47656
|
-
original_length: originalText.length,
|
|
47657
|
-
stripped_count: result.stripped.length,
|
|
47658
|
-
stripped_patterns: result.stripped,
|
|
47659
|
-
timestamp: new Date().toISOString()
|
|
47660
|
-
};
|
|
47661
|
-
fs13.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
47662
|
-
`, "utf-8");
|
|
47663
|
-
} catch {}
|
|
47664
|
-
}
|
|
47665
|
-
}
|
|
47666
|
-
}
|
|
47667
|
-
};
|
|
47668
|
-
return safeHook(hook);
|
|
47669
|
-
}
|
|
47670
|
-
// src/hooks/delegation-tracker.ts
|
|
47671
|
-
init_constants();
|
|
47672
|
-
init_schema();
|
|
47673
|
-
function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
47674
|
-
return async (input, _output) => {
|
|
47675
|
-
const now = Date.now();
|
|
47676
|
-
if (!input.agent || input.agent === "") {
|
|
47677
|
-
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
47678
|
-
if (session2?.delegationActive) {
|
|
47679
|
-
session2.delegationActive = false;
|
|
47680
|
-
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
47681
|
-
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
47682
|
-
updateAgentEventTime(input.sessionID);
|
|
47683
|
-
} else if (!session2) {
|
|
47684
|
-
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
47685
|
-
}
|
|
47686
|
-
return;
|
|
47687
|
-
}
|
|
47688
|
-
const agentName = input.agent;
|
|
47689
|
-
const previousAgent = swarmState.activeAgent.get(input.sessionID);
|
|
47690
|
-
swarmState.activeAgent.set(input.sessionID, agentName);
|
|
47691
|
-
const strippedAgent = stripKnownSwarmPrefix(agentName);
|
|
47692
|
-
const isArchitect = strippedAgent === ORCHESTRATOR_NAME;
|
|
47693
|
-
const session = ensureAgentSession(input.sessionID, agentName);
|
|
47694
|
-
session.delegationActive = !isArchitect;
|
|
47695
|
-
recordPhaseAgentDispatch(input.sessionID, agentName);
|
|
47696
|
-
if (!isArchitect && guardrailsEnabled) {
|
|
47697
|
-
beginInvocation(input.sessionID, agentName);
|
|
47698
|
-
}
|
|
47699
|
-
const delegationTrackerEnabled = config3.hooks?.delegation_tracker === true;
|
|
47700
|
-
const delegationGateEnabled = config3.hooks?.delegation_gate !== false;
|
|
47701
|
-
if ((delegationTrackerEnabled || delegationGateEnabled) && previousAgent && previousAgent !== agentName) {
|
|
47702
|
-
const entry = {
|
|
47703
|
-
from: previousAgent,
|
|
47704
|
-
to: agentName,
|
|
47705
|
-
timestamp: now
|
|
47706
|
-
};
|
|
47707
|
-
if (!swarmState.delegationChains.has(input.sessionID)) {
|
|
47708
|
-
swarmState.delegationChains.set(input.sessionID, []);
|
|
47709
|
-
}
|
|
47710
|
-
const chain = swarmState.delegationChains.get(input.sessionID);
|
|
47711
|
-
chain?.push(entry);
|
|
47712
|
-
if (delegationTrackerEnabled) {
|
|
47713
|
-
swarmState.pendingEvents++;
|
|
47714
|
-
}
|
|
47715
|
-
}
|
|
47716
|
-
};
|
|
47717
|
-
}
|
|
47324
|
+
|
|
47718
47325
|
// src/hooks/guardrails.ts
|
|
47719
47326
|
init_constants();
|
|
47720
47327
|
init_schema();
|
|
47721
47328
|
init_manager2();
|
|
47722
47329
|
import * as path25 from "path";
|
|
47723
47330
|
init_utils();
|
|
47331
|
+
var storedInputArgs = new Map;
|
|
47332
|
+
function debugStoredArgs(action, callID, extra) {
|
|
47333
|
+
const args2 = storedInputArgs.get(callID);
|
|
47334
|
+
const argsObj = args2;
|
|
47335
|
+
const subagentType = argsObj?.subagent_type;
|
|
47336
|
+
console.log(`[swarm-debug-task] stored-args.${action} | callID=${callID} subagent_type=${subagentType ?? "(none)"}`, extra ? JSON.stringify(extra) : "");
|
|
47337
|
+
}
|
|
47338
|
+
function getStoredInputArgs(callID) {
|
|
47339
|
+
debugStoredArgs("get", callID);
|
|
47340
|
+
return storedInputArgs.get(callID);
|
|
47341
|
+
}
|
|
47342
|
+
function setStoredInputArgs(callID, args2) {
|
|
47343
|
+
debugStoredArgs("set", callID);
|
|
47344
|
+
storedInputArgs.set(callID, args2);
|
|
47345
|
+
}
|
|
47346
|
+
function deleteStoredInputArgs(callID) {
|
|
47347
|
+
debugStoredArgs("delete", callID);
|
|
47348
|
+
storedInputArgs.delete(callID);
|
|
47349
|
+
}
|
|
47724
47350
|
function extractPhaseNumber(phaseString) {
|
|
47725
47351
|
if (!phaseString)
|
|
47726
47352
|
return 1;
|
|
@@ -47854,7 +47480,6 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
|
|
|
47854
47480
|
};
|
|
47855
47481
|
}
|
|
47856
47482
|
const cfg = guardrailsConfig;
|
|
47857
|
-
const inputArgsByCallID = new Map;
|
|
47858
47483
|
return {
|
|
47859
47484
|
toolBefore: async (input, output) => {
|
|
47860
47485
|
const currentSession = swarmState.agentSessions.get(input.sessionID);
|
|
@@ -47868,495 +47493,1001 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
|
|
|
47868
47493
|
}
|
|
47869
47494
|
}
|
|
47870
47495
|
}
|
|
47871
|
-
} else if (isArchitect(input.sessionID)) {
|
|
47872
|
-
const coderDelegArgs = output.args;
|
|
47873
|
-
const coderDeleg = isAgentDelegation(input.tool, coderDelegArgs);
|
|
47874
|
-
if (coderDeleg.isDelegation && coderDeleg.targetAgent === "coder") {
|
|
47875
|
-
const coderSession = swarmState.agentSessions.get(input.sessionID);
|
|
47876
|
-
if (coderSession) {
|
|
47877
|
-
coderSession.modifiedFilesThisCoderTask = [];
|
|
47878
|
-
}
|
|
47496
|
+
} else if (isArchitect(input.sessionID)) {
|
|
47497
|
+
const coderDelegArgs = output.args;
|
|
47498
|
+
const coderDeleg = isAgentDelegation(input.tool, coderDelegArgs);
|
|
47499
|
+
if (coderDeleg.isDelegation && coderDeleg.targetAgent === "coder") {
|
|
47500
|
+
const coderSession = swarmState.agentSessions.get(input.sessionID);
|
|
47501
|
+
if (coderSession) {
|
|
47502
|
+
coderSession.modifiedFilesThisCoderTask = [];
|
|
47503
|
+
}
|
|
47504
|
+
}
|
|
47505
|
+
}
|
|
47506
|
+
if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
47507
|
+
const args2 = output.args;
|
|
47508
|
+
const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
|
|
47509
|
+
if (typeof targetPath === "string" && targetPath.length > 0) {
|
|
47510
|
+
const resolvedTarget = path25.resolve(directory, targetPath).toLowerCase();
|
|
47511
|
+
const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
|
|
47512
|
+
const planJsonPath = path25.resolve(directory, ".swarm", "plan.json").toLowerCase();
|
|
47513
|
+
if (resolvedTarget === planMdPath || resolvedTarget === planJsonPath) {
|
|
47514
|
+
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
47515
|
+
}
|
|
47516
|
+
}
|
|
47517
|
+
if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
|
|
47518
|
+
const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
|
|
47519
|
+
if (typeof patchText === "string" && patchText.length <= 1e6) {
|
|
47520
|
+
const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
|
|
47521
|
+
const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
|
|
47522
|
+
const paths = new Set;
|
|
47523
|
+
for (const match of patchText.matchAll(patchPathPattern)) {
|
|
47524
|
+
paths.add(match[1].trim());
|
|
47525
|
+
}
|
|
47526
|
+
for (const match of patchText.matchAll(diffPathPattern)) {
|
|
47527
|
+
const p = match[1].trim();
|
|
47528
|
+
if (p !== "/dev/null")
|
|
47529
|
+
paths.add(p);
|
|
47530
|
+
}
|
|
47531
|
+
const gitDiffPathPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
|
|
47532
|
+
for (const match of patchText.matchAll(gitDiffPathPattern)) {
|
|
47533
|
+
const aPath = match[1].trim();
|
|
47534
|
+
const bPath = match[2].trim();
|
|
47535
|
+
if (aPath !== "/dev/null")
|
|
47536
|
+
paths.add(aPath);
|
|
47537
|
+
if (bPath !== "/dev/null")
|
|
47538
|
+
paths.add(bPath);
|
|
47539
|
+
}
|
|
47540
|
+
const minusPathPattern = /^---\s+a\/(.+)$/gm;
|
|
47541
|
+
for (const match of patchText.matchAll(minusPathPattern)) {
|
|
47542
|
+
const p = match[1].trim();
|
|
47543
|
+
if (p !== "/dev/null")
|
|
47544
|
+
paths.add(p);
|
|
47545
|
+
}
|
|
47546
|
+
const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
47547
|
+
const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
47548
|
+
for (const match of patchText.matchAll(traditionalMinusPattern)) {
|
|
47549
|
+
const p = match[1].trim();
|
|
47550
|
+
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
47551
|
+
paths.add(p);
|
|
47552
|
+
}
|
|
47553
|
+
}
|
|
47554
|
+
for (const match of patchText.matchAll(traditionalPlusPattern)) {
|
|
47555
|
+
const p = match[1].trim();
|
|
47556
|
+
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
47557
|
+
paths.add(p);
|
|
47558
|
+
}
|
|
47559
|
+
}
|
|
47560
|
+
for (const p of paths) {
|
|
47561
|
+
const resolvedP = path25.resolve(directory, p);
|
|
47562
|
+
const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
|
|
47563
|
+
const planJsonPath = path25.resolve(directory, ".swarm", "plan.json").toLowerCase();
|
|
47564
|
+
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
47565
|
+
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
47566
|
+
}
|
|
47567
|
+
if (isOutsideSwarmDir(p, directory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
47568
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
47569
|
+
if (session2) {
|
|
47570
|
+
session2.architectWriteCount++;
|
|
47571
|
+
warn("Architect direct code edit detected via apply_patch", {
|
|
47572
|
+
tool: input.tool,
|
|
47573
|
+
sessionID: input.sessionID,
|
|
47574
|
+
targetPath: p,
|
|
47575
|
+
writeCount: session2.architectWriteCount
|
|
47576
|
+
});
|
|
47577
|
+
}
|
|
47578
|
+
break;
|
|
47579
|
+
}
|
|
47580
|
+
}
|
|
47581
|
+
}
|
|
47582
|
+
}
|
|
47583
|
+
if (typeof targetPath === "string" && targetPath.length > 0 && isOutsideSwarmDir(targetPath, directory) && isSourceCodePath(path25.relative(directory, path25.resolve(directory, targetPath)))) {
|
|
47584
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
47585
|
+
if (session2) {
|
|
47586
|
+
session2.architectWriteCount++;
|
|
47587
|
+
warn("Architect direct code edit detected", {
|
|
47588
|
+
tool: input.tool,
|
|
47589
|
+
sessionID: input.sessionID,
|
|
47590
|
+
targetPath,
|
|
47591
|
+
writeCount: session2.architectWriteCount
|
|
47592
|
+
});
|
|
47593
|
+
if (session2.lastGateFailure && Date.now() - session2.lastGateFailure.timestamp < 120000) {
|
|
47594
|
+
const failedGate = session2.lastGateFailure.tool;
|
|
47595
|
+
const failedTaskId = session2.lastGateFailure.taskId;
|
|
47596
|
+
warn("Self-fix after gate failure detected", {
|
|
47597
|
+
failedGate,
|
|
47598
|
+
failedTaskId,
|
|
47599
|
+
currentTool: input.tool,
|
|
47600
|
+
sessionID: input.sessionID
|
|
47601
|
+
});
|
|
47602
|
+
session2.selfFixAttempted = true;
|
|
47603
|
+
}
|
|
47604
|
+
}
|
|
47605
|
+
}
|
|
47606
|
+
}
|
|
47607
|
+
const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
|
|
47608
|
+
const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
|
|
47609
|
+
if (strippedAgent === ORCHESTRATOR_NAME) {
|
|
47610
|
+
return;
|
|
47611
|
+
}
|
|
47612
|
+
const existingSession = swarmState.agentSessions.get(input.sessionID);
|
|
47613
|
+
if (existingSession) {
|
|
47614
|
+
const sessionAgent = stripKnownSwarmPrefix(existingSession.agentName);
|
|
47615
|
+
if (sessionAgent === ORCHESTRATOR_NAME) {
|
|
47616
|
+
return;
|
|
47617
|
+
}
|
|
47618
|
+
}
|
|
47619
|
+
const agentName = swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME;
|
|
47620
|
+
const session = ensureAgentSession(input.sessionID, agentName);
|
|
47621
|
+
const resolvedName = stripKnownSwarmPrefix(session.agentName);
|
|
47622
|
+
if (resolvedName === ORCHESTRATOR_NAME) {
|
|
47623
|
+
return;
|
|
47624
|
+
}
|
|
47625
|
+
const agentConfig = resolveGuardrailsConfig(cfg, session.agentName);
|
|
47626
|
+
if (agentConfig.max_duration_minutes === 0 && agentConfig.max_tool_calls === 0) {
|
|
47627
|
+
return;
|
|
47628
|
+
}
|
|
47629
|
+
if (!getActiveWindow(input.sessionID)) {
|
|
47630
|
+
const fallbackAgent = swarmState.activeAgent.get(input.sessionID) ?? session.agentName;
|
|
47631
|
+
const stripped = stripKnownSwarmPrefix(fallbackAgent);
|
|
47632
|
+
if (stripped !== ORCHESTRATOR_NAME) {
|
|
47633
|
+
beginInvocation(input.sessionID, fallbackAgent);
|
|
47634
|
+
}
|
|
47635
|
+
}
|
|
47636
|
+
const window2 = getActiveWindow(input.sessionID);
|
|
47637
|
+
if (!window2) {
|
|
47638
|
+
return;
|
|
47639
|
+
}
|
|
47640
|
+
if (window2.hardLimitHit) {
|
|
47641
|
+
throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
|
|
47642
|
+
}
|
|
47643
|
+
window2.toolCalls++;
|
|
47644
|
+
const hash3 = hashArgs(output.args);
|
|
47645
|
+
window2.recentToolCalls.push({
|
|
47646
|
+
tool: input.tool,
|
|
47647
|
+
argsHash: hash3,
|
|
47648
|
+
timestamp: Date.now()
|
|
47649
|
+
});
|
|
47650
|
+
if (window2.recentToolCalls.length > 20) {
|
|
47651
|
+
window2.recentToolCalls.shift();
|
|
47652
|
+
}
|
|
47653
|
+
let repetitionCount = 0;
|
|
47654
|
+
if (window2.recentToolCalls.length > 0) {
|
|
47655
|
+
const lastEntry = window2.recentToolCalls[window2.recentToolCalls.length - 1];
|
|
47656
|
+
for (let i2 = window2.recentToolCalls.length - 1;i2 >= 0; i2--) {
|
|
47657
|
+
const entry = window2.recentToolCalls[i2];
|
|
47658
|
+
if (entry.tool === lastEntry.tool && entry.argsHash === lastEntry.argsHash) {
|
|
47659
|
+
repetitionCount++;
|
|
47660
|
+
} else {
|
|
47661
|
+
break;
|
|
47662
|
+
}
|
|
47663
|
+
}
|
|
47664
|
+
}
|
|
47665
|
+
const elapsedMinutes = (Date.now() - window2.startedAtMs) / 60000;
|
|
47666
|
+
if (agentConfig.max_tool_calls > 0 && window2.toolCalls >= agentConfig.max_tool_calls) {
|
|
47667
|
+
window2.hardLimitHit = true;
|
|
47668
|
+
warn("Circuit breaker: tool call limit hit", {
|
|
47669
|
+
sessionID: input.sessionID,
|
|
47670
|
+
agentName: window2.agentName,
|
|
47671
|
+
invocationId: window2.id,
|
|
47672
|
+
windowKey: `${window2.agentName}:${window2.id}`,
|
|
47673
|
+
resolvedMaxCalls: agentConfig.max_tool_calls,
|
|
47674
|
+
currentCalls: window2.toolCalls
|
|
47675
|
+
});
|
|
47676
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Tool calls exhausted (${window2.toolCalls}/${agentConfig.max_tool_calls}). Finish the current operation and return your progress summary.`);
|
|
47677
|
+
}
|
|
47678
|
+
if (agentConfig.max_duration_minutes > 0 && elapsedMinutes >= agentConfig.max_duration_minutes) {
|
|
47679
|
+
window2.hardLimitHit = true;
|
|
47680
|
+
warn("Circuit breaker: duration limit hit", {
|
|
47681
|
+
sessionID: input.sessionID,
|
|
47682
|
+
agentName: window2.agentName,
|
|
47683
|
+
invocationId: window2.id,
|
|
47684
|
+
windowKey: `${window2.agentName}:${window2.id}`,
|
|
47685
|
+
resolvedMaxMinutes: agentConfig.max_duration_minutes,
|
|
47686
|
+
elapsedMinutes: Math.floor(elapsedMinutes)
|
|
47687
|
+
});
|
|
47688
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Duration exhausted (${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min). Finish the current operation and return your progress summary.`);
|
|
47689
|
+
}
|
|
47690
|
+
if (repetitionCount >= agentConfig.max_repetitions) {
|
|
47691
|
+
window2.hardLimitHit = true;
|
|
47692
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Repeated the same tool call ${repetitionCount} times. This suggests a loop. Return your progress summary.`);
|
|
47693
|
+
}
|
|
47694
|
+
if (window2.consecutiveErrors >= agentConfig.max_consecutive_errors) {
|
|
47695
|
+
window2.hardLimitHit = true;
|
|
47696
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${window2.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong.`);
|
|
47697
|
+
}
|
|
47698
|
+
const idleMinutes = (Date.now() - window2.lastSuccessTimeMs) / 60000;
|
|
47699
|
+
if (idleMinutes >= agentConfig.idle_timeout_minutes) {
|
|
47700
|
+
window2.hardLimitHit = true;
|
|
47701
|
+
warn("Circuit breaker: idle timeout hit", {
|
|
47702
|
+
sessionID: input.sessionID,
|
|
47703
|
+
agentName: window2.agentName,
|
|
47704
|
+
invocationId: window2.id,
|
|
47705
|
+
windowKey: `${window2.agentName}:${window2.id}`,
|
|
47706
|
+
idleTimeoutMinutes: agentConfig.idle_timeout_minutes,
|
|
47707
|
+
idleMinutes: Math.floor(idleMinutes)
|
|
47708
|
+
});
|
|
47709
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: No successful tool call for ${Math.floor(idleMinutes)} minutes (idle timeout: ${agentConfig.idle_timeout_minutes} min). This suggests the agent may be stuck. Return your progress summary.`);
|
|
47710
|
+
}
|
|
47711
|
+
if (!window2.warningIssued) {
|
|
47712
|
+
const toolPct = agentConfig.max_tool_calls > 0 ? window2.toolCalls / agentConfig.max_tool_calls : 0;
|
|
47713
|
+
const durationPct = agentConfig.max_duration_minutes > 0 ? elapsedMinutes / agentConfig.max_duration_minutes : 0;
|
|
47714
|
+
const repPct = repetitionCount / agentConfig.max_repetitions;
|
|
47715
|
+
const errorPct = window2.consecutiveErrors / agentConfig.max_consecutive_errors;
|
|
47716
|
+
const reasons = [];
|
|
47717
|
+
if (agentConfig.max_tool_calls > 0 && toolPct >= agentConfig.warning_threshold) {
|
|
47718
|
+
reasons.push(`tool calls ${window2.toolCalls}/${agentConfig.max_tool_calls}`);
|
|
47719
|
+
}
|
|
47720
|
+
if (durationPct >= agentConfig.warning_threshold) {
|
|
47721
|
+
reasons.push(`duration ${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min`);
|
|
47722
|
+
}
|
|
47723
|
+
if (repPct >= agentConfig.warning_threshold) {
|
|
47724
|
+
reasons.push(`repetitions ${repetitionCount}/${agentConfig.max_repetitions}`);
|
|
47725
|
+
}
|
|
47726
|
+
if (errorPct >= agentConfig.warning_threshold) {
|
|
47727
|
+
reasons.push(`errors ${window2.consecutiveErrors}/${agentConfig.max_consecutive_errors}`);
|
|
47728
|
+
}
|
|
47729
|
+
if (reasons.length > 0) {
|
|
47730
|
+
window2.warningIssued = true;
|
|
47731
|
+
window2.warningReason = reasons.join(", ");
|
|
47879
47732
|
}
|
|
47880
47733
|
}
|
|
47881
|
-
|
|
47882
|
-
|
|
47883
|
-
|
|
47884
|
-
|
|
47885
|
-
|
|
47886
|
-
|
|
47887
|
-
const
|
|
47888
|
-
if (
|
|
47889
|
-
|
|
47734
|
+
setStoredInputArgs(input.callID, output.args);
|
|
47735
|
+
},
|
|
47736
|
+
toolAfter: async (input, output) => {
|
|
47737
|
+
const session = swarmState.agentSessions.get(input.sessionID);
|
|
47738
|
+
if (session) {
|
|
47739
|
+
if (isGateTool(input.tool)) {
|
|
47740
|
+
const taskId = getCurrentTaskId(input.sessionID);
|
|
47741
|
+
if (!session.gateLog.has(taskId)) {
|
|
47742
|
+
session.gateLog.set(taskId, new Set);
|
|
47890
47743
|
}
|
|
47891
|
-
|
|
47892
|
-
|
|
47893
|
-
const
|
|
47894
|
-
if (
|
|
47895
|
-
|
|
47896
|
-
|
|
47897
|
-
|
|
47898
|
-
|
|
47899
|
-
|
|
47900
|
-
|
|
47901
|
-
|
|
47902
|
-
|
|
47903
|
-
|
|
47904
|
-
|
|
47905
|
-
|
|
47906
|
-
|
|
47907
|
-
|
|
47908
|
-
|
|
47909
|
-
|
|
47910
|
-
if (aPath !== "/dev/null")
|
|
47911
|
-
paths.add(aPath);
|
|
47912
|
-
if (bPath !== "/dev/null")
|
|
47913
|
-
paths.add(bPath);
|
|
47914
|
-
}
|
|
47915
|
-
const minusPathPattern = /^---\s+a\/(.+)$/gm;
|
|
47916
|
-
for (const match of patchText.matchAll(minusPathPattern)) {
|
|
47917
|
-
const p = match[1].trim();
|
|
47918
|
-
if (p !== "/dev/null")
|
|
47919
|
-
paths.add(p);
|
|
47920
|
-
}
|
|
47921
|
-
const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
47922
|
-
const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
47923
|
-
for (const match of patchText.matchAll(traditionalMinusPattern)) {
|
|
47924
|
-
const p = match[1].trim();
|
|
47925
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
47926
|
-
paths.add(p);
|
|
47927
|
-
}
|
|
47928
|
-
}
|
|
47929
|
-
for (const match of patchText.matchAll(traditionalPlusPattern)) {
|
|
47930
|
-
const p = match[1].trim();
|
|
47931
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
47932
|
-
paths.add(p);
|
|
47933
|
-
}
|
|
47934
|
-
}
|
|
47935
|
-
for (const p of paths) {
|
|
47936
|
-
const resolvedP = path25.resolve(directory, p);
|
|
47937
|
-
const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
|
|
47938
|
-
const planJsonPath = path25.resolve(directory, ".swarm", "plan.json").toLowerCase();
|
|
47939
|
-
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
47940
|
-
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
47744
|
+
session.gateLog.get(taskId)?.add(input.tool);
|
|
47745
|
+
const outputStr = typeof output.output === "string" ? output.output : "";
|
|
47746
|
+
const hasFailure = output.output === null || output.output === undefined || outputStr.includes("FAIL") || outputStr.includes("error") || outputStr.toLowerCase().includes("gates_passed: false");
|
|
47747
|
+
if (hasFailure) {
|
|
47748
|
+
session.lastGateFailure = {
|
|
47749
|
+
tool: input.tool,
|
|
47750
|
+
taskId,
|
|
47751
|
+
timestamp: Date.now()
|
|
47752
|
+
};
|
|
47753
|
+
} else {
|
|
47754
|
+
session.lastGateFailure = null;
|
|
47755
|
+
if (input.tool === "pre_check_batch") {
|
|
47756
|
+
const successStr = typeof output.output === "string" ? output.output : "";
|
|
47757
|
+
let isPassed = false;
|
|
47758
|
+
try {
|
|
47759
|
+
const result = JSON.parse(successStr);
|
|
47760
|
+
isPassed = result.gates_passed === true;
|
|
47761
|
+
} catch {
|
|
47762
|
+
isPassed = false;
|
|
47941
47763
|
}
|
|
47942
|
-
if (
|
|
47943
|
-
|
|
47944
|
-
|
|
47945
|
-
|
|
47946
|
-
warn("
|
|
47947
|
-
|
|
47948
|
-
|
|
47949
|
-
targetPath: p,
|
|
47950
|
-
writeCount: session2.architectWriteCount
|
|
47764
|
+
if (isPassed && session.currentTaskId) {
|
|
47765
|
+
try {
|
|
47766
|
+
advanceTaskState(session, session.currentTaskId, "pre_check_passed");
|
|
47767
|
+
} catch (err2) {
|
|
47768
|
+
warn("Failed to advance task state after pre_check_batch pass", {
|
|
47769
|
+
taskId: session.currentTaskId,
|
|
47770
|
+
error: String(err2)
|
|
47951
47771
|
});
|
|
47952
47772
|
}
|
|
47953
|
-
break;
|
|
47954
47773
|
}
|
|
47955
47774
|
}
|
|
47956
47775
|
}
|
|
47957
47776
|
}
|
|
47958
|
-
|
|
47959
|
-
|
|
47960
|
-
|
|
47961
|
-
|
|
47962
|
-
|
|
47963
|
-
|
|
47964
|
-
|
|
47965
|
-
|
|
47966
|
-
|
|
47967
|
-
}
|
|
47968
|
-
|
|
47969
|
-
|
|
47970
|
-
|
|
47971
|
-
|
|
47972
|
-
|
|
47973
|
-
|
|
47974
|
-
|
|
47975
|
-
|
|
47976
|
-
|
|
47977
|
-
|
|
47777
|
+
const inputArgs = getStoredInputArgs(input.callID);
|
|
47778
|
+
const delegation = isAgentDelegation(input.tool, inputArgs);
|
|
47779
|
+
if (delegation.isDelegation && (delegation.targetAgent === "reviewer" || delegation.targetAgent === "test_engineer")) {
|
|
47780
|
+
let currentPhase = 1;
|
|
47781
|
+
try {
|
|
47782
|
+
const plan = await loadPlan(directory);
|
|
47783
|
+
if (plan) {
|
|
47784
|
+
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
47785
|
+
currentPhase = extractPhaseNumber(phaseString);
|
|
47786
|
+
}
|
|
47787
|
+
} catch {}
|
|
47788
|
+
const count = session.reviewerCallCount.get(currentPhase) ?? 0;
|
|
47789
|
+
session.reviewerCallCount.set(currentPhase, count + 1);
|
|
47790
|
+
}
|
|
47791
|
+
if (delegation.isDelegation && delegation.targetAgent === "coder" && session.lastCoderDelegationTaskId) {
|
|
47792
|
+
session.currentTaskId = session.lastCoderDelegationTaskId;
|
|
47793
|
+
session.partialGateWarningsIssuedForTask?.delete(session.currentTaskId);
|
|
47794
|
+
if (session.declaredCoderScope !== null) {
|
|
47795
|
+
const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope));
|
|
47796
|
+
if (undeclaredFiles.length > 2) {
|
|
47797
|
+
const safeTaskId = String(session.currentTaskId ?? "").replace(/[\r\n\t]/g, "_");
|
|
47798
|
+
session.lastScopeViolation = `Scope violation for task ${safeTaskId}: ` + `${undeclaredFiles.length} undeclared files modified: ` + undeclaredFiles.join(", ");
|
|
47799
|
+
session.scopeViolationDetected = true;
|
|
47800
|
+
}
|
|
47801
|
+
}
|
|
47802
|
+
session.modifiedFilesThisCoderTask = [];
|
|
47803
|
+
}
|
|
47804
|
+
}
|
|
47805
|
+
const window2 = getActiveWindow(input.sessionID);
|
|
47806
|
+
if (!window2)
|
|
47807
|
+
return;
|
|
47808
|
+
const hasError = output.output === null || output.output === undefined;
|
|
47809
|
+
if (hasError) {
|
|
47810
|
+
window2.consecutiveErrors++;
|
|
47811
|
+
} else {
|
|
47812
|
+
window2.consecutiveErrors = 0;
|
|
47813
|
+
window2.lastSuccessTimeMs = Date.now();
|
|
47814
|
+
}
|
|
47815
|
+
},
|
|
47816
|
+
messagesTransform: async (_input, output) => {
|
|
47817
|
+
const messages = output.messages;
|
|
47818
|
+
if (!messages || messages.length === 0) {
|
|
47819
|
+
return;
|
|
47820
|
+
}
|
|
47821
|
+
const lastMessage = messages[messages.length - 1];
|
|
47822
|
+
const sessionId = lastMessage.info?.sessionID;
|
|
47823
|
+
if (!sessionId) {
|
|
47824
|
+
return;
|
|
47825
|
+
}
|
|
47826
|
+
{
|
|
47827
|
+
const { modelID } = extractModelInfo(messages);
|
|
47828
|
+
if (modelID && isLowCapabilityModel(modelID)) {
|
|
47829
|
+
for (const msg of messages) {
|
|
47830
|
+
if (msg.info?.role !== "system")
|
|
47831
|
+
continue;
|
|
47832
|
+
for (const part of msg.parts) {
|
|
47833
|
+
try {
|
|
47834
|
+
if (part == null)
|
|
47835
|
+
continue;
|
|
47836
|
+
if (part.type !== "text" || typeof part.text !== "string")
|
|
47837
|
+
continue;
|
|
47838
|
+
if (!part.text.includes("<!-- BEHAVIORAL_GUIDANCE_START -->"))
|
|
47839
|
+
continue;
|
|
47840
|
+
part.text = part.text.replace(/<!--\s*BEHAVIORAL_GUIDANCE_START\s*-->[\s\S]*?<!--\s*BEHAVIORAL_GUIDANCE_END\s*-->/g, "[Enforcement: programmatic gates active]");
|
|
47841
|
+
} catch {}
|
|
47842
|
+
}
|
|
47843
|
+
}
|
|
47844
|
+
}
|
|
47845
|
+
}
|
|
47846
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
47847
|
+
const activeAgent = swarmState.activeAgent.get(sessionId);
|
|
47848
|
+
const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
|
|
47849
|
+
if (isArchitectSession && session && session.architectWriteCount > 0) {
|
|
47850
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47851
|
+
if (textPart2) {
|
|
47852
|
+
textPart2.text = `\u26A0\uFE0F SELF-CODING DETECTED: You have used ${session.architectWriteCount} write-class tool(s) directly on non-.swarm/ files.
|
|
47853
|
+
` + `Rule 1 requires ALL coding to be delegated to @coder.
|
|
47854
|
+
` + `If you have not exhausted QA_RETRY_LIMIT coder failures on this task, STOP and delegate.
|
|
47855
|
+
|
|
47856
|
+
` + textPart2.text;
|
|
47857
|
+
}
|
|
47858
|
+
}
|
|
47859
|
+
if (isArchitectSession && session && session.selfFixAttempted && session.lastGateFailure && Date.now() - session.lastGateFailure.timestamp < 120000) {
|
|
47860
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47861
|
+
if (textPart2 && !textPart2.text.includes("SELF-FIX DETECTED")) {
|
|
47862
|
+
textPart2.text = `\u26A0\uFE0F SELF-FIX DETECTED: Gate '${session.lastGateFailure.tool}' failed on task ${session.lastGateFailure.taskId}.
|
|
47863
|
+
` + `You are now using a write tool instead of delegating to @coder.
|
|
47864
|
+
` + `GATE FAILURE RESPONSE RULES require: return to coder with structured rejection.
|
|
47865
|
+
` + `Do NOT fix gate failures yourself.
|
|
47866
|
+
|
|
47867
|
+
` + textPart2.text;
|
|
47868
|
+
session.selfFixAttempted = false;
|
|
47869
|
+
}
|
|
47870
|
+
}
|
|
47871
|
+
const isArchitectSessionForGates = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
|
|
47872
|
+
if (isArchitectSessionForGates && session) {
|
|
47873
|
+
const taskId = getCurrentTaskId(sessionId);
|
|
47874
|
+
if (!session.partialGateWarningsIssuedForTask.has(taskId)) {
|
|
47875
|
+
const gates = session.gateLog.get(taskId);
|
|
47876
|
+
const REQUIRED_GATES = [
|
|
47877
|
+
"diff",
|
|
47878
|
+
"syntax_check",
|
|
47879
|
+
"placeholder_scan",
|
|
47880
|
+
"lint",
|
|
47881
|
+
"pre_check_batch"
|
|
47882
|
+
];
|
|
47883
|
+
const missingGates = [];
|
|
47884
|
+
if (!gates) {
|
|
47885
|
+
missingGates.push(...REQUIRED_GATES);
|
|
47886
|
+
} else {
|
|
47887
|
+
for (const gate of REQUIRED_GATES) {
|
|
47888
|
+
if (!gates.has(gate)) {
|
|
47889
|
+
missingGates.push(gate);
|
|
47890
|
+
}
|
|
47891
|
+
}
|
|
47892
|
+
}
|
|
47893
|
+
let currentPhaseForCheck = 1;
|
|
47894
|
+
try {
|
|
47895
|
+
const plan = await loadPlan(directory);
|
|
47896
|
+
if (plan) {
|
|
47897
|
+
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
47898
|
+
currentPhaseForCheck = extractPhaseNumber(phaseString);
|
|
47899
|
+
}
|
|
47900
|
+
} catch {}
|
|
47901
|
+
const hasReviewerDelegation = (session.reviewerCallCount.get(currentPhaseForCheck) ?? 0) > 0;
|
|
47902
|
+
if (missingGates.length > 0 || !hasReviewerDelegation) {
|
|
47903
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47904
|
+
if (textPart2 && !textPart2.text.includes("PARTIAL GATE VIOLATION")) {
|
|
47905
|
+
const missing = [...missingGates];
|
|
47906
|
+
if (!hasReviewerDelegation) {
|
|
47907
|
+
missing.push("reviewer/test_engineer (no delegations this phase)");
|
|
47908
|
+
}
|
|
47909
|
+
session.partialGateWarningsIssuedForTask.add(taskId);
|
|
47910
|
+
textPart2.text = `\u26A0\uFE0F PARTIAL GATE VIOLATION: Task may be marked complete but missing gates: [${missing.join(", ")}].
|
|
47911
|
+
` + `The QA gate is ALL steps or NONE. Revert any \u2713 marks and run the missing gates.
|
|
47912
|
+
|
|
47913
|
+
` + textPart2.text;
|
|
47978
47914
|
}
|
|
47979
47915
|
}
|
|
47980
47916
|
}
|
|
47981
47917
|
}
|
|
47982
|
-
|
|
47983
|
-
|
|
47984
|
-
|
|
47985
|
-
|
|
47986
|
-
|
|
47987
|
-
|
|
47988
|
-
|
|
47989
|
-
|
|
47990
|
-
if (sessionAgent === ORCHESTRATOR_NAME) {
|
|
47991
|
-
return;
|
|
47918
|
+
if (isArchitectSessionForGates && session && session.scopeViolationDetected) {
|
|
47919
|
+
session.scopeViolationDetected = false;
|
|
47920
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47921
|
+
if (textPart2 && session.lastScopeViolation) {
|
|
47922
|
+
textPart2.text = `\u26A0\uFE0F SCOPE VIOLATION: ${session.lastScopeViolation}
|
|
47923
|
+
` + `Only modify files within your declared scope. Request scope expansion from architect if needed.
|
|
47924
|
+
|
|
47925
|
+
` + textPart2.text;
|
|
47992
47926
|
}
|
|
47993
47927
|
}
|
|
47994
|
-
|
|
47995
|
-
|
|
47996
|
-
|
|
47997
|
-
|
|
47998
|
-
|
|
47928
|
+
if (isArchitectSessionForGates && session && session.catastrophicPhaseWarnings) {
|
|
47929
|
+
try {
|
|
47930
|
+
const plan = await loadPlan(directory);
|
|
47931
|
+
if (plan?.phases) {
|
|
47932
|
+
for (const phase of plan.phases) {
|
|
47933
|
+
if (phase.status === "complete") {
|
|
47934
|
+
const phaseNum = phase.id;
|
|
47935
|
+
if (!session.catastrophicPhaseWarnings.has(phaseNum)) {
|
|
47936
|
+
const reviewerCount = session.reviewerCallCount.get(phaseNum) ?? 0;
|
|
47937
|
+
if (reviewerCount === 0) {
|
|
47938
|
+
session.catastrophicPhaseWarnings.add(phaseNum);
|
|
47939
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47940
|
+
if (textPart2 && !textPart2.text.includes("CATASTROPHIC VIOLATION")) {
|
|
47941
|
+
textPart2.text = `[CATASTROPHIC VIOLATION: Phase ${phaseNum} completed with ZERO reviewer delegations.` + ` Every coder task requires reviewer approval. Recommend retrospective review of all Phase ${phaseNum} tasks.]
|
|
47942
|
+
|
|
47943
|
+
` + textPart2.text;
|
|
47944
|
+
}
|
|
47945
|
+
break;
|
|
47946
|
+
}
|
|
47947
|
+
}
|
|
47948
|
+
}
|
|
47949
|
+
}
|
|
47950
|
+
}
|
|
47951
|
+
} catch {}
|
|
47999
47952
|
}
|
|
48000
|
-
const
|
|
48001
|
-
if (
|
|
47953
|
+
const targetWindow = getActiveWindow(sessionId);
|
|
47954
|
+
if (!targetWindow || !targetWindow.warningIssued && !targetWindow.hardLimitHit) {
|
|
48002
47955
|
return;
|
|
48003
47956
|
}
|
|
48004
|
-
|
|
48005
|
-
|
|
48006
|
-
const stripped = stripKnownSwarmPrefix(fallbackAgent);
|
|
48007
|
-
if (stripped !== ORCHESTRATOR_NAME) {
|
|
48008
|
-
beginInvocation(input.sessionID, fallbackAgent);
|
|
48009
|
-
}
|
|
48010
|
-
}
|
|
48011
|
-
const window2 = getActiveWindow(input.sessionID);
|
|
48012
|
-
if (!window2) {
|
|
47957
|
+
const textPart = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
47958
|
+
if (!textPart) {
|
|
48013
47959
|
return;
|
|
48014
47960
|
}
|
|
48015
|
-
if (
|
|
48016
|
-
|
|
48017
|
-
|
|
48018
|
-
|
|
48019
|
-
|
|
48020
|
-
|
|
48021
|
-
|
|
48022
|
-
|
|
48023
|
-
|
|
48024
|
-
});
|
|
48025
|
-
if (window2.recentToolCalls.length > 20) {
|
|
48026
|
-
window2.recentToolCalls.shift();
|
|
48027
|
-
}
|
|
48028
|
-
let repetitionCount = 0;
|
|
48029
|
-
if (window2.recentToolCalls.length > 0) {
|
|
48030
|
-
const lastEntry = window2.recentToolCalls[window2.recentToolCalls.length - 1];
|
|
48031
|
-
for (let i2 = window2.recentToolCalls.length - 1;i2 >= 0; i2--) {
|
|
48032
|
-
const entry = window2.recentToolCalls[i2];
|
|
48033
|
-
if (entry.tool === lastEntry.tool && entry.argsHash === lastEntry.argsHash) {
|
|
48034
|
-
repetitionCount++;
|
|
48035
|
-
} else {
|
|
48036
|
-
break;
|
|
48037
|
-
}
|
|
48038
|
-
}
|
|
48039
|
-
}
|
|
48040
|
-
const elapsedMinutes = (Date.now() - window2.startedAtMs) / 60000;
|
|
48041
|
-
if (agentConfig.max_tool_calls > 0 && window2.toolCalls >= agentConfig.max_tool_calls) {
|
|
48042
|
-
window2.hardLimitHit = true;
|
|
48043
|
-
warn("Circuit breaker: tool call limit hit", {
|
|
48044
|
-
sessionID: input.sessionID,
|
|
48045
|
-
agentName: window2.agentName,
|
|
48046
|
-
invocationId: window2.id,
|
|
48047
|
-
windowKey: `${window2.agentName}:${window2.id}`,
|
|
48048
|
-
resolvedMaxCalls: agentConfig.max_tool_calls,
|
|
48049
|
-
currentCalls: window2.toolCalls
|
|
48050
|
-
});
|
|
48051
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Tool calls exhausted (${window2.toolCalls}/${agentConfig.max_tool_calls}). Finish the current operation and return your progress summary.`);
|
|
48052
|
-
}
|
|
48053
|
-
if (agentConfig.max_duration_minutes > 0 && elapsedMinutes >= agentConfig.max_duration_minutes) {
|
|
48054
|
-
window2.hardLimitHit = true;
|
|
48055
|
-
warn("Circuit breaker: duration limit hit", {
|
|
48056
|
-
sessionID: input.sessionID,
|
|
48057
|
-
agentName: window2.agentName,
|
|
48058
|
-
invocationId: window2.id,
|
|
48059
|
-
windowKey: `${window2.agentName}:${window2.id}`,
|
|
48060
|
-
resolvedMaxMinutes: agentConfig.max_duration_minutes,
|
|
48061
|
-
elapsedMinutes: Math.floor(elapsedMinutes)
|
|
48062
|
-
});
|
|
48063
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Duration exhausted (${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min). Finish the current operation and return your progress summary.`);
|
|
48064
|
-
}
|
|
48065
|
-
if (repetitionCount >= agentConfig.max_repetitions) {
|
|
48066
|
-
window2.hardLimitHit = true;
|
|
48067
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Repeated the same tool call ${repetitionCount} times. This suggests a loop. Return your progress summary.`);
|
|
48068
|
-
}
|
|
48069
|
-
if (window2.consecutiveErrors >= agentConfig.max_consecutive_errors) {
|
|
48070
|
-
window2.hardLimitHit = true;
|
|
48071
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${window2.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong.`);
|
|
48072
|
-
}
|
|
48073
|
-
const idleMinutes = (Date.now() - window2.lastSuccessTimeMs) / 60000;
|
|
48074
|
-
if (idleMinutes >= agentConfig.idle_timeout_minutes) {
|
|
48075
|
-
window2.hardLimitHit = true;
|
|
48076
|
-
warn("Circuit breaker: idle timeout hit", {
|
|
48077
|
-
sessionID: input.sessionID,
|
|
48078
|
-
agentName: window2.agentName,
|
|
48079
|
-
invocationId: window2.id,
|
|
48080
|
-
windowKey: `${window2.agentName}:${window2.id}`,
|
|
48081
|
-
idleTimeoutMinutes: agentConfig.idle_timeout_minutes,
|
|
48082
|
-
idleMinutes: Math.floor(idleMinutes)
|
|
48083
|
-
});
|
|
48084
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: No successful tool call for ${Math.floor(idleMinutes)} minutes (idle timeout: ${agentConfig.idle_timeout_minutes} min). This suggests the agent may be stuck. Return your progress summary.`);
|
|
48085
|
-
}
|
|
48086
|
-
if (!window2.warningIssued) {
|
|
48087
|
-
const toolPct = agentConfig.max_tool_calls > 0 ? window2.toolCalls / agentConfig.max_tool_calls : 0;
|
|
48088
|
-
const durationPct = agentConfig.max_duration_minutes > 0 ? elapsedMinutes / agentConfig.max_duration_minutes : 0;
|
|
48089
|
-
const repPct = repetitionCount / agentConfig.max_repetitions;
|
|
48090
|
-
const errorPct = window2.consecutiveErrors / agentConfig.max_consecutive_errors;
|
|
48091
|
-
const reasons = [];
|
|
48092
|
-
if (agentConfig.max_tool_calls > 0 && toolPct >= agentConfig.warning_threshold) {
|
|
48093
|
-
reasons.push(`tool calls ${window2.toolCalls}/${agentConfig.max_tool_calls}`);
|
|
48094
|
-
}
|
|
48095
|
-
if (durationPct >= agentConfig.warning_threshold) {
|
|
48096
|
-
reasons.push(`duration ${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min`);
|
|
48097
|
-
}
|
|
48098
|
-
if (repPct >= agentConfig.warning_threshold) {
|
|
48099
|
-
reasons.push(`repetitions ${repetitionCount}/${agentConfig.max_repetitions}`);
|
|
48100
|
-
}
|
|
48101
|
-
if (errorPct >= agentConfig.warning_threshold) {
|
|
48102
|
-
reasons.push(`errors ${window2.consecutiveErrors}/${agentConfig.max_consecutive_errors}`);
|
|
48103
|
-
}
|
|
48104
|
-
if (reasons.length > 0) {
|
|
48105
|
-
window2.warningIssued = true;
|
|
48106
|
-
window2.warningReason = reasons.join(", ");
|
|
48107
|
-
}
|
|
47961
|
+
if (targetWindow.hardLimitHit) {
|
|
47962
|
+
textPart.text = `[\uD83D\uDED1 LIMIT REACHED: Your resource budget is exhausted. Do not make additional tool calls. Return a summary of your progress and any remaining work.]
|
|
47963
|
+
|
|
47964
|
+
` + textPart.text;
|
|
47965
|
+
} else if (targetWindow.warningIssued) {
|
|
47966
|
+
const reasonSuffix = targetWindow.warningReason ? ` (${targetWindow.warningReason})` : "";
|
|
47967
|
+
textPart.text = `[\u26A0\uFE0F APPROACHING LIMITS${reasonSuffix}: You still have capacity to finish your current step. Complete what you're working on, then return your results.]
|
|
47968
|
+
|
|
47969
|
+
` + textPart.text;
|
|
48108
47970
|
}
|
|
48109
|
-
|
|
48110
|
-
|
|
48111
|
-
|
|
48112
|
-
|
|
48113
|
-
|
|
48114
|
-
|
|
48115
|
-
|
|
48116
|
-
|
|
48117
|
-
|
|
48118
|
-
|
|
48119
|
-
|
|
48120
|
-
|
|
48121
|
-
|
|
48122
|
-
|
|
48123
|
-
|
|
48124
|
-
|
|
48125
|
-
|
|
48126
|
-
|
|
48127
|
-
|
|
48128
|
-
|
|
48129
|
-
|
|
48130
|
-
|
|
48131
|
-
|
|
48132
|
-
|
|
47971
|
+
}
|
|
47972
|
+
};
|
|
47973
|
+
}
|
|
47974
|
+
function hashArgs(args2) {
|
|
47975
|
+
try {
|
|
47976
|
+
if (typeof args2 !== "object" || args2 === null) {
|
|
47977
|
+
return 0;
|
|
47978
|
+
}
|
|
47979
|
+
const sortedKeys = Object.keys(args2).sort();
|
|
47980
|
+
return Number(Bun.hash(JSON.stringify(args2, sortedKeys)));
|
|
47981
|
+
} catch {
|
|
47982
|
+
return 0;
|
|
47983
|
+
}
|
|
47984
|
+
}
|
|
47985
|
+
|
|
47986
|
+
// src/hooks/delegation-gate.ts
|
|
47987
|
+
function extractTaskLine(text) {
|
|
47988
|
+
const match = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
47989
|
+
return match ? match[1].trim() : null;
|
|
47990
|
+
}
|
|
47991
|
+
function extractPlanTaskId(text) {
|
|
47992
|
+
const taskListMatch = text.match(/^[ \t]*-[ \t]*(?:\[[ x]\][ \t]+)?(\d+\.\d+(?:\.\d+)*)[:. ]/m);
|
|
47993
|
+
if (taskListMatch) {
|
|
47994
|
+
return taskListMatch[1];
|
|
47995
|
+
}
|
|
47996
|
+
const taskLineMatch = text.match(/TASK:\s*(?:.+?\s)?(\d+\.\d+(?:\.\d+)*)(?:\s|$|:)/i);
|
|
47997
|
+
if (taskLineMatch) {
|
|
47998
|
+
return taskLineMatch[1];
|
|
47999
|
+
}
|
|
48000
|
+
return null;
|
|
48001
|
+
}
|
|
48002
|
+
function createDelegationGateHook(config3) {
|
|
48003
|
+
const enabled = config3.hooks?.delegation_gate !== false;
|
|
48004
|
+
const delegationMaxChars = config3.hooks?.delegation_max_chars ?? 4000;
|
|
48005
|
+
if (!enabled) {
|
|
48006
|
+
return {
|
|
48007
|
+
messagesTransform: async (_input, _output) => {},
|
|
48008
|
+
toolAfter: async () => {}
|
|
48009
|
+
};
|
|
48010
|
+
}
|
|
48011
|
+
const toolAfter = async (input, _output) => {
|
|
48012
|
+
if (!input.sessionID)
|
|
48013
|
+
return;
|
|
48014
|
+
const session = swarmState.agentSessions.get(input.sessionID);
|
|
48015
|
+
if (!session)
|
|
48016
|
+
return;
|
|
48017
|
+
const taskStates = session.taskWorkflowStates ? Object.entries(session.taskWorkflowStates) : [];
|
|
48018
|
+
const statesSummary = taskStates.length > 0 ? taskStates.map(([k, v]) => `${k}=${v}`).join(",") : "(none)";
|
|
48019
|
+
console.log(`[swarm-debug-task] delegation-gate.toolAfter | session=${input.sessionID} callID=${input.callID} tool=${input.tool}`);
|
|
48020
|
+
const normalized = input.tool.replace(/^[^:]+[:.]/, "");
|
|
48021
|
+
if (normalized === "Task" || normalized === "task") {
|
|
48022
|
+
const storedArgs = getStoredInputArgs(input.callID);
|
|
48023
|
+
const argsObj = storedArgs;
|
|
48024
|
+
const subagentType = argsObj?.subagent_type;
|
|
48025
|
+
console.log(`[swarm-debug-task] delegation-gate.taskDetected | session=${input.sessionID} subagent_type=${subagentType ?? "(none)"} currentStates=[${statesSummary}]`);
|
|
48026
|
+
let hasReviewer = false;
|
|
48027
|
+
let hasTestEngineer = false;
|
|
48028
|
+
if (typeof subagentType === "string") {
|
|
48029
|
+
const targetAgent = stripKnownSwarmPrefix(subagentType);
|
|
48030
|
+
if (targetAgent === "reviewer")
|
|
48031
|
+
hasReviewer = true;
|
|
48032
|
+
if (targetAgent === "test_engineer")
|
|
48033
|
+
hasTestEngineer = true;
|
|
48034
|
+
if (targetAgent === "reviewer" && session.taskWorkflowStates) {
|
|
48035
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
48036
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
48037
|
+
try {
|
|
48038
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
48039
|
+
} catch {}
|
|
48040
|
+
}
|
|
48041
|
+
}
|
|
48042
|
+
}
|
|
48043
|
+
if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
|
|
48044
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
48045
|
+
if (state === "reviewer_run") {
|
|
48133
48046
|
try {
|
|
48134
|
-
|
|
48135
|
-
|
|
48136
|
-
|
|
48137
|
-
|
|
48047
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
48048
|
+
} catch {}
|
|
48049
|
+
}
|
|
48050
|
+
}
|
|
48051
|
+
}
|
|
48052
|
+
if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
|
|
48053
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
48054
|
+
if (otherSession === session)
|
|
48055
|
+
continue;
|
|
48056
|
+
if (!otherSession.taskWorkflowStates)
|
|
48057
|
+
continue;
|
|
48058
|
+
if (targetAgent === "reviewer") {
|
|
48059
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
48060
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
48061
|
+
try {
|
|
48062
|
+
advanceTaskState(otherSession, taskId, "reviewer_run");
|
|
48063
|
+
} catch {}
|
|
48064
|
+
}
|
|
48138
48065
|
}
|
|
48139
|
-
|
|
48140
|
-
|
|
48141
|
-
|
|
48142
|
-
|
|
48143
|
-
|
|
48144
|
-
taskId
|
|
48145
|
-
|
|
48146
|
-
});
|
|
48066
|
+
}
|
|
48067
|
+
if (targetAgent === "test_engineer") {
|
|
48068
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
48069
|
+
if (state === "reviewer_run") {
|
|
48070
|
+
try {
|
|
48071
|
+
advanceTaskState(otherSession, taskId, "tests_run");
|
|
48072
|
+
} catch {}
|
|
48147
48073
|
}
|
|
48148
48074
|
}
|
|
48149
48075
|
}
|
|
48150
48076
|
}
|
|
48151
48077
|
}
|
|
48152
|
-
|
|
48153
|
-
|
|
48154
|
-
|
|
48155
|
-
|
|
48156
|
-
|
|
48157
|
-
|
|
48158
|
-
|
|
48159
|
-
|
|
48160
|
-
|
|
48161
|
-
|
|
48078
|
+
}
|
|
48079
|
+
if (argsObj !== undefined) {
|
|
48080
|
+
deleteStoredInputArgs(input.callID);
|
|
48081
|
+
}
|
|
48082
|
+
if (!subagentType || !hasReviewer) {
|
|
48083
|
+
const delegationChain = swarmState.delegationChains.get(input.sessionID);
|
|
48084
|
+
if (delegationChain && delegationChain.length > 0) {
|
|
48085
|
+
let lastCoderIndex = -1;
|
|
48086
|
+
for (let i2 = delegationChain.length - 1;i2 >= 0; i2--) {
|
|
48087
|
+
const target = stripKnownSwarmPrefix(delegationChain[i2].to);
|
|
48088
|
+
if (target.includes("coder")) {
|
|
48089
|
+
lastCoderIndex = i2;
|
|
48090
|
+
break;
|
|
48162
48091
|
}
|
|
48163
|
-
}
|
|
48164
|
-
|
|
48165
|
-
|
|
48166
|
-
|
|
48167
|
-
|
|
48168
|
-
|
|
48169
|
-
|
|
48170
|
-
|
|
48171
|
-
|
|
48172
|
-
if (
|
|
48173
|
-
|
|
48174
|
-
|
|
48175
|
-
|
|
48092
|
+
}
|
|
48093
|
+
if (lastCoderIndex === -1) {
|
|
48094
|
+
return;
|
|
48095
|
+
}
|
|
48096
|
+
const afterCoder = delegationChain.slice(lastCoderIndex);
|
|
48097
|
+
for (const delegation of afterCoder) {
|
|
48098
|
+
const target = stripKnownSwarmPrefix(delegation.to);
|
|
48099
|
+
if (target === "reviewer")
|
|
48100
|
+
hasReviewer = true;
|
|
48101
|
+
if (target === "test_engineer")
|
|
48102
|
+
hasTestEngineer = true;
|
|
48103
|
+
}
|
|
48104
|
+
if (hasReviewer && hasTestEngineer) {
|
|
48105
|
+
session.qaSkipCount = 0;
|
|
48106
|
+
session.qaSkipTaskIds = [];
|
|
48107
|
+
}
|
|
48108
|
+
if (hasReviewer && session.taskWorkflowStates) {
|
|
48109
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
48110
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
48111
|
+
try {
|
|
48112
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
48113
|
+
} catch {}
|
|
48114
|
+
}
|
|
48115
|
+
}
|
|
48116
|
+
}
|
|
48117
|
+
if (hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
|
|
48118
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
48119
|
+
if (state === "reviewer_run") {
|
|
48120
|
+
try {
|
|
48121
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
48122
|
+
} catch {}
|
|
48123
|
+
}
|
|
48124
|
+
}
|
|
48125
|
+
}
|
|
48126
|
+
if (hasReviewer) {
|
|
48127
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
48128
|
+
if (otherSession === session)
|
|
48129
|
+
continue;
|
|
48130
|
+
if (!otherSession.taskWorkflowStates)
|
|
48131
|
+
continue;
|
|
48132
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
48133
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
48134
|
+
try {
|
|
48135
|
+
advanceTaskState(otherSession, taskId, "reviewer_run");
|
|
48136
|
+
} catch {}
|
|
48137
|
+
}
|
|
48138
|
+
}
|
|
48139
|
+
}
|
|
48140
|
+
}
|
|
48141
|
+
if (hasReviewer && hasTestEngineer) {
|
|
48142
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
48143
|
+
if (otherSession === session)
|
|
48144
|
+
continue;
|
|
48145
|
+
if (!otherSession.taskWorkflowStates)
|
|
48146
|
+
continue;
|
|
48147
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
48148
|
+
if (state === "reviewer_run") {
|
|
48149
|
+
try {
|
|
48150
|
+
advanceTaskState(otherSession, taskId, "tests_run");
|
|
48151
|
+
} catch {}
|
|
48152
|
+
}
|
|
48153
|
+
}
|
|
48176
48154
|
}
|
|
48177
48155
|
}
|
|
48178
|
-
session.modifiedFilesThisCoderTask = [];
|
|
48179
48156
|
}
|
|
48180
48157
|
}
|
|
48181
|
-
|
|
48182
|
-
|
|
48183
|
-
|
|
48184
|
-
const hasError = output.output === null || output.output === undefined;
|
|
48185
|
-
if (hasError) {
|
|
48186
|
-
window2.consecutiveErrors++;
|
|
48187
|
-
} else {
|
|
48188
|
-
window2.consecutiveErrors = 0;
|
|
48189
|
-
window2.lastSuccessTimeMs = Date.now();
|
|
48190
|
-
}
|
|
48191
|
-
},
|
|
48158
|
+
}
|
|
48159
|
+
};
|
|
48160
|
+
return {
|
|
48192
48161
|
messagesTransform: async (_input, output) => {
|
|
48193
48162
|
const messages = output.messages;
|
|
48194
|
-
if (!messages || messages.length === 0)
|
|
48163
|
+
if (!messages || messages.length === 0)
|
|
48195
48164
|
return;
|
|
48165
|
+
let lastUserMessageIndex = -1;
|
|
48166
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
48167
|
+
if (messages[i2]?.info?.role === "user") {
|
|
48168
|
+
lastUserMessageIndex = i2;
|
|
48169
|
+
break;
|
|
48170
|
+
}
|
|
48196
48171
|
}
|
|
48197
|
-
|
|
48198
|
-
const sessionId = lastMessage.info?.sessionID;
|
|
48199
|
-
if (!sessionId) {
|
|
48172
|
+
if (lastUserMessageIndex === -1)
|
|
48200
48173
|
return;
|
|
48201
|
-
|
|
48202
|
-
|
|
48203
|
-
|
|
48204
|
-
|
|
48205
|
-
|
|
48206
|
-
|
|
48207
|
-
|
|
48208
|
-
|
|
48209
|
-
|
|
48210
|
-
|
|
48211
|
-
|
|
48212
|
-
|
|
48213
|
-
|
|
48214
|
-
|
|
48215
|
-
|
|
48216
|
-
|
|
48217
|
-
|
|
48218
|
-
|
|
48174
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
48175
|
+
if (!lastUserMessage?.parts)
|
|
48176
|
+
return;
|
|
48177
|
+
const agent = lastUserMessage.info?.agent;
|
|
48178
|
+
const strippedAgent = agent ? stripKnownSwarmPrefix(agent) : undefined;
|
|
48179
|
+
if (strippedAgent && strippedAgent !== "architect")
|
|
48180
|
+
return;
|
|
48181
|
+
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
48182
|
+
if (textPartIndex === -1)
|
|
48183
|
+
return;
|
|
48184
|
+
const textPart = lastUserMessage.parts[textPartIndex];
|
|
48185
|
+
const text = textPart.text ?? "";
|
|
48186
|
+
const taskDisclosureSessionID = lastUserMessage.info?.sessionID;
|
|
48187
|
+
if (taskDisclosureSessionID) {
|
|
48188
|
+
const taskSession = ensureAgentSession(taskDisclosureSessionID);
|
|
48189
|
+
const currentTaskIdForWindow = taskSession.currentTaskId;
|
|
48190
|
+
if (currentTaskIdForWindow) {
|
|
48191
|
+
const taskLineRegex = /^[ \t]*-[ \t]*(?:\[[ x]\][ \t]+)?(\d+\.\d+(?:\.\d+)*)[:. ].*/gm;
|
|
48192
|
+
const taskLines = [];
|
|
48193
|
+
taskLineRegex.lastIndex = 0;
|
|
48194
|
+
let regexMatch = taskLineRegex.exec(text);
|
|
48195
|
+
while (regexMatch !== null) {
|
|
48196
|
+
taskLines.push({
|
|
48197
|
+
line: regexMatch[0],
|
|
48198
|
+
taskId: regexMatch[1],
|
|
48199
|
+
index: regexMatch.index
|
|
48200
|
+
});
|
|
48201
|
+
regexMatch = taskLineRegex.exec(text);
|
|
48202
|
+
}
|
|
48203
|
+
if (taskLines.length > 5) {
|
|
48204
|
+
const currentIdx = taskLines.findIndex((t) => t.taskId === currentTaskIdForWindow);
|
|
48205
|
+
const windowStart = Math.max(0, currentIdx - 2);
|
|
48206
|
+
const windowEnd = Math.min(taskLines.length - 1, currentIdx + 3);
|
|
48207
|
+
const visibleTasks = taskLines.slice(windowStart, windowEnd + 1);
|
|
48208
|
+
const hiddenBefore = windowStart;
|
|
48209
|
+
const hiddenAfter = taskLines.length - 1 - windowEnd;
|
|
48210
|
+
const totalTasks = taskLines.length;
|
|
48211
|
+
const visibleCount = visibleTasks.length;
|
|
48212
|
+
const firstTaskIndex = taskLines[0].index;
|
|
48213
|
+
const lastTask = taskLines[taskLines.length - 1];
|
|
48214
|
+
const lastTaskEnd = lastTask.index + lastTask.line.length;
|
|
48215
|
+
const before = text.slice(0, firstTaskIndex);
|
|
48216
|
+
const after = text.slice(lastTaskEnd);
|
|
48217
|
+
const visibleLines = visibleTasks.map((t) => t.line).join(`
|
|
48218
|
+
`);
|
|
48219
|
+
const trimComment = `[Task window: showing ${visibleCount} of ${totalTasks} tasks]`;
|
|
48220
|
+
const trimmedMiddle = (hiddenBefore > 0 ? `[...${hiddenBefore} tasks hidden...]
|
|
48221
|
+
` : "") + visibleLines + (hiddenAfter > 0 ? `
|
|
48222
|
+
[...${hiddenAfter} tasks hidden...]` : "");
|
|
48223
|
+
textPart.text = `${before}${trimmedMiddle}
|
|
48224
|
+
${trimComment}${after}`;
|
|
48219
48225
|
}
|
|
48220
48226
|
}
|
|
48221
48227
|
}
|
|
48222
|
-
const
|
|
48223
|
-
const
|
|
48224
|
-
const
|
|
48225
|
-
|
|
48226
|
-
|
|
48227
|
-
|
|
48228
|
-
|
|
48229
|
-
|
|
48230
|
-
|
|
48231
|
-
|
|
48232
|
-
|
|
48228
|
+
const sessionID = lastUserMessage.info?.sessionID;
|
|
48229
|
+
const planTaskId = extractPlanTaskId(text);
|
|
48230
|
+
const taskIdMatch = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
48231
|
+
const taskIdFromLine = taskIdMatch ? taskIdMatch[1].trim() : null;
|
|
48232
|
+
const currentTaskId = planTaskId ?? taskIdFromLine;
|
|
48233
|
+
const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
|
|
48234
|
+
const isCoderDelegation = coderDelegationPattern.test(text);
|
|
48235
|
+
const priorCoderTaskId = sessionID ? ensureAgentSession(sessionID).lastCoderDelegationTaskId ?? null : null;
|
|
48236
|
+
if (sessionID && isCoderDelegation && currentTaskId) {
|
|
48237
|
+
const session = ensureAgentSession(sessionID);
|
|
48238
|
+
session.lastCoderDelegationTaskId = currentTaskId;
|
|
48239
|
+
const fileDirPattern = /^FILE:\s*(.+)$/gm;
|
|
48240
|
+
const declaredFiles = [];
|
|
48241
|
+
for (const match of text.matchAll(fileDirPattern)) {
|
|
48242
|
+
const filePath = match[1].trim();
|
|
48243
|
+
if (filePath.length > 0 && !declaredFiles.includes(filePath)) {
|
|
48244
|
+
declaredFiles.push(filePath);
|
|
48245
|
+
}
|
|
48246
|
+
}
|
|
48247
|
+
session.declaredCoderScope = declaredFiles.length > 0 ? declaredFiles : null;
|
|
48248
|
+
try {
|
|
48249
|
+
advanceTaskState(session, currentTaskId, "coder_delegated");
|
|
48250
|
+
} catch (err2) {
|
|
48251
|
+
console.warn(`[delegation-gate] state machine warn: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
48233
48252
|
}
|
|
48234
48253
|
}
|
|
48235
|
-
if (
|
|
48236
|
-
const
|
|
48237
|
-
if (
|
|
48238
|
-
|
|
48239
|
-
|
|
48240
|
-
|
|
48241
|
-
` + `Do NOT fix gate failures yourself.
|
|
48254
|
+
if (sessionID && !isCoderDelegation && currentTaskId) {
|
|
48255
|
+
const session = ensureAgentSession(sessionID);
|
|
48256
|
+
if (session.architectWriteCount > 0 && session.lastCoderDelegationTaskId !== currentTaskId) {
|
|
48257
|
+
const warningText2 = `\u26A0\uFE0F DELEGATION VIOLATION: Code modifications detected for task ${currentTaskId} with zero coder delegations.
|
|
48258
|
+
Rule 1: DELEGATE all coding to coder. You do NOT write code.`;
|
|
48259
|
+
textPart.text = `${warningText2}
|
|
48242
48260
|
|
|
48243
|
-
|
|
48244
|
-
session.selfFixAttempted = false;
|
|
48261
|
+
${text}`;
|
|
48245
48262
|
}
|
|
48246
48263
|
}
|
|
48247
|
-
|
|
48248
|
-
|
|
48249
|
-
|
|
48250
|
-
|
|
48251
|
-
|
|
48252
|
-
|
|
48253
|
-
|
|
48254
|
-
|
|
48255
|
-
|
|
48256
|
-
|
|
48257
|
-
|
|
48258
|
-
|
|
48259
|
-
|
|
48260
|
-
|
|
48261
|
-
|
|
48262
|
-
} else {
|
|
48263
|
-
for (const gate of REQUIRED_GATES) {
|
|
48264
|
-
if (!gates.has(gate)) {
|
|
48265
|
-
missingGates.push(gate);
|
|
48266
|
-
}
|
|
48267
|
-
}
|
|
48268
|
-
}
|
|
48269
|
-
let currentPhaseForCheck = 1;
|
|
48270
|
-
try {
|
|
48271
|
-
const plan = await loadPlan(directory);
|
|
48272
|
-
if (plan) {
|
|
48273
|
-
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
48274
|
-
currentPhaseForCheck = extractPhaseNumber(phaseString);
|
|
48264
|
+
{
|
|
48265
|
+
const deliberationSessionID = lastUserMessage.info?.sessionID;
|
|
48266
|
+
if (deliberationSessionID) {
|
|
48267
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(deliberationSessionID)) {} else {
|
|
48268
|
+
const deliberationSession = ensureAgentSession(deliberationSessionID);
|
|
48269
|
+
const lastGate = deliberationSession.lastGateOutcome;
|
|
48270
|
+
let preamble;
|
|
48271
|
+
if (lastGate) {
|
|
48272
|
+
const gateResult = lastGate.passed ? "PASSED" : "FAILED";
|
|
48273
|
+
const sanitizedGate = lastGate.gate.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 64);
|
|
48274
|
+
const sanitizedTaskId = lastGate.taskId.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 32);
|
|
48275
|
+
preamble = `[Last gate: ${sanitizedGate} ${gateResult} for task ${sanitizedTaskId}]
|
|
48276
|
+
[DELIBERATE: Before proceeding \u2014 what is the SINGLE next task? What gates must it pass?]`;
|
|
48277
|
+
} else {
|
|
48278
|
+
preamble = `[DELIBERATE: Identify the first task from the plan. What gates must it pass before marking complete?]`;
|
|
48275
48279
|
}
|
|
48276
|
-
|
|
48277
|
-
|
|
48278
|
-
if (missingGates.length > 0 || !hasReviewerDelegation) {
|
|
48279
|
-
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
48280
|
-
if (textPart2 && !textPart2.text.includes("PARTIAL GATE VIOLATION")) {
|
|
48281
|
-
const missing = [...missingGates];
|
|
48282
|
-
if (!hasReviewerDelegation) {
|
|
48283
|
-
missing.push("reviewer/test_engineer (no delegations this phase)");
|
|
48284
|
-
}
|
|
48285
|
-
session.partialGateWarningsIssuedForTask.add(taskId);
|
|
48286
|
-
textPart2.text = `\u26A0\uFE0F PARTIAL GATE VIOLATION: Task may be marked complete but missing gates: [${missing.join(", ")}].
|
|
48287
|
-
` + `The QA gate is ALL steps or NONE. Revert any \u2713 marks and run the missing gates.
|
|
48280
|
+
const currentText = textPart.text ?? "";
|
|
48281
|
+
textPart.text = `${preamble}
|
|
48288
48282
|
|
|
48289
|
-
|
|
48290
|
-
}
|
|
48283
|
+
${currentText}`;
|
|
48291
48284
|
}
|
|
48292
48285
|
}
|
|
48293
48286
|
}
|
|
48294
|
-
if (
|
|
48295
|
-
|
|
48296
|
-
|
|
48297
|
-
|
|
48298
|
-
|
|
48299
|
-
|
|
48300
|
-
|
|
48301
|
-
|
|
48287
|
+
if (!isCoderDelegation)
|
|
48288
|
+
return;
|
|
48289
|
+
const warnings = [];
|
|
48290
|
+
if (text.length > delegationMaxChars) {
|
|
48291
|
+
warnings.push(`Delegation exceeds recommended size (${text.length} chars, limit ${delegationMaxChars}). Consider splitting into smaller tasks.`);
|
|
48292
|
+
}
|
|
48293
|
+
const fileMatches = text.match(/^FILE:/gm);
|
|
48294
|
+
if (fileMatches && fileMatches.length > 1) {
|
|
48295
|
+
warnings.push(`Multiple FILE: directives detected (${fileMatches.length}). Each coder task should target ONE file.`);
|
|
48296
|
+
}
|
|
48297
|
+
const taskMatches = text.match(/^TASK:/gm);
|
|
48298
|
+
if (taskMatches && taskMatches.length > 1) {
|
|
48299
|
+
warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
|
|
48300
|
+
}
|
|
48301
|
+
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with|while you'?re at it)[.,]?\b/gi;
|
|
48302
|
+
const batchingMatches = text.match(batchingPattern);
|
|
48303
|
+
if (batchingMatches && batchingMatches.length > 0) {
|
|
48304
|
+
warnings.push(`Batching language detected (${batchingMatches.join(", ")}). Break compound objectives into separate coder calls.`);
|
|
48305
|
+
}
|
|
48306
|
+
const taskLine = extractTaskLine(text);
|
|
48307
|
+
if (taskLine) {
|
|
48308
|
+
const andPattern = /\s+and\s+(update|add|remove|modify|refactor|implement|create|delete|fix|change|build|deploy|write|test|move|rename|extend|extract|convert|migrate|upgrade|replace)\b/i;
|
|
48309
|
+
if (andPattern.test(taskLine)) {
|
|
48310
|
+
warnings.push('TASK line contains "and" connecting separate actions');
|
|
48302
48311
|
}
|
|
48303
48312
|
}
|
|
48304
|
-
if (
|
|
48305
|
-
|
|
48306
|
-
|
|
48307
|
-
|
|
48308
|
-
|
|
48309
|
-
|
|
48310
|
-
|
|
48311
|
-
|
|
48312
|
-
|
|
48313
|
-
|
|
48314
|
-
|
|
48315
|
-
|
|
48316
|
-
|
|
48317
|
-
|
|
48318
|
-
|
|
48319
|
-
|
|
48320
|
-
|
|
48321
|
-
|
|
48322
|
-
|
|
48323
|
-
|
|
48313
|
+
if (sessionID) {
|
|
48314
|
+
const delegationChain = swarmState.delegationChains.get(sessionID);
|
|
48315
|
+
if (delegationChain && delegationChain.length >= 2) {
|
|
48316
|
+
const coderIndices = [];
|
|
48317
|
+
for (let i2 = delegationChain.length - 1;i2 >= 0; i2--) {
|
|
48318
|
+
if (stripKnownSwarmPrefix(delegationChain[i2].to).includes("coder")) {
|
|
48319
|
+
coderIndices.unshift(i2);
|
|
48320
|
+
if (coderIndices.length === 2)
|
|
48321
|
+
break;
|
|
48322
|
+
}
|
|
48323
|
+
}
|
|
48324
|
+
if (coderIndices.length === 2) {
|
|
48325
|
+
const prevCoderIndex = coderIndices[0];
|
|
48326
|
+
const betweenCoders = delegationChain.slice(prevCoderIndex + 1);
|
|
48327
|
+
const hasReviewer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "reviewer");
|
|
48328
|
+
const hasTestEngineer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "test_engineer");
|
|
48329
|
+
const session = ensureAgentSession(sessionID);
|
|
48330
|
+
const priorTaskStuckAtCoder = priorCoderTaskId !== null && getTaskState(session, priorCoderTaskId) === "coder_delegated";
|
|
48331
|
+
if (!hasReviewer || !hasTestEngineer || priorTaskStuckAtCoder) {
|
|
48332
|
+
if (session.qaSkipCount >= 1) {
|
|
48333
|
+
const skippedTasks = session.qaSkipTaskIds.join(", ");
|
|
48334
|
+
throw new Error(`\uD83D\uDED1 QA GATE ENFORCEMENT: ${session.qaSkipCount + 1} consecutive coder delegations without reviewer/test_engineer. ` + `Skipped tasks: [${skippedTasks}]. ` + `DELEGATE to reviewer and test_engineer NOW before any further coder work.`);
|
|
48324
48335
|
}
|
|
48336
|
+
session.qaSkipCount++;
|
|
48337
|
+
session.qaSkipTaskIds.push(currentTaskId ?? "unknown");
|
|
48338
|
+
warnings.push(`\u26A0\uFE0F PROTOCOL VIOLATION: Previous coder task completed, but QA gate was skipped. ` + `You MUST delegate to reviewer (code review) and test_engineer (test execution) ` + `before starting a new coder task. Review RULES 7-8 in your system prompt.`);
|
|
48325
48339
|
}
|
|
48326
48340
|
}
|
|
48327
|
-
}
|
|
48341
|
+
}
|
|
48328
48342
|
}
|
|
48329
|
-
|
|
48330
|
-
if (!targetWindow || !targetWindow.warningIssued && !targetWindow.hardLimitHit) {
|
|
48343
|
+
if (warnings.length === 0)
|
|
48331
48344
|
return;
|
|
48345
|
+
const warningLines = warnings.map((w) => `Detected signal: ${w}`);
|
|
48346
|
+
const warningText = `\u26A0\uFE0F BATCH DETECTED: Your coder delegation appears to contain multiple tasks.
|
|
48347
|
+
Rule 3: ONE task per coder call. Split this into separate delegations.
|
|
48348
|
+
${warningLines.join(`
|
|
48349
|
+
`)}`;
|
|
48350
|
+
const originalText = textPart.text ?? "";
|
|
48351
|
+
textPart.text = `${warningText}
|
|
48352
|
+
|
|
48353
|
+
${originalText}`;
|
|
48354
|
+
},
|
|
48355
|
+
toolAfter
|
|
48356
|
+
};
|
|
48357
|
+
}
|
|
48358
|
+
// src/hooks/delegation-sanitizer.ts
|
|
48359
|
+
init_utils2();
|
|
48360
|
+
import * as fs13 from "fs";
|
|
48361
|
+
var SANITIZATION_PATTERNS = [
|
|
48362
|
+
/\b\d+(st|nd|rd|th)\s+(attempt|try|time)\b/gi,
|
|
48363
|
+
/\b(5th|fifth|final|last)\s+attempt\b/gi,
|
|
48364
|
+
/attempt\s+\d+\s*\/\s*\d+/gi,
|
|
48365
|
+
/\bthis\s+is\s+(the\s+)?(5th|fifth|final|last)\b/gi,
|
|
48366
|
+
/\bwe('re|\s+are)\s+(behind|late)\b/gi,
|
|
48367
|
+
/\buser\s+is\s+waiting\b/gi,
|
|
48368
|
+
/\bship\s+(this|it)\s+now\b/gi,
|
|
48369
|
+
/\bor\s+I('ll|\s+will)\s+(stop|halt|alert)\b/gi,
|
|
48370
|
+
/\bor\s+all\s+work\s+stops\b/gi,
|
|
48371
|
+
/\bthis\s+will\s+(delay|block)\s+everything\b/gi,
|
|
48372
|
+
/\b(I'm|I\s+am)\s+(frustrated|disappointed)\b/gi
|
|
48373
|
+
];
|
|
48374
|
+
function sanitizeMessage(text, patterns = SANITIZATION_PATTERNS) {
|
|
48375
|
+
let sanitized = text;
|
|
48376
|
+
const stripped = [];
|
|
48377
|
+
for (const pattern of patterns) {
|
|
48378
|
+
const matches = sanitized.match(pattern);
|
|
48379
|
+
if (matches) {
|
|
48380
|
+
stripped.push(...matches);
|
|
48381
|
+
sanitized = sanitized.replace(pattern, "");
|
|
48382
|
+
}
|
|
48383
|
+
}
|
|
48384
|
+
sanitized = sanitized.replace(/\s+/g, " ").trim();
|
|
48385
|
+
return {
|
|
48386
|
+
sanitized,
|
|
48387
|
+
modified: stripped.length > 0,
|
|
48388
|
+
stripped
|
|
48389
|
+
};
|
|
48390
|
+
}
|
|
48391
|
+
function isGateAgentMessage(agentName) {
|
|
48392
|
+
const gateAgents = ["reviewer", "test_engineer", "critic", "test-engineer"];
|
|
48393
|
+
const normalized = agentName.toLowerCase().replace(/-/g, "_");
|
|
48394
|
+
return gateAgents.includes(normalized);
|
|
48395
|
+
}
|
|
48396
|
+
function createDelegationSanitizerHook(directory) {
|
|
48397
|
+
const hook = async (_input, output) => {
|
|
48398
|
+
const messages = output?.messages;
|
|
48399
|
+
if (!messages || !Array.isArray(messages)) {
|
|
48400
|
+
return;
|
|
48401
|
+
}
|
|
48402
|
+
for (const message of messages) {
|
|
48403
|
+
const info2 = message?.info;
|
|
48404
|
+
if (!info2)
|
|
48405
|
+
continue;
|
|
48406
|
+
const agent = info2.agent;
|
|
48407
|
+
if (!agent || !isGateAgentMessage(agent)) {
|
|
48408
|
+
continue;
|
|
48332
48409
|
}
|
|
48333
|
-
|
|
48334
|
-
|
|
48335
|
-
return;
|
|
48410
|
+
if (!message.parts || !Array.isArray(message.parts)) {
|
|
48411
|
+
continue;
|
|
48336
48412
|
}
|
|
48337
|
-
|
|
48338
|
-
|
|
48339
|
-
|
|
48340
|
-
|
|
48341
|
-
|
|
48342
|
-
const
|
|
48343
|
-
|
|
48344
|
-
|
|
48345
|
-
|
|
48413
|
+
for (const part of message.parts) {
|
|
48414
|
+
if (part?.type !== "text" || !part.text) {
|
|
48415
|
+
continue;
|
|
48416
|
+
}
|
|
48417
|
+
const originalText = part.text;
|
|
48418
|
+
const result = sanitizeMessage(originalText);
|
|
48419
|
+
if (result.modified) {
|
|
48420
|
+
part.text = result.sanitized;
|
|
48421
|
+
try {
|
|
48422
|
+
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
48423
|
+
const event = {
|
|
48424
|
+
event: "message_sanitized",
|
|
48425
|
+
agent,
|
|
48426
|
+
original_length: originalText.length,
|
|
48427
|
+
stripped_count: result.stripped.length,
|
|
48428
|
+
stripped_patterns: result.stripped,
|
|
48429
|
+
timestamp: new Date().toISOString()
|
|
48430
|
+
};
|
|
48431
|
+
fs13.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
48432
|
+
`, "utf-8");
|
|
48433
|
+
} catch {}
|
|
48434
|
+
}
|
|
48346
48435
|
}
|
|
48347
48436
|
}
|
|
48348
48437
|
};
|
|
48438
|
+
return safeHook(hook);
|
|
48349
48439
|
}
|
|
48350
|
-
|
|
48351
|
-
|
|
48352
|
-
|
|
48353
|
-
|
|
48440
|
+
// src/hooks/delegation-tracker.ts
|
|
48441
|
+
init_constants();
|
|
48442
|
+
init_schema();
|
|
48443
|
+
function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
48444
|
+
return async (input, _output) => {
|
|
48445
|
+
const now = Date.now();
|
|
48446
|
+
const debugSession = swarmState.agentSessions.get(input.sessionID);
|
|
48447
|
+
const taskStates = debugSession?.taskWorkflowStates ? Object.entries(debugSession.taskWorkflowStates) : [];
|
|
48448
|
+
const statesSummary = taskStates.length > 0 ? taskStates.map(([k, v]) => `${k}=${v}`).join(",") : "(none)";
|
|
48449
|
+
console.log(`[swarm-debug-task] chat.message | session=${input.sessionID} agent=${input.agent ?? "(none)"} prevAgent=${swarmState.activeAgent.get(input.sessionID) ?? "(none)"} taskStates=[${statesSummary}]`);
|
|
48450
|
+
if (!input.agent || input.agent === "") {
|
|
48451
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
48452
|
+
if (session2?.delegationActive) {
|
|
48453
|
+
session2.delegationActive = false;
|
|
48454
|
+
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
48455
|
+
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
48456
|
+
updateAgentEventTime(input.sessionID);
|
|
48457
|
+
} else if (!session2) {
|
|
48458
|
+
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
48459
|
+
}
|
|
48460
|
+
return;
|
|
48354
48461
|
}
|
|
48355
|
-
const
|
|
48356
|
-
|
|
48357
|
-
|
|
48358
|
-
|
|
48359
|
-
|
|
48462
|
+
const agentName = input.agent;
|
|
48463
|
+
const previousAgent = swarmState.activeAgent.get(input.sessionID);
|
|
48464
|
+
swarmState.activeAgent.set(input.sessionID, agentName);
|
|
48465
|
+
const strippedAgent = stripKnownSwarmPrefix(agentName);
|
|
48466
|
+
const isArchitect2 = strippedAgent === ORCHESTRATOR_NAME;
|
|
48467
|
+
const session = ensureAgentSession(input.sessionID, agentName);
|
|
48468
|
+
session.delegationActive = !isArchitect2;
|
|
48469
|
+
recordPhaseAgentDispatch(input.sessionID, agentName);
|
|
48470
|
+
if (!isArchitect2 && guardrailsEnabled) {
|
|
48471
|
+
beginInvocation(input.sessionID, agentName);
|
|
48472
|
+
}
|
|
48473
|
+
const delegationTrackerEnabled = config3.hooks?.delegation_tracker === true;
|
|
48474
|
+
const delegationGateEnabled = config3.hooks?.delegation_gate !== false;
|
|
48475
|
+
if ((delegationTrackerEnabled || delegationGateEnabled) && previousAgent && previousAgent !== agentName) {
|
|
48476
|
+
const entry = {
|
|
48477
|
+
from: previousAgent,
|
|
48478
|
+
to: agentName,
|
|
48479
|
+
timestamp: now
|
|
48480
|
+
};
|
|
48481
|
+
if (!swarmState.delegationChains.has(input.sessionID)) {
|
|
48482
|
+
swarmState.delegationChains.set(input.sessionID, []);
|
|
48483
|
+
}
|
|
48484
|
+
const chain = swarmState.delegationChains.get(input.sessionID);
|
|
48485
|
+
chain?.push(entry);
|
|
48486
|
+
if (delegationTrackerEnabled) {
|
|
48487
|
+
swarmState.pendingEvents++;
|
|
48488
|
+
}
|
|
48489
|
+
}
|
|
48490
|
+
};
|
|
48360
48491
|
}
|
|
48361
48492
|
// src/hooks/messages-transform.ts
|
|
48362
48493
|
function consolidateSystemMessages(messages) {
|
|
@@ -60436,6 +60567,13 @@ ${footerLines.join(`
|
|
|
60436
60567
|
}
|
|
60437
60568
|
|
|
60438
60569
|
// src/index.ts
|
|
60570
|
+
function debugTaskLog(hook, ctx, extra) {
|
|
60571
|
+
const activeAgent = swarmState.activeAgent.get(ctx.sessionID);
|
|
60572
|
+
const session = swarmState.agentSessions.get(ctx.sessionID);
|
|
60573
|
+
const taskStates = session?.taskWorkflowStates ? Object.entries(session.taskWorkflowStates) : [];
|
|
60574
|
+
const statesSummary = taskStates.length > 0 ? taskStates.map(([k, v]) => `${k}=${v}`).join(",") : "(none)";
|
|
60575
|
+
console.log(`[swarm-debug-task] ${hook} | session=${ctx.sessionID} callID=${ctx.callID ?? "(n/a)"} agent=${activeAgent ?? "(none)"} taskStates=[${statesSummary}]`, extra ? JSON.stringify(extra) : "");
|
|
60576
|
+
}
|
|
60439
60577
|
var OpenCodeSwarm = async (ctx) => {
|
|
60440
60578
|
const { config: config3, loadedFromFile } = loadPluginConfigWithMeta(ctx.directory);
|
|
60441
60579
|
await loadSnapshot(ctx.directory);
|
|
@@ -60730,6 +60868,10 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
60730
60868
|
"experimental.session.compacting": compactionHook["experimental.session.compacting"],
|
|
60731
60869
|
"command.execute.before": safeHook(commandHandler),
|
|
60732
60870
|
"tool.execute.before": async (input, output) => {
|
|
60871
|
+
debugTaskLog("tool.execute.before", {
|
|
60872
|
+
sessionID: input.sessionID,
|
|
60873
|
+
callID: input.callID
|
|
60874
|
+
}, { tool: input.tool });
|
|
60733
60875
|
if (!swarmState.activeAgent.has(input.sessionID)) {
|
|
60734
60876
|
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
60735
60877
|
}
|
|
@@ -60749,6 +60891,10 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
60749
60891
|
await safeHook(activityHooks.toolBefore)(input, output);
|
|
60750
60892
|
},
|
|
60751
60893
|
"tool.execute.after": async (input, output) => {
|
|
60894
|
+
debugTaskLog("tool.execute.after", {
|
|
60895
|
+
sessionID: input.sessionID,
|
|
60896
|
+
callID: input.callID
|
|
60897
|
+
}, { tool: input.tool });
|
|
60752
60898
|
await activityHooks.toolAfter(input, output);
|
|
60753
60899
|
await guardrailsHooks.toolAfter(input, output);
|
|
60754
60900
|
await safeHook(delegationGateHooks.toolAfter)(input, output);
|
|
@@ -60778,6 +60924,9 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
60778
60924
|
}
|
|
60779
60925
|
if (input.tool === "task") {
|
|
60780
60926
|
const sessionId = input.sessionID;
|
|
60927
|
+
const beforeSession = swarmState.agentSessions.get(sessionId);
|
|
60928
|
+
const beforeStates = beforeSession?.taskWorkflowStates ? Object.entries(beforeSession.taskWorkflowStates).map(([k, v]) => `${k}=${v}`).join(",") : "(none)";
|
|
60929
|
+
console.log(`[swarm-debug-task] tool.execute.after.taskHandoff | session=${sessionId} BEFORE: taskStates=[${beforeStates}]`);
|
|
60781
60930
|
swarmState.activeAgent.set(sessionId, ORCHESTRATOR_NAME);
|
|
60782
60931
|
ensureAgentSession(sessionId, ORCHESTRATOR_NAME);
|
|
60783
60932
|
const session = swarmState.agentSessions.get(sessionId);
|
|
@@ -60785,6 +60934,10 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
60785
60934
|
session.delegationActive = false;
|
|
60786
60935
|
session.lastAgentEventTime = Date.now();
|
|
60787
60936
|
}
|
|
60937
|
+
const afterSession = swarmState.agentSessions.get(sessionId);
|
|
60938
|
+
const afterStates = afterSession?.taskWorkflowStates ? Object.entries(afterSession.taskWorkflowStates).map(([k, v]) => `${k}=${v}`).join(",") : "(none)";
|
|
60939
|
+
const afterActive = swarmState.activeAgent.get(sessionId);
|
|
60940
|
+
console.log(`[swarm-debug-task] tool.execute.after.taskHandoff | session=${sessionId} AFTER: activeAgent=${afterActive} delegationActive=${afterSession?.delegationActive} taskStates=[${afterStates}]`);
|
|
60788
60941
|
}
|
|
60789
60942
|
},
|
|
60790
60943
|
"chat.message": safeHook(delegationHandler),
|