@eir-labs/coltrane 0.24.35 → 0.24.37

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.
Files changed (34) hide show
  1. package/agents/session-analyst.json +28 -0
  2. package/agents/session-review-verifier.json +31 -0
  3. package/agents/session-reviewer.json +32 -0
  4. package/dist/src/claude_invoker.d.ts +26 -0
  5. package/dist/src/claude_invoker.js +57 -1
  6. package/dist/src/claude_invoker.js.map +1 -1
  7. package/dist/src/cli.d.ts +1 -1
  8. package/dist/src/mcp.js +6 -0
  9. package/dist/src/mcp.js.map +1 -1
  10. package/dist/src/runtime.d.ts +16 -0
  11. package/dist/src/runtime.js +43 -12
  12. package/dist/src/runtime.js.map +1 -1
  13. package/dist/src/server.d.ts +20 -0
  14. package/dist/src/server.js +102 -1
  15. package/dist/src/server.js.map +1 -1
  16. package/dist/src/version.d.ts +1 -1
  17. package/dist/src/version.js +1 -1
  18. package/domain_types/session-analysis.json +59 -0
  19. package/domain_types/session-census.json +72 -0
  20. package/domain_types/session-review-verdict.json +48 -0
  21. package/domain_types/session-review.json +86 -0
  22. package/domain_types/session-target.json +26 -0
  23. package/package.json +1 -1
  24. package/releases.json +82 -0
  25. package/skills/gig-census/fixtures/basic.json +33 -0
  26. package/skills/gig-census/fixtures/ledger.jsonl +3 -0
  27. package/skills/gig-census/fixtures/outputs/00000000-0000-4000-8000-00000000cens.jsonl +4 -0
  28. package/skills/gig-census/meta.json +20 -0
  29. package/skills/gig-census/skill.mjs +114 -0
  30. package/skills/session-census/fixtures/basic.json +10 -0
  31. package/skills/session-census/fixtures/sample.jsonl +6 -0
  32. package/skills/session-census/meta.json +25 -0
  33. package/skills/session-census/skill.mjs +141 -0
  34. package/standards/session-review-v0.json +104 -0
@@ -24,7 +24,7 @@ import { createOutputMirror, defaultMirrorDir, outputPreview, mirrorStorageRef }
24
24
  import { FileLedger, SplitLedger, LedgerError, LEDGER_SCHEMA_VERSION, defaultLedgerPath, defaultGenomeLedgerPath, } from "./ledger.js";
25
25
  import { sealDrill } from "./seal_drill.js";
26
26
  import { standardSimulate } from "./simulate.js";
27
- import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState } from "./runtime.js";
27
+ import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState, partitionGigInputKeys, unknownGigInputMessage } from "./runtime.js";
28
28
  import { assembleRunDeps, resolveWorkingRepo } from "./run_deps.js";
29
29
  import { createCheckpointStore, createReuseStore } from "./reuse.js";
30
30
  import { killLiveChairChildren } from "./claude_invoker.js";
@@ -318,6 +318,32 @@ async function runImpl(slug, args, deps, approval) {
318
318
  });
319
319
  return { ok: true, requires_approval: approval, data: res };
320
320
  }
321
+ case "seat_ask": {
322
+ // contract-seat-ask-v1 — ask a PAST seat WHY, on the conversation it actually held. The
323
+ // handler seals NO output and writes NO ledger row (I2): it only resumes a session and
324
+ // returns its prose. The resume + cage live in the Claude invoker's hands-off ask path
325
+ // (deps.invoke.askSeat) so the spawn is built by the SAME buildInvokerArgs every seat uses.
326
+ const gig_id = typeof args["gig_id"] === "string" ? args["gig_id"] : "";
327
+ const role = typeof args["role"] === "string" ? args["role"] : "";
328
+ const question = typeof args["question"] === "string" ? args["question"] : "";
329
+ const max_turns = typeof args["max_turns"] === "number" ? args["max_turns"] : undefined;
330
+ const asker = deps.invoke;
331
+ if (!asker || typeof asker.askSeat !== "function") {
332
+ return { ok: false, requires_approval: approval, error: "seat_ask needs a Claude invoker with a resumable session path (deps.invoke); none is wired on this surface" };
333
+ }
334
+ const asked = await asker.askSeat({ gig_id, role, question, max_turns });
335
+ // F1 — no conversation to ask: a TYPED refusal naming the gig and role, and NO fresh seat was
336
+ // spawned (the invoker returned this before any --session-id open). Returned, never thrown.
337
+ if ("session_lost" in asked) {
338
+ return {
339
+ ok: false, requires_approval: approval, refusal: "no_conversation",
340
+ error: `no conversation to ask: gig "${gig_id}" role "${role}" has no resumable seat session — it never ran or its session is gone. Refusing rather than spawning a fresh seat that would invent the reasoning.`,
341
+ };
342
+ }
343
+ // O1/F2 — the seat's own reasoning, verbatim (empty stays empty), stamped with the session it
344
+ // came from and marked a resume, not a fresh open.
345
+ return { ok: true, requires_approval: approval, data: { answer: asked.answer, session_id: asked.session_id, resumed: asked.resumed } };
346
+ }
321
347
  case "type_browse": {
322
348
  let types = deps.registry.listTypes();
323
349
  if (args["domain"])
@@ -3579,6 +3605,81 @@ async function callSurfaceTool(slug, args, deps) {
3579
3605
  // Hosted dispatch NEVER spawns. With a queue seam it queues (the gig table is the
3580
3606
  // queue; a drain worker claims and runs); without one it says so, typed.
3581
3607
  if (deps.queueGig) {
3608
+ // contract-post-time-gig-input-v1 — validate the payload BEFORE it is queued, in the SAME
3609
+ // words as runGig's preflight, so the caller who can still fix the key is the one who is told
3610
+ // (not the worker, later). Everything below the guard queues exactly as today; a throw on this
3611
+ // path would reach the caller as an opaque `tool failed`, so every refusal here is a typed JSON
3612
+ // reply and NEVER an exception.
3613
+ // DOT access on purpose (not a bracket-and-string index): the advertised_args_are_read parser
3614
+ // slurps the last dispatchTool case (venue_credential_mint) to end-of-file, and a bracket read
3615
+ // of a string-literal key here would be miscounted as that tool reading an arg it does not
3616
+ // advertise — this comment must not spell that index pattern out either, or the parser counts
3617
+ // the explanation as an instance of it. gig_dispatch advertises both keys; dot access keeps the
3618
+ // read where it belongs. (Same reason org_hire above reads its args by dot.)
3619
+ const input = args.input;
3620
+ const isPayloadObject = typeof input === "object" && input !== null && !Array.isArray(input);
3621
+ if (isPayloadObject) {
3622
+ const standard_slug = String(args.standard_slug ?? "");
3623
+ const payload = input;
3624
+ // (O4) declared inputs from deps.standards FIRST; only on a miss, exactly one narrow read of
3625
+ // the host-wired reader. The reader may throw or answer undefined — either means "unresolved".
3626
+ let declared;
3627
+ const held = deps.standards?.get(standard_slug);
3628
+ if (held) {
3629
+ declared = held.input_types ?? [];
3630
+ }
3631
+ else if (deps.declaredGigInputs) {
3632
+ try {
3633
+ declared = await deps.declaredGigInputs(standard_slug);
3634
+ }
3635
+ catch {
3636
+ declared = undefined; // a failed read is "could not check", never a guess or a refusal
3637
+ }
3638
+ }
3639
+ if (declared === undefined) {
3640
+ // (F1) neither source could supply the declared inputs. Do NOT guess and do NOT refuse:
3641
+ // queue as today and SAY the payload was not validated, so the reply never implies a check
3642
+ // it could not run. The preflight remains the backstop when the worker runs the gig.
3643
+ try {
3644
+ const data = await deps.queueGig(args);
3645
+ return {
3646
+ ok: true,
3647
+ data,
3648
+ input_validated: false,
3649
+ reason: `the payload was not validated at post time: standard "${standard_slug}" is not on this ` +
3650
+ `surface (deps.standards) and no declaredGigInputs reader resolved its declared inputs. ` +
3651
+ `The gig is queued and runGig's preflight validates it when the worker runs it.`,
3652
+ };
3653
+ }
3654
+ catch (e) {
3655
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
3656
+ }
3657
+ }
3658
+ // The SAME partition the preflight runs: a near miss refuses, other undeclared keys are named.
3659
+ const part = partitionGigInputKeys(Object.keys(payload), declared, (d) => payload[d] !== undefined);
3660
+ if (part.nearMiss !== undefined) {
3661
+ // (O1/O2) a typed refusal in the preflight's exact words — NOTHING is queued, nothing throws.
3662
+ return {
3663
+ ok: false,
3664
+ refusal: "unknown_gig_input",
3665
+ error: unknownGigInputMessage(part.nearMiss.key, part.nearMiss.declaredKey, standard_slug),
3666
+ };
3667
+ }
3668
+ // (O3/I2) no near miss: queue the UNCHANGED args, and name the harmless undeclared keys (sorted)
3669
+ // on the reply so the caller learns at post time what the engine will ignore. A clean payload
3670
+ // adds nothing — `undeclared_input_keys` is present only when there is at least one extra.
3671
+ try {
3672
+ const data = await deps.queueGig(args);
3673
+ return part.extras.length > 0
3674
+ ? { ok: true, data, undeclared_input_keys: part.extras }
3675
+ : { ok: true, data };
3676
+ }
3677
+ catch (e) {
3678
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
3679
+ }
3680
+ }
3681
+ // (F2) an absent or non-object payload is a shape the door already handles — no new refusal,
3682
+ // queued exactly as today.
3582
3683
  try {
3583
3684
  const data = await deps.queueGig(args);
3584
3685
  return { ok: true, data };