tickmarkr 2.6.7 → 2.6.9
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/adapters/catalog-remote.js +5 -2
- package/dist/adapters/codex.d.ts +1 -0
- package/dist/adapters/codex.js +18 -10
- package/dist/adapters/model-lints.js +21 -11
- package/dist/adapters/model-windows.js +0 -1
- package/dist/adapters/registry.js +7 -3
- package/dist/adapters/types.d.ts +2 -0
- package/dist/cli/commands/beat.d.ts +3 -3
- package/dist/cli/commands/beat.js +39 -19
- package/dist/cli/commands/doctor.js +3 -0
- package/dist/cli/commands/fleet.js +2 -0
- package/dist/cli/commands/plan.js +16 -18
- package/dist/cli/commands/verify.js +4 -2
- package/dist/cli/help.js +5 -2
- package/dist/config/config.js +6 -5
- package/dist/drivers/herdr.js +2 -2
- package/dist/drivers/orca.d.ts +12 -4
- package/dist/drivers/orca.js +200 -39
- package/dist/gates/evidence.js +3 -3
- package/dist/gates/review.d.ts +1 -1
- package/dist/gates/review.js +62 -13
- package/dist/gates/run-gates.d.ts +3 -0
- package/dist/gates/run-gates.js +115 -20
- package/dist/gates/test-manifest.d.ts +36 -2
- package/dist/gates/test-manifest.js +55 -10
- package/dist/run/beat-lifecycle.d.ts +109 -24
- package/dist/run/beat-lifecycle.js +1173 -216
- package/dist/run/daemon.d.ts +4 -0
- package/dist/run/daemon.js +111 -33
- package/dist/run/git-trust.d.ts +78 -3
- package/dist/run/git-trust.js +175 -19
- package/dist/run/git.d.ts +24 -3
- package/dist/run/git.js +140 -21
- package/dist/run/merge.js +2 -2
- package/dist/run/verification-job.d.ts +2 -0
- package/dist/run/verification-job.js +32 -10
- package/dist/tui/ink/fleet-app.d.ts +7 -2
- package/dist/tui/ink/fleet-app.js +81 -23
- package/package.json +4 -1
- package/skills/tickmarkr-auto/SKILL.md +58 -14
- package/skills/tickmarkr-loop/SKILL.md +65 -14
- package/skills/tickmarkr-overseer/SKILL.md +97 -28
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +9 -7
|
@@ -505,9 +505,12 @@ export async function refreshCatalogCommand(opts) {
|
|
|
505
505
|
let modelsDev = current.catalog.modelsDev;
|
|
506
506
|
let modelsDevUpdated = false;
|
|
507
507
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
508
|
+
// v2.6.9 (queue row 97): validate before the payload replaces the spine — an invalid payload left in
|
|
509
|
+
// modelsDev would be written beside an updated sibling leg and make the whole cache unreadable.
|
|
510
|
+
const fetched = await fetchCatalog(fetcher, MODELS_DEV_CATALOG_URL, {}, timeoutMs, (r) => r.json());
|
|
511
|
+
if (!validModelsDevCatalog(fetched))
|
|
510
512
|
throw new Error("models.dev catalog schema is invalid");
|
|
513
|
+
modelsDev = fetched;
|
|
511
514
|
modelsDevUpdated = true;
|
|
512
515
|
legAt.modelsDev = stamp;
|
|
513
516
|
legs.push({ leg: "models.dev", status: "updated", detail: "fetched", retry: "next retry after this leg is stale" });
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare function readCodexModelsCache(path?: string): {
|
|
|
3
3
|
models: string[];
|
|
4
4
|
fetchedAt?: string;
|
|
5
5
|
retirements?: Record<string, ModelRetirement | null>;
|
|
6
|
+
hidden?: string[];
|
|
6
7
|
};
|
|
7
8
|
export declare const CODEX_GIT_GRANT = "-c \"sandbox_workspace_write.writable_roots=[$(tkr_common=$(git rev-parse --path-format=absolute --git-common-dir); tkr_gitdir=$(git rev-parse --absolute-git-dir); if [ \"$tkr_gitdir\" != \"$tkr_common\" ]; then printf '\"%s\",' \"$tkr_gitdir\"; fi; printf '\"%s/objects\",\"%s/refs\",\"%s/logs\"' \"$tkr_common\" \"$tkr_common\" \"$tkr_common\")]\"";
|
|
8
9
|
export declare const CODEX_TRUST_DIALOG: TrustDialog;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -61,23 +61,29 @@ function codexSessionDayDirs(sessions, sinceMs) {
|
|
|
61
61
|
// Fails OPEN to { models: [] } on missing/corrupt cache. Version drift observed (installed CLI
|
|
62
62
|
// 0.143.0 vs cache client_version 0.144.0, 2026-07-10) — hence the defensive try/catch and no shape
|
|
63
63
|
// assumptions. CODEX_HOME is codex's own relocation env. fetchedAt is the honest codex knowledge age.
|
|
64
|
-
// v2.6.4 T2 (B1a): each
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
64
|
+
// v2.6.4 T2 (B1a): each entry's `upgrade` is the CLI's own retirement notice; its successor is whatever
|
|
65
|
+
// that notice names — never a fixed id in this file. A listed row with upgrade: null records known clean;
|
|
66
|
+
// a malformed or absent `upgrade` records nothing, so that id stays unknown. v2.6.9 (queue row 64): the CLI
|
|
67
|
+
// HIDES a model before it retires it (0.162.0, 2026-10-08: gpt-5.5 visibility "hide" with a dated
|
|
68
|
+
// upgrade), so a valid dated notice is read from hidden rows too; a hidden row still never lists, and a
|
|
69
|
+
// hidden row without a dated notice records nothing (delisted is not known clean). `hidden` names every
|
|
70
|
+
// visibility "hide" row (D-1526: routing evidence). An unreadable cache returns neither (unknown, never clean).
|
|
68
71
|
export function readCodexModelsCache(path) {
|
|
69
72
|
const p = path ?? join(process.env.CODEX_HOME || join(homedir(), ".codex"), "models_cache.json");
|
|
70
73
|
try {
|
|
71
74
|
const d = JSON.parse(readFileSync(p, "utf8"));
|
|
72
|
-
const
|
|
73
|
-
.filter((m) =>
|
|
75
|
+
const rows = (d.models ?? [])
|
|
76
|
+
.filter((m) => typeof m?.slug === "string" && m.slug.length > 0 && MODEL_ID_RE.test(m.slug));
|
|
77
|
+
const listed = rows.filter((m) => m.visibility === "list");
|
|
74
78
|
const retirements = {};
|
|
75
|
-
for (const m of
|
|
79
|
+
for (const m of rows) {
|
|
76
80
|
const notice = codexRetirement(m.upgrade);
|
|
77
|
-
if (notice !==
|
|
78
|
-
|
|
81
|
+
if (notice === undefined || (notice === null && m.visibility !== "list"))
|
|
82
|
+
continue;
|
|
83
|
+
retirements[m.slug] = notice;
|
|
79
84
|
}
|
|
80
|
-
|
|
85
|
+
const hidden = rows.filter((m) => m.visibility === "hide").map((m) => m.slug);
|
|
86
|
+
return { models: listed.map((m) => m.slug), fetchedAt: typeof d.fetched_at === "string" ? d.fetched_at : undefined, retirements, hidden };
|
|
81
87
|
}
|
|
82
88
|
catch {
|
|
83
89
|
return { models: [] };
|
|
@@ -305,6 +311,8 @@ export const codex = {
|
|
|
305
311
|
listModelsFetchedAt: () => readCodexModelsCache().fetchedAt,
|
|
306
312
|
// v2.6.4 T2 (B1a): the cache's per-model retirement notices, persisted beside models in doctor.json.
|
|
307
313
|
listModelsRetirements: () => readCodexModelsCache().retirements,
|
|
314
|
+
// v2.6.9 (D-1526): the ids the cache marks hidden, persisted beside models as routing evidence.
|
|
315
|
+
listModelsHidden: () => readCodexModelsCache().hidden,
|
|
308
316
|
collectUsage(cwd, sinceMs) {
|
|
309
317
|
try {
|
|
310
318
|
const real = realpathSync(cwd);
|
|
@@ -497,13 +497,15 @@ function storedRetirement(record, model) {
|
|
|
497
497
|
return { retiresAt: n.retiresAt };
|
|
498
498
|
return typeof n.successor === "string" && MODEL_ID_RE.test(n.successor) ? { retiresAt: n.retiresAt, successor: n.successor } : undefined;
|
|
499
499
|
}
|
|
500
|
-
// Configured
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
|
|
500
|
+
// Configured models the CLI lists, plus configured models it stopped listing that still carry a dated
|
|
501
|
+
// notice (the CLI hides a model before retiring it); any other unlisted id carries the bare tombstone
|
|
502
|
+
// advisory instead. The CLI's notice is quoted as-is: its successor is the CLI's upgrade target, never
|
|
503
|
+
// a tier or benchmark claim, and no model name or visibility is read as one. The notice never changes routing;
|
|
504
|
+
// the CLI's hide does (D-1526), so a hidden id's lint says it is not routed instead of "routing unchanged".
|
|
505
|
+
function retirementLints(id, configured, listed, hiddenIds, record, nowMs) {
|
|
504
506
|
const lints = [];
|
|
505
507
|
const unknown = [];
|
|
506
|
-
for (const model of
|
|
508
|
+
for (const model of configured) {
|
|
507
509
|
const notice = storedRetirement(record, model);
|
|
508
510
|
if (notice === undefined) {
|
|
509
511
|
unknown.push(model);
|
|
@@ -511,12 +513,14 @@ function retirementLints(id, listedConfigured, record, nowMs) {
|
|
|
511
513
|
}
|
|
512
514
|
if (!notice)
|
|
513
515
|
continue; // listed with no notice: known clean
|
|
516
|
+
const hidden = listed.includes(model) ? "" : " (the CLI no longer lists it)";
|
|
517
|
+
const routing = hiddenIds.includes(model) && !listed.includes(model) ? "not routed while the CLI hides it" : "routing unchanged";
|
|
514
518
|
const successor = notice.successor
|
|
515
519
|
? `successor ${notice.successor} (named by the CLI — not a tier claim; classify it per benchmark policy)`
|
|
516
520
|
: "no successor named";
|
|
517
521
|
lints.push(nowMs >= Date.parse(notice.retiresAt)
|
|
518
|
-
? `${id}: ${model} retired ${notice.retiresAt} per the CLI's notice; ${successor} — tombstone it (${model}: null overlay) or verify the id (advisory — routing
|
|
519
|
-
: `${id}: ${model} retires ${notice.retiresAt} per the CLI's notice; ${successor} — tombstone it (${model}: null overlay) before then (advisory — routing
|
|
522
|
+
? `${id}: ${model} retired ${notice.retiresAt} per the CLI's notice${hidden}; ${successor} — tombstone it (${model}: null overlay) or verify the id (advisory — ${routing})`
|
|
523
|
+
: `${id}: ${model} retires ${notice.retiresAt} per the CLI's notice${hidden}; ${successor} — tombstone it (${model}: null overlay) before then (advisory — ${routing})`);
|
|
520
524
|
}
|
|
521
525
|
if (unknown.length) {
|
|
522
526
|
const why = record && typeof record === "object"
|
|
@@ -555,19 +559,25 @@ export function modelLints(cfg, health, adapters, opts) {
|
|
|
555
559
|
}
|
|
556
560
|
const h = health[id];
|
|
557
561
|
const detected = h?.models ?? []; // MANDATORY default: pre-v1.5 files lack a populated models array
|
|
562
|
+
const configured = Object.keys(cfg.tiers[id]?.models ?? {});
|
|
563
|
+
// a configured id the CLI stopped listing but still carries a dated notice for gets the dated lint, not the bare one
|
|
564
|
+
const noticed = (model) => !!adapter.listModelsRetirements && !!storedRetirement(h?.modelRetirements, model);
|
|
558
565
|
if (detected.length === 0) {
|
|
559
566
|
if (h?.installed)
|
|
560
567
|
lints.push(`${id}: no detection data — run tickmarkr doctor`);
|
|
568
|
+
// D-1526: a cache holding only hidden rows lists nothing, yet its dated notices are still the CLI's word
|
|
569
|
+
if (adapter.listModelsRetirements)
|
|
570
|
+
lints.push(...retirementLints(id, configured.filter(noticed), detected, h?.modelsHidden ?? [], h?.modelRetirements, nowMs));
|
|
561
571
|
continue; // no data to diff or age
|
|
562
572
|
}
|
|
563
|
-
const configured = Object.keys(cfg.tiers[id]?.models ?? {});
|
|
564
573
|
for (const model of configured) {
|
|
565
|
-
if (!detected.includes(model)) {
|
|
574
|
+
if (!detected.includes(model) && !noticed(model)) {
|
|
566
575
|
lints.push(`${id}: tiers lists ${model} — CLI no longer reports it; tombstone it (${model}: null overlay) or verify the id`);
|
|
567
576
|
}
|
|
568
577
|
}
|
|
569
|
-
if (adapter.listModelsRetirements)
|
|
570
|
-
lints.push(...retirementLints(id, configured.filter((m) => detected.includes(m)), h?.modelRetirements, nowMs));
|
|
578
|
+
if (adapter.listModelsRetirements) {
|
|
579
|
+
lints.push(...retirementLints(id, configured.filter((m) => detected.includes(m) || noticed(m)), detected, h?.modelsHidden ?? [], h?.modelRetirements, nowMs));
|
|
580
|
+
}
|
|
571
581
|
const extra = collapseUnclassified(detected, new Set(configured));
|
|
572
582
|
if (extra.length) {
|
|
573
583
|
const shown = extra.slice(0, cap).map((row) => row.model).join(", ");
|
|
@@ -49,7 +49,6 @@ const VENDORED_MODEL_WINDOW_CLAIMS = [
|
|
|
49
49
|
{ modelId: "gpt-6-sol", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
50
50
|
{ modelId: "gpt-6-luna", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
51
51
|
{ modelId: "gpt-5.6-sol", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
52
|
-
{ modelId: "gpt-5.5", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
53
52
|
{ modelId: "gpt-5.6-terra", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
54
53
|
{ modelId: "gpt-5.6-luna", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
55
54
|
{ modelId: "composer-2.5", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
@@ -107,14 +107,18 @@ function modelListValidated(h) {
|
|
|
107
107
|
return !!h?.modelsDetectedAt && Array.isArray(h.models);
|
|
108
108
|
}
|
|
109
109
|
export function invalidConfiguredModels(cfg, adapterId, h) {
|
|
110
|
+
const configured = Object.keys(cfg.tiers[adapterId]?.models ?? {});
|
|
111
|
+
// v2.6.9 (queue row 64, D-1526): an id the CLI's own list marks hidden is positive evidence it stopped offering
|
|
112
|
+
// it — out whatever the overlap below. The fail-open that follows covers only ids with no such evidence.
|
|
113
|
+
const hidden = new Set(h?.modelsHidden ?? []);
|
|
114
|
+
const hiddenConfigured = configured.filter((m) => hidden.has(m) && !(h?.models ?? []).includes(m));
|
|
110
115
|
if (!modelListValidated(h))
|
|
111
|
-
return
|
|
116
|
+
return hiddenConfigured;
|
|
112
117
|
const listed = new Set(h.models);
|
|
113
|
-
const configured = Object.keys(cfg.tiers[adapterId]?.models ?? {});
|
|
114
118
|
// Fail open until the CLI list intersects at least one configured model — proves the list is authoritative
|
|
115
119
|
// for this adapter's tier namespace (MODEL-04: unrelated detected ids must not invalidate routing).
|
|
116
120
|
if (!configured.some((m) => listed.has(m)))
|
|
117
|
-
return
|
|
121
|
+
return hiddenConfigured;
|
|
118
122
|
return configured.filter((m) => !listed.has(m));
|
|
119
123
|
}
|
|
120
124
|
export function modelAliasExclusions(cfg, adapters, health) {
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface AuthHealth {
|
|
|
51
51
|
modelAuth?: Record<string, ModelAuth>;
|
|
52
52
|
modelIdentities?: Record<string, string>;
|
|
53
53
|
modelRetirements?: Record<string, ModelRetirement | null>;
|
|
54
|
+
modelsHidden?: string[];
|
|
54
55
|
codexCommit?: CodexCommitStatus;
|
|
55
56
|
launchServices?: LaunchServicesRecord;
|
|
56
57
|
}
|
|
@@ -206,6 +207,7 @@ export interface WorkerAdapter {
|
|
|
206
207
|
listModels?(): Promise<string[]>;
|
|
207
208
|
listModelsFetchedAt?(): string | undefined;
|
|
208
209
|
listModelsRetirements?(): Record<string, ModelRetirement | null> | undefined;
|
|
210
|
+
listModelsHidden?(): string[] | undefined;
|
|
209
211
|
collectUsage?(cwd: string, sinceMs: number): TokenUsage | undefined;
|
|
210
212
|
contextUsage?(session: SessionRef): ContextUsage | null;
|
|
211
213
|
readSessionTranscript?(session: SessionRef): {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type SupervisionTier } from "../../run/supervision.js";
|
|
2
|
-
import { type BeatLifecycleDeps } from "../../run/beat-lifecycle.js";
|
|
2
|
+
import { type BeatLifecycleDeps, type ClaimGuard } from "../../run/beat-lifecycle.js";
|
|
3
3
|
export declare function beat(argv: string[], cwd?: string, deps?: BeatLifecycleDeps): Promise<string | {
|
|
4
4
|
out: string;
|
|
5
5
|
code: number;
|
|
6
6
|
}>;
|
|
7
|
-
/** Record an explicit hand-off: the tier reads DISARMED, a stood-down tier, not a dead one. */
|
|
8
|
-
export declare function standDownTier(repoRoot: string, tier: SupervisionTier, seat: string): string;
|
|
7
|
+
/** Record an explicit hand-off: the tier reads DISARMED, a stood-down tier, not a dead one. `held`: the writer's claimed-act guard. */
|
|
8
|
+
export declare function standDownTier(repoRoot: string, tier: SupervisionTier, seat: string, held?: ClaimGuard): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { SUPERVISION_BEAT_MS, SUPERVISION_DEFAULT_THRESHOLD_PCT, SUPERVISION_STALE_MS, SUPERVISION_TIERS, beatSupervision, locateSupervisionRoot, newSupervisionArm, publishStandDown, readSupervisionArm, resolveSupervisionRoot, } from "../../run/supervision.js";
|
|
3
|
-
import { BEAT_GENERATION_ENV, beatStatus, defaultBeatDeps, startBeat, stopBeat, withLegacyClaim, } from "../../run/beat-lifecycle.js";
|
|
2
|
+
import { SUPERVISION_BEAT_MS, SUPERVISION_DEFAULT_THRESHOLD_PCT, SUPERVISION_STALE_MS, SUPERVISION_TIERS, beatSupervision, locateSupervisionRoot, newSupervisionArm, publishStandDown, readSupervisionArm, resolveSupervisionRoot, supervisionStatus, } from "../../run/supervision.js";
|
|
3
|
+
import { BEAT_GENERATION_ENV, beatStatus, defaultBeatDeps, startBeat, stopBeat, supervised, unclaimed, withLegacyClaim, } from "../../run/beat-lifecycle.js";
|
|
4
4
|
// Two forms share one strict parser. The lifecycle verbs `start|stop|status <tier>` own a DETACHED loop
|
|
5
5
|
// (src/run/beat-lifecycle.ts); the legacy `<tier>` form keeps its duties: one-shot ticks reuse a durable
|
|
6
6
|
// arm, only --new-arm (or --loop) acknowledges a stand-down, and --status is the read-only status verb.
|
|
@@ -35,10 +35,24 @@ export async function beat(argv, cwd = process.cwd(), deps = defaultBeatDeps())
|
|
|
35
35
|
return startBeat(repoRoot, tier, seat, deps);
|
|
36
36
|
if (verb === "stop")
|
|
37
37
|
return stopBeat(repoRoot, tier, seat, deps);
|
|
38
|
+
// Every legacy writer reclaims a claim whose recorded pids are all confirmed dead (a writer killed inside its claimed
|
|
39
|
+
// tick), recovers what a killed taker or writer left (its removal lock or pid-named lock entry, act marker, stage
|
|
40
|
+
// directory) and retires a dead or abandoned generation recorded for its seat (stood down, owner removed, nothing
|
|
41
|
+
// signalled); each such notice leads the final result or refusal, so a recovery is never reported as a plain write.
|
|
42
|
+
const notices = [];
|
|
43
|
+
const told = (message) => [...notices, message].join("\n");
|
|
44
|
+
try {
|
|
45
|
+
return told(await legacyWrite(repoRoot, tier, seat, values, deps, (notice) => { notices.push(notice); }));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw notices.length > 0 && error instanceof Error ? new Error(told(error.message)) : error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function legacyWrite(repoRoot, tier, seat, values, deps, noticed) {
|
|
38
52
|
const generation = deps.env[BEAT_GENERATION_ENV]?.trim() || undefined;
|
|
39
|
-
const writer = (settled = false, arm) => ({ generation, seat, settled, arm, deps });
|
|
53
|
+
const writer = (settled = false, arm) => ({ generation, seat, settled, arm, deps, noticed });
|
|
40
54
|
if (values["stand-down"]) {
|
|
41
|
-
return withLegacyClaim(repoRoot, tier, writer(), (recheck) => { recheck(); return standDownTier(repoRoot, tier, seat); });
|
|
55
|
+
return withLegacyClaim(repoRoot, tier, writer(), (recheck, held) => { recheck(); return standDownTier(repoRoot, tier, seat, held); });
|
|
42
56
|
}
|
|
43
57
|
const loop = values.loop === true;
|
|
44
58
|
const armId = values["arm-id"];
|
|
@@ -52,11 +66,18 @@ export async function beat(argv, cwd = process.cwd(), deps = defaultBeatDeps())
|
|
|
52
66
|
// arm id + epoch) is on record; every other writer only while no detached beat owns the tier. `created` marks
|
|
53
67
|
// the arm this same claim just published: an owned re-arm rebinds the owner record to it — only while that arm
|
|
54
68
|
// and the owner checked before it are still the ones on disk — and is checked again as that new generation
|
|
55
|
-
// before its beat. A running loop carries its arm into every check.
|
|
56
|
-
//
|
|
57
|
-
|
|
69
|
+
// before its beat. A running loop carries its arm into every check. Each supervision write (arm, beat, marker)
|
|
70
|
+
// is staged and committed record by record, each rename one claimed act under `held` (supervised): inside it this
|
|
71
|
+
// same ownership check runs again, every record must still be what the write was staged from, and the claim is
|
|
72
|
+
// verified last — a claim lost, or an owner or arm replaced, before a commit lands nothing further. A claim someone
|
|
73
|
+
// else holds is waited out under the ONE finite policy (BEAT_CLAIM_WAIT: 20 attempts 250 ms apart, shared with
|
|
74
|
+
// stop), then refuses BUSY with no bytes changed — unless every pid it records is confirmed dead, when it is
|
|
75
|
+
// reclaimed with a notice.
|
|
76
|
+
const tick = (recheck, held, arm, created = false) => {
|
|
58
77
|
recheck(arm, created);
|
|
59
|
-
|
|
78
|
+
// The fence travels with the beat: a stand-down that landed while this beat was committed still dominates it.
|
|
79
|
+
if (supervised(repoRoot, tier, held, (root) => beatSupervision(root, tier, seat, observe(arm), { arm, armId, loop })) &&
|
|
80
|
+
supervisionStatus(repoRoot, tier).state !== "DISARMED")
|
|
60
81
|
return;
|
|
61
82
|
const current = arm && readSupervisionArm(repoRoot, tier);
|
|
62
83
|
if (arm && (current?.armId !== arm.armId || current.armEpoch !== arm.armEpoch)) {
|
|
@@ -65,22 +86,21 @@ export async function beat(argv, cwd = process.cwd(), deps = defaultBeatDeps())
|
|
|
65
86
|
throw new Error(`${tier} DISARMED — stood down; use --new-arm to resume beating`);
|
|
66
87
|
};
|
|
67
88
|
if (values["new-arm"] || loop) {
|
|
68
|
-
const arm = await withLegacyClaim(repoRoot, tier, writer(), (recheck) => {
|
|
89
|
+
const arm = await withLegacyClaim(repoRoot, tier, writer(), (recheck, held) => {
|
|
69
90
|
recheck();
|
|
70
|
-
const created =
|
|
71
|
-
tick(recheck, created, true);
|
|
91
|
+
const created = supervised(repoRoot, tier, held, (root) => newSupervisionArm(root, tier, armId));
|
|
92
|
+
tick(recheck, held, created, true);
|
|
72
93
|
return created;
|
|
73
94
|
});
|
|
74
|
-
// ponytail:
|
|
75
|
-
//
|
|
76
|
-
const waits = { tries: 20, sleep: deps.sleep };
|
|
95
|
+
// ponytail: every tick outlasts a brief claim holder (a repeat start, a stop) for ~5 s of retries, then
|
|
96
|
+
// refuses nonzero; a per-tick deadline if holders ever legitimately run longer.
|
|
77
97
|
while (loop) {
|
|
78
98
|
await deps.sleep(SUPERVISION_BEAT_MS);
|
|
79
|
-
await withLegacyClaim(repoRoot, tier, writer(true, arm), (recheck) => tick(recheck,
|
|
99
|
+
await withLegacyClaim(repoRoot, tier, writer(true, arm), (recheck, held) => tick(recheck, held, arm));
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
else
|
|
83
|
-
await withLegacyClaim(repoRoot, tier, writer(), (recheck) => tick(recheck));
|
|
103
|
+
await withLegacyClaim(repoRoot, tier, writer(), (recheck, held) => tick(recheck, held));
|
|
84
104
|
return `${tier} ARMED as ${seat} — beat again every ${SUPERVISION_BEAT_MS / 1_000}s; the tier reads STALE ${SUPERVISION_STALE_MS / 1_000}s after the last beat`;
|
|
85
105
|
}
|
|
86
106
|
/** Unknown, duplicate, conflicting or empty options refuse here — before anything is read or written. */
|
|
@@ -138,9 +158,9 @@ function percentage(raw, name) {
|
|
|
138
158
|
}
|
|
139
159
|
return value;
|
|
140
160
|
}
|
|
141
|
-
/** Record an explicit hand-off: the tier reads DISARMED, a stood-down tier, not a dead one. */
|
|
142
|
-
export function standDownTier(repoRoot, tier, seat) {
|
|
143
|
-
|
|
161
|
+
/** Record an explicit hand-off: the tier reads DISARMED, a stood-down tier, not a dead one. `held`: the writer's claimed-act guard. */
|
|
162
|
+
export function standDownTier(repoRoot, tier, seat, held = unclaimed) {
|
|
163
|
+
supervised(repoRoot, tier, held, (root) => publishStandDown(root, tier, seat));
|
|
144
164
|
return `${tier} DISARMED — ${seat} handed off; status reads it stood down, not dead`;
|
|
145
165
|
}
|
|
146
166
|
const isTier = (v) => SUPERVISION_TIERS.includes(v ?? "");
|
|
@@ -600,6 +600,9 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
600
600
|
const retirements = a.listModelsRetirements?.();
|
|
601
601
|
if (retirements)
|
|
602
602
|
health[a.id].modelRetirements = retirements;
|
|
603
|
+
const hidden = a.listModelsHidden?.(); // v2.6.9 (D-1526): the CLI's hidden flag, read by routing
|
|
604
|
+
if (hidden)
|
|
605
|
+
health[a.id].modelsHidden = hidden;
|
|
603
606
|
}
|
|
604
607
|
catch { /* fail open: leave models as-is, doctor stays healthy */ }
|
|
605
608
|
}
|
|
@@ -1265,6 +1265,8 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
1265
1265
|
steeringOptionsFor,
|
|
1266
1266
|
reviewAdvisories,
|
|
1267
1267
|
reviewOverlay,
|
|
1268
|
+
// B-KEYS r2 (D-1446): the editor's row memo keys on both overlay layers' bytes — previewCfg's own key
|
|
1269
|
+
layerStamp: () => `${currentUserText()}\u0000${currentRepoOverlayText(cwd)}`,
|
|
1268
1270
|
reloadGuard,
|
|
1269
1271
|
stagedRouting,
|
|
1270
1272
|
holderOf,
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_CONFIG, effectiveReviewPolicy, overlayPreferShapes, ROUTING_MOD
|
|
|
11
11
|
import { batteryPriority, chainDepth, dispatchWaves, graphDefinitionHash, loadGraph, stateDirName } from "../../graph/graph.js";
|
|
12
12
|
import { filesGlob } from "../../graph/files-glob.js";
|
|
13
13
|
import { renderAcceptanceItem } from "../../graph/schema.js";
|
|
14
|
-
import { pendingDaemonApprovalActions, resolveRunMode } from "../../run/daemon.js";
|
|
14
|
+
import { pendingDaemonApprovalActions, replayedReviewerDemotions, resolveRunMode } from "../../run/daemon.js";
|
|
15
15
|
import { disallowedBy, excludedChannels, exclusionLine, observedSeat, routingEntrySeatLines } from "../../route/preference.js";
|
|
16
16
|
import { decayWeight, HALF_LIFE_RUNS, staffLedEvidence } from "../../route/profile.js";
|
|
17
17
|
import { resolvedFloor, route, RoutingError } from "../../route/router.js";
|
|
@@ -193,22 +193,20 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
193
193
|
.filter((event) => event.event === "gate-result" && event.data.gate === "review" && typeof event.data.reviewer === "string")
|
|
194
194
|
.map((event) => event.data.reviewer)
|
|
195
195
|
: [];
|
|
196
|
-
// daemon.ts
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
.filter((event) => event.event === "review-pool-demotion" && typeof event.data.reviewer === "string")
|
|
200
|
-
.map((event) => event.data.reviewer))];
|
|
201
|
-
const demotedReviewers = new Set(replayedReviewerExclusions);
|
|
196
|
+
// daemon.ts demotedReviewers: a journaled review-pool-demotion is a SOFT preference in the resumed run
|
|
197
|
+
// this plan previews (ranked last, never excluded; reviewer role only) — never a launch-caused one (v2.6.8 T1).
|
|
198
|
+
const demotedReviewers = replayedReviewerDemotions(matchingEvents ?? []);
|
|
202
199
|
// daemon.ts demotedChannels: the resumed run's worker exclusions (journal replayExcludedChannels).
|
|
203
200
|
const runJournal = matchingRunId ? Journal.open(cwd, matchingRunId) : undefined;
|
|
204
201
|
const demotedWorkers = runJournal?.replayExcludedChannels() ?? new Set();
|
|
205
|
-
// run-gates.ts retired (OBS-1025 add.2): seats with two no-verdicts this run are out of every pick,
|
|
206
|
-
// as daemon.ts seeds reviewNoVerdicts — noVerdict rows only, and a
|
|
202
|
+
// run-gates.ts retired (OBS-1025 add.2): seats with two real no-verdicts this run are out of every pick,
|
|
203
|
+
// tallied as daemon.ts seeds reviewNoVerdicts — noVerdict rows only, and a launch failure (any
|
|
204
|
+
// launchCause, or none) never strikes (v2.6.8 T1).
|
|
207
205
|
const noVerdicts = new Map();
|
|
208
206
|
for (const event of matchingEvents ?? []) {
|
|
209
207
|
if (event.event !== "review-no-verdict" || event.data.noVerdict !== true || typeof event.data.reviewer !== "string")
|
|
210
208
|
continue;
|
|
211
|
-
if (event.data.cause === "seat-launch-failed"
|
|
209
|
+
if (event.data.cause === "seat-launch-failed")
|
|
212
210
|
continue;
|
|
213
211
|
noVerdicts.set(event.data.reviewer, (noVerdicts.get(event.data.reviewer) ?? 0) + 1);
|
|
214
212
|
}
|
|
@@ -412,15 +410,15 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
412
410
|
// Never dispatched, daemon.ts subjectAuthors is just the fresh author; its vendor stays off the review.
|
|
413
411
|
const authors = [channelKey(author)];
|
|
414
412
|
const authorVendors = carriedAuthorVendors(pools.review, authors);
|
|
415
|
-
// Production's inputs as the task's first review round sees them: excludeReviewers = daemon badReviewers
|
|
416
|
-
//
|
|
417
|
-
// tiers (daemon taskReviewers) ∪ excludeReviewers,
|
|
418
|
-
//
|
|
419
|
-
const priorReviewers =
|
|
420
|
-
|
|
421
|
-
|
|
413
|
+
// Production's inputs as the task's first review round sees them: excludeReviewers = daemon badReviewers,
|
|
414
|
+
// empty at resume (a replayed demotion is soft). run-gates floors on THIS task's journaled reviewers at their
|
|
415
|
+
// dispatched tiers (daemon taskReviewers) ∪ excludeReviewers, and picks with excludeReviewers ∪ retired.
|
|
416
|
+
// prefer only reorders (the seat count below passes none).
|
|
417
|
+
const priorReviewers = (matchingEvents ?? []).filter((e) => e.taskId === task.id && typeof e.data.reviewer === "string"
|
|
418
|
+
&& ((e.event === "gate-result" && e.data.gate === "review") || e.event === "review-no-verdict"))
|
|
419
|
+
.map((e) => ({ reviewer: e.data.reviewer, tier: e.data.reviewerTier }));
|
|
422
420
|
const reviewFloor = gateReviewerFloor(task, cfg, author, pools.review, priorReviewers).floor;
|
|
423
|
-
const exclusions =
|
|
421
|
+
const exclusions = retiredReviewers;
|
|
424
422
|
const reviewer = pickReviewer(author, pools.review, exclusions, cfg.review.prefer ?? [], reviewFloor, reviewHistory, (seat, count) => { rotationSeat = seat; rotationCount = count; }, demotedReviewers, authorVendors, authors);
|
|
425
423
|
if (!reviewer)
|
|
426
424
|
return { line: "none — no cross-vendor reviewer available", cost: 0 };
|
|
@@ -13,7 +13,7 @@ import { runGates } from "../../gates/run-gates.js";
|
|
|
13
13
|
import { getTask, loadGraph } from "../../graph/graph.js";
|
|
14
14
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
15
15
|
import { executionSignal } from "../../run/execution-budget.js";
|
|
16
|
-
import { linkNodeModules, removeWorktree, shGit, shGitOk } from "../../run/git.js";
|
|
16
|
+
import { addDetachedWorktree, linkNodeModules, removeWorktree, shGit, shGitOk } from "../../run/git.js";
|
|
17
17
|
import { foldOwedChecks, integrationMapped, owedCriteria, Journal, OWED_DISCHARGE_EVENT } from "../../run/journal.js";
|
|
18
18
|
import { withRepositoryLease } from "../../run/lease.js";
|
|
19
19
|
import { isPidLive } from "../../run/lock.js";
|
|
@@ -417,7 +417,9 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
417
417
|
}
|
|
418
418
|
const baseDir = mkdtempSync(join(stateDir, `base-${mergeBase.slice(0, 12)}-${process.pid}-`));
|
|
419
419
|
try {
|
|
420
|
-
|
|
420
|
+
const added = await addDetachedWorktree(cwd, baseDir, mergeBase);
|
|
421
|
+
if (added.code !== 0)
|
|
422
|
+
throw new Error(`command failed (${added.code}): git worktree add --detach '${baseDir}' '${mergeBase}'\n${added.stderr || added.stdout}`);
|
|
421
423
|
linkNodeModules(cwd, baseDir, { force: true });
|
|
422
424
|
const baseline = await captureBaseline(baseDir, subset);
|
|
423
425
|
if (baseline.refusal)
|
package/dist/cli/help.js
CHANGED
|
@@ -149,7 +149,10 @@ export const COMMAND_HELP = {
|
|
|
149
149
|
},
|
|
150
150
|
beat: {
|
|
151
151
|
usage: "beat <start|stop|status> <orchestrator|orchestrator-context|overseer|overseer-context|watch> --seat <identity> | beat <tier> --seat <identity> [options]",
|
|
152
|
-
description: "start launches one detached --loop beat per tier and exits 0 only after reading back that child's own advancing beat; stop retires only that recorded generation and reads back DISARMED; status is read-only and exits nonzero for STALE, MISMATCH or
|
|
152
|
+
description: "start launches one detached --loop beat per tier and exits 0 only after reading back that child's own advancing beat; stop retires only that recorded generation and reads back DISARMED; status is read-only and exits nonzero for STALE, MISMATCH, UNREADABLE or a dead invocation's leftovers. The legacy <tier> form writes one supervision beat using the recorded arm: stood-down arms stay DISARMED until explicitly re-armed; stopped beats become STALE. " +
|
|
153
|
+
"Legacy migration: start refuses a legacy (unowned) ARMED/STALE arm nonzero and prints its exit under the RECORDED seat — `tickmarkr beat <tier> --seat <seat> --stand-down` (`--seat=<seat>` when the seat begins with a dash), after \"stop pid <N> first\" when its --loop writer is still alive (tickmarkr never signals a process it did not launch, nor takes its live claim; an arm with no recorded seat gets no invented one) — then start owns the tier and reads back ARMED. A writer stopped inside its claimed tick does not strand that step: the --stand-down reclaims the claim it died holding, printing the notice; while the writer lives, the step waits and refuses BUSY. " +
|
|
154
|
+
"Claims — actors IN: compliant claim contenders (start, stop and each legacy write — one-shot, --new-arm, --loop tick, --stand-down — through this CLI), a crashed owner (holder pid ESRCH or a zombie, its claim and records left behind) and a killed compliant recovery or release taker (dead at any step of the one claim-removal path); OUT, the one residual: an external writer editing, replacing or deleting claim, lock or record files outside this CLI, under which nothing beyond the identity MISMATCH refusals is promised (its edit between a final claim check and the mutation's own system call is not seen — there is no filesystem compare-and-rename). A claim records its holder pid and birth (its runtime's own start record, read with no process probe) and, for a launch claim, the launched writer publishing under it. Every claimant takes over a held claim only when every pid read from that claim's own bytes is confirmed dead (ESRCH or a zombie) — the legacy {tier, token, pid, claimedAt} claim included — printing the reclaim notice with its final result even when it then had to wait or refuse; a live, reused or unreadable holder or writer is never taken over: start tries once, every other claimant waits at most 20 attempts 250 ms apart, then refuses BUSY with nothing written. A launched writer acts under its launcher's claim only while that launcher is not confirmed dead, recording itself as the claim's writer before its first act; once the launcher is dead, a writer past its first beat releases that claim itself (with a notice) and takes the tier claim like any writer — or, when that launcher died before binding its arm epoch, exits right after the release, leaving an abandoned launch the next start, stop or legacy write retires — and one still launching refuses having written nothing; a killed start neither strands stop nor lets recovery take the claim from a live writer. Under its launcher's claim the writer checks nothing after its last act (each act verified the claim last), so start's release after its read-back never ends the loop it launched. A claim is removed in one way only, by recovery and by its holder's release alike: under a per-claim removal lock that appears already naming its taker's pid and birth, once no claimed act is running under those bytes (a release waits up to 2 s, a recovery refuses BUSY), the checked bytes are moved aside into the lock and kept only when they are exactly those bytes — other bytes found there are put straight back, and a claim linked while that removal is under way is never acted under: its claimant withdraws exactly the claim it linked and refuses BUSY (its next attempt links afresh). Claim bytes an invocation could not remove — another live taker held them or moved them aside, even on a mistaken death proof, or its release was refused — stay provably that process's strand wherever they are (its pid, its checked birth and a token none of its invocations still holds): while a live taker holds them every claimant is BUSY naming it; once none does, that process's own next start, stop or legacy write withdraws them from the claim path, or discards them from a killed taker's lock instead of restoring them, with a notice; its own status reports them as its stranded claim wherever they are — on the claim path or moved aside into a live or dead taker's lock, naming that taker — with the recovery command (to run once a live taker has finished or exited); and once that process has exited they are a dead claim any start, stop or legacy write reclaims — so no caller, a long-lived bridge included, needs to exit before recovery completes, and no claim is left that nobody acts under and nobody can recover. Killed-taker recovery: a lock whose taker is confirmed dead is recovered by the next start, stop or legacy write with a notice naming that taker — bytes it moved aside are discarded when every pid they record is dead, otherwise restored — and that invocation also removes the act markers, <tier>.stage.<pid>.<id> directories and the pid-named entry a taker killed while assembling or releasing its lock (before its owner entry is written, or after it is moved out) left, each with a notice naming the dead pid; a live or unprovable taker's lock is never taken (BUSY, naming it). \"Never taken over\" therefore means: only a claim or lock proven dead is reclaimed, exactly as checked; nothing live or unknown is taken, and no claim, lock or stage directory is ever the operator's to remove by hand. Dead owners recover the same way: a generation whose recorded pid is confirmed dead, or a launch abandoned before it bound its pid or arm epoch (no launch holds the claim and no recorded process is alive), is retired — its owner record removed, nothing signalled — by the next stop (which stands its arm down and reads back DISARMED), by start (which stands it down, then launches and reads back ARMED) or by a legacy write from that seat (whose requested write then proceeds: a one-shot records its beat on that arm, --stand-down stands it down, --new-arm/--loop replace it), each printing the recovery; an owner whose recorded process lives is never retired this way. Every claimed mutation — each owner record rename, log open, spawn, signal and removal, and each record a staged stand-down, arm or beat write changed — is one claimed act: it marks the claim, re-runs its owner-generation check inside the act (a staged write also compares every supervision record with what it was staged from), then verifies the claim LAST, immediately before the mutation. No claim removal lands inside an act, and a claim, owner or arm replaced before that final check lands nothing further: nothing more is written, signalled or removed and the invocation refuses nonzero at its next check, its release included (a launched writer under its launcher's claim has no release: what its acts landed, they landed under the verified claim). Status (beat status <tier> and the legacy <tier> --status) only observes: over a dead claim or a killed taker's or writer's leftovers it changes no file, exits nonzero naming the dead pids and prints the command that recovers them, and it names every live holder (the claim's, and each removal lock's taker) beside the state it reads, `tickmarkr beat stop <tier> --seat <seat>` (the recorded seat, else the one given, quoted as one shell word) — start or a legacy write recovers the same way; status never does. " +
|
|
155
|
+
"Identity: Linux teardown argv ([comm]) and a child dying mid-read stay UNREADABLE until a complete read or confirmed death (\"died before its beat read back\"); on Darwin a missing or unusable lsof refuses UNREADABLE naming lsof, and restoring lsof restores start/status/stop.",
|
|
153
156
|
options: {
|
|
154
157
|
"--seat <identity>": "Required for start, stop and every write: identify the supervising pane or agent; optional for status.",
|
|
155
158
|
"--new-arm": "Create a new durable arm, allowing beats to resume after stand-down; the hand-off step for a seat taking over a stood-down tier (combine with --arm-id and --pct on a context tier).",
|
|
@@ -157,7 +160,7 @@ export const COMMAND_HELP = {
|
|
|
157
160
|
"--arm-id <identity>": "Name a newly created arm; with --pct, identify the context observation arm. Never refuses a tick.",
|
|
158
161
|
"--pct <0..100>": "Report current context consumption percentage.",
|
|
159
162
|
"--threshold-pct <0..100>": "Set the context warning threshold (default 75).",
|
|
160
|
-
"--stand-down": "Record an explicit handoff and stand down this tier. A later one-shot tick on the old arm exits non-zero; context observations still raise and discharge the clear duty.",
|
|
163
|
+
"--stand-down": "Record an explicit handoff and stand down this tier. A later one-shot tick on the old arm exits non-zero; context observations still raise and discharge the clear duty. With the recorded seat it is the legacy migration step start prints — it reclaims a claim the stopped writer died holding, never a live one; then start owns the tier.",
|
|
161
164
|
"--status": "Legacy spelling of beat status <tier>: the same read-only result, writing nothing.",
|
|
162
165
|
}, examples: [
|
|
163
166
|
"beat start overseer --seat supervisor", "beat status overseer --seat supervisor", "beat stop overseer --seat supervisor",
|
package/dist/config/config.js
CHANGED
|
@@ -551,20 +551,21 @@ export const DEFAULT_CONFIG = {
|
|
|
551
551
|
// astra=frontier and luna=cheap follow the provider's own descriptions in the local cache (read
|
|
552
552
|
// 2026-09-29: "Frontier intelligence for the most demanding work" / "Fast and affordable model for
|
|
553
553
|
// easier tasks"); sol=frontier keeps the shipped workhorse-family mapping (gpt-5.6-sol above) by
|
|
554
|
-
// continuity. Learned routing or an overlay may retier on evidence. gpt-5.5
|
|
555
|
-
// CLI
|
|
556
|
-
//
|
|
554
|
+
// continuity. Learned routing or an overlay may retier on evidence. v2.6.9 (queue row 64): gpt-5.5 left
|
|
555
|
+
// the seed — the CLI hides it and announces its retirement (2026-10-14T19:00:00Z); its successor is
|
|
556
|
+
// whatever the CLI's notice names (doctor quotes it), never a fixed id here.
|
|
557
|
+
// Windows: the same snapshot lists context_window=272000 for the six still seeded — the budget the CLI launches
|
|
557
558
|
// with, and the authority for these seeds. The API's published 1,050,000 is a different surface and is
|
|
558
559
|
// not contradicted. A later cache listing more ids (gpt-6.1-sol) does not widen this sealed set.
|
|
559
560
|
codex: {
|
|
560
561
|
vendor: "openai", channel: "sub",
|
|
561
562
|
models: {
|
|
562
563
|
"gpt-6-astra": "frontier", "gpt-6-sol": "frontier", "gpt-6-luna": "cheap",
|
|
563
|
-
"gpt-5.6-sol": "frontier", "gpt-5.
|
|
564
|
+
"gpt-5.6-sol": "frontier", "gpt-5.6-terra": "mid", "gpt-5.6-luna": "cheap",
|
|
564
565
|
},
|
|
565
566
|
windows: {
|
|
566
567
|
"gpt-6-astra": 272_000, "gpt-6-sol": 272_000, "gpt-6-luna": 272_000,
|
|
567
|
-
"gpt-5.6-sol": 272_000, "gpt-5.
|
|
568
|
+
"gpt-5.6-sol": 272_000, "gpt-5.6-terra": 272_000, "gpt-5.6-luna": 272_000,
|
|
568
569
|
},
|
|
569
570
|
},
|
|
570
571
|
// grok-4.5 (xAI, released 2026-07-08) → mid: AA Intelligence 54 (#4), Terminal-Bench 2.1 83.3%
|
package/dist/drivers/herdr.js
CHANGED
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { declaredInputBoxForWorkerName, matchesEmptyInputBox, matchesInputBox, matchesOccupiedInputBox, missingInputStateDeclarations, shq } from "../adapters/types.js";
|
|
6
6
|
import { consumePaneLaunchIntent, PANE_IDENTITY_ENV, paneIdentityLine } from "../brand.js";
|
|
7
|
-
import { createWorktree, sh } from "../run/git.js";
|
|
7
|
+
import { createWorktree, sh, shGit } from "../run/git.js";
|
|
8
8
|
import { Journal } from "../run/journal.js";
|
|
9
9
|
import { readSupervision, readWatchBoard, requestWatchBoardStop, reserveWatchBoard, stopWatchBoard, WATCH_OWNER_ENV } from "../run/supervision.js";
|
|
10
10
|
import { canonicalWorktreePath } from "./orca.js";
|
|
@@ -487,7 +487,7 @@ export class HerdrDriver {
|
|
|
487
487
|
if (typeof pane.cwd !== "string" || canonicalWorktreePath(pane.cwd) !== checkout) {
|
|
488
488
|
throw new Error(`herdr adopt: pane ${pane.pane_id} labelled ${slot.name} sits in ${pane.cwd ?? "an unreported cwd"}, not the task checkout ${checkout} — foreign`);
|
|
489
489
|
}
|
|
490
|
-
const top = await
|
|
490
|
+
const top = await shGit("git rev-parse --show-toplevel", checkout);
|
|
491
491
|
if (top.code !== 0 || canonicalWorktreePath(top.stdout.trim()) !== checkout) {
|
|
492
492
|
throw new Error(`herdr adopt: ${checkout} is not its own git checkout — no checkout proof for ${slot.name}`);
|
|
493
493
|
}
|
package/dist/drivers/orca.d.ts
CHANGED
|
@@ -32,10 +32,13 @@ export declare class OrcaError extends Error {
|
|
|
32
32
|
readonly code?: string;
|
|
33
33
|
readonly runtimeId?: string;
|
|
34
34
|
readonly launchCause?: SeatLaunchCause;
|
|
35
|
+
/** v2.6.8 T1: the CLI was killed at the timeout it was handed — the one evidence that a deadline interrupted it. */
|
|
36
|
+
readonly timedOut?: boolean;
|
|
35
37
|
constructor(family: string, reason: string, raw: string, opts?: {
|
|
36
38
|
code?: string;
|
|
37
39
|
runtimeId?: string;
|
|
38
40
|
launchCause?: SeatLaunchCause;
|
|
41
|
+
timedOut?: boolean;
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
/** The slot cannot be addressed: dead/unknown terminal record, or a handle that cannot be recovered
|
|
@@ -86,9 +89,12 @@ export declare function canonicalWorktreePath(path: string): string;
|
|
|
86
89
|
/** The proof line a worker terminal prints first: create, recovery and focus read it back. */
|
|
87
90
|
export declare const CHECKOUT_MARK = "TICKMARKR_CHECKOUT";
|
|
88
91
|
/** E1: the one typed launch cause a driver attaches to its launch failure. `checkout-proof-timeout`
|
|
89
|
-
* means the terminal printed NO proof frame by the ceiling —
|
|
90
|
-
*
|
|
92
|
+
* means the terminal printed NO proof frame by the ceiling — or (v2.6.8 T1) a read the ceiling
|
|
93
|
+
* interrupted left the own frame's prefix unfinished — a slow host, not a misplaced seat. A complete
|
|
94
|
+
* foreign frame, or a malformed one read whole (in a page landing late or at the ceiling too), is
|
|
95
|
+
* placement evidence and carries no cause. */
|
|
91
96
|
export type SeatLaunchCause = "checkout-proof-timeout";
|
|
97
|
+
export declare function withLaunchGuard<T>(guard: () => void, run: () => Promise<T>): Promise<T>;
|
|
92
98
|
/** The exact bytes the create command prints as its first line. */
|
|
93
99
|
export declare function checkoutProofLine(checkout: string): string;
|
|
94
100
|
/** Every complete frame in a scrollback, decoded and canonicalized; whether an incomplete one was seen. */
|
|
@@ -266,12 +272,14 @@ export declare class OrcaDriver implements ExecutorDriver {
|
|
|
266
272
|
* the terminal record must name `handle` and `_meta.runtimeId` must be `runtimeId` — the runtime
|
|
267
273
|
* that supplied the ownership listing — else another terminal's or another runtime's bytes were
|
|
268
274
|
* answered and nothing is proven. Proven means provesCheckout: exact canonical full-path equality
|
|
269
|
-
* of a complete frame, no other checkout named, no incomplete frame
|
|
275
|
+
* of a complete frame, no other checkout named, no incomplete frame or unparseable mark — in the pages
|
|
276
|
+
* or the anchor's tail (v2.6.8 T1).
|
|
270
277
|
* E1: with a `deadline` (create's window) there is ONE absolute deadline: a read is issued only while
|
|
271
278
|
* budget is left and is handed exactly that budget, and a page that still lands past it is late —
|
|
272
279
|
* nothing late proves the checkout. `seen` is what the scrollback showed, for the typed timeout:
|
|
273
280
|
* `misplaced` — a frame naming another checkout, a cut frame or an unparseable mark in anything read
|
|
274
|
-
* (the anchor's tail and every page included, even when paging stopped
|
|
281
|
+
* (the anchor's tail and every page included, even when paging stopped, a later read threw or the page's
|
|
282
|
+
* own cursors were malformed — v2.6.8 T1; the tail read after the pages it overlaps, tailInContext);
|
|
275
283
|
* `absent` — ONLY when every page down to the start of the scrollback was read before the ceiling
|
|
276
284
|
* and none carries a trace of the mark; `unread` — every other exit: PROOF_PAGES reached, a limited
|
|
277
285
|
* read with no (or the same) next cursor or a truncated anchor without one, a read that threw, a read
|