@indigoai-us/hq-cli 5.140.0 → 5.142.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 +25 -0
- package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +77 -2
- package/dist/command-catalog.generated.d.ts +6 -0
- package/dist/command-catalog.generated.js +8 -0
- package/dist/commands/bot-jobs.js +16 -3
- package/dist/commands/lanes.js +125 -26
- package/dist/lib/agent-kit/run/heartbeat.js +2 -2
- package/dist/lib/bot/run.js +2 -2
- package/dist/lib/heartbeat-identity.d.ts +1 -0
- package/dist/lib/heartbeat-identity.js +5 -0
- package/dist/lib/lanes/effort.d.ts +3 -2
- package/dist/lib/lanes/effort.js +5 -1
- package/dist/lib/lanes/errors.d.ts +1 -1
- package/dist/lib/lanes/index.d.ts +2 -0
- package/dist/lib/lanes/index.js +1 -0
- package/dist/lib/lanes/mesh-events.d.ts +4 -5
- package/dist/lib/lanes/mesh-events.js +14 -4
- package/dist/lib/lanes/profile.d.ts +48 -0
- package/dist/lib/lanes/profile.js +405 -0
- package/dist/lib/lanes/reconcile.js +25 -3
- package/dist/lib/lanes/registry.d.ts +3 -0
- package/dist/lib/lanes/registry.js +45 -1
- package/dist/lib/lanes/spawn.d.ts +14 -2
- package/dist/lib/lanes/spawn.js +238 -14
- package/dist/lib/lanes/store.js +10 -0
- package/dist/lib/lanes/types.d.ts +11 -1
- package/dist/lib/lanes/types.js +3 -3
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import { parseUserYaml } from "../../utils/user-yaml-error.js";
|
|
|
11
11
|
import { accountMaxRunning, loadLanesConfig, providerQuotaDefaultHoldMs } from "./config.js";
|
|
12
12
|
import { MAX_LANE_DEPTH } from "./constants.js";
|
|
13
13
|
import { LanesError, isLaneVersionConflict } from "./errors.js";
|
|
14
|
+
import { loadWorkerProfile } from "./profile.js";
|
|
14
15
|
import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, } from "./liveness.js";
|
|
15
16
|
import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, isPidRecordAlive, laneRunDir, readPidRecord, readSpawnManifest, spawnEnvelopeRepair, spawnLaneWorker, } from "./spawn.js";
|
|
16
17
|
import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
|
|
@@ -18,7 +19,7 @@ import { applyThrottleDecision, defaultThrottleDeps, emptyThrottleState, evaluat
|
|
|
18
19
|
import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./quota.js";
|
|
19
20
|
import { answeredQuestionEntries, raiseEnvelopeQuestions } from "./questions.js";
|
|
20
21
|
import { SILENCE_MS, classifyLaneExit, isPidAlive, readLatestEnvelope, readLatestEnvelopeFile, readLatestEnvelopeRecord, } from "./watcher.js";
|
|
21
|
-
import { createLaneMesh, emitLaneDecisionEvents } from "./mesh-events.js";
|
|
22
|
+
import { createLaneMesh, emitLaneDecisionEvents, laneMeshCompany } from "./mesh-events.js";
|
|
22
23
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
23
24
|
import { providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
24
25
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
@@ -317,13 +318,14 @@ async function emitAgedQuestionAtUser(summary, deps, lane, question) {
|
|
|
317
318
|
if (deps.dryRun)
|
|
318
319
|
return;
|
|
319
320
|
const mesh = deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh;
|
|
320
|
-
|
|
321
|
+
const company = laneMeshCompany(lane);
|
|
322
|
+
if (!mesh || !company || !lane.thread_id)
|
|
321
323
|
return;
|
|
322
324
|
const timeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
|
|
323
325
|
let timer;
|
|
324
326
|
try {
|
|
325
327
|
await Promise.race([
|
|
326
|
-
mesh.appendThreadEvent(
|
|
328
|
+
mesh.appendThreadEvent(company, lane.thread_id, "lane_question_at_user", { lane_id: lane.lane_id, question_id: question.question_id }),
|
|
327
329
|
new Promise((_resolve, reject) => {
|
|
328
330
|
timer = setTimeout(() => reject(new Error(`question-at-user mesh emit timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
329
331
|
}),
|
|
@@ -1011,6 +1013,26 @@ async function admitQueued(hqRoot, lanes, summary, deps, now) {
|
|
|
1011
1013
|
continue;
|
|
1012
1014
|
}
|
|
1013
1015
|
}
|
|
1016
|
+
let profileMaxConcurrent;
|
|
1017
|
+
if (lane.worker_profile !== undefined) {
|
|
1018
|
+
try {
|
|
1019
|
+
profileMaxConcurrent = loadWorkerProfile(hqRoot, lane.worker_profile).meta.max_concurrent;
|
|
1020
|
+
}
|
|
1021
|
+
catch (err) {
|
|
1022
|
+
markDirty(summary, {
|
|
1023
|
+
kind: "lane",
|
|
1024
|
+
id: lane.lane_id,
|
|
1025
|
+
why: `profile admission failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1026
|
+
});
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (profileMaxConcurrent !== undefined) {
|
|
1031
|
+
const profileRunning = running.filter((row) => row.worker_profile === lane.worker_profile).length +
|
|
1032
|
+
spawned.filter((row) => row.worker_profile === lane.worker_profile).length;
|
|
1033
|
+
if (profileRunning >= profileMaxConcurrent)
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1014
1036
|
try {
|
|
1015
1037
|
if (deps.dryRun) {
|
|
1016
1038
|
summary.admitted.push({
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* `worker_tenancy_unresolved` when that cannot be determined.
|
|
24
24
|
*/
|
|
25
25
|
import * as fs from "node:fs";
|
|
26
|
+
import { type WorkerProfile } from "./profile.js";
|
|
26
27
|
export declare const WORKER_REGISTRY_REL: string;
|
|
27
28
|
export interface RegistryWorkerEntry {
|
|
28
29
|
id: string;
|
|
@@ -87,6 +88,8 @@ export interface ResolvedLaneWorker {
|
|
|
87
88
|
humanCheckpoints: string[];
|
|
88
89
|
/** Set when the worker's realpath lives in a package. */
|
|
89
90
|
pack?: string;
|
|
91
|
+
/** Set when this worker was resolved from personal/workers/<id>/meta.yaml. */
|
|
92
|
+
profile?: WorkerProfile;
|
|
90
93
|
}
|
|
91
94
|
export interface RegistryDeps {
|
|
92
95
|
existsSync: (p: string) => boolean;
|
|
@@ -28,6 +28,7 @@ import { parseUserYaml } from "../../utils/user-yaml-error.js";
|
|
|
28
28
|
import { readContributes } from "../scan-packages/index.js";
|
|
29
29
|
import { contributionLinks, linkStatus, } from "../../utils/pack-contributions.js";
|
|
30
30
|
import { WorkerResolutionError } from "./errors.js";
|
|
31
|
+
import { loadWorkerProfile, workerProfileMarker, } from "./profile.js";
|
|
31
32
|
export const WORKER_REGISTRY_REL = path.join("core", "workers", "registry.yaml");
|
|
32
33
|
function defaultRegistryDeps() {
|
|
33
34
|
return {
|
|
@@ -247,6 +248,9 @@ function personalDirClaim(hqRoot, workerId, deps) {
|
|
|
247
248
|
const dir = personalWorkerDir(hqRoot, workerId);
|
|
248
249
|
let st;
|
|
249
250
|
try {
|
|
251
|
+
const linkStat = fs.lstatSync(dir);
|
|
252
|
+
if (linkStat.isSymbolicLink())
|
|
253
|
+
return "none";
|
|
250
254
|
st = deps.statSync(dir);
|
|
251
255
|
}
|
|
252
256
|
catch (err) {
|
|
@@ -369,7 +373,10 @@ function readRegistryOrEmpty(hqRoot, deps) {
|
|
|
369
373
|
}
|
|
370
374
|
export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultRegistryDeps()) {
|
|
371
375
|
const personalRel = path.posix.join("personal", "workers", workerId);
|
|
372
|
-
const
|
|
376
|
+
const profile = workerProfileMarker(hqRoot, workerId) === "present"
|
|
377
|
+
? loadWorkerProfile(hqRoot, workerId)
|
|
378
|
+
: undefined;
|
|
379
|
+
const personalClaim = profile ? "profile" : personalDirClaim(hqRoot, workerId, deps);
|
|
373
380
|
if (personalClaim === "missing_yaml") {
|
|
374
381
|
throw new WorkerResolutionError("worker_not_found", `Worker ${JSON.stringify(workerId)} is at ${personalRel}/ but worker.yaml is missing`, { worker: workerId, path: `${personalRel}/`, missing: "worker.yaml" });
|
|
375
382
|
}
|
|
@@ -390,6 +397,43 @@ export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultR
|
|
|
390
397
|
realpath: realpathOf(hqRoot, rel),
|
|
391
398
|
});
|
|
392
399
|
}
|
|
400
|
+
if (profile !== undefined && candidates.length > 0) {
|
|
401
|
+
throw new WorkerResolutionError("worker_profile_ambiguous", "Worker " +
|
|
402
|
+
JSON.stringify(workerId) +
|
|
403
|
+
" has both a personal profile and a registry worker; name one explicitly instead of falling through", {
|
|
404
|
+
worker: workerId,
|
|
405
|
+
profile_path: personalRel + "/",
|
|
406
|
+
registry_paths: candidates.map((item) => item.path),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
if (profile !== undefined) {
|
|
410
|
+
const description = "Reusable worker profile " + profile.id;
|
|
411
|
+
return {
|
|
412
|
+
id: profile.id,
|
|
413
|
+
path: personalRel + "/",
|
|
414
|
+
type: "WorkerProfile",
|
|
415
|
+
status: "active",
|
|
416
|
+
description,
|
|
417
|
+
tenancy: { kind: "global" },
|
|
418
|
+
workerYaml: {
|
|
419
|
+
worker: {
|
|
420
|
+
id: profile.id,
|
|
421
|
+
name: profile.id,
|
|
422
|
+
description,
|
|
423
|
+
type: "WorkerProfile",
|
|
424
|
+
status: "active",
|
|
425
|
+
},
|
|
426
|
+
instructions: "Apply reusable worker profile " + profile.id + " at every provider spawn.",
|
|
427
|
+
},
|
|
428
|
+
workerDir: profile.dir,
|
|
429
|
+
resolvedContextBase: [],
|
|
430
|
+
skillFiles: [],
|
|
431
|
+
defaultTimeout: undefined,
|
|
432
|
+
approvalRequired: false,
|
|
433
|
+
humanCheckpoints: [],
|
|
434
|
+
profile,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
393
437
|
if (personalClaim === "yaml") {
|
|
394
438
|
const rel = normalizeRel(personalRel);
|
|
395
439
|
const real = realpathOf(hqRoot, rel);
|
|
@@ -11,7 +11,7 @@ import { PROVIDER_HOME_ENV } from "./accounts.js";
|
|
|
11
11
|
import { type RunCompactionFn } from "./compaction.js";
|
|
12
12
|
import { type LaunchTrigger } from "./delivery.js";
|
|
13
13
|
import { type MachineIdentity } from "./machine-identity.js";
|
|
14
|
-
import { type LaneProvider, type LaneRecord } from "./types.js";
|
|
14
|
+
import { type LaneCompanyRef, type LaneProvider, type LaneRecord } from "./types.js";
|
|
15
15
|
export declare const LANES_RUNS_REL: string;
|
|
16
16
|
/** Written by reconcile before a resume spawn; the worker prompt names this path. */
|
|
17
17
|
export declare const ANSWERED_QUESTIONS_FILE = "answered-questions.json";
|
|
@@ -27,12 +27,18 @@ export declare const CODEX_FAST_MODE_FLAGS: readonly ["-c", "service_tier=\"fast
|
|
|
27
27
|
export declare function parseCliModel(raw: string | undefined): string | undefined;
|
|
28
28
|
export declare function resolveLaneModel(input: {
|
|
29
29
|
cliModel?: string;
|
|
30
|
+
profileModel?: string;
|
|
30
31
|
accountModel?: string;
|
|
31
32
|
providerModel?: string;
|
|
32
33
|
}): string | undefined;
|
|
33
34
|
export declare function appendModelFlag(provider: LaneProvider, args: string[], model: string | undefined): string[];
|
|
34
35
|
/** Add Codex fast-mode flags before the prompt/positional separator. */
|
|
35
36
|
export declare function appendFastModeFlags(provider: LaneProvider, args: string[], enabled: boolean | undefined): string[];
|
|
37
|
+
export interface ProviderProfileLaunch {
|
|
38
|
+
systemPrompt: string;
|
|
39
|
+
settingsPath?: string;
|
|
40
|
+
ignoredFields?: readonly string[];
|
|
41
|
+
}
|
|
36
42
|
export type DetachedStdio = ["ignore", number, number];
|
|
37
43
|
export interface DetachedSpawnOptions {
|
|
38
44
|
detached: true;
|
|
@@ -197,6 +203,8 @@ export declare function buildProviderCommand(provider: LaneProvider, input: {
|
|
|
197
203
|
effort?: string;
|
|
198
204
|
/** Enable Codex's fast service tier for this resolved model. */
|
|
199
205
|
fastMode?: boolean;
|
|
206
|
+
/** Per-spawn worker profile instructions and materialized settings. */
|
|
207
|
+
profile?: ProviderProfileLaunch;
|
|
200
208
|
/** Continue a recorded session. Only providers in PROVIDER_SESSION_RESUME accept this. */
|
|
201
209
|
resume?: ProviderResumeInput;
|
|
202
210
|
/**
|
|
@@ -209,7 +217,7 @@ export declare function buildProviderCommand(provider: LaneProvider, input: {
|
|
|
209
217
|
export declare function workerEnv(base: NodeJS.ProcessEnv, resolved: {
|
|
210
218
|
envVar: string;
|
|
211
219
|
home: string;
|
|
212
|
-
}, laneId: string, hqRoot: string, autoCompactTokens?: number): NodeJS.ProcessEnv;
|
|
220
|
+
}, laneId: string, hqRoot: string, autoCompactTokens?: number, meshCompany?: LaneCompanyRef): NodeJS.ProcessEnv;
|
|
213
221
|
export declare function markSpawnFailure(hqRoot: string, laneId: string, reasonRaw: string, details: Record<string, unknown>): LaneRecord;
|
|
214
222
|
/**
|
|
215
223
|
* Spec §5.2 executing-side check. Compares this machine's identity to
|
|
@@ -227,6 +235,10 @@ export interface SpawnManifestFile {
|
|
|
227
235
|
/** Byte length of logs/worker.out before this run started; its result is after this. */
|
|
228
236
|
worker_out_offset?: number;
|
|
229
237
|
fast_mode?: boolean;
|
|
238
|
+
worker_profile?: string;
|
|
239
|
+
worker_profile_hash?: string;
|
|
240
|
+
worker_profile_ignored?: string[];
|
|
241
|
+
worker_profile_settings?: string;
|
|
230
242
|
}
|
|
231
243
|
/** Read the per-run manifest. Missing file or field reads as absent. */
|
|
232
244
|
export declare function readSpawnManifest(runDir: string): SpawnManifestFile;
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -18,6 +18,7 @@ import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } f
|
|
|
18
18
|
import { appendEffortFlag, resolveLaneEffort } from "./effort.js";
|
|
19
19
|
import { claimableStatesFor, planLaneLaunch, providerSupportsSessionResume, } from "./delivery.js";
|
|
20
20
|
import { AccountResolutionError, LanesError } from "./errors.js";
|
|
21
|
+
import { ignoredWorkerProfileFields, loadWorkerProfile, materializeWorkerProfile, workerProfilePrompt, } from "./profile.js";
|
|
21
22
|
import { resolveMachineIdentity, } from "./machine-identity.js";
|
|
22
23
|
import { readLane, retryUpdateLane } from "./store.js";
|
|
23
24
|
import { LANE_ENVELOPE_JSON_SCHEMA, parseLaneProvider, } from "./types.js";
|
|
@@ -86,6 +87,9 @@ export function parseCliModel(raw) {
|
|
|
86
87
|
export function resolveLaneModel(input) {
|
|
87
88
|
if (input.cliModel !== undefined)
|
|
88
89
|
return input.cliModel;
|
|
90
|
+
if (input.profileModel !== undefined && input.profileModel.trim().length > 0) {
|
|
91
|
+
return input.profileModel.trim();
|
|
92
|
+
}
|
|
89
93
|
if (input.accountModel !== undefined && input.accountModel.trim().length > 0) {
|
|
90
94
|
return input.accountModel.trim();
|
|
91
95
|
}
|
|
@@ -119,6 +123,40 @@ export function appendFastModeFlags(provider, args, enabled) {
|
|
|
119
123
|
return [...args, ...flags];
|
|
120
124
|
return [...args.slice(0, end), ...flags, ...args.slice(end)];
|
|
121
125
|
}
|
|
126
|
+
function appendProviderProfileFlags(provider, args, profile) {
|
|
127
|
+
if (profile === undefined)
|
|
128
|
+
return args;
|
|
129
|
+
const ignored = new Set(profile.ignoredFields ?? []);
|
|
130
|
+
switch (provider) {
|
|
131
|
+
case "claude": {
|
|
132
|
+
const flags = [
|
|
133
|
+
"--append-system-prompt",
|
|
134
|
+
profile.systemPrompt,
|
|
135
|
+
// A profile is a per-spawn input. Do not let Claude's session resume
|
|
136
|
+
// snapshot hide a changed profile from the next launch.
|
|
137
|
+
"--system-prompt-snapshot",
|
|
138
|
+
"off",
|
|
139
|
+
...(profile.settingsPath !== undefined && !ignored.has("settings")
|
|
140
|
+
? ["--settings", profile.settingsPath]
|
|
141
|
+
: []),
|
|
142
|
+
];
|
|
143
|
+
return [...args, ...flags];
|
|
144
|
+
}
|
|
145
|
+
case "codex": {
|
|
146
|
+
const flags = ["-c", `developer_instructions=${JSON.stringify(profile.systemPrompt)}`];
|
|
147
|
+
const end = args.indexOf("--");
|
|
148
|
+
if (end === -1)
|
|
149
|
+
return [...args, ...flags];
|
|
150
|
+
return [...args.slice(0, end), ...flags, ...args.slice(end)];
|
|
151
|
+
}
|
|
152
|
+
case "grok":
|
|
153
|
+
return [...args, "--rules", profile.systemPrompt];
|
|
154
|
+
default: {
|
|
155
|
+
const _exhaustive = provider;
|
|
156
|
+
return _exhaustive;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
122
160
|
export const PID_LIVENESS_DECISIONS = [
|
|
123
161
|
"esrch",
|
|
124
162
|
"starttime_mismatch",
|
|
@@ -713,10 +751,10 @@ export function buildProviderCommand(provider, input) {
|
|
|
713
751
|
}
|
|
714
752
|
return {
|
|
715
753
|
command,
|
|
716
|
-
args: appendFastModeFlags(parsed, appendEffortFlag(parsed, appendModelFlag(parsed, args, input.model), input.effort), input.fastMode),
|
|
754
|
+
args: appendFastModeFlags(parsed, appendEffortFlag(parsed, appendModelFlag(parsed, appendProviderProfileFlags(parsed, args, input.profile), input.model), input.effort), input.fastMode),
|
|
717
755
|
};
|
|
718
756
|
}
|
|
719
|
-
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens) {
|
|
757
|
+
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, meshCompany) {
|
|
720
758
|
const accepted = Object.values(PROVIDER_HOME_ENV);
|
|
721
759
|
if (!accepted.includes(resolved.envVar)) {
|
|
722
760
|
throw new LanesError("unknown_provider_home_env", `Unknown provider home variable ${JSON.stringify(resolved.envVar)}. Accepted: ${accepted.join(", ")}`, { accepted, value: resolved.envVar });
|
|
@@ -731,6 +769,12 @@ export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens) {
|
|
|
731
769
|
}
|
|
732
770
|
env.HQ_LANE_ID = laneId;
|
|
733
771
|
env.HQ_ROOT = hqRoot;
|
|
772
|
+
if (meshCompany) {
|
|
773
|
+
env.HQ_SPAWN_COMPANY = meshCompany.slug || meshCompany.uid;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
delete env.HQ_SPAWN_COMPANY;
|
|
777
|
+
}
|
|
734
778
|
return env;
|
|
735
779
|
}
|
|
736
780
|
function ensureDir(dir) {
|
|
@@ -752,6 +796,15 @@ function writeText(filePath, text) {
|
|
|
752
796
|
ensureDir(path.dirname(filePath));
|
|
753
797
|
fs.writeFileSync(filePath, text, "utf8");
|
|
754
798
|
}
|
|
799
|
+
function appendWorkerProfileEvent(runDir, event) {
|
|
800
|
+
const eventsPath = path.join(runDir, "watcher", "profile-events.jsonl");
|
|
801
|
+
ensureDir(path.dirname(eventsPath));
|
|
802
|
+
fs.appendFileSync(eventsPath, `${JSON.stringify({
|
|
803
|
+
event: "profile_applied",
|
|
804
|
+
...event,
|
|
805
|
+
at: new Date().toISOString(),
|
|
806
|
+
})}\n`);
|
|
807
|
+
}
|
|
755
808
|
function openLog(filePath) {
|
|
756
809
|
ensureDir(path.dirname(filePath));
|
|
757
810
|
return fs.openSync(filePath, "a");
|
|
@@ -906,6 +959,16 @@ function writeSpawnManifest(runDir, input) {
|
|
|
906
959
|
resume: input.resume,
|
|
907
960
|
worker_out_offset: input.workerOutOffset,
|
|
908
961
|
...(input.fastMode ? { fast_mode: true } : {}),
|
|
962
|
+
...(input.profile
|
|
963
|
+
? {
|
|
964
|
+
worker_profile: input.profile.id,
|
|
965
|
+
worker_profile_hash: input.profile.hash,
|
|
966
|
+
worker_profile_ignored: [...input.profile.ignoredFields],
|
|
967
|
+
...(input.profile.settingsPath
|
|
968
|
+
? { worker_profile_settings: input.profile.settingsPath }
|
|
969
|
+
: {}),
|
|
970
|
+
}
|
|
971
|
+
: {}),
|
|
909
972
|
}, null, 2)}\n`);
|
|
910
973
|
}
|
|
911
974
|
/** Arm (or re-arm) the detached per-lane watcher. */
|
|
@@ -969,6 +1032,15 @@ export function readSpawnManifest(runDir) {
|
|
|
969
1032
|
}
|
|
970
1033
|
if (typeof rec.fast_mode === "boolean")
|
|
971
1034
|
out.fast_mode = rec.fast_mode;
|
|
1035
|
+
if (typeof rec.worker_profile === "string")
|
|
1036
|
+
out.worker_profile = rec.worker_profile;
|
|
1037
|
+
if (typeof rec.worker_profile_hash === "string")
|
|
1038
|
+
out.worker_profile_hash = rec.worker_profile_hash;
|
|
1039
|
+
if (Array.isArray(rec.worker_profile_ignored) && rec.worker_profile_ignored.every((item) => typeof item === "string")) {
|
|
1040
|
+
out.worker_profile_ignored = rec.worker_profile_ignored;
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof rec.worker_profile_settings === "string")
|
|
1043
|
+
out.worker_profile_settings = rec.worker_profile_settings;
|
|
972
1044
|
if (rec.resume &&
|
|
973
1045
|
typeof rec.resume === "object" &&
|
|
974
1046
|
typeof rec.resume.session_id === "string") {
|
|
@@ -1024,13 +1096,38 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1024
1096
|
const logsDir = path.join(runDir, "logs");
|
|
1025
1097
|
ensureDir(logsDir);
|
|
1026
1098
|
ensureDir(path.join(runDir, "envelopes"));
|
|
1099
|
+
const profile = existing.worker_profile
|
|
1100
|
+
? loadWorkerProfile(hqRoot, existing.worker_profile)
|
|
1101
|
+
: undefined;
|
|
1102
|
+
const profileDefaultsApply = profile !== undefined && profile.meta.provider === existing.provider;
|
|
1103
|
+
const profileModel = profileDefaultsApply ? profile.meta.model : undefined;
|
|
1104
|
+
const profileEffort = profileDefaultsApply ? profile.meta.effort : undefined;
|
|
1105
|
+
const ignoredProfileFields = profile
|
|
1106
|
+
? ignoredWorkerProfileFields(existing.provider)
|
|
1107
|
+
: [];
|
|
1108
|
+
const profileRuntime = profile
|
|
1109
|
+
? materializeWorkerProfile(profile, runDir)
|
|
1110
|
+
: undefined;
|
|
1111
|
+
const profileLaunch = profile
|
|
1112
|
+
? {
|
|
1113
|
+
systemPrompt: workerProfilePrompt(profile),
|
|
1114
|
+
...(profileRuntime?.settingsPath !== undefined
|
|
1115
|
+
? { settingsPath: profileRuntime.settingsPath }
|
|
1116
|
+
: {}),
|
|
1117
|
+
ignoredFields: ignoredProfileFields,
|
|
1118
|
+
}
|
|
1119
|
+
: undefined;
|
|
1120
|
+
const profileChanged = profile !== undefined && existing.worker_profile_hash !== profile.contentHash;
|
|
1027
1121
|
const config = loadLanesConfig(hqRoot);
|
|
1122
|
+
const pinnedModel = existing.model_source === undefined || existing.model_source === "cli"
|
|
1123
|
+
? existing.model
|
|
1124
|
+
: undefined;
|
|
1028
1125
|
let resolved;
|
|
1029
1126
|
try {
|
|
1030
1127
|
resolved = resolveAccount(lane.provider, lane.account, config, {
|
|
1031
1128
|
hqRoot,
|
|
1032
1129
|
existsSync,
|
|
1033
|
-
model:
|
|
1130
|
+
model: pinnedModel ?? profileModel,
|
|
1034
1131
|
});
|
|
1035
1132
|
}
|
|
1036
1133
|
catch (err) {
|
|
@@ -1078,21 +1175,26 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1078
1175
|
const autoCompactTokens = lane.provider === "claude"
|
|
1079
1176
|
? providerAutoCompactTokens(config.providers.claude)
|
|
1080
1177
|
: undefined;
|
|
1081
|
-
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens);
|
|
1178
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined);
|
|
1082
1179
|
const model = resolveLaneModel({
|
|
1083
|
-
cliModel:
|
|
1180
|
+
cliModel: pinnedModel,
|
|
1181
|
+
profileModel,
|
|
1084
1182
|
accountModel: accountCfg?.model,
|
|
1085
1183
|
providerModel: config.providers[lane.provider].model,
|
|
1086
1184
|
});
|
|
1087
1185
|
const effort = resolveLaneEffort({
|
|
1088
1186
|
cliEffort: existing.effort_source === "cli" ? existing.effort : undefined,
|
|
1187
|
+
profileEffort,
|
|
1089
1188
|
accountEffort: accountCfg?.effort,
|
|
1090
1189
|
providerEffort: config.providers[lane.provider].effort,
|
|
1091
1190
|
model,
|
|
1092
1191
|
});
|
|
1093
|
-
const fastMode = lane.provider === "codex"
|
|
1094
|
-
|
|
1095
|
-
|
|
1192
|
+
const fastMode = lane.provider === "codex"
|
|
1193
|
+
? profileDefaultsApply
|
|
1194
|
+
? profile?.meta.fast_mode === true
|
|
1195
|
+
: model !== undefined &&
|
|
1196
|
+
(config.providers.codex.fast_mode_models ?? []).includes(model)
|
|
1197
|
+
: false;
|
|
1096
1198
|
const resume = plan.mode === "resume"
|
|
1097
1199
|
? {
|
|
1098
1200
|
session_id: plan.session_id,
|
|
@@ -1122,6 +1224,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1122
1224
|
model,
|
|
1123
1225
|
effort: effort.effort,
|
|
1124
1226
|
fastMode,
|
|
1227
|
+
profile: profileLaunch,
|
|
1125
1228
|
resume,
|
|
1126
1229
|
});
|
|
1127
1230
|
// A systemd PATH mismatch is an environment defect, not a lane failure.
|
|
@@ -1170,6 +1273,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1170
1273
|
model,
|
|
1171
1274
|
effort: effort.effort,
|
|
1172
1275
|
fastMode,
|
|
1276
|
+
profile: profileLaunch,
|
|
1173
1277
|
resume: { session_id: resume.session_id, prompt: COMPACTION_PROMPT },
|
|
1174
1278
|
});
|
|
1175
1279
|
compaction = compactLaneSession({
|
|
@@ -1213,13 +1317,36 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1213
1317
|
pending_resume: undefined,
|
|
1214
1318
|
account: resolved.id,
|
|
1215
1319
|
...(model !== undefined ? { model } : {}),
|
|
1320
|
+
...(model !== undefined
|
|
1321
|
+
? {
|
|
1322
|
+
model_source: pinnedModel !== undefined
|
|
1323
|
+
? "cli"
|
|
1324
|
+
: profileDefaultsApply
|
|
1325
|
+
? "profile"
|
|
1326
|
+
: accountCfg?.model !== undefined
|
|
1327
|
+
? "account"
|
|
1328
|
+
: config.providers[lane.provider].model !== undefined
|
|
1329
|
+
? "provider"
|
|
1330
|
+
: "none",
|
|
1331
|
+
}
|
|
1332
|
+
: { model_source: "none" }),
|
|
1216
1333
|
effort_source: effort.source,
|
|
1217
1334
|
last_host_report_at: nowIso,
|
|
1335
|
+
...(profile
|
|
1336
|
+
? {
|
|
1337
|
+
worker_profile_hash: profile.contentHash,
|
|
1338
|
+
worker_profile_ignored: [...ignoredProfileFields],
|
|
1339
|
+
}
|
|
1340
|
+
: {}),
|
|
1218
1341
|
};
|
|
1219
1342
|
if (effort.effort !== undefined)
|
|
1220
1343
|
next.effort = effort.effort;
|
|
1221
1344
|
else
|
|
1222
1345
|
delete next.effort;
|
|
1346
|
+
// `undefined` deliberately overlays the CAS merge's current record so a
|
|
1347
|
+
// dynamic default disappearing removes the stale persisted model.
|
|
1348
|
+
if (model === undefined)
|
|
1349
|
+
next.model = undefined;
|
|
1223
1350
|
if (fastMode)
|
|
1224
1351
|
next.fast_mode = true;
|
|
1225
1352
|
else
|
|
@@ -1295,7 +1422,28 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1295
1422
|
resume: resume ? { session_id: resume.session_id } : null,
|
|
1296
1423
|
workerOutOffset,
|
|
1297
1424
|
fastMode,
|
|
1425
|
+
...(profile
|
|
1426
|
+
? {
|
|
1427
|
+
profile: {
|
|
1428
|
+
id: profile.id,
|
|
1429
|
+
hash: profile.contentHash,
|
|
1430
|
+
ignoredFields: ignoredProfileFields,
|
|
1431
|
+
...(profileRuntime?.settingsPath !== undefined
|
|
1432
|
+
? { settingsPath: profileRuntime.settingsPath }
|
|
1433
|
+
: {}),
|
|
1434
|
+
},
|
|
1435
|
+
}
|
|
1436
|
+
: {}),
|
|
1298
1437
|
});
|
|
1438
|
+
if (profile !== undefined) {
|
|
1439
|
+
appendWorkerProfileEvent(runDir, {
|
|
1440
|
+
profile_id: profile.id,
|
|
1441
|
+
profile_hash: profile.contentHash,
|
|
1442
|
+
changed: profileChanged,
|
|
1443
|
+
provider: lane.provider,
|
|
1444
|
+
ignored_fields: ignoredProfileFields,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1299
1447
|
}
|
|
1300
1448
|
catch (err) {
|
|
1301
1449
|
if (workerPid !== undefined)
|
|
@@ -1355,30 +1503,59 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
1355
1503
|
const logsDir = path.join(runDir, "logs");
|
|
1356
1504
|
ensureDir(logsDir);
|
|
1357
1505
|
const config = loadLanesConfig(hqRoot);
|
|
1506
|
+
const profile = lane.worker_profile
|
|
1507
|
+
? loadWorkerProfile(hqRoot, lane.worker_profile)
|
|
1508
|
+
: undefined;
|
|
1509
|
+
const profileDefaultsApply = profile !== undefined && profile.meta.provider === lane.provider;
|
|
1510
|
+
const profileModel = profileDefaultsApply ? profile.meta.model : undefined;
|
|
1511
|
+
const profileEffort = profileDefaultsApply ? profile.meta.effort : undefined;
|
|
1512
|
+
const ignoredProfileFields = profile
|
|
1513
|
+
? ignoredWorkerProfileFields(lane.provider)
|
|
1514
|
+
: [];
|
|
1515
|
+
const profileRuntime = profile
|
|
1516
|
+
? materializeWorkerProfile(profile, runDir)
|
|
1517
|
+
: undefined;
|
|
1518
|
+
const profileLaunch = profile
|
|
1519
|
+
? {
|
|
1520
|
+
systemPrompt: workerProfilePrompt(profile),
|
|
1521
|
+
...(profileRuntime?.settingsPath !== undefined
|
|
1522
|
+
? { settingsPath: profileRuntime.settingsPath }
|
|
1523
|
+
: {}),
|
|
1524
|
+
ignoredFields: ignoredProfileFields,
|
|
1525
|
+
}
|
|
1526
|
+
: undefined;
|
|
1527
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
1528
|
+
? lane.model
|
|
1529
|
+
: undefined;
|
|
1358
1530
|
const resolved = resolveAccount(lane.provider, lane.account, config, {
|
|
1359
1531
|
hqRoot,
|
|
1360
1532
|
existsSync,
|
|
1361
|
-
model:
|
|
1533
|
+
model: pinnedModel ?? profileModel,
|
|
1362
1534
|
});
|
|
1363
1535
|
const autoCompactTokens = lane.provider === "claude"
|
|
1364
1536
|
? providerAutoCompactTokens(config.providers.claude)
|
|
1365
1537
|
: undefined;
|
|
1366
|
-
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens);
|
|
1538
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined);
|
|
1367
1539
|
const accountCfg = config.providers[lane.provider].accounts[resolved.id];
|
|
1368
1540
|
const model = resolveLaneModel({
|
|
1369
|
-
cliModel:
|
|
1541
|
+
cliModel: pinnedModel,
|
|
1542
|
+
profileModel,
|
|
1370
1543
|
accountModel: accountCfg?.model,
|
|
1371
1544
|
providerModel: config.providers[lane.provider].model,
|
|
1372
1545
|
});
|
|
1373
1546
|
const effort = resolveLaneEffort({
|
|
1374
1547
|
cliEffort: lane.effort_source === "cli" ? lane.effort : undefined,
|
|
1548
|
+
profileEffort,
|
|
1375
1549
|
accountEffort: accountCfg?.effort,
|
|
1376
1550
|
providerEffort: config.providers[lane.provider].effort,
|
|
1377
1551
|
model,
|
|
1378
1552
|
});
|
|
1379
|
-
const fastMode = lane.provider === "codex"
|
|
1380
|
-
|
|
1381
|
-
|
|
1553
|
+
const fastMode = lane.provider === "codex"
|
|
1554
|
+
? profileDefaultsApply
|
|
1555
|
+
? profile?.meta.fast_mode === true
|
|
1556
|
+
: model !== undefined &&
|
|
1557
|
+
(config.providers.codex.fast_mode_models ?? []).includes(model)
|
|
1558
|
+
: false;
|
|
1382
1559
|
const repairPrompt = envelopeRepairPrompt({
|
|
1383
1560
|
envelopePath: input.envelopePath,
|
|
1384
1561
|
error: input.error,
|
|
@@ -1392,6 +1569,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
1392
1569
|
model,
|
|
1393
1570
|
effort: effort.effort,
|
|
1394
1571
|
fastMode,
|
|
1572
|
+
profile: profileLaunch,
|
|
1395
1573
|
...(mode === "resume" && sessionId !== null
|
|
1396
1574
|
? { resume: { session_id: sessionId, prompt: repairPrompt } }
|
|
1397
1575
|
: { freshTurnPrompt: repairPrompt }),
|
|
@@ -1418,9 +1596,55 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
1418
1596
|
resume: mode === "resume" && sessionId !== null ? { session_id: sessionId } : null,
|
|
1419
1597
|
workerOutOffset,
|
|
1420
1598
|
fastMode,
|
|
1599
|
+
...(profile
|
|
1600
|
+
? {
|
|
1601
|
+
profile: {
|
|
1602
|
+
id: profile.id,
|
|
1603
|
+
hash: profile.contentHash,
|
|
1604
|
+
ignoredFields: ignoredProfileFields,
|
|
1605
|
+
...(profileRuntime?.settingsPath !== undefined
|
|
1606
|
+
? { settingsPath: profileRuntime.settingsPath }
|
|
1607
|
+
: {}),
|
|
1608
|
+
},
|
|
1609
|
+
}
|
|
1610
|
+
: {}),
|
|
1421
1611
|
});
|
|
1612
|
+
if (profile !== undefined) {
|
|
1613
|
+
appendWorkerProfileEvent(runDir, {
|
|
1614
|
+
profile_id: profile.id,
|
|
1615
|
+
profile_hash: profile.contentHash,
|
|
1616
|
+
changed: lane.worker_profile_hash !== profile.contentHash,
|
|
1617
|
+
provider: lane.provider,
|
|
1618
|
+
ignored_fields: ignoredProfileFields,
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1422
1621
|
retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => {
|
|
1423
1622
|
const next = { ...current };
|
|
1623
|
+
if (profile !== undefined) {
|
|
1624
|
+
next.worker_profile_hash = profile.contentHash;
|
|
1625
|
+
next.worker_profile_ignored = [...ignoredProfileFields];
|
|
1626
|
+
if (model !== undefined) {
|
|
1627
|
+
next.model = model;
|
|
1628
|
+
next.model_source = pinnedModel !== undefined
|
|
1629
|
+
? "cli"
|
|
1630
|
+
: profileDefaultsApply
|
|
1631
|
+
? "profile"
|
|
1632
|
+
: accountCfg?.model !== undefined
|
|
1633
|
+
? "account"
|
|
1634
|
+
: config.providers[lane.provider].model !== undefined
|
|
1635
|
+
? "provider"
|
|
1636
|
+
: "none";
|
|
1637
|
+
}
|
|
1638
|
+
else {
|
|
1639
|
+
next.model = undefined;
|
|
1640
|
+
next.model_source = "none";
|
|
1641
|
+
}
|
|
1642
|
+
next.effort_source = effort.source;
|
|
1643
|
+
if (effort.effort !== undefined)
|
|
1644
|
+
next.effort = effort.effort;
|
|
1645
|
+
else
|
|
1646
|
+
delete next.effort;
|
|
1647
|
+
}
|
|
1424
1648
|
if (fastMode)
|
|
1425
1649
|
next.fast_mode = true;
|
|
1426
1650
|
else
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -268,6 +268,15 @@ function parseOptionalCompanyField(raw, filePath) {
|
|
|
268
268
|
requireString(raw.company.slug, "company.slug");
|
|
269
269
|
requireString(raw.company.uid, "company.uid");
|
|
270
270
|
}
|
|
271
|
+
function parseOptionalMeshCompanyField(raw, filePath) {
|
|
272
|
+
if (raw.mesh_company === undefined)
|
|
273
|
+
return;
|
|
274
|
+
if (!isPlainObject(raw.mesh_company)) {
|
|
275
|
+
throw new LaneSchemaError("mesh_company must be an object", { path: filePath });
|
|
276
|
+
}
|
|
277
|
+
requireString(raw.mesh_company.slug, "mesh_company.slug");
|
|
278
|
+
requireString(raw.mesh_company.uid, "mesh_company.uid");
|
|
279
|
+
}
|
|
271
280
|
export function parseLaneRecord(raw, filePath) {
|
|
272
281
|
if (!isPlainObject(raw)) {
|
|
273
282
|
throw new LaneSchemaError("Lane record must be a JSON object", {
|
|
@@ -294,6 +303,7 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
294
303
|
throw new LaneSchemaError(`Unknown provider ${JSON.stringify(raw.provider)}`, { path: filePath, value: raw.provider });
|
|
295
304
|
}
|
|
296
305
|
parseOptionalCompanyField(raw, filePath);
|
|
306
|
+
parseOptionalMeshCompanyField(raw, filePath);
|
|
297
307
|
if (!isPlainObject(raw.senior)) {
|
|
298
308
|
throw new LaneSchemaError("senior must be an object", { path: filePath });
|
|
299
309
|
}
|