kantban-cli 0.1.47 → 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-VDCHOIQI.js → chunk-4R27WTCJ.js} +22 -15
- package/dist/chunk-4R27WTCJ.js.map +1 -0
- package/dist/{chunk-O47Q6CNM.js → chunk-DENXSVKE.js} +9 -3
- package/dist/chunk-DENXSVKE.js.map +1 -0
- package/dist/{chunk-UKWQ6VUQ.js → chunk-QHJZIGEE.js} +146 -7
- package/dist/chunk-QHJZIGEE.js.map +1 -0
- package/dist/{cron-TLNKEKOW.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-EUOOLKHN.js → pipeline-GZOSDNPF.js} +230 -51
- package/dist/pipeline-GZOSDNPF.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-O47Q6CNM.js.map +0 -1
- package/dist/chunk-UKWQ6VUQ.js.map +0 -1
- package/dist/chunk-VDCHOIQI.js.map +0 -1
- package/dist/pipeline-EUOOLKHN.js.map +0 -1
- /package/dist/{cron-TLNKEKOW.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,8 +11,9 @@ 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,7 +21,7 @@ import {
|
|
|
20
21
|
parseGateConfig,
|
|
21
22
|
parseTimeout,
|
|
22
23
|
resolveGatesForColumn
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-4R27WTCJ.js";
|
|
24
25
|
import {
|
|
25
26
|
IS_WINDOWS,
|
|
26
27
|
crossSpawnOptions,
|
|
@@ -32,7 +33,13 @@ import {
|
|
|
32
33
|
// src/commands/pipeline.ts
|
|
33
34
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync3, unlinkSync as unlinkSync3, existsSync as existsSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
34
35
|
import { homedir as homedir2 } from "os";
|
|
35
|
-
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
|
+
}
|
|
36
43
|
|
|
37
44
|
// src/lib/tool-profiles.ts
|
|
38
45
|
function resolveToolRestrictions(builtinTools, allowedTools, disallowedTools) {
|
|
@@ -470,7 +477,9 @@ function parseReplannerResponse(raw) {
|
|
|
470
477
|
}
|
|
471
478
|
|
|
472
479
|
// src/lib/orchestrator.ts
|
|
473
|
-
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;
|
|
474
483
|
function classifyTier(input) {
|
|
475
484
|
if (input.invocationTier === "light") return "light";
|
|
476
485
|
if (input.invocationTier === "heavy") return "heavy";
|
|
@@ -510,6 +519,12 @@ var PipelineOrchestrator = class {
|
|
|
510
519
|
blockedColumns = /* @__PURE__ */ new Set();
|
|
511
520
|
/** Per-ticket advisor invocation count for the current column transit */
|
|
512
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();
|
|
513
528
|
/** Per-ticket model override (set by RETRY_DIFFERENT_MODEL, consumed by startTrackedLoop) */
|
|
514
529
|
ticketModelOverrides = /* @__PURE__ */ new Map();
|
|
515
530
|
/** Stable session ID for this orchestrator instance (pipeline run) */
|
|
@@ -555,6 +570,10 @@ var PipelineOrchestrator = class {
|
|
|
555
570
|
get allBoardColumnNames() {
|
|
556
571
|
return this.cachedBoardScope?.columns.map((c) => c.name) ?? [];
|
|
557
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
|
+
}
|
|
558
577
|
/** Returns the total number of active loops. */
|
|
559
578
|
get activeLoopCount() {
|
|
560
579
|
return this.activeLoops.size;
|
|
@@ -633,12 +652,16 @@ var PipelineOrchestrator = class {
|
|
|
633
652
|
} : void 0,
|
|
634
653
|
builtinTools: cfg?.builtin_tools,
|
|
635
654
|
allowedTools: cfg?.allowed_tools,
|
|
636
|
-
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
|
|
637
659
|
});
|
|
638
660
|
this.columnScopes.set(col.id, colScope);
|
|
639
661
|
this.loopQueues.set(col.id, []);
|
|
640
662
|
})
|
|
641
663
|
);
|
|
664
|
+
console.error(`[orchestrator] zombie TTL: ${ACTIVE_LOOP_ZOMBIE_TTL_MS}ms`);
|
|
642
665
|
}
|
|
643
666
|
/**
|
|
644
667
|
* Refresh the cached column scope for a single column.
|
|
@@ -944,6 +967,9 @@ var PipelineOrchestrator = class {
|
|
|
944
967
|
if (this.isColumnBlocked(columnId)) {
|
|
945
968
|
this.blockedColumns.add(columnId);
|
|
946
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
|
+
}
|
|
947
973
|
continue;
|
|
948
974
|
}
|
|
949
975
|
console.error(` [scan] Column ${columnId} (${colScope.column.name}): ${String(colScope.tickets.length)} ticket(s)`);
|
|
@@ -970,6 +996,7 @@ var PipelineOrchestrator = class {
|
|
|
970
996
|
this.knownTickets.delete(event.ticketId);
|
|
971
997
|
this.spawning.delete(event.ticketId);
|
|
972
998
|
this.deferredTickets.delete(event.ticketId);
|
|
999
|
+
void this.clearDispatchDeferred(event.ticketId);
|
|
973
1000
|
const oldQueue = this.loopQueues.get(event.fromColumnId);
|
|
974
1001
|
if (oldQueue) {
|
|
975
1002
|
const idx = oldQueue.indexOf(event.ticketId);
|
|
@@ -984,6 +1011,7 @@ var PipelineOrchestrator = class {
|
|
|
984
1011
|
if (this.isColumnBlocked(event.columnId)) {
|
|
985
1012
|
console.error(` [event] ${event.type} ${event.ticketId} \u2192 column ${event.columnId}: BLOCKED by firing constraints \u2014 deferred`);
|
|
986
1013
|
this.deferredTickets.set(event.ticketId, event.columnId);
|
|
1014
|
+
await this.emitDispatchDeferred(event.ticketId, "firing_constraint", { columnId: event.columnId });
|
|
987
1015
|
} else {
|
|
988
1016
|
await this.spawnOrQueue(event.ticketId, event.columnId, true);
|
|
989
1017
|
}
|
|
@@ -1033,6 +1061,9 @@ var PipelineOrchestrator = class {
|
|
|
1033
1061
|
this.deferredTickets.delete(event.ticketId);
|
|
1034
1062
|
this.spawning.delete(event.ticketId);
|
|
1035
1063
|
this.advisorBudget.delete(event.ticketId);
|
|
1064
|
+
this.gutterResetCount.delete(event.ticketId);
|
|
1065
|
+
this.gutterResetExhausted.delete(event.ticketId);
|
|
1066
|
+
void this.clearDispatchDeferred(event.ticketId);
|
|
1036
1067
|
for (const [, queue] of this.loopQueues) {
|
|
1037
1068
|
const idx = queue.indexOf(event.ticketId);
|
|
1038
1069
|
if (idx !== -1) {
|
|
@@ -1076,6 +1107,7 @@ var PipelineOrchestrator = class {
|
|
|
1076
1107
|
this.releaseSlot(columnId);
|
|
1077
1108
|
this.deferredTickets.set(ticketId, columnId);
|
|
1078
1109
|
console.error(` [skip] ${ticketId} has unresolved blockers \u2014 deferred`);
|
|
1110
|
+
void this.emitDispatchDeferred(ticketId, "unresolved_blockers", { columnId });
|
|
1079
1111
|
void this.drainQueue(columnId).catch((err) => {
|
|
1080
1112
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1081
1113
|
console.error(` [error] drainQueue failed for column ${columnId}: ${msg}`);
|
|
@@ -1088,6 +1120,7 @@ var PipelineOrchestrator = class {
|
|
|
1088
1120
|
this.spawning.delete(ticketId);
|
|
1089
1121
|
this.releaseSlot(columnId);
|
|
1090
1122
|
this.deferredTickets.set(ticketId, columnId);
|
|
1123
|
+
void this.emitDispatchDeferred(ticketId, "unresolved_blockers", { columnId });
|
|
1091
1124
|
void this.drainQueue(columnId).catch((err2) => {
|
|
1092
1125
|
const msg2 = err2 instanceof Error ? err2.message : String(err2);
|
|
1093
1126
|
console.error(` [error] drainQueue failed for column ${columnId}: ${msg2}`);
|
|
@@ -1096,6 +1129,7 @@ var PipelineOrchestrator = class {
|
|
|
1096
1129
|
}
|
|
1097
1130
|
try {
|
|
1098
1131
|
await this.deps.claimTicket(ticketId);
|
|
1132
|
+
await this.clearDispatchDeferred(ticketId);
|
|
1099
1133
|
this.startTrackedLoop(ticketId, columnId, colConfig);
|
|
1100
1134
|
} catch (err) {
|
|
1101
1135
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1254,6 +1288,8 @@ var PipelineOrchestrator = class {
|
|
|
1254
1288
|
...startGutterCount !== void 0 && { startGutterCount },
|
|
1255
1289
|
...startFingerprint !== void 0 && { startFingerprint },
|
|
1256
1290
|
...config.stuckDetection && { stuckDetection: config.stuckDetection },
|
|
1291
|
+
...config.repromptOnBranchMerged !== void 0 && { repromptOnBranchMerged: config.repromptOnBranchMerged },
|
|
1292
|
+
...config.maxRepromptAttempts !== void 0 && { maxRepromptAttempts: config.maxRepromptAttempts },
|
|
1257
1293
|
...this.deps.costTracker && { isBudgetExhausted: () => this.deps.costTracker.isExhausted() }
|
|
1258
1294
|
};
|
|
1259
1295
|
const toolRestrictions = resolveToolRestrictions(
|
|
@@ -1283,6 +1319,33 @@ var PipelineOrchestrator = class {
|
|
|
1283
1319
|
})
|
|
1284
1320
|
);
|
|
1285
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
|
+
}
|
|
1286
1349
|
/**
|
|
1287
1350
|
* Phase 2: Invoke the advisor for failure recovery.
|
|
1288
1351
|
* Returns true if the ticket should be retried, false if handled.
|
|
@@ -1386,6 +1449,10 @@ var PipelineOrchestrator = class {
|
|
|
1386
1449
|
}
|
|
1387
1450
|
switch (response.action) {
|
|
1388
1451
|
case "RETRY_WITH_FEEDBACK": {
|
|
1452
|
+
if (!await this.checkGutterResetCap(ticketId, colConfig)) {
|
|
1453
|
+
this.completing.delete(ticketId);
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1389
1456
|
if (response.feedback) {
|
|
1390
1457
|
await this.safeAction(
|
|
1391
1458
|
ticketId,
|
|
@@ -1400,6 +1467,10 @@ ${response.feedback}`)
|
|
|
1400
1467
|
return true;
|
|
1401
1468
|
}
|
|
1402
1469
|
case "RETRY_DIFFERENT_MODEL": {
|
|
1470
|
+
if (!await this.checkGutterResetCap(ticketId, colConfig)) {
|
|
1471
|
+
this.completing.delete(ticketId);
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1403
1474
|
const currentModel = result.model ?? colConfig.modelPreference ?? "default";
|
|
1404
1475
|
const escalation = colConfig.modelRouting?.escalation ?? [];
|
|
1405
1476
|
let nextModel;
|
|
@@ -1558,10 +1629,17 @@ ${response.feedback}`)
|
|
|
1558
1629
|
);
|
|
1559
1630
|
}
|
|
1560
1631
|
}
|
|
1632
|
+
await this.emitHarnessSignal(ticketId, "advisor_action", {
|
|
1633
|
+
action: "ESCALATE",
|
|
1634
|
+
reason: response.reason
|
|
1635
|
+
});
|
|
1561
1636
|
await this.safeAction(
|
|
1562
1637
|
ticketId,
|
|
1563
|
-
"advisor:
|
|
1564
|
-
() => this.deps.createComment(
|
|
1638
|
+
"advisor:escalateComment",
|
|
1639
|
+
() => this.deps.createComment(
|
|
1640
|
+
ticketId,
|
|
1641
|
+
`Needs human review \u2014 see 'advisor_action' signal for details.`
|
|
1642
|
+
)
|
|
1565
1643
|
);
|
|
1566
1644
|
return false;
|
|
1567
1645
|
}
|
|
@@ -1817,6 +1895,8 @@ ${findingsText}`)
|
|
|
1817
1895
|
const colConfig = this.pipelineColumns.get(columnId);
|
|
1818
1896
|
const terminalCleanup = async () => {
|
|
1819
1897
|
this.advisorBudget.delete(ticketId);
|
|
1898
|
+
this.gutterResetCount.delete(ticketId);
|
|
1899
|
+
this.gutterResetExhausted.delete(ticketId);
|
|
1820
1900
|
if (colConfig?.checkpointEnabled && this.deps.setFieldValue && result.reason !== "stopped") {
|
|
1821
1901
|
await this.safeAction(
|
|
1822
1902
|
ticketId,
|
|
@@ -1950,47 +2030,41 @@ ${findingsText}`)
|
|
|
1950
2030
|
}
|
|
1951
2031
|
break;
|
|
1952
2032
|
case "max_iterations":
|
|
1953
|
-
|
|
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.`;
|
|
1954
2037
|
break;
|
|
1955
2038
|
case "stalled":
|
|
1956
|
-
|
|
2039
|
+
if (!this.gutterResetExhausted.has(ticketId)) {
|
|
2040
|
+
await this.emitHarnessSignal(ticketId, "agent_stalled", {
|
|
2041
|
+
iterations: result.iterations,
|
|
2042
|
+
gutterCount: result.gutterCount
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
1957
2045
|
break;
|
|
1958
2046
|
case "error":
|
|
1959
|
-
|
|
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.`;
|
|
1960
2052
|
break;
|
|
1961
2053
|
case "stopped":
|
|
1962
2054
|
comment = `Pipeline agent was stopped externally after ${result.iterations} iteration(s).`;
|
|
1963
2055
|
break;
|
|
1964
2056
|
case "deleted":
|
|
1965
|
-
comment = `Pipeline agent stopped \u2014 ticket was deleted or archived during iteration ${result.iterations}.`;
|
|
1966
2057
|
break;
|
|
1967
2058
|
case "budget":
|
|
1968
2059
|
comment = `Pipeline budget exhausted after ${result.iterations} iteration(s). Increase token budget in pipeline.gates.yaml settings.budget to continue.`;
|
|
1969
2060
|
break;
|
|
1970
2061
|
}
|
|
1971
|
-
if (
|
|
2062
|
+
if (comment !== void 0) {
|
|
1972
2063
|
await this.deps.createComment(ticketId, comment).catch((err) => {
|
|
1973
2064
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1974
2065
|
console.error(` [warn] Failed to write completion comment for ${ticketId}: ${msg}`);
|
|
1975
2066
|
});
|
|
1976
2067
|
}
|
|
1977
|
-
if (result.reason === "stalled") {
|
|
1978
|
-
await this.deps.createSignal(
|
|
1979
|
-
ticketId,
|
|
1980
|
-
`Previous pipeline run stalled after ${result.iterations} iterations with no progress. Review comments for details before retrying.`
|
|
1981
|
-
).catch((err) => {
|
|
1982
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1983
|
-
console.error(` [warn] Failed to write signal for ${ticketId}: ${msg}`);
|
|
1984
|
-
});
|
|
1985
|
-
} else if (result.reason === "error") {
|
|
1986
|
-
await this.deps.createSignal(
|
|
1987
|
-
ticketId,
|
|
1988
|
-
`Previous pipeline run failed after ${result.iterations} iteration(s): ${result.lastError ?? "unknown error"}. Review comments for details before retrying.`
|
|
1989
|
-
).catch((err) => {
|
|
1990
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1991
|
-
console.error(` [warn] Failed to write signal for ${ticketId}: ${msg}`);
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
2068
|
await terminalCleanup();
|
|
1995
2069
|
} finally {
|
|
1996
2070
|
this.completing.delete(ticketId);
|
|
@@ -2117,6 +2191,7 @@ ${findingsText}`)
|
|
|
2117
2191
|
this.deferredTickets.set(nextTicketId, columnId);
|
|
2118
2192
|
this.spawning.delete(nextTicketId);
|
|
2119
2193
|
this.releaseSlot(columnId);
|
|
2194
|
+
void this.emitDispatchDeferred(nextTicketId, "unresolved_blockers", { columnId });
|
|
2120
2195
|
continue;
|
|
2121
2196
|
}
|
|
2122
2197
|
} catch (err) {
|
|
@@ -2125,10 +2200,12 @@ ${findingsText}`)
|
|
|
2125
2200
|
this.deferredTickets.set(nextTicketId, columnId);
|
|
2126
2201
|
this.spawning.delete(nextTicketId);
|
|
2127
2202
|
this.releaseSlot(columnId);
|
|
2203
|
+
void this.emitDispatchDeferred(nextTicketId, "unresolved_blockers", { columnId });
|
|
2128
2204
|
continue;
|
|
2129
2205
|
}
|
|
2130
2206
|
try {
|
|
2131
2207
|
await this.deps.claimTicket(nextTicketId);
|
|
2208
|
+
await this.clearDispatchDeferred(nextTicketId);
|
|
2132
2209
|
this.startTrackedLoop(nextTicketId, columnId, colConfig);
|
|
2133
2210
|
} catch (err) {
|
|
2134
2211
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2157,6 +2234,63 @@ ${findingsText}`)
|
|
|
2157
2234
|
const current = this.columnReservations.get(columnId) ?? 0;
|
|
2158
2235
|
if (current > 0) this.columnReservations.set(columnId, current - 1);
|
|
2159
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
|
+
}
|
|
2160
2294
|
};
|
|
2161
2295
|
|
|
2162
2296
|
// src/lib/run-memory.ts
|
|
@@ -3522,22 +3656,22 @@ var CodexProvider = class {
|
|
|
3522
3656
|
}
|
|
3523
3657
|
}
|
|
3524
3658
|
}
|
|
3659
|
+
const WRITING_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "Bash", "NotebookEdit"]);
|
|
3525
3660
|
if (request.toolRestrictions) {
|
|
3526
3661
|
const tr = request.toolRestrictions;
|
|
3527
|
-
const writingTools = ["Write", "Edit", "Bash", "NotebookEdit", "shell", "file_write", "file_edit"];
|
|
3528
3662
|
if (tr.tools === "") {
|
|
3529
3663
|
args.push("--sandbox", "read-only");
|
|
3530
3664
|
degraded.push("builtinToolStripping");
|
|
3531
|
-
} else
|
|
3532
|
-
|
|
3533
|
-
|
|
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
|
+
}
|
|
3534
3671
|
}
|
|
3535
3672
|
if (tr.allowedTools?.length) {
|
|
3536
3673
|
degraded.push("toolAllowlist");
|
|
3537
3674
|
}
|
|
3538
|
-
if (tr.disallowedTools?.length && !degraded.includes("toolDenylist")) {
|
|
3539
|
-
degraded.push("toolDenylist");
|
|
3540
|
-
}
|
|
3541
3675
|
}
|
|
3542
3676
|
if (request.maxTurns) {
|
|
3543
3677
|
degraded.push("maxTurns");
|
|
@@ -4055,9 +4189,25 @@ function createProviderRegistry() {
|
|
|
4055
4189
|
registry.register(new GeminiProvider());
|
|
4056
4190
|
return registry;
|
|
4057
4191
|
}
|
|
4058
|
-
function readMcpConfigAsProviderConfig(filePath) {
|
|
4059
|
-
|
|
4060
|
-
|
|
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
|
+
}
|
|
4061
4211
|
}
|
|
4062
4212
|
function parseArgs(args) {
|
|
4063
4213
|
const positional = [];
|
|
@@ -4304,6 +4454,7 @@ async function runPipeline(client, args) {
|
|
|
4304
4454
|
process.exit(1);
|
|
4305
4455
|
}
|
|
4306
4456
|
}
|
|
4457
|
+
reapOrphanedMcpConfigDirs(pidDir(opts.boardId));
|
|
4307
4458
|
const mcpConfigPath = generateMcpConfig(client.baseUrl, client.token, opts.boardId);
|
|
4308
4459
|
const boardProviderConfig = {
|
|
4309
4460
|
...opts.provider ? { default_provider: opts.provider } : {},
|
|
@@ -4347,7 +4498,7 @@ async function runPipeline(client, args) {
|
|
|
4347
4498
|
if (effectiveConfig.model) {
|
|
4348
4499
|
effectiveConfig.model = registry.resolveModel(columnProvider, effectiveConfig.model);
|
|
4349
4500
|
}
|
|
4350
|
-
const columnGates = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4501
|
+
const { gates: columnGates } = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4351
4502
|
const gateCwd = effectiveConfig.worktreePath ?? (effectiveConfig.worktreeName ? join3(process.cwd(), effectiveConfig.worktreeName) : process.cwd());
|
|
4352
4503
|
const effectiveMcpConfigPath = columnGates.length > 0 ? generateGateProxyMcpConfig(
|
|
4353
4504
|
client.baseUrl,
|
|
@@ -4360,7 +4511,20 @@ async function runPipeline(client, args) {
|
|
|
4360
4511
|
gateCwd,
|
|
4361
4512
|
ticketId
|
|
4362
4513
|
) : mcpConfigPath;
|
|
4363
|
-
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
|
+
);
|
|
4364
4528
|
const loopDeps = {
|
|
4365
4529
|
fetchTicketContext: (tid) => client.get(`/projects/${projectId}/pipeline-context`, { ticketId: tid }),
|
|
4366
4530
|
fetchColumnContext: (cid) => client.get(`/projects/${projectId}/pipeline-context`, { columnId: cid }),
|
|
@@ -4468,7 +4632,7 @@ async function runPipeline(client, args) {
|
|
|
4468
4632
|
};
|
|
4469
4633
|
}
|
|
4470
4634
|
effectiveConfig.onPostIterationGates = async (tid, iteration) => {
|
|
4471
|
-
const gates = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4635
|
+
const { gates } = resolveGatesForColumn(gateConfig, resolvedColumnName);
|
|
4472
4636
|
if (gates.length === 0) {
|
|
4473
4637
|
return gateSnapshotStore.record(tid, iteration, []);
|
|
4474
4638
|
}
|
|
@@ -4504,6 +4668,12 @@ async function runPipeline(client, args) {
|
|
|
4504
4668
|
contentPrefix,
|
|
4505
4669
|
content: body
|
|
4506
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}`),
|
|
4507
4677
|
claimTicket: (ticketId) => client.claimTicket(projectId, ticketId),
|
|
4508
4678
|
fetchBlockedTickets: (ticketId) => client.get(
|
|
4509
4679
|
`/projects/${projectId}/tickets/${ticketId}/blocked-tickets`
|
|
@@ -4664,7 +4834,7 @@ async function runPipeline(client, args) {
|
|
|
4664
4834
|
console.error(`Error: Failed to initialize pipeline: ${message}`);
|
|
4665
4835
|
console.error("Check that the board exists, has pipeline columns configured, and the API is reachable.");
|
|
4666
4836
|
cleanupMcpConfig(mcpConfigPath);
|
|
4667
|
-
cleanupGateProxyConfigs(
|
|
4837
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4668
4838
|
process.exit(1);
|
|
4669
4839
|
}
|
|
4670
4840
|
const columnIds = orchestrator.pipelineColumnIds;
|
|
@@ -4720,7 +4890,7 @@ async function runPipeline(client, args) {
|
|
|
4720
4890
|
if (columnIds.length === 0) {
|
|
4721
4891
|
console.log('No pipeline columns found (columns need has_prompt=true and type !== "done").');
|
|
4722
4892
|
cleanupMcpConfig(mcpConfigPath);
|
|
4723
|
-
cleanupGateProxyConfigs(
|
|
4893
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4724
4894
|
return;
|
|
4725
4895
|
}
|
|
4726
4896
|
console.log(`Discovered ${String(columnIds.length)} pipeline column(s).`);
|
|
@@ -4734,6 +4904,15 @@ async function runPipeline(client, args) {
|
|
|
4734
4904
|
` No board column name matched (after canonicalization). These overrides will never fire.`
|
|
4735
4905
|
);
|
|
4736
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
|
+
}
|
|
4737
4916
|
if (opts.dryRun) {
|
|
4738
4917
|
console.log("\n--- Dry Run Configuration ---");
|
|
4739
4918
|
console.log(`Board ID: ${opts.boardId}`);
|
|
@@ -4749,7 +4928,7 @@ async function runPipeline(client, args) {
|
|
|
4749
4928
|
console.log(`MCP config: ${mcpConfigPath}`);
|
|
4750
4929
|
console.log("\n[Dry run -- no agents started]");
|
|
4751
4930
|
cleanupMcpConfig(mcpConfigPath);
|
|
4752
|
-
cleanupGateProxyConfigs(
|
|
4931
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4753
4932
|
return;
|
|
4754
4933
|
}
|
|
4755
4934
|
let shutdownInProgress = false;
|
|
@@ -4777,7 +4956,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4777
4956
|
console.log(costTracker.generateReport(gateConfig.settings?.pricing));
|
|
4778
4957
|
}
|
|
4779
4958
|
cleanupMcpConfig(mcpConfigPath);
|
|
4780
|
-
cleanupGateProxyConfigs(
|
|
4959
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4781
4960
|
killReaper(reaperPidPath);
|
|
4782
4961
|
removePidFile(opts.boardId);
|
|
4783
4962
|
removeChildManifest(opts.boardId);
|
|
@@ -4815,7 +4994,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4815
4994
|
pidFilePath: pidFilePath(opts.boardId),
|
|
4816
4995
|
reaperPidPath,
|
|
4817
4996
|
mcpConfigPath,
|
|
4818
|
-
pipelineDir:
|
|
4997
|
+
pipelineDir: dirname2(mcpConfigPath)
|
|
4819
4998
|
});
|
|
4820
4999
|
logger.orchestrator(`Watchdog reaper spawned (PID ${String(reaperProcess.pid ?? "unknown")})`);
|
|
4821
5000
|
let eventQueue = null;
|
|
@@ -4900,7 +5079,7 @@ Received ${signal}. Shutting down gracefully...`);
|
|
|
4900
5079
|
wsClient.stop();
|
|
4901
5080
|
eventQueue.stop();
|
|
4902
5081
|
cleanupMcpConfig(mcpConfigPath);
|
|
4903
|
-
cleanupGateProxyConfigs(
|
|
5082
|
+
cleanupGateProxyConfigs(dirname2(mcpConfigPath));
|
|
4904
5083
|
killReaper(reaperPidPath);
|
|
4905
5084
|
removePidFile(opts.boardId);
|
|
4906
5085
|
removeChildManifest(opts.boardId);
|
|
@@ -4998,4 +5177,4 @@ export {
|
|
|
4998
5177
|
runPipeline,
|
|
4999
5178
|
stopPipeline
|
|
5000
5179
|
};
|
|
5001
|
-
//# sourceMappingURL=pipeline-
|
|
5180
|
+
//# sourceMappingURL=pipeline-GZOSDNPF.js.map
|