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.
- package/README.md +14 -3
- package/dist/adapters/catalog-remote.js +89 -47
- package/dist/adapters/claude-code.js +9 -6
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/prompt.d.ts +1 -0
- package/dist/adapters/prompt.js +14 -6
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +12 -4
- package/dist/adapters/types.js +6 -0
- package/dist/cli/commands/approve.d.ts +5 -1
- package/dist/cli/commands/approve.js +66 -23
- package/dist/cli/commands/compile.js +13 -3
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +11 -3
- package/dist/cli/commands/fleet.js +45 -7
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +24 -19
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +9 -2
- package/dist/config/config.d.ts +20 -0
- package/dist/config/config.js +47 -8
- package/dist/config/fleet-overlay.d.ts +1 -0
- package/dist/config/fleet-overlay.js +56 -0
- package/dist/drivers/orca.d.ts +26 -1
- package/dist/drivers/orca.js +193 -60
- package/dist/eval/canary.d.ts +2 -1
- package/dist/eval/canary.js +2 -2
- package/dist/eval/dispatch.js +1 -0
- package/dist/gates/acceptance.d.ts +2 -1
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +12 -1
- package/dist/gates/baseline.js +11 -4
- package/dist/gates/llm.d.ts +5 -4
- package/dist/gates/llm.js +13 -13
- package/dist/gates/review.d.ts +8 -0
- package/dist/gates/review.js +40 -4
- package/dist/gates/run-gates.d.ts +2 -1
- package/dist/gates/run-gates.js +27 -13
- package/dist/gates/test-manifest.d.ts +3 -1
- package/dist/gates/test-manifest.js +9 -2
- package/dist/graph/schema.d.ts +2 -0
- package/dist/graph/schema.js +2 -0
- package/dist/plan/scope.js +2 -2
- package/dist/route/preference.d.ts +20 -2
- package/dist/route/preference.js +48 -13
- package/dist/route/router.js +30 -15
- package/dist/run/consult.d.ts +13 -1
- package/dist/run/consult.js +14 -5
- package/dist/run/daemon.d.ts +37 -2
- package/dist/run/daemon.js +579 -141
- package/dist/run/git.d.ts +8 -0
- package/dist/run/git.js +14 -0
- package/dist/run/journal.d.ts +126 -3
- package/dist/run/journal.js +410 -37
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- package/dist/run/operator-summary.d.ts +3 -0
- package/dist/run/operator-summary.js +3 -1
- package/dist/run/protocol.d.ts +31 -1
- package/dist/run/protocol.js +3 -1
- package/dist/run/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/tui/cockpit/board.js +3 -3
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.js +13 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +26 -1
- package/dist/tui/cockpit/run-view.d.ts +9 -3
- package/dist/tui/cockpit/run-view.js +60 -7
- package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
- package/dist/tui/cockpit/setup-cockpit.js +6 -3
- package/dist/tui/ink/fleet-app.d.ts +15 -3
- package/dist/tui/ink/fleet-app.js +91 -22
- package/package.json +2 -1
- package/schema/config.schema.json +818 -0
- package/skills/tickmarkr-loop/SKILL.md +8 -2
- package/skills/tickmarkr-overseer/SKILL.md +42 -0
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
- package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
package/dist/cli/help.d.ts
CHANGED
|
@@ -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
|
|
141
|
-
}, examples: [
|
|
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]",
|
package/dist/config/config.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/config/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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(),
|
|
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:
|
|
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
|
}
|
|
@@ -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
|
}
|
package/dist/drivers/orca.d.ts
CHANGED
|
@@ -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). */
|