@indigoai-us/hq-cli 5.287.0 → 5.289.0
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/CHANGELOG.md +48 -5
- package/assets/bot-workers/setup/skills/explain-hq.md +35 -0
- package/assets/bot-workers/setup/worker.yaml +25 -23
- package/dist/command-catalog.generated.d.ts +3 -0
- package/dist/command-catalog.generated.js +4 -0
- package/dist/commands/agent-enroll.js +10 -2
- package/dist/commands/agents.d.ts +1 -0
- package/dist/commands/agents.js +12 -4
- package/dist/commands/core-ontology.d.ts +4 -1
- package/dist/commands/core-ontology.js +26 -0
- package/dist/commands/lanes-resume.js +41 -2
- package/dist/commands/lanes-shared.d.ts +2 -0
- package/dist/commands/lanes.js +204 -6
- package/dist/lanes-drain-hook.d.ts +12 -0
- package/dist/lanes-drain-hook.js +59 -55
- package/dist/lib/bot/run.js +92 -2
- package/dist/lib/bot/setup-explain.d.ts +68 -0
- package/dist/lib/bot/setup-explain.js +141 -0
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +10 -1
- package/dist/lib/checkpoint-ontology/merge-copies.js +17 -2
- package/dist/lib/checkpoint-ontology/plan.d.ts +0 -88
- package/dist/lib/checkpoint-ontology/plan.js +80 -107
- package/dist/lib/lanes/dropbox.d.ts +1 -2
- package/dist/lib/lanes/dropbox.js +10 -111
- package/dist/lib/lanes/message-replay.d.ts +8 -0
- package/dist/lib/lanes/message-replay.js +119 -0
- package/dist/lib/lanes/store.js +30 -12
- package/dist/lib/lanes/types.d.ts +6 -2
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/plan-limit-nag.js +11 -0
- package/dist/main.js +17 -4
- package/dist/utils/cli-telemetry.d.ts +21 -0
- package/dist/utils/cli-telemetry.js +101 -1
- package/dist/utils/cognito-refresh-sentry.d.ts +3 -0
- package/dist/utils/cognito-refresh-sentry.js +49 -0
- package/dist/utils/cognito-session.js +55 -0
- package/package.json +1 -1
|
@@ -25,99 +25,12 @@ export const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
|
25
25
|
* 6.18.7 or later; see ./sync-client.ts.
|
|
26
26
|
*/
|
|
27
27
|
export const ONTOLOGY_SYNC_GATED_FLAG = "checkpoint.ontology.sync-gated";
|
|
28
|
-
export const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
29
|
-
/**
|
|
30
|
-
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
31
|
-
* with stripped inputs lost 0.139 recall; Grok with stripped inputs gained
|
|
32
|
-
* precision without losing recall. A machine that falls back from grok keeps
|
|
33
|
-
* the annotated inputs.
|
|
34
|
-
*/
|
|
35
|
-
export const IGNORE_ANNOTATIONS_FLAG = "checkpoint.ontology.ignore-annotations";
|
|
36
|
-
/**
|
|
37
|
-
* With `--backend auto`, `hq core checkpoint` tries grok first for this
|
|
38
|
-
* company. Read here, in the detached sibling, and recorded in the company's
|
|
39
|
-
* state directory, so the foreground checkpoint makes no network call.
|
|
40
|
-
*/
|
|
41
|
-
export const GROK_BACKEND_FLAG = "checkpoint.ontology.grok-backend";
|
|
42
28
|
/**
|
|
43
29
|
* Several writers in one company vault (design section 20): record written
|
|
44
30
|
* entity bytes in the outbox, merge back set-aside versions, and report
|
|
45
31
|
* duplicate entities.
|
|
46
32
|
*/
|
|
47
33
|
export const MULTI_WRITER_FLAG = "checkpoint.ontology.multi-writer";
|
|
48
|
-
/**
|
|
49
|
-
* A candidate that links to an existing entity adds no aliases to it; the plan
|
|
50
|
-
* carries `link_aliases: false` and apply honours it. CO-42: most aliases
|
|
51
|
-
* written through links on the CO-41 runs were other things the text named
|
|
52
|
-
* next to the entity (`Publisher` on indigo, `Stripe Connect` on Stripe), and
|
|
53
|
-
* entity-hints multiplied them (22 aliases on a flag-off run, 45 and 125 on two
|
|
54
|
-
* hinted runs).
|
|
55
|
-
*/
|
|
56
|
-
export const NO_LINK_ALIASES_FLAG = "checkpoint.ontology.no-link-aliases";
|
|
57
|
-
/**
|
|
58
|
-
* A person the input and the company's entities give only a first name for is
|
|
59
|
-
* named by that first name instead of being left out. CO-44: on the CO-41
|
|
60
|
-
* grok-4.6 runs the model read "not a first name alone" as "skip them", and
|
|
61
|
-
* dropped people such as Amir and Sebastian, whose own entities carry only a
|
|
62
|
-
* first name. People were 24 of the 138 missed gold entities over three runs.
|
|
63
|
-
* The plan carries `first_name_people: true`, and apply then defers a one-word
|
|
64
|
-
* person whose word starts an existing person's fuller name or alias, since
|
|
65
|
-
* the model cannot see that `John Smith` already exists.
|
|
66
|
-
*/
|
|
67
|
-
export const FIRST_NAME_PEOPLE_FLAG = "checkpoint.ontology.first-name-people";
|
|
68
|
-
/**
|
|
69
|
-
* A knowledge input does not link to a concept entity whose canonical name is
|
|
70
|
-
* also the canonical name of an entity of another type (`concept/hq-secrets`
|
|
71
|
-
* beside `project/hq-secrets`). The plan carries
|
|
72
|
-
* `skip_ambiguous_concept_links: true`, and apply rejects the link as
|
|
73
|
-
* `ambiguous_type`. CO-46: 34 of the 73 false positives on the knowledge
|
|
74
|
-
* fixtures were such concept links; skipping them raised precision from 0.733
|
|
75
|
-
* to 0.803 and cost recall 0.023 on a replay of six runs.
|
|
76
|
-
*/
|
|
77
|
-
export const SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG = "checkpoint.ontology.skip-ambiguous-concept-links";
|
|
78
|
-
/**
|
|
79
|
-
* A knowledge link to the weaker of two entities that share a canonical name
|
|
80
|
-
* across types is redirected to the sibling other entity files link to
|
|
81
|
-
* (`project/build-your-own-agi` beside `concept/build-your-own-agi`, or
|
|
82
|
-
* `project/hq-core 2` beside `project/hq-core` when a concept also holds the
|
|
83
|
-
* name). The plan carries `dominant_sibling_link: true`, and apply reports
|
|
84
|
-
* the move in `dominant_sibling_redirects` without writing the entity files.
|
|
85
|
-
* CO-60: a replay of six runs raised knowledge precision from 0.725 to 0.754
|
|
86
|
-
* and recall from 0.784 to 0.816, and never changed a create.
|
|
87
|
-
*/
|
|
88
|
-
export const DOMINANT_SIBLING_LINK_FLAG = "checkpoint.ontology.dominant-sibling-link";
|
|
89
|
-
/**
|
|
90
|
-
* A knowledge link to a non-person entity that 50 or more
|
|
91
|
-
* other entity files link to is rejected as `hub_under_mentioned` when the
|
|
92
|
-
* input names it fewer than 2 times (whole word, any case, canonical name
|
|
93
|
-
* only). The plan carries `hub_mention_floor: true`. CO-62: a replay of six
|
|
94
|
-
* runs raised knowledge precision from 0.753 to 0.781 at the same recall.
|
|
95
|
-
*/
|
|
96
|
-
export const HUB_MENTION_FLOOR_FLAG = "checkpoint.ontology.hub-mention-floor";
|
|
97
|
-
/**
|
|
98
|
-
* A knowledge concept candidate whose name is an alias of more than one
|
|
99
|
-
* entity, and which apply would defer as `ambiguous_alias`, links instead to
|
|
100
|
-
* the one project whose canonical name it is (`HQ` typed as a concept beside
|
|
101
|
-
* `project/HQ` and several concepts aliased `HQ`). The plan carries
|
|
102
|
-
* `concept_project_link: true`. CO-51: over six runs of the shipping config
|
|
103
|
-
* the model typed HQ as a concept in some runs and not others, and the
|
|
104
|
-
* deferral lost 61 gold links; a replay of this rule raised recall from 0.639
|
|
105
|
-
* to 0.709 at precision 0.747 to 0.731 (61 of the 63 added links are HQ).
|
|
106
|
-
*/
|
|
107
|
-
export const CONCEPT_PROJECT_LINK_FLAG = "checkpoint.ontology.concept-project-link";
|
|
108
|
-
/**
|
|
109
|
-
* A knowledge link to the company's own company entity, or a
|
|
110
|
-
* concept-project-link redirect, needs evidence that names the entity as a
|
|
111
|
-
* word: the candidate name or one of its aliases, not inside a path, domain,
|
|
112
|
-
* handle, tag or identifier (`indigoai-us/hq`, `getindigo.ai`,
|
|
113
|
-
* `scope: company:indigo`). The plan carries `named_evidence: true`, and apply
|
|
114
|
-
* rejects such a link as `unnamed_in_evidence`. CO-57: on the six CO-52 runs,
|
|
115
|
-
* 67 of 143 false positives were links to company/indigo and project/HQ, most
|
|
116
|
-
* on evidence that only held the name inside an identifier; a replay of this
|
|
117
|
-
* rule raised precision on the concept-project-link arm from 0.705 to 0.750
|
|
118
|
-
* and lost no gold link.
|
|
119
|
-
*/
|
|
120
|
-
export const NAMED_EVIDENCE_FLAG = "checkpoint.ontology.named-evidence";
|
|
121
34
|
/**
|
|
122
35
|
* CO-59: consume the files touched by company checkpoints that were queued
|
|
123
36
|
* behind a busy sibling (see queued-touches.ts). On, the plan adds the
|
|
@@ -139,7 +52,6 @@ export const BUDGET = {
|
|
|
139
52
|
chunkTokens: 12_000,
|
|
140
53
|
};
|
|
141
54
|
export const READ_PATH_INSTRUCTION = "When an input has read_path, read that file (relative to the run directory) instead of path, with the same line range. It is the same text with an older annotator's entity lists and entity links removed.";
|
|
142
|
-
export const PERSON_NAME_INSTRUCTION = "Name a person by the full name the input or the company's entities give, not a first name alone.";
|
|
143
55
|
export const FIRST_NAME_PEOPLE_INSTRUCTION = "Name a person by the full name the input or the company's entities give. When neither gives more than a first name, name the person by that first name; do not leave them out.";
|
|
144
56
|
const CANDIDATE_INSTRUCTIONS = [
|
|
145
57
|
`Write ${CANDIDATES_FILE} in the run directory as {"schema":"${CANDIDATES_SCHEMA}","candidates":[...]} and nothing else.`,
|
|
@@ -317,17 +229,18 @@ export async function planOntologyRun(options) {
|
|
|
317
229
|
fs.rmSync(backendPreferencePath(hqRoot, company), { force: true });
|
|
318
230
|
return { status: "skipped", reason: syncClient && !syncClient.ok ? syncClient.reason : "flag off" };
|
|
319
231
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
232
|
+
// Was checkpoint.ontology.ignore-annotations, always on since CO-68.
|
|
233
|
+
// Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
234
|
+
// with stripped inputs lost 0.139 recall; Grok with stripped inputs gained
|
|
235
|
+
// precision without losing recall. A machine that falls back from grok keeps
|
|
236
|
+
// the annotated inputs.
|
|
237
|
+
const ignoreAnnotations = payload.backend === "grok";
|
|
238
|
+
// Was checkpoint.ontology.grok-backend, always on since CO-68.
|
|
239
|
+
// With `--backend auto`, `hq core checkpoint` tries grok first for this
|
|
240
|
+
// company. Read here, in the detached sibling, and recorded in the company's
|
|
241
|
+
// state directory, so the foreground checkpoint makes no network call.
|
|
242
|
+
recordBackendPreference(hqRoot, company, true);
|
|
323
243
|
const multiWriterOn = await options.readFlag(MULTI_WRITER_FLAG, company);
|
|
324
|
-
const noLinkAliases = await options.readFlag(NO_LINK_ALIASES_FLAG, company);
|
|
325
|
-
const firstNamePeople = await options.readFlag(FIRST_NAME_PEOPLE_FLAG, company);
|
|
326
|
-
const skipAmbiguousConceptLinks = await options.readFlag(SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG, company);
|
|
327
|
-
const dominantSiblingLink = await options.readFlag(DOMINANT_SIBLING_LINK_FLAG, company);
|
|
328
|
-
const hubMentionFloor = await options.readFlag(HUB_MENTION_FLOOR_FLAG, company);
|
|
329
|
-
const conceptProjectLink = await options.readFlag(CONCEPT_PROJECT_LINK_FLAG, company);
|
|
330
|
-
const namedEvidence = await options.readFlag(NAMED_EVIDENCE_FLAG, company);
|
|
331
244
|
const queuedTouchesOn = borrowed || (await options.readFlag(QUEUED_TOUCHES_FLAG, company));
|
|
332
245
|
// A borrowed company's run belongs to another session: its own files and
|
|
333
246
|
// session text are not this company's input.
|
|
@@ -343,7 +256,8 @@ export async function planOntologyRun(options) {
|
|
|
343
256
|
// a plan still merges set-aside versions and records the brief; the brief
|
|
344
257
|
// follows merge-back so its counts include merged entities.
|
|
345
258
|
const multiWriter = multiWriterOn ? startMultiWriterRun(hqRoot, company, options.now ?? new Date()) : undefined;
|
|
346
|
-
|
|
259
|
+
// Was checkpoint.ontology.company-brief, always on since CO-68.
|
|
260
|
+
const companyBrief = writeCompanyBrief(hqRoot, company, options.now ?? new Date());
|
|
347
261
|
const pending = Object.entries(ledger.entries)
|
|
348
262
|
.filter(([, entry]) => entry.state === "pending" && entry.reason === "session-touched")
|
|
349
263
|
.sort(([aRel, a], [bRel, b]) => a.mtime_ms - b.mtime_ms || aRel.localeCompare(bRel));
|
|
@@ -446,13 +360,72 @@ export async function planOntologyRun(options) {
|
|
|
446
360
|
}
|
|
447
361
|
: {}),
|
|
448
362
|
...(multiWriter ? { multi_writer: multiWriter } : {}),
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
363
|
+
// Was checkpoint.ontology.no-link-aliases, always on since CO-68.
|
|
364
|
+
// A candidate that links to an existing entity adds no aliases to it; the plan
|
|
365
|
+
// carries `link_aliases: false` and apply honours it. CO-42: most aliases
|
|
366
|
+
// written through links on the CO-41 runs were other things the text named
|
|
367
|
+
// next to the entity (`Publisher` on indigo, `Stripe Connect` on Stripe), and
|
|
368
|
+
// entity-hints multiplied them (22 aliases on a flag-off run, 45 and 125 on two
|
|
369
|
+
// hinted runs).
|
|
370
|
+
link_aliases: false,
|
|
371
|
+
// Was checkpoint.ontology.first-name-people, always on since CO-68.
|
|
372
|
+
// A person the input and the company's entities give only a first name for is
|
|
373
|
+
// named by that first name instead of being left out. CO-44: on the CO-41
|
|
374
|
+
// grok-4.6 runs the model read "not a first name alone" as "skip them", and
|
|
375
|
+
// dropped people such as Amir and Sebastian, whose own entities carry only a
|
|
376
|
+
// first name. People were 24 of the 138 missed gold entities over three runs.
|
|
377
|
+
// The plan carries `first_name_people: true`, and apply then defers a one-word
|
|
378
|
+
// person whose word starts an existing person's fuller name or alias, since
|
|
379
|
+
// the model cannot see that `John Smith` already exists.
|
|
380
|
+
first_name_people: true,
|
|
381
|
+
// Was checkpoint.ontology.skip-ambiguous-concept-links, always on since CO-68.
|
|
382
|
+
// A knowledge input does not link to a concept entity whose canonical name is
|
|
383
|
+
// also the canonical name of an entity of another type (`concept/hq-secrets`
|
|
384
|
+
// beside `project/hq-secrets`). The plan carries
|
|
385
|
+
// `skip_ambiguous_concept_links: true`, and apply rejects the link as
|
|
386
|
+
// `ambiguous_type`. CO-46: 34 of the 73 false positives on the knowledge
|
|
387
|
+
// fixtures were such concept links; skipping them raised precision from 0.733
|
|
388
|
+
// to 0.803 and cost recall 0.023 on a replay of six runs.
|
|
389
|
+
skip_ambiguous_concept_links: true,
|
|
390
|
+
// Was checkpoint.ontology.dominant-sibling-link, always on since CO-68.
|
|
391
|
+
// A knowledge link to the weaker of two entities that share a canonical name
|
|
392
|
+
// across types is redirected to the sibling other entity files link to
|
|
393
|
+
// (`project/build-your-own-agi` beside `concept/build-your-own-agi`, or
|
|
394
|
+
// `project/hq-core 2` beside `project/hq-core` when a concept also holds the
|
|
395
|
+
// name). The plan carries `dominant_sibling_link: true`, and apply reports
|
|
396
|
+
// the move in `dominant_sibling_redirects` without writing the entity files.
|
|
397
|
+
// CO-60: a replay of six runs raised knowledge precision from 0.725 to 0.754
|
|
398
|
+
// and recall from 0.784 to 0.816, and never changed a create.
|
|
399
|
+
dominant_sibling_link: true,
|
|
400
|
+
// Was checkpoint.ontology.hub-mention-floor, always on since CO-68.
|
|
401
|
+
// A knowledge link to a non-person entity that 50 or more
|
|
402
|
+
// other entity files link to is rejected as `hub_under_mentioned` when the
|
|
403
|
+
// input names it fewer than 2 times (whole word, any case, canonical name
|
|
404
|
+
// only). The plan carries `hub_mention_floor: true`. CO-62: a replay of six
|
|
405
|
+
// runs raised knowledge precision from 0.753 to 0.781 at the same recall.
|
|
406
|
+
hub_mention_floor: true,
|
|
407
|
+
// Was checkpoint.ontology.concept-project-link, always on since CO-68.
|
|
408
|
+
// A knowledge concept candidate whose name is an alias of more than one
|
|
409
|
+
// entity, and which apply would defer as `ambiguous_alias`, links instead to
|
|
410
|
+
// the one project whose canonical name it is (`HQ` typed as a concept beside
|
|
411
|
+
// `project/HQ` and several concepts aliased `HQ`). The plan carries
|
|
412
|
+
// `concept_project_link: true`. CO-51: over six runs of the shipping config
|
|
413
|
+
// the model typed HQ as a concept in some runs and not others, and the
|
|
414
|
+
// deferral lost 61 gold links; a replay of this rule raised recall from 0.639
|
|
415
|
+
// to 0.709 at precision 0.747 to 0.731 (61 of the 63 added links are HQ).
|
|
416
|
+
concept_project_link: true,
|
|
417
|
+
// Was checkpoint.ontology.named-evidence, always on since CO-68.
|
|
418
|
+
// A knowledge link to the company's own company entity, or a
|
|
419
|
+
// concept-project-link redirect, needs evidence that names the entity as a
|
|
420
|
+
// word: the candidate name or one of its aliases, not inside a path, domain,
|
|
421
|
+
// handle, tag or identifier (`indigoai-us/hq`, `getindigo.ai`,
|
|
422
|
+
// `scope: company:indigo`). The plan carries `named_evidence: true`, and apply
|
|
423
|
+
// rejects such a link as `unnamed_in_evidence`. CO-57: on the six CO-52 runs,
|
|
424
|
+
// 67 of 143 false positives were links to company/indigo and project/HQ, most
|
|
425
|
+
// on evidence that only held the name inside an identifier; a replay of this
|
|
426
|
+
// rule raised precision on the concept-project-link arm from 0.705 to 0.750
|
|
427
|
+
// and lost no gold link.
|
|
428
|
+
named_evidence: true,
|
|
456
429
|
inputs,
|
|
457
430
|
session_text: sessionText,
|
|
458
431
|
pending_after_run: remaining,
|
|
@@ -466,7 +439,7 @@ export async function planOntologyRun(options) {
|
|
|
466
439
|
instructions: [
|
|
467
440
|
...CANDIDATE_INSTRUCTIONS.slice(0, 3),
|
|
468
441
|
...(inputs.some((input) => input.read_path) ? [READ_PATH_INSTRUCTION] : []),
|
|
469
|
-
`${CANDIDATE_INSTRUCTIONS[3]} ${
|
|
442
|
+
`${CANDIDATE_INSTRUCTIONS[3]} ${FIRST_NAME_PEOPLE_INSTRUCTION}`,
|
|
470
443
|
...CANDIDATE_INSTRUCTIONS.slice(4),
|
|
471
444
|
],
|
|
472
445
|
};
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* overlap.
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
|
-
import { probeProviderCompactionsSince } from "./compaction.js";
|
|
12
11
|
import { tryReadLane } from "./store.js";
|
|
13
12
|
import { type LaneRecord, type LaneState, type QuestionRecord } from "./types.js";
|
|
14
13
|
export declare const DROPBOX_DIR = "dropbox";
|
|
@@ -59,7 +58,7 @@ export interface LaneDrainHookInput {
|
|
|
59
58
|
}
|
|
60
59
|
export interface RunDrainHookDeps {
|
|
61
60
|
readLane?: typeof tryReadLane;
|
|
62
|
-
probeCompactionsSince?: typeof probeProviderCompactionsSince;
|
|
61
|
+
probeCompactionsSince?: typeof import("./compaction.js").probeProviderCompactionsSince;
|
|
63
62
|
}
|
|
64
63
|
export declare function mintClaimId(): string;
|
|
65
64
|
export declare function assertSafeLaneId(laneId: string): void;
|
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import * as path from "node:path";
|
|
13
|
-
import { probeProviderCompactionsSince, providerTranscriptSize, } from "./compaction.js";
|
|
14
13
|
import { LanesError } from "./errors.js";
|
|
15
|
-
import { MESSAGE_COMPACTION_REPLAY_NOTE, MESSAGE_REPLAY_STATE_NAME, } from "./message-replay.js";
|
|
14
|
+
import { MESSAGE_COMPACTION_REPLAY_NOTE, MESSAGE_REPLAY_STATE_NAME, readMessageReplayState, updateMessageReplayTranscript, writeMessageReplayState, } from "./message-replay.js";
|
|
16
15
|
import { LANES_RUNS_REL, readProcStarttime } from "./spawn.js";
|
|
17
16
|
import { tryReadLane } from "./store.js";
|
|
18
17
|
export const DROPBOX_DIR = "dropbox";
|
|
@@ -430,83 +429,6 @@ export function hookAdditionalContextJson(messages, hookEventName = "PreToolUse"
|
|
|
430
429
|
function replayStatePath(hqRoot, laneId) {
|
|
431
430
|
return path.join(dropboxDir(hqRoot, laneId), MESSAGE_REPLAY_STATE_NAME);
|
|
432
431
|
}
|
|
433
|
-
function safeProviderTranscriptPath(transcriptPath, provider, env) {
|
|
434
|
-
if (provider === "grok")
|
|
435
|
-
return transcriptPath;
|
|
436
|
-
const home = provider === "claude" ? env.CLAUDE_CONFIG_DIR : env.CODEX_HOME;
|
|
437
|
-
if (!home) {
|
|
438
|
-
throw new LanesError("message_replay_provider_home_missing", `Cannot validate ${provider} transcript without its provider home`);
|
|
439
|
-
}
|
|
440
|
-
if (!path.isAbsolute(transcriptPath) || path.extname(transcriptPath) !== ".jsonl") {
|
|
441
|
-
throw new LanesError("message_replay_transcript_path_invalid", `The ${provider} hook transcript path must be an absolute JSONL path`);
|
|
442
|
-
}
|
|
443
|
-
const configuredHomePath = path.resolve(home);
|
|
444
|
-
const homePath = fs.realpathSync(home);
|
|
445
|
-
const candidate = path.resolve(transcriptPath);
|
|
446
|
-
const expectedRoot = provider === "claude" ? "projects" : "sessions";
|
|
447
|
-
const assertWithinProviderHome = (root, resolvedTranscript, message) => {
|
|
448
|
-
const relative = path.relative(root, resolvedTranscript);
|
|
449
|
-
if (relative === "" ||
|
|
450
|
-
relative === ".." ||
|
|
451
|
-
relative.startsWith(`..${path.sep}`) ||
|
|
452
|
-
path.isAbsolute(relative) ||
|
|
453
|
-
relative.split(path.sep)[0] !== expectedRoot) {
|
|
454
|
-
throw new LanesError("message_replay_transcript_outside_provider_home", message);
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
try {
|
|
458
|
-
const realTranscript = fs.realpathSync(candidate);
|
|
459
|
-
assertWithinProviderHome(homePath, realTranscript, `The ${provider} hook transcript resolves outside its ${expectedRoot} directory`);
|
|
460
|
-
return realTranscript;
|
|
461
|
-
}
|
|
462
|
-
catch (err) {
|
|
463
|
-
if (err.code === "ENOENT") {
|
|
464
|
-
assertWithinProviderHome(configuredHomePath, candidate, `The ${provider} hook transcript is outside its configured ${expectedRoot} directory`);
|
|
465
|
-
return candidate;
|
|
466
|
-
}
|
|
467
|
-
throw err;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
function readReplayState(filePath, turnId) {
|
|
471
|
-
const text = fs.readFileSync(filePath, "utf8");
|
|
472
|
-
let parsed;
|
|
473
|
-
try {
|
|
474
|
-
parsed = JSON.parse(text);
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
478
|
-
}
|
|
479
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
480
|
-
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} is not an object`);
|
|
481
|
-
}
|
|
482
|
-
const state = parsed;
|
|
483
|
-
if (state.schema_version !== 1 ||
|
|
484
|
-
state.turn_id !== turnId ||
|
|
485
|
-
(state.last_compaction_marker !== null &&
|
|
486
|
-
(!Number.isSafeInteger(state.last_compaction_marker) || Number(state.last_compaction_marker) < 0)) ||
|
|
487
|
-
(state.transcript_offset !== undefined &&
|
|
488
|
-
(!Number.isSafeInteger(state.transcript_offset) || Number(state.transcript_offset) < 0)) ||
|
|
489
|
-
(state.transcript_path !== undefined && typeof state.transcript_path !== "string") ||
|
|
490
|
-
(state.flag_enabled !== undefined && typeof state.flag_enabled !== "boolean")) {
|
|
491
|
-
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} does not match the current provider turn`);
|
|
492
|
-
}
|
|
493
|
-
// Older state files persisted the removed per-turn flag. Accept it, but
|
|
494
|
-
// remove it before the next write so the stale cache does not live on.
|
|
495
|
-
delete state.flag_enabled;
|
|
496
|
-
return state;
|
|
497
|
-
}
|
|
498
|
-
function writeReplayState(filePath, state) {
|
|
499
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
500
|
-
const tempPath = `${filePath}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
501
|
-
fs.writeFileSync(tempPath, `${JSON.stringify(state)}\n`, { mode: 0o600 });
|
|
502
|
-
try {
|
|
503
|
-
fs.renameSync(tempPath, filePath);
|
|
504
|
-
}
|
|
505
|
-
catch (err) {
|
|
506
|
-
fs.rmSync(tempPath, { force: true });
|
|
507
|
-
throw err;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
432
|
function deliveredMessagesForTurn(hqRoot, laneId, turnId) {
|
|
511
433
|
const delivered = path.join(dropboxDir(hqRoot, laneId), DELIVERED_DIR);
|
|
512
434
|
let names;
|
|
@@ -522,7 +444,8 @@ function deliveredMessagesForTurn(hqRoot, laneId, turnId) {
|
|
|
522
444
|
return names.flatMap((name) => readClaimedFile(path.join(delivered, name), defaultDrainDeps()));
|
|
523
445
|
}
|
|
524
446
|
function reportReplayFailure(err) {
|
|
525
|
-
|
|
447
|
+
const code = err instanceof LanesError ? `${err.error}: ` : "";
|
|
448
|
+
return `hq-lanes-drain: message compaction replay failed: ${code}${err instanceof Error ? err.message : String(err)}\n`;
|
|
526
449
|
}
|
|
527
450
|
/**
|
|
528
451
|
* Hook protocol. HQ_LANE_ID absent or empty → exit 0, empty stdout, empty stderr.
|
|
@@ -564,7 +487,7 @@ export async function runDrainHook(env, input = {}, deps = {}) {
|
|
|
564
487
|
const statePath = replayStatePath(hqRoot, laneId);
|
|
565
488
|
let state;
|
|
566
489
|
try {
|
|
567
|
-
state =
|
|
490
|
+
state = readMessageReplayState(statePath, turnId);
|
|
568
491
|
}
|
|
569
492
|
catch (err) {
|
|
570
493
|
if (err.code !== "ENOENT")
|
|
@@ -576,38 +499,14 @@ export async function runDrainHook(env, input = {}, deps = {}) {
|
|
|
576
499
|
if (!lane) {
|
|
577
500
|
throw new LanesError("message_replay_lane_missing", `Lane ${laneId} could not be read for compaction replay`);
|
|
578
501
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (state.last_compaction_marker === null ||
|
|
585
|
-
state.transcript_path !== transcriptPath ||
|
|
586
|
-
state.transcript_offset === undefined) {
|
|
587
|
-
// Existing transcript history belongs to the turn's resume
|
|
588
|
-
// prompt. Start watching only records appended from this point.
|
|
589
|
-
state.last_compaction_marker = 0;
|
|
590
|
-
state.transcript_path = transcriptPath;
|
|
591
|
-
state.transcript_offset = providerTranscriptSize(transcriptPath);
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
const delta = (deps.probeCompactionsSince ?? probeProviderCompactionsSince)(transcriptPath, lane.provider, state.transcript_offset);
|
|
595
|
-
state.transcript_offset = delta.next_offset;
|
|
596
|
-
if (delta.replaced) {
|
|
597
|
-
// A replaced or truncated transcript establishes a new
|
|
598
|
-
// baseline; it is not evidence of a fresh compaction.
|
|
599
|
-
state.last_compaction_marker = 0;
|
|
600
|
-
}
|
|
601
|
-
else if (delta.count > 0) {
|
|
602
|
-
state.last_compaction_marker += delta.count;
|
|
603
|
-
replayed = deliveredMessagesForTurn(hqRoot, laneId, turnId);
|
|
604
|
-
if (replayed.length > 0)
|
|
605
|
-
replayNote = MESSAGE_COMPACTION_REPLAY_NOTE;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
502
|
+
const update = updateMessageReplayTranscript(state, input.transcript_path, lane.provider, env, deps.probeCompactionsSince);
|
|
503
|
+
if (update === "compacted") {
|
|
504
|
+
replayed = deliveredMessagesForTurn(hqRoot, laneId, turnId);
|
|
505
|
+
if (replayed.length > 0)
|
|
506
|
+
replayNote = MESSAGE_COMPACTION_REPLAY_NOTE;
|
|
608
507
|
}
|
|
609
508
|
}
|
|
610
|
-
|
|
509
|
+
writeMessageReplayState(statePath, state);
|
|
611
510
|
}
|
|
612
511
|
catch (err) {
|
|
613
512
|
// Replay is advisory hook context. Report failures but let the provider
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { probeProviderCompactionsSince } from "./compaction.js";
|
|
2
|
+
import type { LaneProvider } from "./types.js";
|
|
1
3
|
export declare const MESSAGE_REPLAY_STATE_NAME = "message-compaction-replay.json";
|
|
2
4
|
export declare const MESSAGE_COMPACTION_REPLAY_NOTE = "These messages were delivered before a context compaction and still apply.";
|
|
3
5
|
export interface MessageReplayState {
|
|
@@ -9,5 +11,11 @@ export interface MessageReplayState {
|
|
|
9
11
|
transcript_offset?: number;
|
|
10
12
|
/** Canonical transcript path for this provider turn. */
|
|
11
13
|
transcript_path?: string;
|
|
14
|
+
/** Provider needed to inspect appended transcript records without loading the lane store. */
|
|
15
|
+
provider?: LaneProvider;
|
|
12
16
|
}
|
|
17
|
+
export type ReplayTranscriptUpdate = "baseline" | "unchanged" | "replaced" | "compacted";
|
|
18
|
+
export declare function readMessageReplayState(filePath: string, turnId: string, onLegacyFlag?: () => void): MessageReplayState;
|
|
19
|
+
export declare function writeMessageReplayState(filePath: string, state: MessageReplayState): void;
|
|
20
|
+
export declare function updateMessageReplayTranscript(state: MessageReplayState, transcriptPath: string, provider: LaneProvider, env: NodeJS.ProcessEnv, probe?: typeof probeProviderCompactionsSince): ReplayTranscriptUpdate;
|
|
13
21
|
//# sourceMappingURL=message-replay.d.ts.map
|
|
@@ -1,3 +1,122 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { probeProviderCompactionsSince, providerTranscriptSize, } from "./compaction.js";
|
|
5
|
+
import { LanesError } from "./errors.js";
|
|
1
6
|
export const MESSAGE_REPLAY_STATE_NAME = "message-compaction-replay.json";
|
|
2
7
|
export const MESSAGE_COMPACTION_REPLAY_NOTE = "These messages were delivered before a context compaction and still apply.";
|
|
8
|
+
export function readMessageReplayState(filePath, turnId, onLegacyFlag) {
|
|
9
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(text);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
16
|
+
}
|
|
17
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
18
|
+
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} is not an object`);
|
|
19
|
+
}
|
|
20
|
+
const state = parsed;
|
|
21
|
+
if (state.schema_version !== 1 ||
|
|
22
|
+
state.turn_id !== turnId ||
|
|
23
|
+
(state.last_compaction_marker !== null &&
|
|
24
|
+
(!Number.isSafeInteger(state.last_compaction_marker) || Number(state.last_compaction_marker) < 0)) ||
|
|
25
|
+
(state.transcript_offset !== undefined &&
|
|
26
|
+
(!Number.isSafeInteger(state.transcript_offset) || Number(state.transcript_offset) < 0)) ||
|
|
27
|
+
(state.transcript_path !== undefined && typeof state.transcript_path !== "string") ||
|
|
28
|
+
(state.provider !== undefined && !isLaneProvider(state.provider)) ||
|
|
29
|
+
(state.flag_enabled !== undefined && typeof state.flag_enabled !== "boolean")) {
|
|
30
|
+
throw new LanesError("message_replay_state_invalid", `Replay state ${filePath} does not match the current provider turn`);
|
|
31
|
+
}
|
|
32
|
+
// Older state files persisted the removed per-turn flag. Accept it, but
|
|
33
|
+
// remove it before the next write so the stale cache does not live on.
|
|
34
|
+
if (state.flag_enabled !== undefined) {
|
|
35
|
+
delete state.flag_enabled;
|
|
36
|
+
onLegacyFlag?.();
|
|
37
|
+
}
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
function isLaneProvider(value) {
|
|
41
|
+
return value === "claude" || value === "codex" || value === "grok";
|
|
42
|
+
}
|
|
43
|
+
export function writeMessageReplayState(filePath, state) {
|
|
44
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
45
|
+
const tempPath = `${filePath}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
46
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(state)}\n`, { mode: 0o600 });
|
|
47
|
+
try {
|
|
48
|
+
fs.renameSync(tempPath, filePath);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
fs.rmSync(tempPath, { force: true });
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function safeProviderTranscriptPath(transcriptPath, provider, env) {
|
|
56
|
+
if (provider === "grok")
|
|
57
|
+
return transcriptPath;
|
|
58
|
+
const home = provider === "claude" ? env.CLAUDE_CONFIG_DIR : env.CODEX_HOME;
|
|
59
|
+
if (!home) {
|
|
60
|
+
throw new LanesError("message_replay_provider_home_missing", `Cannot validate ${provider} transcript without its provider home`);
|
|
61
|
+
}
|
|
62
|
+
if (!path.isAbsolute(transcriptPath) || path.extname(transcriptPath) !== ".jsonl") {
|
|
63
|
+
throw new LanesError("message_replay_transcript_path_invalid", `The ${provider} hook transcript path must be an absolute JSONL path`);
|
|
64
|
+
}
|
|
65
|
+
const configuredHomePath = path.resolve(home);
|
|
66
|
+
const homePath = fs.realpathSync(home);
|
|
67
|
+
const candidate = path.resolve(transcriptPath);
|
|
68
|
+
const expectedRoot = provider === "claude" ? "projects" : "sessions";
|
|
69
|
+
const assertWithinProviderHome = (root, resolvedTranscript, message) => {
|
|
70
|
+
const relative = path.relative(root, resolvedTranscript);
|
|
71
|
+
if (relative === "" ||
|
|
72
|
+
relative === ".." ||
|
|
73
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
74
|
+
path.isAbsolute(relative) ||
|
|
75
|
+
relative.split(path.sep)[0] !== expectedRoot) {
|
|
76
|
+
throw new LanesError("message_replay_transcript_outside_provider_home", message);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const realTranscript = fs.realpathSync(candidate);
|
|
81
|
+
assertWithinProviderHome(homePath, realTranscript, `The ${provider} hook transcript resolves outside its ${expectedRoot} directory`);
|
|
82
|
+
return realTranscript;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err.code === "ENOENT") {
|
|
86
|
+
assertWithinProviderHome(configuredHomePath, candidate, `The ${provider} hook transcript is outside its configured ${expectedRoot} directory`);
|
|
87
|
+
return candidate;
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function updateMessageReplayTranscript(state, transcriptPath, provider, env, probe = probeProviderCompactionsSince) {
|
|
93
|
+
if (provider === "grok") {
|
|
94
|
+
state.provider = provider;
|
|
95
|
+
state.last_compaction_marker = 0;
|
|
96
|
+
return "unchanged";
|
|
97
|
+
}
|
|
98
|
+
const canonicalPath = safeProviderTranscriptPath(transcriptPath, provider, env);
|
|
99
|
+
const providerChanged = state.provider !== undefined && state.provider !== provider;
|
|
100
|
+
state.provider = provider;
|
|
101
|
+
if (providerChanged ||
|
|
102
|
+
state.last_compaction_marker === null ||
|
|
103
|
+
state.transcript_path !== canonicalPath ||
|
|
104
|
+
state.transcript_offset === undefined) {
|
|
105
|
+
state.last_compaction_marker = 0;
|
|
106
|
+
state.transcript_path = canonicalPath;
|
|
107
|
+
state.transcript_offset = providerTranscriptSize(canonicalPath);
|
|
108
|
+
return "baseline";
|
|
109
|
+
}
|
|
110
|
+
const delta = probe(canonicalPath, provider, state.transcript_offset);
|
|
111
|
+
state.transcript_offset = delta.next_offset;
|
|
112
|
+
if (delta.replaced) {
|
|
113
|
+
state.last_compaction_marker = 0;
|
|
114
|
+
return "replaced";
|
|
115
|
+
}
|
|
116
|
+
if (delta.count > 0) {
|
|
117
|
+
state.last_compaction_marker += delta.count;
|
|
118
|
+
return "compacted";
|
|
119
|
+
}
|
|
120
|
+
return "unchanged";
|
|
121
|
+
}
|
|
3
122
|
//# sourceMappingURL=message-replay.js.map
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -532,26 +532,44 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
532
532
|
const details = raw.queued_details;
|
|
533
533
|
if (details === null || typeof details !== "object" || Array.isArray(details) ||
|
|
534
534
|
typeof details.reason !== "string") {
|
|
535
|
-
throw new LaneSchemaError("queued_details must describe a
|
|
535
|
+
throw new LaneSchemaError("queued_details must describe a supported queue condition", {
|
|
536
536
|
path: filePath,
|
|
537
537
|
field: "queued_details",
|
|
538
538
|
});
|
|
539
539
|
}
|
|
540
540
|
const detailsRecord = details;
|
|
541
|
-
if (detailsRecord.reason
|
|
542
|
-
(detailsRecord.expected_hash !== null && typeof detailsRecord.expected_hash !== "string") ||
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
541
|
+
if (detailsRecord.reason === "worker_profile_changed") {
|
|
542
|
+
if ((detailsRecord.expected_hash !== null && typeof detailsRecord.expected_hash !== "string") ||
|
|
543
|
+
typeof detailsRecord.actual_hash !== "string" || detailsRecord.actual_hash === "" ||
|
|
544
|
+
typeof detailsRecord.recovery_command !== "string" || detailsRecord.recovery_command.trim() === "") {
|
|
545
|
+
throw new LaneSchemaError("queued_details must describe a worker_profile_changed queue reason", {
|
|
546
|
+
path: filePath,
|
|
547
|
+
field: "queued_details",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (raw.queued_reason !== "worker_profile_changed") {
|
|
551
|
+
throw new LaneSchemaError("queued_details requires queued_reason worker_profile_changed", {
|
|
552
|
+
path: filePath,
|
|
553
|
+
field: "queued_details",
|
|
554
|
+
queued_reason: raw.queued_reason,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else if (detailsRecord.reason === "outpost_dispatch_unknown") {
|
|
559
|
+
if (raw.state !== "queued" ||
|
|
560
|
+
detailsRecord.outcome_unknown !== true ||
|
|
561
|
+
(detailsRecord.command_id !== null &&
|
|
562
|
+
(typeof detailsRecord.command_id !== "string" || detailsRecord.command_id.trim() === ""))) {
|
|
563
|
+
throw new LaneSchemaError("queued_details must describe an unknown Outpost dispatch on a queued lane", {
|
|
564
|
+
path: filePath,
|
|
565
|
+
field: "queued_details",
|
|
566
|
+
});
|
|
567
|
+
}
|
|
549
568
|
}
|
|
550
|
-
|
|
551
|
-
throw new LaneSchemaError("queued_details
|
|
569
|
+
else {
|
|
570
|
+
throw new LaneSchemaError("queued_details must describe a supported queue condition", {
|
|
552
571
|
path: filePath,
|
|
553
572
|
field: "queued_details",
|
|
554
|
-
queued_reason: raw.queued_reason,
|
|
555
573
|
});
|
|
556
574
|
}
|
|
557
575
|
}
|
|
@@ -175,12 +175,16 @@ export declare const LANE_PROVIDERS: readonly ["claude", "codex", "grok"];
|
|
|
175
175
|
export type LaneProvider = (typeof LANE_PROVIDERS)[number];
|
|
176
176
|
export declare const QUEUED_REASONS: readonly ["cap", "provider_cap", "account_cap", "profile_cap", "queue_position", "throttle", "provider_binary_missing", "account_retry", "worker_profile_changed"];
|
|
177
177
|
export type QueuedReason = (typeof QUEUED_REASONS)[number];
|
|
178
|
-
export
|
|
178
|
+
export type QueuedDetails = {
|
|
179
179
|
reason: "worker_profile_changed";
|
|
180
180
|
expected_hash: string | null;
|
|
181
181
|
actual_hash: string;
|
|
182
182
|
recovery_command: string;
|
|
183
|
-
}
|
|
183
|
+
} | {
|
|
184
|
+
reason: "outpost_dispatch_unknown";
|
|
185
|
+
outcome_unknown: true;
|
|
186
|
+
command_id: string | null;
|
|
187
|
+
};
|
|
184
188
|
export declare function isQueuedReason(value: unknown): value is QueuedReason;
|
|
185
189
|
export declare const HOST_KINDS: readonly ["local", "outpost"];
|
|
186
190
|
export type HostKind = (typeof HOST_KINDS)[number];
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -231,7 +231,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
231
231
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
232
232
|
last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
233
233
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
234
|
-
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
234
|
+
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command", "reconcile"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
235
235
|
quota_reset_at: ["watcher", "reconcile"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
236
236
|
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], provider_home_at_spawn: ["spawn"], last_resume: ["spawn"], last_run_usage: ["watcher"], last_run_usage_at: ["watcher"], envelope_repair: ["watcher", "reconcile"], envelope_validation_errors: ["watcher"], last_progress_event_at: ["watcher"],
|
|
237
237
|
};
|