@indigoai-us/hq-cli 5.239.0 → 5.241.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 +36 -0
- package/dist/command-catalog.generated.d.ts +41 -0
- package/dist/command-catalog.generated.js +54 -0
- package/dist/commands/agent-enroll.d.ts +8 -0
- package/dist/commands/agent-enroll.js +19 -2
- package/dist/commands/lanes.js +194 -17
- package/dist/lib/agent-kit/creds.d.ts +30 -2
- package/dist/lib/agent-kit/creds.js +75 -6
- package/dist/lib/lanes/approval-gate.d.ts +8 -0
- package/dist/lib/lanes/approval-gate.js +90 -0
- package/dist/lib/lanes/claude-balance.d.ts +2 -0
- package/dist/lib/lanes/claude-balance.js +2 -1
- package/dist/lib/lanes/errors.d.ts +1 -1
- package/dist/lib/lanes/index.d.ts +3 -1
- package/dist/lib/lanes/index.js +2 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.d.ts +18 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.js +109 -0
- package/dist/lib/lanes/profile.d.ts +10 -0
- package/dist/lib/lanes/profile.js +88 -0
- package/dist/lib/lanes/reconcile.js +38 -11
- package/dist/lib/lanes/registry-persona.d.ts +20 -0
- package/dist/lib/lanes/registry-persona.js +329 -0
- package/dist/lib/lanes/registry.d.ts +4 -1
- package/dist/lib/lanes/registry.js +8 -3
- package/dist/lib/lanes/spawn.d.ts +1 -1
- package/dist/lib/lanes/spawn.js +28 -7
- package/dist/lib/lanes/store.js +90 -0
- package/dist/lib/lanes/types.d.ts +29 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.d.ts +2 -0
- package/dist/lib/lanes/watcher.js +46 -15
- package/package.json +1 -1
package/dist/lib/lanes/store.js
CHANGED
|
@@ -543,6 +543,91 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
543
543
|
raw.worker_policy_delivery !== "prompt") {
|
|
544
544
|
throw new LaneSchemaError(`Unknown worker policy delivery mode ${JSON.stringify(raw.worker_policy_delivery)}`, { path: filePath, field: "worker_policy_delivery", value: raw.worker_policy_delivery });
|
|
545
545
|
}
|
|
546
|
+
if (raw.registry_worker !== undefined) {
|
|
547
|
+
const worker = raw.registry_worker;
|
|
548
|
+
if (!isPlainObject(worker) || Object.keys(worker).some((key) => !["id", "path", "worker_yaml_sha256", "approval_required", "human_checkpoints"].includes(key))) {
|
|
549
|
+
throw new LaneSchemaError("registry_worker must be a pinned registry worker record", {
|
|
550
|
+
path: filePath,
|
|
551
|
+
field: "registry_worker",
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
for (const key of ["id", "path"]) {
|
|
555
|
+
if (typeof worker[key] !== "string" || worker[key].trim().length === 0) {
|
|
556
|
+
throw new LaneSchemaError(`registry_worker.${key} must be a non-empty string`, {
|
|
557
|
+
path: filePath,
|
|
558
|
+
field: `registry_worker.${key}`,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const registryPath = worker.path.replaceAll("\\", "/");
|
|
563
|
+
if (path.posix.isAbsolute(registryPath) || registryPath.split("/").some((part) => part === ".." || part === ".")) {
|
|
564
|
+
throw new LaneSchemaError("registry_worker.path must stay within HQ_ROOT", {
|
|
565
|
+
path: filePath,
|
|
566
|
+
field: "registry_worker.path",
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (typeof worker.worker_yaml_sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(worker.worker_yaml_sha256)) {
|
|
570
|
+
throw new LaneSchemaError("registry_worker.worker_yaml_sha256 must be a SHA-256 hash", {
|
|
571
|
+
path: filePath,
|
|
572
|
+
field: "registry_worker.worker_yaml_sha256",
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (typeof worker.approval_required !== "boolean") {
|
|
576
|
+
throw new LaneSchemaError("registry_worker.approval_required must be a boolean", {
|
|
577
|
+
path: filePath,
|
|
578
|
+
field: "registry_worker.approval_required",
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (!Array.isArray(worker.human_checkpoints) || worker.human_checkpoints.some((checkpoint) => typeof checkpoint !== "string" || checkpoint.trim().length === 0) ||
|
|
582
|
+
new Set(worker.human_checkpoints).size !== worker.human_checkpoints.length) {
|
|
583
|
+
throw new LaneSchemaError("registry_worker.human_checkpoints must be unique non-empty strings", {
|
|
584
|
+
path: filePath,
|
|
585
|
+
field: "registry_worker.human_checkpoints",
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (raw.registry_worker_approval_history !== undefined) {
|
|
590
|
+
if (!Array.isArray(raw.registry_worker_approval_history)) {
|
|
591
|
+
throw new LaneSchemaError("registry_worker_approval_history must be an array when present", {
|
|
592
|
+
path: filePath,
|
|
593
|
+
field: "registry_worker_approval_history",
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
for (const [index, entry] of raw.registry_worker_approval_history.entries()) {
|
|
597
|
+
const field = `registry_worker_approval_history[${index}]`;
|
|
598
|
+
if (!isPlainObject(entry)) {
|
|
599
|
+
throw new LaneSchemaError(`${field} must be an approval audit event`, { path: filePath, field });
|
|
600
|
+
}
|
|
601
|
+
const expected = entry.event === "approved"
|
|
602
|
+
? ["event", "approval_id", "checkpoint", "registry_worker_id", "worker_yaml_sha256", "reason", "actor", "at"]
|
|
603
|
+
: entry.event === "consumed"
|
|
604
|
+
? ["event", "approval_id", "envelope_ref", "at"]
|
|
605
|
+
: [];
|
|
606
|
+
if (expected.length === 0 || Object.keys(entry).some((key) => !expected.includes(key))) {
|
|
607
|
+
throw new LaneSchemaError(`${field} has an unknown event or field`, { path: filePath, field });
|
|
608
|
+
}
|
|
609
|
+
for (const key of ["approval_id", ...(entry.event === "approved" ? ["checkpoint", "reason", "actor"] : ["envelope_ref"])]) {
|
|
610
|
+
if (typeof entry[key] !== "string" || entry[key].trim().length === 0) {
|
|
611
|
+
throw new LaneSchemaError(`${field}.${key} must be a non-empty string`, { path: filePath, field });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (entry.event === "approved" && entry.worker_yaml_sha256 !== undefined &&
|
|
615
|
+
(typeof entry.worker_yaml_sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(entry.worker_yaml_sha256))) {
|
|
616
|
+
throw new LaneSchemaError(`${field}.worker_yaml_sha256 must be a SHA-256 hash`, {
|
|
617
|
+
path: filePath,
|
|
618
|
+
field: `${field}.worker_yaml_sha256`,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
if (entry.event === "approved" && entry.registry_worker_id !== undefined &&
|
|
622
|
+
(typeof entry.registry_worker_id !== "string" || entry.registry_worker_id.trim().length === 0)) {
|
|
623
|
+
throw new LaneSchemaError(`${field}.registry_worker_id must be a non-empty string`, {
|
|
624
|
+
path: filePath,
|
|
625
|
+
field: `${field}.registry_worker_id`,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
requireTimestamp(entry.at, `${field}.at`, filePath);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
546
631
|
parseLaneStories(raw, filePath);
|
|
547
632
|
if (raw.last_envelope !== undefined) {
|
|
548
633
|
if (!isPlainObject(raw.last_envelope) || typeof raw.last_envelope.ref !== "string" ||
|
|
@@ -920,6 +1005,11 @@ export function mergeLaneRecords(live, sidecars) {
|
|
|
920
1005
|
worker_profile_repin_history: unionHistory(all.map((record) => record.worker_profile_repin_history ?? []), entryIdentity, base.worker_profile_repin_history ?? []),
|
|
921
1006
|
}
|
|
922
1007
|
: {}),
|
|
1008
|
+
...(all.some((record) => record.registry_worker_approval_history !== undefined)
|
|
1009
|
+
? {
|
|
1010
|
+
registry_worker_approval_history: unionHistory(all.map((record) => record.registry_worker_approval_history ?? []), entryIdentity, base.registry_worker_approval_history ?? []),
|
|
1011
|
+
}
|
|
1012
|
+
: {}),
|
|
923
1013
|
...(all.some((record) => record.failed_resume_history !== undefined)
|
|
924
1014
|
? {
|
|
925
1015
|
failed_resume_history: unionHistory(all.map((record) => record.failed_resume_history ?? []), entryIdentity, base.failed_resume_history ?? []),
|
|
@@ -295,6 +295,31 @@ export interface WorkerProfileRepinHistoryEntry {
|
|
|
295
295
|
at: string;
|
|
296
296
|
reason: string;
|
|
297
297
|
}
|
|
298
|
+
/** The registry worker identity and approval contract pinned at lane creation. */
|
|
299
|
+
export interface RegistryWorkerRecord {
|
|
300
|
+
id: string;
|
|
301
|
+
path: string;
|
|
302
|
+
worker_yaml_sha256: string;
|
|
303
|
+
approval_required: boolean;
|
|
304
|
+
human_checkpoints: string[];
|
|
305
|
+
}
|
|
306
|
+
/** Append-only approval and consumption events for registry worker checkpoints. */
|
|
307
|
+
export type RegistryWorkerApprovalHistoryEntry = {
|
|
308
|
+
event: "approved";
|
|
309
|
+
approval_id: string;
|
|
310
|
+
checkpoint: string;
|
|
311
|
+
/** Pins this approval to the registry worker version that received it. */
|
|
312
|
+
registry_worker_id?: string;
|
|
313
|
+
worker_yaml_sha256?: string;
|
|
314
|
+
reason: string;
|
|
315
|
+
actor: string;
|
|
316
|
+
at: string;
|
|
317
|
+
} | {
|
|
318
|
+
event: "consumed";
|
|
319
|
+
approval_id: string;
|
|
320
|
+
envelope_ref: string;
|
|
321
|
+
at: string;
|
|
322
|
+
};
|
|
298
323
|
export interface AttributionHistoryEntry {
|
|
299
324
|
project_id: string;
|
|
300
325
|
story_id: string;
|
|
@@ -390,6 +415,10 @@ export interface LaneRecord {
|
|
|
390
415
|
hire_grant?: number;
|
|
391
416
|
/** Content hash of the profile last applied to this lane. */
|
|
392
417
|
worker_profile_hash?: string;
|
|
418
|
+
/** Registry persona selected by the profile, pinned at lane creation. */
|
|
419
|
+
registry_worker?: RegistryWorkerRecord;
|
|
420
|
+
/** Append-only approvals and one-envelope consumption records. */
|
|
421
|
+
registry_worker_approval_history?: RegistryWorkerApprovalHistoryEntry[];
|
|
393
422
|
/** Bounded audit history of authorized Outpost profile hash re-pins. */
|
|
394
423
|
worker_profile_repin_history?: WorkerProfileRepinHistoryEntry[];
|
|
395
424
|
/** Profile fields intentionally unsupported by the selected provider. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -223,7 +223,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
223
223
|
state: ["creator", "command", "watcher", "reconcile", "spawn"], queued_reason: ["creator", "command", "spawn", "watcher", "reconcile"], queued_at: ["creator", "command", "spawn", "watcher", "reconcile"], admission_reserved_at: ["creator", "command", "reconcile", "spawn", "watcher"],
|
|
224
224
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
225
225
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
226
|
-
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
226
|
+
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
227
227
|
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
228
228
|
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
229
229
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
|
|
@@ -164,6 +164,8 @@ export declare function applyTerminalToLane(hqRoot: string, laneId: string, clas
|
|
|
164
164
|
export declare function applyTerminalToLaneWithResult(hqRoot: string, laneId: string, classification: TerminalClassification, opts?: ApplyTerminalOpts): {
|
|
165
165
|
record: LaneRecord;
|
|
166
166
|
changed: boolean;
|
|
167
|
+
envelope?: LaneEnvelope;
|
|
168
|
+
classification: TerminalClassification;
|
|
167
169
|
};
|
|
168
170
|
export declare function isPidAlive(pid: number): boolean;
|
|
169
171
|
export interface LatestEnvelope {
|
|
@@ -37,7 +37,8 @@ import { LanesError } from "./errors.js";
|
|
|
37
37
|
import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
38
38
|
import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, } from "./types.js";
|
|
39
39
|
import { laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
40
|
-
import { readLane, retryUpdateLane, withLaneHireLocks } from "./store.js";
|
|
40
|
+
import { appendCappedHistory, readLane, retryUpdateLane, withLaneHireLocks } from "./store.js";
|
|
41
|
+
import { enforceRegistryApprovalGate } from "./approval-gate.js";
|
|
41
42
|
import { readWorkerRunResult } from "./delivery.js";
|
|
42
43
|
import { applyDefaultQuotaHold, quotaSignalFromFields, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
43
44
|
import { PROVIDER_REJECTED_REASON, QUOTA_STATUS, isProviderLoginFailure, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
@@ -311,6 +312,7 @@ export function applyTerminalToLane(hqRoot, laneId, classification, opts = {}) {
|
|
|
311
312
|
/** Terminal write outcome for emitters: only the CAS winner may publish mesh effects. */
|
|
312
313
|
export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, opts = {}) {
|
|
313
314
|
const at = opts.at ?? new Date().toISOString();
|
|
315
|
+
const envelopeRef = opts.envelopeRef ?? "envelopes/unknown.json";
|
|
314
316
|
const existing = readLane(hqRoot, laneId);
|
|
315
317
|
let questionRefusalCode = null;
|
|
316
318
|
if (!isTerminalLaneState(existing.state) &&
|
|
@@ -334,7 +336,16 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
334
336
|
reason: `question_record_refused:${questionRefusalCode}`,
|
|
335
337
|
};
|
|
336
338
|
let changed = false;
|
|
339
|
+
let effectiveEnvelope = opts.envelope ?? undefined;
|
|
340
|
+
let effectiveClassification = terminalClassification;
|
|
337
341
|
const updated = retryUpdateLane(hqRoot, laneId, "watcher", (current) => {
|
|
342
|
+
const approvalGate = opts.envelope !== undefined && opts.envelope !== null
|
|
343
|
+
? enforceRegistryApprovalGate(current, opts.envelope)
|
|
344
|
+
: undefined;
|
|
345
|
+
effectiveEnvelope = approvalGate?.envelope ?? opts.envelope ?? undefined;
|
|
346
|
+
effectiveClassification = approvalGate?.violation
|
|
347
|
+
? { state: "blocked", reason: "approval_gate_violated" }
|
|
348
|
+
: terminalClassification;
|
|
338
349
|
const terminal = current.state === "done" ||
|
|
339
350
|
current.state === "failed" ||
|
|
340
351
|
current.state === "interrupted" ||
|
|
@@ -343,24 +354,24 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
343
354
|
if (terminal)
|
|
344
355
|
return current;
|
|
345
356
|
if (current.state === "awaiting_input" || current.state === "blocked") {
|
|
346
|
-
if (current.state ===
|
|
357
|
+
if (current.state === effectiveClassification.state)
|
|
347
358
|
return current;
|
|
348
359
|
}
|
|
349
360
|
const next = {
|
|
350
361
|
...current,
|
|
351
362
|
...runResultFields(hqRoot, current),
|
|
352
|
-
state:
|
|
363
|
+
state: effectiveClassification.state,
|
|
353
364
|
queued_reason: undefined,
|
|
354
365
|
stalled: undefined,
|
|
355
366
|
stalled_at: undefined,
|
|
356
367
|
stalled_reason: undefined,
|
|
357
|
-
interrupted_at:
|
|
368
|
+
interrupted_at: effectiveClassification.state === "interrupted" ? at : undefined,
|
|
358
369
|
envelope_validation_errors: opts.envelopeValidationErrors,
|
|
359
370
|
};
|
|
360
371
|
changed = true;
|
|
361
|
-
if (
|
|
372
|
+
if (effectiveClassification.reason) {
|
|
362
373
|
next.terminal_reason =
|
|
363
|
-
|
|
374
|
+
effectiveClassification.reason;
|
|
364
375
|
}
|
|
365
376
|
if (opts.quota?.quota_reset_at) {
|
|
366
377
|
next.quota_reset_at = opts.quota.quota_reset_at;
|
|
@@ -371,10 +382,18 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
371
382
|
if (opts.providerRejection) {
|
|
372
383
|
next.terminal_details = providerRejectionDetails(opts.providerRejection, current);
|
|
373
384
|
}
|
|
374
|
-
const envelope =
|
|
375
|
-
if (
|
|
385
|
+
const envelope = effectiveEnvelope;
|
|
386
|
+
if (approvalGate && !approvalGate.violation && approvalGate.approvedIdsToConsume.length > 0) {
|
|
387
|
+
next.registry_worker_approval_history = approvalGate.approvedIdsToConsume.reduce((history, approvalId) => appendCappedHistory(history, {
|
|
388
|
+
event: "consumed",
|
|
389
|
+
approval_id: approvalId,
|
|
390
|
+
envelope_ref: envelopeRef,
|
|
391
|
+
at,
|
|
392
|
+
}), current.registry_worker_approval_history ?? []);
|
|
393
|
+
}
|
|
394
|
+
if (envelope) {
|
|
376
395
|
next.last_envelope = {
|
|
377
|
-
ref:
|
|
396
|
+
ref: envelopeRef,
|
|
378
397
|
decision: envelope.decision,
|
|
379
398
|
at,
|
|
380
399
|
artifacts: envelope.artifacts,
|
|
@@ -411,7 +430,12 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
411
430
|
provider: updated.provider,
|
|
412
431
|
account: updated.account,
|
|
413
432
|
});
|
|
414
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
record: updated,
|
|
435
|
+
changed,
|
|
436
|
+
...(effectiveEnvelope !== undefined ? { envelope: effectiveEnvelope } : {}),
|
|
437
|
+
classification: effectiveClassification,
|
|
438
|
+
};
|
|
415
439
|
}
|
|
416
440
|
export function isPidAlive(pid) {
|
|
417
441
|
try {
|
|
@@ -1145,6 +1169,8 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1145
1169
|
providerRejection,
|
|
1146
1170
|
});
|
|
1147
1171
|
const updated = terminalWrite.record;
|
|
1172
|
+
const effectiveEnvelope = terminalWrite.envelope ?? envelope;
|
|
1173
|
+
const effectiveClassification = terminalWrite.classification;
|
|
1148
1174
|
logDecision({
|
|
1149
1175
|
event: "terminal_write",
|
|
1150
1176
|
at: tick.emit.at,
|
|
@@ -1155,11 +1181,16 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1155
1181
|
// lane_blocked; ask → lane_ask with the queue ids. After the state
|
|
1156
1182
|
// write, so a mesh failure can never leave the lane unclassified.
|
|
1157
1183
|
if (mesh && terminalWrite.changed) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1184
|
+
const envelopeState = effectiveEnvelope?.decision === "ask"
|
|
1185
|
+
? "awaiting_input"
|
|
1186
|
+
: effectiveEnvelope?.decision === "blocked"
|
|
1187
|
+
? "blocked"
|
|
1188
|
+
: "done";
|
|
1189
|
+
if (effectiveEnvelope && latest && updated.state === envelopeState) {
|
|
1190
|
+
const questionIds = effectiveEnvelope.decision === "ask"
|
|
1191
|
+
? envelopeQuestionIds(laneId, latest.ref, effectiveEnvelope, questionIndexes)
|
|
1161
1192
|
: undefined;
|
|
1162
|
-
await emitMeshSoft(`lane_${
|
|
1193
|
+
await emitMeshSoft(`lane_${effectiveEnvelope.decision}`, logDecision, tick.emit.at, (signal) => emitLaneDecisionEvents(mesh, updated, effectiveEnvelope, {
|
|
1163
1194
|
questionIds,
|
|
1164
1195
|
signal,
|
|
1165
1196
|
storyStatusSuppressions,
|
|
@@ -1167,7 +1198,7 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1167
1198
|
}
|
|
1168
1199
|
else if (updated.state === "failed" || updated.state === "interrupted" ||
|
|
1169
1200
|
updated.state === "cancelled" || updated.state === "killed") {
|
|
1170
|
-
await emitMeshSoft(`lane_${updated.state}`, logDecision, tick.emit.at, (signal) => emitLaneTerminalStateEvent(mesh, updated, updated.state, updated.terminal_reason ??
|
|
1201
|
+
await emitMeshSoft(`lane_${updated.state}`, logDecision, tick.emit.at, (signal) => emitLaneTerminalStateEvent(mesh, updated, updated.state, updated.terminal_reason ?? effectiveClassification.reason, signal), meshRequestTimeoutMs);
|
|
1171
1202
|
}
|
|
1172
1203
|
}
|
|
1173
1204
|
if (terminalWrite.changed && deps.recoverQueuedMessages) {
|