@pantheon.ai/agents 0.0.10 → 0.0.12
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 +43 -6
- package/dist/index.js +2311 -151
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { Command, createCommand } from "commander";
|
|
4
|
+
import process$1 from "node:process";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { multistream, pino, transport } from "pino";
|
|
@@ -13,6 +13,8 @@ import expandTilde from "expand-tilde";
|
|
|
13
13
|
import express, { Router } from "express";
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { URL as URL$1 } from "node:url";
|
|
16
|
+
import blessed from "reblessed";
|
|
17
|
+
import { inspect } from "node:util";
|
|
16
18
|
|
|
17
19
|
//#region \0rolldown/runtime.js
|
|
18
20
|
var __create = Object.create;
|
|
@@ -420,7 +422,7 @@ var require_cli_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
420
422
|
|
|
421
423
|
//#endregion
|
|
422
424
|
//#region package.json
|
|
423
|
-
var version$1 = "0.0.
|
|
425
|
+
var version$1 = "0.0.12";
|
|
424
426
|
|
|
425
427
|
//#endregion
|
|
426
428
|
//#region src/schemas/task-list.ts
|
|
@@ -9611,6 +9613,12 @@ async function getPantheonBranch({ projectId, branchId, getOutputIfFinished = fa
|
|
|
9611
9613
|
}
|
|
9612
9614
|
return { state: "others" };
|
|
9613
9615
|
}
|
|
9616
|
+
async function getPantheonBranchInfo({ projectId, branchId }) {
|
|
9617
|
+
return await executor.execute(getProjectBranch, {
|
|
9618
|
+
projectId,
|
|
9619
|
+
branchId
|
|
9620
|
+
}, null);
|
|
9621
|
+
}
|
|
9614
9622
|
async function executeOnPantheon({ projectId, branchId, prompt, agent }) {
|
|
9615
9623
|
return (await executor.execute(createProjectExploration, { projectId }, {
|
|
9616
9624
|
shared_prompt_sequence: [prompt],
|
|
@@ -9706,6 +9714,327 @@ async function startPendingTask(provider, task, logger) {
|
|
|
9706
9714
|
logger.info(`Task ${task.id} started successfully.`);
|
|
9707
9715
|
}
|
|
9708
9716
|
|
|
9717
|
+
//#endregion
|
|
9718
|
+
//#region src/core/watch/selector.ts
|
|
9719
|
+
const finishedTaskStatuses = [
|
|
9720
|
+
"completed",
|
|
9721
|
+
"failed",
|
|
9722
|
+
"cancelled"
|
|
9723
|
+
];
|
|
9724
|
+
function isFinishedTaskStatus(status) {
|
|
9725
|
+
return finishedTaskStatuses.includes(status);
|
|
9726
|
+
}
|
|
9727
|
+
function getTaskTimestampMs(task) {
|
|
9728
|
+
switch (task.status) {
|
|
9729
|
+
case "running": return task.started_at.getTime();
|
|
9730
|
+
case "completed": return task.ended_at.getTime();
|
|
9731
|
+
case "failed": return task.ended_at?.getTime() ?? task.started_at?.getTime() ?? 0;
|
|
9732
|
+
case "cancelled": return task.cancelled_at.getTime();
|
|
9733
|
+
case "pending": return task.queued_at.getTime();
|
|
9734
|
+
}
|
|
9735
|
+
}
|
|
9736
|
+
function pickLatestRunningTask(tasks) {
|
|
9737
|
+
const running = tasks.filter((task) => task.status === "running");
|
|
9738
|
+
running.sort((a, b) => getTaskTimestampMs(b) - getTaskTimestampMs(a));
|
|
9739
|
+
return running[0];
|
|
9740
|
+
}
|
|
9741
|
+
function pickLatestFinishedTasks(tasks, limit) {
|
|
9742
|
+
const finished = tasks.filter((task) => isFinishedTaskStatus(task.status));
|
|
9743
|
+
finished.sort((a, b) => getTaskTimestampMs(b) - getTaskTimestampMs(a));
|
|
9744
|
+
return finished.slice(0, limit);
|
|
9745
|
+
}
|
|
9746
|
+
function selectSingleAgentWatchTasks(tasks, options = {}) {
|
|
9747
|
+
const finishedLimit = options.finishedLimit ?? 3;
|
|
9748
|
+
const out = [];
|
|
9749
|
+
const running = pickLatestRunningTask(tasks);
|
|
9750
|
+
if (running) out.push(running);
|
|
9751
|
+
const finished = pickLatestFinishedTasks(tasks, finishedLimit);
|
|
9752
|
+
out.push(...finished);
|
|
9753
|
+
return out;
|
|
9754
|
+
}
|
|
9755
|
+
function selectMultiAgentWatchTasks(tasks, agentNames) {
|
|
9756
|
+
const out = [];
|
|
9757
|
+
for (const agent of agentNames) {
|
|
9758
|
+
const agentTasks = tasks.filter((entry) => entry.agent === agent).map((entry) => entry.task);
|
|
9759
|
+
const running = pickLatestRunningTask(agentTasks);
|
|
9760
|
+
if (running) {
|
|
9761
|
+
out.push({
|
|
9762
|
+
agent,
|
|
9763
|
+
task: running
|
|
9764
|
+
});
|
|
9765
|
+
continue;
|
|
9766
|
+
}
|
|
9767
|
+
const latestFinished = pickLatestFinishedTasks(agentTasks, 1)[0];
|
|
9768
|
+
if (latestFinished) out.push({
|
|
9769
|
+
agent,
|
|
9770
|
+
task: latestFinished
|
|
9771
|
+
});
|
|
9772
|
+
}
|
|
9773
|
+
return out;
|
|
9774
|
+
}
|
|
9775
|
+
function selectFallbackWatchTasks(tasks, options = {}) {
|
|
9776
|
+
const agentLimit = options.agentLimit ?? 3;
|
|
9777
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
9778
|
+
for (const entry of tasks) {
|
|
9779
|
+
const existing = byAgent.get(entry.agent);
|
|
9780
|
+
if (existing) existing.push(entry.task);
|
|
9781
|
+
else byAgent.set(entry.agent, [entry.task]);
|
|
9782
|
+
}
|
|
9783
|
+
const runningCandidates = [];
|
|
9784
|
+
const finishedCandidates = [];
|
|
9785
|
+
for (const [agent, agentTasks] of byAgent) {
|
|
9786
|
+
const running = pickLatestRunningTask(agentTasks);
|
|
9787
|
+
if (running) {
|
|
9788
|
+
runningCandidates.push({
|
|
9789
|
+
agent,
|
|
9790
|
+
task: running
|
|
9791
|
+
});
|
|
9792
|
+
continue;
|
|
9793
|
+
}
|
|
9794
|
+
const latestFinished = pickLatestFinishedTasks(agentTasks, 1)[0];
|
|
9795
|
+
if (latestFinished) finishedCandidates.push({
|
|
9796
|
+
agent,
|
|
9797
|
+
task: latestFinished
|
|
9798
|
+
});
|
|
9799
|
+
}
|
|
9800
|
+
runningCandidates.sort((a, b) => getTaskTimestampMs(b.task) - getTaskTimestampMs(a.task));
|
|
9801
|
+
finishedCandidates.sort((a, b) => getTaskTimestampMs(b.task) - getTaskTimestampMs(a.task));
|
|
9802
|
+
const selected = [];
|
|
9803
|
+
const selectedAgents = /* @__PURE__ */ new Set();
|
|
9804
|
+
for (const entry of [...runningCandidates, ...finishedCandidates]) {
|
|
9805
|
+
if (selected.length >= agentLimit) break;
|
|
9806
|
+
if (selectedAgents.has(entry.agent)) continue;
|
|
9807
|
+
selectedAgents.add(entry.agent);
|
|
9808
|
+
selected.push(entry);
|
|
9809
|
+
}
|
|
9810
|
+
return selected;
|
|
9811
|
+
}
|
|
9812
|
+
|
|
9813
|
+
//#endregion
|
|
9814
|
+
//#region src/core/watch/stream.ts
|
|
9815
|
+
async function consumeOpaqueSseStream(options) {
|
|
9816
|
+
const reader = options.stream.getReader();
|
|
9817
|
+
let ended = false;
|
|
9818
|
+
let abortedByIdleTimeout = false;
|
|
9819
|
+
let buffer = "";
|
|
9820
|
+
let idleTimer;
|
|
9821
|
+
function warn(message) {
|
|
9822
|
+
options.onWarning?.(message);
|
|
9823
|
+
}
|
|
9824
|
+
function cancelReader(reason) {
|
|
9825
|
+
try {
|
|
9826
|
+
reader.cancel(reason);
|
|
9827
|
+
} catch {}
|
|
9828
|
+
}
|
|
9829
|
+
function resetIdleTimer() {
|
|
9830
|
+
if (options.idleTimeoutMs == null) return;
|
|
9831
|
+
clearTimeout(idleTimer);
|
|
9832
|
+
idleTimer = setTimeout(() => {
|
|
9833
|
+
abortedByIdleTimeout = true;
|
|
9834
|
+
cancelReader("idle_timeout");
|
|
9835
|
+
}, options.idleTimeoutMs);
|
|
9836
|
+
}
|
|
9837
|
+
resetIdleTimer();
|
|
9838
|
+
function processEventBlock(block) {
|
|
9839
|
+
const lines = block.split("\n");
|
|
9840
|
+
for (const rawLine of lines) {
|
|
9841
|
+
const line = rawLine.trimEnd();
|
|
9842
|
+
if (!line.startsWith("data:")) continue;
|
|
9843
|
+
const part = line.slice(5).trimStart();
|
|
9844
|
+
if (!part || part === "[DONE]") continue;
|
|
9845
|
+
try {
|
|
9846
|
+
options.onData(part);
|
|
9847
|
+
resetIdleTimer();
|
|
9848
|
+
} catch (error) {
|
|
9849
|
+
warn(`onData handler threw; skipping chunk. ${error instanceof Error ? error.message : String(error)}`);
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
}
|
|
9853
|
+
const textDecoder = new TextDecoder();
|
|
9854
|
+
try {
|
|
9855
|
+
while (true) {
|
|
9856
|
+
if (options.signal?.aborted) {
|
|
9857
|
+
cancelReader("aborted");
|
|
9858
|
+
break;
|
|
9859
|
+
}
|
|
9860
|
+
const { value, done } = await reader.read();
|
|
9861
|
+
if (done) {
|
|
9862
|
+
ended = !abortedByIdleTimeout;
|
|
9863
|
+
break;
|
|
9864
|
+
}
|
|
9865
|
+
const decoded = textDecoder.decode(value, { stream: true });
|
|
9866
|
+
buffer += decoded.replaceAll("\r\n", "\n");
|
|
9867
|
+
while (true) {
|
|
9868
|
+
const sepIndex = buffer.indexOf("\n\n");
|
|
9869
|
+
if (sepIndex === -1) break;
|
|
9870
|
+
const block = buffer.slice(0, sepIndex);
|
|
9871
|
+
buffer = buffer.slice(sepIndex + 2);
|
|
9872
|
+
if (block.trim()) processEventBlock(block);
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
} finally {
|
|
9876
|
+
clearTimeout(idleTimer);
|
|
9877
|
+
}
|
|
9878
|
+
if (buffer.trim()) warn("Stream ended with trailing incomplete SSE data; ignoring remaining buffer.");
|
|
9879
|
+
return {
|
|
9880
|
+
ended,
|
|
9881
|
+
abortedByIdleTimeout
|
|
9882
|
+
};
|
|
9883
|
+
}
|
|
9884
|
+
var WatchStepAggregator = class {
|
|
9885
|
+
maxLeadingTextChars;
|
|
9886
|
+
maxLeadingReasoningChars;
|
|
9887
|
+
maxLogLines;
|
|
9888
|
+
maxWarnings;
|
|
9889
|
+
leadingText = "";
|
|
9890
|
+
leadingReasoning = "";
|
|
9891
|
+
toolActions = /* @__PURE__ */ new Map();
|
|
9892
|
+
logLines = [];
|
|
9893
|
+
warnings = [];
|
|
9894
|
+
unknownEventCount = 0;
|
|
9895
|
+
orderCounter = 0;
|
|
9896
|
+
constructor(options = {}) {
|
|
9897
|
+
this.maxLeadingTextChars = options.maxLeadingTextChars ?? 800;
|
|
9898
|
+
this.maxLeadingReasoningChars = options.maxLeadingReasoningChars ?? 800;
|
|
9899
|
+
this.maxLogLines = options.maxLogLines ?? 200;
|
|
9900
|
+
this.maxWarnings = options.maxWarnings ?? 50;
|
|
9901
|
+
}
|
|
9902
|
+
pushLog(line) {
|
|
9903
|
+
if (!line) return;
|
|
9904
|
+
this.logLines.push(line);
|
|
9905
|
+
if (this.logLines.length > this.maxLogLines) this.logLines.splice(0, this.logLines.length - this.maxLogLines);
|
|
9906
|
+
}
|
|
9907
|
+
pushWarning(message) {
|
|
9908
|
+
if (!message) return;
|
|
9909
|
+
this.warnings.push(message);
|
|
9910
|
+
if (this.warnings.length > this.maxWarnings) this.warnings.splice(0, this.warnings.length - this.maxWarnings);
|
|
9911
|
+
}
|
|
9912
|
+
pushCommandExecutionOutput(output) {
|
|
9913
|
+
const out = output;
|
|
9914
|
+
const aggregated = typeof out?.aggregated_output === "string" ? out.aggregated_output : "";
|
|
9915
|
+
const exitCode = typeof out?.exit_code === "number" ? out.exit_code : void 0;
|
|
9916
|
+
const trimmed = aggregated.replaceAll("\r\n", "\n").trimEnd();
|
|
9917
|
+
if (trimmed) trimmed.split("\n").forEach((line) => this.pushLog(line));
|
|
9918
|
+
if (exitCode != null && exitCode !== 0) this.pushLog(`exit_code=${exitCode}`);
|
|
9919
|
+
}
|
|
9920
|
+
getOrCreateToolAction(toolCallId) {
|
|
9921
|
+
const existing = this.toolActions.get(toolCallId);
|
|
9922
|
+
if (existing) return existing;
|
|
9923
|
+
const created = {
|
|
9924
|
+
toolCallId,
|
|
9925
|
+
status: "in_progress",
|
|
9926
|
+
_order: this.orderCounter++
|
|
9927
|
+
};
|
|
9928
|
+
this.toolActions.set(toolCallId, created);
|
|
9929
|
+
return created;
|
|
9930
|
+
}
|
|
9931
|
+
pushUiChunk(chunk) {
|
|
9932
|
+
if (typeof chunk !== "object" || chunk === null) return;
|
|
9933
|
+
const type = chunk.type;
|
|
9934
|
+
if (typeof type !== "string") return;
|
|
9935
|
+
switch (type) {
|
|
9936
|
+
case "text-delta": {
|
|
9937
|
+
const delta = typeof chunk.delta === "string" ? chunk.delta : "";
|
|
9938
|
+
if (!delta) return;
|
|
9939
|
+
if (this.leadingText.length < this.maxLeadingTextChars) {
|
|
9940
|
+
const remaining = this.maxLeadingTextChars - this.leadingText.length;
|
|
9941
|
+
this.leadingText += delta.slice(0, remaining);
|
|
9942
|
+
}
|
|
9943
|
+
return;
|
|
9944
|
+
}
|
|
9945
|
+
case "reasoning-delta": {
|
|
9946
|
+
const delta = typeof chunk.delta === "string" ? chunk.delta : "";
|
|
9947
|
+
if (!delta) return;
|
|
9948
|
+
if (this.leadingReasoning.length < this.maxLeadingReasoningChars) {
|
|
9949
|
+
const remaining = this.maxLeadingReasoningChars - this.leadingReasoning.length;
|
|
9950
|
+
this.leadingReasoning += delta.slice(0, remaining);
|
|
9951
|
+
}
|
|
9952
|
+
return;
|
|
9953
|
+
}
|
|
9954
|
+
case "tool-input-start": {
|
|
9955
|
+
const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : "";
|
|
9956
|
+
if (!toolCallId) return;
|
|
9957
|
+
const toolName = typeof chunk.toolName === "string" ? chunk.toolName : void 0;
|
|
9958
|
+
const toolAction = this.getOrCreateToolAction(toolCallId);
|
|
9959
|
+
toolAction.toolName = toolName ?? toolAction.toolName;
|
|
9960
|
+
toolAction.status = "in_progress";
|
|
9961
|
+
if (toolAction.toolName !== "command_execution") this.pushLog(`→ tool ${toolAction.toolName ?? "<tool>"} (${toolCallId})`);
|
|
9962
|
+
return;
|
|
9963
|
+
}
|
|
9964
|
+
case "tool-input-delta": {
|
|
9965
|
+
const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : "";
|
|
9966
|
+
if (!toolCallId) return;
|
|
9967
|
+
const toolAction = this.getOrCreateToolAction(toolCallId);
|
|
9968
|
+
toolAction.status = "in_progress";
|
|
9969
|
+
return;
|
|
9970
|
+
}
|
|
9971
|
+
case "tool-input-available": {
|
|
9972
|
+
const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : "";
|
|
9973
|
+
if (!toolCallId) return;
|
|
9974
|
+
const toolName = typeof chunk.toolName === "string" ? chunk.toolName : void 0;
|
|
9975
|
+
const title = typeof chunk.title === "string" ? chunk.title : void 0;
|
|
9976
|
+
const input = chunk.input;
|
|
9977
|
+
const toolAction = this.getOrCreateToolAction(toolCallId);
|
|
9978
|
+
toolAction.toolName = toolName ?? toolAction.toolName;
|
|
9979
|
+
toolAction.title = title ?? toolAction.title;
|
|
9980
|
+
toolAction.input = input ?? toolAction.input;
|
|
9981
|
+
toolAction.status = "in_progress";
|
|
9982
|
+
const toolLabel = toolAction.title ? `${toolAction.toolName ?? "<tool>"}: ${toolAction.title}` : `${toolAction.toolName ?? "<tool>"}`;
|
|
9983
|
+
if (toolAction.toolName !== "command_execution") this.pushLog(`→ tool ${toolLabel} (${toolCallId})`);
|
|
9984
|
+
return;
|
|
9985
|
+
}
|
|
9986
|
+
case "tool-output-available": {
|
|
9987
|
+
const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : "";
|
|
9988
|
+
if (!toolCallId) return;
|
|
9989
|
+
const output = chunk.output;
|
|
9990
|
+
const toolAction = this.getOrCreateToolAction(toolCallId);
|
|
9991
|
+
toolAction.output = output;
|
|
9992
|
+
toolAction.status = "completed";
|
|
9993
|
+
if (toolAction.toolName === "command_execution") {
|
|
9994
|
+
this.pushCommandExecutionOutput(output);
|
|
9995
|
+
return;
|
|
9996
|
+
}
|
|
9997
|
+
this.pushLog(`← tool ${toolAction.toolName ?? "<tool>"} ok (${toolCallId})`);
|
|
9998
|
+
return;
|
|
9999
|
+
}
|
|
10000
|
+
case "tool-output-error":
|
|
10001
|
+
case "tool-input-error": {
|
|
10002
|
+
const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : "";
|
|
10003
|
+
if (!toolCallId) return;
|
|
10004
|
+
const errorText = typeof chunk.errorText === "string" ? chunk.errorText : "";
|
|
10005
|
+
const toolAction = this.getOrCreateToolAction(toolCallId);
|
|
10006
|
+
toolAction.errorText = errorText || toolAction.errorText;
|
|
10007
|
+
toolAction.status = "failed";
|
|
10008
|
+
this.pushLog(`← tool ${toolAction.toolName ?? "<tool>"} error (${toolCallId})`);
|
|
10009
|
+
return;
|
|
10010
|
+
}
|
|
10011
|
+
case "data-agent-unknown": {
|
|
10012
|
+
this.unknownEventCount++;
|
|
10013
|
+
const data = chunk.data;
|
|
10014
|
+
const reason = typeof data?.reason === "string" ? data.reason : "unknown chunk";
|
|
10015
|
+
this.pushWarning(reason);
|
|
10016
|
+
this.pushLog(`! unknown: ${reason}`);
|
|
10017
|
+
return;
|
|
10018
|
+
}
|
|
10019
|
+
default: return;
|
|
10020
|
+
}
|
|
10021
|
+
}
|
|
10022
|
+
pushUiChunks(chunks) {
|
|
10023
|
+
chunks.forEach((chunk) => this.pushUiChunk(chunk));
|
|
10024
|
+
}
|
|
10025
|
+
snapshot() {
|
|
10026
|
+
const actions = Array.from(this.toolActions.values()).sort((a, b) => a._order - b._order).map(({ _order: _unused, ...rest }) => rest);
|
|
10027
|
+
return {
|
|
10028
|
+
leadingText: this.leadingText,
|
|
10029
|
+
leadingReasoning: this.leadingReasoning,
|
|
10030
|
+
toolActions: actions,
|
|
10031
|
+
logLines: [...this.logLines],
|
|
10032
|
+
warnings: [...this.warnings],
|
|
10033
|
+
unknownEventCount: this.unknownEventCount
|
|
10034
|
+
};
|
|
10035
|
+
}
|
|
10036
|
+
};
|
|
10037
|
+
|
|
9709
10038
|
//#endregion
|
|
9710
10039
|
//#region src/core/index.ts
|
|
9711
10040
|
function normalizeSkills(value) {
|
|
@@ -9780,38 +10109,37 @@ async function runAgent(name, options, logger) {
|
|
|
9780
10109
|
}
|
|
9781
10110
|
async function configAgent(name, options) {
|
|
9782
10111
|
const provider = new TaskListTidbProvider(name, pino());
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
process.exitCode = 1;
|
|
9795
|
-
return;
|
|
9796
|
-
}
|
|
9797
|
-
console.log(`Configuring agent ${name} as ${options.role} for project ${options.projectId}.`);
|
|
9798
|
-
if (!options.rootBranchId) {
|
|
9799
|
-
console.log("No root branch id specified, using project root branch.");
|
|
9800
|
-
const project = await getPantheonProjectInfo({ projectId: options.projectId });
|
|
9801
|
-
if (!project.root_branch_id) {
|
|
9802
|
-
console.error(`Project ${options.projectId} has no root branch. Project status is ${project.status}`);
|
|
9803
|
-
await provider.close();
|
|
10112
|
+
try {
|
|
10113
|
+
const previousConfig = await provider.getAgentConfig(options.projectId);
|
|
10114
|
+
if (previousConfig) {
|
|
10115
|
+
if (previousConfig.role === options.role) {
|
|
10116
|
+
console.log(`Agent ${name} already configured as ${options.role} for project ${options.projectId}.`);
|
|
10117
|
+
console.log(`Base branch id: ${previousConfig.base_branch_id}`);
|
|
10118
|
+
return;
|
|
10119
|
+
}
|
|
10120
|
+
console.error(`Agent ${name} already configured as ${previousConfig.role} for project ${options.projectId}.`);
|
|
10121
|
+
console.error(`Base branch id: ${previousConfig.base_branch_id}`);
|
|
10122
|
+
console.error(`Cannot change role to ${options.role}`);
|
|
9804
10123
|
process.exitCode = 1;
|
|
9805
10124
|
return;
|
|
9806
10125
|
}
|
|
9807
|
-
options.
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
10126
|
+
console.log(`Configuring agent ${name} as ${options.role} for project ${options.projectId}.`);
|
|
10127
|
+
if (!options.rootBranchId) {
|
|
10128
|
+
console.log("No root branch id specified, using project root branch.");
|
|
10129
|
+
const project = await getPantheonProjectInfo({ projectId: options.projectId });
|
|
10130
|
+
if (!project.root_branch_id) {
|
|
10131
|
+
console.error(`Project ${options.projectId} has no root branch. Project status is ${project.status}`);
|
|
10132
|
+
process.exitCode = 1;
|
|
10133
|
+
return;
|
|
10134
|
+
}
|
|
10135
|
+
options.rootBranchId = project.root_branch_id;
|
|
10136
|
+
}
|
|
10137
|
+
if (options.bootstrap) {
|
|
10138
|
+
const branchId = await executeOnPantheon({
|
|
10139
|
+
projectId: options.projectId,
|
|
10140
|
+
branchId: options.rootBranchId,
|
|
10141
|
+
agent: "codex",
|
|
10142
|
+
prompt: `You must follow these instructions":
|
|
9815
10143
|
1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
|
|
9816
10144
|
2. Copy the <pantheon-agents>/agents/${options.role}/AGENTS.md to \`<workspace>/AGENTS.md\`.
|
|
9817
10145
|
3. Copy the <pantheon-agents>/agents/${options.role}/skills directory to \`<workspace>/.codex/skills\` if the source directory exists.
|
|
@@ -9825,58 +10153,61 @@ Validate <workspace>: check if files and directorys exists:
|
|
|
9825
10153
|
|
|
9826
10154
|
Finally, outputs the first 5 lines of <workspace>/AGENTS.md and the skills list in <workspace>/.codex/skills
|
|
9827
10155
|
`
|
|
9828
|
-
});
|
|
9829
|
-
let retried = 0;
|
|
9830
|
-
const maxRetries = 3;
|
|
9831
|
-
let i = 0;
|
|
9832
|
-
console.log(`Bootstrap branch created: ${branchId}. Waiting for ready... [poll interval = 10s]`);
|
|
9833
|
-
while (true) {
|
|
9834
|
-
await new Promise((resolve) => {
|
|
9835
|
-
setTimeout(resolve, 1e4);
|
|
9836
10156
|
});
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
10157
|
+
let retried = 0;
|
|
10158
|
+
const maxRetries = 3;
|
|
10159
|
+
let i = 0;
|
|
10160
|
+
console.log(`Bootstrap branch created: ${branchId}. Waiting for ready... [poll interval = 10s]`);
|
|
10161
|
+
while (true) {
|
|
10162
|
+
await new Promise((resolve) => {
|
|
10163
|
+
setTimeout(resolve, 1e4);
|
|
10164
|
+
});
|
|
10165
|
+
const result = await getPantheonBranch({
|
|
10166
|
+
branchId,
|
|
10167
|
+
projectId: options.projectId,
|
|
10168
|
+
getOutputIfFinished: true
|
|
10169
|
+
}).then((result) => {
|
|
10170
|
+
retried = 0;
|
|
10171
|
+
return result;
|
|
10172
|
+
}).catch((reason) => {
|
|
10173
|
+
if (retried < maxRetries) {
|
|
10174
|
+
retried++;
|
|
10175
|
+
return { state: "others" };
|
|
10176
|
+
}
|
|
10177
|
+
throw new Error(`Failed to get bootstrap branch status. Retry ${retried} times. Last error: ${getErrorMessage(reason)}`);
|
|
10178
|
+
});
|
|
10179
|
+
if (result.state === "failed") {
|
|
10180
|
+
console.error("Bootstrap failed: " + result.error);
|
|
10181
|
+
process.exitCode = 1;
|
|
10182
|
+
return;
|
|
10183
|
+
}
|
|
10184
|
+
if (result.state === "succeed") {
|
|
10185
|
+
console.log("Bootstrap succeeded. Output is:");
|
|
10186
|
+
console.log(result.output);
|
|
10187
|
+
break;
|
|
10188
|
+
}
|
|
10189
|
+
console.log(`Bootstrap in progress... [${++i}]`);
|
|
10190
|
+
}
|
|
10191
|
+
await provider.setAgentConfig({
|
|
10192
|
+
project_id: options.projectId,
|
|
10193
|
+
base_branch_id: branchId,
|
|
10194
|
+
execute_agent: options.executeAgent,
|
|
10195
|
+
role: options.role,
|
|
10196
|
+
skills: options.skills,
|
|
10197
|
+
prototype_url: options.prototypeUrl
|
|
9849
10198
|
});
|
|
9850
|
-
|
|
9851
|
-
console.error("Bootstrap failed: " + result.error);
|
|
9852
|
-
await provider.close();
|
|
9853
|
-
process.exit(1);
|
|
9854
|
-
}
|
|
9855
|
-
if (result.state === "succeed") {
|
|
9856
|
-
console.log("Bootstrap succeeded. Output is:");
|
|
9857
|
-
console.log(result.output);
|
|
9858
|
-
break;
|
|
9859
|
-
}
|
|
9860
|
-
console.log(`Bootstrap in progress... [${++i}]`);
|
|
9861
|
-
}
|
|
9862
|
-
await provider.setAgentConfig({
|
|
10199
|
+
} else await provider.setAgentConfig({
|
|
9863
10200
|
project_id: options.projectId,
|
|
9864
|
-
base_branch_id:
|
|
10201
|
+
base_branch_id: options.rootBranchId,
|
|
9865
10202
|
execute_agent: options.executeAgent,
|
|
9866
10203
|
role: options.role,
|
|
9867
10204
|
skills: options.skills,
|
|
9868
10205
|
prototype_url: options.prototypeUrl
|
|
9869
10206
|
});
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
role: options.role,
|
|
9875
|
-
skills: options.skills,
|
|
9876
|
-
prototype_url: options.prototypeUrl
|
|
9877
|
-
});
|
|
9878
|
-
console.log(`Agent ${name} configured successfully.`);
|
|
9879
|
-
await provider.close();
|
|
10207
|
+
console.log(`Agent ${name} configured successfully.`);
|
|
10208
|
+
} finally {
|
|
10209
|
+
await provider.close();
|
|
10210
|
+
}
|
|
9880
10211
|
}
|
|
9881
10212
|
async function reconfigAgentWithDeps(name, options, deps) {
|
|
9882
10213
|
const previousConfig = await deps.provider.getAgentConfig(options.projectId);
|
|
@@ -9939,15 +10270,18 @@ async function reconfigAgent(name, options) {
|
|
|
9939
10270
|
}
|
|
9940
10271
|
async function addTask(name, options) {
|
|
9941
10272
|
const provider = new TaskListTidbProvider(name, pino());
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
task
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
10273
|
+
try {
|
|
10274
|
+
const config = await provider.getAgentConfig(options.projectId);
|
|
10275
|
+
if (!config) throw new Error(`Agent ${name} not configured for project ${options.projectId}`);
|
|
10276
|
+
const task = await provider.createTask({
|
|
10277
|
+
task: options.prompt,
|
|
10278
|
+
project_id: options.projectId,
|
|
10279
|
+
base_branch_id: config.base_branch_id
|
|
10280
|
+
});
|
|
10281
|
+
console.log(`Queued task ${task.id} successfully.`);
|
|
10282
|
+
} finally {
|
|
10283
|
+
await provider.close();
|
|
10284
|
+
}
|
|
9951
10285
|
}
|
|
9952
10286
|
async function deleteTask(agentName, taskId) {
|
|
9953
10287
|
const provider = new TaskListTidbProvider(agentName, pino());
|
|
@@ -10102,11 +10436,22 @@ async function assertsSingleton(logger, pidFile) {
|
|
|
10102
10436
|
}
|
|
10103
10437
|
}
|
|
10104
10438
|
|
|
10439
|
+
//#endregion
|
|
10440
|
+
//#region src/cli/utils/env.ts
|
|
10441
|
+
function ensureEnv(keys) {
|
|
10442
|
+
const missing = keys.filter((key) => !process.env[key]);
|
|
10443
|
+
if (missing.length === 0) return true;
|
|
10444
|
+
for (const key of missing) console.error(`${key} environment variable is not set.`);
|
|
10445
|
+
process.exitCode = 1;
|
|
10446
|
+
return false;
|
|
10447
|
+
}
|
|
10448
|
+
|
|
10105
10449
|
//#endregion
|
|
10106
10450
|
//#region src/cli/commands/add-task.ts
|
|
10107
10451
|
function createAddTaskCommand(version) {
|
|
10108
|
-
return createCommand("
|
|
10452
|
+
return createCommand("add-task").version(version).description("Add a task to an agent").argument("<name>", "The name of the agent.").argument("<project-id>", "The project id of the agent.").argument("<task-prompt>", "The prompt of the task.").action(async function() {
|
|
10109
10453
|
const [name, projectId, taskPrompt] = this.args;
|
|
10454
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
10110
10455
|
await addTask(name, {
|
|
10111
10456
|
projectId,
|
|
10112
10457
|
prompt: taskPrompt
|
|
@@ -10117,12 +10462,16 @@ function createAddTaskCommand(version) {
|
|
|
10117
10462
|
//#endregion
|
|
10118
10463
|
//#region src/cli/commands/config.ts
|
|
10119
10464
|
function createConfigAgentCommand(version) {
|
|
10120
|
-
return createCommand("
|
|
10465
|
+
return createCommand("config").version(version).description("Configure agent for pantheon project").argument("<name>", "The name of the agent.").argument("<role>", "The role of the agent.").argument("<project-id>", "The project id of the agent.").option("--skills <skills>", "The skills of the agent. Multiple values are separated by comma.", (val) => val.split(",").map((s) => s.trim()).filter((s) => s !== ""), []).option("--execute-agent <agent>", "The execute agent of the agent.", "codex").option("--root-branch-id <branchId>", "The root branch id of the agent. Default to project root branch id.").option("--prototype-url <url>", "Role and skill definitions repo.", "https://github.com/pingcap-inc/pantheon-agents").option("--no-bootstrap", "Prevent bootstrap base branch for agent. Use the root branch as base branch.").action(async function() {
|
|
10121
10466
|
const [name, role, projectId] = this.args;
|
|
10467
|
+
const options = this.opts();
|
|
10468
|
+
const requiredEnvVars = ["DATABASE_URL"];
|
|
10469
|
+
if (options.bootstrap || !options.rootBranchId) requiredEnvVars.push("PANTHEON_API_KEY");
|
|
10470
|
+
if (!ensureEnv(requiredEnvVars)) return;
|
|
10122
10471
|
await configAgent(name, {
|
|
10123
10472
|
role,
|
|
10124
10473
|
projectId,
|
|
10125
|
-
...
|
|
10474
|
+
...options
|
|
10126
10475
|
});
|
|
10127
10476
|
});
|
|
10128
10477
|
}
|
|
@@ -10130,8 +10479,9 @@ function createConfigAgentCommand(version) {
|
|
|
10130
10479
|
//#endregion
|
|
10131
10480
|
//#region src/cli/commands/delete-task.ts
|
|
10132
10481
|
function createDeleteTaskCommand(version) {
|
|
10133
|
-
return createCommand("
|
|
10482
|
+
return createCommand("delete-task").version(version).description("Delete a task for an agent").argument("<name>", "The name of the agent.").argument("<task-id>", "The id of the task.").action(async function() {
|
|
10134
10483
|
const [name, taskId] = this.args;
|
|
10484
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
10135
10485
|
const rl = readline.createInterface({
|
|
10136
10486
|
input: process$1.stdin,
|
|
10137
10487
|
output: process$1.stdout
|
|
@@ -10139,16 +10489,19 @@ function createDeleteTaskCommand(version) {
|
|
|
10139
10489
|
try {
|
|
10140
10490
|
if ((await rl.question(`Type the task id (${taskId}) to confirm deletion: `)).trim() !== taskId) {
|
|
10141
10491
|
console.error("Confirmation failed. Task id did not match.");
|
|
10142
|
-
process$1.
|
|
10492
|
+
process$1.exitCode = 1;
|
|
10493
|
+
return;
|
|
10143
10494
|
}
|
|
10144
10495
|
if ((await rl.question("Type DELETE to permanently remove this task: ")).trim() !== "DELETE") {
|
|
10145
10496
|
console.error("Confirmation failed. Aborting deletion.");
|
|
10146
|
-
process$1.
|
|
10497
|
+
process$1.exitCode = 1;
|
|
10498
|
+
return;
|
|
10147
10499
|
}
|
|
10148
10500
|
const deletedTask = await deleteTask(name, taskId);
|
|
10149
10501
|
if (!deletedTask) {
|
|
10150
10502
|
console.error(`Task ${taskId} not found for agent ${name}.`);
|
|
10151
|
-
process$1.
|
|
10503
|
+
process$1.exitCode = 1;
|
|
10504
|
+
return;
|
|
10152
10505
|
}
|
|
10153
10506
|
console.log(`Deleted task ${taskId} for agent ${name}. Status was ${deletedTask.status}.`);
|
|
10154
10507
|
} finally {
|
|
@@ -10177,33 +10530,31 @@ function parseUniqueCommaList(value) {
|
|
|
10177
10530
|
//#endregion
|
|
10178
10531
|
//#region src/cli/commands/reconfig.ts
|
|
10179
10532
|
function createReconfigAgentCommand(version) {
|
|
10180
|
-
return createCommand("
|
|
10533
|
+
return createCommand("reconfig").version(version).description("Re-bootstrap an existing agent configuration for a project").argument("<name>", "The name of the agent.").argument("<project-id>", "The project id of the agent.").option("--role <role>", "Override role for the agent.").option("--skills <skills>", "Override skills for the agent (comma-separated, replaces existing list).", parseUniqueCommaList).action(async function() {
|
|
10181
10534
|
const [name, projectId] = this.args;
|
|
10182
10535
|
const options = this.opts();
|
|
10536
|
+
if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
|
|
10183
10537
|
const resolvedRole = options.role?.trim();
|
|
10184
10538
|
if (options.role != null && !resolvedRole) {
|
|
10185
10539
|
console.error("--role must be non-empty.");
|
|
10186
|
-
process$1.
|
|
10187
|
-
|
|
10188
|
-
try {
|
|
10189
|
-
await reconfigAgent(name, {
|
|
10190
|
-
projectId,
|
|
10191
|
-
role: resolvedRole,
|
|
10192
|
-
skills: options.skills
|
|
10193
|
-
});
|
|
10194
|
-
} catch (error) {
|
|
10195
|
-
console.error(getErrorMessage(error));
|
|
10196
|
-
process$1.exit(1);
|
|
10540
|
+
process$1.exitCode = 1;
|
|
10541
|
+
return;
|
|
10197
10542
|
}
|
|
10543
|
+
await reconfigAgent(name, {
|
|
10544
|
+
projectId,
|
|
10545
|
+
role: resolvedRole,
|
|
10546
|
+
skills: options.skills
|
|
10547
|
+
});
|
|
10198
10548
|
});
|
|
10199
10549
|
}
|
|
10200
10550
|
|
|
10201
10551
|
//#endregion
|
|
10202
10552
|
//#region src/cli/commands/run.ts
|
|
10203
10553
|
function createRunAgentCommand(version) {
|
|
10204
|
-
return createCommand("
|
|
10554
|
+
return createCommand("run").version(version).description("Start a pantheon agent").argument("<name>", "The name of the agent.").option("--data-dir [dir]", "Data directory.", expandTilde("~/.pantheon-agents")).option("--loop-interval <seconds>", "The interval of the loop in seconds. Defaults to 5.", (val) => parseInt(val, 10), 5).action(async function() {
|
|
10205
10555
|
const [name] = this.args;
|
|
10206
10556
|
const options = this.opts();
|
|
10557
|
+
if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
|
|
10207
10558
|
const logFileTransport = transport({
|
|
10208
10559
|
target: "pino-roll",
|
|
10209
10560
|
options: {
|
|
@@ -21793,6 +22144,141 @@ function taskToDTO(agent, task) {
|
|
|
21793
22144
|
error: task.status === "failed" ? task.error : null
|
|
21794
22145
|
};
|
|
21795
22146
|
}
|
|
22147
|
+
function agentConfigToDTO(config) {
|
|
22148
|
+
return {
|
|
22149
|
+
agent: config.agent,
|
|
22150
|
+
project_id: config.project_id,
|
|
22151
|
+
base_branch_id: config.base_branch_id,
|
|
22152
|
+
role: config.role,
|
|
22153
|
+
skills: config.skills,
|
|
22154
|
+
prototype_url: config.prototype_url,
|
|
22155
|
+
execute_agent: config.execute_agent
|
|
22156
|
+
};
|
|
22157
|
+
}
|
|
22158
|
+
|
|
22159
|
+
//#endregion
|
|
22160
|
+
//#region src/server/agent-config.ts
|
|
22161
|
+
function assertPantheonApiKeyConfigured() {
|
|
22162
|
+
if (!process.env.PANTHEON_API_KEY?.trim()) throw new ApiError({
|
|
22163
|
+
status: 503,
|
|
22164
|
+
code: "pantheon_api_key_missing",
|
|
22165
|
+
message: "Missing PANTHEON_API_KEY. This endpoint requires Pantheon API access."
|
|
22166
|
+
});
|
|
22167
|
+
}
|
|
22168
|
+
async function getBranchOutputOrThrow(options) {
|
|
22169
|
+
let state;
|
|
22170
|
+
try {
|
|
22171
|
+
state = await options.getPantheonBranchFn({
|
|
22172
|
+
projectId: options.projectId,
|
|
22173
|
+
branchId: options.branchId,
|
|
22174
|
+
getOutputIfFinished: true,
|
|
22175
|
+
manifestingAsSucceed: true
|
|
22176
|
+
});
|
|
22177
|
+
} catch (error) {
|
|
22178
|
+
throw new ApiError({
|
|
22179
|
+
status: 502,
|
|
22180
|
+
code: "pantheon_api_error",
|
|
22181
|
+
message: `Failed to fetch Pantheon branch output for ${options.branchId}.`,
|
|
22182
|
+
details: {
|
|
22183
|
+
project_id: options.projectId,
|
|
22184
|
+
branch_id: options.branchId,
|
|
22185
|
+
error: getErrorMessage(error)
|
|
22186
|
+
}
|
|
22187
|
+
});
|
|
22188
|
+
}
|
|
22189
|
+
if (state.state === "succeed") return state.output ?? "";
|
|
22190
|
+
if (state.state === "failed") throw new ApiError({
|
|
22191
|
+
status: 502,
|
|
22192
|
+
code: "pantheon_branch_failed",
|
|
22193
|
+
message: `Pantheon branch ${options.branchId} failed.`,
|
|
22194
|
+
details: {
|
|
22195
|
+
project_id: options.projectId,
|
|
22196
|
+
branch_id: options.branchId,
|
|
22197
|
+
error: state.error
|
|
22198
|
+
}
|
|
22199
|
+
});
|
|
22200
|
+
throw new ApiError({
|
|
22201
|
+
status: 502,
|
|
22202
|
+
code: "pantheon_branch_not_ready",
|
|
22203
|
+
message: `Pantheon branch ${options.branchId} is not finished yet.`,
|
|
22204
|
+
details: {
|
|
22205
|
+
project_id: options.projectId,
|
|
22206
|
+
branch_id: options.branchId
|
|
22207
|
+
}
|
|
22208
|
+
});
|
|
22209
|
+
}
|
|
22210
|
+
async function getAgentConfigWithBootstrapOutput(options) {
|
|
22211
|
+
assertPantheonApiKeyConfigured();
|
|
22212
|
+
const config = await options.provider.getAgentConfig(options.projectId);
|
|
22213
|
+
if (!config) throw new ApiError({
|
|
22214
|
+
status: 404,
|
|
22215
|
+
code: "config_not_found",
|
|
22216
|
+
message: `No config found for agent ${options.agent} and project ${options.projectId}.`
|
|
22217
|
+
});
|
|
22218
|
+
const bootstrapOutput = await getBranchOutputOrThrow({
|
|
22219
|
+
projectId: config.project_id,
|
|
22220
|
+
branchId: config.base_branch_id,
|
|
22221
|
+
getPantheonBranchFn: options.getPantheonBranchFn ?? getPantheonBranch
|
|
22222
|
+
});
|
|
22223
|
+
return {
|
|
22224
|
+
config: agentConfigToDTO(config),
|
|
22225
|
+
bootstrap_output: bootstrapOutput
|
|
22226
|
+
};
|
|
22227
|
+
}
|
|
22228
|
+
async function reconfigAgentConfigWithBootstrapOutput(options) {
|
|
22229
|
+
assertPantheonApiKeyConfigured();
|
|
22230
|
+
const getPantheonBranchFn = options.getPantheonBranchFn ?? getPantheonBranch;
|
|
22231
|
+
const wrappedGetPantheonBranch = (args) => getPantheonBranchFn({
|
|
22232
|
+
...args,
|
|
22233
|
+
manifestingAsSucceed: true
|
|
22234
|
+
});
|
|
22235
|
+
let result;
|
|
22236
|
+
try {
|
|
22237
|
+
result = await reconfigAgentWithDeps(options.agent, options.reconfig, {
|
|
22238
|
+
provider: options.provider,
|
|
22239
|
+
executeOnPantheonFn: options.executeOnPantheonFn ?? executeOnPantheon,
|
|
22240
|
+
getPantheonBranchFn: wrappedGetPantheonBranch,
|
|
22241
|
+
sleep: options.sleep,
|
|
22242
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
22243
|
+
maxRetries: options.maxRetries
|
|
22244
|
+
});
|
|
22245
|
+
} catch (error) {
|
|
22246
|
+
if (error instanceof ApiError) throw error;
|
|
22247
|
+
const message = getErrorMessage(error);
|
|
22248
|
+
if (message.startsWith("No config found for agent ")) throw new ApiError({
|
|
22249
|
+
status: 404,
|
|
22250
|
+
code: "config_not_found",
|
|
22251
|
+
message
|
|
22252
|
+
});
|
|
22253
|
+
if (message === "--role must be non-empty.") throw new ApiError({
|
|
22254
|
+
status: 400,
|
|
22255
|
+
code: "validation_error",
|
|
22256
|
+
message
|
|
22257
|
+
});
|
|
22258
|
+
if (message.startsWith("Bootstrap failed:")) throw new ApiError({
|
|
22259
|
+
status: 502,
|
|
22260
|
+
code: "bootstrap_failed",
|
|
22261
|
+
message
|
|
22262
|
+
});
|
|
22263
|
+
throw new ApiError({
|
|
22264
|
+
status: 502,
|
|
22265
|
+
code: "reconfig_failed",
|
|
22266
|
+
message,
|
|
22267
|
+
details: { error: message }
|
|
22268
|
+
});
|
|
22269
|
+
}
|
|
22270
|
+
const updated = await options.provider.getAgentConfig(result.previousConfig.project_id);
|
|
22271
|
+
if (!updated) throw new ApiError({
|
|
22272
|
+
status: 500,
|
|
22273
|
+
code: "config_update_lost",
|
|
22274
|
+
message: "Config disappeared after reconfig update."
|
|
22275
|
+
});
|
|
22276
|
+
return {
|
|
22277
|
+
previous_config: agentConfigToDTO(result.previousConfig),
|
|
22278
|
+
config: agentConfigToDTO(updated),
|
|
22279
|
+
bootstrap_output: result.bootstrapOutput
|
|
22280
|
+
};
|
|
22281
|
+
}
|
|
21796
22282
|
|
|
21797
22283
|
//#endregion
|
|
21798
22284
|
//#region src/server/api.ts
|
|
@@ -21831,6 +22317,10 @@ const createTaskBodySchema = z$1.object({
|
|
|
21831
22317
|
project_id: z$1.string().min(1),
|
|
21832
22318
|
task: z$1.string().min(1)
|
|
21833
22319
|
});
|
|
22320
|
+
const reconfigBodySchema = z$1.object({
|
|
22321
|
+
role: z$1.string().trim().min(1).optional(),
|
|
22322
|
+
skills: z$1.array(z$1.string().trim().min(1)).optional()
|
|
22323
|
+
});
|
|
21834
22324
|
function getAgentParam(value) {
|
|
21835
22325
|
if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
|
|
21836
22326
|
status: 400,
|
|
@@ -21839,6 +22329,14 @@ function getAgentParam(value) {
|
|
|
21839
22329
|
});
|
|
21840
22330
|
return value;
|
|
21841
22331
|
}
|
|
22332
|
+
function getProjectIdParam(value) {
|
|
22333
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
|
|
22334
|
+
status: 400,
|
|
22335
|
+
code: "validation_error",
|
|
22336
|
+
message: "Invalid project_id parameter."
|
|
22337
|
+
});
|
|
22338
|
+
return value;
|
|
22339
|
+
}
|
|
21842
22340
|
function getTaskIdParam(value) {
|
|
21843
22341
|
if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
|
|
21844
22342
|
status: 400,
|
|
@@ -21854,6 +22352,31 @@ function createApiRouter(options) {
|
|
|
21854
22352
|
const agents = await new TaskListTidbProvider("server", logger, { db }).listAgentNames();
|
|
21855
22353
|
res.json({ agents });
|
|
21856
22354
|
}));
|
|
22355
|
+
router.get("/agents/:agent/configs/:project_id", asyncHandler(async (req, res) => {
|
|
22356
|
+
const agent = getAgentParam(req.params.agent);
|
|
22357
|
+
const result = await getAgentConfigWithBootstrapOutput({
|
|
22358
|
+
agent,
|
|
22359
|
+
projectId: getProjectIdParam(req.params.project_id),
|
|
22360
|
+
provider: new TaskListTidbProvider(agent, logger, { db })
|
|
22361
|
+
});
|
|
22362
|
+
res.json(result);
|
|
22363
|
+
}));
|
|
22364
|
+
router.post("/agents/:agent/configs/:project_id/reconfig", asyncHandler(async (req, res) => {
|
|
22365
|
+
const agent = getAgentParam(req.params.agent);
|
|
22366
|
+
const projectId = getProjectIdParam(req.params.project_id);
|
|
22367
|
+
const body = reconfigBodySchema.parse(req.body);
|
|
22368
|
+
const provider = new TaskListTidbProvider(agent, logger, { db });
|
|
22369
|
+
const result = await reconfigAgentConfigWithBootstrapOutput({
|
|
22370
|
+
agent,
|
|
22371
|
+
reconfig: {
|
|
22372
|
+
projectId,
|
|
22373
|
+
role: body.role,
|
|
22374
|
+
skills: body.skills
|
|
22375
|
+
},
|
|
22376
|
+
provider
|
|
22377
|
+
});
|
|
22378
|
+
res.json(result);
|
|
22379
|
+
}));
|
|
21857
22380
|
router.get("/agents/:agent/tasks", asyncHandler(async (req, res) => {
|
|
21858
22381
|
const agent = getAgentParam(req.params.agent);
|
|
21859
22382
|
const query = listTasksQuerySchema.parse(req.query);
|
|
@@ -31721,6 +32244,51 @@ function createAgentsMcpServer(options) {
|
|
|
31721
32244
|
structuredContent: { deleted: true }
|
|
31722
32245
|
};
|
|
31723
32246
|
});
|
|
32247
|
+
server.registerTool("configs.get", {
|
|
32248
|
+
description: "Get an agent's config for a project, including the bootstrap branch output.",
|
|
32249
|
+
inputSchema: {
|
|
32250
|
+
agent: z$1.string().min(1),
|
|
32251
|
+
project_id: z$1.string().min(1)
|
|
32252
|
+
}
|
|
32253
|
+
}, async ({ agent, project_id }) => {
|
|
32254
|
+
return {
|
|
32255
|
+
content: [{
|
|
32256
|
+
type: "text",
|
|
32257
|
+
text: "OK"
|
|
32258
|
+
}],
|
|
32259
|
+
structuredContent: await getAgentConfigWithBootstrapOutput({
|
|
32260
|
+
agent,
|
|
32261
|
+
projectId: project_id,
|
|
32262
|
+
provider: new TaskListTidbProvider(agent, options.logger, { db: options.db })
|
|
32263
|
+
})
|
|
32264
|
+
};
|
|
32265
|
+
});
|
|
32266
|
+
server.registerTool("configs.reconfig", {
|
|
32267
|
+
description: "Re-bootstrap an existing agent configuration for a project (like `pantheon-agents reconfig`).",
|
|
32268
|
+
inputSchema: {
|
|
32269
|
+
agent: z$1.string().min(1),
|
|
32270
|
+
project_id: z$1.string().min(1),
|
|
32271
|
+
role: z$1.string().trim().min(1).optional(),
|
|
32272
|
+
skills: z$1.array(z$1.string().trim().min(1)).optional()
|
|
32273
|
+
}
|
|
32274
|
+
}, async ({ agent, project_id, role, skills }) => {
|
|
32275
|
+
const provider = new TaskListTidbProvider(agent, options.logger, { db: options.db });
|
|
32276
|
+
return {
|
|
32277
|
+
content: [{
|
|
32278
|
+
type: "text",
|
|
32279
|
+
text: "OK"
|
|
32280
|
+
}],
|
|
32281
|
+
structuredContent: await reconfigAgentConfigWithBootstrapOutput({
|
|
32282
|
+
agent,
|
|
32283
|
+
reconfig: {
|
|
32284
|
+
projectId: project_id,
|
|
32285
|
+
role,
|
|
32286
|
+
skills
|
|
32287
|
+
},
|
|
32288
|
+
provider
|
|
32289
|
+
})
|
|
32290
|
+
};
|
|
32291
|
+
});
|
|
31724
32292
|
return server;
|
|
31725
32293
|
}
|
|
31726
32294
|
|
|
@@ -31831,11 +32399,13 @@ async function startAgentsServer(options, logger, overrides = {}) {
|
|
|
31831
32399
|
//#endregion
|
|
31832
32400
|
//#region src/cli/commands/server.ts
|
|
31833
32401
|
function createServerCommand(version) {
|
|
31834
|
-
return createCommand("
|
|
32402
|
+
return createCommand("server").version(version).description("Start the task HTTP API + MCP server").option("--host <host>", "Bind host. Defaults to 127.0.0.1.", "127.0.0.1").option("--port <port>", "Bind port. Defaults to 8000.", (v) => parseInt(v, 10), 8e3).option("--base-path <path>", "Mount base path prefix. Defaults to /.", "/").option("--token <token>", "Bearer token for API auth. Defaults to env PANTHEON_AGENTS_API_TOKEN.").option("--no-auth", "Disable auth (NOT recommended).").action(async function() {
|
|
31835
32403
|
const options = this.opts();
|
|
32404
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31836
32405
|
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
31837
32406
|
console.error("Invalid --port value. Must be a positive integer.");
|
|
31838
|
-
process$1.
|
|
32407
|
+
process$1.exitCode = 1;
|
|
32408
|
+
return;
|
|
31839
32409
|
}
|
|
31840
32410
|
const logger = pino({
|
|
31841
32411
|
timestamp: pino.stdTimeFunctions.isoTime,
|
|
@@ -31858,14 +32428,16 @@ function createServerCommand(version) {
|
|
|
31858
32428
|
//#endregion
|
|
31859
32429
|
//#region src/cli/commands/show-config.ts
|
|
31860
32430
|
function createShowConfigCommand(version) {
|
|
31861
|
-
return createCommand("
|
|
32431
|
+
return createCommand("show-config").version(version).description("Show agent config for a project").argument("<name>", "The name of the agent.").argument("[project-id]", "The project id.").option("--json", "Output config as JSON.").action(async function() {
|
|
31862
32432
|
const [name, projectId] = this.args;
|
|
31863
32433
|
const options = this.opts();
|
|
32434
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31864
32435
|
if (projectId) {
|
|
31865
32436
|
const config = await showAgentConfig(name, projectId);
|
|
31866
32437
|
if (!config) {
|
|
31867
32438
|
console.error(`No config found for agent ${name} and project ${projectId}.`);
|
|
31868
|
-
process$1.
|
|
32439
|
+
process$1.exitCode = 1;
|
|
32440
|
+
return;
|
|
31869
32441
|
}
|
|
31870
32442
|
if (options.json) {
|
|
31871
32443
|
console.log(JSON.stringify(config, null, 2));
|
|
@@ -31885,7 +32457,8 @@ function createShowConfigCommand(version) {
|
|
|
31885
32457
|
const configs = await showAgentConfigs(name);
|
|
31886
32458
|
if (!configs.length) {
|
|
31887
32459
|
console.error(`No configs found for agent ${name}.`);
|
|
31888
|
-
process$1.
|
|
32460
|
+
process$1.exitCode = 1;
|
|
32461
|
+
return;
|
|
31889
32462
|
}
|
|
31890
32463
|
if (options.json) {
|
|
31891
32464
|
console.log(JSON.stringify(configs, null, 2));
|
|
@@ -31922,46 +32495,50 @@ const orderDirections = ["asc", "desc"];
|
|
|
31922
32495
|
//#endregion
|
|
31923
32496
|
//#region src/cli/commands/show-tasks.ts
|
|
31924
32497
|
function createShowTasksCommand(version) {
|
|
31925
|
-
return createCommand("
|
|
32498
|
+
return createCommand("show-tasks").version(version).description("Show tasks for an agent").argument("[name]", "The name of the agent.").option("--agents <names>", "Comma-separated agent names to query in one call.", parseCommaList, []).option("--all", "Show tasks for all agents.").option("--status <status>", "Filter tasks by status. Multiple values are separated by comma.", parseCommaList, []).option("--order-by <field>", "Order by queued_at, started_at, or ended_at.", "queued_at").option("--order-direction <direction>", "Order direction: asc or desc.", "desc").option("--limit <number>", "Limit the number of tasks shown.", (val) => parseInt(val, 10)).option("--max-task-length <number>", "Maximum task length for table output.", (val) => parseInt(val, 10), 120).option("--full-task", "Do not truncate task text in output.").option("--no-concise", "Disable concise one-line entries.").option("--no-color", "Disable colored output.").option("--json", "Output tasks as JSON.").action(async function() {
|
|
31926
32499
|
const [name] = this.args;
|
|
31927
32500
|
const options = this.opts();
|
|
32501
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31928
32502
|
const agentNames = /* @__PURE__ */ new Set();
|
|
31929
32503
|
if (name) agentNames.add(name);
|
|
31930
32504
|
options.agents.forEach((agent) => agentNames.add(agent));
|
|
31931
32505
|
if (options.all && agentNames.size > 0) {
|
|
31932
32506
|
console.error("Use either a specific agent name or --all, not both.");
|
|
31933
|
-
process$1.
|
|
32507
|
+
process$1.exitCode = 1;
|
|
32508
|
+
return;
|
|
31934
32509
|
}
|
|
31935
32510
|
if (!options.all && agentNames.size === 0) {
|
|
31936
32511
|
console.error("Provide an agent name, --agents, or --all.");
|
|
31937
|
-
process$1.
|
|
32512
|
+
process$1.exitCode = 1;
|
|
32513
|
+
return;
|
|
31938
32514
|
}
|
|
31939
32515
|
const invalidStatuses = options.status.filter((status) => !taskStatuses.includes(status));
|
|
31940
32516
|
if (invalidStatuses.length > 0) {
|
|
31941
32517
|
console.error(`Invalid status values: ${invalidStatuses.join(", ")}. Allowed: ${taskStatuses.join(", ")}.`);
|
|
31942
|
-
process$1.
|
|
32518
|
+
process$1.exitCode = 1;
|
|
32519
|
+
return;
|
|
31943
32520
|
}
|
|
31944
32521
|
if (!orderByFields.includes(options.orderBy)) {
|
|
31945
32522
|
console.error(`Invalid order-by value: ${options.orderBy}. Allowed: ${orderByFields.join(", ")}.`);
|
|
31946
|
-
process$1.
|
|
32523
|
+
process$1.exitCode = 1;
|
|
32524
|
+
return;
|
|
31947
32525
|
}
|
|
31948
32526
|
if (!orderDirections.includes(options.orderDirection)) {
|
|
31949
32527
|
console.error(`Invalid order-direction value: ${options.orderDirection}. Allowed: ${orderDirections.join(", ")}.`);
|
|
31950
|
-
process$1.
|
|
32528
|
+
process$1.exitCode = 1;
|
|
32529
|
+
return;
|
|
31951
32530
|
}
|
|
31952
32531
|
if (options.limit != null && Number.isNaN(options.limit)) {
|
|
31953
32532
|
console.error("Invalid limit value. Must be a number.");
|
|
31954
|
-
process$1.
|
|
32533
|
+
process$1.exitCode = 1;
|
|
32534
|
+
return;
|
|
31955
32535
|
}
|
|
31956
32536
|
if (Number.isNaN(options.maxTaskLength) || options.maxTaskLength <= 0) {
|
|
31957
32537
|
console.error("Invalid max-task-length value. Must be a positive number.");
|
|
31958
|
-
process$1.
|
|
32538
|
+
process$1.exitCode = 1;
|
|
32539
|
+
return;
|
|
31959
32540
|
}
|
|
31960
32541
|
if (options.fullTask && options.maxTaskLength) options.maxTaskLength = Number.POSITIVE_INFINITY;
|
|
31961
|
-
if (options.json && options.concise) {
|
|
31962
|
-
console.error("Use either --json or --concise, not both.");
|
|
31963
|
-
process$1.exit(1);
|
|
31964
|
-
}
|
|
31965
32542
|
const limitedTasks = await showTasksForAgents({
|
|
31966
32543
|
agents: Array.from(agentNames),
|
|
31967
32544
|
allAgents: options.all,
|
|
@@ -31998,36 +32575,1619 @@ function createShowTasksCommand(version) {
|
|
|
31998
32575
|
}
|
|
31999
32576
|
|
|
32000
32577
|
//#endregion
|
|
32001
|
-
//#region src/
|
|
32002
|
-
function
|
|
32003
|
-
|
|
32578
|
+
//#region ../agent-stream-parser/src/utils.ts
|
|
32579
|
+
function isRecord(value) {
|
|
32580
|
+
return typeof value === "object" && value !== null;
|
|
32581
|
+
}
|
|
32582
|
+
function safeJsonParse(text) {
|
|
32583
|
+
try {
|
|
32584
|
+
return {
|
|
32585
|
+
ok: true,
|
|
32586
|
+
value: JSON.parse(text)
|
|
32587
|
+
};
|
|
32588
|
+
} catch (error) {
|
|
32589
|
+
const sanitized = sanitizeMalformedJsonLine(text);
|
|
32590
|
+
if (sanitized !== text) try {
|
|
32591
|
+
return {
|
|
32592
|
+
ok: true,
|
|
32593
|
+
value: JSON.parse(sanitized)
|
|
32594
|
+
};
|
|
32595
|
+
} catch {}
|
|
32596
|
+
return {
|
|
32597
|
+
ok: false,
|
|
32598
|
+
error
|
|
32599
|
+
};
|
|
32600
|
+
}
|
|
32601
|
+
}
|
|
32602
|
+
function sanitizeMalformedJsonLine(text) {
|
|
32603
|
+
return text.replaceAll("", "'").replaceAll("", "—").replaceAll("", "—").replaceAll(/[\u0000-\u001F]/g, "");
|
|
32604
|
+
}
|
|
32605
|
+
function toErrorMessage(error) {
|
|
32606
|
+
if (error instanceof Error) return error.message;
|
|
32607
|
+
try {
|
|
32608
|
+
return JSON.stringify(error);
|
|
32609
|
+
} catch {
|
|
32610
|
+
return String(error);
|
|
32611
|
+
}
|
|
32612
|
+
}
|
|
32613
|
+
function computeAppendDelta(previous, next) {
|
|
32614
|
+
if (next.startsWith(previous)) return {
|
|
32615
|
+
delta: next.slice(previous.length),
|
|
32616
|
+
reset: false
|
|
32617
|
+
};
|
|
32618
|
+
return {
|
|
32619
|
+
delta: next,
|
|
32620
|
+
reset: true
|
|
32621
|
+
};
|
|
32622
|
+
}
|
|
32623
|
+
|
|
32624
|
+
//#endregion
|
|
32625
|
+
//#region ../agent-stream-parser/src/codex.ts
|
|
32626
|
+
const usageSchema = z$1.object({
|
|
32627
|
+
input_tokens: z$1.number(),
|
|
32628
|
+
cached_input_tokens: z$1.number(),
|
|
32629
|
+
output_tokens: z$1.number()
|
|
32630
|
+
});
|
|
32631
|
+
const agentMessageItemSchema = z$1.object({
|
|
32632
|
+
id: z$1.string(),
|
|
32633
|
+
type: z$1.literal("agent_message"),
|
|
32634
|
+
text: z$1.string()
|
|
32635
|
+
});
|
|
32636
|
+
const reasoningItemSchema = z$1.object({
|
|
32637
|
+
id: z$1.string(),
|
|
32638
|
+
type: z$1.literal("reasoning"),
|
|
32639
|
+
text: z$1.string()
|
|
32640
|
+
});
|
|
32641
|
+
const commandExecutionItemSchema = z$1.object({
|
|
32642
|
+
id: z$1.string(),
|
|
32643
|
+
type: z$1.literal("command_execution"),
|
|
32644
|
+
command: z$1.string(),
|
|
32645
|
+
aggregated_output: z$1.string(),
|
|
32646
|
+
exit_code: z$1.preprocess((value) => value === null ? void 0 : value, z$1.number().optional()),
|
|
32647
|
+
status: z$1.enum([
|
|
32648
|
+
"in_progress",
|
|
32649
|
+
"completed",
|
|
32650
|
+
"failed"
|
|
32651
|
+
])
|
|
32652
|
+
});
|
|
32653
|
+
const mcpToolCallItemSchema = z$1.object({
|
|
32654
|
+
id: z$1.string(),
|
|
32655
|
+
type: z$1.literal("mcp_tool_call"),
|
|
32656
|
+
server: z$1.string(),
|
|
32657
|
+
tool: z$1.string(),
|
|
32658
|
+
arguments: z$1.any(),
|
|
32659
|
+
result: z$1.object({
|
|
32660
|
+
content: z$1.array(z$1.any()),
|
|
32661
|
+
structured_content: z$1.any()
|
|
32662
|
+
}).optional(),
|
|
32663
|
+
error: z$1.object({ message: z$1.string() }).optional(),
|
|
32664
|
+
status: z$1.enum([
|
|
32665
|
+
"in_progress",
|
|
32666
|
+
"completed",
|
|
32667
|
+
"failed"
|
|
32668
|
+
])
|
|
32669
|
+
});
|
|
32670
|
+
const fileChangeItemSchema = z$1.object({
|
|
32671
|
+
id: z$1.string(),
|
|
32672
|
+
type: z$1.literal("file_change"),
|
|
32673
|
+
changes: z$1.array(z$1.object({
|
|
32674
|
+
path: z$1.string(),
|
|
32675
|
+
kind: z$1.enum([
|
|
32676
|
+
"add",
|
|
32677
|
+
"delete",
|
|
32678
|
+
"update"
|
|
32679
|
+
])
|
|
32680
|
+
})),
|
|
32681
|
+
status: z$1.enum(["completed", "failed"])
|
|
32682
|
+
});
|
|
32683
|
+
const webSearchItemSchema = z$1.object({
|
|
32684
|
+
id: z$1.string(),
|
|
32685
|
+
type: z$1.literal("web_search"),
|
|
32686
|
+
query: z$1.string()
|
|
32687
|
+
});
|
|
32688
|
+
const todoListItemSchema = z$1.object({
|
|
32689
|
+
id: z$1.string(),
|
|
32690
|
+
type: z$1.literal("todo_list"),
|
|
32691
|
+
items: z$1.array(z$1.object({
|
|
32692
|
+
text: z$1.string(),
|
|
32693
|
+
completed: z$1.boolean()
|
|
32694
|
+
}))
|
|
32695
|
+
});
|
|
32696
|
+
const errorItemSchema = z$1.object({
|
|
32697
|
+
id: z$1.string(),
|
|
32698
|
+
type: z$1.literal("error"),
|
|
32699
|
+
message: z$1.string()
|
|
32700
|
+
});
|
|
32701
|
+
const threadItemSchema = z$1.discriminatedUnion("type", [
|
|
32702
|
+
agentMessageItemSchema,
|
|
32703
|
+
reasoningItemSchema,
|
|
32704
|
+
commandExecutionItemSchema,
|
|
32705
|
+
mcpToolCallItemSchema,
|
|
32706
|
+
fileChangeItemSchema,
|
|
32707
|
+
webSearchItemSchema,
|
|
32708
|
+
todoListItemSchema,
|
|
32709
|
+
errorItemSchema
|
|
32710
|
+
]);
|
|
32711
|
+
const threadEventSchema = z$1.discriminatedUnion("type", [
|
|
32712
|
+
z$1.object({
|
|
32713
|
+
type: z$1.literal("thread.started"),
|
|
32714
|
+
thread_id: z$1.string()
|
|
32715
|
+
}),
|
|
32716
|
+
z$1.object({ type: z$1.literal("turn.started") }),
|
|
32717
|
+
z$1.object({
|
|
32718
|
+
type: z$1.literal("turn.completed"),
|
|
32719
|
+
usage: usageSchema
|
|
32720
|
+
}),
|
|
32721
|
+
z$1.object({
|
|
32722
|
+
type: z$1.literal("turn.failed"),
|
|
32723
|
+
error: z$1.object({ message: z$1.string() })
|
|
32724
|
+
}),
|
|
32725
|
+
z$1.object({
|
|
32726
|
+
type: z$1.literal("item.started"),
|
|
32727
|
+
item: threadItemSchema
|
|
32728
|
+
}),
|
|
32729
|
+
z$1.object({
|
|
32730
|
+
type: z$1.literal("item.updated"),
|
|
32731
|
+
item: threadItemSchema
|
|
32732
|
+
}),
|
|
32733
|
+
z$1.object({
|
|
32734
|
+
type: z$1.literal("item.completed"),
|
|
32735
|
+
item: threadItemSchema
|
|
32736
|
+
}),
|
|
32737
|
+
z$1.object({
|
|
32738
|
+
type: z$1.literal("error"),
|
|
32739
|
+
message: z$1.string()
|
|
32740
|
+
})
|
|
32741
|
+
]);
|
|
32742
|
+
function createCodexNormalizer(options = {}) {
|
|
32743
|
+
const includeMetaEvents = options.includeMetaEvents ?? false;
|
|
32744
|
+
const activeText = /* @__PURE__ */ new Map();
|
|
32745
|
+
const activeReasoning = /* @__PURE__ */ new Map();
|
|
32746
|
+
function maybeMetaEvent(event) {
|
|
32747
|
+
if (!includeMetaEvents) return [];
|
|
32748
|
+
return [{
|
|
32749
|
+
type: "data-agent-event",
|
|
32750
|
+
data: event
|
|
32751
|
+
}];
|
|
32752
|
+
}
|
|
32753
|
+
function handleTextLike({ map, id, fullText, eventType, startType, deltaType, endType }) {
|
|
32754
|
+
const out = [];
|
|
32755
|
+
let state = map.get(id);
|
|
32756
|
+
if (!state || !state.open) {
|
|
32757
|
+
state = {
|
|
32758
|
+
lastText: "",
|
|
32759
|
+
open: true
|
|
32760
|
+
};
|
|
32761
|
+
map.set(id, state);
|
|
32762
|
+
out.push({
|
|
32763
|
+
type: startType,
|
|
32764
|
+
id
|
|
32765
|
+
});
|
|
32766
|
+
}
|
|
32767
|
+
const { delta, reset } = computeAppendDelta(state.lastText, fullText);
|
|
32768
|
+
if (delta) {
|
|
32769
|
+
out.push({
|
|
32770
|
+
type: deltaType,
|
|
32771
|
+
id,
|
|
32772
|
+
delta
|
|
32773
|
+
});
|
|
32774
|
+
state.lastText = reset ? fullText : state.lastText + delta;
|
|
32775
|
+
}
|
|
32776
|
+
if (eventType === "item.completed") {
|
|
32777
|
+
out.push({
|
|
32778
|
+
type: endType,
|
|
32779
|
+
id
|
|
32780
|
+
});
|
|
32781
|
+
state.open = false;
|
|
32782
|
+
}
|
|
32783
|
+
return out;
|
|
32784
|
+
}
|
|
32785
|
+
function normalizeItemEvent(eventType, item) {
|
|
32786
|
+
const out = [];
|
|
32787
|
+
switch (item.type) {
|
|
32788
|
+
case "agent_message": return {
|
|
32789
|
+
recognized: true,
|
|
32790
|
+
chunks: handleTextLike({
|
|
32791
|
+
map: activeText,
|
|
32792
|
+
id: item.id,
|
|
32793
|
+
fullText: item.text,
|
|
32794
|
+
eventType,
|
|
32795
|
+
startType: "text-start",
|
|
32796
|
+
deltaType: "text-delta",
|
|
32797
|
+
endType: "text-end"
|
|
32798
|
+
})
|
|
32799
|
+
};
|
|
32800
|
+
case "reasoning": return {
|
|
32801
|
+
recognized: true,
|
|
32802
|
+
chunks: handleTextLike({
|
|
32803
|
+
map: activeReasoning,
|
|
32804
|
+
id: item.id,
|
|
32805
|
+
fullText: item.text,
|
|
32806
|
+
eventType,
|
|
32807
|
+
startType: "reasoning-start",
|
|
32808
|
+
deltaType: "reasoning-delta",
|
|
32809
|
+
endType: "reasoning-end"
|
|
32810
|
+
})
|
|
32811
|
+
};
|
|
32812
|
+
case "command_execution": {
|
|
32813
|
+
const toolCallId = item.id;
|
|
32814
|
+
out.push({
|
|
32815
|
+
type: "tool-input-available",
|
|
32816
|
+
toolCallId,
|
|
32817
|
+
toolName: "command_execution",
|
|
32818
|
+
title: item.command,
|
|
32819
|
+
input: { command: item.command },
|
|
32820
|
+
dynamic: true
|
|
32821
|
+
});
|
|
32822
|
+
if (item.status === "completed") out.push({
|
|
32823
|
+
type: "tool-output-available",
|
|
32824
|
+
toolCallId,
|
|
32825
|
+
output: {
|
|
32826
|
+
exit_code: item.exit_code ?? null,
|
|
32827
|
+
aggregated_output: item.aggregated_output
|
|
32828
|
+
},
|
|
32829
|
+
dynamic: true
|
|
32830
|
+
});
|
|
32831
|
+
else if (item.status === "failed") {
|
|
32832
|
+
const exitCode = item.exit_code;
|
|
32833
|
+
out.push({
|
|
32834
|
+
type: "tool-output-error",
|
|
32835
|
+
toolCallId,
|
|
32836
|
+
errorText: exitCode != null ? `Command failed (exit_code=${exitCode}).\n${item.aggregated_output}` : `Command failed.\n${item.aggregated_output}`,
|
|
32837
|
+
dynamic: true
|
|
32838
|
+
});
|
|
32839
|
+
}
|
|
32840
|
+
return {
|
|
32841
|
+
recognized: true,
|
|
32842
|
+
chunks: out
|
|
32843
|
+
};
|
|
32844
|
+
}
|
|
32845
|
+
case "mcp_tool_call": {
|
|
32846
|
+
const toolCallId = item.id;
|
|
32847
|
+
out.push({
|
|
32848
|
+
type: "tool-input-available",
|
|
32849
|
+
toolCallId,
|
|
32850
|
+
toolName: "mcp_tool_call",
|
|
32851
|
+
title: `${item.server}.${item.tool}`,
|
|
32852
|
+
input: item.arguments,
|
|
32853
|
+
dynamic: true
|
|
32854
|
+
});
|
|
32855
|
+
if (item.status === "completed") out.push({
|
|
32856
|
+
type: "tool-output-available",
|
|
32857
|
+
toolCallId,
|
|
32858
|
+
output: item.result?.structured_content ?? null,
|
|
32859
|
+
dynamic: true
|
|
32860
|
+
});
|
|
32861
|
+
else if (item.status === "failed") out.push({
|
|
32862
|
+
type: "tool-output-error",
|
|
32863
|
+
toolCallId,
|
|
32864
|
+
errorText: item.error?.message ?? "MCP tool call failed",
|
|
32865
|
+
dynamic: true
|
|
32866
|
+
});
|
|
32867
|
+
return {
|
|
32868
|
+
recognized: true,
|
|
32869
|
+
chunks: out
|
|
32870
|
+
};
|
|
32871
|
+
}
|
|
32872
|
+
case "file_change": {
|
|
32873
|
+
const toolCallId = item.id;
|
|
32874
|
+
out.push({
|
|
32875
|
+
type: "tool-input-available",
|
|
32876
|
+
toolCallId,
|
|
32877
|
+
toolName: "file_change",
|
|
32878
|
+
input: { changes: item.changes },
|
|
32879
|
+
dynamic: true
|
|
32880
|
+
});
|
|
32881
|
+
if (item.status === "completed") out.push({
|
|
32882
|
+
type: "tool-output-available",
|
|
32883
|
+
toolCallId,
|
|
32884
|
+
output: { status: "completed" },
|
|
32885
|
+
dynamic: true
|
|
32886
|
+
});
|
|
32887
|
+
else out.push({
|
|
32888
|
+
type: "tool-output-error",
|
|
32889
|
+
toolCallId,
|
|
32890
|
+
errorText: "Patch apply failed",
|
|
32891
|
+
dynamic: true
|
|
32892
|
+
});
|
|
32893
|
+
return {
|
|
32894
|
+
recognized: true,
|
|
32895
|
+
chunks: out
|
|
32896
|
+
};
|
|
32897
|
+
}
|
|
32898
|
+
case "web_search": {
|
|
32899
|
+
const toolCallId = item.id;
|
|
32900
|
+
out.push({
|
|
32901
|
+
type: "tool-input-available",
|
|
32902
|
+
toolCallId,
|
|
32903
|
+
toolName: "web_search",
|
|
32904
|
+
title: item.query,
|
|
32905
|
+
input: { query: item.query },
|
|
32906
|
+
dynamic: true
|
|
32907
|
+
});
|
|
32908
|
+
if (eventType === "item.completed") out.push({
|
|
32909
|
+
type: "tool-output-available",
|
|
32910
|
+
toolCallId,
|
|
32911
|
+
output: null,
|
|
32912
|
+
dynamic: true
|
|
32913
|
+
});
|
|
32914
|
+
return {
|
|
32915
|
+
recognized: true,
|
|
32916
|
+
chunks: out
|
|
32917
|
+
};
|
|
32918
|
+
}
|
|
32919
|
+
case "todo_list": return {
|
|
32920
|
+
recognized: true,
|
|
32921
|
+
chunks: maybeMetaEvent({
|
|
32922
|
+
kind: "codex.todo_list",
|
|
32923
|
+
items: item.items
|
|
32924
|
+
})
|
|
32925
|
+
};
|
|
32926
|
+
case "error":
|
|
32927
|
+
out.push({
|
|
32928
|
+
type: "error",
|
|
32929
|
+
errorText: item.message
|
|
32930
|
+
});
|
|
32931
|
+
out.push(...maybeMetaEvent({
|
|
32932
|
+
kind: "codex.error",
|
|
32933
|
+
message: item.message
|
|
32934
|
+
}));
|
|
32935
|
+
return {
|
|
32936
|
+
recognized: true,
|
|
32937
|
+
chunks: out
|
|
32938
|
+
};
|
|
32939
|
+
default: return {
|
|
32940
|
+
recognized: false,
|
|
32941
|
+
chunks: []
|
|
32942
|
+
};
|
|
32943
|
+
}
|
|
32944
|
+
}
|
|
32945
|
+
function push(chunk) {
|
|
32946
|
+
const parsed = threadEventSchema.safeParse(chunk);
|
|
32947
|
+
if (!parsed.success) return {
|
|
32948
|
+
recognized: false,
|
|
32949
|
+
chunks: []
|
|
32950
|
+
};
|
|
32951
|
+
const event = parsed.data;
|
|
32952
|
+
switch (event.type) {
|
|
32953
|
+
case "thread.started": {
|
|
32954
|
+
const out = [{
|
|
32955
|
+
type: "message-metadata",
|
|
32956
|
+
messageMetadata: {
|
|
32957
|
+
threadId: event.thread_id,
|
|
32958
|
+
source: "codex"
|
|
32959
|
+
}
|
|
32960
|
+
}];
|
|
32961
|
+
out.push(...maybeMetaEvent({
|
|
32962
|
+
kind: "codex.thread.started",
|
|
32963
|
+
threadId: event.thread_id
|
|
32964
|
+
}));
|
|
32965
|
+
return {
|
|
32966
|
+
recognized: true,
|
|
32967
|
+
chunks: out
|
|
32968
|
+
};
|
|
32969
|
+
}
|
|
32970
|
+
case "turn.started": {
|
|
32971
|
+
const out = [{ type: "start-step" }];
|
|
32972
|
+
out.push(...maybeMetaEvent({ kind: "codex.turn.started" }));
|
|
32973
|
+
return {
|
|
32974
|
+
recognized: true,
|
|
32975
|
+
chunks: out
|
|
32976
|
+
};
|
|
32977
|
+
}
|
|
32978
|
+
case "turn.completed": {
|
|
32979
|
+
const out = [{ type: "finish-step" }];
|
|
32980
|
+
out.push(...maybeMetaEvent({
|
|
32981
|
+
kind: "codex.turn.completed",
|
|
32982
|
+
usage: event.usage
|
|
32983
|
+
}));
|
|
32984
|
+
return {
|
|
32985
|
+
recognized: true,
|
|
32986
|
+
chunks: out
|
|
32987
|
+
};
|
|
32988
|
+
}
|
|
32989
|
+
case "turn.failed": {
|
|
32990
|
+
const out = [{
|
|
32991
|
+
type: "error",
|
|
32992
|
+
errorText: event.error.message
|
|
32993
|
+
}, { type: "finish-step" }];
|
|
32994
|
+
out.push(...maybeMetaEvent({
|
|
32995
|
+
kind: "codex.turn.failed",
|
|
32996
|
+
message: event.error.message
|
|
32997
|
+
}));
|
|
32998
|
+
return {
|
|
32999
|
+
recognized: true,
|
|
33000
|
+
chunks: out
|
|
33001
|
+
};
|
|
33002
|
+
}
|
|
33003
|
+
case "error": {
|
|
33004
|
+
const out = [{
|
|
33005
|
+
type: "error",
|
|
33006
|
+
errorText: event.message
|
|
33007
|
+
}];
|
|
33008
|
+
out.push(...maybeMetaEvent({
|
|
33009
|
+
kind: "codex.error",
|
|
33010
|
+
message: event.message
|
|
33011
|
+
}));
|
|
33012
|
+
return {
|
|
33013
|
+
recognized: true,
|
|
33014
|
+
chunks: out
|
|
33015
|
+
};
|
|
33016
|
+
}
|
|
33017
|
+
case "item.started":
|
|
33018
|
+
case "item.updated":
|
|
33019
|
+
case "item.completed": return normalizeItemEvent(event.type, event.item);
|
|
33020
|
+
default: return {
|
|
33021
|
+
recognized: false,
|
|
33022
|
+
chunks: []
|
|
33023
|
+
};
|
|
33024
|
+
}
|
|
33025
|
+
}
|
|
33026
|
+
function finish() {
|
|
33027
|
+
const out = [];
|
|
33028
|
+
for (const [id, state] of activeText) if (state.open) {
|
|
33029
|
+
out.push({
|
|
33030
|
+
type: "text-end",
|
|
33031
|
+
id
|
|
33032
|
+
});
|
|
33033
|
+
state.open = false;
|
|
33034
|
+
}
|
|
33035
|
+
for (const [id, state] of activeReasoning) if (state.open) {
|
|
33036
|
+
out.push({
|
|
33037
|
+
type: "reasoning-end",
|
|
33038
|
+
id
|
|
33039
|
+
});
|
|
33040
|
+
state.open = false;
|
|
33041
|
+
}
|
|
33042
|
+
return out;
|
|
33043
|
+
}
|
|
33044
|
+
return {
|
|
33045
|
+
push,
|
|
33046
|
+
finish
|
|
33047
|
+
};
|
|
33048
|
+
}
|
|
33049
|
+
|
|
33050
|
+
//#endregion
|
|
33051
|
+
//#region ../agent-stream-parser/src/claude.ts
|
|
33052
|
+
const topLevelSchema = z$1.object({ type: z$1.string() }).passthrough();
|
|
33053
|
+
const streamEventSchema = z$1.object({
|
|
33054
|
+
type: z$1.string(),
|
|
33055
|
+
index: z$1.number().optional()
|
|
33056
|
+
}).passthrough();
|
|
33057
|
+
function createClaudeCodeNormalizer(options = {}) {
|
|
33058
|
+
const includeMetaEvents = options.includeMetaEvents ?? false;
|
|
33059
|
+
const textBlocks = /* @__PURE__ */ new Map();
|
|
33060
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
33061
|
+
let sawStreamEvents = false;
|
|
33062
|
+
function maybeMeta(event) {
|
|
33063
|
+
if (!includeMetaEvents) return [];
|
|
33064
|
+
return [{
|
|
33065
|
+
type: "data-agent-event",
|
|
33066
|
+
data: event
|
|
33067
|
+
}];
|
|
33068
|
+
}
|
|
33069
|
+
function ensureTextBlock(index) {
|
|
33070
|
+
const existing = textBlocks.get(index);
|
|
33071
|
+
if (existing?.open) return [];
|
|
33072
|
+
const id = existing?.id ?? `claude-text-${index}`;
|
|
33073
|
+
textBlocks.set(index, {
|
|
33074
|
+
id,
|
|
33075
|
+
open: true
|
|
33076
|
+
});
|
|
33077
|
+
return [{
|
|
33078
|
+
type: "text-start",
|
|
33079
|
+
id
|
|
33080
|
+
}];
|
|
33081
|
+
}
|
|
33082
|
+
function ensureToolBlock(index, block) {
|
|
33083
|
+
const existing = toolBlocks.get(index);
|
|
33084
|
+
if (existing?.open) return [];
|
|
33085
|
+
const toolCallId = block?.id ?? existing?.toolCallId ?? `claude-tool-${index}`;
|
|
33086
|
+
const toolName = block?.name ?? existing?.toolName ?? "tool";
|
|
33087
|
+
toolBlocks.set(index, {
|
|
33088
|
+
toolCallId,
|
|
33089
|
+
toolName,
|
|
33090
|
+
json: "",
|
|
33091
|
+
open: true
|
|
33092
|
+
});
|
|
33093
|
+
return [{
|
|
33094
|
+
type: "tool-input-start",
|
|
33095
|
+
toolCallId,
|
|
33096
|
+
toolName,
|
|
33097
|
+
dynamic: true
|
|
33098
|
+
}];
|
|
33099
|
+
}
|
|
33100
|
+
function handleStreamEvent(event) {
|
|
33101
|
+
if (!isRecord(event) || typeof event.type !== "string") return [];
|
|
33102
|
+
const out = [];
|
|
33103
|
+
const index = typeof event.index === "number" ? event.index : void 0;
|
|
33104
|
+
switch (event.type) {
|
|
33105
|
+
case "content_block_start": {
|
|
33106
|
+
if (index == null) return [];
|
|
33107
|
+
const block = event.content_block;
|
|
33108
|
+
if (!isRecord(block) || typeof block.type !== "string") return [];
|
|
33109
|
+
sawStreamEvents = true;
|
|
33110
|
+
if (block.type === "text") out.push(...ensureTextBlock(index));
|
|
33111
|
+
else if (block.type === "tool_use") out.push(...ensureToolBlock(index, {
|
|
33112
|
+
id: typeof block.id === "string" ? block.id : void 0,
|
|
33113
|
+
name: typeof block.name === "string" ? block.name : void 0
|
|
33114
|
+
}));
|
|
33115
|
+
return out;
|
|
33116
|
+
}
|
|
33117
|
+
case "content_block_delta": {
|
|
33118
|
+
if (index == null) return [];
|
|
33119
|
+
const delta = event.delta;
|
|
33120
|
+
if (!isRecord(delta) || typeof delta.type !== "string") return [];
|
|
33121
|
+
sawStreamEvents = true;
|
|
33122
|
+
if (delta.type === "text_delta") {
|
|
33123
|
+
const text = typeof delta.text === "string" ? delta.text : "";
|
|
33124
|
+
if (!text) return [];
|
|
33125
|
+
out.push(...ensureTextBlock(index));
|
|
33126
|
+
const id = textBlocks.get(index).id;
|
|
33127
|
+
out.push({
|
|
33128
|
+
type: "text-delta",
|
|
33129
|
+
id,
|
|
33130
|
+
delta: text
|
|
33131
|
+
});
|
|
33132
|
+
return out;
|
|
33133
|
+
}
|
|
33134
|
+
if (delta.type === "input_json_delta") {
|
|
33135
|
+
const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : "";
|
|
33136
|
+
if (!partialJson) return [];
|
|
33137
|
+
out.push(...ensureToolBlock(index));
|
|
33138
|
+
const tool = toolBlocks.get(index);
|
|
33139
|
+
tool.json += partialJson;
|
|
33140
|
+
out.push({
|
|
33141
|
+
type: "tool-input-delta",
|
|
33142
|
+
toolCallId: tool.toolCallId,
|
|
33143
|
+
inputTextDelta: partialJson
|
|
33144
|
+
});
|
|
33145
|
+
return out;
|
|
33146
|
+
}
|
|
33147
|
+
return [];
|
|
33148
|
+
}
|
|
33149
|
+
case "content_block_stop": {
|
|
33150
|
+
if (index == null) return [];
|
|
33151
|
+
sawStreamEvents = true;
|
|
33152
|
+
const text = textBlocks.get(index);
|
|
33153
|
+
if (text?.open) {
|
|
33154
|
+
text.open = false;
|
|
33155
|
+
out.push({
|
|
33156
|
+
type: "text-end",
|
|
33157
|
+
id: text.id
|
|
33158
|
+
});
|
|
33159
|
+
}
|
|
33160
|
+
const tool = toolBlocks.get(index);
|
|
33161
|
+
if (tool?.open) {
|
|
33162
|
+
tool.open = false;
|
|
33163
|
+
let input = tool.json ? tool.json : {};
|
|
33164
|
+
if (typeof input === "string") {
|
|
33165
|
+
const parsed = safeJsonParse(input);
|
|
33166
|
+
if (parsed.ok) input = parsed.value;
|
|
33167
|
+
}
|
|
33168
|
+
out.push({
|
|
33169
|
+
type: "tool-input-available",
|
|
33170
|
+
toolCallId: tool.toolCallId,
|
|
33171
|
+
toolName: tool.toolName,
|
|
33172
|
+
input,
|
|
33173
|
+
dynamic: true
|
|
33174
|
+
});
|
|
33175
|
+
}
|
|
33176
|
+
return out;
|
|
33177
|
+
}
|
|
33178
|
+
default: return [];
|
|
33179
|
+
}
|
|
33180
|
+
}
|
|
33181
|
+
function handleAssistantMessage(message) {
|
|
33182
|
+
if (sawStreamEvents) return [];
|
|
33183
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
33184
|
+
const out = [];
|
|
33185
|
+
blocks.forEach((block, index) => {
|
|
33186
|
+
if (!isRecord(block) || typeof block.type !== "string") return;
|
|
33187
|
+
if (block.type === "text") {
|
|
33188
|
+
const id = `claude-assistant-text-${index}`;
|
|
33189
|
+
const text = typeof block.text === "string" ? block.text : "";
|
|
33190
|
+
out.push({
|
|
33191
|
+
type: "text-start",
|
|
33192
|
+
id
|
|
33193
|
+
});
|
|
33194
|
+
if (text) out.push({
|
|
33195
|
+
type: "text-delta",
|
|
33196
|
+
id,
|
|
33197
|
+
delta: text
|
|
33198
|
+
});
|
|
33199
|
+
out.push({
|
|
33200
|
+
type: "text-end",
|
|
33201
|
+
id
|
|
33202
|
+
});
|
|
33203
|
+
}
|
|
33204
|
+
if (block.type === "tool_use") {
|
|
33205
|
+
const toolCallId = typeof block.id === "string" ? block.id : `claude-assistant-tool-${index}`;
|
|
33206
|
+
const toolName = typeof block.name === "string" ? block.name : "tool";
|
|
33207
|
+
const input = block.input;
|
|
33208
|
+
out.push({
|
|
33209
|
+
type: "tool-input-available",
|
|
33210
|
+
toolCallId,
|
|
33211
|
+
toolName,
|
|
33212
|
+
input,
|
|
33213
|
+
dynamic: true
|
|
33214
|
+
});
|
|
33215
|
+
}
|
|
33216
|
+
});
|
|
33217
|
+
return out;
|
|
33218
|
+
}
|
|
33219
|
+
function push(chunk) {
|
|
33220
|
+
const parsed = topLevelSchema.safeParse(chunk);
|
|
33221
|
+
if (!parsed.success) return {
|
|
33222
|
+
recognized: false,
|
|
33223
|
+
chunks: []
|
|
33224
|
+
};
|
|
33225
|
+
const msg = parsed.data;
|
|
33226
|
+
const out = [];
|
|
33227
|
+
switch (msg.type) {
|
|
33228
|
+
case "system": {
|
|
33229
|
+
const model = typeof msg.model === "string" ? msg.model : void 0;
|
|
33230
|
+
const sessionId = typeof msg.session_id === "string" ? msg.session_id : typeof msg.sessionId === "string" ? msg.sessionId : void 0;
|
|
33231
|
+
out.push({
|
|
33232
|
+
type: "message-metadata",
|
|
33233
|
+
messageMetadata: {
|
|
33234
|
+
source: "claude-code",
|
|
33235
|
+
model,
|
|
33236
|
+
sessionId
|
|
33237
|
+
}
|
|
33238
|
+
});
|
|
33239
|
+
out.push(...maybeMeta({
|
|
33240
|
+
kind: "claude.system",
|
|
33241
|
+
model,
|
|
33242
|
+
sessionId
|
|
33243
|
+
}));
|
|
33244
|
+
return {
|
|
33245
|
+
recognized: true,
|
|
33246
|
+
chunks: out
|
|
33247
|
+
};
|
|
33248
|
+
}
|
|
33249
|
+
case "stream_event": {
|
|
33250
|
+
sawStreamEvents = true;
|
|
33251
|
+
const event = streamEventSchema.safeParse(msg.event);
|
|
33252
|
+
if (!event.success) return {
|
|
33253
|
+
recognized: false,
|
|
33254
|
+
chunks: []
|
|
33255
|
+
};
|
|
33256
|
+
return {
|
|
33257
|
+
recognized: true,
|
|
33258
|
+
chunks: handleStreamEvent(event.data)
|
|
33259
|
+
};
|
|
33260
|
+
}
|
|
33261
|
+
case "assistant": {
|
|
33262
|
+
const message = msg.message;
|
|
33263
|
+
if (!isRecord(message)) return {
|
|
33264
|
+
recognized: false,
|
|
33265
|
+
chunks: []
|
|
33266
|
+
};
|
|
33267
|
+
return {
|
|
33268
|
+
recognized: true,
|
|
33269
|
+
chunks: handleAssistantMessage(message)
|
|
33270
|
+
};
|
|
33271
|
+
}
|
|
33272
|
+
case "result": return {
|
|
33273
|
+
recognized: true,
|
|
33274
|
+
chunks: maybeMeta({
|
|
33275
|
+
kind: "claude.result",
|
|
33276
|
+
subtype: typeof msg.subtype === "string" ? msg.subtype : void 0,
|
|
33277
|
+
isError: typeof msg.is_error === "boolean" ? msg.is_error : void 0
|
|
33278
|
+
})
|
|
33279
|
+
};
|
|
33280
|
+
default: return {
|
|
33281
|
+
recognized: false,
|
|
33282
|
+
chunks: []
|
|
33283
|
+
};
|
|
33284
|
+
}
|
|
33285
|
+
}
|
|
33286
|
+
function finish() {
|
|
33287
|
+
const out = [];
|
|
33288
|
+
for (const [index, block] of textBlocks) if (block.open) {
|
|
33289
|
+
block.open = false;
|
|
33290
|
+
out.push({
|
|
33291
|
+
type: "text-end",
|
|
33292
|
+
id: block.id
|
|
33293
|
+
});
|
|
33294
|
+
}
|
|
33295
|
+
for (const [index, tool] of toolBlocks) if (tool.open) {
|
|
33296
|
+
tool.open = false;
|
|
33297
|
+
const reason = `Stream ended before tool input completed (index=${index}).`;
|
|
33298
|
+
out.push({
|
|
33299
|
+
type: "tool-input-error",
|
|
33300
|
+
toolCallId: tool.toolCallId,
|
|
33301
|
+
toolName: tool.toolName,
|
|
33302
|
+
input: tool.json,
|
|
33303
|
+
errorText: reason,
|
|
33304
|
+
dynamic: true
|
|
33305
|
+
});
|
|
33306
|
+
}
|
|
33307
|
+
return out;
|
|
33308
|
+
}
|
|
33309
|
+
return {
|
|
33310
|
+
push,
|
|
33311
|
+
finish
|
|
33312
|
+
};
|
|
33313
|
+
}
|
|
33314
|
+
|
|
33315
|
+
//#endregion
|
|
33316
|
+
//#region ../agent-stream-parser/src/normalizer.ts
|
|
33317
|
+
function createAgentStreamNormalizer(options) {
|
|
33318
|
+
const unknownChunkPolicy = options.unknownChunkPolicy ?? "ignore";
|
|
33319
|
+
const includeMetaEvents = options.includeMetaEvents ?? false;
|
|
33320
|
+
const emitStartChunk = options.emitStartChunk ?? true;
|
|
33321
|
+
const impl = options.source === "codex" ? createCodexNormalizer({ includeMetaEvents }) : createClaudeCodeNormalizer({ includeMetaEvents });
|
|
33322
|
+
let started = false;
|
|
33323
|
+
function maybeEmitStart() {
|
|
33324
|
+
if (!emitStartChunk || started) return [];
|
|
33325
|
+
started = true;
|
|
33326
|
+
const messageMetadata = { source: options.source };
|
|
33327
|
+
if (options.messageId) return [{
|
|
33328
|
+
type: "start",
|
|
33329
|
+
messageId: options.messageId,
|
|
33330
|
+
messageMetadata
|
|
33331
|
+
}];
|
|
33332
|
+
return [{
|
|
33333
|
+
type: "start",
|
|
33334
|
+
messageMetadata
|
|
33335
|
+
}];
|
|
33336
|
+
}
|
|
33337
|
+
function onUnknownChunk(chunk, reason) {
|
|
33338
|
+
if (unknownChunkPolicy === "ignore") return [];
|
|
33339
|
+
if (unknownChunkPolicy === "throw") throw new Error(`Unknown chunk: ${reason}`);
|
|
33340
|
+
return [{
|
|
33341
|
+
type: "data-agent-unknown",
|
|
33342
|
+
data: {
|
|
33343
|
+
source: options.source,
|
|
33344
|
+
reason,
|
|
33345
|
+
chunk
|
|
33346
|
+
}
|
|
33347
|
+
}];
|
|
33348
|
+
}
|
|
33349
|
+
function push(chunk) {
|
|
33350
|
+
let parsedChunk = chunk;
|
|
33351
|
+
if (typeof chunk === "string") {
|
|
33352
|
+
const parsed = safeJsonParse(chunk);
|
|
33353
|
+
if (!parsed.ok) {
|
|
33354
|
+
const unknown = onUnknownChunk(chunk, `invalid JSON string: ${toErrorMessage(parsed.error)}`);
|
|
33355
|
+
return unknown.length > 0 ? [...maybeEmitStart(), ...unknown] : [];
|
|
33356
|
+
}
|
|
33357
|
+
parsedChunk = parsed.value;
|
|
33358
|
+
}
|
|
33359
|
+
if (!isRecord(parsedChunk) || typeof parsedChunk.type !== "string") {
|
|
33360
|
+
const unknown = onUnknownChunk(chunk, "missing object with string 'type'");
|
|
33361
|
+
return unknown.length > 0 ? [...maybeEmitStart(), ...unknown] : [];
|
|
33362
|
+
}
|
|
33363
|
+
const result = impl.push(parsedChunk);
|
|
33364
|
+
if (result.chunks.length > 0) return [...maybeEmitStart(), ...result.chunks];
|
|
33365
|
+
if (result.recognized) return [];
|
|
33366
|
+
const type = typeof parsedChunk.type === "string" ? parsedChunk.type : "<unknown>";
|
|
33367
|
+
const unknown = onUnknownChunk(chunk, `unrecognized ${options.source} chunk type: ${type}`);
|
|
33368
|
+
return unknown.length > 0 ? [...maybeEmitStart(), ...unknown] : [];
|
|
33369
|
+
}
|
|
33370
|
+
function finish() {
|
|
33371
|
+
const out = impl.finish();
|
|
33372
|
+
if (out.length === 0) return [];
|
|
33373
|
+
return [...maybeEmitStart(), ...out];
|
|
33374
|
+
}
|
|
33375
|
+
return {
|
|
33376
|
+
source: options.source,
|
|
33377
|
+
push,
|
|
33378
|
+
finish
|
|
33379
|
+
};
|
|
33380
|
+
}
|
|
33381
|
+
|
|
33382
|
+
//#endregion
|
|
33383
|
+
//#region src/cli/commands/watch/format.ts
|
|
33384
|
+
function tryExtractBashCommand(command) {
|
|
33385
|
+
const wrapper = command.trim().match(/^(?:\/\S*\/bash|bash)\s+(-\S*c)\s+([\s\S]+)$/);
|
|
33386
|
+
if (!wrapper) return void 0;
|
|
33387
|
+
const rest = wrapper[2].trimStart();
|
|
33388
|
+
if (!rest) return void 0;
|
|
33389
|
+
if (rest.startsWith("$'")) {
|
|
33390
|
+
const end = rest.indexOf("'", 2);
|
|
33391
|
+
return end === -1 ? rest.slice(2) : rest.slice(2, end);
|
|
33392
|
+
}
|
|
33393
|
+
if (rest.startsWith("'")) {
|
|
33394
|
+
const end = rest.indexOf("'", 1);
|
|
33395
|
+
return end === -1 ? rest.slice(1) : rest.slice(1, end);
|
|
33396
|
+
}
|
|
33397
|
+
if (rest.startsWith("\"")) {
|
|
33398
|
+
let out = "";
|
|
33399
|
+
let escaped = false;
|
|
33400
|
+
for (let i = 1; i < rest.length; i++) {
|
|
33401
|
+
const ch = rest[i];
|
|
33402
|
+
if (escaped) {
|
|
33403
|
+
out += ch;
|
|
33404
|
+
escaped = false;
|
|
33405
|
+
continue;
|
|
33406
|
+
}
|
|
33407
|
+
if (ch === "\\") {
|
|
33408
|
+
escaped = true;
|
|
33409
|
+
continue;
|
|
33410
|
+
}
|
|
33411
|
+
if (ch === "\"") return out;
|
|
33412
|
+
out += ch;
|
|
33413
|
+
}
|
|
33414
|
+
return out;
|
|
33415
|
+
}
|
|
33416
|
+
return rest.trim();
|
|
33417
|
+
}
|
|
33418
|
+
function formatCommandExecutionDisplayCommand(command) {
|
|
33419
|
+
const trimmed = command.trim();
|
|
33420
|
+
return tryExtractBashCommand(trimmed)?.trim() || trimmed;
|
|
33421
|
+
}
|
|
33422
|
+
function formatPreviewLine(label, text, options = {}) {
|
|
33423
|
+
const trimmed = text.trim();
|
|
33424
|
+
if (!trimmed) return void 0;
|
|
33425
|
+
const oneLine = trimmed.replaceAll(/\s+/g, " ");
|
|
33426
|
+
const max = options.maxChars ?? 220;
|
|
33427
|
+
const preview = oneLine.length > max ? oneLine.slice(0, max - 1) + "…" : oneLine;
|
|
33428
|
+
return `${label}: ${options.formatValue ? options.formatValue(preview) : preview}`;
|
|
33429
|
+
}
|
|
33430
|
+
|
|
33431
|
+
//#endregion
|
|
33432
|
+
//#region src/cli/commands/watch.ts
|
|
33433
|
+
const PANTHEON_BASE_URL = "https://pantheon-ai.tidb.ai";
|
|
33434
|
+
function ensureEnvOrExit(keys) {
|
|
33435
|
+
const missing = keys.filter((key) => !process.env[key]);
|
|
33436
|
+
if (missing.length === 0) return;
|
|
33437
|
+
for (const key of missing) console.error(`${key} environment variable is not set.`);
|
|
33438
|
+
process.exit(1);
|
|
33439
|
+
}
|
|
33440
|
+
function createDb() {
|
|
33441
|
+
return new Kysely({ dialect: new MysqlDialect({ pool: createPool({
|
|
33442
|
+
uri: process.env.DATABASE_URL,
|
|
33443
|
+
supportBigNumbers: true,
|
|
33444
|
+
bigNumberStrings: true
|
|
33445
|
+
}) }) });
|
|
33446
|
+
}
|
|
33447
|
+
function formatDurationMs(ms) {
|
|
33448
|
+
const seconds = Math.max(0, Math.floor(ms / 1e3));
|
|
33449
|
+
const minutes = Math.floor(seconds / 60);
|
|
33450
|
+
const hours = Math.floor(minutes / 60);
|
|
33451
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
33452
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
33453
|
+
return `${seconds}s`;
|
|
33454
|
+
}
|
|
33455
|
+
function getTaskStartTime(task) {
|
|
33456
|
+
switch (task.status) {
|
|
33457
|
+
case "pending": return task.queued_at;
|
|
33458
|
+
case "running": return task.started_at;
|
|
33459
|
+
case "completed":
|
|
33460
|
+
case "failed": return task.started_at ?? task.queued_at;
|
|
33461
|
+
case "cancelled": return task.queued_at;
|
|
33462
|
+
}
|
|
33463
|
+
}
|
|
33464
|
+
function getTaskElapsedMs(task, now = /* @__PURE__ */ new Date()) {
|
|
33465
|
+
const start = getTaskStartTime(task);
|
|
33466
|
+
if (!start) return 0;
|
|
33467
|
+
switch (task.status) {
|
|
33468
|
+
case "completed": return task.ended_at.getTime() - start.getTime();
|
|
33469
|
+
case "failed": return (task.ended_at ?? now).getTime() - start.getTime();
|
|
33470
|
+
case "cancelled": return task.cancelled_at.getTime() - start.getTime();
|
|
33471
|
+
default: return now.getTime() - start.getTime();
|
|
33472
|
+
}
|
|
33473
|
+
}
|
|
33474
|
+
function toStreamSourceFromExecuteAgent(executeAgent) {
|
|
33475
|
+
if (!executeAgent) return void 0;
|
|
33476
|
+
const normalized = executeAgent.toLowerCase();
|
|
33477
|
+
if (normalized.includes("claude")) return "claude-code";
|
|
33478
|
+
if (normalized.includes("codex")) return "codex";
|
|
33479
|
+
}
|
|
33480
|
+
function detectStreamSourceFromRawPayload(payload) {
|
|
33481
|
+
if (payload.includes("\"type\":\"thread.") || payload.includes("\"type\":\"turn.") || payload.includes("\"type\":\"item.")) return "codex";
|
|
33482
|
+
if (payload.includes("\"type\":\"message_start\"") || payload.includes("\"type\":\"message_delta\"") || payload.includes("\"type\":\"message_stop\"") || payload.includes("\"type\":\"content_block_start\"") || payload.includes("\"type\":\"content_block_delta\"") || payload.includes("\"type\":\"content_block_stop\"")) return "claude-code";
|
|
33483
|
+
}
|
|
33484
|
+
async function getAgentExecuteAgentMap(db, targets) {
|
|
33485
|
+
const agents = Array.from(new Set(targets.map((t) => t.agent)));
|
|
33486
|
+
if (agents.length === 0) return /* @__PURE__ */ new Map();
|
|
33487
|
+
const configs = await db.selectFrom("agent_project_config").select([
|
|
33488
|
+
"agent",
|
|
33489
|
+
"project_id",
|
|
33490
|
+
"execute_agent"
|
|
33491
|
+
]).where("agent", "in", agents).execute();
|
|
33492
|
+
const map = /* @__PURE__ */ new Map();
|
|
33493
|
+
for (const row of configs) map.set(`${row.agent}|${row.project_id}`, row.execute_agent);
|
|
33494
|
+
return map;
|
|
33495
|
+
}
|
|
33496
|
+
async function loadTasksByIds(db, ids) {
|
|
33497
|
+
if (ids.length === 0) return [];
|
|
33498
|
+
const rows = await db.selectFrom("task").selectAll().where("id", "in", ids).execute();
|
|
33499
|
+
const out = [];
|
|
33500
|
+
for (const row of rows) {
|
|
33501
|
+
const parsed = taskItemSchema.safeParse(row);
|
|
33502
|
+
if (!parsed.success) {
|
|
33503
|
+
console.error(`Failed to parse task row id=${row.id} agent=${row.agent} status=${row.status}: ${parsed.error.message}`);
|
|
33504
|
+
continue;
|
|
33505
|
+
}
|
|
33506
|
+
out.push({
|
|
33507
|
+
agent: row.agent,
|
|
33508
|
+
task: parsed.data
|
|
33509
|
+
});
|
|
33510
|
+
}
|
|
33511
|
+
return out;
|
|
33512
|
+
}
|
|
33513
|
+
async function loadTasksForAgent(db, agent) {
|
|
33514
|
+
const rows = await db.selectFrom("task").selectAll().where("agent", "=", agent).execute();
|
|
33515
|
+
const out = [];
|
|
33516
|
+
for (const row of rows) {
|
|
33517
|
+
const parsed = taskItemSchema.safeParse(row);
|
|
33518
|
+
if (!parsed.success) continue;
|
|
33519
|
+
out.push(parsed.data);
|
|
33520
|
+
}
|
|
33521
|
+
return out;
|
|
33522
|
+
}
|
|
33523
|
+
async function loadAllTasks(db, limit) {
|
|
33524
|
+
const rows = await db.selectFrom("task").selectAll().orderBy("queued_at", "desc").limit(limit).execute();
|
|
33525
|
+
const out = [];
|
|
33526
|
+
for (const row of rows) {
|
|
33527
|
+
const parsed = taskItemSchema.safeParse(row);
|
|
33528
|
+
if (!parsed.success) continue;
|
|
33529
|
+
out.push({
|
|
33530
|
+
agent: row.agent,
|
|
33531
|
+
task: parsed.data
|
|
33532
|
+
});
|
|
33533
|
+
}
|
|
33534
|
+
return out;
|
|
33535
|
+
}
|
|
33536
|
+
async function resolveWatchTargets(options) {
|
|
33537
|
+
const warnings = [];
|
|
33538
|
+
let selected = [];
|
|
33539
|
+
if (options.taskIds.length > 0) {
|
|
33540
|
+
const uniqueTaskIds = Array.from(new Set(options.taskIds));
|
|
33541
|
+
const found = await loadTasksByIds(options.db, uniqueTaskIds);
|
|
33542
|
+
const foundIds = new Set(found.map((t) => t.task.id));
|
|
33543
|
+
const missing = uniqueTaskIds.filter((id) => !foundIds.has(id));
|
|
33544
|
+
if (missing.length > 0) warnings.push(`Requested task IDs not found: ${missing.join(", ")}`);
|
|
33545
|
+
const byId = new Map(found.map((t) => [t.task.id, t]));
|
|
33546
|
+
selected = uniqueTaskIds.map((id) => byId.get(id)).filter(Boolean);
|
|
33547
|
+
if (selected.length === 0) warnings.push("No requested tasks found; falling back to default selection.");
|
|
33548
|
+
} else if (options.agentNames.length > 0) {
|
|
33549
|
+
const uniqueAgents = Array.from(new Set(options.agentNames));
|
|
33550
|
+
if (uniqueAgents.length === 1) {
|
|
33551
|
+
selected = selectSingleAgentWatchTasks(await loadTasksForAgent(options.db, uniqueAgents[0]), { finishedLimit: 3 }).map((task) => ({
|
|
33552
|
+
agent: uniqueAgents[0],
|
|
33553
|
+
task
|
|
33554
|
+
}));
|
|
33555
|
+
if (selected.length === 0) warnings.push(`No tasks found for agent ${uniqueAgents[0]}; falling back to default selection.`);
|
|
33556
|
+
} else {
|
|
33557
|
+
selected = selectMultiAgentWatchTasks(await Promise.all(uniqueAgents.map(async (agent) => {
|
|
33558
|
+
return (await loadTasksForAgent(options.db, agent)).map((task) => ({
|
|
33559
|
+
agent,
|
|
33560
|
+
task
|
|
33561
|
+
}));
|
|
33562
|
+
})).then((groups) => groups.flat()), uniqueAgents);
|
|
33563
|
+
const foundAgents = new Set(selected.map((t) => t.agent));
|
|
33564
|
+
const missing = uniqueAgents.filter((agent) => !foundAgents.has(agent));
|
|
33565
|
+
if (missing.length > 0) warnings.push(`No tasks found for agent(s): ${missing.join(", ")}`);
|
|
33566
|
+
if (selected.length === 0) warnings.push("No requested agent tasks found; falling back to default selection.");
|
|
33567
|
+
}
|
|
33568
|
+
} else warnings.push("No targets specified; selecting up to 3 agents automatically.");
|
|
33569
|
+
if (selected.length === 0) selected = selectFallbackWatchTasks(await loadAllTasks(options.db, 500), { agentLimit: 3 });
|
|
33570
|
+
const executeAgentMap = await getAgentExecuteAgentMap(options.db, selected);
|
|
33571
|
+
return {
|
|
33572
|
+
targets: selected.map((entry) => ({
|
|
33573
|
+
...entry,
|
|
33574
|
+
executeAgent: executeAgentMap.get(`${entry.agent}|${entry.task.project_id}`)
|
|
33575
|
+
})),
|
|
33576
|
+
warnings
|
|
33577
|
+
};
|
|
33578
|
+
}
|
|
33579
|
+
function makeStreamUrl(streamId) {
|
|
33580
|
+
return `${PANTHEON_BASE_URL}/ai_stream_proxy/v2/streams/${encodeURIComponent(streamId)}/stream?format=opaque-stream-json`;
|
|
33581
|
+
}
|
|
33582
|
+
async function buildStepSnapshot(options) {
|
|
33583
|
+
const aggregator = new WatchStepAggregator();
|
|
33584
|
+
let inferredSource;
|
|
33585
|
+
let normalizer = void 0;
|
|
33586
|
+
const url = makeStreamUrl(options.streamId);
|
|
33587
|
+
const res = await fetch(url, { headers: {
|
|
33588
|
+
Authorization: `Bearer ${process.env.PANTHEON_API_KEY}`,
|
|
33589
|
+
Accept: "text/event-stream"
|
|
33590
|
+
} });
|
|
33591
|
+
if (!res.body) return { step: aggregator.snapshot() };
|
|
33592
|
+
await consumeOpaqueSseStream({
|
|
33593
|
+
stream: res.body,
|
|
33594
|
+
idleTimeoutMs: options.idleTimeoutMs,
|
|
33595
|
+
onWarning: (warning) => aggregator.pushUiChunk({
|
|
33596
|
+
type: "data-agent-unknown",
|
|
33597
|
+
data: { reason: warning }
|
|
33598
|
+
}),
|
|
33599
|
+
onData: (payload) => {
|
|
33600
|
+
if (!normalizer) {
|
|
33601
|
+
inferredSource = options.preferredSource ?? detectStreamSourceFromRawPayload(payload) ?? "codex";
|
|
33602
|
+
normalizer = createAgentStreamNormalizer({
|
|
33603
|
+
source: inferredSource,
|
|
33604
|
+
emitStartChunk: false,
|
|
33605
|
+
includeMetaEvents: false,
|
|
33606
|
+
unknownChunkPolicy: "emit"
|
|
33607
|
+
});
|
|
33608
|
+
}
|
|
33609
|
+
const chunks = normalizer.push(payload);
|
|
33610
|
+
aggregator.pushUiChunks(chunks);
|
|
33611
|
+
}
|
|
33612
|
+
});
|
|
33613
|
+
if (normalizer) aggregator.pushUiChunks(normalizer.finish());
|
|
33614
|
+
return {
|
|
33615
|
+
step: aggregator.snapshot(),
|
|
33616
|
+
inferredSource
|
|
33617
|
+
};
|
|
33618
|
+
}
|
|
33619
|
+
async function fetchBranchSnapshot(options) {
|
|
33620
|
+
const { task } = options;
|
|
33621
|
+
if (task.status !== "running" && task.status !== "completed" && task.status !== "failed") return {};
|
|
33622
|
+
const branchId = "branch_id" in task ? task.branch_id ?? void 0 : void 0;
|
|
33623
|
+
if (!branchId) return {};
|
|
33624
|
+
const branch = await getPantheonBranchInfo({
|
|
33625
|
+
projectId: task.project_id,
|
|
33626
|
+
branchId
|
|
33627
|
+
});
|
|
33628
|
+
return {
|
|
33629
|
+
branch,
|
|
33630
|
+
stepIndex: branch.latest_snap?.step_index,
|
|
33631
|
+
streamId: branch.latest_snap?.event_stream_id ?? void 0
|
|
33632
|
+
};
|
|
33633
|
+
}
|
|
33634
|
+
function printSnapshotLineBlock(lines) {
|
|
33635
|
+
lines.forEach((line) => console.log(line));
|
|
33636
|
+
}
|
|
33637
|
+
const ANSI = {
|
|
33638
|
+
reset: "\x1B[0m",
|
|
33639
|
+
bold: "\x1B[1m",
|
|
33640
|
+
dim: "\x1B[2m",
|
|
33641
|
+
red: "\x1B[31m",
|
|
33642
|
+
green: "\x1B[32m",
|
|
33643
|
+
yellow: "\x1B[33m",
|
|
33644
|
+
cyan: "\x1B[36m",
|
|
33645
|
+
gray: "\x1B[90m"
|
|
33646
|
+
};
|
|
33647
|
+
function useAnsiStyles() {
|
|
33648
|
+
if (!process.stdout.isTTY) return false;
|
|
33649
|
+
if (process.env.NO_COLOR != null) return false;
|
|
33650
|
+
return true;
|
|
33651
|
+
}
|
|
33652
|
+
function sgrWrap(text, code, enabled) {
|
|
33653
|
+
return enabled ? `${code}${text}${ANSI.reset}` : text;
|
|
33654
|
+
}
|
|
33655
|
+
function bold(text, enabled) {
|
|
33656
|
+
return sgrWrap(text, ANSI.bold, enabled);
|
|
33657
|
+
}
|
|
33658
|
+
function dim(text, enabled) {
|
|
33659
|
+
return sgrWrap(text, ANSI.dim, enabled);
|
|
33660
|
+
}
|
|
33661
|
+
function gray(text, enabled) {
|
|
33662
|
+
return sgrWrap(text, ANSI.gray, enabled);
|
|
33663
|
+
}
|
|
33664
|
+
function red(text, enabled) {
|
|
33665
|
+
return sgrWrap(text, ANSI.red, enabled);
|
|
33666
|
+
}
|
|
33667
|
+
function green(text, enabled) {
|
|
33668
|
+
return sgrWrap(text, ANSI.green, enabled);
|
|
33669
|
+
}
|
|
33670
|
+
function yellow(text, enabled) {
|
|
33671
|
+
return sgrWrap(text, ANSI.yellow, enabled);
|
|
33672
|
+
}
|
|
33673
|
+
function cyan(text, enabled) {
|
|
33674
|
+
return sgrWrap(text, ANSI.cyan, enabled);
|
|
33675
|
+
}
|
|
33676
|
+
function kv(label, value, style) {
|
|
33677
|
+
return `${dim(`${label}:`, style)} ${value}`;
|
|
33678
|
+
}
|
|
33679
|
+
function formatTaskStatus(status, style) {
|
|
33680
|
+
switch (status) {
|
|
33681
|
+
case "running": return yellow(status, style);
|
|
33682
|
+
case "completed": return green(status, style);
|
|
33683
|
+
case "failed": return red(status, style);
|
|
33684
|
+
case "pending": return cyan(status, style);
|
|
33685
|
+
case "cancelled": return gray(status, style);
|
|
33686
|
+
}
|
|
33687
|
+
}
|
|
33688
|
+
function formatBranchStatus(status, style) {
|
|
33689
|
+
if (!status) return gray("(no branch)", style);
|
|
33690
|
+
switch (status) {
|
|
33691
|
+
case "succeed": return green(status, style);
|
|
33692
|
+
case "failed": return red(status, style);
|
|
33693
|
+
case "running":
|
|
33694
|
+
case "pending": return yellow(status, style);
|
|
33695
|
+
case "manifesting":
|
|
33696
|
+
case "ready_for_manifest": return cyan(status, style);
|
|
33697
|
+
default: return gray(status, style);
|
|
33698
|
+
}
|
|
33699
|
+
}
|
|
33700
|
+
function formatToolLabel(tool, style) {
|
|
33701
|
+
const name = tool.toolName ?? "<tool>";
|
|
33702
|
+
if (name === "command_execution") {
|
|
33703
|
+
const title = typeof tool.title === "string" ? tool.title : "";
|
|
33704
|
+
const inputCommand = typeof tool.input?.command === "string" ? tool.input.command : "";
|
|
33705
|
+
const raw = title || inputCommand;
|
|
33706
|
+
const display = raw ? formatCommandExecutionDisplayCommand(raw) : "(unknown command)";
|
|
33707
|
+
return `${bold("$", style)} ${cyan(display, style)}`;
|
|
33708
|
+
}
|
|
33709
|
+
const renderedName = bold(name, style);
|
|
33710
|
+
if (!tool.title) return renderedName;
|
|
33711
|
+
return `${renderedName}: ${tool.title}`;
|
|
33712
|
+
}
|
|
33713
|
+
function formatToolSummaryLine(tool, style) {
|
|
33714
|
+
const label = formatToolLabel(tool, style);
|
|
33715
|
+
const id = gray(tool.toolCallId, style);
|
|
33716
|
+
switch (tool.status) {
|
|
33717
|
+
case "completed": return `${green("✓", style)} ${label} (${id})`;
|
|
33718
|
+
case "failed": return `${red("✗", style)} ${label} (${id})`;
|
|
33719
|
+
default: return `${yellow("…", style)} ${label} (${id})`;
|
|
33720
|
+
}
|
|
33721
|
+
}
|
|
33722
|
+
function toDisplayLines(text) {
|
|
33723
|
+
return text.replaceAll("\r\n", "\n").split("\n");
|
|
33724
|
+
}
|
|
33725
|
+
function wrapCollapsedWhitespaceToWidth(text, width) {
|
|
33726
|
+
const collapsed = text.trim().replaceAll(/\s+/g, " ");
|
|
33727
|
+
if (!collapsed) return [];
|
|
33728
|
+
if (width <= 0) return [collapsed];
|
|
33729
|
+
const words = collapsed.split(" ");
|
|
33730
|
+
const lines = [];
|
|
33731
|
+
let current = "";
|
|
33732
|
+
function pushLongWord(word) {
|
|
33733
|
+
for (let i = 0; i < word.length; i += width) {
|
|
33734
|
+
const chunk = word.slice(i, i + width);
|
|
33735
|
+
if (chunk.length === width) lines.push(chunk);
|
|
33736
|
+
else current = chunk;
|
|
33737
|
+
}
|
|
33738
|
+
}
|
|
33739
|
+
for (const word of words) {
|
|
33740
|
+
if (!current) {
|
|
33741
|
+
if (word.length <= width) current = word;
|
|
33742
|
+
else pushLongWord(word);
|
|
33743
|
+
continue;
|
|
33744
|
+
}
|
|
33745
|
+
if (current.length + 1 + word.length <= width) {
|
|
33746
|
+
current += ` ${word}`;
|
|
33747
|
+
continue;
|
|
33748
|
+
}
|
|
33749
|
+
lines.push(current);
|
|
33750
|
+
current = "";
|
|
33751
|
+
if (word.length <= width) current = word;
|
|
33752
|
+
else pushLongWord(word);
|
|
33753
|
+
}
|
|
33754
|
+
if (current) lines.push(current);
|
|
33755
|
+
return lines;
|
|
33756
|
+
}
|
|
33757
|
+
function formatWrappedPreviewLines(label, text, options) {
|
|
33758
|
+
const trimmed = text.trim();
|
|
33759
|
+
if (!trimmed) return [];
|
|
33760
|
+
const prefixVisible = visibleLength(`${label}: `);
|
|
33761
|
+
const kept = trimLinesWithEllipsis(wrapCollapsedWhitespaceToWidth(trimmed, Math.max(10, options.widthHint - prefixVisible)), options.maxLines);
|
|
33762
|
+
const indent = " ".repeat(prefixVisible);
|
|
33763
|
+
return kept.map((line, idx) => {
|
|
33764
|
+
const value = options.formatValue ? options.formatValue(line) : line;
|
|
33765
|
+
return idx === 0 ? `${label}: ${value}` : `${indent}${value}`;
|
|
33766
|
+
});
|
|
33767
|
+
}
|
|
33768
|
+
function trimLinesWithEllipsis(lines, maxLines) {
|
|
33769
|
+
if (maxLines <= 0) return [];
|
|
33770
|
+
if (lines.length <= maxLines) return lines;
|
|
33771
|
+
const kept = lines.slice(0, maxLines);
|
|
33772
|
+
const lastIndex = kept.length - 1;
|
|
33773
|
+
const last = kept[lastIndex]?.trimEnd() ?? "";
|
|
33774
|
+
kept[lastIndex] = last ? `${last}…` : "…";
|
|
33775
|
+
return kept;
|
|
33776
|
+
}
|
|
33777
|
+
function formatToolResultPreviewLines(tool, options) {
|
|
33778
|
+
if (tool.status === "in_progress") return [];
|
|
33779
|
+
if (options.maxLines <= 0) return [];
|
|
33780
|
+
if (tool.status === "failed") {
|
|
33781
|
+
const lines = trimLinesWithEllipsis(toDisplayLines(tool.errorText ?? "(no error text)"), options.maxLines);
|
|
33782
|
+
const prefix = dim("error:", options.style);
|
|
33783
|
+
return lines.map((line, idx) => idx === 0 ? `${prefix} ${line}` : ` ${line}`);
|
|
33784
|
+
}
|
|
33785
|
+
if (tool.output == null) return [`${dim("output:", options.style)} (none)`];
|
|
33786
|
+
if (tool.toolName === "command_execution") {
|
|
33787
|
+
const output = tool.output;
|
|
33788
|
+
const exitCode = output?.exit_code ?? null;
|
|
33789
|
+
const outLines = trimLinesWithEllipsis(toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd()), options.maxLines);
|
|
33790
|
+
const exitCodeLabel = typeof exitCode === "number" ? exitCode === 0 ? green(String(exitCode), options.style) : red(String(exitCode), options.style) : gray(String(exitCode), options.style);
|
|
33791
|
+
const outputPrefix = dim("output:", options.style);
|
|
33792
|
+
if (outLines.length === 0) return [`${outputPrefix} exit_code=${exitCodeLabel}`];
|
|
33793
|
+
return outLines.map((line, idx) => idx === 0 ? `${outputPrefix} exit_code=${exitCodeLabel} ${line}` : ` ${line}`);
|
|
33794
|
+
}
|
|
33795
|
+
const lines = trimLinesWithEllipsis(toDisplayLines(inspect(tool.output, {
|
|
33796
|
+
depth: 6,
|
|
33797
|
+
maxArrayLength: 50,
|
|
33798
|
+
maxStringLength: 1e3,
|
|
33799
|
+
compact: true,
|
|
33800
|
+
breakLength: Math.max(20, options.widthHint)
|
|
33801
|
+
})), options.maxLines);
|
|
33802
|
+
const outputPrefix = dim("output:", options.style);
|
|
33803
|
+
return lines.map((line, idx) => idx === 0 ? `${outputPrefix} ${line}` : ` ${line}`);
|
|
33804
|
+
}
|
|
33805
|
+
function formatToolBlocks(step, options) {
|
|
33806
|
+
const tools = options.maxTools != null ? step.toolActions.slice(-options.maxTools) : step.toolActions;
|
|
33807
|
+
if (tools.length === 0) return [` ${gray("(none)", options.style)}`];
|
|
33808
|
+
const widthHint = Math.max(20, options.widthHint);
|
|
33809
|
+
const out = [];
|
|
33810
|
+
for (const tool of tools) {
|
|
33811
|
+
out.push(` ${formatToolSummaryLine(tool, options.style)}`);
|
|
33812
|
+
formatToolResultPreviewLines(tool, {
|
|
33813
|
+
maxLines: options.maxResultLines,
|
|
33814
|
+
widthHint: widthHint - 4,
|
|
33815
|
+
style: options.style
|
|
33816
|
+
}).forEach((line) => out.push(` ${line}`));
|
|
33817
|
+
}
|
|
33818
|
+
return out.map((line) => clip(line, widthHint));
|
|
33819
|
+
}
|
|
33820
|
+
const ANSI_SGR_REGEX = /\u001b\[[0-9;]*m/g;
|
|
33821
|
+
function visibleLength(value) {
|
|
33822
|
+
return value.replaceAll(ANSI_SGR_REGEX, "").length;
|
|
33823
|
+
}
|
|
33824
|
+
function sliceSgrByVisibleWidth(value, maxVisible) {
|
|
33825
|
+
if (maxVisible <= 0) return "";
|
|
33826
|
+
let out = "";
|
|
33827
|
+
let visible = 0;
|
|
33828
|
+
for (let i = 0; i < value.length; i++) {
|
|
33829
|
+
if (visible >= maxVisible) break;
|
|
33830
|
+
const ch = value[i];
|
|
33831
|
+
if (ch === "\x1B" && value[i + 1] === "[") {
|
|
33832
|
+
const end = value.indexOf("m", i);
|
|
33833
|
+
if (end === -1) break;
|
|
33834
|
+
out += value.slice(i, end + 1);
|
|
33835
|
+
i = end;
|
|
33836
|
+
continue;
|
|
33837
|
+
}
|
|
33838
|
+
out += ch;
|
|
33839
|
+
visible += 1;
|
|
33840
|
+
}
|
|
33841
|
+
return out;
|
|
33842
|
+
}
|
|
33843
|
+
function clip(value, maxVisible) {
|
|
33844
|
+
if (maxVisible <= 0) return "";
|
|
33845
|
+
if (visibleLength(value) <= maxVisible) return value;
|
|
33846
|
+
if (maxVisible === 1) return "…";
|
|
33847
|
+
const sliced = sliceSgrByVisibleWidth(value, maxVisible - 1);
|
|
33848
|
+
return `${sliced}…${sliced.includes("\x1B[") ? ANSI.reset : ""}`;
|
|
33849
|
+
}
|
|
33850
|
+
function createWatchCommand(version) {
|
|
33851
|
+
return createCommand("watch").version(version).description("Inspect task progress via live/near-live stream data (internal tooling).").option("--tasks <ids>", "Comma-separated task IDs to watch.", parseCommaList, []).option("--agents <names>", "Comma-separated agent names to watch.", parseCommaList, []).option("--follow", "Follow mode: live updating TUI.", false).option("--idle-timeout-seconds <seconds>", "Snapshot mode: stop reading a stream after this many seconds with no new messages.", (val) => parseFloat(val), 3).action(async function() {
|
|
33852
|
+
ensureEnvOrExit(["PANTHEON_API_KEY", "DATABASE_URL"]);
|
|
33853
|
+
const options = this.opts();
|
|
33854
|
+
if (options.tasks.length > 0 && options.agents.length > 0) {
|
|
33855
|
+
console.error("Use either --tasks or --agents, not both.");
|
|
33856
|
+
process.exit(1);
|
|
33857
|
+
}
|
|
33858
|
+
const idleTimeoutMs = Math.max(0, options.idleTimeoutSeconds * 1e3);
|
|
33859
|
+
const db = createDb();
|
|
33860
|
+
try {
|
|
33861
|
+
const { targets, warnings } = await resolveWatchTargets({
|
|
33862
|
+
db,
|
|
33863
|
+
taskIds: options.tasks,
|
|
33864
|
+
agentNames: options.agents
|
|
33865
|
+
});
|
|
33866
|
+
warnings.forEach((w) => console.error(`warning: ${w}`));
|
|
33867
|
+
if (targets.length === 0) {
|
|
33868
|
+
console.error("No tasks found to watch.");
|
|
33869
|
+
process.exit(1);
|
|
33870
|
+
}
|
|
33871
|
+
if (!options.follow) {
|
|
33872
|
+
const style = useAnsiStyles();
|
|
33873
|
+
const widthHint = process.stdout.columns ?? 120;
|
|
33874
|
+
const now = /* @__PURE__ */ new Date();
|
|
33875
|
+
for (const target of targets) {
|
|
33876
|
+
const { task } = target;
|
|
33877
|
+
const elapsedMs = getTaskElapsedMs(task, now);
|
|
33878
|
+
let branchInfo = {};
|
|
33879
|
+
try {
|
|
33880
|
+
branchInfo = await fetchBranchSnapshot({ task });
|
|
33881
|
+
} catch (error) {
|
|
33882
|
+
console.error(`warning: failed to fetch branch info for task ${task.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
33883
|
+
}
|
|
33884
|
+
const preferredSource = toStreamSourceFromExecuteAgent(target.executeAgent);
|
|
33885
|
+
let stepSnapshot = void 0;
|
|
33886
|
+
let inferredSource = void 0;
|
|
33887
|
+
if (branchInfo.streamId) try {
|
|
33888
|
+
const result = await buildStepSnapshot({
|
|
33889
|
+
streamId: branchInfo.streamId,
|
|
33890
|
+
preferredSource,
|
|
33891
|
+
idleTimeoutMs
|
|
33892
|
+
});
|
|
33893
|
+
stepSnapshot = result.step;
|
|
33894
|
+
inferredSource = result.inferredSource;
|
|
33895
|
+
} catch (error) {
|
|
33896
|
+
console.error(`warning: failed to read stream for task ${task.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
33897
|
+
}
|
|
33898
|
+
const lines = [];
|
|
33899
|
+
const sep = gray(" • ", style);
|
|
33900
|
+
lines.push([
|
|
33901
|
+
kv("task", task.id, style),
|
|
33902
|
+
kv("status", formatTaskStatus(task.status, style), style),
|
|
33903
|
+
kv("agent", target.agent, style)
|
|
33904
|
+
].join(sep));
|
|
33905
|
+
const branchId = "branch_id" in task ? task.branch_id ?? void 0 : void 0;
|
|
33906
|
+
const mergedBranchLine = [
|
|
33907
|
+
kv("project", task.project_id, style),
|
|
33908
|
+
branchId ? kv("branch", branchId, style) : void 0,
|
|
33909
|
+
branchId || branchInfo.branch?.status ? kv("branch_status", formatBranchStatus(branchInfo.branch?.status, style), style) : void 0,
|
|
33910
|
+
branchInfo.stepIndex != null ? kv("step", String(branchInfo.stepIndex), style) : void 0
|
|
33911
|
+
].filter(Boolean).join(sep);
|
|
33912
|
+
if (mergedBranchLine) lines.push(mergedBranchLine);
|
|
33913
|
+
if (branchInfo.streamId) {
|
|
33914
|
+
const src = preferredSource ?? inferredSource;
|
|
33915
|
+
const mergedStreamLine = [kv("stream", branchInfo.streamId, style), src ? kv("source", src, style) : void 0].filter(Boolean).join(sep);
|
|
33916
|
+
if (mergedStreamLine) lines.push(mergedStreamLine);
|
|
33917
|
+
}
|
|
33918
|
+
const start = getTaskStartTime(task);
|
|
33919
|
+
const mergedTimingLine = [start ? kv("started", start.toISOString(), style) : void 0, kv("elapsed", formatDurationMs(elapsedMs), style)].filter(Boolean).join(sep);
|
|
33920
|
+
if (mergedTimingLine) lines.push(mergedTimingLine);
|
|
33921
|
+
if (stepSnapshot) {
|
|
33922
|
+
const reasoningLine = formatPreviewLine(dim("reasoning", style), stepSnapshot.leadingReasoning, { formatValue: (value) => gray(value, style) });
|
|
33923
|
+
if (reasoningLine) lines.push(reasoningLine);
|
|
33924
|
+
lines.push(bold("tools:", style));
|
|
33925
|
+
lines.push(...formatToolBlocks(stepSnapshot, {
|
|
33926
|
+
style,
|
|
33927
|
+
widthHint,
|
|
33928
|
+
maxResultLines: 3
|
|
33929
|
+
}));
|
|
33930
|
+
const messageLine = formatPreviewLine(dim("message", style), stepSnapshot.leadingText);
|
|
33931
|
+
if (messageLine) lines.push(messageLine);
|
|
33932
|
+
if (stepSnapshot.warnings.length > 0) lines.push(kv("warnings", yellow(`${stepSnapshot.warnings.length} (unknown=${stepSnapshot.unknownEventCount})`, style), style));
|
|
33933
|
+
} else lines.push(`${bold("tools:", style)} ${gray("(no stream)", style)}`);
|
|
33934
|
+
printSnapshotLineBlock(lines.map((line) => clip(line, widthHint)));
|
|
33935
|
+
console.log("");
|
|
33936
|
+
}
|
|
33937
|
+
return;
|
|
33938
|
+
}
|
|
33939
|
+
if (!process.stdout.isTTY) {
|
|
33940
|
+
console.error("--follow requires a TTY.");
|
|
33941
|
+
process.exit(1);
|
|
33942
|
+
}
|
|
33943
|
+
const abortController = new AbortController();
|
|
33944
|
+
const onSigInt = () => abortController.abort();
|
|
33945
|
+
process.on("SIGINT", onSigInt);
|
|
33946
|
+
const runtime = targets.map((target) => ({
|
|
33947
|
+
target,
|
|
33948
|
+
aggregator: new WatchStepAggregator({ maxLogLines: 120 }),
|
|
33949
|
+
streamDone: false
|
|
33950
|
+
}));
|
|
33951
|
+
function isTerminalBranchStatus(status) {
|
|
33952
|
+
return status === "succeed" || status === "failed";
|
|
33953
|
+
}
|
|
33954
|
+
async function startStream(rt, streamId) {
|
|
33955
|
+
rt.streamAbort?.abort();
|
|
33956
|
+
rt.streamAbort = new AbortController();
|
|
33957
|
+
rt.streamDone = false;
|
|
33958
|
+
const preferred = toStreamSourceFromExecuteAgent(rt.target.executeAgent);
|
|
33959
|
+
rt.source = preferred;
|
|
33960
|
+
const url = makeStreamUrl(streamId);
|
|
33961
|
+
(async () => {
|
|
33962
|
+
try {
|
|
33963
|
+
const res = await fetch(url, {
|
|
33964
|
+
headers: {
|
|
33965
|
+
Authorization: `Bearer ${process.env.PANTHEON_API_KEY}`,
|
|
33966
|
+
Accept: "text/event-stream"
|
|
33967
|
+
},
|
|
33968
|
+
signal: rt.streamAbort.signal
|
|
33969
|
+
});
|
|
33970
|
+
if (!res.body) {
|
|
33971
|
+
rt.lastError = "missing response body";
|
|
33972
|
+
rt.streamDone = true;
|
|
33973
|
+
return;
|
|
33974
|
+
}
|
|
33975
|
+
let normalizer;
|
|
33976
|
+
await consumeOpaqueSseStream({
|
|
33977
|
+
stream: res.body,
|
|
33978
|
+
signal: rt.streamAbort.signal,
|
|
33979
|
+
onWarning: (warning) => rt.aggregator.pushUiChunk({
|
|
33980
|
+
type: "data-agent-unknown",
|
|
33981
|
+
data: { reason: warning }
|
|
33982
|
+
}),
|
|
33983
|
+
onData: (payload) => {
|
|
33984
|
+
if (!normalizer) {
|
|
33985
|
+
rt.source = preferred ?? detectStreamSourceFromRawPayload(payload) ?? "codex";
|
|
33986
|
+
normalizer = createAgentStreamNormalizer({
|
|
33987
|
+
source: rt.source,
|
|
33988
|
+
emitStartChunk: false,
|
|
33989
|
+
includeMetaEvents: false,
|
|
33990
|
+
unknownChunkPolicy: "emit"
|
|
33991
|
+
});
|
|
33992
|
+
}
|
|
33993
|
+
rt.aggregator.pushUiChunks(normalizer.push(payload));
|
|
33994
|
+
}
|
|
33995
|
+
});
|
|
33996
|
+
if (normalizer) rt.aggregator.pushUiChunks(normalizer.finish());
|
|
33997
|
+
rt.streamDone = true;
|
|
33998
|
+
} catch (error) {
|
|
33999
|
+
if (rt.streamAbort?.signal.aborted) {
|
|
34000
|
+
rt.streamDone = true;
|
|
34001
|
+
return;
|
|
34002
|
+
}
|
|
34003
|
+
rt.lastError = error instanceof Error ? error.message : String(error);
|
|
34004
|
+
rt.streamDone = true;
|
|
34005
|
+
}
|
|
34006
|
+
})();
|
|
34007
|
+
}
|
|
34008
|
+
async function pollBranchesOnce() {
|
|
34009
|
+
await Promise.all(runtime.map(async (rt) => {
|
|
34010
|
+
const { task } = rt.target;
|
|
34011
|
+
if (task.status !== "running" && task.status !== "completed" && task.status !== "failed") return;
|
|
34012
|
+
const branchId = "branch_id" in task ? task.branch_id ?? void 0 : void 0;
|
|
34013
|
+
if (!branchId) return;
|
|
34014
|
+
try {
|
|
34015
|
+
const branch = await getPantheonBranchInfo({
|
|
34016
|
+
projectId: task.project_id,
|
|
34017
|
+
branchId
|
|
34018
|
+
});
|
|
34019
|
+
rt.branch = branch;
|
|
34020
|
+
rt.stepIndex = branch.latest_snap?.step_index;
|
|
34021
|
+
const nextStreamId = branch.latest_snap?.event_stream_id ?? void 0;
|
|
34022
|
+
if (nextStreamId && nextStreamId !== rt.streamId) {
|
|
34023
|
+
rt.streamId = nextStreamId;
|
|
34024
|
+
rt.aggregator = new WatchStepAggregator({ maxLogLines: 120 });
|
|
34025
|
+
await startStream(rt, nextStreamId);
|
|
34026
|
+
}
|
|
34027
|
+
} catch (error) {
|
|
34028
|
+
rt.lastError = error instanceof Error ? error.message : String(error);
|
|
34029
|
+
}
|
|
34030
|
+
}));
|
|
34031
|
+
}
|
|
34032
|
+
const screen = blessed.screen({
|
|
34033
|
+
smartCSR: true,
|
|
34034
|
+
title: "pantheon-agents watch"
|
|
34035
|
+
});
|
|
34036
|
+
screen.key(["q", "C-c"], () => abortController.abort());
|
|
34037
|
+
const footer = blessed.box({
|
|
34038
|
+
parent: screen,
|
|
34039
|
+
bottom: 0,
|
|
34040
|
+
left: 0,
|
|
34041
|
+
height: 1,
|
|
34042
|
+
width: "100%",
|
|
34043
|
+
tags: false,
|
|
34044
|
+
content: "Ctrl+C to exit • q to quit",
|
|
34045
|
+
style: { fg: "gray" }
|
|
34046
|
+
});
|
|
34047
|
+
const panes = runtime.map(() => blessed.box({
|
|
34048
|
+
parent: screen,
|
|
34049
|
+
top: 0,
|
|
34050
|
+
left: 0,
|
|
34051
|
+
height: "100%-1",
|
|
34052
|
+
width: "100%",
|
|
34053
|
+
border: "line",
|
|
34054
|
+
tags: false,
|
|
34055
|
+
scrollable: false,
|
|
34056
|
+
wrap: false,
|
|
34057
|
+
style: { border: { fg: "gray" } }
|
|
34058
|
+
}));
|
|
34059
|
+
let lastGlobalError;
|
|
34060
|
+
function layoutPanes(columns) {
|
|
34061
|
+
const n = panes.length;
|
|
34062
|
+
for (let i = 0; i < n; i++) {
|
|
34063
|
+
const left = Math.floor(i * columns / n);
|
|
34064
|
+
const right = Math.floor((i + 1) * columns / n);
|
|
34065
|
+
panes[i].left = left;
|
|
34066
|
+
panes[i].width = Math.max(1, right - left);
|
|
34067
|
+
}
|
|
34068
|
+
}
|
|
34069
|
+
const renderTimer = setInterval(() => {
|
|
34070
|
+
const style = useAnsiStyles();
|
|
34071
|
+
const columns = Math.max(40, screen.width ?? 120);
|
|
34072
|
+
const rows = Math.max(8, screen.height ?? 24);
|
|
34073
|
+
layoutPanes(columns);
|
|
34074
|
+
const paneWidth = Math.max(1, Math.floor(columns / runtime.length));
|
|
34075
|
+
const paneInnerWidth = Math.max(0, paneWidth - 2);
|
|
34076
|
+
const paneOuterHeight = Math.max(1, rows - 1);
|
|
34077
|
+
const maxContentLines = Math.max(0, paneOuterHeight - 2);
|
|
34078
|
+
const nextFooterParts = ["Ctrl+C to exit", "q to quit"];
|
|
34079
|
+
if (lastGlobalError) nextFooterParts.push(`error: ${lastGlobalError}`);
|
|
34080
|
+
footer.setContent(nextFooterParts.join(" • "));
|
|
34081
|
+
runtime.forEach((rt, idx) => {
|
|
34082
|
+
const { task } = rt.target;
|
|
34083
|
+
const step = rt.aggregator.snapshot();
|
|
34084
|
+
const elapsed = formatDurationMs(getTaskElapsedMs(task, /* @__PURE__ */ new Date()));
|
|
34085
|
+
const title = [
|
|
34086
|
+
rt.target.agent,
|
|
34087
|
+
`task ${task.id}`,
|
|
34088
|
+
formatTaskStatus(task.status, style),
|
|
34089
|
+
formatBranchStatus(rt.branch?.status, style),
|
|
34090
|
+
rt.stepIndex != null ? `step ${rt.stepIndex}` : void 0,
|
|
34091
|
+
`elapsed ${elapsed}`
|
|
34092
|
+
].filter(Boolean).join(gray(" • ", style));
|
|
34093
|
+
const branchId = "branch_id" in task ? task.branch_id ?? void 0 : void 0;
|
|
34094
|
+
const headerLines = [kv("project", task.project_id, style), kv("branch", branchId ? branchId : gray("(no branch)", style), style)];
|
|
34095
|
+
const reasoningLines = formatWrappedPreviewLines(dim("reasoning", style), step.leadingReasoning, {
|
|
34096
|
+
maxLines: 4,
|
|
34097
|
+
widthHint: paneInnerWidth,
|
|
34098
|
+
formatValue: (value) => gray(value, style)
|
|
34099
|
+
});
|
|
34100
|
+
const messageLine = formatPreviewLine(dim("message", style), step.leadingText);
|
|
34101
|
+
const extraLines = [];
|
|
34102
|
+
if (rt.lastError) extraLines.push(kv("error", red(rt.lastError, style), style));
|
|
34103
|
+
if (step.warnings.length > 0) extraLines.push(kv("warnings", yellow(`${step.warnings.length} (unknown=${step.unknownEventCount})`, style), style));
|
|
34104
|
+
const toolLines = step.toolActions.filter((t) => t.toolName !== "command_execution").slice(-3).map((tool) => formatToolSummaryLine(tool, style));
|
|
34105
|
+
const baseLines = [
|
|
34106
|
+
...headerLines,
|
|
34107
|
+
...reasoningLines,
|
|
34108
|
+
...toolLines,
|
|
34109
|
+
...messageLine ? [messageLine] : [],
|
|
34110
|
+
...extraLines
|
|
34111
|
+
];
|
|
34112
|
+
const remainingForLogs = Math.max(0, maxContentLines - baseLines.length);
|
|
34113
|
+
const logLines = step.logLines.slice(-remainingForLogs);
|
|
34114
|
+
panes[idx].setLabel(title);
|
|
34115
|
+
panes[idx].setContent([...baseLines, ...logLines].map((line) => clip(line, paneInnerWidth)).join("\n"));
|
|
34116
|
+
});
|
|
34117
|
+
screen.render();
|
|
34118
|
+
}, 300);
|
|
34119
|
+
let pollTimer;
|
|
34120
|
+
let doneTimer;
|
|
34121
|
+
try {
|
|
34122
|
+
await pollBranchesOnce();
|
|
34123
|
+
if (abortController.signal.aborted) return;
|
|
34124
|
+
pollTimer = setInterval(() => {
|
|
34125
|
+
pollBranchesOnce().catch((e) => {
|
|
34126
|
+
lastGlobalError = e instanceof Error ? e.message : String(e);
|
|
34127
|
+
});
|
|
34128
|
+
}, 2e3);
|
|
34129
|
+
await new Promise((resolve) => {
|
|
34130
|
+
let resolved = false;
|
|
34131
|
+
const resolveOnce = () => {
|
|
34132
|
+
if (resolved) return;
|
|
34133
|
+
resolved = true;
|
|
34134
|
+
if (doneTimer) {
|
|
34135
|
+
clearInterval(doneTimer);
|
|
34136
|
+
doneTimer = void 0;
|
|
34137
|
+
}
|
|
34138
|
+
resolve();
|
|
34139
|
+
};
|
|
34140
|
+
abortController.signal.addEventListener("abort", resolveOnce, { once: true });
|
|
34141
|
+
if (abortController.signal.aborted) {
|
|
34142
|
+
resolveOnce();
|
|
34143
|
+
return;
|
|
34144
|
+
}
|
|
34145
|
+
const terminalGraceMs = Math.max(1e3, idleTimeoutMs);
|
|
34146
|
+
let terminalSince;
|
|
34147
|
+
doneTimer = setInterval(() => {
|
|
34148
|
+
const now = Date.now();
|
|
34149
|
+
if (!runtime.every((rt) => isTerminalBranchStatus(rt.branch?.status))) {
|
|
34150
|
+
terminalSince = void 0;
|
|
34151
|
+
return;
|
|
34152
|
+
}
|
|
34153
|
+
terminalSince ??= now;
|
|
34154
|
+
if (runtime.every((rt) => !rt.streamId || rt.streamDone)) {
|
|
34155
|
+
resolveOnce();
|
|
34156
|
+
return;
|
|
34157
|
+
}
|
|
34158
|
+
if (now - terminalSince >= terminalGraceMs) resolveOnce();
|
|
34159
|
+
}, 1e3);
|
|
34160
|
+
});
|
|
34161
|
+
} finally {
|
|
34162
|
+
process.off("SIGINT", onSigInt);
|
|
34163
|
+
clearInterval(renderTimer);
|
|
34164
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
34165
|
+
if (doneTimer) clearInterval(doneTimer);
|
|
34166
|
+
runtime.forEach((rt) => rt.streamAbort?.abort());
|
|
34167
|
+
screen.destroy();
|
|
34168
|
+
}
|
|
34169
|
+
} finally {
|
|
34170
|
+
await db.destroy();
|
|
34171
|
+
}
|
|
34172
|
+
});
|
|
34173
|
+
}
|
|
34174
|
+
|
|
34175
|
+
//#endregion
|
|
34176
|
+
//#region src/cli/index.ts
|
|
34177
|
+
const program = new Command().name("pantheon-agents").description("Pantheon agents CLI").version(version$1).showHelpAfterError().showSuggestionAfterError().addHelpCommand().addCommand(createAddTaskCommand(version$1)).addCommand(createConfigAgentCommand(version$1)).addCommand(createDeleteTaskCommand(version$1)).addCommand(createReconfigAgentCommand(version$1)).addCommand(createRunAgentCommand(version$1)).addCommand(createServerCommand(version$1)).addCommand(createShowConfigCommand(version$1)).addCommand(createShowTasksCommand(version$1)).addCommand(createWatchCommand(version$1));
|
|
34178
|
+
async function main() {
|
|
34179
|
+
if (process$1.argv.length <= 2) {
|
|
34180
|
+
program.outputHelp();
|
|
34181
|
+
return;
|
|
34182
|
+
}
|
|
34183
|
+
await program.parseAsync(process$1.argv);
|
|
34184
|
+
}
|
|
34185
|
+
try {
|
|
34186
|
+
await main();
|
|
34187
|
+
} catch (error) {
|
|
34188
|
+
console.error(getErrorMessage(error));
|
|
34189
|
+
process$1.exitCode = 1;
|
|
32004
34190
|
}
|
|
32005
|
-
|
|
32006
|
-
//#endregion
|
|
32007
|
-
//#region src/cli/index.ts
|
|
32008
|
-
warnIfMissingEnv(["PANTHEON_API_KEY", "DATABASE_URL"]);
|
|
32009
|
-
const commands = {
|
|
32010
|
-
"add-task": createAddTaskCommand(version$1),
|
|
32011
|
-
config: createConfigAgentCommand(version$1),
|
|
32012
|
-
"delete-task": createDeleteTaskCommand(version$1),
|
|
32013
|
-
reconfig: createReconfigAgentCommand(version$1),
|
|
32014
|
-
run: createRunAgentCommand(version$1),
|
|
32015
|
-
server: createServerCommand(version$1),
|
|
32016
|
-
"show-config": createShowConfigCommand(version$1),
|
|
32017
|
-
"show-tasks": createShowTasksCommand(version$1)
|
|
32018
|
-
};
|
|
32019
|
-
function printCommandHelpAndExit(command) {
|
|
32020
|
-
console.error(`Invalid command: ${command}. Supported commands: ${Object.keys(commands).join(", ")}.`);
|
|
32021
|
-
console.error(" Run pantheon-agents help <command> for more information.");
|
|
32022
|
-
process$1.exit(1);
|
|
32023
|
-
}
|
|
32024
|
-
if (process$1.argv[2] === "help") {
|
|
32025
|
-
const command = process$1.argv[3];
|
|
32026
|
-
if (command in commands) commands[command].help({ error: false });
|
|
32027
|
-
else printCommandHelpAndExit(command);
|
|
32028
|
-
}
|
|
32029
|
-
if (!commands[process$1.argv[2]]) printCommandHelpAndExit(process$1.argv[2]);
|
|
32030
|
-
commands[process$1.argv[2]].parse(process$1.argv.slice(3), { from: "user" });
|
|
32031
34191
|
|
|
32032
34192
|
//#endregion
|
|
32033
34193
|
export { };
|