tickmarkr 2.6.0 → 2.6.1

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 (47) hide show
  1. package/dist/adapters/prompt.d.ts +2 -1
  2. package/dist/adapters/prompt.js +10 -0
  3. package/dist/adapters/types.d.ts +1 -0
  4. package/dist/adapters/types.js +10 -0
  5. package/dist/cli/commands/fleet.js +26 -1
  6. package/dist/cli/commands/plan.js +7 -3
  7. package/dist/cli/commands/status.js +13 -3
  8. package/dist/cli/help.d.ts +2 -0
  9. package/dist/cli/help.js +2 -0
  10. package/dist/drivers/orca.js +6 -1
  11. package/dist/gates/cache.d.ts +3 -1
  12. package/dist/gates/cache.js +10 -3
  13. package/dist/gates/llm.js +4 -1
  14. package/dist/gates/review.js +7 -11
  15. package/dist/gates/run-gates.d.ts +4 -0
  16. package/dist/gates/run-gates.js +7 -4
  17. package/dist/gates/test-manifest.d.ts +12 -0
  18. package/dist/gates/test-manifest.js +26 -6
  19. package/dist/graph/graph.d.ts +6 -2
  20. package/dist/graph/graph.js +15 -4
  21. package/dist/route/role-pick.d.ts +16 -0
  22. package/dist/route/role-pick.js +15 -0
  23. package/dist/route/router.d.ts +14 -0
  24. package/dist/route/router.js +9 -1
  25. package/dist/run/consult.js +5 -9
  26. package/dist/run/daemon.d.ts +9 -0
  27. package/dist/run/daemon.js +395 -59
  28. package/dist/run/git.d.ts +36 -1
  29. package/dist/run/git.js +89 -5
  30. package/dist/run/host-health.d.ts +20 -0
  31. package/dist/run/host-health.js +64 -0
  32. package/dist/run/journal.js +9 -0
  33. package/dist/run/operator-state.d.ts +24 -2
  34. package/dist/run/operator-state.js +41 -5
  35. package/dist/run/stall.d.ts +38 -2
  36. package/dist/run/stall.js +276 -6
  37. package/dist/tui/cockpit/board.d.ts +1 -1
  38. package/dist/tui/cockpit/board.js +27 -19
  39. package/dist/tui/cockpit/derive.d.ts +2 -0
  40. package/dist/tui/cockpit/derive.js +4 -0
  41. package/dist/tui/cockpit/live-store.d.ts +1 -0
  42. package/dist/tui/cockpit/live-store.js +31 -8
  43. package/dist/tui/cockpit/run-cockpit.js +2 -1
  44. package/dist/tui/cockpit/run-view.d.ts +2 -4
  45. package/dist/tui/cockpit/run-view.js +9 -8
  46. package/package.json +1 -1
  47. package/skills/tickmarkr-overseer/SKILL.md +43 -6
@@ -0,0 +1,15 @@
1
+ import { discoverChannels } from "../adapters/registry.js";
2
+ import { rankPreferredChannels, reviewPreferenceTieBreak } from "./router.js";
3
+ export { preferIndex, rankPreferredChannels, reviewPreferenceTieBreak } from "./router.js";
4
+ /** Strict role selection: no task, author, pin, or default seat is invented. */
5
+ export function pickRole(role, cfg, adapters, health, opts = {}) {
6
+ // Worker preferences belong to shapes; judge currently has only an explicit pin.
7
+ const prefer = role === "review" ? cfg.review.prefer : role === "consult" ? cfg.consult.prefer : undefined;
8
+ if (!prefer?.length)
9
+ return { ok: false, reason: "missing-prefer" };
10
+ const pool = discoverChannels(cfg, adapters, health, role).filter((c) => !opts.excludeVendors?.has(c.vendor) && (opts.eligible?.(c) ?? true));
11
+ const channel = rankPreferredChannels(pool, prefer, {
12
+ tieBreak: role === "review" ? reviewPreferenceTieBreak : undefined,
13
+ })[0];
14
+ return channel ? { ok: true, channel } : { ok: false, reason: "no-eligible-preferred-channel" };
15
+ }
@@ -31,6 +31,19 @@ export declare class RoutingError extends Error {
31
31
  constructor(msg: string);
32
32
  }
33
33
  export declare function marginalCostRank(c: BillingChannel): number;
34
+ type ChannelSeat = {
35
+ adapter: string;
36
+ model: string;
37
+ };
38
+ /** Shared adapter | adapter:model grammar; unmatched channels follow every declared entry. */
39
+ export declare function preferIndex(c: ChannelSeat, prefer?: readonly string[]): number;
40
+ /** Rank an already eligible pool. Review alone retains its legacy unpreferred tail. */
41
+ export declare function rankPreferredChannels<C extends ChannelSeat>(channels: readonly C[], prefer?: readonly string[], opts?: {
42
+ includeUnpreferred?: boolean;
43
+ tieBreak?: (a: C, b: C) => number;
44
+ }): C[];
45
+ /** Review preserves its established tier-then-cost order inside a preference band. */
46
+ export declare const reviewPreferenceTieBreak: (a: BillingChannel, b: BillingChannel) => number;
34
47
  export declare function route(task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], profile?: RoutingProfile, preferCtx?: RoutingPreferContext, exclude?: ReadonlySet<string>, exploreCtx?: ExploreContext): Route;
35
48
  export declare function nextChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): Assignment | null;
36
49
  export type ClimbPick = Assignment & {
@@ -46,3 +59,4 @@ export type ClimbResult = ClimbPick;
46
59
  * returns nextChannel's same-or-higher pick flagged climbed: false naming the reason.
47
60
  */
48
61
  export declare function climbChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): ClimbPick | null;
62
+ export {};
@@ -52,10 +52,18 @@ function resolvePin(pin, channels) {
52
52
  }
53
53
  return c;
54
54
  }
55
- function preferIndex(c, prefer = []) {
55
+ /** Shared adapter | adapter:model grammar; unmatched channels follow every declared entry. */
56
+ export function preferIndex(c, prefer = []) {
56
57
  const i = prefer.findIndex((p) => p === c.adapter || p === channelKey(c));
57
58
  return i === -1 ? prefer.length : i;
58
59
  }
60
+ /** Rank an already eligible pool. Review alone retains its legacy unpreferred tail. */
61
+ export function rankPreferredChannels(channels, prefer = [], opts = {}) {
62
+ return channels.filter((c) => opts.includeUnpreferred || preferIndex(c, prefer) < prefer.length)
63
+ .sort((a, b) => preferIndex(a, prefer) - preferIndex(b, prefer) || opts.tieBreak?.(a, b) || 0);
64
+ }
65
+ /** Review preserves its established tier-then-cost order inside a preference band. */
66
+ export const reviewPreferenceTieBreak = (a, b) => TIER_RANK[b.tier] - TIER_RANK[a.tier] || Number(a.channel === "api") - Number(b.channel === "api");
59
67
  function ladderFor(task, entry) {
60
68
  const escalate = task.routingHints?.escalate ?? entry?.escalate ?? true;
61
69
  return escalate ? ["retry", "escalate", "consult", "human"] : ["retry", "consult", "human"];
@@ -4,6 +4,7 @@ import { getAdapter } from "../adapters/registry.js";
4
4
  import { bannerShell, paneDispatchCommand } from "../brand.js";
5
5
  import { dewrapPaneVerdict, extractVerdictJson, gateExitTrailer, gatePaneName, generateVerdictNonce, verdictNonceLine } from "../gates/llm.js";
6
6
  import { classifyVerdictCause } from "../gates/verdict-cause.js";
7
+ import { rankPreferredChannels } from "../route/role-pick.js";
7
8
  import { disallowedBy, routingModelProvider } from "../route/preference.js";
8
9
  import { sh } from "./git.js";
9
10
  import { redactSecrets } from "./redact.js";
@@ -212,15 +213,10 @@ opts = {}) {
212
213
  }
213
214
  return parsed;
214
215
  };
215
- // v1.54 T1: ranked seat failover. Walk consult.prefer (adapter:model entries) to the first entry
216
- // whose adapter is in the live channel set; a failed seat or unparseable verdict falls to the next;
217
- // the pinned consult.adapter/model is always the final seat. No channels provided (non-daemon
218
- // callers) ⇒ empty live set ⇒ pin only, byte-identical to pre-v1.54 behavior. Failover changes
219
- // only WHICH seat answers — per-seat parsing and the fail-safe human action below are untouched.
220
- const live = new Set((opts.channels ?? []).map((c) => c.adapter));
221
- const seats = (cfg.consult.prefer ?? [])
222
- .map((entry) => ({ adapter: entry.slice(0, entry.indexOf(":")), model: entry.slice(entry.indexOf(":") + 1) }))
223
- .filter((s) => live.has(s.adapter));
216
+ // Rank only exact live models from the daemon's role-scoped discovery pool.
217
+ // The explicit pin remains a caller-owned fallback, outside strict preference selection.
218
+ const live = (opts.channels ?? []).filter((c) => typeof c.model === "string");
219
+ const seats = rankPreferredChannels(live.filter((c) => disallowedBy(c, cfg.routing, "consult") === null), cfg.consult.prefer);
224
220
  seats.push({ adapter: cfg.consult.adapter, model: cfg.consult.model });
225
221
  // v1.87 T2: a consult seat reads the code, so it passes through the operator's policy exactly like
226
222
  // a worker does. The filter runs AFTER the pin is pushed, so the final pinned seat is checked by
@@ -5,6 +5,7 @@ import { type ExecutorDriver, type Slot } from "../drivers/types.js";
5
5
  import { type Baseline } from "../gates/baseline.js";
6
6
  import type { GateResult } from "../gates/types.js";
7
7
  import { type RunGraph } from "../graph/schema.js";
8
+ import { type PreserveProducer } from "./git.js";
8
9
  import { Journal, type JournalEvent } from "./journal.js";
9
10
  export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
10
11
  /** Optional authoritative transport receipt. Screen text can prove execution, never nonacceptance. */
@@ -67,6 +68,7 @@ export interface RunSummary {
67
68
  /** the accepted approvals that never reached a dispatch — named, never left to the park buckets */
68
69
  outstandingApprovals?: string[];
69
70
  }
71
+ export declare function pendingDaemonApprovalActions(events: JournalEvent[]): Map<string, import("./journal.js").PendingApprovalAction>;
70
72
  /**
71
73
  * T14, amended by v2.2 T3: approvals the run accepted and never acted on. `approved` above is still
72
74
  * built ONCE at startup — replay determinism depends on it — but a live approval is no longer inert:
@@ -126,6 +128,9 @@ export declare const APPROVAL_POLL_MS = 250;
126
128
  export declare const APPROVAL_WINDOW_MS = 120000;
127
129
  export declare const setApprovalWindowForTests: (ms: number) => void;
128
130
  export declare const resetApprovalWindowForTests: () => void;
131
+ /** Test seam — shrink the capacity backoff without minute-long sleeps. */
132
+ export declare function setCapacityBackoffMsForTests(ms: number): void;
133
+ export declare function resetCapacityBackoffMsForTests(): void;
129
134
  export declare const EARLY_LAUNCH_LIVENESS_MS = 60000;
130
135
  /** Test seam — lowers the empty-pane liveness window without sleeping 60s per case. */
131
136
  export declare function setEarlyLaunchLivenessMsForTests(ms: number): void;
@@ -218,5 +223,9 @@ export declare function liveSuiteCount(repoRoot: string): Promise<number>;
218
223
  /** Test seam — exercise the production observer's total read bound with a small real tree. */
219
224
  export declare function setObserveBudgetBytesForTests(bytes: number): void;
220
225
  export declare function resetObserveBudgetBytesForTests(): void;
226
+ /** The attempt whose worker last launched into the task's current checkout; "unknown" once a
227
+ * recreation replaced that tree without a new launch (recheck, gate-only restore) or when no
228
+ * dispatch assignment is on record. Never the newest dispatch by itself. */
229
+ export declare function knownProducer(events: readonly JournalEvent[], taskId: string): PreserveProducer;
221
230
  export declare function recordFatalRunEnd(journal: Journal, runId: string, branch: string, err: unknown, graph?: RunGraph, phase?: string): TipProof | undefined;
222
231
  export declare function runDaemon(repoRoot: string, opts?: RunOptions): Promise<RunSummary>;