@appchy/jarvis 0.1.49 → 0.1.50
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/bin.js +345 -132
- package/dist/bin.js.map +1 -1
- package/dist/ui/app.js +102 -0
- package/dist/ui/index.html +22 -0
- package/harness/presets/appchy/PRESET.md +4 -2
- package/package.json +11 -2
package/dist/bin.js
CHANGED
|
@@ -669,8 +669,8 @@ function rpcRouter() {
|
|
|
669
669
|
return;
|
|
670
670
|
}
|
|
671
671
|
if (Array.isArray(parsed)) {
|
|
672
|
-
for (const
|
|
673
|
-
await this.dispatch(ctx,
|
|
672
|
+
for (const item2 of parsed) {
|
|
673
|
+
await this.dispatch(ctx, item2, send);
|
|
674
674
|
}
|
|
675
675
|
return;
|
|
676
676
|
}
|
|
@@ -748,7 +748,7 @@ function startIpcServer(socketPath, userId) {
|
|
|
748
748
|
};
|
|
749
749
|
}
|
|
750
750
|
async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
|
|
751
|
-
return new Promise((
|
|
751
|
+
return new Promise((resolve10, reject) => {
|
|
752
752
|
const conn = net.connect(socketPath);
|
|
753
753
|
let buf = "";
|
|
754
754
|
let settled = false;
|
|
@@ -780,7 +780,7 @@ async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
|
|
|
780
780
|
if (parsed.error) {
|
|
781
781
|
finish(() => reject(new Error(parsed.error?.message ?? "ipc error")));
|
|
782
782
|
} else {
|
|
783
|
-
finish(() =>
|
|
783
|
+
finish(() => resolve10(parsed.result));
|
|
784
784
|
}
|
|
785
785
|
} catch (err) {
|
|
786
786
|
finish(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -1106,9 +1106,21 @@ function writeRootsDirect(roots) {
|
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
1108
|
// src/exchange.ts
|
|
1109
|
-
import { execSync as execSync2 } from "child_process";
|
|
1110
1109
|
import os3 from "os";
|
|
1111
1110
|
import { createInterface } from "readline";
|
|
1111
|
+
|
|
1112
|
+
// src/browser.ts
|
|
1113
|
+
import { execSync as execSync2 } from "child_process";
|
|
1114
|
+
function openBrowser(req) {
|
|
1115
|
+
try {
|
|
1116
|
+
if (process.platform === "darwin") execSync2(`open "${req.url}"`);
|
|
1117
|
+
else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
|
|
1118
|
+
else execSync2(`xdg-open "${req.url}"`);
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/exchange.ts
|
|
1112
1124
|
var POLL_INTERVAL_MS2 = 2e3;
|
|
1113
1125
|
var POLL_TIMEOUT_MS = 10 * 6e4;
|
|
1114
1126
|
async function exchangeCode(req) {
|
|
@@ -1203,21 +1215,13 @@ function humanizeAgo(iso) {
|
|
|
1203
1215
|
}
|
|
1204
1216
|
function readlineQuestion(req) {
|
|
1205
1217
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1206
|
-
return new Promise((
|
|
1218
|
+
return new Promise((resolve10) => {
|
|
1207
1219
|
rl.question(req.prompt, (answer) => {
|
|
1208
1220
|
rl.close();
|
|
1209
|
-
|
|
1221
|
+
resolve10(answer);
|
|
1210
1222
|
});
|
|
1211
1223
|
});
|
|
1212
1224
|
}
|
|
1213
|
-
function openBrowser(req) {
|
|
1214
|
-
try {
|
|
1215
|
-
if (process.platform === "darwin") execSync2(`open "${req.url}"`);
|
|
1216
|
-
else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
|
|
1217
|
-
else execSync2(`xdg-open "${req.url}"`);
|
|
1218
|
-
} catch {
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
1225
|
function sleep(ms) {
|
|
1222
1226
|
return new Promise((r) => setTimeout(r, ms));
|
|
1223
1227
|
}
|
|
@@ -2286,10 +2290,10 @@ function rootsEqual(a, b) {
|
|
|
2286
2290
|
}
|
|
2287
2291
|
async function readlineQuestion2(question) {
|
|
2288
2292
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2289
|
-
return new Promise((
|
|
2293
|
+
return new Promise((resolve10) => {
|
|
2290
2294
|
rl.question(question, (input) => {
|
|
2291
2295
|
rl.close();
|
|
2292
|
-
|
|
2296
|
+
resolve10(input);
|
|
2293
2297
|
});
|
|
2294
2298
|
});
|
|
2295
2299
|
}
|
|
@@ -2366,8 +2370,8 @@ function defaultPrompts() {
|
|
|
2366
2370
|
}
|
|
2367
2371
|
async function promptList(question, fallback) {
|
|
2368
2372
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2369
|
-
const answer = await new Promise((
|
|
2370
|
-
rl.question(question, (input) =>
|
|
2373
|
+
const answer = await new Promise((resolve10) => {
|
|
2374
|
+
rl.question(question, (input) => resolve10(input.trim()));
|
|
2371
2375
|
});
|
|
2372
2376
|
rl.close();
|
|
2373
2377
|
const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -2521,8 +2525,8 @@ function register5(program) {
|
|
|
2521
2525
|
}
|
|
2522
2526
|
async function confirm(question) {
|
|
2523
2527
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
2524
|
-
const answer = await new Promise((
|
|
2525
|
-
rl.question(question, (input) =>
|
|
2528
|
+
const answer = await new Promise((resolve10) => {
|
|
2529
|
+
rl.question(question, (input) => resolve10(input.trim()));
|
|
2526
2530
|
});
|
|
2527
2531
|
rl.close();
|
|
2528
2532
|
return /^y(es)?$/i.test(answer);
|
|
@@ -2768,7 +2772,7 @@ async function idOf(term) {
|
|
|
2768
2772
|
} catch {
|
|
2769
2773
|
}
|
|
2770
2774
|
}
|
|
2771
|
-
await new Promise((
|
|
2775
|
+
await new Promise((resolve10) => setTimeout(resolve10, 250));
|
|
2772
2776
|
}
|
|
2773
2777
|
throw new Error("Claude Code started but never registered a session id");
|
|
2774
2778
|
}
|
|
@@ -2837,13 +2841,13 @@ async function live(req) {
|
|
|
2837
2841
|
else options.signal.addEventListener("abort", () => term.kill(), { once: true });
|
|
2838
2842
|
}
|
|
2839
2843
|
rest();
|
|
2840
|
-
await new Promise((
|
|
2844
|
+
await new Promise((resolve10) => {
|
|
2841
2845
|
term.onExit(({ exitCode, signal }) => {
|
|
2842
2846
|
if (idle) clearTimeout(idle);
|
|
2843
2847
|
if (closedForIdle) report2("stopped", "closed after sitting idle");
|
|
2844
2848
|
else if (exitCode === 0) report2("done");
|
|
2845
2849
|
else report2("failed", endedAs(exitCode, signal));
|
|
2846
|
-
|
|
2850
|
+
resolve10();
|
|
2847
2851
|
});
|
|
2848
2852
|
});
|
|
2849
2853
|
}
|
|
@@ -4189,6 +4193,19 @@ function toHolders(commits) {
|
|
|
4189
4193
|
}
|
|
4190
4194
|
return distinct;
|
|
4191
4195
|
}
|
|
4196
|
+
function toHoldersByItem(commits) {
|
|
4197
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
4198
|
+
for (const commit of commits) {
|
|
4199
|
+
for (const id of readTrailers(commit.message).items) {
|
|
4200
|
+
const forItem = byItem.get(id);
|
|
4201
|
+
if (forItem) forItem.push(commit);
|
|
4202
|
+
else byItem.set(id, [commit]);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
const holders = /* @__PURE__ */ new Map();
|
|
4206
|
+
for (const [id, forItem] of byItem) holders.set(id, toHolders(forItem));
|
|
4207
|
+
return holders;
|
|
4208
|
+
}
|
|
4192
4209
|
function warning(holders, mine) {
|
|
4193
4210
|
const others = holders.filter((holder) => holder.session !== mine);
|
|
4194
4211
|
if (others.length === 0) return "";
|
|
@@ -4207,6 +4224,7 @@ function isQuiet(commit) {
|
|
|
4207
4224
|
}
|
|
4208
4225
|
function readTrailers(message) {
|
|
4209
4226
|
const events = [];
|
|
4227
|
+
const items = [];
|
|
4210
4228
|
let session = "";
|
|
4211
4229
|
let machine = "";
|
|
4212
4230
|
for (const raw of message.split("\n")) {
|
|
@@ -4214,10 +4232,11 @@ function readTrailers(message) {
|
|
|
4214
4232
|
const m = /^(Work-[A-Za-z]+):\s*(.*)$/.exec(line);
|
|
4215
4233
|
if (!m) continue;
|
|
4216
4234
|
if (m[1] === "Work-Event" && m[2]) events.push(m[2]);
|
|
4235
|
+
if (m[1] === "Work-Item" && m[2]) items.push(m[2]);
|
|
4217
4236
|
if (m[1] === "Work-Session") session = m[2] ?? "";
|
|
4218
4237
|
if (m[1] === "Work-Machine") machine = m[2] ?? "";
|
|
4219
4238
|
}
|
|
4220
|
-
return { session, machine, events };
|
|
4239
|
+
return { session, machine, events, items };
|
|
4221
4240
|
}
|
|
4222
4241
|
function describeAge(when) {
|
|
4223
4242
|
const at = Date.parse(when);
|
|
@@ -4232,6 +4251,8 @@ function describeAge(when) {
|
|
|
4232
4251
|
// ../../packages/board/src/board.git.ts
|
|
4233
4252
|
var run3 = promisify4(execFile4);
|
|
4234
4253
|
var FORMAT = "%x1e%H%x1f%aI%x1f%an%x1f%B";
|
|
4254
|
+
var SOURCED = "%x1e%H%x1f%S%x1f%aI%x1f%an%x1f%B";
|
|
4255
|
+
var TRAILER = "^Work-Item: ";
|
|
4235
4256
|
var SEP = "";
|
|
4236
4257
|
var REC = "";
|
|
4237
4258
|
async function boardCommits(root, id) {
|
|
@@ -4279,6 +4300,48 @@ async function git2(root, argv) {
|
|
|
4279
4300
|
function escapeGrep(value) {
|
|
4280
4301
|
return value.replace(/[^A-Za-z0-9_-]/g, (c) => `\\${c}`);
|
|
4281
4302
|
}
|
|
4303
|
+
async function boardHistory(root) {
|
|
4304
|
+
const [out, tips] = await Promise.all([
|
|
4305
|
+
git2(root, ["log", "--all", "--source", "--reverse", `--format=${SOURCED}`, "-E", `--grep=${TRAILER}`]),
|
|
4306
|
+
branchTips(root)
|
|
4307
|
+
]);
|
|
4308
|
+
const commits = [];
|
|
4309
|
+
for (const record of out.split(REC)) {
|
|
4310
|
+
if (!record.trim()) continue;
|
|
4311
|
+
const [sha, ref, when, author, message] = record.split(SEP);
|
|
4312
|
+
if (!message || !sha) continue;
|
|
4313
|
+
const branch = shorten(ref ?? "");
|
|
4314
|
+
commits.push({
|
|
4315
|
+
sha,
|
|
4316
|
+
when: when ?? "",
|
|
4317
|
+
by: author ?? "",
|
|
4318
|
+
message,
|
|
4319
|
+
branch,
|
|
4320
|
+
branchLastCommitAt: tips.get(branch) ?? ""
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
return commits;
|
|
4324
|
+
}
|
|
4325
|
+
async function branchTips(root) {
|
|
4326
|
+
const out = await git2(root, [
|
|
4327
|
+
"for-each-ref",
|
|
4328
|
+
`--format=%(refname:short)${SEP}%(committerdate:iso-strict)`,
|
|
4329
|
+
"refs/heads",
|
|
4330
|
+
"refs/remotes"
|
|
4331
|
+
]);
|
|
4332
|
+
const tips = /* @__PURE__ */ new Map();
|
|
4333
|
+
for (const line of out.split("\n")) {
|
|
4334
|
+
const [name, when] = line.split(SEP);
|
|
4335
|
+
if (name && when) tips.set(name, when);
|
|
4336
|
+
}
|
|
4337
|
+
return tips;
|
|
4338
|
+
}
|
|
4339
|
+
function shorten(ref) {
|
|
4340
|
+
return ref.replace(/^refs\/(heads|remotes)\//, "");
|
|
4341
|
+
}
|
|
4342
|
+
async function listHoldersByItem(root) {
|
|
4343
|
+
return toHoldersByItem(await boardHistory(root));
|
|
4344
|
+
}
|
|
4282
4345
|
async function listHolders(root, id) {
|
|
4283
4346
|
return toHolders(await boardCommits(root, id));
|
|
4284
4347
|
}
|
|
@@ -4291,11 +4354,11 @@ import { promisify as promisify5 } from "util";
|
|
|
4291
4354
|
|
|
4292
4355
|
// ../../packages/errors/src/errors.ts
|
|
4293
4356
|
var ServiceError = class extends Error {
|
|
4294
|
-
constructor(message, code, statusCode = 500,
|
|
4357
|
+
constructor(message, code, statusCode = 500, context2) {
|
|
4295
4358
|
super(message);
|
|
4296
4359
|
this.code = code;
|
|
4297
4360
|
this.statusCode = statusCode;
|
|
4298
|
-
this.context =
|
|
4361
|
+
this.context = context2;
|
|
4299
4362
|
this.name = "ServiceError";
|
|
4300
4363
|
Error.captureStackTrace?.(this, this.constructor);
|
|
4301
4364
|
}
|
|
@@ -4320,8 +4383,8 @@ var NotFoundError = class extends ServiceError {
|
|
|
4320
4383
|
}
|
|
4321
4384
|
};
|
|
4322
4385
|
var ConflictError = class extends ServiceError {
|
|
4323
|
-
constructor(message,
|
|
4324
|
-
super(message, "CONFLICT", 409,
|
|
4386
|
+
constructor(message, context2) {
|
|
4387
|
+
super(message, "CONFLICT", 409, context2);
|
|
4325
4388
|
this.name = "ConflictError";
|
|
4326
4389
|
}
|
|
4327
4390
|
};
|
|
@@ -4351,7 +4414,7 @@ function toWorkItem(req) {
|
|
|
4351
4414
|
code: asList(front.code),
|
|
4352
4415
|
covers: asList(front.covers),
|
|
4353
4416
|
dependsOn: asList(front.depends_on),
|
|
4354
|
-
epic: backlog ? parts[1] ?? "" : parts
|
|
4417
|
+
epic: backlog ? parts[1] ?? "" : parts.at(-4) ?? "",
|
|
4355
4418
|
version: backlog ? "backlog" : parts[1] ?? "",
|
|
4356
4419
|
path: req.path,
|
|
4357
4420
|
holders: [],
|
|
@@ -4359,7 +4422,7 @@ function toWorkItem(req) {
|
|
|
4359
4422
|
};
|
|
4360
4423
|
}
|
|
4361
4424
|
function toPage(items, req) {
|
|
4362
|
-
const matched = items.filter((
|
|
4425
|
+
const matched = items.filter((item2) => req.status ? item2.status === req.status : true).filter((item2) => req.owner ? item2.owner === req.owner : true).filter((item2) => req.epic ? item2.epic === req.epic : true).filter((item2) => req.version ? item2.version === req.version : true).sort(byPriorityThenName);
|
|
4363
4426
|
const offset = req.offset ?? 0;
|
|
4364
4427
|
const limit2 = Math.min(req.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
|
|
4365
4428
|
const page = matched.slice(offset, offset + limit2);
|
|
@@ -4447,12 +4510,19 @@ async function listWorkItems(_ctx, req, options) {
|
|
|
4447
4510
|
}
|
|
4448
4511
|
async function findWorkItem(_ctx, req, options) {
|
|
4449
4512
|
const all = await scanTree(options.root);
|
|
4450
|
-
return all.find((
|
|
4513
|
+
return all.find((item2) => item2.id === req.id) ?? null;
|
|
4451
4514
|
}
|
|
4452
4515
|
async function getWorkItem(ctx, req, options) {
|
|
4453
|
-
const
|
|
4454
|
-
if (!
|
|
4455
|
-
return
|
|
4516
|
+
const item2 = await findWorkItem(ctx, req, options);
|
|
4517
|
+
if (!item2) throw new NotFoundError("work item", req.id);
|
|
4518
|
+
return item2;
|
|
4519
|
+
}
|
|
4520
|
+
async function getBrief(_ctx, req, options) {
|
|
4521
|
+
try {
|
|
4522
|
+
return await readFile(join3(options.root, "work", req.path), "utf-8");
|
|
4523
|
+
} catch {
|
|
4524
|
+
return null;
|
|
4525
|
+
}
|
|
4456
4526
|
}
|
|
4457
4527
|
async function createWorkItem(ctx, req, options) {
|
|
4458
4528
|
await harness(
|
|
@@ -4577,8 +4647,8 @@ async function scanTree(root) {
|
|
|
4577
4647
|
const text = await readFile(file, "utf-8");
|
|
4578
4648
|
const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
|
|
4579
4649
|
const path22 = relative(work, file).split(sep).join("/");
|
|
4580
|
-
const
|
|
4581
|
-
if (
|
|
4650
|
+
const item2 = toWorkItem({ path: path22, text, changedAt });
|
|
4651
|
+
if (item2) items.push(item2);
|
|
4582
4652
|
}
|
|
4583
4653
|
}
|
|
4584
4654
|
return items;
|
|
@@ -4606,14 +4676,10 @@ function notices(_ctx, _id) {
|
|
|
4606
4676
|
}
|
|
4607
4677
|
async function listWorkItems2(ctx, req, options) {
|
|
4608
4678
|
const page = await listWorkItems(ctx, req, options);
|
|
4609
|
-
const
|
|
4610
|
-
|
|
4611
|
-
...item,
|
|
4612
|
-
holders: await listHolders(options.root, item.id)
|
|
4613
|
-
}))
|
|
4614
|
-
);
|
|
4679
|
+
const holders = await listHoldersByItem(options.root);
|
|
4680
|
+
const items = page.items.map((item2) => ({ ...item2, holders: holders.get(item2.id) ?? [] }));
|
|
4615
4681
|
if (!req.takeable) return { ...page, items, notices: notices(ctx) };
|
|
4616
|
-
const free = items.filter((
|
|
4682
|
+
const free = items.filter((item2) => item2.holders.length === 0);
|
|
4617
4683
|
return {
|
|
4618
4684
|
items: free,
|
|
4619
4685
|
offset: page.offset,
|
|
@@ -4623,13 +4689,21 @@ async function listWorkItems2(ctx, req, options) {
|
|
|
4623
4689
|
notices: notices(ctx)
|
|
4624
4690
|
};
|
|
4625
4691
|
}
|
|
4692
|
+
async function getWorkItem2(ctx, req, options) {
|
|
4693
|
+
const item2 = await getWorkItem(ctx, { id: req.id }, options);
|
|
4694
|
+
const [holders, brief3] = await Promise.all([
|
|
4695
|
+
listHolders(options.root, item2.id),
|
|
4696
|
+
getBrief(ctx, { path: item2.path }, options)
|
|
4697
|
+
]);
|
|
4698
|
+
return { item: { ...item2, holders }, brief: brief3, notices: notices(ctx, item2.id) };
|
|
4699
|
+
}
|
|
4626
4700
|
async function takeWorkItem(ctx, req, options) {
|
|
4627
|
-
const
|
|
4701
|
+
const item2 = await getWorkItem(ctx, { id: req.id }, options);
|
|
4628
4702
|
const before = await listHolders(options.root, req.id);
|
|
4629
4703
|
const note = warning(before, req.sessionId ?? "");
|
|
4630
4704
|
if (req.sessionId)
|
|
4631
4705
|
await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
|
|
4632
|
-
const moved =
|
|
4706
|
+
const moved = item2.status === "in-progress" ? await getWorkItem(ctx, { id: req.id }, options) : await moveWorkItem(
|
|
4633
4707
|
ctx,
|
|
4634
4708
|
{
|
|
4635
4709
|
id: req.id,
|
|
@@ -4645,19 +4719,19 @@ async function takeWorkItem(ctx, req, options) {
|
|
|
4645
4719
|
};
|
|
4646
4720
|
}
|
|
4647
4721
|
async function updateWorkItem(ctx, req, options) {
|
|
4648
|
-
let
|
|
4722
|
+
let item2 = await getWorkItem(ctx, { id: req.id }, options);
|
|
4649
4723
|
if (req.observed) {
|
|
4650
|
-
|
|
4724
|
+
item2 = await observeWorkItem(
|
|
4651
4725
|
ctx,
|
|
4652
4726
|
{ id: req.id, ac: req.observed.ac, saw: req.observed.saw },
|
|
4653
4727
|
options
|
|
4654
4728
|
);
|
|
4655
4729
|
}
|
|
4656
4730
|
if (req.sessionId) {
|
|
4657
|
-
|
|
4731
|
+
item2 = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
|
|
4658
4732
|
}
|
|
4659
4733
|
if (req.question) {
|
|
4660
|
-
|
|
4734
|
+
item2 = await askWorkItem(
|
|
4661
4735
|
ctx,
|
|
4662
4736
|
{
|
|
4663
4737
|
id: req.id,
|
|
@@ -4669,7 +4743,7 @@ async function updateWorkItem(ctx, req, options) {
|
|
|
4669
4743
|
);
|
|
4670
4744
|
}
|
|
4671
4745
|
return {
|
|
4672
|
-
item: { ...
|
|
4746
|
+
item: { ...item2, holders: await listHolders(options.root, req.id) },
|
|
4673
4747
|
notices: notices(ctx, req.id)
|
|
4674
4748
|
};
|
|
4675
4749
|
}
|
|
@@ -4677,7 +4751,7 @@ async function completeWorkItem2(ctx, req, options) {
|
|
|
4677
4751
|
for (const seen of req.observed ?? []) {
|
|
4678
4752
|
await observeWorkItem(ctx, { id: req.id, ac: seen.ac, saw: seen.saw }, options);
|
|
4679
4753
|
}
|
|
4680
|
-
const
|
|
4754
|
+
const item2 = await completeWorkItem(
|
|
4681
4755
|
ctx,
|
|
4682
4756
|
{
|
|
4683
4757
|
id: req.id,
|
|
@@ -4688,7 +4762,7 @@ async function completeWorkItem2(ctx, req, options) {
|
|
|
4688
4762
|
options
|
|
4689
4763
|
);
|
|
4690
4764
|
return {
|
|
4691
|
-
item: { ...
|
|
4765
|
+
item: { ...item2, holders: await listHolders(options.root, req.id) },
|
|
4692
4766
|
notices: notices(ctx, req.id)
|
|
4693
4767
|
};
|
|
4694
4768
|
}
|
|
@@ -4697,9 +4771,9 @@ async function verifyWorkItem2(ctx, req, options) {
|
|
|
4697
4771
|
return { ...verified, notices: notices(ctx, req.id) };
|
|
4698
4772
|
}
|
|
4699
4773
|
async function createWorkItem2(ctx, req, options) {
|
|
4700
|
-
const
|
|
4774
|
+
const item2 = await createWorkItem(ctx, req, options);
|
|
4701
4775
|
return {
|
|
4702
|
-
item: { ...
|
|
4776
|
+
item: { ...item2, holders: await listHolders(options.root, item2.id) },
|
|
4703
4777
|
notices: notices(ctx)
|
|
4704
4778
|
};
|
|
4705
4779
|
}
|
|
@@ -4795,6 +4869,9 @@ function parseList(params) {
|
|
|
4795
4869
|
if (params && typeof params !== "object") throw new Error("params must be an object");
|
|
4796
4870
|
return params ?? {};
|
|
4797
4871
|
}
|
|
4872
|
+
function parseGet(params) {
|
|
4873
|
+
return { id: requireId(params) };
|
|
4874
|
+
}
|
|
4798
4875
|
function parseTake(params) {
|
|
4799
4876
|
const p = fields(params);
|
|
4800
4877
|
const actor = p.actor;
|
|
@@ -4845,6 +4922,9 @@ function parseCreate(params) {
|
|
|
4845
4922
|
async function list(ctx, req) {
|
|
4846
4923
|
return listWorkItems2(ctx, req, boardOptions());
|
|
4847
4924
|
}
|
|
4925
|
+
async function get(ctx, req) {
|
|
4926
|
+
return getWorkItem2(ctx, req, boardOptions());
|
|
4927
|
+
}
|
|
4848
4928
|
async function take(ctx, req) {
|
|
4849
4929
|
return takeWorkItem(ctx, req, boardOptions());
|
|
4850
4930
|
}
|
|
@@ -4861,6 +4941,7 @@ async function create(ctx, req) {
|
|
|
4861
4941
|
return createWorkItem2(ctx, req, boardOptions());
|
|
4862
4942
|
}
|
|
4863
4943
|
router.method("board.list", parseList, list);
|
|
4944
|
+
router.method("board.get", parseGet, get);
|
|
4864
4945
|
router.method("board.take", parseTake, take);
|
|
4865
4946
|
router.method("board.update", parseUpdate, update);
|
|
4866
4947
|
router.method("board.verify", parseVerify, verify);
|
|
@@ -5119,10 +5200,10 @@ var GraphStore = class {
|
|
|
5119
5200
|
}
|
|
5120
5201
|
}
|
|
5121
5202
|
};
|
|
5122
|
-
function pushInto(map2, key,
|
|
5203
|
+
function pushInto(map2, key, item2) {
|
|
5123
5204
|
const list4 = map2.get(key);
|
|
5124
|
-
if (list4) list4.push(
|
|
5125
|
-
else map2.set(key, [
|
|
5205
|
+
if (list4) list4.push(item2);
|
|
5206
|
+
else map2.set(key, [item2]);
|
|
5126
5207
|
}
|
|
5127
5208
|
function describeRef(to) {
|
|
5128
5209
|
return typeof to === "string" ? to : `${to.type}:${to.name}`;
|
|
@@ -5237,36 +5318,36 @@ async function find(run6, spec) {
|
|
|
5237
5318
|
const listed = await guard(run6, spec.name, () => spec.from.list(ctx));
|
|
5238
5319
|
if (listed === void 0) return void 0;
|
|
5239
5320
|
const kept = [];
|
|
5240
|
-
for (const
|
|
5241
|
-
const checked = await validate(run6, spec,
|
|
5321
|
+
for (const item2 of listed) {
|
|
5322
|
+
const checked = await validate(run6, spec, item2);
|
|
5242
5323
|
if (checked) kept.push(checked);
|
|
5243
5324
|
}
|
|
5244
5325
|
return {
|
|
5245
5326
|
spec,
|
|
5246
|
-
entries: kept.map((
|
|
5327
|
+
entries: kept.map((item2) => ({ item: item2, record: {}, items: [] })),
|
|
5247
5328
|
ledger: { name: spec.name, items: kept.length, nodes: 0, edges: {}, reserved: {} }
|
|
5248
5329
|
};
|
|
5249
5330
|
}
|
|
5250
|
-
async function validate(run6, spec,
|
|
5331
|
+
async function validate(run6, spec, item2) {
|
|
5251
5332
|
const schema = spec.from.schema;
|
|
5252
|
-
if (!schema) return
|
|
5333
|
+
if (!schema) return item2;
|
|
5253
5334
|
const checked = await guard(
|
|
5254
5335
|
run6,
|
|
5255
5336
|
spec.name,
|
|
5256
|
-
async () => await schema["~standard"].validate(
|
|
5337
|
+
async () => await schema["~standard"].validate(item2.data)
|
|
5257
5338
|
);
|
|
5258
5339
|
if (checked === void 0) return void 0;
|
|
5259
5340
|
if (checked.issues) {
|
|
5260
5341
|
run6.report({
|
|
5261
5342
|
severity: "error",
|
|
5262
5343
|
code: "MAP_SCHEMA",
|
|
5263
|
-
message: `${spec.name}: ${
|
|
5344
|
+
message: `${spec.name}: ${item2.ref} does not match its schema \u2014 ${issueText(checked.issues)}. Keys present: ${keyText(item2.data)}`,
|
|
5264
5345
|
plugin: spec.name,
|
|
5265
|
-
...
|
|
5346
|
+
...item2.file !== void 0 ? { file: item2.file } : {}
|
|
5266
5347
|
});
|
|
5267
5348
|
return void 0;
|
|
5268
5349
|
}
|
|
5269
|
-
return { ...
|
|
5350
|
+
return { ...item2, data: checked.value };
|
|
5270
5351
|
}
|
|
5271
5352
|
async function shape(run6, scope2) {
|
|
5272
5353
|
const ctx = mapContext(run6, scope2.spec.name);
|
|
@@ -5378,8 +5459,8 @@ function toNodeInput(id, record, req) {
|
|
|
5378
5459
|
if (key === "items" || key === "links" || key === "rel" || key === "inverse" || key === "ref")
|
|
5379
5460
|
continue;
|
|
5380
5461
|
if (RESERVED.includes(key) || key === "id") continue;
|
|
5381
|
-
const
|
|
5382
|
-
if (
|
|
5462
|
+
const json2 = jsonOf(value);
|
|
5463
|
+
if (json2 !== void 0) free[key] = json2;
|
|
5383
5464
|
}
|
|
5384
5465
|
return {
|
|
5385
5466
|
id,
|
|
@@ -5409,16 +5490,16 @@ function jsonOf(value) {
|
|
|
5409
5490
|
if (Array.isArray(value)) {
|
|
5410
5491
|
const out = [];
|
|
5411
5492
|
for (const one of value) {
|
|
5412
|
-
const
|
|
5413
|
-
if (
|
|
5493
|
+
const json2 = jsonOf(one);
|
|
5494
|
+
if (json2 !== void 0) out.push(json2);
|
|
5414
5495
|
}
|
|
5415
5496
|
return out;
|
|
5416
5497
|
}
|
|
5417
5498
|
if (typeof value === "object") {
|
|
5418
5499
|
const out = {};
|
|
5419
5500
|
for (const [key, held] of Object.entries(value)) {
|
|
5420
|
-
const
|
|
5421
|
-
if (
|
|
5501
|
+
const json2 = jsonOf(held);
|
|
5502
|
+
if (json2 !== void 0) out[key] = json2;
|
|
5422
5503
|
}
|
|
5423
5504
|
return out;
|
|
5424
5505
|
}
|
|
@@ -6026,8 +6107,8 @@ async function pool(items, limit2, work) {
|
|
|
6026
6107
|
let next = 0;
|
|
6027
6108
|
const runners = Array.from({ length: Math.min(limit2, items.length) }, async () => {
|
|
6028
6109
|
for (let i = next++; i < items.length; i = next++) {
|
|
6029
|
-
const
|
|
6030
|
-
if (
|
|
6110
|
+
const item2 = items[i];
|
|
6111
|
+
if (item2 !== void 0) await work(item2);
|
|
6031
6112
|
}
|
|
6032
6113
|
});
|
|
6033
6114
|
await Promise.all(runners);
|
|
@@ -6806,8 +6887,8 @@ function notReadyMs() {
|
|
|
6806
6887
|
var PENDING = /* @__PURE__ */ Symbol("pending");
|
|
6807
6888
|
async function readyWithin(pending) {
|
|
6808
6889
|
let timer;
|
|
6809
|
-
const timeout = new Promise((
|
|
6810
|
-
timer = setTimeout(() =>
|
|
6890
|
+
const timeout = new Promise((resolve10) => {
|
|
6891
|
+
timer = setTimeout(() => resolve10(PENDING), notReadyMs());
|
|
6811
6892
|
timer.unref?.();
|
|
6812
6893
|
});
|
|
6813
6894
|
try {
|
|
@@ -7110,34 +7191,34 @@ function workLadder(graph2) {
|
|
|
7110
7191
|
for (const node of graph2.nodes()) {
|
|
7111
7192
|
if (typeof node.metadata.statusCategory === "string") items.push(node);
|
|
7112
7193
|
}
|
|
7113
|
-
const isItem = new Set(items.map((
|
|
7194
|
+
const isItem = new Set(items.map((item2) => item2.id));
|
|
7114
7195
|
const containers = /* @__PURE__ */ new Set();
|
|
7115
7196
|
const holders = /* @__PURE__ */ new Map();
|
|
7116
|
-
for (const
|
|
7117
|
-
for (const edge of graph2.edges({ from:
|
|
7197
|
+
for (const item2 of items) {
|
|
7198
|
+
for (const edge of graph2.edges({ from: item2.id, relation: "belongs_to" })) {
|
|
7118
7199
|
if (!isItem.has(edge.to)) continue;
|
|
7119
7200
|
containers.add(edge.to);
|
|
7120
|
-
holders.set(
|
|
7201
|
+
holders.set(item2.id, edge.to);
|
|
7121
7202
|
}
|
|
7122
7203
|
}
|
|
7123
7204
|
const children = /* @__PURE__ */ new Map();
|
|
7124
7205
|
const roots = [];
|
|
7125
|
-
for (const
|
|
7126
|
-
const home = holders.get(
|
|
7206
|
+
for (const item2 of items) {
|
|
7207
|
+
const home = holders.get(item2.id);
|
|
7127
7208
|
if (home === void 0) {
|
|
7128
|
-
roots.push(
|
|
7209
|
+
roots.push(item2);
|
|
7129
7210
|
continue;
|
|
7130
7211
|
}
|
|
7131
7212
|
const bucket = children.get(home) ?? { containers: [], leaves: [] };
|
|
7132
|
-
(containers.has(
|
|
7213
|
+
(containers.has(item2.id) ? bucket.containers : bucket.leaves).push(item2);
|
|
7133
7214
|
children.set(home, bucket);
|
|
7134
7215
|
}
|
|
7135
7216
|
const members = /* @__PURE__ */ new Map();
|
|
7136
|
-
for (const
|
|
7137
|
-
if (containers.has(
|
|
7138
|
-
const category = String(
|
|
7139
|
-
const seen = /* @__PURE__ */ new Set([
|
|
7140
|
-
let home = holders.get(
|
|
7217
|
+
for (const item2 of items) {
|
|
7218
|
+
if (containers.has(item2.id)) continue;
|
|
7219
|
+
const category = String(item2.metadata.statusCategory);
|
|
7220
|
+
const seen = /* @__PURE__ */ new Set([item2.id]);
|
|
7221
|
+
let home = holders.get(item2.id);
|
|
7141
7222
|
while (home !== void 0 && !seen.has(home)) {
|
|
7142
7223
|
seen.add(home);
|
|
7143
7224
|
const rollup = members.get(home) ?? {};
|
|
@@ -7149,7 +7230,7 @@ function workLadder(graph2) {
|
|
|
7149
7230
|
return { items, containers, holders, children, members, roots };
|
|
7150
7231
|
}
|
|
7151
7232
|
function leavesOf(ladder) {
|
|
7152
|
-
return ladder.items.filter((
|
|
7233
|
+
return ladder.items.filter((item2) => !ladder.containers.has(item2.id));
|
|
7153
7234
|
}
|
|
7154
7235
|
|
|
7155
7236
|
// ../../packages/data/src/mcp/tools/cache.ts
|
|
@@ -7314,15 +7395,15 @@ async function brief(ctx, req) {
|
|
|
7314
7395
|
const leaves = leavesOf(ladder);
|
|
7315
7396
|
const byCategory = {};
|
|
7316
7397
|
const inFlight = [];
|
|
7317
|
-
for (const
|
|
7318
|
-
const category = String(
|
|
7398
|
+
for (const item2 of leaves) {
|
|
7399
|
+
const category = String(item2.metadata.statusCategory);
|
|
7319
7400
|
byCategory[category] = (byCategory[category] ?? 0) + 1;
|
|
7320
7401
|
if (category === "in-progress")
|
|
7321
|
-
inFlight.push(renderWorkItem(ctx,
|
|
7402
|
+
inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
|
|
7322
7403
|
}
|
|
7323
7404
|
const openItems = leaves.filter((i) => i.metadata.statusCategory === "open");
|
|
7324
|
-
for (const
|
|
7325
|
-
inFlight.push(renderWorkItem(ctx,
|
|
7405
|
+
for (const item2 of openItems.slice(0, Math.max(0, itemCap - inFlight.length))) {
|
|
7406
|
+
inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
|
|
7326
7407
|
}
|
|
7327
7408
|
const shown = inFlight.slice(0, itemCap);
|
|
7328
7409
|
const allContainers = [...ladder.containers].map((id) => ctx.graph.node(id)).filter((n) => n !== void 0).map((n) => ({
|
|
@@ -7407,15 +7488,15 @@ function byContainerRelevance(a, b) {
|
|
|
7407
7488
|
const size = (c) => Object.values(c.members).reduce((sum, n) => sum + n, 0);
|
|
7408
7489
|
return flight(b) - flight(a) || size(b) - size(a) || a.id.localeCompare(b.id);
|
|
7409
7490
|
}
|
|
7410
|
-
function renderWorkItem(ctx,
|
|
7411
|
-
const priority =
|
|
7412
|
-
const dependsOn = ctx.graph.edges({ from:
|
|
7491
|
+
function renderWorkItem(ctx, item2, home) {
|
|
7492
|
+
const priority = item2.metadata.priority;
|
|
7493
|
+
const dependsOn = ctx.graph.edges({ from: item2.id, relation: "depends_on" }).map((e) => e.to);
|
|
7413
7494
|
return {
|
|
7414
|
-
id:
|
|
7415
|
-
...labelOf(
|
|
7416
|
-
type:
|
|
7417
|
-
...typeof
|
|
7418
|
-
statusCategory: String(
|
|
7495
|
+
id: item2.id,
|
|
7496
|
+
...labelOf(item2) ? { title: labelOf(item2) } : {},
|
|
7497
|
+
type: item2.type,
|
|
7498
|
+
...typeof item2.metadata.status === "string" ? { status: item2.metadata.status } : {},
|
|
7499
|
+
statusCategory: String(item2.metadata.statusCategory),
|
|
7419
7500
|
...typeof priority === "string" ? { priority } : {},
|
|
7420
7501
|
...dependsOn.length ? { dependsOn } : {},
|
|
7421
7502
|
...home !== void 0 ? { belongsTo: home } : {}
|
|
@@ -7476,7 +7557,7 @@ function listConnections(ctx, req) {
|
|
|
7476
7557
|
const statusOf0 = start ? statusOf(start) : void 0;
|
|
7477
7558
|
const seen = new Set(startIds);
|
|
7478
7559
|
const withStatus = /* @__PURE__ */ new Map();
|
|
7479
|
-
const
|
|
7560
|
+
const context2 = /* @__PURE__ */ new Map();
|
|
7480
7561
|
let settled = 0;
|
|
7481
7562
|
const take2 = (id, via) => {
|
|
7482
7563
|
if (seen.has(id)) return false;
|
|
@@ -7488,7 +7569,7 @@ function listConnections(ctx, req) {
|
|
|
7488
7569
|
if (via === void 0 && wants(ctx, node, req.direction)) {
|
|
7489
7570
|
const thing = render(ctx, node);
|
|
7490
7571
|
const key = thing.id ?? thing.file ?? id;
|
|
7491
|
-
if (!
|
|
7572
|
+
if (!context2.has(key)) context2.set(key, thing);
|
|
7492
7573
|
}
|
|
7493
7574
|
return true;
|
|
7494
7575
|
}
|
|
@@ -7517,7 +7598,7 @@ function listConnections(ctx, req) {
|
|
|
7517
7598
|
);
|
|
7518
7599
|
const page = ordered.slice(0, limit2);
|
|
7519
7600
|
const label = start ? labelOf(start) ?? start.id : req.node;
|
|
7520
|
-
const read3 = [...
|
|
7601
|
+
const read3 = [...context2.values()];
|
|
7521
7602
|
const nothing = ordered.length === 0 && read3.length === 0;
|
|
7522
7603
|
return {
|
|
7523
7604
|
summary: nothing ? `Nothing in the map connects to ${label}.` : `${label} connects to ${ordered.length} thing(s) still open${settled > 0 ? ` (and ${settled} settled)` : ""}${read3.length > 0 ? `, plus ${read3.length} it is read against` : ""}.${ordered.length === 0 ? " Nothing open is on it." : ""}`,
|
|
@@ -8331,10 +8412,10 @@ function buildEditSet(ctx, found, limit2, namedGovIds, origin) {
|
|
|
8331
8412
|
confidence: "precise",
|
|
8332
8413
|
...origin ? { origin } : {}
|
|
8333
8414
|
});
|
|
8334
|
-
const
|
|
8415
|
+
const named2 = namedGovIds.has(hit.id);
|
|
8335
8416
|
for (const r of enforcing.results) {
|
|
8336
8417
|
if (!r.file) continue;
|
|
8337
|
-
if (
|
|
8418
|
+
if (named2) namedGovFiles.add(r.file);
|
|
8338
8419
|
const why = `enforces ${hit.id}`;
|
|
8339
8420
|
const existing = byFile.get(r.file);
|
|
8340
8421
|
if (existing) existing.why = existing.why ? `${existing.why}; ${why}` : why;
|
|
@@ -9084,11 +9165,11 @@ function graph() {
|
|
|
9084
9165
|
}
|
|
9085
9166
|
function refuseAnotherBranch(req) {
|
|
9086
9167
|
const asked = req ?? {};
|
|
9087
|
-
const
|
|
9088
|
-
if (typeof
|
|
9168
|
+
const named2 = typeof asked.branch === "string" ? asked.branch : asked.ref;
|
|
9169
|
+
if (typeof named2 !== "string" || !named2.trim()) return;
|
|
9089
9170
|
throw new RpcError(
|
|
9090
9171
|
JSON_RPC.INVALID_PARAMS,
|
|
9091
|
-
`the map answers for the branch this machine has checked out, and cannot answer for '${
|
|
9172
|
+
`the map answers for the branch this machine has checked out, and cannot answer for '${named2}'. It is built from one checkout, so an answer for another branch would join this branch's code to that branch's work \u2014 which looks like an answer and is not one. Check that branch out and rebuild, or ask the BOARD instead, which does read any branch: \`work list --branch ${named2}\` or \`work find\`.`,
|
|
9092
9173
|
{ type: "map_is_per_checkout" }
|
|
9093
9174
|
);
|
|
9094
9175
|
}
|
|
@@ -9540,7 +9621,7 @@ function parseList2(params) {
|
|
|
9540
9621
|
if (params && typeof params !== "object") throw new Error("params must be an object");
|
|
9541
9622
|
return params ?? {};
|
|
9542
9623
|
}
|
|
9543
|
-
function
|
|
9624
|
+
function parseGet2(params) {
|
|
9544
9625
|
if (!params || typeof params !== "object") throw new Error("params required");
|
|
9545
9626
|
const p = params;
|
|
9546
9627
|
if (typeof p.id !== "string") throw new Error("id required");
|
|
@@ -9570,7 +9651,7 @@ function parseDelete(params) {
|
|
|
9570
9651
|
async function list2(ctx, req) {
|
|
9571
9652
|
return listNotifications(ctx, req);
|
|
9572
9653
|
}
|
|
9573
|
-
async function
|
|
9654
|
+
async function get2(ctx, req) {
|
|
9574
9655
|
return getNotification(ctx, req);
|
|
9575
9656
|
}
|
|
9576
9657
|
async function create2(ctx, req) {
|
|
@@ -9584,7 +9665,7 @@ async function remove(ctx, req) {
|
|
|
9584
9665
|
return { ok: true };
|
|
9585
9666
|
}
|
|
9586
9667
|
router.method("notifications.list", parseList2, list2);
|
|
9587
|
-
router.method("notifications.get",
|
|
9668
|
+
router.method("notifications.get", parseGet2, get2);
|
|
9588
9669
|
router.method("notifications.create", parseCreate2, create2);
|
|
9589
9670
|
router.method("notifications.update", parseUpdate2, update2);
|
|
9590
9671
|
router.method("notifications.delete", parseDelete, remove);
|
|
@@ -9667,7 +9748,7 @@ function parseList3(params) {
|
|
|
9667
9748
|
if (params && typeof params !== "object") throw new Error("params must be an object");
|
|
9668
9749
|
return params ?? {};
|
|
9669
9750
|
}
|
|
9670
|
-
function
|
|
9751
|
+
function parseGet3(params) {
|
|
9671
9752
|
if (!params || typeof params !== "object") throw new Error("params required");
|
|
9672
9753
|
const p = params;
|
|
9673
9754
|
if (typeof p.id !== "string") throw new Error("id required");
|
|
@@ -9677,12 +9758,12 @@ async function list3(ctx, req) {
|
|
|
9677
9758
|
void ctx;
|
|
9678
9759
|
return listSessions(req);
|
|
9679
9760
|
}
|
|
9680
|
-
async function
|
|
9761
|
+
async function get3(ctx, req) {
|
|
9681
9762
|
void ctx;
|
|
9682
9763
|
return getSession(req);
|
|
9683
9764
|
}
|
|
9684
9765
|
router.method("sessions.list", parseList3, list3);
|
|
9685
|
-
router.method("sessions.get",
|
|
9766
|
+
router.method("sessions.get", parseGet3, get3);
|
|
9686
9767
|
|
|
9687
9768
|
// src/workdir.ts
|
|
9688
9769
|
import { exec as execCb } from "child_process";
|
|
@@ -9788,8 +9869,8 @@ import { createRequire as createRequire2 } from "module";
|
|
|
9788
9869
|
var _require = createRequire2(import.meta.url);
|
|
9789
9870
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
9790
9871
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
9791
|
-
var SHA = "
|
|
9792
|
-
var BUILT = "2026-09-
|
|
9872
|
+
var SHA = "de4a370";
|
|
9873
|
+
var BUILT = "2026-09-09";
|
|
9793
9874
|
var BUILD = SHA ?? "source";
|
|
9794
9875
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
9795
9876
|
|
|
@@ -10139,12 +10220,12 @@ async function startAgent(options) {
|
|
|
10139
10220
|
upstream.onConnected(() => {
|
|
10140
10221
|
if (!paired) {
|
|
10141
10222
|
paired = true;
|
|
10142
|
-
const
|
|
10223
|
+
const board2 = findBoardOrigin(options.workspacePath);
|
|
10143
10224
|
publish(envCtx, "env.paired", {
|
|
10144
10225
|
envId,
|
|
10145
10226
|
version: process.env.JARVIS_VERSION ?? "0.0.0",
|
|
10146
10227
|
os: `${os9.type()} ${os9.release()}`,
|
|
10147
|
-
...
|
|
10228
|
+
...board2 ? { boardRepo: board2.repo, boardBranch: board2.branch } : {}
|
|
10148
10229
|
});
|
|
10149
10230
|
}
|
|
10150
10231
|
publish(envCtx, "env.heartbeat", { envId, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -10242,7 +10323,7 @@ async function startAgent(options) {
|
|
|
10242
10323
|
timestamp: Date.now()
|
|
10243
10324
|
};
|
|
10244
10325
|
publish(envCtx, "env.disconnected", { envId: options.envId, reason: "shutdown" });
|
|
10245
|
-
await new Promise((
|
|
10326
|
+
await new Promise((resolve10) => setImmediate(resolve10));
|
|
10246
10327
|
}
|
|
10247
10328
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10248
10329
|
if (ipcServer) {
|
|
@@ -10519,10 +10600,10 @@ async function getHealth(req = {}) {
|
|
|
10519
10600
|
async function probeSocket(url, probeMs) {
|
|
10520
10601
|
const port = portOf(url);
|
|
10521
10602
|
if (port === null) return { url, reachable: false };
|
|
10522
|
-
const reachable = await new Promise((
|
|
10603
|
+
const reachable = await new Promise((resolve10) => {
|
|
10523
10604
|
const done = (answer) => {
|
|
10524
10605
|
socket.destroy();
|
|
10525
|
-
|
|
10606
|
+
resolve10(answer);
|
|
10526
10607
|
};
|
|
10527
10608
|
const socket = net2.connect({ host: new URL(url).hostname, port }).once("connect", () => done(true)).once("error", () => done(false));
|
|
10528
10609
|
socket.setTimeout(probeMs, () => done(false));
|
|
@@ -10695,10 +10776,10 @@ function removeDir(p) {
|
|
|
10695
10776
|
}
|
|
10696
10777
|
function confirm2(prompt) {
|
|
10697
10778
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
10698
|
-
return new Promise((
|
|
10779
|
+
return new Promise((resolve10) => {
|
|
10699
10780
|
rl.question(prompt, (answer) => {
|
|
10700
10781
|
rl.close();
|
|
10701
|
-
|
|
10782
|
+
resolve10(/^y(es)?$/i.test(answer.trim()));
|
|
10702
10783
|
});
|
|
10703
10784
|
});
|
|
10704
10785
|
}
|
|
@@ -11588,10 +11669,10 @@ function createTool(handlers) {
|
|
|
11588
11669
|
}
|
|
11589
11670
|
};
|
|
11590
11671
|
}
|
|
11591
|
-
function describeItem(
|
|
11592
|
-
const held =
|
|
11593
|
-
const priority =
|
|
11594
|
-
return `${
|
|
11672
|
+
function describeItem(item2) {
|
|
11673
|
+
const held = item2.holders && item2.holders.length > 0 ? ` \xB7 held by ${item2.holders.map((holder) => holder.by || holder.session).join(", ")}` : "";
|
|
11674
|
+
const priority = item2.priority ? ` ${item2.priority}` : "";
|
|
11675
|
+
return `${item2.id} \xB7 ${item2.status}${priority}${held} \u2014 ${item2.title}`;
|
|
11595
11676
|
}
|
|
11596
11677
|
function describeList(response) {
|
|
11597
11678
|
if (response.items.length === 0) return "No work items match.";
|
|
@@ -11888,9 +11969,140 @@ function register20(program) {
|
|
|
11888
11969
|
});
|
|
11889
11970
|
}
|
|
11890
11971
|
|
|
11972
|
+
// src/commands/ui.ts
|
|
11973
|
+
import { basename as basename2 } from "path";
|
|
11974
|
+
|
|
11975
|
+
// src/ui/server.ts
|
|
11976
|
+
import { createServer as createServer2 } from "http";
|
|
11977
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
11978
|
+
import { basename, dirname as dirname7, extname, join as join9, resolve as resolve9 } from "path";
|
|
11979
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11980
|
+
var ASSETS = resolve9(dirname7(fileURLToPath4(import.meta.url)), "ui");
|
|
11981
|
+
var TYPES = {
|
|
11982
|
+
".html": "text/html; charset=utf-8",
|
|
11983
|
+
".js": "text/javascript; charset=utf-8",
|
|
11984
|
+
".css": "text/css; charset=utf-8",
|
|
11985
|
+
".map": "application/json; charset=utf-8"
|
|
11986
|
+
};
|
|
11987
|
+
function named(html, repo) {
|
|
11988
|
+
return html.replace("<title>Jarvis</title>", `<title>${basename(repo).replace(/[<>]/g, "")} \xB7 Jarvis</title>`);
|
|
11989
|
+
}
|
|
11990
|
+
function context() {
|
|
11991
|
+
return { requestId: `ui-${Date.now().toString(36)}`, userId: "local" };
|
|
11992
|
+
}
|
|
11993
|
+
function json(res, status2, body) {
|
|
11994
|
+
const payload2 = JSON.stringify(body);
|
|
11995
|
+
res.writeHead(status2, {
|
|
11996
|
+
"content-type": "application/json; charset=utf-8",
|
|
11997
|
+
"content-length": Buffer.byteLength(payload2),
|
|
11998
|
+
// The board changes under you as sessions work it; a cached page would show a board that has
|
|
11999
|
+
// moved on and give no sign of it.
|
|
12000
|
+
"cache-control": "no-store"
|
|
12001
|
+
});
|
|
12002
|
+
res.end(payload2);
|
|
12003
|
+
}
|
|
12004
|
+
async function board(repo, url, res) {
|
|
12005
|
+
const req = {};
|
|
12006
|
+
const limit2 = url.searchParams.get("limit");
|
|
12007
|
+
if (limit2) req.limit = Number.parseInt(limit2, 10);
|
|
12008
|
+
const offset = url.searchParams.get("offset");
|
|
12009
|
+
if (offset) req.offset = Number.parseInt(offset, 10);
|
|
12010
|
+
const status2 = url.searchParams.get("status");
|
|
12011
|
+
if (status2) req.status = status2;
|
|
12012
|
+
const page = await listWorkItems2(context(), req, { root: repo, harness: harness2() });
|
|
12013
|
+
json(res, 200, { items: page.items, total: page.total, hasMore: page.hasMore });
|
|
12014
|
+
}
|
|
12015
|
+
async function item(repo, id, res) {
|
|
12016
|
+
const read3 = await getWorkItem2(context(), { id }, { root: repo, harness: harness2() });
|
|
12017
|
+
json(res, 200, { item: read3.item, brief: read3.brief });
|
|
12018
|
+
}
|
|
12019
|
+
async function asset(repo, url, res) {
|
|
12020
|
+
const wanted = url.pathname === "/" || !extname(url.pathname) ? "/index.html" : url.pathname;
|
|
12021
|
+
const path22 = join9(ASSETS, wanted);
|
|
12022
|
+
if (!path22.startsWith(ASSETS)) {
|
|
12023
|
+
res.writeHead(403).end("no");
|
|
12024
|
+
return;
|
|
12025
|
+
}
|
|
12026
|
+
try {
|
|
12027
|
+
const raw = await readFile7(path22);
|
|
12028
|
+
const body = extname(path22) === ".html" ? Buffer.from(named(raw.toString("utf-8"), repo)) : raw;
|
|
12029
|
+
res.writeHead(200, {
|
|
12030
|
+
"content-type": TYPES[extname(path22)] ?? "application/octet-stream",
|
|
12031
|
+
"content-length": body.byteLength
|
|
12032
|
+
});
|
|
12033
|
+
res.end(body);
|
|
12034
|
+
} catch {
|
|
12035
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
12036
|
+
res.end(
|
|
12037
|
+
"not found. If this is a fresh checkout, the UI is built by `pnpm --filter @appchy/jarvis build`."
|
|
12038
|
+
);
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
function serveUi(repo, port) {
|
|
12042
|
+
const server = createServer2((req, res) => {
|
|
12043
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
12044
|
+
void (async () => {
|
|
12045
|
+
try {
|
|
12046
|
+
if (url.pathname.startsWith("/api/v1/board/items")) {
|
|
12047
|
+
if (req.method !== "GET") {
|
|
12048
|
+
json(res, 405, {
|
|
12049
|
+
error: "this board is read-only here. Changing it runs the harness that owns the tree's invariants \u2014 use `jarvis work` for now."
|
|
12050
|
+
});
|
|
12051
|
+
return;
|
|
12052
|
+
}
|
|
12053
|
+
const id = url.pathname.slice("/api/v1/board/items".length).replace(/^\//, "");
|
|
12054
|
+
if (id) await item(repo, decodeURIComponent(id), res);
|
|
12055
|
+
else await board(repo, url, res);
|
|
12056
|
+
return;
|
|
12057
|
+
}
|
|
12058
|
+
await asset(repo, url, res);
|
|
12059
|
+
} catch (err) {
|
|
12060
|
+
const status2 = err instanceof ServiceError ? err.statusCode : 500;
|
|
12061
|
+
json(res, status2, { error: err instanceof Error ? err.message : String(err) });
|
|
12062
|
+
}
|
|
12063
|
+
})();
|
|
12064
|
+
});
|
|
12065
|
+
return new Promise((ok, fail) => {
|
|
12066
|
+
server.once("error", fail);
|
|
12067
|
+
server.listen(port, "127.0.0.1", () => {
|
|
12068
|
+
ok({
|
|
12069
|
+
url: `http://localhost:${port}`,
|
|
12070
|
+
close: () => new Promise((done) => server.close(() => done()))
|
|
12071
|
+
});
|
|
12072
|
+
});
|
|
12073
|
+
});
|
|
12074
|
+
}
|
|
12075
|
+
|
|
12076
|
+
// src/commands/ui.ts
|
|
12077
|
+
function register21(program) {
|
|
12078
|
+
program.command("ui").description("Open this repo's board in a browser \u2014 nothing to configure, nothing to join").option("--repo <path>", "The repo to show. Default: the working directory").option("--port <number>", "The port to listen on. Default: 3402").option("--no-open", "Print the address instead of opening a browser").action(async (opts) => {
|
|
12079
|
+
const repo = resolveRepo(opts.repo);
|
|
12080
|
+
const port = opts.port ? Number.parseInt(opts.port, 10) : 3402;
|
|
12081
|
+
let server;
|
|
12082
|
+
try {
|
|
12083
|
+
server = await serveUi(repo, port);
|
|
12084
|
+
} catch (err) {
|
|
12085
|
+
const busy = err.code === "EADDRINUSE";
|
|
12086
|
+
console.error(
|
|
12087
|
+
busy ? `port ${port} is already in use. Something else is on it \u2014 pass --port to pick another.` : `could not start: ${err instanceof Error ? err.message : String(err)}`
|
|
12088
|
+
);
|
|
12089
|
+
process.exitCode = 1;
|
|
12090
|
+
return;
|
|
12091
|
+
}
|
|
12092
|
+
console.log(`${basename2(repo)} \u2192 ${server.url}`);
|
|
12093
|
+
console.log("Reading the board from this checkout. Ctrl-C to stop.");
|
|
12094
|
+
if (opts.open !== false) openBrowser({ url: server.url });
|
|
12095
|
+
await new Promise((done) => {
|
|
12096
|
+
const stop = () => void server.close().then(done);
|
|
12097
|
+
process.once("SIGINT", stop);
|
|
12098
|
+
process.once("SIGTERM", stop);
|
|
12099
|
+
});
|
|
12100
|
+
});
|
|
12101
|
+
}
|
|
12102
|
+
|
|
11891
12103
|
// src/commands/work.ts
|
|
11892
12104
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
11893
|
-
function
|
|
12105
|
+
function register22(program) {
|
|
11894
12106
|
program.command("work").description("The board \u2014 take work, prove it, finish it").argument("[args...]", "Passed to the harness untouched").allowUnknownOption().passThroughOptions().helpOption(false).action((passed) => {
|
|
11895
12107
|
const problem = harnessProblem();
|
|
11896
12108
|
if (problem) {
|
|
@@ -11934,6 +12146,7 @@ function createCli() {
|
|
|
11934
12146
|
register20(program);
|
|
11935
12147
|
register2(program);
|
|
11936
12148
|
register21(program);
|
|
12149
|
+
register22(program);
|
|
11937
12150
|
return program;
|
|
11938
12151
|
}
|
|
11939
12152
|
|