@indigoai-us/hq-cli 5.218.0 → 5.219.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.
@@ -6,6 +6,20 @@
6
6
  * Requires HQ_ROOT in the environment.
7
7
  */
8
8
  import { runWatcherLoop } from "./watcher.js";
9
+ import { LanesError } from "./errors.js";
10
+ import { openMeshTransport } from "../mesh/client.js";
11
+ import { CompanySelectionError } from "../../utils/company-selection-error.js";
12
+ async function resolveQuestionActiveCompany() {
13
+ try {
14
+ const { company } = await openMeshTransport({});
15
+ return { slug: company.companySlug, uid: company.companyUid };
16
+ }
17
+ catch (err) {
18
+ if (!(err instanceof CompanySelectionError))
19
+ throw err;
20
+ throw new LanesError("question_company_unavailable", "Cannot raise a lane question because the active HQ company could not be resolved", {});
21
+ }
22
+ }
9
23
  function main() {
10
24
  const laneId = process.argv[2]?.trim();
11
25
  const hqRoot = process.env.HQ_ROOT?.trim();
@@ -13,7 +27,9 @@ function main() {
13
27
  process.stderr.write("hq lanes watcher: requires lane id argv and HQ_ROOT in the environment\n");
14
28
  process.exit(1);
15
29
  }
16
- void runWatcherLoop(hqRoot, laneId).catch((err) => {
30
+ void runWatcherLoop(hqRoot, laneId, {
31
+ resolveQuestionActiveCompany,
32
+ }).catch((err) => {
17
33
  process.stderr.write(`hq lanes watcher: ${err instanceof Error ? err.message : String(err)}${err instanceof Error && err.stack ? `\n${err.stack}` : ""}\n`);
18
34
  process.exit(1);
19
35
  });
@@ -31,7 +31,7 @@
31
31
  * Queue admission of the next lane when a slot frees is owned by the
32
32
  * periodic reconcile pass and is out of scope here.
33
33
  */
34
- import { ENVELOPE_INVALID_REASON, type LaneEnvelope, type LaneRecord, type LaneState } from "./types.js";
34
+ import { ENVELOPE_INVALID_REASON, type LaneEnvelope, type LaneCompanyRef, type LaneRecord, type LaneState } from "./types.js";
35
35
  import { type QuotaSignal } from "./quota.js";
36
36
  import { type ProviderRejection } from "./provider-rejection.js";
37
37
  import type { AdmissionHost } from "./admission.js";
@@ -145,6 +145,7 @@ export interface ApplyTerminalOpts {
145
145
  envelopeRef?: string;
146
146
  envelopeValidationErrors?: string[];
147
147
  questionIndexes?: readonly number[];
148
+ activeCompany?: LaneCompanyRef | null;
148
149
  at?: string;
149
150
  quota?: QuotaSignal | null;
150
151
  /** Written to `terminal_details` with the model and account the run used. */
@@ -219,6 +220,8 @@ export interface WatcherLoopDeps {
219
220
  resolveAdmissionHost?: () => AdmissionHost;
220
221
  /** Spawn seams for account retry; host scope is supplied by the resolver above. */
221
222
  retrySpawnDeps?: Omit<SpawnLaneDeps, "admissionHost">;
223
+ /** Active-company fallback when an unscoped lane asks from its envelope. */
224
+ resolveQuestionActiveCompany?: () => Promise<LaneCompanyRef>;
222
225
  }
223
226
  /**
224
227
  * Wrap one mesh call. A failed emit is logged as `mesh_event_failed` with
@@ -34,7 +34,7 @@
34
34
  import * as fs from "node:fs";
35
35
  import * as path from "node:path";
36
36
  import { LanesError } from "./errors.js";
37
- import { raiseEnvelopeQuestions } from "./questions.js";
37
+ import { questionRecordRefusalCode, raiseEnvelopeQuestions } 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
40
  import { readLane, retryUpdateLane } from "./store.js";
@@ -309,12 +309,27 @@ export function applyTerminalToLane(hqRoot, laneId, classification, opts = {}) {
309
309
  export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, opts = {}) {
310
310
  const at = opts.at ?? new Date().toISOString();
311
311
  const existing = readLane(hqRoot, laneId);
312
+ let questionRefusalCode = null;
312
313
  if (!isTerminalLaneState(existing.state) &&
313
314
  classification.state === "awaiting_input" &&
314
315
  opts.envelope &&
315
316
  (opts.envelope.questions?.length ?? 0) > 0) {
316
- raiseEnvelopeQuestions(hqRoot, existing, opts.envelope, opts.envelopeRef ?? "envelopes/unknown.json", at, opts.questionIndexes);
317
+ try {
318
+ raiseEnvelopeQuestions(hqRoot, existing, opts.envelope, opts.envelopeRef ?? "envelopes/unknown.json", at, opts.questionIndexes, opts.activeCompany);
319
+ }
320
+ catch (err) {
321
+ questionRefusalCode = questionRecordRefusalCode(err);
322
+ if (questionRefusalCode === null)
323
+ throw err;
324
+ process.stderr.write(`hq lanes watcher: terminal ask refused for ${laneId}: ${questionRefusalCode}\n`);
325
+ }
317
326
  }
327
+ const terminalClassification = questionRefusalCode === null
328
+ ? classification
329
+ : {
330
+ state: "failed",
331
+ reason: `question_record_refused:${questionRefusalCode}`,
332
+ };
318
333
  let changed = false;
319
334
  const updated = retryUpdateLane(hqRoot, laneId, "watcher", (current) => {
320
335
  const terminal = current.state === "done" ||
@@ -331,7 +346,7 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
331
346
  const next = {
332
347
  ...current,
333
348
  ...runResultFields(hqRoot, current),
334
- state: classification.state,
349
+ state: terminalClassification.state,
335
350
  queued_reason: undefined,
336
351
  stalled: undefined,
337
352
  stalled_at: undefined,
@@ -340,9 +355,9 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
340
355
  envelope_validation_errors: opts.envelopeValidationErrors,
341
356
  };
342
357
  changed = true;
343
- if (classification.reason) {
358
+ if (terminalClassification.reason) {
344
359
  next.terminal_reason =
345
- classification.reason;
360
+ terminalClassification.reason;
346
361
  }
347
362
  if (opts.quota?.quota_reset_at) {
348
363
  next.quota_reset_at = opts.quota.quota_reset_at;
@@ -1054,11 +1069,19 @@ export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
1054
1069
  return;
1055
1070
  }
1056
1071
  }
1072
+ const terminalLane = readLane(hqRoot, laneId);
1073
+ const activeQuestionCompany = envelope?.decision === "ask" &&
1074
+ (envelope.questions?.length ?? 0) > 0 &&
1075
+ terminalLane.company === null &&
1076
+ terminalLane.mesh_company === undefined
1077
+ ? await deps.resolveQuestionActiveCompany?.()
1078
+ : undefined;
1057
1079
  const terminalWrite = applyTerminalToLaneWithResult(hqRoot, laneId, classification, {
1058
1080
  envelope,
1059
1081
  envelopeRef: latest?.ref,
1060
1082
  envelopeValidationErrors,
1061
1083
  questionIndexes,
1084
+ activeCompany: activeQuestionCompany,
1062
1085
  at: tick.emit.at,
1063
1086
  quota,
1064
1087
  providerRejection,
@@ -1074,7 +1097,7 @@ export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
1074
1097
  // lane_blocked; ask → lane_ask with the queue ids. After the state
1075
1098
  // write, so a mesh failure can never leave the lane unclassified.
1076
1099
  if (mesh && terminalWrite.changed) {
1077
- if (envelope && latest) {
1100
+ if (envelope && latest && updated.state === classification.state) {
1078
1101
  const questionIds = envelope.decision === "ask"
1079
1102
  ? envelopeQuestionIds(laneId, latest.ref, envelope, questionIndexes)
1080
1103
  : undefined;
@@ -1086,7 +1109,7 @@ export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
1086
1109
  }
1087
1110
  else if (updated.state === "failed" || updated.state === "interrupted" ||
1088
1111
  updated.state === "cancelled" || updated.state === "killed") {
1089
- await emitMeshSoft(`lane_${updated.state}`, logDecision, tick.emit.at, (signal) => emitLaneTerminalStateEvent(mesh, updated, updated.state, classification.reason, signal), meshRequestTimeoutMs);
1112
+ await emitMeshSoft(`lane_${updated.state}`, logDecision, tick.emit.at, (signal) => emitLaneTerminalStateEvent(mesh, updated, updated.state, updated.terminal_reason ?? classification.reason, signal), meshRequestTimeoutMs);
1090
1113
  }
1091
1114
  }
1092
1115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.218.0",
3
+ "version": "5.219.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {