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/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
- if (req.model) args.push("--model", req.model);
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: m ? `Codex model: ${m} (checked on first use; unsupported models fail over)` : "Codex model: Codex's built-in default (your ~/.codex/config.toml is ignored for workers; pick one with -W codex:<model>)"
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