tickmarkr 2.6.1 → 2.6.2

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 (85) hide show
  1. package/README.md +14 -3
  2. package/dist/adapters/catalog-remote.js +89 -47
  3. package/dist/adapters/claude-code.js +9 -6
  4. package/dist/adapters/codex.js +7 -4
  5. package/dist/adapters/prompt.d.ts +1 -0
  6. package/dist/adapters/prompt.js +14 -6
  7. package/dist/adapters/registry.js +3 -3
  8. package/dist/adapters/types.d.ts +12 -4
  9. package/dist/adapters/types.js +6 -0
  10. package/dist/cli/commands/approve.d.ts +5 -1
  11. package/dist/cli/commands/approve.js +66 -23
  12. package/dist/cli/commands/compile.js +13 -3
  13. package/dist/cli/commands/doctor.d.ts +2 -0
  14. package/dist/cli/commands/doctor.js +11 -3
  15. package/dist/cli/commands/fleet.js +45 -7
  16. package/dist/cli/commands/report.js +18 -2
  17. package/dist/cli/commands/resume.js +4 -2
  18. package/dist/cli/commands/status.js +24 -19
  19. package/dist/cli/help.d.ts +2 -0
  20. package/dist/cli/help.js +9 -2
  21. package/dist/config/config.d.ts +20 -0
  22. package/dist/config/config.js +47 -8
  23. package/dist/config/fleet-overlay.d.ts +1 -0
  24. package/dist/config/fleet-overlay.js +56 -0
  25. package/dist/drivers/orca.d.ts +26 -1
  26. package/dist/drivers/orca.js +193 -60
  27. package/dist/eval/canary.d.ts +2 -1
  28. package/dist/eval/canary.js +2 -2
  29. package/dist/eval/dispatch.js +1 -0
  30. package/dist/gates/acceptance.d.ts +2 -1
  31. package/dist/gates/acceptance.js +7 -2
  32. package/dist/gates/baseline.d.ts +12 -1
  33. package/dist/gates/baseline.js +11 -4
  34. package/dist/gates/llm.d.ts +5 -4
  35. package/dist/gates/llm.js +13 -13
  36. package/dist/gates/review.d.ts +8 -0
  37. package/dist/gates/review.js +40 -4
  38. package/dist/gates/run-gates.d.ts +2 -1
  39. package/dist/gates/run-gates.js +27 -13
  40. package/dist/gates/test-manifest.d.ts +3 -1
  41. package/dist/gates/test-manifest.js +9 -2
  42. package/dist/graph/schema.d.ts +2 -0
  43. package/dist/graph/schema.js +2 -0
  44. package/dist/plan/scope.js +2 -2
  45. package/dist/route/preference.d.ts +20 -2
  46. package/dist/route/preference.js +48 -13
  47. package/dist/route/router.js +30 -15
  48. package/dist/run/consult.d.ts +13 -1
  49. package/dist/run/consult.js +14 -5
  50. package/dist/run/daemon.d.ts +37 -2
  51. package/dist/run/daemon.js +579 -141
  52. package/dist/run/git.d.ts +8 -0
  53. package/dist/run/git.js +14 -0
  54. package/dist/run/journal.d.ts +126 -3
  55. package/dist/run/journal.js +410 -37
  56. package/dist/run/merge.d.ts +3 -1
  57. package/dist/run/merge.js +3 -2
  58. package/dist/run/operator-summary.d.ts +3 -0
  59. package/dist/run/operator-summary.js +3 -1
  60. package/dist/run/protocol.d.ts +31 -1
  61. package/dist/run/protocol.js +3 -1
  62. package/dist/run/supervision.d.ts +7 -1
  63. package/dist/run/supervision.js +5 -2
  64. package/dist/tui/cockpit/board.js +3 -3
  65. package/dist/tui/cockpit/decision-actions.d.ts +8 -5
  66. package/dist/tui/cockpit/decision-actions.js +55 -32
  67. package/dist/tui/cockpit/derive.js +13 -2
  68. package/dist/tui/cockpit/live-runtime.d.ts +10 -0
  69. package/dist/tui/cockpit/live-runtime.js +50 -3
  70. package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
  71. package/dist/tui/cockpit/run-cockpit.js +26 -1
  72. package/dist/tui/cockpit/run-view.d.ts +9 -3
  73. package/dist/tui/cockpit/run-view.js +60 -7
  74. package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
  75. package/dist/tui/cockpit/setup-cockpit.js +6 -3
  76. package/dist/tui/ink/fleet-app.d.ts +15 -3
  77. package/dist/tui/ink/fleet-app.js +91 -22
  78. package/package.json +2 -1
  79. package/schema/config.schema.json +818 -0
  80. package/skills/tickmarkr-loop/SKILL.md +8 -2
  81. package/skills/tickmarkr-overseer/SKILL.md +42 -0
  82. package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
  83. package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
  84. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
  85. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
@@ -163,6 +163,8 @@ export declare const COMMAND_HELP: {
163
163
  usage: string;
164
164
  description: string;
165
165
  options: {
166
+ "--park <line>@<ts>": string;
167
+ "--gate <gate>": string;
166
168
  "--files <glob,\u2026>": string;
167
169
  "--by <name>": string;
168
170
  "--reason <text>": string;
package/dist/cli/help.js CHANGED
@@ -131,14 +131,21 @@ export const COMMAND_HELP = {
131
131
  approve: {
132
132
  usage: "approve <run-id> <task-id> [options]", description: "Append a validated park decision. A running owner may enact it; a closed run requires a separate resume. Decisions cannot be undone.",
133
133
  options: {
134
+ "--park <line>@<ts>": "Bind the decision to the park token status prints (a failed task's recheck binds its failure token); refused once a newer park opens.",
135
+ "--gate <gate>": "Bind a waive to the park's failed gate; refused when the park failed another gate.",
134
136
  "--files <glob,…>": "Extend files[] for a scope-request park with comma-separated repository-relative globs; required for scope approval.",
135
137
  "--by <name>": "Name the actor (default: current OS user).",
136
138
  "--reason <text>": "Record the decision reason.",
137
139
  "--waive": "Waive only the identified failed gate.",
138
140
  "--uphold": "Uphold a review failure and fund a fixed attempt.",
139
141
  "--recheck": "Request rechecking an infra or failed-gate park; satisfies no gate.",
140
- "--review-rounds <N>": "Set a positive integer review-round ceiling with the decision.",
141
- }, examples: ["approve run-example T1 --by operator --reason 'ready to proceed'", "approve run-example T1 --recheck --reason 'infra recovered'"],
142
+ "--review-rounds <N>": "Set a positive integer review-round ceiling that binds only this approval engagement; a later approval without --review-rounds restores the built-in default.",
143
+ }, examples: [
144
+ "approve run-example T1 --park 42@2026-09-26T08:00:00.000Z --by operator --reason 'ready to proceed'",
145
+ "approve run-example T1 --waive --park 42@2026-09-26T08:00:00.000Z --gate review",
146
+ "approve run-example T1 --recheck --park 42@2026-09-26T08:00:00.000Z --reason 'infra recovered'",
147
+ "approve run-example T3 --recheck --park 57@2026-09-26T08:05:00.000Z --reason 'failed task: re-gate its landed commits'",
148
+ ],
142
149
  },
143
150
  beat: {
144
151
  usage: "beat <orchestrator|orchestrator-context|overseer|overseer-context|watch> --seat <identity> [options]",
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type Effort } from "../graph/schema.js";
2
3
  declare const TierEnum: z.ZodEnum<{
3
4
  cheap: "cheap";
4
5
  mid: "mid";
@@ -41,6 +42,7 @@ export declare const MapEntrySchema: z.ZodObject<{
41
42
  escalate: z.ZodOptional<z.ZodBoolean>;
42
43
  }, z.core.$strip>;
43
44
  export type MapEntry = z.infer<typeof MapEntrySchema>;
45
+ export declare const EFFORT_ADAPTERS: readonly ["claude-code", "codex"];
44
46
  export declare const TierEntrySchema: z.ZodObject<{
45
47
  vendor: z.ZodNullable<z.ZodString>;
46
48
  channel: z.ZodEnum<{
@@ -58,6 +60,11 @@ export declare const TierEntrySchema: z.ZodObject<{
58
60
  sub: "sub";
59
61
  api: "api";
60
62
  }>>;
63
+ effort: z.ZodOptional<z.ZodEnum<{
64
+ low: "low";
65
+ medium: "medium";
66
+ high: "high";
67
+ }>>;
61
68
  }, z.core.$strip>>>;
62
69
  windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
63
70
  }, z.core.$strip>;
@@ -274,6 +281,11 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
274
281
  sub: "sub";
275
282
  api: "api";
276
283
  }>>;
284
+ effort: z.ZodOptional<z.ZodEnum<{
285
+ low: "low";
286
+ medium: "medium";
287
+ high: "high";
288
+ }>>;
277
289
  }, z.core.$strip>>>;
278
290
  windows: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
279
291
  }, z.core.$strip>>;
@@ -383,6 +395,12 @@ export declare function loadConfigWithMode(repoRoot: string, opts?: {
383
395
  cfg: TickmarkrConfig;
384
396
  mode: ModeResolution;
385
397
  };
398
+ /** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay (defaults + global)
399
+ * merge them. Read raw, never schema-validated: a lower layer may only validate beside repo fields
400
+ * (global `vendor: null` completed by a repo vendor) and its override metadata is still inherited. */
401
+ export declare function lowerLayerModelOverrides(opts?: {
402
+ globalDir?: string;
403
+ }): Record<string, Record<string, Record<string, unknown>>>;
386
404
  export declare function loadConfig(repoRoot: string, opts?: {
387
405
  globalDir?: string;
388
406
  }): TickmarkrConfig;
@@ -416,6 +434,8 @@ export type FleetEditable = Partial<Record<DenyScopeKey, string[]>> & {
416
434
  denyModels: string[];
417
435
  allowOut?: string[];
418
436
  tiers: Record<string, Record<string, FleetTierAssignment | null>>;
437
+ /** OBS-1182: tiers.<adapter>.modelOverrides.<model>.effort — absent = CLI default; present only when one is set */
438
+ efforts?: Record<string, Record<string, Effort>>;
419
439
  map: Record<string, MapEntry>;
420
440
  floors: Record<string, Tier>;
421
441
  };
@@ -2,11 +2,11 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { filesGlob } from "../graph/files-glob.js";
5
- import { Document, parse } from "yaml";
5
+ import { Document, isScalar, parse } from "yaml";
6
6
  import { z } from "zod";
7
7
  import { CITED_MODEL_WINDOWS } from "../adapters/model-windows.js";
8
8
  import { stateDirName } from "../graph/graph.js";
9
- import { SHAPES, TIERS } from "../graph/schema.js";
9
+ import { EFFORTS, SHAPES, TIERS } from "../graph/schema.js";
10
10
  const TierEnum = z.enum(TIERS, {
11
11
  error: (iss) => `Invalid option: expected one of "cheap"|"mid"|"frontier" (got ${JSON.stringify(iss.input)})`,
12
12
  });
@@ -51,21 +51,32 @@ export const MapEntrySchema = z.object({
51
51
  escalate: z.boolean().optional(),
52
52
  });
53
53
  const DeclaredVendorSchema = z.string().trim().min(1, "vendor must be nonempty");
54
- const ModelOverrideSchema = z
54
+ // OBS-1182: effort is launch metadata only these CLIs accept (claude --effort, codex -c
55
+ // model_reasoning_effort=). Every other adapter's override refuses the key outright, so config
56
+ // load and the emitted JSON schema both reject an effort that could never be launched.
57
+ export const EFFORT_ADAPTERS = ["claude-code", "codex"];
58
+ const EffortEnum = z.enum(EFFORTS, {
59
+ error: (iss) => `Invalid effort: expected one of "low"|"medium"|"high" (got ${JSON.stringify(iss.input)})`,
60
+ });
61
+ const NoEffort = z.never({
62
+ error: () => `effort is launch metadata only ${EFFORT_ADAPTERS.join(" and ")} accept — this adapter's CLI takes no effort level`,
63
+ });
64
+ const modelOverrideSchema = (effort) => z
55
65
  .object({
56
66
  vendor: DeclaredVendorSchema.optional(),
57
67
  channel: z.enum(["sub", "api"]).optional(),
68
+ effort: effort.optional(),
58
69
  })
59
- .refine((override) => override.vendor !== undefined || override.channel !== undefined, {
60
- message: "a model override must declare vendor, channel, or both",
70
+ .refine((override) => override.vendor !== undefined || override.channel !== undefined || override.effort !== undefined, {
71
+ message: "a model override must declare vendor, channel, effort, or a combination",
61
72
  });
62
- export const TierEntrySchema = z.object({
73
+ const tierEntrySchema = (effort) => z.object({
63
74
  vendor: DeclaredVendorSchema.nullable(),
64
75
  channel: z.enum(["sub", "api"]),
65
76
  models: z.record(z.string(), TierEnum),
66
77
  // T19: keep model values scalar for existing consumers. Optional sibling metadata resolves before
67
78
  // parent metadata in channelsFromConfig; neither provider prefixes nor adapter ids manufacture it.
68
- modelOverrides: z.record(z.string(), ModelOverrideSchema).optional(),
79
+ modelOverrides: z.record(z.string(), modelOverrideSchema(effort)).optional(),
69
80
  // v1.47 T3: optional per-model context-window sizes (tokens). Absent block ⇒ no doctor column, no plan lint.
70
81
  windows: z.record(z.string(), z.number().int().positive()).optional(),
71
82
  }).superRefine((entry, ctx) => {
@@ -81,6 +92,13 @@ export const TierEntrySchema = z.object({
81
92
  });
82
93
  }
83
94
  });
95
+ export const TierEntrySchema = tierEntrySchema(EffortEnum);
96
+ // Per-adapter object + catchall rather than a record, so the emitted JSON schema carries the same
97
+ // adapter restriction as the loader; typed as the record every consumer already reads.
98
+ // ponytail: catchall parses the EFFORT_ADAPTERS keys first — the seed table already lists them first.
99
+ const TiersSchema = z
100
+ .object(Object.fromEntries(EFFORT_ADAPTERS.map((id) => [id, TierEntrySchema.optional()])))
101
+ .catchall(tierEntrySchema(NoEffort));
84
102
  // v1.10 FLEET-06: optional routing.allow/deny fleet preference; absent blocks ⇒ byte-identical routing/discovery.
85
103
  // deny wins on conflict; presence of allow (even {} / empty arrays) activates allowlist (fail-closed).
86
104
  const PrefBlockSchema = z.object({
@@ -372,7 +390,7 @@ export const TickmarkrConfigSchema = z.object({
372
390
  allow: PrefBlockSchema.optional(),
373
391
  deny: DenyBlockSchema.optional(),
374
392
  }),
375
- tiers: z.record(z.string(), TierEntrySchema),
393
+ tiers: TiersSchema,
376
394
  pricing: z.record(z.string(), z.number()),
377
395
  // v1.20 REC-02: optional detailed price table for cost estimation. Distinct from `pricing` above
378
396
  // (the coarse per-task tier estimate `tickmarkr plan` shows) — this one drives the usage/cost report.
@@ -798,6 +816,15 @@ export function loadConfigWithMode(repoRoot, opts = {}) {
798
816
  assertSeededModelWindows();
799
817
  return { cfg: r.data, mode: resolveRoutingMode(r.data, [globalCfg, repoCfg]) };
800
818
  }
819
+ /** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay (defaults + global)
820
+ * merge them. Read raw, never schema-validated: a lower layer may only validate beside repo fields
821
+ * (global `vendor: null` completed by a repo vendor) and its override metadata is still inherited. */
822
+ export function lowerLayerModelOverrides(opts = {}) {
823
+ const isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
824
+ const merged = deepMerge(structuredClone(DEFAULT_CONFIG), readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml")));
825
+ const tiers = isMap(merged) && isMap(merged.tiers) ? merged.tiers : {};
826
+ return Object.fromEntries(Object.entries(tiers).map(([adapter, entry]) => [adapter, Object.fromEntries(Object.entries(isMap(entry) && isMap(entry.modelOverrides) ? entry.modelOverrides : {}).filter(([, o]) => isMap(o)))]));
827
+ }
801
828
  export function loadConfig(repoRoot, opts = {}) {
802
829
  return loadConfigWithMode(repoRoot, opts).cfg;
803
830
  }
@@ -959,10 +986,14 @@ export function universeCovers(universe, entry) {
959
986
  // Absent universe ⇒ deny arrays verbatim and no allowOut, so non-UI callers stay byte-identical.
960
987
  export function fleetEditableFromConfig(cfg, universe) {
961
988
  const tiers = {};
989
+ const efforts = {};
962
990
  for (const [adapter, entry] of Object.entries(cfg.tiers)) {
963
991
  tiers[adapter] = {};
964
992
  for (const [model, tier] of Object.entries(entry.models)) {
965
993
  tiers[adapter][model] = { tier };
994
+ const effort = entry.modelOverrides?.[model]?.effort;
995
+ if (effort)
996
+ (efforts[adapter] ??= {})[model] = effort;
966
997
  }
967
998
  }
968
999
  // every enumerated deny scope rides verbatim (sorted) under its own key
@@ -991,6 +1022,7 @@ export function fleetEditableFromConfig(cfg, universe) {
991
1022
  ...denyLists,
992
1023
  ...(allowOut !== undefined ? { allowOut } : {}),
993
1024
  tiers,
1025
+ ...(Object.keys(efforts).length ? { efforts } : {}),
994
1026
  map: structuredClone(cfg.routing.map),
995
1027
  floors: { ...cfg.routing.floors },
996
1028
  };
@@ -1037,6 +1069,13 @@ export function formatFleetPrint(repoRoot, opts = {}) {
1037
1069
  for (const [adapter, entry] of Object.entries(effective.tiers)) {
1038
1070
  for (const model of Object.keys(entry.models))
1039
1071
  annotate(["tiers", adapter, "models", model]);
1072
+ // OBS-1182: the effort value prints in place; its note names the layer that set it and the edit
1073
+ for (const [model, override] of Object.entries(entry.modelOverrides ?? {})) {
1074
+ const path = ["tiers", adapter, "modelOverrides", model, "effort"];
1075
+ const node = doc.getIn(path, true);
1076
+ if (override.effort && isScalar(node))
1077
+ node.comment = ` ${fleetKeyLayer(repoRoot, path.join("."), opts)} — fleet e edits`;
1078
+ }
1040
1079
  }
1041
1080
  return String(doc);
1042
1081
  }
@@ -20,6 +20,7 @@ export type FleetOverlayWrite = {
20
20
  };
21
21
  };
22
22
  universe?: FleetUniverseRow[];
23
+ lowerOverrides?: Record<string, Record<string, Record<string, unknown>>>;
23
24
  };
24
25
  export type FleetFirstTouch = {
25
26
  vendor: string;
@@ -360,6 +360,62 @@ export function renderFleetOverlayWrite(priorBytes, write) {
360
360
  setScalarPreservingComment(doc, path, after.tier, unpackFleetProvenance(after.provenance).provenance);
361
361
  }
362
362
  }
363
+ // OBS-1182: effort lands in tiers.<adapter>.modelOverrides.<model>.effort — the scalar tier and
364
+ // every sibling override key (vendor, channel) keep their bytes.
365
+ const efforts = (e) => e.efforts ?? {};
366
+ for (const adapter of new Set([...Object.keys(efforts(initial)), ...Object.keys(efforts(edited))])) {
367
+ for (const model of new Set([...Object.keys(efforts(initial)[adapter] ?? {}), ...Object.keys(efforts(edited)[adapter] ?? {})])) {
368
+ const after = efforts(edited)[adapter]?.[model];
369
+ if (efforts(initial)[adapter]?.[model] === after)
370
+ continue;
371
+ const override = ["tiers", adapter, "modelOverrides", model];
372
+ if (after !== undefined) {
373
+ // Setting beneath a tombstone (the override, or modelOverrides above it) lifts it, and
374
+ // deepMerge would restore what it masked: re-mask each lower-layer key it covered — sibling
375
+ // models, then this override's vendor/channel (unknown lower ⇒ both, blind).
376
+ // ponytail: tiers.<adapter> itself tombstoned is unreachable here — its models, and so a tier, are masked too.
377
+ const tombstoned = (depth) => {
378
+ const node = doc.getIn(override.slice(0, depth), true);
379
+ return node !== undefined && !isMap(node);
380
+ };
381
+ const lowerModels = write.lowerOverrides?.[adapter] ?? {};
382
+ const remask = [];
383
+ if (tombstoned(3))
384
+ for (const other of Object.keys(lowerModels))
385
+ if (other !== model)
386
+ remask.push([...override.slice(0, 3), other]);
387
+ if (tombstoned(3) || tombstoned(4)) {
388
+ for (const key of write.lowerOverrides ? Object.keys(lowerModels[model] ?? {}) : ["vendor", "channel"]) {
389
+ if (key !== "effort")
390
+ remask.push([...override, key]);
391
+ }
392
+ }
393
+ setScalarPreservingComment(doc, [...override, "effort"], after);
394
+ for (const path of remask)
395
+ setScalarPreservingComment(doc, path, null);
396
+ continue;
397
+ }
398
+ // Clearing means no effort in the MERGED config: drop the repo key, then mask an effort the
399
+ // lower layers still declare (unknown lower ⇒ a repo that never held the key inherited it).
400
+ const held = doc.getIn([...override, "effort"]) !== undefined;
401
+ deleteAt(doc, [...override, "effort"]);
402
+ const lower = write.lowerOverrides?.[adapter]?.[model];
403
+ const maskEffort = write.lowerOverrides ? lower?.effort !== undefined : !held;
404
+ const repoNode = doc.getIn(override, true);
405
+ const { effort: _masked, ...rest } = { ...lower, ...(isMap(repoNode) ? repoNode.toJSON() : {}) };
406
+ if (Object.values(rest).some((v) => v !== null && v !== undefined)) {
407
+ if (maskEffort)
408
+ setScalarPreservingComment(doc, [...override, "effort"], null);
409
+ }
410
+ else if (maskEffort || (isMap(repoNode) && repoNode.items.length > 0)) {
411
+ // Tombstone-only siblings must still suppress lower metadata when effort is cleared,
412
+ // even without inherited effort. Their merged {} would fail the nonempty refinement.
413
+ setScalarPreservingComment(doc, override, null);
414
+ }
415
+ for (const depth of [4, 3, 2, 1])
416
+ deleteEmptyMap(doc, override.slice(0, depth));
417
+ }
418
+ }
363
419
  if (write.mode !== undefined) {
364
420
  setScalarPreservingComment(doc, ["routing", "mode"], write.mode);
365
421
  }
@@ -123,12 +123,13 @@ export declare function mapAgentState(term: Record<string, unknown>, tuiIdle: bo
123
123
  * A retired record is replaced by a new reservation (CAS on the tombstone bytes) only once its pane
124
124
  * is proven gone. Everything else — a reservation or claim with no bound pane, a failed cleanup, a
125
125
  * record another driver holds with a live observer — refuses and keeps the record exactly as it is.
126
+ * One reservation or claim is HELD rather than in flight: its placer is this process after an
127
+ * indeterminate split receipt (OBS-1172). It is reconciled again on every call, never split over.
126
128
  */
127
129
  /** `placer` is the pid of the narrator that wrote the reservation: a reserved or claimed record is a
128
130
  * normal intermediate state while that pid lives (another driver awaiting listing or bind) and a
129
131
  * crash to recover only once it is dead. */
130
132
  type BoardRecord = WatchBoardOwner & {
131
- retired?: true;
132
133
  runtimeId?: string;
133
134
  placer?: number;
134
135
  };
@@ -198,6 +199,16 @@ export declare class OrcaDriver implements ExecutorDriver {
198
199
  /** Explicit runtime probe. Also T3's doctor probe. */
199
200
  probeRuntime(cwd?: string): Promise<string>;
200
201
  slot(cwd: string, name: string, opts?: SlotOpts): Promise<Slot>;
202
+ /**
203
+ * OBS-1109: bind THIS instance to a terminal an earlier daemon created for an OWNED slot, so a
204
+ * resumed daemon can read the interrupted attempt's pane. Binding needs ownership evidence, never
205
+ * a title or handle alone: exactly one tab on the tracked worktree carries the FULL journaled owned
206
+ * title, it resolves to exactly one terminal, and that terminal's scrollback proves the exact task
207
+ * checkout `slot.cwd` (the frame only tickmarkr's own create command prints). Zero, ambiguous or
208
+ * foreign candidates leave the slot unbound: the throw is the caller's decline. The journaled
209
+ * nonce is then checked by the caller against the trailer this binding reads.
210
+ */
211
+ adopt(slot: Slot): Promise<Slot>;
201
212
  /** Where to invoke the CLI for this slot's calls (see OrcaSlotState.dir). */
202
213
  private cliCwd;
203
214
  private state;
@@ -290,6 +301,15 @@ export declare class OrcaDriver implements ExecutorDriver {
290
301
  sendKey(slot: Slot, key: string): Promise<void>;
291
302
  nudge(slot: Slot, message: string): Promise<boolean>;
292
303
  narrator(cwd: string, command: string, runId?: string): Promise<Slot>;
304
+ /** OBS-1172: what an indeterminate split left behind, reconciled from the owner record and Orca's
305
+ * terminal table — never from the receipt that did not arrive. The reservation's token reaches no
306
+ * process but the split's own command, so a claim proves that split started a board; the owned
307
+ * record (this driver, the watch title) is ours by that same token. The claimant must be live (pid
308
+ * and arm presence), and it must name the terminal it runs in — a listed pane in the launching tab.
309
+ * A claim that names none proves a board started, not WHICH pane hosts it: a sole neighbouring pane
310
+ * (row titles are shell-controlled, never ownership keys) is not adopted, so the claim stays held.
311
+ * No claim and no such pane: the split started nothing the table shows. Anything else is held. */
312
+ private settleIndeterminate;
293
313
  private boardPath;
294
314
  /** A recorded pane (a receipt's handle bound to the split envelope's runtime, never a guess) is
295
315
  * gone when that runtime no longer lists it, or when a handle-bound close receipt names it.
@@ -303,6 +323,11 @@ export declare class OrcaDriver implements ExecutorDriver {
303
323
  * handle-bound close (timeout keeps the tombstone and the pane); a dead one never acknowledges,
304
324
  * so it is only asked. */
305
325
  private retireAndClose;
326
+ /** Stop a retired board's observer before its pane is closed. A live observer acknowledges on
327
+ * injected time; a dead one never can, so it is only asked. OBS-1131: an acknowledgement timeout
328
+ * is not proof of death — while the pid lives the owned pane stays protected — but a pid that died
329
+ * during the wait is, and its pane is closed instead of leaked. */
330
+ private stopObserver;
306
331
  /** WB-1 seam: the daemon reports this board lost. "Lost" can be a stale beat or missing presence
307
332
  * under a still-live owner pid, so it is not proof of a dead observer — retirement keeps close's
308
333
  * acknowledgement discipline (Leg-2 T9 P1). */