pipe-kan 0.30.1 → 0.32.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/README.md +1 -1
- package/dist/pipe-kan.js +382 -47
- package/dist/ui/assets/index-DH0NTte3.js +56 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-CyO_1G_D.js +0 -56
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ bunx pipe-kan --plane --projects APH,PULSE,PKAN,PUI,DEC
|
|
|
57
57
|
| `PLANE_API_KEY` | (required in Plane mode) | personal access token (`X-API-Key`) |
|
|
58
58
|
| `PLANE_HOST` | `https://plane.tail48fe8.ts.net` | Plane origin; API is `{host}/api/v1` |
|
|
59
59
|
|
|
60
|
-
Same `--projects` style as Jira: comma-separated, trimmed, uppercased. The header **Scope flags** field accepts the same string, then **Refresh**.
|
|
60
|
+
Same `--projects` style as Jira: comma-separated, trimmed, uppercased. The header **Scope flags** field accepts the same string, then **Refresh**. In Plane mode a header **Workspace** pill (beside the project-identifier chip) types a slug, writes `--workspace` into Scope flags, and Refresh-confirms all Epics. Jira omits the pill. Next launch still uses argv `--workspace`, else `PLANE_WORKSPACE`, else `personal`.
|
|
61
61
|
|
|
62
62
|
On Refresh:
|
|
63
63
|
|
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 readFileSync7, writeSync } from "node:fs";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join11 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/app.ts
|
|
10
10
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -399,8 +399,16 @@ function parseFlags(flags) {
|
|
|
399
399
|
statusEq.push(status);
|
|
400
400
|
continue;
|
|
401
401
|
}
|
|
402
|
-
if (token === "--projects") {
|
|
403
|
-
|
|
402
|
+
if (token === "--projects" || token.startsWith("--projects=")) {
|
|
403
|
+
const parts = [];
|
|
404
|
+
if (token.startsWith("--projects=")) {
|
|
405
|
+
parts.push(token.slice("--projects=".length));
|
|
406
|
+
}
|
|
407
|
+
while (i + 1 < list.length && !list[i + 1].startsWith("-")) {
|
|
408
|
+
i++;
|
|
409
|
+
parts.push(list[i]);
|
|
410
|
+
}
|
|
411
|
+
projectsValue = parts.join(",");
|
|
404
412
|
continue;
|
|
405
413
|
}
|
|
406
414
|
if (token === "-q" || token === "--jql") {
|
|
@@ -1504,6 +1512,12 @@ function createApp(opts) {
|
|
|
1504
1512
|
error: err instanceof Error ? err.message : "jira issue view failed"
|
|
1505
1513
|
};
|
|
1506
1514
|
}
|
|
1515
|
+
},
|
|
1516
|
+
async listWorkspaces() {
|
|
1517
|
+
return cli.listWorkspaces?.() ?? [];
|
|
1518
|
+
},
|
|
1519
|
+
async listProjectIdentifiers(workspace) {
|
|
1520
|
+
return cli.listProjectIdentifiers?.(workspace) ?? [];
|
|
1507
1521
|
}
|
|
1508
1522
|
};
|
|
1509
1523
|
return app;
|
|
@@ -1877,6 +1891,19 @@ function createPlaneCli(opts) {
|
|
|
1877
1891
|
return;
|
|
1878
1892
|
return { id, identifier, name: asString(row?.name) ?? identifier };
|
|
1879
1893
|
}
|
|
1894
|
+
function parseWorkspace(raw) {
|
|
1895
|
+
const row = asRecord(raw);
|
|
1896
|
+
const slug = asString(row?.slug);
|
|
1897
|
+
if (!slug)
|
|
1898
|
+
return;
|
|
1899
|
+
return { id: asString(row?.id) ?? slug, name: asString(row?.name) ?? slug, slug };
|
|
1900
|
+
}
|
|
1901
|
+
function parseWorkspaceList(raw) {
|
|
1902
|
+
const rows = Array.isArray(raw) ? raw : asRecord(raw)?.results;
|
|
1903
|
+
if (!Array.isArray(rows))
|
|
1904
|
+
return;
|
|
1905
|
+
return rows.map(parseWorkspace).filter((row) => !!row);
|
|
1906
|
+
}
|
|
1880
1907
|
function parseState(raw) {
|
|
1881
1908
|
const row = asRecord(raw);
|
|
1882
1909
|
const id = asString(row?.id);
|
|
@@ -2092,6 +2119,25 @@ function createPlaneCli(opts) {
|
|
|
2092
2119
|
const loaded = await loadCatalog(flags || defaultFlags);
|
|
2093
2120
|
return loaded.columns;
|
|
2094
2121
|
},
|
|
2122
|
+
async listWorkspaces() {
|
|
2123
|
+
for (const path of ["/users/me/workspaces/", "/workspaces/"]) {
|
|
2124
|
+
try {
|
|
2125
|
+
return parseWorkspaceList(await requestJson(path)) ?? [];
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
const status = err.status;
|
|
2128
|
+
if (status === 404 && path === "/users/me/workspaces/")
|
|
2129
|
+
continue;
|
|
2130
|
+
return [];
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return [];
|
|
2134
|
+
},
|
|
2135
|
+
async listProjectIdentifiers(workspace) {
|
|
2136
|
+
const slug = workspace.trim();
|
|
2137
|
+
if (!slug)
|
|
2138
|
+
return [];
|
|
2139
|
+
return (await paginate(`/workspaces/${slug}/projects/`)).map(parseProject).filter((row) => !!row).map((row) => row.identifier);
|
|
2140
|
+
},
|
|
2095
2141
|
async move(key, status) {
|
|
2096
2142
|
try {
|
|
2097
2143
|
const module = (await loadCatalog(lastFlags)).modules.get(key);
|
|
@@ -2234,6 +2280,123 @@ async function refreshFromJira(app, kind, opts = {}) {
|
|
|
2234
2280
|
await app.refresh();
|
|
2235
2281
|
}
|
|
2236
2282
|
|
|
2283
|
+
// src/factory-ledger.ts
|
|
2284
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
2285
|
+
import { homedir as homedir3 } from "node:os";
|
|
2286
|
+
import { join as join4 } from "node:path";
|
|
2287
|
+
var DEFAULT_LEDGER_PATH = "~/code/software-factory/asf/runtime/factory-coordinator/jobs.jsonl";
|
|
2288
|
+
function resolveLedgerPath(env = process.env) {
|
|
2289
|
+
const raw = env.FACTORY_LEDGER_PATH ?? DEFAULT_LEDGER_PATH;
|
|
2290
|
+
return raw.startsWith("~/") ? join4(homedir3(), raw.slice(2)) : raw;
|
|
2291
|
+
}
|
|
2292
|
+
function parseJobsJsonl(text) {
|
|
2293
|
+
const events = [];
|
|
2294
|
+
for (const line of text.split(`
|
|
2295
|
+
`)) {
|
|
2296
|
+
const trimmed = line.trim();
|
|
2297
|
+
if (!trimmed)
|
|
2298
|
+
continue;
|
|
2299
|
+
try {
|
|
2300
|
+
events.push(JSON.parse(trimmed));
|
|
2301
|
+
} catch {}
|
|
2302
|
+
}
|
|
2303
|
+
return events;
|
|
2304
|
+
}
|
|
2305
|
+
function lc(...parts) {
|
|
2306
|
+
return parts.filter(Boolean).join(" ").toLowerCase();
|
|
2307
|
+
}
|
|
2308
|
+
function deriveStageTag(events) {
|
|
2309
|
+
if (!events.length)
|
|
2310
|
+
return null;
|
|
2311
|
+
const last = events[events.length - 1];
|
|
2312
|
+
if (last.status === "done" || last.status === "cancelled")
|
|
2313
|
+
return null;
|
|
2314
|
+
const text = lc(last.detail, last.summary, last.target_agent, last.from_bot);
|
|
2315
|
+
const fromBot = lc(last.from_bot);
|
|
2316
|
+
const targetAgent = lc(last.target_agent);
|
|
2317
|
+
const detail = lc(last.detail);
|
|
2318
|
+
if (last.status === "blocked" || /needs.{0,4}judgment|green.{0,4}light|await.{0,4}ship|awaiting.{0,4}ship|needs.{0,4}input|waiting.{0,4}operator|checks.{0,4}pass|verify.{0,4}pass|human.{0,4}gate/.test(text)) {
|
|
2319
|
+
return "human-wait";
|
|
2320
|
+
}
|
|
2321
|
+
if (last.status === "queued" || last.status === "routed" || /grokbot.{0,4}coordinator|intake.{0,4}switchboard/.test(fromBot) || /routing|switchboard/.test(text)) {
|
|
2322
|
+
return "coordinator";
|
|
2323
|
+
}
|
|
2324
|
+
if (/planner|plan_|stage_1a/.test(targetAgent) || /planner|to.{0,2}spec|to.{0,2}tickets|grill.{0,4}with.{0,4}docs|stage_1/.test(text)) {
|
|
2325
|
+
return "planning";
|
|
2326
|
+
}
|
|
2327
|
+
if (/desk=/.test(detail) || /preflight|research.{0,4}gate|research.{0,4}dossier/.test(text)) {
|
|
2328
|
+
return "desk";
|
|
2329
|
+
}
|
|
2330
|
+
if (/maker|implement|referee|omp|stage_2/.test(targetAgent) || /\bmaker\b|implement|referee|stage_2|pr.{0,4}open|pull.{0,4}request/.test(text)) {
|
|
2331
|
+
return "maker";
|
|
2332
|
+
}
|
|
2333
|
+
return "coordinator";
|
|
2334
|
+
}
|
|
2335
|
+
function extractPrUrl(detail) {
|
|
2336
|
+
if (!detail)
|
|
2337
|
+
return;
|
|
2338
|
+
const m = /https:\/\/github\.com\/[^\s"']+\/pull\/\d+/.exec(detail);
|
|
2339
|
+
return m?.[0];
|
|
2340
|
+
}
|
|
2341
|
+
function groupByLatest(events) {
|
|
2342
|
+
const map = new Map;
|
|
2343
|
+
for (const ev of events) {
|
|
2344
|
+
if (!ev.id)
|
|
2345
|
+
continue;
|
|
2346
|
+
const list = map.get(ev.id);
|
|
2347
|
+
if (list) {
|
|
2348
|
+
list.push(ev);
|
|
2349
|
+
} else {
|
|
2350
|
+
map.set(ev.id, [ev]);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return map;
|
|
2354
|
+
}
|
|
2355
|
+
function jobFromEvents(id, events) {
|
|
2356
|
+
const last = events[events.length - 1];
|
|
2357
|
+
const pullRequestUrl = events.map((e) => e.pull_request_url ?? extractPrUrl(e.detail)).find(Boolean);
|
|
2358
|
+
return {
|
|
2359
|
+
id,
|
|
2360
|
+
summary: last.summary ?? "",
|
|
2361
|
+
status: last.status ?? "",
|
|
2362
|
+
stageTag: deriveStageTag(events),
|
|
2363
|
+
latestTs: last.ts,
|
|
2364
|
+
...pullRequestUrl ? { pullRequestUrl } : {},
|
|
2365
|
+
...last.plane_project ? { planeProject: last.plane_project } : {},
|
|
2366
|
+
...last.detail ? { detail: last.detail } : {},
|
|
2367
|
+
...last.session ? { session: last.session } : {},
|
|
2368
|
+
...last.from_bot ? { fromBot: last.from_bot } : {}
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function classifyJobs(events) {
|
|
2372
|
+
const grouped = groupByLatest(events);
|
|
2373
|
+
const jobs = [];
|
|
2374
|
+
for (const [id, evs] of grouped) {
|
|
2375
|
+
jobs.push(jobFromEvents(id, evs));
|
|
2376
|
+
}
|
|
2377
|
+
jobs.sort((a, b) => b.latestTs.localeCompare(a.latestTs));
|
|
2378
|
+
return jobs;
|
|
2379
|
+
}
|
|
2380
|
+
var _cache = null;
|
|
2381
|
+
function readLedger(env = process.env) {
|
|
2382
|
+
const path = resolveLedgerPath(env);
|
|
2383
|
+
if (!existsSync2(path)) {
|
|
2384
|
+
return { jobs: [], error: `Ledger not found: ${path}` };
|
|
2385
|
+
}
|
|
2386
|
+
try {
|
|
2387
|
+
const mtime = statSync(path).mtimeMs;
|
|
2388
|
+
if (_cache && _cache.path === path && _cache.mtime === mtime) {
|
|
2389
|
+
return { jobs: _cache.jobs };
|
|
2390
|
+
}
|
|
2391
|
+
const text = readFileSync3(path, "utf8");
|
|
2392
|
+
const jobs = classifyJobs(parseJobsJsonl(text));
|
|
2393
|
+
_cache = { path, mtime, jobs };
|
|
2394
|
+
return { jobs };
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
return { jobs: [], error: String(err) };
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2237
2400
|
// src/app-api.ts
|
|
2238
2401
|
function json(res, status, body) {
|
|
2239
2402
|
res.statusCode = status;
|
|
@@ -2259,11 +2422,46 @@ function reply(req, res, work) {
|
|
|
2259
2422
|
json(res, 500, { error: message });
|
|
2260
2423
|
});
|
|
2261
2424
|
}
|
|
2262
|
-
function
|
|
2425
|
+
async function boardEnvelope(body, kind, flags, env, app) {
|
|
2426
|
+
if (kind !== "plane")
|
|
2427
|
+
return { ...body, kind };
|
|
2428
|
+
const envelope = { ...body, kind, workspace: planeWorkspace(flags, env) };
|
|
2429
|
+
try {
|
|
2430
|
+
const workspaces = await app.listWorkspaces?.();
|
|
2431
|
+
if (workspaces && workspaces.length)
|
|
2432
|
+
return { ...envelope, workspaces };
|
|
2433
|
+
} catch {}
|
|
2434
|
+
return envelope;
|
|
2435
|
+
}
|
|
2436
|
+
function handleAppApi(req, res, app, opts = {}) {
|
|
2437
|
+
const kind = opts.kind ?? "store";
|
|
2438
|
+
const env = opts.env ?? process.env;
|
|
2263
2439
|
const url = pathOf(req);
|
|
2264
2440
|
const method = (req.method ?? "GET").toUpperCase();
|
|
2265
2441
|
if (url.pathname === "/api/board" && method === "GET") {
|
|
2266
|
-
|
|
2442
|
+
reply(req, res, async () => {
|
|
2443
|
+
json(res, 200, await boardEnvelope({ ...app.board(), flags: app.flags }, kind, app.flags, env, app));
|
|
2444
|
+
});
|
|
2445
|
+
return true;
|
|
2446
|
+
}
|
|
2447
|
+
if (url.pathname === "/api/projects" && method === "GET") {
|
|
2448
|
+
reply(req, res, async () => {
|
|
2449
|
+
if (kind !== "plane") {
|
|
2450
|
+
json(res, 404, { error: "not plane" });
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const workspace = (url.searchParams.get("workspace") ?? "").trim();
|
|
2454
|
+
if (!workspace) {
|
|
2455
|
+
json(res, 400, { error: "workspace required" });
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
try {
|
|
2459
|
+
json(res, 200, { projects: await app.listProjectIdentifiers?.(workspace) ?? [] });
|
|
2460
|
+
} catch (err) {
|
|
2461
|
+
const message = err instanceof Error ? err.message : "catalog failed";
|
|
2462
|
+
json(res, 409, { error: message });
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2267
2465
|
return true;
|
|
2268
2466
|
}
|
|
2269
2467
|
if (url.pathname === "/api/refresh" && method === "POST") {
|
|
@@ -2275,10 +2473,10 @@ function handleAppApi(req, res, app) {
|
|
|
2275
2473
|
json(res, 400, { error: "selected Refresh requires epicKeys" });
|
|
2276
2474
|
return;
|
|
2277
2475
|
}
|
|
2278
|
-
json(res, 200, await app.refresh(undefined, { scope: "selected", epicKeys }));
|
|
2476
|
+
json(res, 200, await boardEnvelope(await app.refresh(undefined, { scope: "selected", epicKeys }), kind, app.flags, env, app));
|
|
2279
2477
|
return;
|
|
2280
2478
|
}
|
|
2281
|
-
json(res, 200, await app.refresh(body.flags));
|
|
2479
|
+
json(res, 200, await boardEnvelope(await app.refresh(body.flags), kind, app.flags, env, app));
|
|
2282
2480
|
});
|
|
2283
2481
|
return true;
|
|
2284
2482
|
}
|
|
@@ -2343,6 +2541,26 @@ function handleAppApi(req, res, app) {
|
|
|
2343
2541
|
});
|
|
2344
2542
|
return true;
|
|
2345
2543
|
}
|
|
2544
|
+
if (url.pathname === "/api/factory/jobs" && method === "GET") {
|
|
2545
|
+
reply(req, res, async () => {
|
|
2546
|
+
const result = readLedger();
|
|
2547
|
+
json(res, result.error && result.jobs.length === 0 ? 503 : 200, result);
|
|
2548
|
+
});
|
|
2549
|
+
return true;
|
|
2550
|
+
}
|
|
2551
|
+
if (/^\/api\/factory\/jobs\/[^/]+$/.test(url.pathname) && method === "GET") {
|
|
2552
|
+
reply(req, res, async () => {
|
|
2553
|
+
const id = url.pathname.slice("/api/factory/jobs/".length);
|
|
2554
|
+
const result = readLedger();
|
|
2555
|
+
const job = result.jobs.find((j) => j.id === id);
|
|
2556
|
+
if (!job) {
|
|
2557
|
+
json(res, 404, { error: `Job not found: ${id}`, ledgerError: result.error });
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
json(res, 200, { job });
|
|
2561
|
+
});
|
|
2562
|
+
return true;
|
|
2563
|
+
}
|
|
2346
2564
|
return false;
|
|
2347
2565
|
}
|
|
2348
2566
|
|
|
@@ -2432,10 +2650,10 @@ function formatCard(card) {
|
|
|
2432
2650
|
}
|
|
2433
2651
|
|
|
2434
2652
|
// src/server/agent/config.ts
|
|
2435
|
-
import { existsSync as
|
|
2436
|
-
import { homedir as
|
|
2437
|
-
import { delimiter as delimiter2, dirname as dirname2, join as
|
|
2438
|
-
var DEFAULT_CONFIG_PATH =
|
|
2653
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2654
|
+
import { homedir as homedir4 } from "node:os";
|
|
2655
|
+
import { delimiter as delimiter2, dirname as dirname2, join as join5 } from "node:path";
|
|
2656
|
+
var DEFAULT_CONFIG_PATH = join5(homedir4(), ".config", "pipe-kan", "agent.json");
|
|
2439
2657
|
var FALLBACK_MODELS = {
|
|
2440
2658
|
cursor: [
|
|
2441
2659
|
{ id: "composer-2", name: "Composer 2" },
|
|
@@ -2477,21 +2695,21 @@ function commandOnPath(command, env = process.env) {
|
|
|
2477
2695
|
if (!command)
|
|
2478
2696
|
return false;
|
|
2479
2697
|
if (command.includes("/") || command.includes("\\"))
|
|
2480
|
-
return
|
|
2698
|
+
return existsSync3(command);
|
|
2481
2699
|
const pathVar = env.PATH ?? "";
|
|
2482
2700
|
for (const dir of pathVar.split(delimiter2)) {
|
|
2483
2701
|
if (!dir)
|
|
2484
2702
|
continue;
|
|
2485
|
-
if (
|
|
2703
|
+
if (existsSync3(join5(dir, command)))
|
|
2486
2704
|
return true;
|
|
2487
2705
|
}
|
|
2488
2706
|
return false;
|
|
2489
2707
|
}
|
|
2490
2708
|
function loadAgentConfig(path = agentConfigPath()) {
|
|
2491
|
-
if (!
|
|
2709
|
+
if (!existsSync3(path))
|
|
2492
2710
|
return structuredClone(DEFAULT_CONFIG);
|
|
2493
2711
|
try {
|
|
2494
|
-
const raw = JSON.parse(
|
|
2712
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
2495
2713
|
return mergeConfig(raw);
|
|
2496
2714
|
} catch {
|
|
2497
2715
|
return structuredClone(DEFAULT_CONFIG);
|
|
@@ -2527,7 +2745,7 @@ function mergeConfig(raw) {
|
|
|
2527
2745
|
}
|
|
2528
2746
|
function ensureAgentConfigDir(path = agentConfigPath()) {
|
|
2529
2747
|
const dir = dirname2(path);
|
|
2530
|
-
if (!
|
|
2748
|
+
if (!existsSync3(dir))
|
|
2531
2749
|
mkdirSync2(dir, { recursive: true });
|
|
2532
2750
|
}
|
|
2533
2751
|
|
|
@@ -2535,7 +2753,7 @@ function ensureAgentConfigDir(path = agentConfigPath()) {
|
|
|
2535
2753
|
import { spawn as spawn2 } from "node:child_process";
|
|
2536
2754
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
2537
2755
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
2538
|
-
import { join as
|
|
2756
|
+
import { join as join6 } from "node:path";
|
|
2539
2757
|
import { Readable } from "node:stream";
|
|
2540
2758
|
|
|
2541
2759
|
// node_modules/@agentclientprotocol/sdk/dist/schema/index.js
|
|
@@ -12261,7 +12479,7 @@ class AcpSession {
|
|
|
12261
12479
|
executeTool = null;
|
|
12262
12480
|
stderr = "";
|
|
12263
12481
|
selectedModelId = null;
|
|
12264
|
-
workspace = mkdtempSync(
|
|
12482
|
+
workspace = mkdtempSync(join6(tmpdir2(), "pipe-kan-agent-"));
|
|
12265
12483
|
constructor(config) {
|
|
12266
12484
|
this.config = config;
|
|
12267
12485
|
this.id = crypto.randomUUID();
|
|
@@ -12573,17 +12791,17 @@ function permissionOptionForDecision(options, decision) {
|
|
|
12573
12791
|
}
|
|
12574
12792
|
|
|
12575
12793
|
// src/server/agent/skills.ts
|
|
12576
|
-
import { existsSync as
|
|
12577
|
-
import { homedir as
|
|
12578
|
-
import { join as
|
|
12794
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync } from "node:fs";
|
|
12795
|
+
import { homedir as homedir5 } from "node:os";
|
|
12796
|
+
import { join as join7 } from "node:path";
|
|
12579
12797
|
import { fileURLToPath } from "node:url";
|
|
12580
12798
|
function createSkillRegistry(bundledDir) {
|
|
12581
|
-
const dir = bundledDir ??
|
|
12582
|
-
const userDir =
|
|
12799
|
+
const dir = bundledDir ?? join7(fileURLToPath(new URL(".", import.meta.url)), "..", "..", "..", ".agents", "skills");
|
|
12800
|
+
const userDir = join7(homedir5(), ".pi", "agent", "skills");
|
|
12583
12801
|
return {
|
|
12584
12802
|
list() {
|
|
12585
12803
|
const bundled = listSkills(dir);
|
|
12586
|
-
const user =
|
|
12804
|
+
const user = existsSync4(userDir) ? listSkills(userDir) : [];
|
|
12587
12805
|
const map = new Map;
|
|
12588
12806
|
for (const skill of bundled)
|
|
12589
12807
|
map.set(skill.id, skill);
|
|
@@ -12593,10 +12811,10 @@ function createSkillRegistry(bundledDir) {
|
|
|
12593
12811
|
},
|
|
12594
12812
|
load(id) {
|
|
12595
12813
|
const userPath = skillPath(userDir, id);
|
|
12596
|
-
if (
|
|
12814
|
+
if (existsSync4(userPath))
|
|
12597
12815
|
return readSkill(userPath, id);
|
|
12598
12816
|
const bundledPath = skillPath(dir, id);
|
|
12599
|
-
if (
|
|
12817
|
+
if (existsSync4(bundledPath))
|
|
12600
12818
|
return readSkill(bundledPath, id);
|
|
12601
12819
|
return;
|
|
12602
12820
|
}
|
|
@@ -12613,16 +12831,16 @@ function skillContextBlock(skill) {
|
|
|
12613
12831
|
};
|
|
12614
12832
|
}
|
|
12615
12833
|
function listSkills(dir) {
|
|
12616
|
-
if (!
|
|
12834
|
+
if (!existsSync4(dir))
|
|
12617
12835
|
return [];
|
|
12618
12836
|
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readSkill(skillPath(dir, entry.name), entry.name)).filter((skill) => skill !== undefined);
|
|
12619
12837
|
}
|
|
12620
12838
|
function skillPath(dir, id) {
|
|
12621
|
-
return
|
|
12839
|
+
return join7(dir, id, "SKILL.md");
|
|
12622
12840
|
}
|
|
12623
12841
|
function readSkill(path, id) {
|
|
12624
12842
|
try {
|
|
12625
|
-
const text =
|
|
12843
|
+
const text = readFileSync5(path, "utf8");
|
|
12626
12844
|
const front = parseFrontMatter(text);
|
|
12627
12845
|
return {
|
|
12628
12846
|
id,
|
|
@@ -12649,8 +12867,8 @@ function parseFrontMatter(text) {
|
|
|
12649
12867
|
}
|
|
12650
12868
|
|
|
12651
12869
|
// src/server/agent/tools.ts
|
|
12652
|
-
import { readFileSync as
|
|
12653
|
-
import { join as
|
|
12870
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12871
|
+
import { join as join8 } from "node:path";
|
|
12654
12872
|
var TOOLS = [
|
|
12655
12873
|
{
|
|
12656
12874
|
name: "board_state",
|
|
@@ -12726,6 +12944,35 @@ var TOOLS = [
|
|
|
12726
12944
|
labels: { type: "string", description: "Comma-separated replacement labels" }
|
|
12727
12945
|
},
|
|
12728
12946
|
mutates: true
|
|
12947
|
+
},
|
|
12948
|
+
{
|
|
12949
|
+
name: "factory_job_status",
|
|
12950
|
+
description: "Read the latest status and factory stage tag for one or more software-factory jobs from the local ledger. Pass a job id (gbj-YYYYMMDD-NNN) for a single job, or omit id to get the 20 most-recent active jobs.",
|
|
12951
|
+
parameters: {
|
|
12952
|
+
id: { type: "string", description: "Optional job id, e.g. gbj-20260926-011" }
|
|
12953
|
+
},
|
|
12954
|
+
mutates: false
|
|
12955
|
+
},
|
|
12956
|
+
{
|
|
12957
|
+
name: "factory_start_job",
|
|
12958
|
+
description: "Request the Grokbot Coordinator to start a new factory job. Writes a start-request to docs/factory/outbox/ for the coordinator to pick up. Requires user approval. Does NOT append to jobs.jsonl directly.",
|
|
12959
|
+
parameters: {
|
|
12960
|
+
summary: { type: "string", description: "One-line job summary" },
|
|
12961
|
+
repo: { type: "string", description: "Target repo path, e.g. ~/code/pipe-kan" },
|
|
12962
|
+
session: { type: "string", description: "Herdr session name, e.g. jira-kan" },
|
|
12963
|
+
notes: { type: "string", description: "Optional extra context for the coordinator" }
|
|
12964
|
+
},
|
|
12965
|
+
mutates: true
|
|
12966
|
+
},
|
|
12967
|
+
{
|
|
12968
|
+
name: "factory_move_job",
|
|
12969
|
+
description: "Request the Grokbot Coordinator to move a factory job to a new status or stage. Writes a move-request to docs/factory/outbox/ for the coordinator to pick up. Requires user approval. Does NOT append to jobs.jsonl directly.",
|
|
12970
|
+
parameters: {
|
|
12971
|
+
id: { type: "string", description: "Job id to move, e.g. gbj-20260926-011" },
|
|
12972
|
+
status: { type: "string", description: "Target status: queued|routed|in_progress|blocked|done|cancelled" },
|
|
12973
|
+
detail: { type: "string", description: "Optional detail message for the ledger event" }
|
|
12974
|
+
},
|
|
12975
|
+
mutates: true
|
|
12729
12976
|
}
|
|
12730
12977
|
];
|
|
12731
12978
|
var skills;
|
|
@@ -12770,7 +13017,7 @@ var EXECUTORS = {
|
|
|
12770
13017
|
return { ok: false, error: "Path traversal not allowed" };
|
|
12771
13018
|
const repoRoot = process.cwd();
|
|
12772
13019
|
try {
|
|
12773
|
-
const text =
|
|
13020
|
+
const text = readFileSync6(join8(repoRoot, relPath), "utf8");
|
|
12774
13021
|
return { ok: true, value: { path: relPath, text } };
|
|
12775
13022
|
} catch (err) {
|
|
12776
13023
|
return { ok: false, error: String(err) };
|
|
@@ -12841,6 +13088,93 @@ var EXECUTORS = {
|
|
|
12841
13088
|
if (result.error)
|
|
12842
13089
|
return { ok: false, error: result.error };
|
|
12843
13090
|
return { ok: true, value: { key, __ui_action: "refresh_board" } };
|
|
13091
|
+
},
|
|
13092
|
+
factory_job_status(args) {
|
|
13093
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
13094
|
+
const ledger = readLedger();
|
|
13095
|
+
if (id) {
|
|
13096
|
+
const job = ledger.jobs.find((j) => j.id === id);
|
|
13097
|
+
if (!job) {
|
|
13098
|
+
return { ok: false, error: `Job not found: ${id}${ledger.error ? ` (${ledger.error})` : ""}` };
|
|
13099
|
+
}
|
|
13100
|
+
return { ok: true, value: { job, ledgerError: ledger.error } };
|
|
13101
|
+
}
|
|
13102
|
+
const active = ledger.jobs.filter((j) => j.stageTag !== null).slice(0, 20);
|
|
13103
|
+
return {
|
|
13104
|
+
ok: true,
|
|
13105
|
+
value: { jobs: active, total: ledger.jobs.length, ledgerError: ledger.error }
|
|
13106
|
+
};
|
|
13107
|
+
},
|
|
13108
|
+
factory_start_job(args) {
|
|
13109
|
+
const summary = String(args.summary ?? "").trim();
|
|
13110
|
+
if (!summary)
|
|
13111
|
+
return { ok: false, error: "Missing summary" };
|
|
13112
|
+
const repo = String(args.repo ?? "").trim();
|
|
13113
|
+
const session = String(args.session ?? "").trim();
|
|
13114
|
+
const notes = typeof args.notes === "string" ? args.notes.trim() : undefined;
|
|
13115
|
+
const ts = new Date().toISOString();
|
|
13116
|
+
const requestId = crypto.randomUUID();
|
|
13117
|
+
const outboxDir = join8(process.cwd(), "docs/factory/outbox");
|
|
13118
|
+
mkdirSync3(outboxDir, { recursive: true });
|
|
13119
|
+
const filename = `${ts.replace(/[:.]/g, "-")}-${requestId}-start.json`;
|
|
13120
|
+
const payload = {
|
|
13121
|
+
action: "start_job",
|
|
13122
|
+
requested_by: "pipe-kan-acp",
|
|
13123
|
+
ts,
|
|
13124
|
+
requestId,
|
|
13125
|
+
summary,
|
|
13126
|
+
...repo ? { repo } : {},
|
|
13127
|
+
...session ? { session } : {},
|
|
13128
|
+
...notes ? { notes } : {}
|
|
13129
|
+
};
|
|
13130
|
+
writeFileSync3(join8(outboxDir, filename), JSON.stringify(payload, null, 2) + `
|
|
13131
|
+
`, "utf8");
|
|
13132
|
+
return {
|
|
13133
|
+
ok: true,
|
|
13134
|
+
value: {
|
|
13135
|
+
message: `Start request written to docs/factory/outbox/${filename}. The Grokbot Coordinator will pick this up and append the real ledger event.`,
|
|
13136
|
+
outboxFile: filename
|
|
13137
|
+
}
|
|
13138
|
+
};
|
|
13139
|
+
},
|
|
13140
|
+
factory_move_job(args) {
|
|
13141
|
+
const id = String(args.id ?? "").trim();
|
|
13142
|
+
const status = String(args.status ?? "").trim();
|
|
13143
|
+
if (!id)
|
|
13144
|
+
return { ok: false, error: "Missing job id" };
|
|
13145
|
+
if (!status)
|
|
13146
|
+
return { ok: false, error: "Missing target status" };
|
|
13147
|
+
const validStatuses = ["queued", "routed", "in_progress", "blocked", "done", "cancelled"];
|
|
13148
|
+
if (!validStatuses.includes(status)) {
|
|
13149
|
+
return {
|
|
13150
|
+
ok: false,
|
|
13151
|
+
error: `Invalid status '${status}'. Must be one of: ${validStatuses.join(", ")}`
|
|
13152
|
+
};
|
|
13153
|
+
}
|
|
13154
|
+
const detail = typeof args.detail === "string" ? args.detail.trim() : undefined;
|
|
13155
|
+
const ts = new Date().toISOString();
|
|
13156
|
+
const requestId = crypto.randomUUID();
|
|
13157
|
+
const outboxDir = join8(process.cwd(), "docs/factory/outbox");
|
|
13158
|
+
mkdirSync3(outboxDir, { recursive: true });
|
|
13159
|
+
const filename = `${ts.replace(/[:.]/g, "-")}-${requestId}-move.json`;
|
|
13160
|
+
const payload = {
|
|
13161
|
+
action: "move_job",
|
|
13162
|
+
requested_by: "pipe-kan-acp",
|
|
13163
|
+
ts,
|
|
13164
|
+
requestId,
|
|
13165
|
+
id,
|
|
13166
|
+
status,
|
|
13167
|
+
...detail ? { detail } : {}
|
|
13168
|
+
};
|
|
13169
|
+
writeFileSync3(join8(outboxDir, filename), JSON.stringify(payload, null, 2) + `
|
|
13170
|
+
`, "utf8");
|
|
13171
|
+
return {
|
|
13172
|
+
ok: true,
|
|
13173
|
+
value: {
|
|
13174
|
+
message: `Move request for ${id} → ${status} written to docs/factory/outbox/${filename}. The Grokbot Coordinator will pick this up and append the real ledger event.`,
|
|
13175
|
+
outboxFile: filename
|
|
13176
|
+
}
|
|
13177
|
+
};
|
|
12844
13178
|
}
|
|
12845
13179
|
};
|
|
12846
13180
|
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.
|
|
@@ -13307,20 +13641,21 @@ function handleFakeJira(req, res, store) {
|
|
|
13307
13641
|
|
|
13308
13642
|
// src/http.ts
|
|
13309
13643
|
function handleRequest(req, res, ctx) {
|
|
13310
|
-
if (handleAppApi(req, res, ctx.app) || handleAgentApi(req, res, ctx.app))
|
|
13644
|
+
if (handleAppApi(req, res, ctx.app, { kind: ctx.kind }) || handleAgentApi(req, res, ctx.app)) {
|
|
13311
13645
|
return true;
|
|
13646
|
+
}
|
|
13312
13647
|
if (ctx.kind === "plane")
|
|
13313
13648
|
return false;
|
|
13314
13649
|
return handleFakeJira(req, res, ctx.store);
|
|
13315
13650
|
}
|
|
13316
13651
|
|
|
13317
13652
|
// src/jira-config.ts
|
|
13318
|
-
import { mkdirSync as
|
|
13319
|
-
import { join as
|
|
13653
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
13654
|
+
import { join as join9 } from "node:path";
|
|
13320
13655
|
function writeJiraConfig(dir, server) {
|
|
13321
|
-
|
|
13322
|
-
const path =
|
|
13323
|
-
|
|
13656
|
+
mkdirSync4(dir, { recursive: true });
|
|
13657
|
+
const path = join9(dir, "jira.config.yml");
|
|
13658
|
+
writeFileSync4(path, [
|
|
13324
13659
|
"installation: Cloud",
|
|
13325
13660
|
`server: ${server}`,
|
|
13326
13661
|
`login: ${ME.emailAddress}`,
|
|
@@ -13374,8 +13709,8 @@ function stdinStat() {
|
|
|
13374
13709
|
}
|
|
13375
13710
|
|
|
13376
13711
|
// src/ui.ts
|
|
13377
|
-
import { createReadStream, existsSync as
|
|
13378
|
-
import { dirname as dirname3, extname, join as
|
|
13712
|
+
import { createReadStream, existsSync as existsSync5, statSync as statSync2 } from "node:fs";
|
|
13713
|
+
import { dirname as dirname3, extname, join as join10, resolve as resolve2, sep } from "node:path";
|
|
13379
13714
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13380
13715
|
var types = {
|
|
13381
13716
|
".css": "text/css; charset=utf-8",
|
|
@@ -13392,7 +13727,7 @@ function packageRoot(from = import.meta.url) {
|
|
|
13392
13727
|
return resolve2(dirname3(fileURLToPath2(from)), "..");
|
|
13393
13728
|
}
|
|
13394
13729
|
function uiDir(root) {
|
|
13395
|
-
return
|
|
13730
|
+
return join10(root, "dist", "ui");
|
|
13396
13731
|
}
|
|
13397
13732
|
function inside(root, file) {
|
|
13398
13733
|
const base = resolve2(root);
|
|
@@ -13401,12 +13736,12 @@ function inside(root, file) {
|
|
|
13401
13736
|
}
|
|
13402
13737
|
function sendUi(root, req, res) {
|
|
13403
13738
|
const ui = uiDir(root);
|
|
13404
|
-
const index =
|
|
13405
|
-
if (!
|
|
13739
|
+
const index = join10(ui, "index.html");
|
|
13740
|
+
if (!existsSync5(index))
|
|
13406
13741
|
return false;
|
|
13407
13742
|
const path = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
|
|
13408
13743
|
const wanted = resolve2(ui, `.${decodeURIComponent(path)}`);
|
|
13409
|
-
const file = inside(ui, wanted) &&
|
|
13744
|
+
const file = inside(ui, wanted) && existsSync5(wanted) && statSync2(wanted).isFile() ? wanted : index;
|
|
13410
13745
|
res.setHeader("content-type", types[extname(file)] ?? "application/octet-stream");
|
|
13411
13746
|
createReadStream(file).pipe(res);
|
|
13412
13747
|
return true;
|
|
@@ -13422,7 +13757,7 @@ function announce(line) {
|
|
|
13422
13757
|
}
|
|
13423
13758
|
async function runServer(opts) {
|
|
13424
13759
|
const piped = await readPipe();
|
|
13425
|
-
const raw = piped ?? JSON.parse(
|
|
13760
|
+
const raw = piped ?? JSON.parse(readFileSync7(join11(opts.root, "fixtures/issues.json"), "utf8"));
|
|
13426
13761
|
const flags = argvToFlags(process.argv);
|
|
13427
13762
|
const { app, store, kind } = await createBoardApp({
|
|
13428
13763
|
raw,
|
|
@@ -13459,7 +13794,7 @@ async function runServer(opts) {
|
|
|
13459
13794
|
announce(`cli plane ${planeHost()} ${planeWorkspace(flags)}`);
|
|
13460
13795
|
announce(`Plane host default ${DEFAULT_PLANE_HOST}`);
|
|
13461
13796
|
} else {
|
|
13462
|
-
const fakeConfig = writeJiraConfig(
|
|
13797
|
+
const fakeConfig = writeJiraConfig(join11(tmpdir3(), "pipe-kan"), origin);
|
|
13463
13798
|
announce(`cli ${kind === "jira" ? resolveJiraBin() : "store"}`);
|
|
13464
13799
|
announce(`Fake Jira ${origin}/rest/api/2/search`);
|
|
13465
13800
|
announce(`Fake Jira config ${fakeConfig}`);
|