pitroom 0.6.4 → 0.6.6
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +48 -0
- package/PRIVACY.md +28 -12
- package/README.md +234 -79
- package/THIRD_PARTY_NOTICES.md +44 -0
- package/dist/lib.mjs +70 -6
- package/dist/pitroom.mjs +1421 -469
- package/dist/ui/app.css +2 -0
- package/dist/ui/app.js +20 -0
- package/docs/backends.md +1 -1
- package/hooks/session-start.mjs +3 -2
- package/package.json +20 -4
- package/skills/pitroom-crew/SKILL.md +4 -3
- package/skills/pitroom-writing-plans/SKILL.md +1 -1
- package/skills/using-pitroom/SKILL.md +5 -1
package/dist/lib.mjs
CHANGED
|
@@ -49,6 +49,9 @@ function resolveCommand(bin) {
|
|
|
49
49
|
return { command: bin, prefix: [] };
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/backends/types.ts
|
|
53
|
+
var MAX_STEPS = 400;
|
|
54
|
+
|
|
52
55
|
// src/backends/claude/events.ts
|
|
53
56
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
54
57
|
var DENIED = /permission|not allowed|denied|blocked|disallowed/i;
|
|
@@ -67,6 +70,8 @@ function parseEvents(jsonl) {
|
|
|
67
70
|
const tools = {};
|
|
68
71
|
const edits = [];
|
|
69
72
|
const pendingEdits = /* @__PURE__ */ new Map();
|
|
73
|
+
const timeline = [];
|
|
74
|
+
const stepOf = /* @__PURE__ */ new Map();
|
|
70
75
|
const steps = /* @__PURE__ */ new Set();
|
|
71
76
|
let sessionId;
|
|
72
77
|
let model;
|
|
@@ -97,18 +102,26 @@ function parseEvents(jsonl) {
|
|
|
97
102
|
if (c.type === "text" && String(c.text ?? "").trim()) {
|
|
98
103
|
lastText = String(c.text).trim();
|
|
99
104
|
lastActivity = `says: ${oneLine(lastText)}`;
|
|
105
|
+
if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip(lastText, 600) });
|
|
100
106
|
} else if (c.type === "tool_use") {
|
|
101
107
|
usage.toolCalls++;
|
|
102
108
|
tools[c.name] = (tools[c.name] ?? 0) + 1;
|
|
103
109
|
const target = c.input?.file_path ?? c.input?.path ?? c.input?.pattern ?? c.input?.command ?? c.input?.url;
|
|
104
110
|
lastActivity = `${c.name} ${target ? oneLine(String(target), 60) : ""}`.trim();
|
|
105
111
|
if (EDIT_TOOLS.has(c.name) && c.input?.file_path) pendingEdits.set(c.id, String(c.input.file_path));
|
|
112
|
+
if (timeline.length < MAX_STEPS) {
|
|
113
|
+
const step = { kind: EDIT_TOOLS.has(c.name) ? "edit" : c.name === "Bash" ? "shell" : "tool", name: c.name, text: clip(String(target ?? ""), 240) };
|
|
114
|
+
timeline.push(step);
|
|
115
|
+
stepOf.set(String(c.id), step);
|
|
116
|
+
}
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
} else if (e.type === "user") {
|
|
109
120
|
for (const c of e.message?.content ?? []) {
|
|
110
121
|
if (c?.type !== "tool_result") continue;
|
|
111
122
|
const text = typeof c.content === "string" ? c.content : JSON.stringify(c.content ?? "");
|
|
123
|
+
const step = stepOf.get(String(c.tool_use_id));
|
|
124
|
+
if (step) step.ok = !c.is_error;
|
|
112
125
|
if (c.is_error) {
|
|
113
126
|
if (DENIED.test(text)) usage.denied++;
|
|
114
127
|
} else if (pendingEdits.has(c.tool_use_id)) {
|
|
@@ -134,8 +147,9 @@ function parseEvents(jsonl) {
|
|
|
134
147
|
}
|
|
135
148
|
}
|
|
136
149
|
usage.steps = steps.size;
|
|
137
|
-
return { sessionId, model, finalText: finalText ?? (error ? "" : lastText), usage, tools, edits, lastActivity, error };
|
|
150
|
+
return { sessionId, model, finalText: finalText ?? (error ? "" : lastText), usage, tools, edits, lastActivity, timeline, error };
|
|
138
151
|
}
|
|
152
|
+
var clip = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
139
153
|
function num(v) {
|
|
140
154
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
141
155
|
}
|
|
@@ -223,7 +237,9 @@ function invocation(req) {
|
|
|
223
237
|
"stream-json",
|
|
224
238
|
"--verbose"
|
|
225
239
|
);
|
|
226
|
-
|
|
240
|
+
const [model, effort] = (req.model ?? "").split("#");
|
|
241
|
+
if (model) args.push("--model", model);
|
|
242
|
+
if (effort) args.push("--effort", effort);
|
|
227
243
|
if (req.sessionId) args.push("--resume", req.sessionId);
|
|
228
244
|
args.push("--", req.prompt);
|
|
229
245
|
const { command, prefix } = resolveCommand(binary());
|
|
@@ -276,11 +292,19 @@ function doctor({ models, hasFallback }) {
|
|
|
276
292
|
if (!hasFallback) checks.push({ level: "warn", message: "no fallback workers configured for Claude Code runs" });
|
|
277
293
|
return checks;
|
|
278
294
|
}
|
|
295
|
+
var CLAUDE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
296
|
+
function catalog() {
|
|
297
|
+
const def = defaultModel();
|
|
298
|
+
const models = ["haiku", "sonnet", "opus"].map((id) => ({ id, efforts: CLAUDE_EFFORTS }));
|
|
299
|
+
if (def && !models.some((m) => m.id === def)) models.push({ id: def, efforts: CLAUDE_EFFORTS });
|
|
300
|
+
return { models, source: "Claude Code aliases (latest of each size) and --effort levels from `claude --help`" };
|
|
301
|
+
}
|
|
279
302
|
var claude = {
|
|
280
303
|
id: "claude",
|
|
281
304
|
name: "Claude Code",
|
|
282
305
|
capabilities: { readOnly: "tool-allowlist", resume: "by-id", reportsCost: true, attachFiles: false },
|
|
283
306
|
binary,
|
|
307
|
+
catalog,
|
|
284
308
|
invocation,
|
|
285
309
|
parse: parseEvents,
|
|
286
310
|
failure,
|
|
@@ -311,6 +335,7 @@ function parseEvents2(jsonl) {
|
|
|
311
335
|
};
|
|
312
336
|
const tools = {};
|
|
313
337
|
const edits = [];
|
|
338
|
+
const timeline = [];
|
|
314
339
|
let sessionId;
|
|
315
340
|
let finalText = "";
|
|
316
341
|
let lastActivity;
|
|
@@ -338,14 +363,17 @@ function parseEvents2(jsonl) {
|
|
|
338
363
|
if (it.type === "agent_message" && String(it.text ?? "").trim()) {
|
|
339
364
|
finalText = String(it.text).trim();
|
|
340
365
|
lastActivity = `says: ${oneLine2(finalText)}`;
|
|
366
|
+
if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip2(finalText, 600) });
|
|
341
367
|
} else if (it.type === "command_execution") {
|
|
342
368
|
if (it.status === "declined" || /operation not permitted|sandbox/i.test(String(it.aggregated_output ?? ""))) usage.denied++;
|
|
343
369
|
lastActivity = `shell ${oneLine2(String(it.command ?? ""), 60)}`;
|
|
370
|
+
if (timeline.length < MAX_STEPS) timeline.push({ kind: "shell", name: "shell", text: clip2(String(it.command ?? "").replace(/^\/bin\/\w+ -lc /, ""), 240), ok: it.exit_code === 0 });
|
|
344
371
|
} else if (it.type === "file_change") {
|
|
345
372
|
const paths = (it.changes ?? []).map((c) => String(c.path));
|
|
346
373
|
if (it.status !== "failed" && it.status !== "declined") edits.push(...paths);
|
|
347
374
|
else usage.denied++;
|
|
348
375
|
lastActivity = `edit ${oneLine2(paths.join(", "), 60)}`;
|
|
376
|
+
if (timeline.length < MAX_STEPS) timeline.push({ kind: "edit", name: "edit", text: clip2(paths.join(", "), 240), ok: it.status !== "failed" && it.status !== "declined" });
|
|
349
377
|
}
|
|
350
378
|
break;
|
|
351
379
|
}
|
|
@@ -369,7 +397,7 @@ function parseEvents2(jsonl) {
|
|
|
369
397
|
break;
|
|
370
398
|
}
|
|
371
399
|
}
|
|
372
|
-
return { sessionId, finalText, usage, tools, edits, lastActivity, error };
|
|
400
|
+
return { sessionId, finalText, usage, tools, edits, lastActivity, timeline, error };
|
|
373
401
|
}
|
|
374
402
|
function describe(raw) {
|
|
375
403
|
const s = String(raw ?? "unknown Codex error");
|
|
@@ -389,6 +417,7 @@ function oneLine2(s, max = 80) {
|
|
|
389
417
|
const t = s.replace(/\s+/g, " ").trim();
|
|
390
418
|
return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
|
|
391
419
|
}
|
|
420
|
+
var clip2 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
392
421
|
|
|
393
422
|
// src/backends/codex/index.ts
|
|
394
423
|
var binary2 = () => findBinary("codex", "PITROOM_CODEX_BIN");
|
|
@@ -474,17 +503,35 @@ function doctor2({ models, hasFallback }) {
|
|
|
474
503
|
login.ok && /logged in/i.test(login.out) ? { level: "ok", message: `Codex: ${login.out.trim().split("\n")[0]}` } : { level: "fail", message: "Codex is not logged in: run `codex login`" }
|
|
475
504
|
);
|
|
476
505
|
for (const m of models) {
|
|
506
|
+
const pinned = m?.replace(/#.*$/, "");
|
|
477
507
|
checks.push({
|
|
478
|
-
level: "ok",
|
|
479
|
-
message:
|
|
508
|
+
level: pinned ? "ok" : "warn",
|
|
509
|
+
message: pinned ? `Codex model: ${pinned} (checked on first use; unsupported models fail over)` : 'Codex model: none pinned, so Codex runs its own default (it can change and cost more); set "models": {"codex": "<model>"} or use -W codex:<model>'
|
|
480
510
|
});
|
|
481
511
|
}
|
|
482
512
|
if (!hasFallback) checks.push({ level: "warn", message: "no fallback workers configured for Codex runs" });
|
|
483
513
|
return checks;
|
|
484
514
|
}
|
|
515
|
+
function catalog2() {
|
|
516
|
+
const file = path3.join(codexHome(), "models_cache.json");
|
|
517
|
+
try {
|
|
518
|
+
const raw = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
519
|
+
const models = (raw.models ?? []).filter((m) => m.visibility !== "hide" && typeof m.slug === "string").map((m) => ({
|
|
520
|
+
id: m.slug,
|
|
521
|
+
efforts: (m.supported_reasoning_levels ?? []).map(
|
|
522
|
+
(l) => typeof l === "string" ? l : l.effort ?? ""
|
|
523
|
+
).filter(Boolean),
|
|
524
|
+
defaultEffort: typeof m.default_reasoning_level === "string" ? m.default_reasoning_level : void 0
|
|
525
|
+
}));
|
|
526
|
+
return { models, source: `Codex model cache${raw.fetched_at ? `, fetched ${raw.fetched_at.slice(0, 10)}` : ""}` };
|
|
527
|
+
} catch {
|
|
528
|
+
return { models: [], source: "no Codex model cache found (run codex once)" };
|
|
529
|
+
}
|
|
530
|
+
}
|
|
485
531
|
var codex = {
|
|
486
532
|
id: "codex",
|
|
487
533
|
name: "Codex",
|
|
534
|
+
catalog: catalog2,
|
|
488
535
|
capabilities: { readOnly: "os-sandbox", resume: "by-id", reportsCost: false, attachFiles: false },
|
|
489
536
|
binary: binary2,
|
|
490
537
|
invocation: invocation2,
|
|
@@ -515,6 +562,7 @@ function parseEvents3(ndjson) {
|
|
|
515
562
|
};
|
|
516
563
|
const tools = {};
|
|
517
564
|
const edits = [];
|
|
565
|
+
const timeline = [];
|
|
518
566
|
const textByMessage = /* @__PURE__ */ new Map();
|
|
519
567
|
let sessionId;
|
|
520
568
|
let lastActivity;
|
|
@@ -537,6 +585,7 @@ function parseEvents3(ndjson) {
|
|
|
537
585
|
if (!textByMessage.has(key)) textByMessage.set(key, []);
|
|
538
586
|
textByMessage.get(key).push(text);
|
|
539
587
|
lastActivity = `says: ${oneLine3(text)}`;
|
|
588
|
+
if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip3(text.trim(), 600), at: stamp(e.timestamp) });
|
|
540
589
|
break;
|
|
541
590
|
}
|
|
542
591
|
case "tool_use": {
|
|
@@ -549,6 +598,11 @@ function parseEvents3(ndjson) {
|
|
|
549
598
|
edits.push(String(st.input?.filePath ?? st.input?.path ?? name));
|
|
550
599
|
}
|
|
551
600
|
lastActivity = `${name} ${describe2(st.input)}`.trim();
|
|
601
|
+
if (timeline.length < MAX_STEPS) {
|
|
602
|
+
const kind = EDIT_TOOLS2.has(name) ? "edit" : /^(bash|shell)$/.test(name) ? "shell" : "tool";
|
|
603
|
+
const text = kind === "shell" ? String(st.input?.command ?? "") : String(st.input?.filePath ?? st.input?.path ?? st.input?.pattern ?? st.input?.url ?? "") || describe2(st.input);
|
|
604
|
+
timeline.push({ kind, name, text: clip3(text, 240), ok: st.status === "completed" ? true : st.status === "error" ? false : void 0, at: stamp(e.timestamp) });
|
|
605
|
+
}
|
|
552
606
|
break;
|
|
553
607
|
}
|
|
554
608
|
case "step_finish": {
|
|
@@ -571,7 +625,7 @@ function parseEvents3(ndjson) {
|
|
|
571
625
|
}
|
|
572
626
|
const groups = [...textByMessage.values()];
|
|
573
627
|
const finalText = (groups[groups.length - 1] ?? []).join("\n").trim();
|
|
574
|
-
return { sessionId, finalText, usage, tools, edits, lastActivity, error };
|
|
628
|
+
return { sessionId, finalText, usage, tools, edits, lastActivity, timeline, error };
|
|
575
629
|
}
|
|
576
630
|
function describeError(err) {
|
|
577
631
|
if (!err || typeof err !== "object") return "unknown OpenCode error";
|
|
@@ -584,6 +638,8 @@ function describeError(err) {
|
|
|
584
638
|
const tags = [err.type, inner, err.status && `HTTP ${err.status}`].filter(Boolean);
|
|
585
639
|
return tags.length ? `${message} [${tags.join(", ")}]` : message;
|
|
586
640
|
}
|
|
641
|
+
var clip3 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
642
|
+
var stamp = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
587
643
|
function num3(v) {
|
|
588
644
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
589
645
|
}
|
|
@@ -889,6 +945,13 @@ function resolveModel2(sessionId) {
|
|
|
889
945
|
return void 0;
|
|
890
946
|
}
|
|
891
947
|
}
|
|
948
|
+
function catalog3() {
|
|
949
|
+
const ids = listModels();
|
|
950
|
+
return {
|
|
951
|
+
models: (ids.length ? ids : listModels()).map((id) => ({ id })),
|
|
952
|
+
source: "`opencode models` (reasoning variants are provider-specific: provider/model#variant)"
|
|
953
|
+
};
|
|
954
|
+
}
|
|
892
955
|
function listModels() {
|
|
893
956
|
return oc(["models"]).out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
894
957
|
}
|
|
@@ -938,6 +1001,7 @@ var opencode = {
|
|
|
938
1001
|
defaultModel: defaultModel2,
|
|
939
1002
|
resolveModel: resolveModel2,
|
|
940
1003
|
listModels,
|
|
1004
|
+
catalog: catalog3,
|
|
941
1005
|
doctor: doctor3
|
|
942
1006
|
};
|
|
943
1007
|
|