kantban-cli 0.1.46 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-DAFLEMLK.js → chunk-4R27WTCJ.js} +31 -17
- package/dist/chunk-4R27WTCJ.js.map +1 -0
- package/dist/{chunk-SDMNXFPV.js → chunk-DENXSVKE.js} +9 -3
- package/dist/chunk-DENXSVKE.js.map +1 -0
- package/dist/{chunk-N4ZHMJD7.js → chunk-QHJZIGEE.js} +146 -7
- package/dist/chunk-QHJZIGEE.js.map +1 -0
- package/dist/{cron-QUEYUVIX.js → cron-F6D6475M.js} +3 -3
- package/dist/index.js +3 -3
- package/dist/lib/gate-proxy-server.js +38 -18
- package/dist/lib/gate-proxy-server.js.map +1 -1
- package/dist/{pipeline-IT7LJXKA.js → pipeline-GZOSDNPF.js} +245 -52
- package/dist/pipeline-GZOSDNPF.js.map +1 -0
- package/dist/{pipeline-init-IGZZOOLK.js → pipeline-init-AUKPFJYE.js} +5 -2
- package/dist/pipeline-init-AUKPFJYE.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DAFLEMLK.js.map +0 -1
- package/dist/chunk-N4ZHMJD7.js.map +0 -1
- package/dist/chunk-SDMNXFPV.js.map +0 -1
- package/dist/pipeline-IT7LJXKA.js.map +0 -1
- package/dist/pipeline-init-IGZZOOLK.js.map +0 -1
- /package/dist/{cron-QUEYUVIX.js.map → cron-F6D6475M.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runGates
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DENXSVKE.js";
|
|
4
4
|
import {
|
|
5
5
|
ClaudeProvider,
|
|
6
6
|
RalphLoop,
|
|
@@ -11,15 +11,17 @@ import {
|
|
|
11
11
|
generateGateProxyMcpConfig,
|
|
12
12
|
generateMcpConfig,
|
|
13
13
|
parseJsonFromLlmOutput,
|
|
14
|
-
parseStuckDetectionResponse
|
|
15
|
-
|
|
14
|
+
parseStuckDetectionResponse,
|
|
15
|
+
reapOrphanedMcpConfigDirs
|
|
16
|
+
} from "./chunk-QHJZIGEE.js";
|
|
16
17
|
import {
|
|
17
18
|
LoopCheckpointSchema,
|
|
18
19
|
VerdictSchema,
|
|
20
|
+
findUnmatchedColumnKeys,
|
|
19
21
|
parseGateConfig,
|
|
20
22
|
parseTimeout,
|
|
21
23
|
resolveGatesForColumn
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-4R27WTCJ.js";
|
|
23
25
|
import {
|
|
24
26
|
IS_WINDOWS,
|
|
25
27
|
crossSpawnOptions,
|
|
@@ -31,7 +33,13 @@ import {
|
|
|
31
33
|
// src/commands/pipeline.ts
|
|
32
34
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync3, unlinkSync as unlinkSync3, existsSync as existsSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
33
35
|
import { homedir as homedir2 } from "os";
|
|
34
|
-
import { join as join3 } from "path";
|
|
36
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
37
|
+
|
|
38
|
+
// src/lib/harness-signal.ts
|
|
39
|
+
var HARNESS_SIGNAL_PREFIX = "@harness:";
|
|
40
|
+
function createHarnessSignalContent(kind, payload) {
|
|
41
|
+
return HARNESS_SIGNAL_PREFIX + JSON.stringify({ ...payload, kind });
|
|
42
|
+
}
|
|
35
43
|
|
|
36
44
|
// src/lib/tool-profiles.ts
|
|
37
45
|
function resolveToolRestrictions(builtinTools, allowedTools, disallowedTools) {
|
|
@@ -469,7 +477,9 @@ function parseReplannerResponse(raw) {
|
|
|
469
477
|
}
|
|
470
478
|
|
|
471
479
|
// src/lib/orchestrator.ts
|
|
472
|
-
var ACTIVE_LOOP_ZOMBIE_TTL_MS =
|
|
480
|
+
var ACTIVE_LOOP_ZOMBIE_TTL_MS = Number(
|
|
481
|
+
process.env.ACTIVE_LOOP_ZOMBIE_TTL_MS
|
|
482
|
+
) || 3 * 60 * 60 * 1e3;
|
|
473
483
|
function classifyTier(input) {
|
|
474
484
|
if (input.invocationTier === "light") return "light";
|
|
475
485
|
if (input.invocationTier === "heavy") return "heavy";
|
|
@@ -509,6 +519,12 @@ var PipelineOrchestrator = class {
|
|
|
509
519
|
blockedColumns = /* @__PURE__ */ new Set();
|
|
510
520
|
/** Per-ticket advisor invocation count for the current column transit */
|
|
511
521
|
advisorBudget = /* @__PURE__ */ new Map();
|
|
522
|
+
/** Count of advisor-driven retries per ticket in the current column transit.
|
|
523
|
+
* Cleared on ticket:moved/archived/deleted and in terminalCleanup. */
|
|
524
|
+
gutterResetCount = /* @__PURE__ */ new Map();
|
|
525
|
+
gutterResetExhausted = /* @__PURE__ */ new Set();
|
|
526
|
+
/** Per-ticket deferred-dispatch signal state — used for dedup and cleanup */
|
|
527
|
+
deferredState = /* @__PURE__ */ new Map();
|
|
512
528
|
/** Per-ticket model override (set by RETRY_DIFFERENT_MODEL, consumed by startTrackedLoop) */
|
|
513
529
|
ticketModelOverrides = /* @__PURE__ */ new Map();
|
|
514
530
|
/** Stable session ID for this orchestrator instance (pipeline run) */
|
|
@@ -550,6 +566,14 @@ var PipelineOrchestrator = class {
|
|
|
550
566
|
get pipelineColumnIds() {
|
|
551
567
|
return Array.from(this.pipelineColumns.keys());
|
|
552
568
|
}
|
|
569
|
+
/** Returns the display names of every column on the board (not just pipeline columns). */
|
|
570
|
+
get allBoardColumnNames() {
|
|
571
|
+
return this.cachedBoardScope?.columns.map((c) => c.name) ?? [];
|
|
572
|
+
}
|
|
573
|
+
/** Returns {name, type} for every column on the board. */
|
|
574
|
+
get allBoardColumns() {
|
|
575
|
+
return this.cachedBoardScope?.columns.map((c) => ({ name: c.name, type: c.type })) ?? [];
|
|
576
|
+
}
|
|
553
577
|
/** Returns the total number of active loops. */
|
|
554
578
|
get activeLoopCount() {
|
|
555
579
|
return this.activeLoops.size;
|
|
@@ -628,12 +652,16 @@ var PipelineOrchestrator = class {
|
|
|
628
652
|
} : void 0,
|
|
629
653
|
builtinTools: cfg?.builtin_tools,
|
|
630
654
|
allowedTools: cfg?.allowed_tools,
|
|
631
|
-
disallowedTools: cfg?.disallowed_tools
|
|
655
|
+
disallowedTools: cfg?.disallowed_tools,
|
|
656
|
+
maxGutterResetsPerTransit: cfg?.max_gutter_resets_per_transit,
|
|
657
|
+
repromptOnBranchMerged: cfg?.reprompt_on_branch_merged,
|
|
658
|
+
maxRepromptAttempts: cfg?.max_reprompt_attempts
|
|
632
659
|
});
|
|
633
660
|
this.columnScopes.set(col.id, colScope);
|
|
634
661
|
this.loopQueues.set(col.id, []);
|
|
635
662
|
})
|
|
636
663
|
);
|
|
664
|
+
console.error(`[orchestrator] zombie TTL: ${ACTIVE_LOOP_ZOMBIE_TTL_MS}ms`);
|
|
637
665
|
}
|
|
638
666
|
/**
|
|
639
667
|
* Refresh the cached column scope for a single column.
|
|
@@ -939,6 +967,9 @@ var PipelineOrchestrator = class {
|
|
|
939
967
|
if (this.isColumnBlocked(columnId)) {
|
|
940
968
|
this.blockedColumns.add(columnId);
|
|
941
969
|
console.error(` [scan] Column ${columnId} (${colScope.column.name}): BLOCKED by firing constraints \u2014 skipping ${String(colScope.tickets.length)} ticket(s)`);
|
|
970
|
+
for (const ticket of colScope.tickets) {
|
|
971
|
+
await this.emitDispatchDeferred(ticket.id, "firing_constraint", { columnId });
|
|
972
|
+
}
|
|
942
973
|
continue;
|
|
943
974
|
}
|
|
944
975
|
console.error(` [scan] Column ${columnId} (${colScope.column.name}): ${String(colScope.tickets.length)} ticket(s)`);
|
|
@@ -965,6 +996,7 @@ var PipelineOrchestrator = class {
|
|
|
965
996
|
this.knownTickets.delete(event.ticketId);
|
|
966
997
|
this.spawning.delete(event.ticketId);
|
|
967
998
|
this.deferredTickets.delete(event.ticketId);
|
|
999
|
+
void this.clearDispatchDeferred(event.ticketId);
|
|
968
1000
|
const oldQueue = this.loopQueues.get(event.fromColumnId);
|
|
969
1001
|
if (oldQueue) {
|
|
970
1002
|
const idx = oldQueue.indexOf(event.ticketId);
|
|
@@ -979,6 +1011,7 @@ var PipelineOrchestrator = class {
|
|
|
979
1011
|
if (this.isColumnBlocked(event.columnId)) {
|
|
980
1012
|
console.error(` [event] ${event.type} ${event.ticketId} \u2192 column ${event.columnId}: BLOCKED by firing constraints \u2014 deferred`);
|
|
981
1013
|
this.deferredTickets.set(event.ticketId, event.columnId);
|
|
1014
|
+
await this.emitDispatchDeferred(event.ticketId, "firing_constraint", { columnId: event.columnId });
|
|
982
1015
|
} else {
|
|
983
1016
|
await this.spawnOrQueue(event.ticketId, event.columnId, true);
|
|
984
1017
|
}
|
|
@@ -1028,6 +1061,9 @@ var PipelineOrchestrator = class {
|
|
|
1028
1061
|
this.deferredTickets.delete(event.ticketId);
|
|
1029
1062
|
this.spawning.delete(event.ticketId);
|
|
1030
1063
|
this.advisorBudget.delete(event.ticketId);
|
|
1064
|
+
this.gutterResetCount.delete(event.ticketId);
|
|
1065
|
+
this.gutterResetExhausted.delete(event.ticketId);
|
|
1066
|
+
void this.clearDispatchDeferred(event.ticketId);
|
|
1031
1067
|
for (const [, queue] of this.loopQueues) {
|
|
1032
1068
|
const idx = queue.indexOf(event.ticketId);
|
|
1033
1069
|
if (idx !== -1) {
|
|
@@ -1071,6 +1107,7 @@ var PipelineOrchestrator = class {
|
|
|
1071
1107
|
this.releaseSlot(columnId);
|
|
1072
1108
|
this.deferredTickets.set(ticketId, columnId);
|
|
1073
1109
|
console.error(` [skip] ${ticketId} has unresolved blockers \u2014 deferred`);
|
|
1110
|
+
void this.emitDispatchDeferred(ticketId, "unresolved_blockers", { columnId });
|
|
1074
1111
|
void this.drainQueue(columnId).catch((err) => {
|
|
1075
1112
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1076
1113
|
console.error(` [error] drainQueue failed for column ${columnId}: ${msg}`);
|
|
@@ -1083,6 +1120,7 @@ var PipelineOrchestrator = class {
|
|
|
1083
1120
|
this.spawning.delete(ticketId);
|
|
1084
1121
|
this.releaseSlot(columnId);
|
|
1085
1122
|
this.deferredTickets.set(ticketId, columnId);
|
|
1123
|
+
void this.emitDispatchDeferred(ticketId, "unresolved_blockers", { columnId });
|
|
1086
1124
|
void this.drainQueue(columnId).catch((err2) => {
|
|
1087
1125
|
const msg2 = err2 instanceof Error ? err2.message : String(err2);
|
|
1088
1126
|
console.error(` [error] drainQueue failed for column ${columnId}: ${msg2}`);
|
|
@@ -1091,6 +1129,7 @@ var PipelineOrchestrator = class {
|
|
|
1091
1129
|
}
|
|
1092
1130
|
try {
|
|
1093
1131
|
await this.deps.claimTicket(ticketId);
|
|
1132
|
+
await this.clearDispatchDeferred(ticketId);
|
|
1094
1133
|
this.startTrackedLoop(ticketId, columnId, colConfig);
|
|
1095
1134
|
} catch (err) {
|
|
1096
1135
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1249,6 +1288,8 @@ var PipelineOrchestrator = class {
|
|
|
1249
1288
|
...startGutterCount !== void 0 && { startGutterCount },
|
|
1250
1289
|
...startFingerprint !== void 0 && { startFingerprint },
|
|
1251
1290
|
...config.stuckDetection && { stuckDetection: config.stuckDetection },
|
|
1291
|
+
...config.repromptOnBranchMerged !== void 0 && { repromptOnBranchMerged: config.repromptOnBranchMerged },
|
|
1292
|
+
...config.maxRepromptAttempts !== void 0 && { maxRepromptAttempts: config.maxRepromptAttempts },
|
|
1252
1293
|
...this.deps.costTracker && { isBudgetExhausted: () => this.deps.costTracker.isExhausted() }
|
|
1253
1294
|
};
|
|
1254
1295
|
const toolRestrictions = resolveToolRestrictions(
|
|
@@ -1278,6 +1319,33 @@ var PipelineOrchestrator = class {
|
|
|
1278
1319
|
})
|
|
1279
1320
|
);
|
|
1280
1321
|
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Increment the gutter-reset counter and check the per-column cap.
|
|
1324
|
+
* Returns false if the cap has been exceeded (and emits signal + comment).
|
|
1325
|
+
* Callers should abort the retry on false.
|
|
1326
|
+
*/
|
|
1327
|
+
async checkGutterResetCap(ticketId, colConfig) {
|
|
1328
|
+
const cap = colConfig.maxGutterResetsPerTransit ?? 2;
|
|
1329
|
+
const count = (this.gutterResetCount.get(ticketId) ?? 0) + 1;
|
|
1330
|
+
this.gutterResetCount.set(ticketId, count);
|
|
1331
|
+
if (count > cap) {
|
|
1332
|
+
this.gutterResetExhausted.add(ticketId);
|
|
1333
|
+
await this.emitHarnessSignal(ticketId, "agent_stalled_exhausted", {
|
|
1334
|
+
resetCount: count,
|
|
1335
|
+
cap
|
|
1336
|
+
});
|
|
1337
|
+
await this.safeAction(
|
|
1338
|
+
ticketId,
|
|
1339
|
+
"stalled:exhaustedComment",
|
|
1340
|
+
() => this.deps.createComment(
|
|
1341
|
+
ticketId,
|
|
1342
|
+
`Stalled after ${String(count)} gutter reset attempt(s) (cap: ${String(cap)}) \u2014 human review needed. See 'agent_stalled_exhausted' signal for details.`
|
|
1343
|
+
)
|
|
1344
|
+
);
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1281
1349
|
/**
|
|
1282
1350
|
* Phase 2: Invoke the advisor for failure recovery.
|
|
1283
1351
|
* Returns true if the ticket should be retried, false if handled.
|
|
@@ -1381,6 +1449,10 @@ var PipelineOrchestrator = class {
|
|
|
1381
1449
|
}
|
|
1382
1450
|
switch (response.action) {
|
|
1383
1451
|
case "RETRY_WITH_FEEDBACK": {
|
|
1452
|
+
if (!await this.checkGutterResetCap(ticketId, colConfig)) {
|
|
1453
|
+
this.completing.delete(ticketId);
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1384
1456
|
if (response.feedback) {
|
|
1385
1457
|
await this.safeAction(
|
|
1386
1458
|
ticketId,
|
|
@@ -1395,6 +1467,10 @@ ${response.feedback}`)
|
|
|
1395
1467
|
return true;
|
|
1396
1468
|
}
|
|
1397
1469
|
case "RETRY_DIFFERENT_MODEL": {
|
|
1470
|
+
if (!await this.checkGutterResetCap(ticketId, colConfig)) {
|
|
1471
|
+
this.completing.delete(ticketId);
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1398
1474
|
const currentModel = result.model ?? colConfig.modelPreference ?? "default";
|
|
1399
1475
|
const escalation = colConfig.modelRouting?.escalation ?? [];
|
|
1400
1476
|
let nextModel;
|
|
@@ -1553,10 +1629,17 @@ ${response.feedback}`)
|
|
|
1553
1629
|
);
|
|
1554
1630
|
}
|
|
1555
1631
|
}
|
|
1632
|
+
await this.emitHarnessSignal(ticketId, "advisor_action", {
|
|
1633
|
+
action: "ESCALATE",
|
|
1634
|
+
reason: response.reason
|
|
1635
|
+
});
|
|
1556
1636
|
await this.safeAction(
|
|
1557
1637
|
ticketId,
|
|
1558
|
-
"advisor:
|
|
1559
|
-
() => this.deps.createComment(
|
|
1638
|
+
"advisor:escalateComment",
|
|
1639
|
+
() => this.deps.createComment(
|
|
1640
|
+
ticketId,
|
|
1641
|
+
`Needs human review \u2014 see 'advisor_action' signal for details.`
|
|
1642
|
+
)
|
|
1560
1643
|
);
|
|
1561
1644
|
return false;
|
|
1562
1645
|
}
|
|
@@ -1812,6 +1895,8 @@ ${findingsText}`)
|
|
|
1812
1895
|
const colConfig = this.pipelineColumns.get(columnId);
|
|
1813
1896
|
const terminalCleanup = async () => {
|
|
1814
1897
|
this.advisorBudget.delete(ticketId);
|
|
1898
|
+
this.gutterResetCount.delete(ticketId);
|
|
1899
|
+
this.gutterResetExhausted.delete(ticketId);
|
|
1815
1900
|
if (colConfig?.checkpointEnabled && this.deps.setFieldValue && result.reason !== "stopped") {
|
|
1816
1901
|
await this.safeAction(
|
|
1817
1902
|
ticketId,
|
|
@@ -1945,47 +2030,41 @@ ${findingsText}`)
|
|
|
1945
2030
|
}
|
|
1946
2031
|
break;
|
|
1947
2032
|
case "max_iterations":
|
|
1948
|
-
|
|
2033
|
+
await this.emitHarnessSignal(ticketId, "agent_max_iterations", {
|
|
2034
|
+
iterations: result.iterations
|
|
2035
|
+
});
|
|
2036
|
+
comment = `Needs human review \u2014 see 'agent_max_iterations' signal for details.`;
|
|
1949
2037
|
break;
|
|
1950
2038
|
case "stalled":
|
|
1951
|
-
|
|
2039
|
+
if (!this.gutterResetExhausted.has(ticketId)) {
|
|
2040
|
+
await this.emitHarnessSignal(ticketId, "agent_stalled", {
|
|
2041
|
+
iterations: result.iterations,
|
|
2042
|
+
gutterCount: result.gutterCount
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
1952
2045
|
break;
|
|
1953
2046
|
case "error":
|
|
1954
|
-
|
|
2047
|
+
await this.emitHarnessSignal(ticketId, "agent_error", {
|
|
2048
|
+
iterations: result.iterations,
|
|
2049
|
+
lastError: result.lastError ?? "unknown error"
|
|
2050
|
+
});
|
|
2051
|
+
comment = `Needs human review \u2014 see 'agent_error' signal for details.`;
|
|
1955
2052
|
break;
|
|
1956
2053
|
case "stopped":
|
|
1957
2054
|
comment = `Pipeline agent was stopped externally after ${result.iterations} iteration(s).`;
|
|
1958
2055
|
break;
|
|
1959
2056
|
case "deleted":
|
|
1960
|
-
comment = `Pipeline agent stopped \u2014 ticket was deleted or archived during iteration ${result.iterations}.`;
|
|
1961
2057
|
break;
|
|
1962
2058
|
case "budget":
|
|
1963
2059
|
comment = `Pipeline budget exhausted after ${result.iterations} iteration(s). Increase token budget in pipeline.gates.yaml settings.budget to continue.`;
|
|
1964
2060
|
break;
|
|
1965
2061
|
}
|
|
1966
|
-
if (
|
|
2062
|
+
if (comment !== void 0) {
|
|
1967
2063
|
await this.deps.createComment(ticketId, comment).catch((err) => {
|
|
1968
2064
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1969
2065
|
console.error(` [warn] Failed to write completion comment for ${ticketId}: ${msg}`);
|
|
1970
2066
|
});
|
|
1971
2067
|
}
|
|
1972
|
-
if (result.reason === "stalled") {
|
|
1973
|
-
await this.deps.createSignal(
|
|
1974
|
-
ticketId,
|
|
1975
|
-
`Previous pipeline run stalled after ${result.iterations} iterations with no progress. Review comments for details before retrying.`
|
|
1976
|
-
).catch((err) => {
|
|
1977
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1978
|
-
console.error(` [warn] Failed to write signal for ${ticketId}: ${msg}`);
|
|
1979
|
-
});
|
|
1980
|
-
} else if (result.reason === "error") {
|
|
1981
|
-
await this.deps.createSignal(
|
|
1982
|
-
ticketId,
|
|
1983
|
-
`Previous pipeline run failed after ${result.iterations} iteration(s): ${result.lastError ?? "unknown error"}. Review comments for details before retrying.`
|
|
1984
|
-
).catch((err) => {
|
|
1985
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1986
|
-
console.error(` [warn] Failed to write signal for ${ticketId}: ${msg}`);
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
2068
|
await terminalCleanup();
|
|
1990
2069
|
} finally {
|
|
1991
2070
|
this.completing.delete(ticketId);
|
|
@@ -2112,6 +2191,7 @@ ${findingsText}`)
|
|
|
2112
2191
|
this.deferredTickets.set(nextTicketId, columnId);
|
|
2113
2192
|
this.spawning.delete(nextTicketId);
|
|
2114
2193
|
this.releaseSlot(columnId);
|
|
2194
|
+
void this.emitDispatchDeferred(nextTicketId, "unresolved_blockers", { columnId });
|
|
2115
2195
|
continue;
|
|
2116
2196
|
}
|
|
2117
2197
|
} catch (err) {
|
|
@@ -2120,10 +2200,12 @@ ${findingsText}`)
|
|
|
2120
2200
|
this.deferredTickets.set(nextTicketId, columnId);
|
|
2121
2201
|
this.spawning.delete(nextTicketId);
|
|
2122
2202
|
this.releaseSlot(columnId);
|
|
2203
|
+
void this.emitDispatchDeferred(nextTicketId, "unresolved_blockers", { columnId });
|
|
2123
2204
|
continue;
|
|
2124
2205
|
}
|
|
2125
2206
|
try {
|
|
2126
2207
|
await this.deps.claimTicket(nextTicketId);
|
|
2208
|
+
await this.clearDispatchDeferred(nextTicketId);
|
|
2127
2209
|
this.startTrackedLoop(nextTicketId, columnId, colConfig);
|
|
2128
2210
|
} catch (err) {
|
|
2129
2211
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2152,6 +2234,63 @@ ${findingsText}`)
|
|
|
2152
2234
|
const current = this.columnReservations.get(columnId) ?? 0;
|
|
2153
2235
|
if (current > 0) this.columnReservations.set(columnId, current - 1);
|
|
2154
2236
|
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Emit a dispatch_deferred harness signal for a ticket.
|
|
2239
|
+
* State-change dedup: same reason → no-op; different reason → delete old + create new.
|
|
2240
|
+
* Best-effort: signal creation failures are logged but never thrown.
|
|
2241
|
+
*/
|
|
2242
|
+
/**
|
|
2243
|
+
* Emit a structured harness signal on a ticket. Uses createSignal (plain, non-tracked).
|
|
2244
|
+
* Best-effort: failures are logged but never thrown.
|
|
2245
|
+
*/
|
|
2246
|
+
async emitHarnessSignal(ticketId, kind, payload) {
|
|
2247
|
+
const content = createHarnessSignalContent(kind, payload);
|
|
2248
|
+
try {
|
|
2249
|
+
await this.deps.createSignal(ticketId, content);
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2252
|
+
console.error(`[harness-signal] failed to create ${kind} signal for ${ticketId}: ${msg}`);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
async emitDispatchDeferred(ticketId, reason, extras) {
|
|
2256
|
+
if (!this.deps.createHarnessSignal) return;
|
|
2257
|
+
const existing = this.deferredState.get(ticketId);
|
|
2258
|
+
if (existing?.reason === reason) return;
|
|
2259
|
+
if (existing && this.deps.deleteSignal) {
|
|
2260
|
+
try {
|
|
2261
|
+
await this.deps.deleteSignal(existing.signalId);
|
|
2262
|
+
} catch {
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
const firstDeferredAt = existing?.firstDeferredAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2266
|
+
const content = createHarnessSignalContent("dispatch_deferred", {
|
|
2267
|
+
reason,
|
|
2268
|
+
firstDeferredAt,
|
|
2269
|
+
...extras
|
|
2270
|
+
});
|
|
2271
|
+
try {
|
|
2272
|
+
const signal = await this.deps.createHarnessSignal(ticketId, content);
|
|
2273
|
+
this.deferredState.set(ticketId, { reason, signalId: signal.id, firstDeferredAt });
|
|
2274
|
+
} catch (err) {
|
|
2275
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2276
|
+
console.error(` [dispatch-deferred] failed to create signal for ticket ${ticketId}: ${msg}`);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Clear the dispatch_deferred harness signal for a ticket (called on dispatch success or ticket:moved).
|
|
2281
|
+
* Best-effort: deletion failures are swallowed.
|
|
2282
|
+
*/
|
|
2283
|
+
async clearDispatchDeferred(ticketId) {
|
|
2284
|
+
const existing = this.deferredState.get(ticketId);
|
|
2285
|
+
if (!existing) return;
|
|
2286
|
+
this.deferredState.delete(ticketId);
|
|
2287
|
+
if (this.deps.deleteSignal) {
|
|
2288
|
+
try {
|
|
2289
|
+
await this.deps.deleteSignal(existing.signalId);
|
|
2290
|
+
} catch {
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2155
2294
|
};
|
|
2156
2295
|
|
|
2157
2296
|
// src/lib/run-memory.ts
|
|
@@ -3517,22 +3656,22 @@ var CodexProvider = class {
|
|
|
3517
3656
|
}
|
|
3518
3657
|
}
|
|
3519
3658
|
}
|
|
3659
|
+
const WRITING_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "Bash", "NotebookEdit"]);
|
|
3520
3660
|
if (request.toolRestrictions) {
|
|
3521
3661
|
const tr = request.toolRestrictions;
|
|
3522
|
-
const writingTools = ["Write", "Edit", "Bash", "NotebookEdit", "shell", "file_write", "file_edit"];
|
|
3523
3662
|
if (tr.tools === "") {
|
|
3524
3663
|
args.push("--sandbox", "read-only");
|
|
3525
3664
|
degraded.push("builtinToolStripping");
|
|
3526
|
-
} else
|
|
3527
|
-
|
|
3528
|
-
|
|
3665
|
+
} else {
|
|
3666
|
+
const hasWritingDeny = (tr.disallowedTools ?? []).some((t) => WRITING_TOOLS.has(t));
|
|
3667
|
+
if (hasWritingDeny) {
|
|
3668
|
+
args.push("--sandbox", "read-only");
|
|
3669
|
+
degraded.push("toolDenylistAdvisory");
|
|
3670
|
+
}
|
|
3529
3671
|
}
|
|
3530
3672
|
if (tr.allowedTools?.length) {
|
|
3531
3673
|
degraded.push("toolAllowlist");
|
|
3532
3674
|
}
|
|
3533
|
-
if (tr.disallowedTools?.length && !degraded.includes("toolDenylist")) {
|
|
3534
|
-
degraded.push("toolDenylist");
|
|
3535
|
-
}
|
|
3536
3675
|
}
|
|
3537
3676
|
if (request.maxTurns) {
|
|
3538
3677
|
degraded.push("maxTurns");
|
|
@@ -4050,9 +4189,25 @@ function createProviderRegistry() {
|
|
|
4050
4189
|
registry.register(new GeminiProvider());
|
|
4051
4190
|
return registry;
|
|
4052
4191
|
}
|
|
4053
|
-
function readMcpConfigAsProviderConfig(filePath) {
|
|
4054
|
-
|
|
4055
|
-
|
|
4192
|
+
function readMcpConfigAsProviderConfig(filePath, regenerate) {
|
|
4193
|
+
function parse(path) {
|
|
4194
|
+
const raw = JSON.parse(readFileSync3(path, "utf-8"));
|
|
4195
|
+
return { servers: raw.mcpServers };
|
|
4196
|
+
}
|
|
4197
|
+
try {
|
|
4198
|
+
return parse(filePath);
|
|
4199
|
+
} catch (err) {
|
|
4200
|
+
const code = err?.code;
|
|
4201
|
+
if (code === "ENOENT") {
|
|
4202
|
+
process.stderr.write(
|
|
4203
|
+
`[mcp-config] regenerating missing file at ${filePath} (pid=${process.pid})
|
|
4204
|
+
`
|
|
4205
|
+
);
|
|
4206
|
+
const newPath = regenerate();
|
|
4207
|
+
return parse(newPath);
|
|
4208
|
+
}
|
|
4209
|
+
throw err;
|
|
4210
|
+
}
|
|
4056
4211
|
}
|
|
4057
4212
|
function parseArgs(args) {
|
|
4058
4213
|
const positional = [];
|
|
@@ -4255,7 +4410,7 @@ function waitForConfirmation() {
|
|
|
4255
4410
|
}
|
|
4256
4411
|
async function runPipeline(client, args) {
|
|
4257
4412
|
if (args[0] === "init") {
|
|
4258
|
-
const { runPipelineInit } = await import("./pipeline-init-
|
|
4413
|
+
const { runPipelineInit } = await import("./pipeline-init-AUKPFJYE.js");
|
|
4259
4414
|
await runPipelineInit();
|
|
4260
4415
|
return;
|
|
4261
4416
|
}
|
|
@@ -4299,6 +4454,7 @@ async function runPipeline(client, args) {
|
|
|
4299
4454
|
process.exit(1);
|
|
4300
4455
|
}
|
|
4301
4456
|
}
|
|
4457
|
+
reapOrphanedMcpConfigDirs(pidDir(opts.boardId));
|
|
4302
4458
|
const mcpConfigPath = generateMcpConfig(client.baseUrl, client.token, opts.boardId);
|
|
4303
4459
|
const boardProviderConfig = {
|
|
4304
4460
|
...opts.provider ? { default_provider: opts.provider } : {},
|
|
@@ -4342,7 +4498,7 @@ async function runPipeline(client, args) {
|
|
|
4342
4498
|
if (effectiveConfig.model) {
|
|
4343
4499
|
effectiveConfig.model = registry.resolveModel(columnProvider, effectiveConfig.model);
|
|
4344
4500
|
}
|
|
4345
|
-
const columnGates = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4501
|
+
const { gates: columnGates } = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4346
4502
|
const gateCwd = effectiveConfig.worktreePath ?? (effectiveConfig.worktreeName ? join3(process.cwd(), effectiveConfig.worktreeName) : process.cwd());
|
|
4347
4503
|
const effectiveMcpConfigPath = columnGates.length > 0 ? generateGateProxyMcpConfig(
|
|
4348
4504
|
client.baseUrl,
|
|
@@ -4355,7 +4511,20 @@ async function runPipeline(client, args) {
|
|
|
4355
4511
|
gateCwd,
|
|
4356
4512
|
ticketId
|
|
4357
4513
|
) : mcpConfigPath;
|
|
4358
|
-
const columnMcpConfig = readMcpConfigAsProviderConfig(
|
|
4514
|
+
const columnMcpConfig = readMcpConfigAsProviderConfig(
|
|
4515
|
+
effectiveMcpConfigPath,
|
|
4516
|
+
() => columnGates.length > 0 ? generateGateProxyMcpConfig(
|
|
4517
|
+
client.baseUrl,
|
|
4518
|
+
client.token,
|
|
4519
|
+
opts.boardId,
|
|
4520
|
+
gateFilePath,
|
|
4521
|
+
columnId,
|
|
4522
|
+
resolvedColumnName,
|
|
4523
|
+
projectId,
|
|
4524
|
+
gateCwd,
|
|
4525
|
+
ticketId
|
|
4526
|
+
) : generateMcpConfig(client.baseUrl, client.token, opts.boardId)
|
|
4527
|
+
);
|
|
4359
4528
|
const loopDeps = {
|
|
4360
4529
|
fetchTicketContext: (tid) => client.get(`/projects/${projectId}/pipeline-context`, { ticketId: tid }),
|
|
4361
4530
|
fetchColumnContext: (cid) => client.get(`/projects/${projectId}/pipeline-context`, { columnId: cid }),
|
|
@@ -4463,7 +4632,7 @@ async function runPipeline(client, args) {
|
|
|
4463
4632
|
};
|
|
4464
4633
|
}
|
|
4465
4634
|
effectiveConfig.onPostIterationGates = async (tid, iteration) => {
|
|
4466
|
-
const gates = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4635
|
+
const { gates } = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4467
4636
|
if (gates.length === 0) {
|
|
4468
4637
|
return gateSnapshotStore.record(tid, iteration, []);
|
|
4469
4638
|
}
|
|
@@ -4499,6 +4668,12 @@ async function runPipeline(client, args) {
|
|
|
4499
4668
|
contentPrefix,
|
|
4500
4669
|
content: body
|
|
4501
4670
|
}),
|
|
4671
|
+
createHarnessSignal: (ticketId, content) => client.post(`/projects/${projectId}/signals`, {
|
|
4672
|
+
scopeType: "ticket",
|
|
4673
|
+
scopeId: ticketId,
|
|
4674
|
+
content
|
|
4675
|
+
}),
|
|
4676
|
+
deleteSignal: (signalId) => client.delete(`/projects/${projectId}/signals/${signalId}`),
|
|
4502
4677
|
claimTicket: (ticketId) => client.claimTicket(projectId, ticketId),
|
|
4503
4678
|
fetchBlockedTickets: (ticketId) => client.get(
|
|
4504
4679
|
`/projects/${projectId}/tickets/${ticketId}/blocked-tickets`
|
|
@@ -4659,7 +4834,7 @@ async function runPipeline(client, args) {
|
|
|
4659
4834
|
console.error(`Error: Failed to initialize pipeline: ${message}`);
|
|
4660
4835
|
console.error("Check that the board exists, has pipeline columns configured, and the API is reachable.");
|
|
4661
4836
|
cleanupMcpConfig(mcpConfigPath);
|
|
4662
|
-
cleanupGateProxyConfigs(
|
|
4837
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4663
4838
|
process.exit(1);
|
|
4664
4839
|
}
|
|
4665
4840
|
const columnIds = orchestrator.pipelineColumnIds;
|
|
@@ -4715,11 +4890,29 @@ async function runPipeline(client, args) {
|
|
|
4715
4890
|
if (columnIds.length === 0) {
|
|
4716
4891
|
console.log('No pipeline columns found (columns need has_prompt=true and type !== "done").');
|
|
4717
4892
|
cleanupMcpConfig(mcpConfigPath);
|
|
4718
|
-
cleanupGateProxyConfigs(
|
|
4893
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4719
4894
|
return;
|
|
4720
4895
|
}
|
|
4721
4896
|
console.log(`Discovered ${String(columnIds.length)} pipeline column(s).`);
|
|
4722
4897
|
logger.orchestrator(`Discovered ${String(columnIds.length)} pipeline columns: ${columnIds.join(", ")}`);
|
|
4898
|
+
const unmatchedKeys = findUnmatchedColumnKeys(gateConfig, orchestrator.allBoardColumnNames);
|
|
4899
|
+
if (unmatchedKeys.length > 0) {
|
|
4900
|
+
console.warn(
|
|
4901
|
+
`[gate-config] Unused column keys in pipeline.gates.yaml: ${unmatchedKeys.join(", ")}.`
|
|
4902
|
+
);
|
|
4903
|
+
console.warn(
|
|
4904
|
+
` No board column name matched (after canonicalization). These overrides will never fire.`
|
|
4905
|
+
);
|
|
4906
|
+
}
|
|
4907
|
+
for (const col of orchestrator.allBoardColumns) {
|
|
4908
|
+
if (col.type !== "done") continue;
|
|
4909
|
+
const { source } = resolveGatesForColumn(gateConfig, col.name);
|
|
4910
|
+
if (source === "override") {
|
|
4911
|
+
console.warn(
|
|
4912
|
+
`[gate-config] column "${col.name}" is done-type AND has an explicit override \u2014 gates will run on moves INTO this column (bypass disabled by your pipeline.gates.yaml).`
|
|
4913
|
+
);
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4723
4916
|
if (opts.dryRun) {
|
|
4724
4917
|
console.log("\n--- Dry Run Configuration ---");
|
|
4725
4918
|
console.log(`Board ID: ${opts.boardId}`);
|
|
@@ -4735,7 +4928,7 @@ async function runPipeline(client, args) {
|
|
|
4735
4928
|
console.log(`MCP config: ${mcpConfigPath}`);
|
|
4736
4929
|
console.log("\n[Dry run -- no agents started]");
|
|
4737
4930
|
cleanupMcpConfig(mcpConfigPath);
|
|
4738
|
-
cleanupGateProxyConfigs(
|
|
4931
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4739
4932
|
return;
|
|
4740
4933
|
}
|
|
4741
4934
|
let shutdownInProgress = false;
|
|
@@ -4763,7 +4956,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4763
4956
|
console.log(costTracker.generateReport(gateConfig.settings?.pricing));
|
|
4764
4957
|
}
|
|
4765
4958
|
cleanupMcpConfig(mcpConfigPath);
|
|
4766
|
-
cleanupGateProxyConfigs(
|
|
4959
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4767
4960
|
killReaper(reaperPidPath);
|
|
4768
4961
|
removePidFile(opts.boardId);
|
|
4769
4962
|
removeChildManifest(opts.boardId);
|
|
@@ -4801,7 +4994,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4801
4994
|
pidFilePath: pidFilePath(opts.boardId),
|
|
4802
4995
|
reaperPidPath,
|
|
4803
4996
|
mcpConfigPath,
|
|
4804
|
-
pipelineDir:
|
|
4997
|
+
pipelineDir: dirname2(mcpConfigPath)
|
|
4805
4998
|
});
|
|
4806
4999
|
logger.orchestrator(`Watchdog reaper spawned (PID ${String(reaperProcess.pid ?? "unknown")})`);
|
|
4807
5000
|
let eventQueue = null;
|
|
@@ -4886,7 +5079,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4886
5079
|
wsClient.stop();
|
|
4887
5080
|
eventQueue.stop();
|
|
4888
5081
|
cleanupMcpConfig(mcpConfigPath);
|
|
4889
|
-
cleanupGateProxyConfigs(
|
|
5082
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4890
5083
|
killReaper(reaperPidPath);
|
|
4891
5084
|
removePidFile(opts.boardId);
|
|
4892
5085
|
removeChildManifest(opts.boardId);
|
|
@@ -4984,4 +5177,4 @@ export {
|
|
|
4984
5177
|
runPipeline,
|
|
4985
5178
|
stopPipeline
|
|
4986
5179
|
};
|
|
4987
|
-
//# sourceMappingURL=pipeline-
|
|
5180
|
+
//# sourceMappingURL=pipeline-GZOSDNPF.js.map
|