pipe-kan 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pipe-kan.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync4, writeSync } from "node:fs";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join8 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/board.ts
|
|
10
10
|
function formatDueDate(value) {
|
|
@@ -904,25 +904,148 @@ function handleAppApi(req, res, app) {
|
|
|
904
904
|
return false;
|
|
905
905
|
}
|
|
906
906
|
|
|
907
|
+
// src/agent-context.ts
|
|
908
|
+
function boardViewContext(view) {
|
|
909
|
+
return [
|
|
910
|
+
{ type: "text", text: formatBoardView({ ...view, scope: view.scope ?? "view" }) },
|
|
911
|
+
{
|
|
912
|
+
type: "resource",
|
|
913
|
+
resource: {
|
|
914
|
+
uri: "pipe-kan://board",
|
|
915
|
+
mimeType: "application/json",
|
|
916
|
+
text: JSON.stringify(view)
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
];
|
|
920
|
+
}
|
|
921
|
+
function pipedBoardContext(board) {
|
|
922
|
+
return boardViewContext({
|
|
923
|
+
scope: "pipe",
|
|
924
|
+
kind: "stories",
|
|
925
|
+
selectedEpic: null,
|
|
926
|
+
search: "",
|
|
927
|
+
filter: {},
|
|
928
|
+
sort: "payload",
|
|
929
|
+
hide: [],
|
|
930
|
+
columns: board.columns,
|
|
931
|
+
epics: board.epics
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
function hasAttachedBoard(context = []) {
|
|
935
|
+
return context.some((block) => block.type === "resource" && block.resource.uri === "pipe-kan://board");
|
|
936
|
+
}
|
|
937
|
+
function withPipedBoardContext(board, clientContext = []) {
|
|
938
|
+
if (hasAttachedBoard(clientContext))
|
|
939
|
+
return clientContext;
|
|
940
|
+
return [...pipedBoardContext(board), ...clientContext];
|
|
941
|
+
}
|
|
942
|
+
function formatBoardView(view) {
|
|
943
|
+
const opener = view.kind === "epics" ? "All epics" : "All stories";
|
|
944
|
+
const scope = view.selectedEpic ? `${opener}, children of ${view.selectedEpic}` : opener;
|
|
945
|
+
const lines = [
|
|
946
|
+
view.scope === "pipe" ? "The user is working with piped Jira issues (the full payload). Answer from these Jira issues. This is not GitHub and not the local source repo." : "The user attached the current Jira Kanban board (visible cards only). Answer from these Jira issues. This is not GitHub and not the local source repo.",
|
|
947
|
+
`Board: ${scope}`
|
|
948
|
+
];
|
|
949
|
+
if (view.search.trim())
|
|
950
|
+
lines.push(`Search: ${view.search.trim()}`);
|
|
951
|
+
if (Object.values(view.filter).some((values) => values.length)) {
|
|
952
|
+
lines.push(`Filter: ${JSON.stringify(view.filter)}`);
|
|
953
|
+
}
|
|
954
|
+
lines.push(`Sort: ${view.sort}`);
|
|
955
|
+
if (view.hide.length)
|
|
956
|
+
lines.push(`Hidden columns: ${view.hide.join(", ")}`);
|
|
957
|
+
lines.push("", "Columns:");
|
|
958
|
+
if (!view.columns.length) {
|
|
959
|
+
lines.push("(empty)");
|
|
960
|
+
} else {
|
|
961
|
+
for (const column of view.columns) {
|
|
962
|
+
lines.push(`## ${column.title}`);
|
|
963
|
+
if (!column.cards.length) {
|
|
964
|
+
lines.push("(no cards)");
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
for (const card of column.cards) {
|
|
968
|
+
lines.push(`- ${formatCard(card)}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (view.epics.length) {
|
|
973
|
+
lines.push("", "Epics:");
|
|
974
|
+
for (const epic of view.epics) {
|
|
975
|
+
const status = epic.status ? ` (${epic.status})` : "";
|
|
976
|
+
lines.push(`- ${epic.key} ${epic.summary}${status}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return lines.join(`
|
|
980
|
+
`);
|
|
981
|
+
}
|
|
982
|
+
function formatCard(card) {
|
|
983
|
+
const bits = [card.key, card.summary];
|
|
984
|
+
if (card.epic)
|
|
985
|
+
bits.push(`[epic ${card.epic}]`);
|
|
986
|
+
const extras = [card.priority, card.assignee, card.dueDate, card.labels?.join(", ")].filter((value) => Boolean(value));
|
|
987
|
+
if (extras.length)
|
|
988
|
+
bits.push(`(${extras.join(", ")})`);
|
|
989
|
+
return bits.join(" ");
|
|
990
|
+
}
|
|
991
|
+
|
|
907
992
|
// src/server/agent/config.ts
|
|
908
|
-
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
993
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
909
994
|
import { homedir } from "node:os";
|
|
910
|
-
import { join as join2 } from "node:path";
|
|
995
|
+
import { delimiter as delimiter2, dirname, join as join2 } from "node:path";
|
|
911
996
|
var DEFAULT_CONFIG_PATH = join2(homedir(), ".config", "pipe-kan", "agent.json");
|
|
997
|
+
var FALLBACK_MODELS = {
|
|
998
|
+
cursor: [
|
|
999
|
+
{ id: "composer-2", name: "Composer 2" },
|
|
1000
|
+
{ id: "composer-2-fast", name: "Composer 2 Fast" },
|
|
1001
|
+
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
|
|
1002
|
+
{ id: "gpt-4.1", name: "GPT-4.1" }
|
|
1003
|
+
],
|
|
1004
|
+
devin: [
|
|
1005
|
+
{ id: "swe-1-6-slow", name: "SWE-1.6 (slow)", description: "Free slower tier" },
|
|
1006
|
+
{ id: "swe-1-6-fast", name: "SWE-1.6 Fast" },
|
|
1007
|
+
{ id: "swe-1-7", name: "SWE-1.7" },
|
|
1008
|
+
{ id: "swe", name: "SWE (latest)" },
|
|
1009
|
+
{ id: "adaptive", name: "Adaptive" },
|
|
1010
|
+
{ id: "sonnet", name: "Sonnet" },
|
|
1011
|
+
{ id: "opus", name: "Opus" },
|
|
1012
|
+
{ id: "gpt", name: "GPT" }
|
|
1013
|
+
]
|
|
1014
|
+
};
|
|
912
1015
|
var DEFAULT_CONFIG = {
|
|
913
|
-
defaultAgent: "
|
|
1016
|
+
defaultAgent: "devin",
|
|
1017
|
+
defaultSkill: null,
|
|
914
1018
|
agents: {
|
|
915
1019
|
cursor: {
|
|
916
1020
|
command: "cursor-agent",
|
|
917
|
-
args: ["acp"]
|
|
1021
|
+
args: ["acp"],
|
|
1022
|
+
model: "claude-sonnet-4"
|
|
918
1023
|
},
|
|
919
1024
|
devin: {
|
|
920
1025
|
command: "devin",
|
|
921
|
-
args: ["acp"]
|
|
1026
|
+
args: ["acp"],
|
|
1027
|
+
model: "swe-1-6-slow"
|
|
922
1028
|
}
|
|
923
1029
|
}
|
|
924
1030
|
};
|
|
925
|
-
function
|
|
1031
|
+
function agentConfigPath() {
|
|
1032
|
+
return process.env.PIPE_KAN_AGENT_CONFIG ?? DEFAULT_CONFIG_PATH;
|
|
1033
|
+
}
|
|
1034
|
+
function commandOnPath(command, env = process.env) {
|
|
1035
|
+
if (!command)
|
|
1036
|
+
return false;
|
|
1037
|
+
if (command.includes("/") || command.includes("\\"))
|
|
1038
|
+
return existsSync2(command);
|
|
1039
|
+
const pathVar = env.PATH ?? "";
|
|
1040
|
+
for (const dir of pathVar.split(delimiter2)) {
|
|
1041
|
+
if (!dir)
|
|
1042
|
+
continue;
|
|
1043
|
+
if (existsSync2(join2(dir, command)))
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
function loadAgentConfig(path = agentConfigPath()) {
|
|
926
1049
|
if (!existsSync2(path))
|
|
927
1050
|
return structuredClone(DEFAULT_CONFIG);
|
|
928
1051
|
try {
|
|
@@ -932,16 +1055,43 @@ function loadAgentConfig(path = DEFAULT_CONFIG_PATH) {
|
|
|
932
1055
|
return structuredClone(DEFAULT_CONFIG);
|
|
933
1056
|
}
|
|
934
1057
|
}
|
|
1058
|
+
function saveAgentConfig(patch, path = agentConfigPath()) {
|
|
1059
|
+
const current = loadAgentConfig(path);
|
|
1060
|
+
const next = {
|
|
1061
|
+
defaultAgent: patch.defaultAgent ?? current.defaultAgent,
|
|
1062
|
+
defaultSkill: patch.defaultSkill !== undefined ? patch.defaultSkill : current.defaultSkill,
|
|
1063
|
+
agents: { ...current.agents }
|
|
1064
|
+
};
|
|
1065
|
+
if (patch.agents) {
|
|
1066
|
+
for (const [id, config] of Object.entries(patch.agents)) {
|
|
1067
|
+
next.agents[id] = { ...next.agents[id], ...config };
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
ensureAgentConfigDir(path);
|
|
1071
|
+
writeFileSync(path, `${JSON.stringify(next, null, 2)}
|
|
1072
|
+
`);
|
|
1073
|
+
return next;
|
|
1074
|
+
}
|
|
935
1075
|
function mergeConfig(raw) {
|
|
1076
|
+
const agents = { ...DEFAULT_CONFIG.agents };
|
|
1077
|
+
for (const [id, config] of Object.entries({ ...DEFAULT_CONFIG.agents, ...raw.agents })) {
|
|
1078
|
+
agents[id] = { ...DEFAULT_CONFIG.agents[id], ...config };
|
|
1079
|
+
}
|
|
936
1080
|
return {
|
|
937
1081
|
defaultAgent: raw.defaultAgent ?? DEFAULT_CONFIG.defaultAgent,
|
|
938
|
-
|
|
1082
|
+
defaultSkill: raw.defaultSkill ?? DEFAULT_CONFIG.defaultSkill,
|
|
1083
|
+
agents
|
|
939
1084
|
};
|
|
940
1085
|
}
|
|
1086
|
+
function ensureAgentConfigDir(path = agentConfigPath()) {
|
|
1087
|
+
const dir = dirname(path);
|
|
1088
|
+
if (!existsSync2(dir))
|
|
1089
|
+
mkdirSync(dir, { recursive: true });
|
|
1090
|
+
}
|
|
941
1091
|
|
|
942
1092
|
// src/server/agent/session.ts
|
|
943
1093
|
import { spawn as spawn2 } from "node:child_process";
|
|
944
|
-
import { mkdtempSync } from "node:fs";
|
|
1094
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
945
1095
|
import { tmpdir } from "node:os";
|
|
946
1096
|
import { join as join3 } from "node:path";
|
|
947
1097
|
import { Readable } from "node:stream";
|
|
@@ -10571,25 +10721,93 @@ var legacyClientNotificationMethods = new Set([
|
|
|
10571
10721
|
CLIENT_METHODS.elicitation_complete
|
|
10572
10722
|
]);
|
|
10573
10723
|
|
|
10574
|
-
// src/server/agent/
|
|
10575
|
-
function
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10724
|
+
// src/server/agent/models.ts
|
|
10725
|
+
function modelsFromConfigOptions(options) {
|
|
10726
|
+
if (!options?.length)
|
|
10727
|
+
return [];
|
|
10728
|
+
const model = options.find((option) => option.category === "model" || option.id === "model");
|
|
10729
|
+
if (!model || model.type !== "select")
|
|
10730
|
+
return [];
|
|
10731
|
+
return flattenSelectOptions(model.options);
|
|
10732
|
+
}
|
|
10733
|
+
function selectedModelFromConfigOptions(options) {
|
|
10734
|
+
const model = options?.find((option) => option.category === "model" || option.id === "model");
|
|
10735
|
+
if (!model || model.type !== "select")
|
|
10736
|
+
return null;
|
|
10737
|
+
return model.currentValue ?? null;
|
|
10738
|
+
}
|
|
10739
|
+
var MODEL_ALIASES = {
|
|
10740
|
+
"swe-1-6": ["swe-1-6-slow"],
|
|
10741
|
+
"swe-1.6": ["swe-1-6-slow"],
|
|
10742
|
+
"swe-1.6-slow": ["swe-1-6-slow"],
|
|
10743
|
+
"swe-1-6-slow": ["swe-1-6"]
|
|
10744
|
+
};
|
|
10745
|
+
function resolveRequestedModel(requested, live) {
|
|
10746
|
+
if (!requested)
|
|
10747
|
+
return null;
|
|
10748
|
+
if (!live.length)
|
|
10749
|
+
return requested;
|
|
10750
|
+
if (live.some((model) => model.id === requested))
|
|
10751
|
+
return requested;
|
|
10752
|
+
for (const alias of MODEL_ALIASES[requested] ?? []) {
|
|
10753
|
+
const hit = live.find((model) => model.id === alias);
|
|
10754
|
+
if (hit)
|
|
10755
|
+
return hit.id;
|
|
10756
|
+
}
|
|
10757
|
+
const lower = requested.toLowerCase();
|
|
10758
|
+
return live.find((model) => model.name.toLowerCase() === lower)?.id ?? null;
|
|
10759
|
+
}
|
|
10760
|
+
function errorText(err) {
|
|
10761
|
+
if (err instanceof Error) {
|
|
10762
|
+
const extra = err.data;
|
|
10763
|
+
return extra ? `${err.message} ${JSON.stringify(extra)}` : err.message;
|
|
10764
|
+
}
|
|
10765
|
+
return String(err);
|
|
10766
|
+
}
|
|
10767
|
+
function parseAvailableModelsFromError(err) {
|
|
10768
|
+
const text = errorText(err);
|
|
10769
|
+
const match = text.match(/Available models:\s*([^\n"}]+)/i);
|
|
10770
|
+
if (!match)
|
|
10771
|
+
return [];
|
|
10772
|
+
return match[1].split(",").map((id) => id.trim()).filter(Boolean);
|
|
10773
|
+
}
|
|
10774
|
+
function flattenSelectOptions(options) {
|
|
10775
|
+
if (!Array.isArray(options))
|
|
10776
|
+
return [];
|
|
10777
|
+
const models = [];
|
|
10778
|
+
for (const item of options) {
|
|
10779
|
+
if (!item || typeof item !== "object")
|
|
10780
|
+
continue;
|
|
10781
|
+
if ("group" in item && Array.isArray(item.options)) {
|
|
10782
|
+
models.push(...flattenSelectOptions(item.options));
|
|
10783
|
+
continue;
|
|
10582
10784
|
}
|
|
10583
|
-
|
|
10785
|
+
if ("value" in item && "name" in item) {
|
|
10786
|
+
const option = item;
|
|
10787
|
+
models.push({
|
|
10788
|
+
id: option.value,
|
|
10789
|
+
name: option.name,
|
|
10790
|
+
...option.description ? { description: option.description } : {}
|
|
10791
|
+
});
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
return models;
|
|
10584
10795
|
}
|
|
10585
10796
|
|
|
10797
|
+
// src/server/agent/session.ts
|
|
10798
|
+
var CONNECT_TIMEOUT_MS = 15000;
|
|
10799
|
+
function createAcpSession(config) {
|
|
10800
|
+
return new AcpSession(config);
|
|
10801
|
+
}
|
|
10586
10802
|
class AcpSession {
|
|
10587
10803
|
config;
|
|
10588
10804
|
id;
|
|
10589
10805
|
proc = null;
|
|
10590
10806
|
activeSession = null;
|
|
10807
|
+
agentCtx = null;
|
|
10808
|
+
configOptions = [];
|
|
10591
10809
|
eventsQueue = [];
|
|
10592
|
-
|
|
10810
|
+
listeners = new Set;
|
|
10593
10811
|
pendingPermissionResolvers = new Map;
|
|
10594
10812
|
closed = false;
|
|
10595
10813
|
lifetime = null;
|
|
@@ -10597,30 +10815,51 @@ class AcpSession {
|
|
|
10597
10815
|
textBuffer = "";
|
|
10598
10816
|
detectToolCall = null;
|
|
10599
10817
|
toolCalls = new Map;
|
|
10818
|
+
shouldAutoApproveTool = null;
|
|
10819
|
+
executeTool = null;
|
|
10820
|
+
stderr = "";
|
|
10821
|
+
selectedModelId = null;
|
|
10822
|
+
workspace = mkdtempSync(join3(tmpdir(), "pipe-kan-agent-"));
|
|
10600
10823
|
constructor(config) {
|
|
10601
10824
|
this.config = config;
|
|
10602
10825
|
this.id = crypto.randomUUID();
|
|
10826
|
+
this.selectedModelId = config.model ?? null;
|
|
10827
|
+
this.ready.promise.catch(() => {
|
|
10828
|
+
return;
|
|
10829
|
+
});
|
|
10603
10830
|
}
|
|
10604
10831
|
async prompt(text, context) {
|
|
10605
10832
|
if (!this.activeSession)
|
|
10606
10833
|
throw new Error("ACP session not connected");
|
|
10607
|
-
const
|
|
10608
|
-
|
|
10609
|
-
|
|
10834
|
+
const resources = (context ?? []).filter((block) => block.type === "resource").map(toContentBlock);
|
|
10835
|
+
const contextText = (context ?? []).filter((block) => block.type === "text").map((block) => block.text).filter(Boolean).join(`
|
|
10836
|
+
|
|
10837
|
+
`);
|
|
10838
|
+
const prompt = contextText ? `${contextText}
|
|
10839
|
+
|
|
10840
|
+
${text}` : text;
|
|
10841
|
+
await this.activeSession.prompt([...resources, { type: "text", text: prompt }]);
|
|
10610
10842
|
}
|
|
10611
10843
|
setToolParser(parser) {
|
|
10612
10844
|
this.detectToolCall = parser;
|
|
10613
10845
|
}
|
|
10846
|
+
setToolExecutor(shouldAutoApprove, execute) {
|
|
10847
|
+
this.shouldAutoApproveTool = shouldAutoApprove;
|
|
10848
|
+
this.executeTool = execute;
|
|
10849
|
+
}
|
|
10614
10850
|
pendingToolCalls() {
|
|
10615
10851
|
return this.toolCalls;
|
|
10616
10852
|
}
|
|
10617
|
-
resolveToolCall(requestId, resultText) {
|
|
10853
|
+
resolveToolCall(requestId, resultText, rawResult) {
|
|
10618
10854
|
const call = this.toolCalls.get(requestId);
|
|
10619
10855
|
if (!call)
|
|
10620
10856
|
return;
|
|
10621
10857
|
this.toolCalls.delete(requestId);
|
|
10858
|
+
this.push({ type: "tool_result", requestId, name: call.name, result: rawResult ?? resultText });
|
|
10622
10859
|
this.prompt(`Tool result for ${call.name}(${JSON.stringify(call.args)}):
|
|
10623
|
-
${resultText}`)
|
|
10860
|
+
${resultText}`).catch(() => {
|
|
10861
|
+
return;
|
|
10862
|
+
});
|
|
10624
10863
|
}
|
|
10625
10864
|
approve(requestId, decision) {
|
|
10626
10865
|
const entry = this.pendingPermissionResolvers.get(requestId);
|
|
@@ -10631,21 +10870,78 @@ ${resultText}`);
|
|
|
10631
10870
|
entry.resolve(outcome);
|
|
10632
10871
|
this.pendingPermissionResolvers.delete(requestId);
|
|
10633
10872
|
}
|
|
10873
|
+
models() {
|
|
10874
|
+
const live = modelsFromConfigOptions(this.configOptions);
|
|
10875
|
+
return live.length ? live : [];
|
|
10876
|
+
}
|
|
10877
|
+
selectedModel() {
|
|
10878
|
+
return selectedModelFromConfigOptions(this.configOptions) ?? this.selectedModelId;
|
|
10879
|
+
}
|
|
10880
|
+
async setModel(id) {
|
|
10881
|
+
const live = this.models();
|
|
10882
|
+
let resolved = resolveRequestedModel(id, live);
|
|
10883
|
+
if (!resolved && live.length)
|
|
10884
|
+
return;
|
|
10885
|
+
resolved = resolved ?? id;
|
|
10886
|
+
this.selectedModelId = resolved;
|
|
10887
|
+
this.config.model = resolved;
|
|
10888
|
+
const option = this.configOptions.find((item) => item.category === "model" || item.id === "model");
|
|
10889
|
+
if (!option || option.type !== "select" || !this.agentCtx || !this.activeSession)
|
|
10890
|
+
return;
|
|
10891
|
+
try {
|
|
10892
|
+
const result = await this.agentCtx.request(methods.agent.session.setConfigOption, {
|
|
10893
|
+
sessionId: this.activeSession.sessionId,
|
|
10894
|
+
configId: option.id,
|
|
10895
|
+
value: resolved
|
|
10896
|
+
});
|
|
10897
|
+
if (result.configOptions)
|
|
10898
|
+
this.configOptions = result.configOptions;
|
|
10899
|
+
this.selectedModelId = this.selectedModel();
|
|
10900
|
+
this.push({ type: "config", models: this.models(), selectedModel: this.selectedModel() });
|
|
10901
|
+
} catch (err) {
|
|
10902
|
+
const available = parseAvailableModelsFromError(err).map((modelId) => ({ id: modelId, name: modelId }));
|
|
10903
|
+
const retry = resolveRequestedModel(id, available) ?? resolveRequestedModel(resolved, available);
|
|
10904
|
+
if (retry && retry !== resolved) {
|
|
10905
|
+
await this.setModel(retry);
|
|
10906
|
+
return;
|
|
10907
|
+
}
|
|
10908
|
+
this.selectedModelId = selectedModelFromConfigOptions(this.configOptions);
|
|
10909
|
+
throw new Error(errorText(err));
|
|
10910
|
+
}
|
|
10911
|
+
}
|
|
10912
|
+
subscribe(listener) {
|
|
10913
|
+
this.listeners.add(listener);
|
|
10914
|
+
if (this.eventsQueue.length) {
|
|
10915
|
+
const queued = this.eventsQueue.splice(0);
|
|
10916
|
+
for (const event of queued)
|
|
10917
|
+
listener(event);
|
|
10918
|
+
}
|
|
10919
|
+
return () => {
|
|
10920
|
+
this.listeners.delete(listener);
|
|
10921
|
+
};
|
|
10922
|
+
}
|
|
10634
10923
|
cancel() {
|
|
10635
10924
|
this.proc?.kill("SIGTERM");
|
|
10636
10925
|
}
|
|
10637
10926
|
async* [Symbol.asyncIterator]() {
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10927
|
+
const queue = [];
|
|
10928
|
+
let wake = null;
|
|
10929
|
+
const unsub = this.subscribe((event) => {
|
|
10930
|
+
queue.push(event);
|
|
10931
|
+
wake?.();
|
|
10932
|
+
});
|
|
10933
|
+
try {
|
|
10934
|
+
while (!this.closed || queue.length) {
|
|
10935
|
+
if (!queue.length) {
|
|
10936
|
+
await new Promise((resolve) => {
|
|
10937
|
+
wake = resolve;
|
|
10938
|
+
});
|
|
10939
|
+
}
|
|
10940
|
+
while (queue.length)
|
|
10941
|
+
yield queue.shift();
|
|
10648
10942
|
}
|
|
10943
|
+
} finally {
|
|
10944
|
+
unsub();
|
|
10649
10945
|
}
|
|
10650
10946
|
}
|
|
10651
10947
|
events() {
|
|
@@ -10653,47 +10949,83 @@ ${resultText}`);
|
|
|
10653
10949
|
}
|
|
10654
10950
|
async close() {
|
|
10655
10951
|
this.closed = true;
|
|
10952
|
+
this.ready.reject(new Error("ACP session closed"));
|
|
10656
10953
|
this.activeSession?.dispose();
|
|
10657
10954
|
this.lifetime?.resolve();
|
|
10658
10955
|
this.proc?.kill("SIGTERM");
|
|
10659
10956
|
this.flushQueue();
|
|
10660
10957
|
this.proc = null;
|
|
10958
|
+
rmSync(this.workspace, { recursive: true, force: true });
|
|
10661
10959
|
}
|
|
10662
10960
|
async connect() {
|
|
10663
|
-
|
|
10961
|
+
if (this.closed)
|
|
10962
|
+
throw new Error("ACP session closed");
|
|
10963
|
+
const cwd = this.workspace;
|
|
10664
10964
|
const cmd = this.config.command;
|
|
10665
10965
|
const args = this.config.args ?? [];
|
|
10666
10966
|
this.proc = spawn2(cmd, args, {
|
|
10667
|
-
cwd,
|
|
10967
|
+
cwd: process.cwd(),
|
|
10668
10968
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
10669
10969
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10670
10970
|
});
|
|
10971
|
+
if (this.closed) {
|
|
10972
|
+
this.proc.kill("SIGTERM");
|
|
10973
|
+
this.proc = null;
|
|
10974
|
+
throw new Error("ACP session closed");
|
|
10975
|
+
}
|
|
10671
10976
|
if (!this.proc.stdout || !this.proc.stdin) {
|
|
10672
10977
|
throw new Error(`Failed to spawn ACP agent: ${cmd}`);
|
|
10673
10978
|
}
|
|
10979
|
+
this.proc.stderr?.on("data", (chunk) => {
|
|
10980
|
+
this.stderr += chunk.toString("utf8");
|
|
10981
|
+
});
|
|
10674
10982
|
const stdout = Readable.toWeb(this.proc.stdout);
|
|
10675
10983
|
const stream = ndJsonStream2(new WritableStream({
|
|
10676
10984
|
write: (chunk) => this.writeStdin(chunk)
|
|
10677
10985
|
}), stdout);
|
|
10678
|
-
this.proc.on("error", (err) =>
|
|
10679
|
-
|
|
10986
|
+
this.proc.on("error", (err) => {
|
|
10987
|
+
this.push({ type: "error", message: err.message });
|
|
10988
|
+
this.ready.reject(err);
|
|
10989
|
+
});
|
|
10990
|
+
this.proc.on("exit", (code) => {
|
|
10680
10991
|
this.push({ type: "disconnected" });
|
|
10681
10992
|
this.closed = true;
|
|
10682
10993
|
this.flushQueue();
|
|
10994
|
+
if (code) {
|
|
10995
|
+
this.ready.reject(new Error(`ACP agent exited (${code})${this.stderr.trim() ? `: ${this.stderr.trim()}` : ""}`));
|
|
10996
|
+
}
|
|
10683
10997
|
});
|
|
10684
10998
|
const app = client({ name: "pipe-kan" });
|
|
10685
10999
|
app.onRequest(methods.client.session.requestPermission, (ctx) => this.handlePermission(ctx.params));
|
|
10686
11000
|
app.onRequest(methods.client.fs.readTextFile, () => Promise.reject(new Error("read_file not implemented")));
|
|
10687
11001
|
this.lifetime = Promise.withResolvers();
|
|
10688
11002
|
const connected = app.connectWith(stream, async (ctx) => {
|
|
11003
|
+
this.agentCtx = ctx;
|
|
10689
11004
|
this.activeSession = await ctx.buildSession(cwd).start();
|
|
11005
|
+
this.configOptions = this.activeSession.newSessionResponse.configOptions ?? [];
|
|
11006
|
+
try {
|
|
11007
|
+
if (this.config.model)
|
|
11008
|
+
await this.setModel(this.config.model);
|
|
11009
|
+
} catch (err) {
|
|
11010
|
+
this.push({ type: "error", message: `Could not set model ${this.config.model}: ${errorText(err)}` });
|
|
11011
|
+
}
|
|
10690
11012
|
this.ready.resolve();
|
|
10691
11013
|
this.push({ type: "connected" });
|
|
11014
|
+
this.push({ type: "config", models: this.models(), selectedModel: this.selectedModel() });
|
|
10692
11015
|
this.readLoop();
|
|
10693
11016
|
await this.lifetime.promise;
|
|
10694
11017
|
});
|
|
10695
11018
|
connected.catch(() => {});
|
|
10696
|
-
|
|
11019
|
+
const timeout = setTimeout(() => {
|
|
11020
|
+
this.ready.reject(new Error(`Timed out starting ${cmd}${this.stderr.trim() ? `:
|
|
11021
|
+
${lastLines(this.stderr)}` : ""}`));
|
|
11022
|
+
this.close();
|
|
11023
|
+
}, CONNECT_TIMEOUT_MS);
|
|
11024
|
+
try {
|
|
11025
|
+
await this.ready.promise;
|
|
11026
|
+
} finally {
|
|
11027
|
+
clearTimeout(timeout);
|
|
11028
|
+
}
|
|
10697
11029
|
}
|
|
10698
11030
|
async readLoop() {
|
|
10699
11031
|
if (!this.activeSession)
|
|
@@ -10718,7 +11050,11 @@ ${resultText}`);
|
|
|
10718
11050
|
const tool = this.detectToolCall?.(this.textBuffer);
|
|
10719
11051
|
if (tool) {
|
|
10720
11052
|
this.toolCalls.set(tool.requestId, tool);
|
|
10721
|
-
this.
|
|
11053
|
+
if (this.shouldAutoApproveTool?.(tool.name)) {
|
|
11054
|
+
this.executeTool?.(tool).then(({ text, result }) => this.resolveToolCall(tool.requestId, text, result));
|
|
11055
|
+
} else {
|
|
11056
|
+
this.push({ type: "tool_call", ...tool });
|
|
11057
|
+
}
|
|
10722
11058
|
return;
|
|
10723
11059
|
}
|
|
10724
11060
|
this.push({ type: "agent_message_chunk", text: update.content.text });
|
|
@@ -10726,9 +11062,12 @@ ${resultText}`);
|
|
|
10726
11062
|
this.push({
|
|
10727
11063
|
type: "tool_call",
|
|
10728
11064
|
requestId: update.toolCallId ?? crypto.randomUUID(),
|
|
10729
|
-
name: update.
|
|
11065
|
+
name: update.title ?? "unknown",
|
|
10730
11066
|
args: {}
|
|
10731
11067
|
});
|
|
11068
|
+
} else if (update.sessionUpdate === "config_option_update") {
|
|
11069
|
+
this.configOptions = update.configOptions ?? this.configOptions;
|
|
11070
|
+
this.push({ type: "config", models: this.models(), selectedModel: this.selectedModel() });
|
|
10732
11071
|
}
|
|
10733
11072
|
}
|
|
10734
11073
|
handlePermission(params) {
|
|
@@ -10738,7 +11077,7 @@ ${resultText}`);
|
|
|
10738
11077
|
this.push({
|
|
10739
11078
|
type: "request_permission",
|
|
10740
11079
|
requestId,
|
|
10741
|
-
kind: params.toolCall.
|
|
11080
|
+
kind: params.toolCall.title ?? "unknown",
|
|
10742
11081
|
description: params.toolCall.content ? JSON.stringify(params.toolCall.content) : ""
|
|
10743
11082
|
});
|
|
10744
11083
|
return promise.then((outcome) => ({ outcome }));
|
|
@@ -10754,19 +11093,23 @@ ${resultText}`);
|
|
|
10754
11093
|
}
|
|
10755
11094
|
}
|
|
10756
11095
|
push(event) {
|
|
10757
|
-
|
|
10758
|
-
if (resolver) {
|
|
10759
|
-
resolver({ value: event, done: false });
|
|
10760
|
-
} else {
|
|
11096
|
+
if (this.listeners.size === 0) {
|
|
10761
11097
|
this.eventsQueue.push(event);
|
|
11098
|
+
return;
|
|
10762
11099
|
}
|
|
11100
|
+
for (const listener of this.listeners)
|
|
11101
|
+
listener(event);
|
|
10763
11102
|
}
|
|
10764
11103
|
flushQueue() {
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
11104
|
+
this.eventsQueue.length = 0;
|
|
11105
|
+
for (const listener of this.listeners)
|
|
11106
|
+
listener({ type: "disconnected" });
|
|
10768
11107
|
}
|
|
10769
11108
|
}
|
|
11109
|
+
function lastLines(text, n = 12) {
|
|
11110
|
+
return text.trim().split(/\n/).slice(-n).join(`
|
|
11111
|
+
`);
|
|
11112
|
+
}
|
|
10770
11113
|
function toContentBlock(ctx) {
|
|
10771
11114
|
if (ctx.type === "resource") {
|
|
10772
11115
|
return {
|
|
@@ -10862,6 +11205,8 @@ function parseFrontMatter(text) {
|
|
|
10862
11205
|
}
|
|
10863
11206
|
|
|
10864
11207
|
// src/server/agent/tools.ts
|
|
11208
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
11209
|
+
import { join as join5 } from "node:path";
|
|
10865
11210
|
var TOOLS = [
|
|
10866
11211
|
{
|
|
10867
11212
|
name: "board_state",
|
|
@@ -10875,6 +11220,18 @@ var TOOLS = [
|
|
|
10875
11220
|
parameters: { key: { type: "string", description: "Issue key, e.g. DEMO-123" } },
|
|
10876
11221
|
mutates: false
|
|
10877
11222
|
},
|
|
11223
|
+
{
|
|
11224
|
+
name: "run_skill",
|
|
11225
|
+
description: "Load a skill by id and return its instructions as context.",
|
|
11226
|
+
parameters: { skillId: { type: "string", description: "Skill id, e.g. triage" } },
|
|
11227
|
+
mutates: false
|
|
11228
|
+
},
|
|
11229
|
+
{
|
|
11230
|
+
name: "read_repo_file",
|
|
11231
|
+
description: "Read a file under the repo root as plain text.",
|
|
11232
|
+
parameters: { path: { type: "string", description: "Relative repo path" } },
|
|
11233
|
+
mutates: false
|
|
11234
|
+
},
|
|
10878
11235
|
{
|
|
10879
11236
|
name: "move_card",
|
|
10880
11237
|
description: "Move a card to a new status. Requires user approval because it changes Jira via jira-cli.",
|
|
@@ -10903,6 +11260,7 @@ var TOOLS = [
|
|
|
10903
11260
|
mutates: true
|
|
10904
11261
|
}
|
|
10905
11262
|
];
|
|
11263
|
+
var skills = createSkillRegistry();
|
|
10906
11264
|
var EXECUTORS = {
|
|
10907
11265
|
board_state(_, app) {
|
|
10908
11266
|
const board = app.board();
|
|
@@ -10921,6 +11279,32 @@ var EXECUTORS = {
|
|
|
10921
11279
|
return { ok: false, error: result.error };
|
|
10922
11280
|
return { ok: true, value: { url: result.url, fields: result.fields } };
|
|
10923
11281
|
},
|
|
11282
|
+
run_skill(args) {
|
|
11283
|
+
const skillId = String(args.skillId ?? "");
|
|
11284
|
+
if (!skillId)
|
|
11285
|
+
return { ok: false, error: "Missing skillId" };
|
|
11286
|
+
const skill = skills.load(skillId);
|
|
11287
|
+
if (!skill)
|
|
11288
|
+
return { ok: false, error: `Skill not found: ${skillId}` };
|
|
11289
|
+
const block = skillContextBlock(skill);
|
|
11290
|
+
if (block.type !== "resource")
|
|
11291
|
+
return { ok: false, error: "Skill produced unexpected block" };
|
|
11292
|
+
return { ok: true, value: block.resource };
|
|
11293
|
+
},
|
|
11294
|
+
read_repo_file(args) {
|
|
11295
|
+
const relPath = String(args.path ?? "");
|
|
11296
|
+
if (!relPath)
|
|
11297
|
+
return { ok: false, error: "Missing path" };
|
|
11298
|
+
if (relPath.includes(".."))
|
|
11299
|
+
return { ok: false, error: "Path traversal not allowed" };
|
|
11300
|
+
const repoRoot = process.cwd();
|
|
11301
|
+
try {
|
|
11302
|
+
const text = readFileSync3(join5(repoRoot, relPath), "utf8");
|
|
11303
|
+
return { ok: true, value: { path: relPath, text } };
|
|
11304
|
+
} catch (err) {
|
|
11305
|
+
return { ok: false, error: String(err) };
|
|
11306
|
+
}
|
|
11307
|
+
},
|
|
10924
11308
|
async move_card(args, app) {
|
|
10925
11309
|
const key = String(args.key ?? "");
|
|
10926
11310
|
const status = String(args.status ?? "");
|
|
@@ -10940,16 +11324,18 @@ var EXECUTORS = {
|
|
|
10940
11324
|
const name = String(args.name ?? "");
|
|
10941
11325
|
if (!name)
|
|
10942
11326
|
return { ok: false, error: "Missing preset name" };
|
|
10943
|
-
return { ok: true, value:
|
|
11327
|
+
return { ok: true, value: { __ui_action: "apply_preset", preset: name } };
|
|
10944
11328
|
},
|
|
10945
11329
|
set_filter(args) {
|
|
10946
11330
|
const filter = args.filter;
|
|
10947
11331
|
if (!filter || typeof filter !== "object")
|
|
10948
11332
|
return { ok: false, error: "Missing filter object" };
|
|
10949
|
-
return { ok: true, value:
|
|
11333
|
+
return { ok: true, value: { __ui_action: "set_filter", filter } };
|
|
10950
11334
|
}
|
|
10951
11335
|
};
|
|
10952
|
-
var SYSTEM_TEXT = `
|
|
11336
|
+
var SYSTEM_TEXT = `Your working context is the attached Jira issues from the pipe. Answer from that payload. Do not search GitHub, git history, or local source files unless the user explicitly asks about the pipe-kan app itself.
|
|
11337
|
+
|
|
11338
|
+
You can call tools by emitting a single JSON code block matching this schema:
|
|
10953
11339
|
|
|
10954
11340
|
{"tool": "<name>", "args": {...}}
|
|
10955
11341
|
|
|
@@ -10986,6 +11372,9 @@ function createToolRegistry() {
|
|
|
10986
11372
|
return;
|
|
10987
11373
|
}
|
|
10988
11374
|
},
|
|
11375
|
+
isMutating(name) {
|
|
11376
|
+
return TOOLS.find((t) => t.name === name)?.mutates ?? true;
|
|
11377
|
+
},
|
|
10989
11378
|
async execute(name, args, app) {
|
|
10990
11379
|
const executor = EXECUTORS[name];
|
|
10991
11380
|
if (!executor)
|
|
@@ -11001,7 +11390,9 @@ function createToolRegistry() {
|
|
|
11001
11390
|
|
|
11002
11391
|
// src/server/agent/api.ts
|
|
11003
11392
|
var sessions = new Map;
|
|
11004
|
-
var
|
|
11393
|
+
var connecting = null;
|
|
11394
|
+
var startEpoch = 0;
|
|
11395
|
+
var skills2 = createSkillRegistry();
|
|
11005
11396
|
var tools = createToolRegistry();
|
|
11006
11397
|
function json2(res, status, body) {
|
|
11007
11398
|
res.statusCode = status;
|
|
@@ -11019,39 +11410,113 @@ function readBody2(req) {
|
|
|
11019
11410
|
function pathOf2(req) {
|
|
11020
11411
|
return new URL(req.url ?? "/", "http://127.0.0.1");
|
|
11021
11412
|
}
|
|
11413
|
+
function publicConfig(cfg) {
|
|
11414
|
+
return {
|
|
11415
|
+
defaultAgent: cfg.defaultAgent,
|
|
11416
|
+
defaultSkill: cfg.defaultSkill ?? null,
|
|
11417
|
+
agents: Object.entries(cfg.agents).map(([id, c]) => ({
|
|
11418
|
+
id,
|
|
11419
|
+
command: [c.command, ...c.args ?? []].join(" "),
|
|
11420
|
+
model: c.model ?? null,
|
|
11421
|
+
options: c.options,
|
|
11422
|
+
available: commandOnPath(c.command),
|
|
11423
|
+
models: FALLBACK_MODELS[id] ?? []
|
|
11424
|
+
}))
|
|
11425
|
+
};
|
|
11426
|
+
}
|
|
11427
|
+
function attachTools(session, app) {
|
|
11428
|
+
session.setToolParser((text) => {
|
|
11429
|
+
const call = tools.parse(text);
|
|
11430
|
+
return call ? { requestId: call.requestId, name: call.name, args: call.args } : undefined;
|
|
11431
|
+
});
|
|
11432
|
+
session.setToolExecutor((name) => !tools.isMutating(name), async (call) => {
|
|
11433
|
+
const result = await tools.execute(call.name, call.args, app);
|
|
11434
|
+
return {
|
|
11435
|
+
text: result.ok ? `Result: ${JSON.stringify(result.value)}` : `Error: ${result.error}`,
|
|
11436
|
+
result: result.ok ? result.value : undefined
|
|
11437
|
+
};
|
|
11438
|
+
});
|
|
11439
|
+
}
|
|
11022
11440
|
function handleAgentApi(req, res, app) {
|
|
11023
11441
|
const url = pathOf2(req);
|
|
11024
11442
|
const method = (req.method ?? "GET").toUpperCase();
|
|
11025
11443
|
if (url.pathname === "/api/agent/config" && method === "GET") {
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
});
|
|
11444
|
+
json2(res, 200, publicConfig(loadAgentConfig()));
|
|
11445
|
+
return true;
|
|
11446
|
+
}
|
|
11447
|
+
if (url.pathname === "/api/agent/config" && method === "POST") {
|
|
11448
|
+
readBody2(req).then((text) => {
|
|
11449
|
+
const body = text ? JSON.parse(text) : {};
|
|
11450
|
+
json2(res, 200, publicConfig(saveAgentConfig(body)));
|
|
11451
|
+
}).catch((err) => json2(res, 500, { error: String(err) }));
|
|
11034
11452
|
return true;
|
|
11035
11453
|
}
|
|
11036
11454
|
if (url.pathname === "/api/agent/skills" && method === "GET") {
|
|
11037
|
-
json2(res, 200,
|
|
11455
|
+
json2(res, 200, skills2.list().map((s) => ({ id: s.id, name: s.name, description: s.description })));
|
|
11038
11456
|
return true;
|
|
11039
11457
|
}
|
|
11040
11458
|
if (url.pathname === "/api/agent/session" && method === "POST") {
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
|
|
11459
|
+
readBody2(req).then(async (text) => {
|
|
11460
|
+
const body = text ? JSON.parse(text) : {};
|
|
11461
|
+
const cfg = loadAgentConfig();
|
|
11462
|
+
const agentId = body.agentId || cfg.defaultAgent;
|
|
11463
|
+
const backendConfig = cfg.agents[agentId];
|
|
11464
|
+
if (!backendConfig) {
|
|
11465
|
+
json2(res, 500, { error: `No agent config for ${agentId}` });
|
|
11466
|
+
return;
|
|
11467
|
+
}
|
|
11468
|
+
if (!commandOnPath(backendConfig.command)) {
|
|
11469
|
+
json2(res, 500, { error: `${backendConfig.command} is not on PATH` });
|
|
11470
|
+
return;
|
|
11471
|
+
}
|
|
11472
|
+
const session = createAcpSession({
|
|
11473
|
+
...backendConfig,
|
|
11474
|
+
model: body.model || backendConfig.model
|
|
11052
11475
|
});
|
|
11053
|
-
|
|
11054
|
-
|
|
11476
|
+
const epoch = ++startEpoch;
|
|
11477
|
+
const previous = connecting;
|
|
11478
|
+
const old = [...sessions.values()];
|
|
11479
|
+
connecting = session;
|
|
11480
|
+
sessions.clear();
|
|
11481
|
+
await Promise.all([previous, ...old].filter((item) => item != null).map((item) => item.close().catch(() => {
|
|
11482
|
+
return;
|
|
11483
|
+
})));
|
|
11484
|
+
try {
|
|
11485
|
+
await session.connect();
|
|
11486
|
+
if (epoch !== startEpoch) {
|
|
11487
|
+
await session.close().catch(() => {
|
|
11488
|
+
return;
|
|
11489
|
+
});
|
|
11490
|
+
json2(res, 500, { error: "Session replaced" });
|
|
11491
|
+
return;
|
|
11492
|
+
}
|
|
11493
|
+
connecting = null;
|
|
11494
|
+
attachTools(session, app);
|
|
11495
|
+
sessions.set(session.id, session);
|
|
11496
|
+
json2(res, 200, {
|
|
11497
|
+
sessionId: session.id,
|
|
11498
|
+
agentId,
|
|
11499
|
+
models: session.models().length ? session.models() : FALLBACK_MODELS[agentId] ?? [],
|
|
11500
|
+
selectedModel: session.selectedModel()
|
|
11501
|
+
});
|
|
11502
|
+
} catch (err) {
|
|
11503
|
+
if (connecting === session)
|
|
11504
|
+
connecting = null;
|
|
11505
|
+
throw err;
|
|
11506
|
+
}
|
|
11507
|
+
}).catch((err) => json2(res, 500, { error: String(err) }));
|
|
11508
|
+
return true;
|
|
11509
|
+
}
|
|
11510
|
+
if (url.pathname === "/api/agent/model" && method === "POST") {
|
|
11511
|
+
readBody2(req).then(async (text) => {
|
|
11512
|
+
const body = JSON.parse(text);
|
|
11513
|
+
const session = sessions.get(body.sessionId ?? "");
|
|
11514
|
+
if (!session) {
|
|
11515
|
+
json2(res, 404, { error: "Session not found" });
|
|
11516
|
+
return;
|
|
11517
|
+
}
|
|
11518
|
+
await session.setModel(String(body.model ?? ""));
|
|
11519
|
+
json2(res, 200, { ok: true, models: session.models(), selectedModel: session.selectedModel() });
|
|
11055
11520
|
}).catch((err) => json2(res, 500, { error: String(err) }));
|
|
11056
11521
|
return true;
|
|
11057
11522
|
}
|
|
@@ -11063,9 +11528,9 @@ function handleAgentApi(req, res, app) {
|
|
|
11063
11528
|
json2(res, 404, { error: "Session not found" });
|
|
11064
11529
|
return;
|
|
11065
11530
|
}
|
|
11066
|
-
const context = [tools.systemBlock(), ...body.context ?? []];
|
|
11531
|
+
const context = [tools.systemBlock(), ...withPipedBoardContext(app.board(), body.context ?? [])];
|
|
11067
11532
|
if (body.skillId) {
|
|
11068
|
-
const skill =
|
|
11533
|
+
const skill = skills2.load(body.skillId);
|
|
11069
11534
|
if (skill)
|
|
11070
11535
|
context.push(skillContextBlock(skill));
|
|
11071
11536
|
}
|
|
@@ -11105,7 +11570,7 @@ function handleAgentApi(req, res, app) {
|
|
|
11105
11570
|
} else {
|
|
11106
11571
|
const result = await tools.execute(toolCall.name, toolCall.args, app);
|
|
11107
11572
|
const resultText = result.ok ? `Result: ${JSON.stringify(result.value)}` : `Error: ${result.error}`;
|
|
11108
|
-
session.resolveToolCall(requestId, resultText);
|
|
11573
|
+
session.resolveToolCall(requestId, resultText, result.ok ? result.value : undefined);
|
|
11109
11574
|
}
|
|
11110
11575
|
json2(res, 200, { ok: true });
|
|
11111
11576
|
return;
|
|
@@ -11135,21 +11600,13 @@ function handleAgentApi(req, res, app) {
|
|
|
11135
11600
|
|
|
11136
11601
|
`);
|
|
11137
11602
|
};
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
if (event.type === "disconnected") {
|
|
11142
|
-
break;
|
|
11143
|
-
}
|
|
11144
|
-
}
|
|
11145
|
-
if (!res.writableEnded)
|
|
11146
|
-
res.end();
|
|
11147
|
-
})().catch((err) => {
|
|
11148
|
-
writeEvent({ type: "error", message: String(err) });
|
|
11149
|
-
if (!res.writableEnded)
|
|
11603
|
+
const unsubscribe = session.subscribe((event) => {
|
|
11604
|
+
writeEvent(event);
|
|
11605
|
+
if (event.type === "disconnected" && !res.writableEnded)
|
|
11150
11606
|
res.end();
|
|
11151
11607
|
});
|
|
11152
11608
|
req.on("close", () => {
|
|
11609
|
+
unsubscribe();
|
|
11153
11610
|
if (!res.writableEnded)
|
|
11154
11611
|
res.end();
|
|
11155
11612
|
});
|
|
@@ -11264,12 +11721,12 @@ function handleRequest(req, res, ctx) {
|
|
|
11264
11721
|
}
|
|
11265
11722
|
|
|
11266
11723
|
// src/jira-config.ts
|
|
11267
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
11268
|
-
import { join as
|
|
11724
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11725
|
+
import { join as join6 } from "node:path";
|
|
11269
11726
|
function writeJiraConfig(dir, server) {
|
|
11270
|
-
|
|
11271
|
-
const path =
|
|
11272
|
-
|
|
11727
|
+
mkdirSync2(dir, { recursive: true });
|
|
11728
|
+
const path = join6(dir, "jira.config.yml");
|
|
11729
|
+
writeFileSync2(path, [
|
|
11273
11730
|
"installation: Cloud",
|
|
11274
11731
|
`server: ${server}`,
|
|
11275
11732
|
`login: ${ME.emailAddress}`,
|
|
@@ -11324,7 +11781,7 @@ function stdinStat() {
|
|
|
11324
11781
|
|
|
11325
11782
|
// src/ui.ts
|
|
11326
11783
|
import { createReadStream, existsSync as existsSync4, statSync } from "node:fs";
|
|
11327
|
-
import { dirname, extname, join as
|
|
11784
|
+
import { dirname as dirname2, extname, join as join7, resolve as resolve2, sep } from "node:path";
|
|
11328
11785
|
import { fileURLToPath } from "node:url";
|
|
11329
11786
|
var types = {
|
|
11330
11787
|
".css": "text/css; charset=utf-8",
|
|
@@ -11338,10 +11795,10 @@ var types = {
|
|
|
11338
11795
|
".woff2": "font/woff2"
|
|
11339
11796
|
};
|
|
11340
11797
|
function packageRoot(from = import.meta.url) {
|
|
11341
|
-
return resolve2(
|
|
11798
|
+
return resolve2(dirname2(fileURLToPath(from)), "..");
|
|
11342
11799
|
}
|
|
11343
11800
|
function uiDir(root) {
|
|
11344
|
-
return
|
|
11801
|
+
return join7(root, "dist", "ui");
|
|
11345
11802
|
}
|
|
11346
11803
|
function inside(root, file) {
|
|
11347
11804
|
const base = resolve2(root);
|
|
@@ -11350,7 +11807,7 @@ function inside(root, file) {
|
|
|
11350
11807
|
}
|
|
11351
11808
|
function sendUi(root, req, res) {
|
|
11352
11809
|
const ui = uiDir(root);
|
|
11353
|
-
const index =
|
|
11810
|
+
const index = join7(ui, "index.html");
|
|
11354
11811
|
if (!existsSync4(index))
|
|
11355
11812
|
return false;
|
|
11356
11813
|
const path = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
|
|
@@ -11371,7 +11828,7 @@ function announce(line) {
|
|
|
11371
11828
|
}
|
|
11372
11829
|
async function runServer(opts) {
|
|
11373
11830
|
const piped = await readPipe();
|
|
11374
|
-
const raw = piped ?? JSON.parse(
|
|
11831
|
+
const raw = piped ?? JSON.parse(readFileSync4(join8(opts.root, "fixtures/issues.json"), "utf8"));
|
|
11375
11832
|
const { app, store, kind } = await createBoardApp({
|
|
11376
11833
|
raw,
|
|
11377
11834
|
piped: Boolean(piped)
|
|
@@ -11391,7 +11848,7 @@ async function runServer(opts) {
|
|
|
11391
11848
|
const { host, port } = resolveListen();
|
|
11392
11849
|
await bindListen(server, host, port);
|
|
11393
11850
|
const origin = `http://127.0.0.1:${port}`;
|
|
11394
|
-
const fakeConfig = writeJiraConfig(
|
|
11851
|
+
const fakeConfig = writeJiraConfig(join8(tmpdir2(), "pipe-kan"), origin);
|
|
11395
11852
|
announce(`pipe-kan http://${host}:${port}`);
|
|
11396
11853
|
announce(`cli ${kind === "jira" ? resolveJiraBin() : "store"}`);
|
|
11397
11854
|
announce(`Fake Jira ${origin}/rest/api/2/search`);
|