@indigoai-us/hq-cli 5.230.1 → 5.231.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 +22 -0
- package/dist/command-catalog.generated.d.ts +3 -0
- package/dist/command-catalog.generated.js +4 -0
- package/dist/commands/lanes.js +74 -14
- package/dist/lib/checkpoint-ontology/apply.d.ts +7 -0
- package/dist/lib/checkpoint-ontology/apply.js +30 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +15 -0
- package/dist/lib/checkpoint-ontology/plan.js +17 -2
- package/dist/lib/lanes/grants.d.ts +4 -4
- package/dist/lib/lanes/grants.js +21 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.231.0] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Behind the off-by-default `checkpoint.ontology.first-name-people` flag, the
|
|
10
|
+
checkpoint ontology instructions tell the model to name a person by their
|
|
11
|
+
first name when neither the input nor the company's entities give more,
|
|
12
|
+
instead of leaving them out. With the flag on, apply also defers a one-word
|
|
13
|
+
person whose word starts an existing person's fuller name or alias (`John`
|
|
14
|
+
when `John Smith` exists), since the model cannot see the entity tree. With
|
|
15
|
+
the flag off the instructions and apply are unchanged.
|
|
16
|
+
|
|
17
|
+
## [5.230.2] — 2026-09-27
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Resuming a terminal Outpost lane whose senior lives on the primary now checks
|
|
22
|
+
hire-grant capacity on both hosts using the primary hierarchy, then syncs the
|
|
23
|
+
running owner record back to the primary. An owner CLI that rejects the
|
|
24
|
+
hierarchy context now fails with an upgrade-required error instead of retrying
|
|
25
|
+
without the grant check.
|
|
26
|
+
|
|
5
27
|
## [5.230.1] — 2026-09-27
|
|
6
28
|
|
|
7
29
|
### Fixed
|
|
@@ -8056,6 +8056,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8056
8056
|
}, {
|
|
8057
8057
|
readonly flags: "--worker <profile>";
|
|
8058
8058
|
readonly description: "Attach a personal worker profile for the resumed turn";
|
|
8059
|
+
}, {
|
|
8060
|
+
readonly flags: "--grant-context <base64url>";
|
|
8061
|
+
readonly description: "";
|
|
8059
8062
|
}, {
|
|
8060
8063
|
readonly flags: "--keep-alive";
|
|
8061
8064
|
readonly description: "Automatically resume done lead lanes while their story remains open";
|
|
@@ -10421,6 +10421,10 @@ export const COMMAND_CATALOG = [
|
|
|
10421
10421
|
"flags": "--worker <profile>",
|
|
10422
10422
|
"description": "Attach a personal worker profile for the resumed turn"
|
|
10423
10423
|
},
|
|
10424
|
+
{
|
|
10425
|
+
"flags": "--grant-context <base64url>",
|
|
10426
|
+
"description": ""
|
|
10427
|
+
},
|
|
10424
10428
|
{
|
|
10425
10429
|
"flags": "--keep-alive",
|
|
10426
10430
|
"description": "Automatically resume done lead lanes while their story remains open"
|
package/dist/commands/lanes.js
CHANGED
|
@@ -1039,15 +1039,36 @@ async function mapWithConcurrency(values, concurrency, map) {
|
|
|
1039
1039
|
await Promise.all(workers);
|
|
1040
1040
|
return results;
|
|
1041
1041
|
}
|
|
1042
|
-
async function withResumeHireAdmission(ctx, lane, fn) {
|
|
1042
|
+
async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
|
|
1043
1043
|
const hqRoot = ctx.hqRoot();
|
|
1044
|
+
if (grantContext !== undefined && (!ctx.isSsmOwnerDispatch() ||
|
|
1045
|
+
lane.host.kind !== "outpost" ||
|
|
1046
|
+
!laneIsHostedHere(ctx, lane) ||
|
|
1047
|
+
lane.senior.kind !== "lane" ||
|
|
1048
|
+
!sameHireGrantSenior(lane.senior, grantContext.expected_senior))) {
|
|
1049
|
+
throw new LanesError("hire_grant_senior_changed", `Lane ${lane.lane_id}'s senior changed after the primary authorized its resume`, { lane_id: lane.lane_id, expected_senior: grantContext.expected_senior, current_senior: lane.senior });
|
|
1050
|
+
}
|
|
1044
1051
|
if (!isTerminalLaneState(lane.state) ||
|
|
1045
1052
|
lane.senior.kind !== "lane" ||
|
|
1046
1053
|
!lane.senior.id) {
|
|
1047
1054
|
return fn();
|
|
1048
1055
|
}
|
|
1056
|
+
if (grantContext === undefined &&
|
|
1057
|
+
lane.host.kind === "outpost" &&
|
|
1058
|
+
laneIsHostedHere(ctx, lane) &&
|
|
1059
|
+
!listLaneIds(hqRoot).includes(lane.senior.id)) {
|
|
1060
|
+
throw new LanesError("hire_grant_context_required", `Resume of Outpost lane ${lane.lane_id} requires the primary senior's hire-grant context`, { lane_id: lane.lane_id, senior_lane_id: lane.senior.id });
|
|
1061
|
+
}
|
|
1049
1062
|
return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
|
|
1050
1063
|
const current = readLane(hqRoot, lane.lane_id);
|
|
1064
|
+
if (grantContext !== undefined &&
|
|
1065
|
+
!sameHireGrantSenior(current.senior, grantContext.expected_senior)) {
|
|
1066
|
+
throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed before the Outpost resumed it`, {
|
|
1067
|
+
lane_id: current.lane_id,
|
|
1068
|
+
expected_senior: grantContext.expected_senior,
|
|
1069
|
+
current_senior: current.senior,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1051
1072
|
// A different resume already reactivated this lane; it occupies the slot
|
|
1052
1073
|
// in the shared snapshot and is not a second hire.
|
|
1053
1074
|
if (!isTerminalLaneState(current.state))
|
|
@@ -1056,7 +1077,7 @@ async function withResumeHireAdmission(ctx, lane, fn) {
|
|
|
1056
1077
|
if (refusal !== null)
|
|
1057
1078
|
throw refusal;
|
|
1058
1079
|
return fn();
|
|
1059
|
-
});
|
|
1080
|
+
}, grantContext?.hierarchy);
|
|
1060
1081
|
}
|
|
1061
1082
|
const CAPACITY_PROBE_TIMEOUT_MS = 15_000;
|
|
1062
1083
|
const MAX_PLACEMENT_OUTPOSTS = 16;
|
|
@@ -3569,6 +3590,10 @@ function outpostRejectedProfileRepinOption(result) {
|
|
|
3569
3590
|
return result.exitCode !== 0 &&
|
|
3570
3591
|
/unknown option\s+(?:['"])?--profile-repin-context(?:['"])?(?:\s|$)/i.test(`${result.stderr}\n${result.stdout}`);
|
|
3571
3592
|
}
|
|
3593
|
+
function outpostRejectedGrantContextOption(result) {
|
|
3594
|
+
return result.exitCode !== 0 &&
|
|
3595
|
+
/unknown option\s+(?:['"])?--grant-context(?:['"])?(?:\s|$)/i.test(`${result.stderr}\n${result.stdout}`);
|
|
3596
|
+
}
|
|
3572
3597
|
function verifyOutpostProfileContent(hqRoot, lane) {
|
|
3573
3598
|
if (lane.host.kind !== "outpost" || lane.worker_profile === undefined)
|
|
3574
3599
|
return;
|
|
@@ -3713,9 +3738,14 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3713
3738
|
grantHierarchy = hierarchy;
|
|
3714
3739
|
grantContext = createOutpostHireGrantContext(lane, hierarchy);
|
|
3715
3740
|
}
|
|
3741
|
+
else if (action === "resume" &&
|
|
3742
|
+
lane.senior.kind === "lane") {
|
|
3743
|
+
grantContext = createOutpostHireGrantContext(lane, loadAllLanes(ctx.hqRoot()));
|
|
3744
|
+
}
|
|
3716
3745
|
let dispatched;
|
|
3746
|
+
let grantContextRejected = false;
|
|
3717
3747
|
try {
|
|
3718
|
-
const dispatchCommand = (
|
|
3748
|
+
const dispatchCommand = (contexts) => ctx.dispatchOutpostExec({
|
|
3719
3749
|
outpostId,
|
|
3720
3750
|
command: buildOutpostLaneCommand({
|
|
3721
3751
|
action: action === "grant"
|
|
@@ -3738,20 +3768,38 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3738
3768
|
hire: options.hire,
|
|
3739
3769
|
senior: options.senior,
|
|
3740
3770
|
reason: options.reason,
|
|
3741
|
-
grantContext,
|
|
3742
|
-
|
|
3771
|
+
grantContext: contexts.grantContext,
|
|
3772
|
+
profileRepinContext: contexts.profileRepinContext,
|
|
3743
3773
|
compact: options.compact,
|
|
3744
3774
|
}),
|
|
3745
3775
|
});
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3776
|
+
let contexts = { grantContext, profileRepinContext };
|
|
3777
|
+
dispatched = await dispatchCommand(contexts);
|
|
3778
|
+
for (let retry = 0; action === "resume" && retry < 2; retry += 1) {
|
|
3779
|
+
if (contexts.grantContext !== undefined && outpostRejectedGrantContextOption(dispatched)) {
|
|
3780
|
+
grantContextRejected = true;
|
|
3781
|
+
break;
|
|
3782
|
+
}
|
|
3783
|
+
else if (contexts.profileRepinContext !== undefined &&
|
|
3784
|
+
outpostRejectedProfileRepinOption(dispatched)) {
|
|
3785
|
+
contexts = { ...contexts, profileRepinContext: undefined };
|
|
3786
|
+
}
|
|
3787
|
+
else {
|
|
3788
|
+
break;
|
|
3789
|
+
}
|
|
3790
|
+
dispatched = await dispatchCommand(contexts);
|
|
3750
3791
|
}
|
|
3751
3792
|
}
|
|
3752
3793
|
catch (err) {
|
|
3753
3794
|
throw new LanesError("outpost_dispatch_failed", `Could not dispatch ${action} for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
|
|
3754
3795
|
}
|
|
3796
|
+
if (grantContextRejected) {
|
|
3797
|
+
throw new LanesError("outpost_cli_upgrade_required", `Outpost ${outpostId} needs an hq-cli release containing this fix before it can resume lane ${lane.lane_id} under its primary-hosted senior`, {
|
|
3798
|
+
lane_id: lane.lane_id,
|
|
3799
|
+
outpost_id: outpostId,
|
|
3800
|
+
required_option: "--grant-context",
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3755
3803
|
const ownerError = ownerErrorFromOutput(dispatched.stdout);
|
|
3756
3804
|
if (options.effort !== undefined && outpostRejectedEffortOption(dispatched)) {
|
|
3757
3805
|
throw new LanesError("outpost_effort_unsupported", `Outpost ${outpostId} rejected --effort; update hq-cli on that Outpost before changing a lane's effort remotely`, {
|
|
@@ -3766,7 +3814,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3766
3814
|
});
|
|
3767
3815
|
}
|
|
3768
3816
|
if (ownerError) {
|
|
3769
|
-
if (action === "resume" &&
|
|
3817
|
+
if (action === "resume" &&
|
|
3818
|
+
(ownerError.error === "worker_profile_hash_mismatch" ||
|
|
3819
|
+
ownerError.error.startsWith("hire_grant_"))) {
|
|
3770
3820
|
throw new LanesError(ownerError.error, ownerError.message, {
|
|
3771
3821
|
...ownerError.details,
|
|
3772
3822
|
lane_id: lane.lane_id,
|
|
@@ -3821,7 +3871,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3821
3871
|
Array.isArray(remote.changed_descendant_ids)
|
|
3822
3872
|
: (remote.delivery_status === "worker_spawned" ||
|
|
3823
3873
|
remote.delivery_status === "queued_on_target_account") &&
|
|
3824
|
-
|
|
3874
|
+
remote.lane !== undefined);
|
|
3825
3875
|
if (!resultConfirmed) {
|
|
3826
3876
|
throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} completed exec without a confirmed ${action} result for lane ${lane.lane_id}`, {
|
|
3827
3877
|
lane_id: lane.lane_id,
|
|
@@ -3842,7 +3892,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3842
3892
|
});
|
|
3843
3893
|
return;
|
|
3844
3894
|
}
|
|
3845
|
-
if (action === "reparent" ||
|
|
3895
|
+
if (action === "reparent" || action === "resume") {
|
|
3846
3896
|
const ownerLane = syncLaneRecordFromOwner(ctx.hqRoot(), lane.lane_id, outpostId, remote.lane, undefined, { allowNonTerminal: true });
|
|
3847
3897
|
const descendants = [];
|
|
3848
3898
|
if (action === "reparent") {
|
|
@@ -6549,16 +6599,25 @@ function laneWithRecoveredProviderSession(hqRoot, lane) {
|
|
|
6549
6599
|
async function runResume(ctx, laneId, opts) {
|
|
6550
6600
|
const hqRoot = ctx.hqRoot();
|
|
6551
6601
|
let lane = readLane(hqRoot, laneId);
|
|
6602
|
+
const hireGrantContext = opts.grantContext === undefined
|
|
6603
|
+
? undefined
|
|
6604
|
+
: (() => {
|
|
6605
|
+
if (!ctx.isSsmOwnerDispatch()) {
|
|
6606
|
+
throw new LanesError("hire_grant_dispatch_unauthorized", "Resume grant context is only valid on an Outpost owner dispatch", { lane_id: laneId });
|
|
6607
|
+
}
|
|
6608
|
+
return parseOutpostHireGrantContext(opts.grantContext, laneId);
|
|
6609
|
+
})();
|
|
6552
6610
|
if (opts.profileRepinContext !== undefined && !ctx.isSsmOwnerDispatch()) {
|
|
6553
6611
|
throw new LanesError("worker_profile_hash_mismatch", "Profile re-pin context is only valid on an Outpost owner dispatch", { lane_id: laneId });
|
|
6554
6612
|
}
|
|
6555
6613
|
if (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane)) {
|
|
6614
|
+
await withResumeHireAdmission(ctx, lane, () => undefined);
|
|
6556
6615
|
await dispatchOutpostLaneCommand(ctx, lane, "resume", opts);
|
|
6557
6616
|
return;
|
|
6558
6617
|
}
|
|
6559
6618
|
// Fail before provider/session work if a terminal child would consume a
|
|
6560
6619
|
// slot that its senior has already assigned elsewhere.
|
|
6561
|
-
await withResumeHireAdmission(ctx, lane, () => undefined);
|
|
6620
|
+
await withResumeHireAdmission(ctx, lane, () => undefined, hireGrantContext);
|
|
6562
6621
|
assertLaneRunsHereForOperatorMutation(ctx, lane, "resume");
|
|
6563
6622
|
const cliEffort = parseCliEffort(lane.provider, opts.effort);
|
|
6564
6623
|
if (cliEffort !== undefined && !providerSupportsSessionResume(lane.provider)) {
|
|
@@ -6768,7 +6827,7 @@ async function runResume(ctx, laneId, opts) {
|
|
|
6768
6827
|
compact: opts.compact !== false,
|
|
6769
6828
|
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
6770
6829
|
});
|
|
6771
|
-
});
|
|
6830
|
+
}, hireGrantContext);
|
|
6772
6831
|
}, ctx.sleep);
|
|
6773
6832
|
}
|
|
6774
6833
|
if (text !== undefined && messageId !== undefined) {
|
|
@@ -9567,6 +9626,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9567
9626
|
.option("--effort <level>", "Reasoning effort for the resumed provider turn")
|
|
9568
9627
|
.option("--worker <profile>", "Attach a personal worker profile for the resumed turn")
|
|
9569
9628
|
.addOption(new Option("--profile-repin-context <base64url>").hideHelp())
|
|
9629
|
+
.addOption(new Option("--grant-context <base64url>").hideHelp())
|
|
9570
9630
|
.option("--keep-alive", "Automatically resume done lead lanes while their story remains open")
|
|
9571
9631
|
.option("--mesh-company <slug>", "Attach Work Mesh attribution after verifying company membership")
|
|
9572
9632
|
.option("--no-compact", "Skip the resume-time compaction check for this resume")
|
|
@@ -128,6 +128,13 @@ export declare function resolveCandidate(index: EntityIndex, type: string, canon
|
|
|
128
128
|
* (`Chaudhry Hassaan Saleem` and `Hassaan Saleem`).
|
|
129
129
|
*/
|
|
130
130
|
export declare function overlappingEntities(index: EntityIndex, type: string, canonical: string, aliases: string[]): string[];
|
|
131
|
+
/**
|
|
132
|
+
* Existing people a one-word person candidate may be: the word is the first
|
|
133
|
+
* word of their name or of one of their aliases, and that name has two words
|
|
134
|
+
* or more (`John` and `John Smith`). The model never sees the entity tree, so
|
|
135
|
+
* it cannot tell a first name that the company already holds in full.
|
|
136
|
+
*/
|
|
137
|
+
export declare function firstNameOverlap(index: EntityIndex, canonical: string): string[];
|
|
131
138
|
export declare function applyOntologyRun(options: ApplyOptions): ApplyReport;
|
|
132
139
|
export {};
|
|
133
140
|
//# sourceMappingURL=apply.d.ts.map
|
|
@@ -113,6 +113,30 @@ export function overlappingEntities(index, type, canonical, aliases) {
|
|
|
113
113
|
}
|
|
114
114
|
return [...paths].sort();
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Existing people a one-word person candidate may be: the word is the first
|
|
118
|
+
* word of their name or of one of their aliases, and that name has two words
|
|
119
|
+
* or more (`John` and `John Smith`). The model never sees the entity tree, so
|
|
120
|
+
* it cannot tell a first name that the company already holds in full.
|
|
121
|
+
*/
|
|
122
|
+
export function firstNameOverlap(index, canonical) {
|
|
123
|
+
const words = normalizeEntityName(canonical, "person").split(" ");
|
|
124
|
+
if (words.length !== 1 || !words[0])
|
|
125
|
+
return [];
|
|
126
|
+
const prefix = entityKey("person", "");
|
|
127
|
+
const paths = new Set();
|
|
128
|
+
for (const map of [index.canonical, index.alias]) {
|
|
129
|
+
for (const [name, rels] of map) {
|
|
130
|
+
if (!name.startsWith(prefix))
|
|
131
|
+
continue;
|
|
132
|
+
const other = name.slice(prefix.length).split(" ");
|
|
133
|
+
if (other.length >= 2 && other[0] === words[0])
|
|
134
|
+
for (const rel of rels)
|
|
135
|
+
paths.add(rel);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [...paths].sort();
|
|
139
|
+
}
|
|
116
140
|
function readJson(file) {
|
|
117
141
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
118
142
|
}
|
|
@@ -335,6 +359,12 @@ export function applyOntologyRun(options) {
|
|
|
335
359
|
report.suspect_duplicates.push({ name: canonical, type: candidate.type, paths: overlap });
|
|
336
360
|
continue;
|
|
337
361
|
}
|
|
362
|
+
const firstName = plan.first_name_people === true && candidate.type === "person" ? firstNameOverlap(index, canonical) : [];
|
|
363
|
+
if (firstName.length > 0) {
|
|
364
|
+
note(report.deferred, "suspect_duplicate");
|
|
365
|
+
report.suspect_duplicates.push({ name: canonical, type: candidate.type, paths: firstName });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
338
368
|
// Tier 5: create.
|
|
339
369
|
if (candidate.confidence < MIN_CREATE_CONFIDENCE) {
|
|
340
370
|
note(report.rejected, "low_confidence");
|
|
@@ -48,6 +48,17 @@ export declare const ENTITY_HINTS_FLAG = "checkpoint.ontology.entity-hints";
|
|
|
48
48
|
* hinted runs).
|
|
49
49
|
*/
|
|
50
50
|
export declare const NO_LINK_ALIASES_FLAG = "checkpoint.ontology.no-link-aliases";
|
|
51
|
+
/**
|
|
52
|
+
* A person the input and the company's entities give only a first name for is
|
|
53
|
+
* named by that first name instead of being left out. CO-44: on the CO-41
|
|
54
|
+
* grok-4.6 runs the model read "not a first name alone" as "skip them", and
|
|
55
|
+
* dropped people such as Amir and Sebastian, whose own entities carry only a
|
|
56
|
+
* first name. People were 24 of the 138 missed gold entities over three runs.
|
|
57
|
+
* The plan carries `first_name_people: true`, and apply then defers a one-word
|
|
58
|
+
* person whose word starts an existing person's fuller name or alias, since
|
|
59
|
+
* the model cannot see that `John Smith` already exists.
|
|
60
|
+
*/
|
|
61
|
+
export declare const FIRST_NAME_PEOPLE_FLAG = "checkpoint.ontology.first-name-people";
|
|
51
62
|
export declare const PLAN_FILE = "ontology-plan.json";
|
|
52
63
|
export declare const CANDIDATES_FILE = "ontology-candidates.json";
|
|
53
64
|
export declare const REPORT_FILE = "ontology-report.json";
|
|
@@ -116,6 +127,8 @@ export interface OntologyPlan {
|
|
|
116
127
|
multi_writer?: MultiWriterStart;
|
|
117
128
|
/** Present, as false, only when `checkpoint.ontology.no-link-aliases` was on for this plan. */
|
|
118
129
|
link_aliases?: false;
|
|
130
|
+
/** Set by checkpoint.ontology.first-name-people: apply defers a one-word person who may be an existing fuller name. */
|
|
131
|
+
first_name_people?: true;
|
|
119
132
|
inputs: PlanInput[];
|
|
120
133
|
/** The session's summary, decisions and learnings, for the `session` input. */
|
|
121
134
|
session_text: string | null;
|
|
@@ -160,6 +173,8 @@ export type PlanOutcome = {
|
|
|
160
173
|
};
|
|
161
174
|
export declare 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.";
|
|
162
175
|
export declare const ENTITY_HINTS_INSTRUCTIONS: string[];
|
|
176
|
+
export declare const PERSON_NAME_INSTRUCTION = "Name a person by the full name the input or the company's entities give, not a first name alone.";
|
|
177
|
+
export declare 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.";
|
|
163
178
|
export declare function ledgerPath(hqRoot: string, company: OntologyCompany): string;
|
|
164
179
|
export declare function readLedger(hqRoot: string, company: OntologyCompany): Ledger | null;
|
|
165
180
|
export declare function writeLedger(hqRoot: string, company: OntologyCompany, ledger: Ledger): void;
|
|
@@ -52,6 +52,17 @@ export const ENTITY_HINTS_FLAG = "checkpoint.ontology.entity-hints";
|
|
|
52
52
|
* hinted runs).
|
|
53
53
|
*/
|
|
54
54
|
export const NO_LINK_ALIASES_FLAG = "checkpoint.ontology.no-link-aliases";
|
|
55
|
+
/**
|
|
56
|
+
* A person the input and the company's entities give only a first name for is
|
|
57
|
+
* named by that first name instead of being left out. CO-44: on the CO-41
|
|
58
|
+
* grok-4.6 runs the model read "not a first name alone" as "skip them", and
|
|
59
|
+
* dropped people such as Amir and Sebastian, whose own entities carry only a
|
|
60
|
+
* first name. People were 24 of the 138 missed gold entities over three runs.
|
|
61
|
+
* The plan carries `first_name_people: true`, and apply then defers a one-word
|
|
62
|
+
* person whose word starts an existing person's fuller name or alias, since
|
|
63
|
+
* the model cannot see that `John Smith` already exists.
|
|
64
|
+
*/
|
|
65
|
+
export const FIRST_NAME_PEOPLE_FLAG = "checkpoint.ontology.first-name-people";
|
|
55
66
|
export const PLAN_FILE = "ontology-plan.json";
|
|
56
67
|
export const CANDIDATES_FILE = "ontology-candidates.json";
|
|
57
68
|
export const REPORT_FILE = "ontology-report.json";
|
|
@@ -68,11 +79,13 @@ export const ENTITY_HINTS_INSTRUCTIONS = [
|
|
|
68
79
|
"A knowledge input's known_entities lists entities that already exist in the company and whose names appear in its text. When the input refers to one of them, under that name or another, use that entity's name and type exactly and do not name a new entity for it.",
|
|
69
80
|
"Skip rows of a glossary or translation table, file and directory paths, and names built from tags or slugs.",
|
|
70
81
|
];
|
|
82
|
+
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.";
|
|
83
|
+
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.";
|
|
71
84
|
const CANDIDATE_INSTRUCTIONS = [
|
|
72
85
|
`Write ${CANDIDATES_FILE} in the run directory as {"schema":"${CANDIDATES_SCHEMA}","candidates":[...]} and nothing else.`,
|
|
73
86
|
"Each candidate: {input, type, name, aliases, confidence, evidence}. input is one of this plan's input ids; type is person, project, company or concept; name is the entity's full proper name; aliases are other names the input uses for it (at most 5); confidence is 0 to 1; evidence is a short quote from the input.",
|
|
74
87
|
"Read only the files and line ranges this plan lists. For a signal, read its body and name the entities in missing_refs if the signal supports them.",
|
|
75
|
-
"Name the specific people, projects, products, companies and named concepts that the input says something about. That includes the company that owns this knowledge and its own products when the input describes them. Name a third-party tool, vendor or service only when a section of the input is about it; skip ones that appear in a list, a stack summary or a passing mention. Skip generic words, file names and headings.
|
|
88
|
+
"Name the specific people, projects, products, companies and named concepts that the input says something about. That includes the company that owns this knowledge and its own products when the input describes them. Name a third-party tool, vendor or service only when a section of the input is about it; skip ones that appear in a list, a stack summary or a passing mention. Skip generic words, file names and headings.",
|
|
76
89
|
"For the session input (its text is session_text in this plan), name only entities that already exist in the company and that the session did work on. Session candidates are recorded as links, never created.",
|
|
77
90
|
"Do not create, edit, move or delete anything under ontology/, signals/ or sources/ yourself.",
|
|
78
91
|
];
|
|
@@ -229,6 +242,7 @@ export async function planOntologyRun(options) {
|
|
|
229
242
|
const multiWriterOn = await options.readFlag(MULTI_WRITER_FLAG, company);
|
|
230
243
|
const hintsOn = await options.readFlag(ENTITY_HINTS_FLAG, company);
|
|
231
244
|
const noLinkAliases = await options.readFlag(NO_LINK_ALIASES_FLAG, company);
|
|
245
|
+
const firstNamePeople = await options.readFlag(FIRST_NAME_PEOPLE_FLAG, company);
|
|
232
246
|
const touched = sessionTouched(hqRoot, company, payload.files);
|
|
233
247
|
// A first run takes its boundary from the entity tree, so the ledger is
|
|
234
248
|
// refreshed before merge-back moves any `last_updated` forward.
|
|
@@ -342,6 +356,7 @@ export async function planOntologyRun(options) {
|
|
|
342
356
|
: {}),
|
|
343
357
|
...(multiWriter ? { multi_writer: multiWriter } : {}),
|
|
344
358
|
...(noLinkAliases ? { link_aliases: false } : {}),
|
|
359
|
+
...(firstNamePeople ? { first_name_people: true } : {}),
|
|
345
360
|
inputs,
|
|
346
361
|
session_text: sessionText,
|
|
347
362
|
pending_after_run: remaining,
|
|
@@ -355,7 +370,7 @@ export async function planOntologyRun(options) {
|
|
|
355
370
|
instructions: [
|
|
356
371
|
...CANDIDATE_INSTRUCTIONS.slice(0, 3),
|
|
357
372
|
...(inputs.some((input) => input.read_path) ? [READ_PATH_INSTRUCTION] : []),
|
|
358
|
-
CANDIDATE_INSTRUCTIONS[3]
|
|
373
|
+
`${CANDIDATE_INSTRUCTIONS[3]} ${firstNamePeople ? FIRST_NAME_PEOPLE_INSTRUCTION : PERSON_NAME_INSTRUCTION}`,
|
|
359
374
|
...(inputs.some((input) => input.known_entities) ? ENTITY_HINTS_INSTRUCTIONS : []),
|
|
360
375
|
...CANDIDATE_INSTRUCTIONS.slice(4),
|
|
361
376
|
],
|
|
@@ -4,16 +4,16 @@ export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" |
|
|
|
4
4
|
export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string): T[];
|
|
5
5
|
export declare function assignedChildHireGrants(children: readonly LaneHireGrantRow[], exceptLaneId?: string): number;
|
|
6
6
|
export interface HireGrantAdmissionSnapshot {
|
|
7
|
-
parent:
|
|
8
|
-
children:
|
|
7
|
+
parent: LaneHireGrantRow;
|
|
8
|
+
children: LaneHireGrantRow[];
|
|
9
9
|
parentGrant: number;
|
|
10
10
|
availableChildGrant: number;
|
|
11
11
|
}
|
|
12
12
|
/** Shared refusal logic for resuming a terminal lane under an active senior. */
|
|
13
13
|
export declare function terminalChildResumeHireGrantError(current: LaneRecord, snapshot: HireGrantAdmissionSnapshot): LanesError | null;
|
|
14
|
-
export declare function hireGrantCapacityError(parent:
|
|
14
|
+
export declare function hireGrantCapacityError(parent: LaneHireGrantRow, children: readonly LaneHireGrantRow[]): LanesError;
|
|
15
15
|
/** Keep the occupancy snapshot and the resulting lane write in one parent-lock scope. */
|
|
16
|
-
export declare function withHireGrantAdmission<T>(hqRoot: string, parentLaneId: string, fn: (snapshot: HireGrantAdmissionSnapshot) => T): T;
|
|
16
|
+
export declare function withHireGrantAdmission<T>(hqRoot: string, parentLaneId: string, fn: (snapshot: HireGrantAdmissionSnapshot) => T, suppliedHierarchy?: readonly LaneHireGrantRow[]): T;
|
|
17
17
|
export interface HireGrantBackfillRow {
|
|
18
18
|
lane_id: string;
|
|
19
19
|
existing_grant: number | null;
|
package/dist/lib/lanes/grants.js
CHANGED
|
@@ -72,10 +72,28 @@ export function hireGrantCapacityError(parent, children) {
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
/** Keep the occupancy snapshot and the resulting lane write in one parent-lock scope. */
|
|
75
|
-
export function withHireGrantAdmission(hqRoot, parentLaneId, fn) {
|
|
75
|
+
export function withHireGrantAdmission(hqRoot, parentLaneId, fn, suppliedHierarchy) {
|
|
76
76
|
return withLaneHireLocks(hqRoot, [parentLaneId], () => {
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const ownerLanes = listLaneIds(hqRoot).map((id) => readLane(hqRoot, id));
|
|
78
|
+
let allLanes;
|
|
79
|
+
let parent;
|
|
80
|
+
if (suppliedHierarchy === undefined) {
|
|
81
|
+
parent = readLane(hqRoot, parentLaneId);
|
|
82
|
+
allLanes = ownerLanes;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const byId = new Map(suppliedHierarchy.map((lane) => [lane.lane_id, lane]));
|
|
86
|
+
// The owner copy is authoritative for every lane it stores; the supplied
|
|
87
|
+
// primary snapshot fills only hierarchy rows that live on another host.
|
|
88
|
+
for (const lane of ownerLanes)
|
|
89
|
+
byId.set(lane.lane_id, lane);
|
|
90
|
+
allLanes = [...byId.values()];
|
|
91
|
+
const suppliedParent = byId.get(parentLaneId);
|
|
92
|
+
if (!suppliedParent) {
|
|
93
|
+
throw new LanesError("hire_grant_context_invalid", `The dispatched hierarchy is missing senior lane ${parentLaneId}`, { senior_lane_id: parentLaneId });
|
|
94
|
+
}
|
|
95
|
+
parent = suppliedParent;
|
|
96
|
+
}
|
|
79
97
|
const children = aliveDirectChildren(allLanes, parentLaneId);
|
|
80
98
|
const parentGrant = parent.hire_grant ?? 0;
|
|
81
99
|
const availableChildGrant = Math.max(0, parentGrant - assignedChildHireGrants(children));
|