@indigoai-us/hq-cli 5.158.0 → 5.160.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 +33 -0
- package/dist/command-catalog.generated.d.ts +29 -3
- package/dist/command-catalog.generated.js +36 -3
- package/dist/commands/agents.d.ts +5 -0
- package/dist/commands/agents.js +114 -3
- package/dist/commands/lanes.js +128 -11
- package/dist/lib/core-utils/sentry-report.d.ts +1 -1
- package/dist/lib/core-utils/sentry-report.js +107 -4
- package/dist/lib/lanes/errors.d.ts +1 -1
- package/dist/lib/lanes/profile.d.ts +5 -5
- package/dist/lib/lanes/profile.js +50 -13
- package/dist/lib/lanes/reconcile.js +20 -9
- package/dist/lib/lanes/spawn.d.ts +12 -1
- package/dist/lib/lanes/spawn.js +79 -16
- package/dist/lib/lanes/store.js +5 -0
- package/dist/lib/lanes/types.d.ts +5 -3
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.d.ts +14 -8
- package/dist/lib/lanes/watcher.js +67 -25
- package/package.json +1 -1
|
@@ -45,11 +45,26 @@ const HOOK_TIMEOUT_WARNING_METRIC = "hq.hook_timeout_warning";
|
|
|
45
45
|
const HOOK_TIMEOUT_EXCEEDED_METRIC = "hq.hook_timeout_exceeded";
|
|
46
46
|
const HOOK_TIMEOUT_EXCEEDED_FINGERPRINT = "hook-timeout-exceeded";
|
|
47
47
|
const HOOK_TIMEOUT_WARNING_FINGERPRINT = "hook-timeout";
|
|
48
|
+
const HOOK_TIMEOUT_MACHINE_WIDE_FINGERPRINT = "hq-hook-slow";
|
|
48
49
|
const MAX_HOOK_SEQUENCE_ENTRIES = 20;
|
|
49
50
|
const HOOK_TIMEOUT_RATE_LIMIT_WINDOW_MS = 10 * 60 * 1_000;
|
|
50
51
|
const RATE_LIMIT_LOCK_WAIT_MS = 500;
|
|
51
52
|
const RATE_LIMIT_LOCK_POLL_MS = 5;
|
|
52
53
|
const RATE_LIMIT_LOCK_STALE_MS = 5_000;
|
|
54
|
+
const KNOWN_HOOK_SCRIPTS = new Set([
|
|
55
|
+
"master-hook.sh",
|
|
56
|
+
"hook-gate.sh",
|
|
57
|
+
"hook-timeout-watchdog.sh",
|
|
58
|
+
"check-hq-update.sh",
|
|
59
|
+
"block-core-writes-bash.sh",
|
|
60
|
+
"block-core-writes.sh",
|
|
61
|
+
"block-hq-worktree-session.sh",
|
|
62
|
+
"inject-policy-on-trigger.sh",
|
|
63
|
+
"inject-local-context.sh",
|
|
64
|
+
"session-title.sh",
|
|
65
|
+
"45-lanes-senior-monitor.sh",
|
|
66
|
+
"lanes-senior-monitor-stop-gate.sh",
|
|
67
|
+
]);
|
|
53
68
|
/**
|
|
54
69
|
* The only hook diagnostics that can be attached as metadata. The list omits
|
|
55
70
|
* command text, prompts, payloads, company names, and repositories. `hook_path`
|
|
@@ -71,6 +86,9 @@ export const ALLOWED_SENTRY_REPORT_METADATA_KEYS = [
|
|
|
71
86
|
"hq_version",
|
|
72
87
|
"platform",
|
|
73
88
|
"load_average",
|
|
89
|
+
"spawn_ms",
|
|
90
|
+
"slow_child",
|
|
91
|
+
"slow_child_ms",
|
|
74
92
|
"exit_code",
|
|
75
93
|
"final_elapsed_ms",
|
|
76
94
|
"bash_env_set",
|
|
@@ -223,6 +241,9 @@ export function parseSentryReportEvent(stdin) {
|
|
|
223
241
|
? boundedHookSequence(rawMetadata[key])
|
|
224
242
|
: rawMetadata[key];
|
|
225
243
|
}
|
|
244
|
+
if (typeof metadata.slow_child === "string") {
|
|
245
|
+
metadata.slow_child = safeSlowChild(metadata);
|
|
246
|
+
}
|
|
226
247
|
return {
|
|
227
248
|
code: 0,
|
|
228
249
|
event: {
|
|
@@ -311,6 +332,63 @@ function loadBucket(loadAverage) {
|
|
|
311
332
|
return "10-20";
|
|
312
333
|
return ">=20";
|
|
313
334
|
}
|
|
335
|
+
function parsedLoadAverage(metadata) {
|
|
336
|
+
const value = metadata.load_average;
|
|
337
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
338
|
+
return value;
|
|
339
|
+
if (typeof value !== "string")
|
|
340
|
+
return undefined;
|
|
341
|
+
if (value === "unavailable")
|
|
342
|
+
return "unavailable";
|
|
343
|
+
const match = /^\s*\{?\s*([0-9]+(?:\.[0-9]+)?)/.exec(value);
|
|
344
|
+
if (!match)
|
|
345
|
+
return undefined;
|
|
346
|
+
const parsed = Number(match[1]);
|
|
347
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
348
|
+
}
|
|
349
|
+
function operatingSystemName(metadata) {
|
|
350
|
+
const platform = stringMetadata(metadata, "platform", "").toLowerCase();
|
|
351
|
+
if (/windows|win32|mingw|msys|cygwin/.test(platform))
|
|
352
|
+
return "windows";
|
|
353
|
+
if (/darwin|macos|mac os/.test(platform))
|
|
354
|
+
return "macos";
|
|
355
|
+
if (/linux/.test(platform))
|
|
356
|
+
return "linux";
|
|
357
|
+
return "other";
|
|
358
|
+
}
|
|
359
|
+
function spawnMsBucket(metadata) {
|
|
360
|
+
const value = metadata.spawn_ms;
|
|
361
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
362
|
+
return "unavailable";
|
|
363
|
+
if (value < 100)
|
|
364
|
+
return "<100ms";
|
|
365
|
+
if (value < 500)
|
|
366
|
+
return "100-499ms";
|
|
367
|
+
if (value < 1_000)
|
|
368
|
+
return "500-999ms";
|
|
369
|
+
return ">=1000ms";
|
|
370
|
+
}
|
|
371
|
+
function safeSlowChild(metadata) {
|
|
372
|
+
const value = metadata.slow_child;
|
|
373
|
+
if (typeof value !== "string" || value.length === 0)
|
|
374
|
+
return "other";
|
|
375
|
+
const script = basenameFromPath(value);
|
|
376
|
+
return KNOWN_HOOK_SCRIPTS.has(script) ? script : "other";
|
|
377
|
+
}
|
|
378
|
+
function isMachineWideHookSlowness(metadata) {
|
|
379
|
+
const spawnMs = metadata.spawn_ms;
|
|
380
|
+
if (typeof spawnMs === "number" && Number.isFinite(spawnMs) && spawnMs >= 500)
|
|
381
|
+
return true;
|
|
382
|
+
if (!Array.isArray(metadata.hook_sequence))
|
|
383
|
+
return false;
|
|
384
|
+
const slowScripts = new Set();
|
|
385
|
+
for (const entry of metadata.hook_sequence) {
|
|
386
|
+
if (entry.ms < 20_000 || !SAFE_HOOK_SCRIPT.test(entry.script))
|
|
387
|
+
continue;
|
|
388
|
+
slowScripts.add(entry.script);
|
|
389
|
+
}
|
|
390
|
+
return slowScripts.size >= 3;
|
|
391
|
+
}
|
|
314
392
|
function bashEnvTag(metadata) {
|
|
315
393
|
const value = metadata.bash_env_set;
|
|
316
394
|
if (value === true || value === "set")
|
|
@@ -330,7 +408,8 @@ function hookTimeoutTags(event) {
|
|
|
330
408
|
: undefined;
|
|
331
409
|
const shell = stringMetadata(metadata, "shell");
|
|
332
410
|
const nproc = metadata.nproc;
|
|
333
|
-
const loadAverage = metadata
|
|
411
|
+
const loadAverage = parsedLoadAverage(metadata);
|
|
412
|
+
const slowChildMs = metadata.slow_child_ms;
|
|
334
413
|
return {
|
|
335
414
|
hook_event: hookEvent(metadata),
|
|
336
415
|
hook_script: hookScript(metadata),
|
|
@@ -346,7 +425,14 @@ function hookTimeoutTags(event) {
|
|
|
346
425
|
shell: safeTagValue(shell),
|
|
347
426
|
bash_env: bashEnvTag(metadata),
|
|
348
427
|
cwd_kind: safeMetadataTag(metadata, "cwd_kind"),
|
|
349
|
-
|
|
428
|
+
os_name: operatingSystemName(metadata),
|
|
429
|
+
load_bucket: loadAverage === "unavailable" ? "unavailable" : loadBucket(loadAverage),
|
|
430
|
+
spawn_ms_bucket: spawnMsBucket(metadata),
|
|
431
|
+
slow_child: safeSlowChild(metadata),
|
|
432
|
+
slow_child_ms_bucket: typeof slowChildMs === "number" && Number.isFinite(slowChildMs)
|
|
433
|
+
? elapsedBucket(slowChildMs)
|
|
434
|
+
: "unknown",
|
|
435
|
+
machine_wide: isMachineWideHookSlowness(metadata) ? "true" : "false",
|
|
350
436
|
nproc: typeof nproc === "number" || typeof nproc === "string"
|
|
351
437
|
? safeTagValue(String(nproc), SAFE_NPROC)
|
|
352
438
|
: "unknown",
|
|
@@ -393,7 +479,20 @@ function hookTimeoutExceededOverrides(event) {
|
|
|
393
479
|
const eventName = hookEvent(event.metadata);
|
|
394
480
|
if (!isHookTimeoutEvent(event))
|
|
395
481
|
return undefined;
|
|
396
|
-
|
|
482
|
+
const kind = hookTimeoutKind(event);
|
|
483
|
+
if (isMachineWideHookSlowness(event.metadata)) {
|
|
484
|
+
return {
|
|
485
|
+
level: "warning",
|
|
486
|
+
fingerprint: [
|
|
487
|
+
HOOK_TIMEOUT_MACHINE_WIDE_FINGERPRINT,
|
|
488
|
+
"machine-wide",
|
|
489
|
+
operatingSystemName(event.metadata),
|
|
490
|
+
eventName,
|
|
491
|
+
kind,
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (kind === "warning") {
|
|
397
496
|
return {
|
|
398
497
|
level: "warning",
|
|
399
498
|
fingerprint: [HOOK_TIMEOUT_WARNING_FINGERPRINT, eventName, hookScript(event.metadata)],
|
|
@@ -536,7 +635,11 @@ function pruneExpiredRateLimitState(directory, now, protectedStatePath) {
|
|
|
536
635
|
}
|
|
537
636
|
function timeoutRateLimitLease(event, kind, options, dependencies) {
|
|
538
637
|
const sessionId = stringMetadata(event.metadata, "session_id");
|
|
539
|
-
const
|
|
638
|
+
const machineWide = isMachineWideHookSlowness(event.metadata);
|
|
639
|
+
const classification = machineWide
|
|
640
|
+
? `machine-wide\0${operatingSystemName(event.metadata)}`
|
|
641
|
+
: `per-script\0${hookScript(event.metadata)}`;
|
|
642
|
+
const key = `${sessionId}\0${hookEvent(event.metadata)}\0${kind}\0${classification}`;
|
|
540
643
|
const directory = rateLimitDirectory(options, dependencies);
|
|
541
644
|
const statePath = rateLimitStatePath(directory, key);
|
|
542
645
|
const releaseLock = acquireRateLimitLock(directory);
|
|
@@ -34,6 +34,6 @@ export declare class AccountResolutionError extends LanesError {
|
|
|
34
34
|
constructor(error: "unknown_account" | "account_disabled" | "no_enabled_account" | "account_model_unavailable" | "account_quota_exhausted", message: string, details?: Record<string, unknown>);
|
|
35
35
|
}
|
|
36
36
|
export declare class WorkerResolutionError extends LanesError {
|
|
37
|
-
constructor(error: "worker_not_found" | "worker_tenancy_unresolved" | "worker_not_available" | "worker_not_active" | "worker_id_conflict" | "worker_profile_invalid" | "worker_profile_ambiguous" | "package_workers_unmounted" | "worker_registry_unreadable", message: string, details?: Record<string, unknown>);
|
|
37
|
+
constructor(error: "worker_not_found" | "worker_tenancy_unresolved" | "worker_not_available" | "worker_not_active" | "worker_id_conflict" | "worker_profile_invalid" | "worker_policy_invalid" | "worker_profile_ambiguous" | "package_workers_unmounted" | "worker_registry_unreadable", message: string, details?: Record<string, unknown>);
|
|
38
38
|
}
|
|
39
39
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -10,10 +10,10 @@ import { type LaneProvider } from "./types.js";
|
|
|
10
10
|
export interface WorkerProfileMeta {
|
|
11
11
|
version?: string;
|
|
12
12
|
provider: LaneProvider;
|
|
13
|
-
model
|
|
14
|
-
account
|
|
15
|
-
effort
|
|
16
|
-
fast_mode
|
|
13
|
+
model?: string;
|
|
14
|
+
account?: string;
|
|
15
|
+
effort?: string;
|
|
16
|
+
fast_mode?: boolean;
|
|
17
17
|
max_concurrent?: number;
|
|
18
18
|
source?: string;
|
|
19
19
|
accounts?: string[];
|
|
@@ -36,7 +36,7 @@ export declare function personalWorkerProfileDir(hqRoot: string, id: string): st
|
|
|
36
36
|
/** A meta.yaml file is the explicit marker for a personal worker profile. */
|
|
37
37
|
export declare function workerProfileMarker(hqRoot: string, id: string): "absent" | "present";
|
|
38
38
|
export declare function loadWorkerProfile(hqRoot: string, id: string): WorkerProfile;
|
|
39
|
-
export declare function workerProfilePrompt(profile: WorkerProfile): string;
|
|
39
|
+
export declare function workerProfilePrompt(profile: WorkerProfile, delivery?: "hook" | "prompt"): string;
|
|
40
40
|
export declare function ignoredWorkerProfileFields(provider: LaneProvider): string[];
|
|
41
41
|
/**
|
|
42
42
|
* Materialize provider settings and hook files in the lane run directory.
|
|
@@ -135,20 +135,22 @@ function parseMeta(id, dir, raw) {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
const providerValue = provider.trim();
|
|
138
|
-
const
|
|
138
|
+
const optionalString = (field) => {
|
|
139
139
|
const value = raw[field];
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return undefined;
|
|
140
142
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
141
|
-
throw profileError(id, dir, `meta.yaml ${field} must be a non-empty string`, {
|
|
143
|
+
throw profileError(id, dir, `meta.yaml ${field} must be a non-empty string when present`, {
|
|
142
144
|
field,
|
|
143
145
|
value,
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
return value.trim();
|
|
147
149
|
};
|
|
148
|
-
const model =
|
|
149
|
-
const account =
|
|
150
|
-
const effort =
|
|
151
|
-
if (!PROVIDER_EFFORT_LEVELS[providerValue].includes(effort)) {
|
|
150
|
+
const model = optionalString("model");
|
|
151
|
+
const account = optionalString("account");
|
|
152
|
+
const effort = optionalString("effort");
|
|
153
|
+
if (effort !== undefined && !PROVIDER_EFFORT_LEVELS[providerValue].includes(effort)) {
|
|
152
154
|
throw profileError(id, dir, `meta.yaml effort ${JSON.stringify(effort)} is invalid for ${providerValue}`, {
|
|
153
155
|
field: "effort",
|
|
154
156
|
provider: providerValue,
|
|
@@ -156,8 +158,8 @@ function parseMeta(id, dir, raw) {
|
|
|
156
158
|
accepted: [...PROVIDER_EFFORT_LEVELS[providerValue]],
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
|
-
if (typeof raw.fast_mode !== "boolean") {
|
|
160
|
-
throw profileError(id, dir, "meta.yaml fast_mode must be a boolean", {
|
|
161
|
+
if (raw.fast_mode !== undefined && typeof raw.fast_mode !== "boolean") {
|
|
162
|
+
throw profileError(id, dir, "meta.yaml fast_mode must be a boolean when present", {
|
|
161
163
|
field: "fast_mode",
|
|
162
164
|
value: raw.fast_mode,
|
|
163
165
|
});
|
|
@@ -205,15 +207,45 @@ function parseMeta(id, dir, raw) {
|
|
|
205
207
|
return {
|
|
206
208
|
...(version !== undefined ? { version } : {}),
|
|
207
209
|
provider: providerValue,
|
|
208
|
-
model,
|
|
209
|
-
account,
|
|
210
|
-
effort,
|
|
211
|
-
fast_mode: raw.fast_mode,
|
|
210
|
+
...(model !== undefined ? { model } : {}),
|
|
211
|
+
...(account !== undefined ? { account } : {}),
|
|
212
|
+
...(effort !== undefined ? { effort } : {}),
|
|
213
|
+
...(raw.fast_mode !== undefined ? { fast_mode: raw.fast_mode } : {}),
|
|
212
214
|
...(maxConcurrent !== undefined ? { max_concurrent: maxConcurrent } : {}),
|
|
213
215
|
...(source !== undefined ? { source } : {}),
|
|
214
216
|
...(accounts !== undefined ? { accounts } : {}),
|
|
215
217
|
};
|
|
216
218
|
}
|
|
219
|
+
function validatePolicyFrontmatter(id, profileDir, relative) {
|
|
220
|
+
const filePath = path.join(profileDir, relative);
|
|
221
|
+
const content = readRequiredText(id, profileDir, filePath, relative);
|
|
222
|
+
const lines = content.split(/\r?\n/);
|
|
223
|
+
if (lines[0] !== "---") {
|
|
224
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} must start with YAML frontmatter`, { worker: id, file: filePath, field: "frontmatter" });
|
|
225
|
+
}
|
|
226
|
+
const closing = lines.findIndex((line, index) => index > 0 && line === "---");
|
|
227
|
+
if (closing < 0 || closing === 1) {
|
|
228
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} must contain a non-empty YAML frontmatter block`, { worker: id, file: filePath, field: "frontmatter" });
|
|
229
|
+
}
|
|
230
|
+
let frontmatter;
|
|
231
|
+
try {
|
|
232
|
+
frontmatter = parseUserYaml(lines.slice(1, closing).join("\n"), filePath);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} has invalid YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`, { worker: id, file: filePath, field: "frontmatter" });
|
|
236
|
+
}
|
|
237
|
+
if (!isPlainObject(frontmatter)) {
|
|
238
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} frontmatter must contain a YAML object`, { worker: id, file: filePath, field: "frontmatter" });
|
|
239
|
+
}
|
|
240
|
+
if (typeof frontmatter.when !== "string" || frontmatter.when.trim().length === 0) {
|
|
241
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} frontmatter must include a non-empty when field`, { worker: id, file: filePath, field: "when" });
|
|
242
|
+
}
|
|
243
|
+
if (!Array.isArray(frontmatter.on) ||
|
|
244
|
+
frontmatter.on.length === 0 ||
|
|
245
|
+
frontmatter.on.some((value) => typeof value !== "string" || value.trim().length === 0)) {
|
|
246
|
+
throw new WorkerResolutionError("worker_policy_invalid", `Worker policy ${filePath} frontmatter must include a non-empty on string array`, { worker: id, file: filePath, field: "on" });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
217
249
|
function readSettings(id, dir) {
|
|
218
250
|
const filePath = path.join(dir, PROFILE_SETTINGS);
|
|
219
251
|
const stat = lstatOrAbsent(filePath);
|
|
@@ -308,6 +340,9 @@ export function loadWorkerProfile(hqRoot, id) {
|
|
|
308
340
|
}
|
|
309
341
|
const settingsResult = readSettings(id, dir);
|
|
310
342
|
const policyFiles = collectFiles(id, dir, PROFILE_POLICIES);
|
|
343
|
+
for (const relative of policyFiles.filter((file) => file.toLowerCase().endsWith(".md"))) {
|
|
344
|
+
validatePolicyFrontmatter(id, dir, relative);
|
|
345
|
+
}
|
|
311
346
|
const hookFiles = collectFiles(id, dir, PROFILE_HOOKS);
|
|
312
347
|
const hashFiles = [
|
|
313
348
|
PROFILE_META,
|
|
@@ -327,8 +362,10 @@ export function loadWorkerProfile(hqRoot, id) {
|
|
|
327
362
|
contentHash: hashProfileFiles(dir, hashFiles),
|
|
328
363
|
};
|
|
329
364
|
}
|
|
330
|
-
export function workerProfilePrompt(profile) {
|
|
365
|
+
export function workerProfilePrompt(profile, delivery = "prompt") {
|
|
331
366
|
const parts = [profile.systemPrompt.trimEnd()];
|
|
367
|
+
if (delivery === "hook")
|
|
368
|
+
return parts.join("\n");
|
|
332
369
|
for (const relative of profile.policyFiles) {
|
|
333
370
|
const content = readRequiredText(profile.id, profile.dir, path.join(profile.dir, relative), relative);
|
|
334
371
|
parts.push(`\n## Profile policy: ${relative}\n${content.trimEnd()}`);
|
|
@@ -15,12 +15,12 @@ import { MAX_LANE_DEPTH } from "./constants.js";
|
|
|
15
15
|
import { AccountResolutionError, LanesError, isLaneVersionConflict } from "./errors.js";
|
|
16
16
|
import { loadWorkerProfile } from "./profile.js";
|
|
17
17
|
import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, } from "./liveness.js";
|
|
18
|
-
import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, isPidRecordAlive, laneRunDir, readPidRecord, readSpawnManifest, markSpawnFailure, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
|
|
18
|
+
import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, readPidRecord, readSpawnManifest, markSpawnFailure, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
|
|
19
19
|
import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
|
|
20
20
|
import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, throttleStatePath, writeThrottleState, } from "./throttle.js";
|
|
21
21
|
import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./quota.js";
|
|
22
22
|
import { answeredQuestionEntries, raiseEnvelopeQuestions } from "./questions.js";
|
|
23
|
-
import { SILENCE_MS, classifyLaneExit, isPidAlive,
|
|
23
|
+
import { SILENCE_MS, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, } from "./watcher.js";
|
|
24
24
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
|
|
25
25
|
import { laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
26
26
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
@@ -570,9 +570,11 @@ async function withReconcileLock(hqRoot, fn) {
|
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
function classifyDeadLocalLane(hqRoot, laneId) {
|
|
573
|
-
const latest = readLatestEnvelopeRecord(hqRoot, laneId);
|
|
574
|
-
const envelope = latest?.envelope ?? readLatestEnvelope(hqRoot, laneId);
|
|
575
573
|
const runDir = laneRunDir(hqRoot, laneId);
|
|
574
|
+
const manifest = readSpawnManifest(runDir);
|
|
575
|
+
const minEnvelopeName = manifest.envelope_path ? path.basename(manifest.envelope_path) : undefined;
|
|
576
|
+
const latest = readLatestEnvelopeRecord(hqRoot, laneId, minEnvelopeName);
|
|
577
|
+
const envelope = latest?.envelope ?? null;
|
|
576
578
|
const hasPartialOutput = [
|
|
577
579
|
path.join(runDir, "logs", "worker.out"),
|
|
578
580
|
path.join(runDir, "logs", "worker.err"),
|
|
@@ -896,22 +898,31 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
896
898
|
// Match the watcher's one-repair decision before classifying an
|
|
897
899
|
// unparseable envelope after both processes have died. As in the
|
|
898
900
|
// watcher, a provider rejection or a quota signal skips the repair.
|
|
899
|
-
const
|
|
901
|
+
const runDir = laneRunDir(hqRoot, lane.lane_id);
|
|
902
|
+
const manifest = readSpawnManifest(runDir);
|
|
903
|
+
const minEnvelopeName = manifest.envelope_path ? path.basename(manifest.envelope_path) : undefined;
|
|
904
|
+
const rawLatest = readLatestEnvelopeFile(hqRoot, lane.lane_id, minEnvelopeName);
|
|
905
|
+
const missingExpected = rawLatest === null
|
|
906
|
+
? missingExpectedEnvelopeFile(hqRoot, lane.lane_id, manifest.envelope_path)
|
|
907
|
+
: null;
|
|
908
|
+
const repairCandidate = rawLatest ?? missingExpected;
|
|
900
909
|
let invalidEnvelope = null;
|
|
901
910
|
if (!deps.dryRun &&
|
|
902
911
|
!providerRejection &&
|
|
903
912
|
!quota &&
|
|
904
|
-
|
|
905
|
-
|
|
913
|
+
repairCandidate &&
|
|
914
|
+
repairCandidate.envelope === null &&
|
|
915
|
+
(!repairCandidate.missing || envelopeRepairMode(classified.provider) === "resume")) {
|
|
906
916
|
const spawnRepair = deps.spawnRepair ??
|
|
907
917
|
((root, repairLane, input) => spawnEnvelopeRepair(root, repairLane, input, deps.spawnLaneDeps));
|
|
908
|
-
const repair = decideEnvelopeRepair(hqRoot, lane.lane_id,
|
|
918
|
+
const repair = decideEnvelopeRepair(hqRoot, lane.lane_id, repairCandidate, nowMs, spawnRepair, () => undefined);
|
|
909
919
|
if (repair.action === "spawned") {
|
|
910
920
|
armLaneWatcher(hqRoot, lane.lane_id, deps.spawnLaneDeps);
|
|
911
921
|
summary.heartbeats.push(`${lane.lane_id}:envelope-repair`);
|
|
912
922
|
continue;
|
|
913
923
|
}
|
|
914
|
-
|
|
924
|
+
if (!repairCandidate.missing)
|
|
925
|
+
invalidEnvelope = { errors: repair.errors };
|
|
915
926
|
}
|
|
916
927
|
const repairedClassification = invalidEnvelope
|
|
917
928
|
? classifyLaneExit({ envelope: null, hasPartialOutput: false, invalidEnvelope })
|
|
@@ -198,6 +198,7 @@ export declare function envelopeRepairPrompt(input: {
|
|
|
198
198
|
envelopePath: string;
|
|
199
199
|
error: string;
|
|
200
200
|
rawText: string;
|
|
201
|
+
missing?: boolean;
|
|
201
202
|
}): string;
|
|
202
203
|
export interface ProviderResumeInput {
|
|
203
204
|
session_id: string;
|
|
@@ -228,7 +229,10 @@ export declare function buildProviderCommand(provider: LaneProvider, input: {
|
|
|
228
229
|
export declare function workerEnv(base: NodeJS.ProcessEnv, resolved: {
|
|
229
230
|
envVar: string;
|
|
230
231
|
home: string;
|
|
231
|
-
}, laneId: string, hqRoot: string, autoCompactTokens?: number, meshCompany?: LaneCompanyRef): NodeJS.ProcessEnv;
|
|
232
|
+
}, laneId: string, hqRoot: string, autoCompactTokens?: number, meshCompany?: LaneCompanyRef, workerPolicyDir?: string): NodeJS.ProcessEnv;
|
|
233
|
+
export declare const WORKER_POLICY_HOOK_CAPABILITY = "# hq-capability: worker-policy-dir v1";
|
|
234
|
+
/** Grok does not run HQ hooks; Claude and Codex use the installed capability marker. */
|
|
235
|
+
export declare function workerPolicyDeliveryMode(hqRoot: string, provider: LaneProvider): "hook" | "prompt";
|
|
232
236
|
export declare function markSpawnFailure(hqRoot: string, laneId: string, reasonRaw: string, details: Record<string, unknown>): LaneRecord;
|
|
233
237
|
/**
|
|
234
238
|
* Spec §5.2 executing-side check. Compares this machine's identity to
|
|
@@ -251,6 +255,11 @@ export interface SpawnManifestFile {
|
|
|
251
255
|
worker_profile_hash?: string;
|
|
252
256
|
worker_profile_ignored?: string[];
|
|
253
257
|
worker_profile_settings?: string;
|
|
258
|
+
worker_policy_delivery?: "hook" | "prompt";
|
|
259
|
+
worker_policy_dir?: string;
|
|
260
|
+
worker_policy_env?: {
|
|
261
|
+
HQ_POLICY_WORKER_DIR: string;
|
|
262
|
+
};
|
|
254
263
|
}
|
|
255
264
|
/** Read the per-run manifest. Missing file or field reads as absent. */
|
|
256
265
|
export declare function readSpawnManifest(runDir: string): SpawnManifestFile;
|
|
@@ -282,6 +291,8 @@ export interface EnvelopeRepairSpawnInput {
|
|
|
282
291
|
envelopePath: string;
|
|
283
292
|
error: string;
|
|
284
293
|
rawText: string;
|
|
294
|
+
/** True when this run never wrote its expected envelope file. */
|
|
295
|
+
missing?: boolean;
|
|
285
296
|
}
|
|
286
297
|
/** How the spec §4 repair turn reaches the provider (recorded as `envelope_repair.mode`). */
|
|
287
298
|
export type EnvelopeRepairMode = "resume" | "fresh_turn";
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -1013,6 +1013,17 @@ export function envelopeRepairPrompt(input) {
|
|
|
1013
1013
|
const rawText = input.rawText.length <= maxRawTextChars
|
|
1014
1014
|
? input.rawText
|
|
1015
1015
|
: `${input.rawText.slice(0, 12_000)}\n\n[... raw envelope truncated; inspect ${input.envelopePath} for the complete file ...]\n\n${input.rawText.slice(-12_000)}`;
|
|
1016
|
+
if (input.missing) {
|
|
1017
|
+
return [
|
|
1018
|
+
`Missing expected envelope file: ${input.envelopePath}`,
|
|
1019
|
+
"The previous envelope is stale and must not be reused or copied into this run's result.",
|
|
1020
|
+
"Printing an envelope in the conversation or writing it to last-message.txt is not enough; write the JSON object directly to the expected file.",
|
|
1021
|
+
"Schema:",
|
|
1022
|
+
JSON.stringify(LANE_ENVELOPE_JSON_SCHEMA, null, 2),
|
|
1023
|
+
`Write the corrected JSON object to ${input.envelopePath}, overwriting it. ` +
|
|
1024
|
+
`Output only that JSON. Do not add keys the schema does not name, and do not resume the task.`,
|
|
1025
|
+
].join("\n\n");
|
|
1026
|
+
}
|
|
1016
1027
|
return [
|
|
1017
1028
|
`The file you wrote at ${input.envelopePath} is not a valid hq-lane-envelope.v1. ` +
|
|
1018
1029
|
`This is the single repair turn: rewrite that file so it validates, and do nothing else.`,
|
|
@@ -1119,7 +1130,7 @@ export function buildProviderCommand(provider, input) {
|
|
|
1119
1130
|
args: appendFastModeFlags(parsed, appendEffortFlag(parsed, appendModelFlag(parsed, appendProviderProfileFlags(parsed, args, input.profile), input.model), input.effort), input.fastMode),
|
|
1120
1131
|
};
|
|
1121
1132
|
}
|
|
1122
|
-
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, meshCompany) {
|
|
1133
|
+
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, meshCompany, workerPolicyDir) {
|
|
1123
1134
|
const accepted = Object.values(PROVIDER_HOME_ENV);
|
|
1124
1135
|
if (!accepted.includes(resolved.envVar)) {
|
|
1125
1136
|
throw new LanesError("unknown_provider_home_env", `Unknown provider home variable ${JSON.stringify(resolved.envVar)}. Accepted: ${accepted.join(", ")}`, { accepted, value: resolved.envVar });
|
|
@@ -1127,6 +1138,7 @@ export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, mes
|
|
|
1127
1138
|
const env = { ...base };
|
|
1128
1139
|
delete env.HQ_PROJECT_ID;
|
|
1129
1140
|
delete env.HQ_STORY_ID;
|
|
1141
|
+
delete env.HQ_POLICY_WORKER_DIR;
|
|
1130
1142
|
env[resolved.envVar] = resolved.home;
|
|
1131
1143
|
delete env[CLAUDE_AUTO_COMPACT_ENV];
|
|
1132
1144
|
if (resolved.envVar === PROVIDER_HOME_ENV.claude && autoCompactTokens !== undefined) {
|
|
@@ -1134,6 +1146,10 @@ export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, mes
|
|
|
1134
1146
|
}
|
|
1135
1147
|
env.HQ_LANE_ID = laneId;
|
|
1136
1148
|
env.HQ_ROOT = hqRoot;
|
|
1149
|
+
if (workerPolicyDir !== undefined &&
|
|
1150
|
+
(resolved.envVar === PROVIDER_HOME_ENV.claude || resolved.envVar === PROVIDER_HOME_ENV.codex)) {
|
|
1151
|
+
env.HQ_POLICY_WORKER_DIR = workerPolicyDir;
|
|
1152
|
+
}
|
|
1137
1153
|
if (meshCompany) {
|
|
1138
1154
|
env.HQ_SPAWN_COMPANY = meshCompany.slug || meshCompany.uid;
|
|
1139
1155
|
}
|
|
@@ -1142,6 +1158,28 @@ export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, mes
|
|
|
1142
1158
|
}
|
|
1143
1159
|
return env;
|
|
1144
1160
|
}
|
|
1161
|
+
export const WORKER_POLICY_HOOK_CAPABILITY = "# hq-capability: worker-policy-dir v1";
|
|
1162
|
+
function installedWorkerPolicyHookSupportsDirectory(hqRoot) {
|
|
1163
|
+
const hookPath = path.join(hqRoot, ".claude", "hooks", "inject-policy-on-trigger.sh");
|
|
1164
|
+
try {
|
|
1165
|
+
return fs.readFileSync(hookPath, "utf8")
|
|
1166
|
+
.split(/\r?\n/)
|
|
1167
|
+
.some((line) => line === WORKER_POLICY_HOOK_CAPABILITY);
|
|
1168
|
+
}
|
|
1169
|
+
catch (err) {
|
|
1170
|
+
const code = err.code;
|
|
1171
|
+
if (code === "ENOENT")
|
|
1172
|
+
return false;
|
|
1173
|
+
process.stderr.write(`hq lanes: cannot inspect worker-policy hook capability at ${hookPath}: ${err instanceof Error ? err.message : String(err)}; keeping policy prompt delivery\n`);
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
/** Grok does not run HQ hooks; Claude and Codex use the installed capability marker. */
|
|
1178
|
+
export function workerPolicyDeliveryMode(hqRoot, provider) {
|
|
1179
|
+
return provider !== "grok" && installedWorkerPolicyHookSupportsDirectory(hqRoot)
|
|
1180
|
+
? "hook"
|
|
1181
|
+
: "prompt";
|
|
1182
|
+
}
|
|
1145
1183
|
function ensureDir(dir) {
|
|
1146
1184
|
fs.mkdirSync(dir, { recursive: true });
|
|
1147
1185
|
}
|
|
@@ -1331,6 +1369,13 @@ function writeSpawnManifest(runDir, input) {
|
|
|
1331
1369
|
worker_profile: input.profile.id,
|
|
1332
1370
|
worker_profile_hash: input.profile.hash,
|
|
1333
1371
|
worker_profile_ignored: [...input.profile.ignoredFields],
|
|
1372
|
+
worker_policy_delivery: input.profile.policyDelivery,
|
|
1373
|
+
...(input.profile.policyDir !== undefined
|
|
1374
|
+
? {
|
|
1375
|
+
worker_policy_dir: input.profile.policyDir,
|
|
1376
|
+
worker_policy_env: { HQ_POLICY_WORKER_DIR: input.profile.policyDir },
|
|
1377
|
+
}
|
|
1378
|
+
: {}),
|
|
1334
1379
|
...(input.profile.settingsPath
|
|
1335
1380
|
? { worker_profile_settings: input.profile.settingsPath }
|
|
1336
1381
|
: {}),
|
|
@@ -1513,12 +1558,18 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1513
1558
|
const ignoredProfileFields = profile
|
|
1514
1559
|
? ignoredWorkerProfileFields(existing.provider)
|
|
1515
1560
|
: [];
|
|
1561
|
+
const policyDelivery = profile
|
|
1562
|
+
? workerPolicyDeliveryMode(hqRoot, existing.provider)
|
|
1563
|
+
: undefined;
|
|
1564
|
+
const workerPolicyDir = profile !== undefined && existing.provider !== "grok"
|
|
1565
|
+
? path.relative(hqRoot, profile.dir).split(path.sep).join("/")
|
|
1566
|
+
: undefined;
|
|
1516
1567
|
const profileRuntime = profile
|
|
1517
1568
|
? materializeWorkerProfile(profile, runDir)
|
|
1518
1569
|
: undefined;
|
|
1519
1570
|
const profileLaunch = profile
|
|
1520
1571
|
? {
|
|
1521
|
-
systemPrompt: workerProfilePrompt(profile),
|
|
1572
|
+
systemPrompt: workerProfilePrompt(profile, policyDelivery ?? "prompt"),
|
|
1522
1573
|
...(profileRuntime?.settingsPath !== undefined
|
|
1523
1574
|
? { settingsPath: profileRuntime.settingsPath }
|
|
1524
1575
|
: {}),
|
|
@@ -1637,7 +1688,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1637
1688
|
const autoCompactTokens = lane.provider === "claude"
|
|
1638
1689
|
? providerAutoCompactTokens(config.providers.claude)
|
|
1639
1690
|
: undefined;
|
|
1640
|
-
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined);
|
|
1691
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir);
|
|
1641
1692
|
const model = resolveLaneModel({
|
|
1642
1693
|
cliModel: pinnedModel,
|
|
1643
1694
|
profileModel,
|
|
@@ -1652,10 +1703,8 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1652
1703
|
model,
|
|
1653
1704
|
});
|
|
1654
1705
|
const fastMode = lane.provider === "codex"
|
|
1655
|
-
? profileDefaultsApply
|
|
1656
|
-
|
|
1657
|
-
: model !== undefined &&
|
|
1658
|
-
(config.providers.codex.fast_mode_models ?? []).includes(model)
|
|
1706
|
+
? (profileDefaultsApply ? profile?.meta.fast_mode : undefined) ??
|
|
1707
|
+
(model !== undefined && (config.providers.codex.fast_mode_models ?? []).includes(model))
|
|
1659
1708
|
: false;
|
|
1660
1709
|
const resume = plan.mode === "resume"
|
|
1661
1710
|
? {
|
|
@@ -1786,7 +1835,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1786
1835
|
? {
|
|
1787
1836
|
model_source: pinnedModel !== undefined
|
|
1788
1837
|
? "cli"
|
|
1789
|
-
:
|
|
1838
|
+
: profileModel !== undefined
|
|
1790
1839
|
? "profile"
|
|
1791
1840
|
: accountCfg?.model !== undefined
|
|
1792
1841
|
? "account"
|
|
@@ -1801,8 +1850,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1801
1850
|
? {
|
|
1802
1851
|
worker_profile_hash: profile.contentHash,
|
|
1803
1852
|
worker_profile_ignored: [...ignoredProfileFields],
|
|
1853
|
+
worker_policy_delivery: policyDelivery,
|
|
1804
1854
|
}
|
|
1805
|
-
: {}),
|
|
1855
|
+
: { worker_policy_delivery: undefined }),
|
|
1806
1856
|
};
|
|
1807
1857
|
if (resume !== undefined) {
|
|
1808
1858
|
next.provider_session_id = resume.session_id;
|
|
@@ -1901,6 +1951,8 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1901
1951
|
id: profile.id,
|
|
1902
1952
|
hash: profile.contentHash,
|
|
1903
1953
|
ignoredFields: ignoredProfileFields,
|
|
1954
|
+
policyDelivery: policyDelivery ?? "prompt",
|
|
1955
|
+
...(workerPolicyDir !== undefined ? { policyDir: workerPolicyDir } : {}),
|
|
1904
1956
|
...(profileRuntime?.settingsPath !== undefined
|
|
1905
1957
|
? { settingsPath: profileRuntime.settingsPath }
|
|
1906
1958
|
: {}),
|
|
@@ -1985,12 +2037,18 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
1985
2037
|
const ignoredProfileFields = profile
|
|
1986
2038
|
? ignoredWorkerProfileFields(lane.provider)
|
|
1987
2039
|
: [];
|
|
2040
|
+
const policyDelivery = profile
|
|
2041
|
+
? workerPolicyDeliveryMode(hqRoot, lane.provider)
|
|
2042
|
+
: undefined;
|
|
2043
|
+
const workerPolicyDir = profile !== undefined && lane.provider !== "grok"
|
|
2044
|
+
? path.relative(hqRoot, profile.dir).split(path.sep).join("/")
|
|
2045
|
+
: undefined;
|
|
1988
2046
|
const profileRuntime = profile
|
|
1989
2047
|
? materializeWorkerProfile(profile, runDir)
|
|
1990
2048
|
: undefined;
|
|
1991
2049
|
const profileLaunch = profile
|
|
1992
2050
|
? {
|
|
1993
|
-
systemPrompt: workerProfilePrompt(profile),
|
|
2051
|
+
systemPrompt: workerProfilePrompt(profile, policyDelivery ?? "prompt"),
|
|
1994
2052
|
...(profileRuntime?.settingsPath !== undefined
|
|
1995
2053
|
? { settingsPath: profileRuntime.settingsPath }
|
|
1996
2054
|
: {}),
|
|
@@ -2014,7 +2072,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2014
2072
|
const autoCompactTokens = lane.provider === "claude"
|
|
2015
2073
|
? providerAutoCompactTokens(config.providers.claude)
|
|
2016
2074
|
: undefined;
|
|
2017
|
-
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined);
|
|
2075
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir);
|
|
2018
2076
|
const accountCfg = config.providers[lane.provider].accounts[resolved.id];
|
|
2019
2077
|
const model = resolveLaneModel({
|
|
2020
2078
|
cliModel: pinnedModel,
|
|
@@ -2030,15 +2088,14 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2030
2088
|
model,
|
|
2031
2089
|
});
|
|
2032
2090
|
const fastMode = lane.provider === "codex"
|
|
2033
|
-
? profileDefaultsApply
|
|
2034
|
-
|
|
2035
|
-
: model !== undefined &&
|
|
2036
|
-
(config.providers.codex.fast_mode_models ?? []).includes(model)
|
|
2091
|
+
? (profileDefaultsApply ? profile?.meta.fast_mode : undefined) ??
|
|
2092
|
+
(model !== undefined && (config.providers.codex.fast_mode_models ?? []).includes(model))
|
|
2037
2093
|
: false;
|
|
2038
2094
|
const repairPrompt = envelopeRepairPrompt({
|
|
2039
2095
|
envelopePath: input.envelopePath,
|
|
2040
2096
|
error: input.error,
|
|
2041
2097
|
rawText: input.rawText,
|
|
2098
|
+
missing: input.missing,
|
|
2042
2099
|
});
|
|
2043
2100
|
const command = buildProviderCommand(lane.provider, {
|
|
2044
2101
|
hqRoot,
|
|
@@ -2081,6 +2138,8 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2081
2138
|
id: profile.id,
|
|
2082
2139
|
hash: profile.contentHash,
|
|
2083
2140
|
ignoredFields: ignoredProfileFields,
|
|
2141
|
+
policyDelivery: policyDelivery ?? "prompt",
|
|
2142
|
+
...(workerPolicyDir !== undefined ? { policyDir: workerPolicyDir } : {}),
|
|
2084
2143
|
...(profileRuntime?.settingsPath !== undefined
|
|
2085
2144
|
? { settingsPath: profileRuntime.settingsPath }
|
|
2086
2145
|
: {}),
|
|
@@ -2102,11 +2161,12 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2102
2161
|
if (profile !== undefined) {
|
|
2103
2162
|
next.worker_profile_hash = profile.contentHash;
|
|
2104
2163
|
next.worker_profile_ignored = [...ignoredProfileFields];
|
|
2164
|
+
next.worker_policy_delivery = policyDelivery;
|
|
2105
2165
|
if (model !== undefined) {
|
|
2106
2166
|
next.model = model;
|
|
2107
2167
|
next.model_source = pinnedModel !== undefined
|
|
2108
2168
|
? "cli"
|
|
2109
|
-
:
|
|
2169
|
+
: profileModel !== undefined
|
|
2110
2170
|
? "profile"
|
|
2111
2171
|
: accountCfg?.model !== undefined
|
|
2112
2172
|
? "account"
|
|
@@ -2124,6 +2184,9 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2124
2184
|
else
|
|
2125
2185
|
delete next.effort;
|
|
2126
2186
|
}
|
|
2187
|
+
else {
|
|
2188
|
+
next.worker_policy_delivery = undefined;
|
|
2189
|
+
}
|
|
2127
2190
|
if (fastMode)
|
|
2128
2191
|
next.fast_mode = true;
|
|
2129
2192
|
else
|