@indigoai-us/hq-cli 5.108.21 → 5.108.22

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 CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.108.22] — 2026-09-07
6
+
7
+ ### Fixed
8
+
9
+ - Work Mesh registrations now retain the runtime harness in the durable outbox
10
+ and send it to the server, including a compatible fallback for older queued
11
+ operations. Fleet sessions discover their company from the standard agent
12
+ identity file without requiring a launcher environment variable, and transcript
13
+ registration uses the shared company precedence and conflict rules.
14
+
5
15
  ## [5.108.21] — 2026-09-07
6
16
 
7
17
  ### Fixed
@@ -300,7 +300,7 @@ export function checkRuntimeProbe(context) {
300
300
  status: "PASS",
301
301
  checkId,
302
302
  target,
303
- message: `Host platform is agents-v2 (hermes fleet): the on-box adapter wrote the ` +
303
+ message: `Host platform is the Agents v2 runtime: the on-box adapter wrote the ` +
304
304
  `policy-trigger ledger through the same .claude hooks, so hook dispatch was ` +
305
305
  `observed this session — the ledger has an entry${scope}.`,
306
306
  },
@@ -24,6 +24,8 @@ export function createWorkSessionDeliverer(opts) {
24
24
  contractVersion: 1,
25
25
  companyUid,
26
26
  sessionId: op.sessionId,
27
+ // Pre-upgrade outbox records have no harness; do not invent a provider.
28
+ harness: op.harness?.trim() || "unknown",
27
29
  clientOperationId: op.clientOperationId,
28
30
  operationId: op.operationId,
29
31
  digest: op.digest,
@@ -219,10 +219,11 @@ export declare function isTranscriptHookCovered(sessionId: string, opts: {
219
219
  }): boolean;
220
220
  /**
221
221
  * Resolve local registration for a transcript session. Never asks, never
222
- * creates a project. Uses deterministic cwd/remote mapping only.
222
+ * creates a project. Shares reconcile's company precedence and conflict rules.
223
223
  */
224
224
  export declare function resolveTranscriptRegistration(input: {
225
225
  sessionId: string;
226
+ env?: NodeJS.ProcessEnv;
226
227
  cwd?: string;
227
228
  hqRoot?: string;
228
229
  workContextRoot: string;
@@ -29,8 +29,7 @@ import * as fs from "node:fs";
29
29
  import * as os from "node:os";
30
30
  import * as path from "node:path";
31
31
  import { CLI_VERSION } from "../../../../cli-version.js";
32
- import { companySlugFromCwd, projectIdFromCwd, resolveDeterministicCompany, } from "../../../work-context/company.js";
33
- import { getDefaultCompany } from "../../../work-context/config.js";
32
+ import { companySlugFromCwd, projectIdFromCwd, resolveCompany, } from "../../../work-context/company.js";
34
33
  import { WORK_CONTEXT_CONTRACT_VERSION } from "../../../work-context/contract.js";
35
34
  import { reconcileObservation, } from "../../../work-context/reconcile.js";
36
35
  import { deriveRemoteOwnerSlug } from "../../../work-context/repo-remote.js";
@@ -441,7 +440,7 @@ export function isTranscriptHookCovered(sessionId, opts) {
441
440
  }
442
441
  /**
443
442
  * Resolve local registration for a transcript session. Never asks, never
444
- * creates a project. Uses deterministic cwd/remote mapping only.
443
+ * creates a project. Shares reconcile's company precedence and conflict rules.
445
444
  */
446
445
  export function resolveTranscriptRegistration(input) {
447
446
  const now = input.now ?? (() => new Date());
@@ -449,33 +448,26 @@ export function resolveTranscriptRegistration(input) {
449
448
  const remoteOwnerSlug = input.cwd
450
449
  ? deriveRemoteOwnerSlug({ cwd: input.cwd, hqRoot: input.hqRoot })
451
450
  : null;
452
- const deterministic = resolveDeterministicCompany({
451
+ const resolution = resolveCompany({
452
+ root: input.workContextRoot,
453
+ sessionId: input.sessionId,
454
+ env: input.env,
453
455
  cwd: input.cwd,
454
456
  hqRoot: input.hqRoot,
455
457
  remoteOwnerSlug,
456
458
  });
457
- const projectId = projectIdFromCwd(input.cwd, input.hqRoot);
458
- // Prefer company slug from deterministic evidence; optionally adopt uid when
459
- // the enabled device default names the same slug (no conflict ask).
460
- const companySlug = deterministic?.slug ?? companySlugFromCwd(input.cwd, input.hqRoot);
461
- let companyUid;
462
- const device = getDefaultCompany({ root: input.workContextRoot });
463
- if (companySlug &&
464
- device?.enabled &&
465
- device.slug &&
466
- device.slug.toLowerCase() === companySlug.toLowerCase()) {
467
- companyUid = device.uid;
468
- }
469
- let contextStatus;
470
- if (!companySlug) {
471
- contextStatus = "needs_company";
472
- }
473
- else if (projectId) {
474
- contextStatus = "needs_task";
475
- }
476
- else {
477
- contextStatus = "unresolved";
478
- }
459
+ const company = resolution.status === "resolved" ? resolution.company : undefined;
460
+ const companySlug = company?.slug;
461
+ const companyUid = company?.uid;
462
+ // A cwd project belongs to its cwd company, never to a higher-precedence
463
+ // identity or explicit scope naming another (or not-yet-mapped) company.
464
+ const cwdCompany = companySlugFromCwd(input.cwd, input.hqRoot);
465
+ const projectId = companySlug && cwdCompany?.toLowerCase() === companySlug.toLowerCase()
466
+ ? projectIdFromCwd(input.cwd, input.hqRoot)
467
+ : undefined;
468
+ const contextStatus = resolution.status === "company_conflict"
469
+ ? "company_conflict"
470
+ : !company ? "needs_company" : projectId ? "needs_task" : "unresolved";
479
471
  const state = {
480
472
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
481
473
  sessionId: input.sessionId,
@@ -58,9 +58,11 @@ export interface CompanyResolveInput {
58
58
  }
59
59
  /** Env naming the on-box identity.json (fleet agent boxes). */
60
60
  export declare const HQ_AGENT_IDENTITY_FILE_ENV = "HQ_AGENT_IDENTITY_FILE";
61
+ export declare const DEFAULT_AGENT_IDENTITY_FILE = "/var/lib/hq-agent/identity.json";
61
62
  /**
62
63
  * Read `companyUid` from the agent identity file when
63
- * `HQ_AGENT_IDENTITY_FILE` is set. Missing/unreadable/malformed undefined
64
+ * `HQ_AGENT_IDENTITY_FILE` is set, or the conventional fleet path otherwise.
65
+ * Missing/unreadable/malformed → undefined
64
66
  * (never throws; never logs file contents).
65
67
  */
66
68
  export declare function readAgentIdentityCompanyUid(env?: NodeJS.ProcessEnv): string | undefined;
@@ -26,6 +26,7 @@ export function companyCorrectionPath(sessionId) {
26
26
  const SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
27
27
  /** Env naming the on-box identity.json (fleet agent boxes). */
28
28
  export const HQ_AGENT_IDENTITY_FILE_ENV = "HQ_AGENT_IDENTITY_FILE";
29
+ export const DEFAULT_AGENT_IDENTITY_FILE = "/var/lib/hq-agent/identity.json";
29
30
  function normalizeSlug(value) {
30
31
  if (!value)
31
32
  return undefined;
@@ -36,11 +37,14 @@ function normalizeSlug(value) {
36
37
  }
37
38
  /**
38
39
  * Read `companyUid` from the agent identity file when
39
- * `HQ_AGENT_IDENTITY_FILE` is set. Missing/unreadable/malformed undefined
40
+ * `HQ_AGENT_IDENTITY_FILE` is set, or the conventional fleet path otherwise.
41
+ * Missing/unreadable/malformed → undefined
40
42
  * (never throws; never logs file contents).
41
43
  */
42
44
  export function readAgentIdentityCompanyUid(env = process.env) {
43
- const file = env[HQ_AGENT_IDENTITY_FILE_ENV]?.trim();
45
+ // An explicit override (including empty/missing paths) never falls back to
46
+ // another identity. This prevents accidental attribution to a different box.
47
+ const file = (env[HQ_AGENT_IDENTITY_FILE_ENV] ?? DEFAULT_AGENT_IDENTITY_FILE).trim();
44
48
  if (!file)
45
49
  return undefined;
46
50
  try {
@@ -5,7 +5,7 @@
5
5
  import type { DeliveryState } from "./contract.js";
6
6
  import { WORK_CONTEXT_CONTRACT_VERSION } from "./contract.js";
7
7
  /** Fields allowed on a durable outbox operation (privacy allowlist). */
8
- export declare const OUTBOX_ALLOWLIST: readonly ["contractVersion", "operationId", "clientOperationId", "sessionId", "companyUid", "companySlug", "projectId", "taskId", "kind", "digest", "delivery", "createdAt", "updatedAt", "attemptCount", "lastErrorCode", "receiptId", "destinationCompanyUid", "destinationCompanySlug", "nextAttemptAt"];
8
+ export declare const OUTBOX_ALLOWLIST: readonly ["contractVersion", "operationId", "clientOperationId", "sessionId", "harness", "companyUid", "companySlug", "projectId", "taskId", "kind", "digest", "delivery", "createdAt", "updatedAt", "attemptCount", "lastErrorCode", "receiptId", "destinationCompanyUid", "destinationCompanySlug", "nextAttemptAt"];
9
9
  /** Base delay for outbox retry backoff (attempt 1 → 30s). */
10
10
  export declare const OUTBOX_RETRY_BASE_MS = 30000;
11
11
  /** Cap for outbox retry backoff (6 hours). */
@@ -20,6 +20,7 @@ export interface OutboxOperation {
20
20
  operationId: string;
21
21
  clientOperationId: string;
22
22
  sessionId: string;
23
+ harness?: string;
23
24
  companyUid?: string;
24
25
  companySlug?: string;
25
26
  projectId?: string;
@@ -41,6 +42,7 @@ export interface OutboxOperation {
41
42
  export interface OutboxEnqueueInput {
42
43
  clientOperationId: string;
43
44
  sessionId: string;
45
+ harness?: string;
44
46
  companyUid?: string;
45
47
  companySlug?: string;
46
48
  projectId?: string;
@@ -54,6 +56,7 @@ export declare function clearOutboxListCache(): void;
54
56
  export declare function stableOperationId(clientOperationId: string, sessionId: string): string;
55
57
  export declare function digestOperation(parts: {
56
58
  sessionId: string;
59
+ harness?: string;
57
60
  clientOperationId: string;
58
61
  companyUid?: string;
59
62
  companySlug?: string;
@@ -15,6 +15,7 @@ export const OUTBOX_ALLOWLIST = [
15
15
  "operationId",
16
16
  "clientOperationId",
17
17
  "sessionId",
18
+ "harness",
18
19
  "companyUid",
19
20
  "companySlug",
20
21
  "projectId",
@@ -59,6 +60,7 @@ export function digestOperation(parts) {
59
60
  const canonical = JSON.stringify({
60
61
  v: 1,
61
62
  sessionId: parts.sessionId,
63
+ ...(parts.harness ? { harness: parts.harness } : {}),
62
64
  clientOperationId: parts.clientOperationId,
63
65
  companyUid: parts.companyUid ?? null,
64
66
  companySlug: parts.companySlug ?? null,
@@ -83,6 +85,8 @@ function projectOutbox(op) {
83
85
  updatedAt: op.updatedAt,
84
86
  attemptCount: op.attemptCount,
85
87
  };
88
+ if (op.harness)
89
+ out.harness = op.harness;
86
90
  if (op.companyUid)
87
91
  out.companyUid = op.companyUid;
88
92
  if (op.companySlug)
@@ -132,6 +136,7 @@ export function enqueueOutbox(input, root) {
132
136
  }
133
137
  const digest = digestOperation({
134
138
  sessionId: input.sessionId,
139
+ harness: input.harness,
135
140
  clientOperationId: input.clientOperationId,
136
141
  companyUid: input.companyUid,
137
142
  companySlug: input.companySlug,
@@ -144,7 +149,14 @@ export function enqueueOutbox(input, root) {
144
149
  const now = (input.now ?? (() => new Date()))().toISOString();
145
150
  const existing = readOutboxOperation(operationId, root);
146
151
  if (existing) {
147
- if (existing.digest !== digest) {
152
+ // A pre-upgrade operation has no harness in its digest. Preserve its
153
+ // original receipt identity when the same observation is enqueued again.
154
+ const legacyReplay = !existing.harness && input.harness && existing.digest === digestOperation({
155
+ ...input,
156
+ harness: undefined,
157
+ kind,
158
+ });
159
+ if (existing.digest !== digest && !legacyReplay) {
148
160
  throw new NotTrackingError(`Idempotency conflict for ${operationId}`, "IdempotencyConflictError");
149
161
  }
150
162
  return existing;
@@ -154,6 +166,7 @@ export function enqueueOutbox(input, root) {
154
166
  operationId,
155
167
  clientOperationId: input.clientOperationId,
156
168
  sessionId: input.sessionId,
169
+ harness: input.harness,
157
170
  companyUid: input.companyUid,
158
171
  companySlug: input.companySlug,
159
172
  projectId: input.projectId,
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import * as fs from "node:fs";
6
6
  import { InvalidSessionIdentityError, normalizeSessionIdentity, } from "../mesh/live/session-identity.js";
7
- import { companyCorrectionPath, resolveCompany, } from "./company.js";
7
+ import { companyCorrectionPath, companySlugFromCwd, resolveCompany, } from "./company.js";
8
8
  import { WORK_CONTEXT_CONTRACT_VERSION, normalizeTaskId, } from "./contract.js";
9
9
  import { EXIT_INVALID_IDENTITY, EXIT_NOT_TRACKING, EXIT_OK, InvalidDecisionOriginError, NotTrackingError, } from "./errors.js";
10
10
  import { decisionFromCandidates, } from "./organize.js";
@@ -385,7 +385,7 @@ export async function reconcileObservation(obs, deps) {
385
385
  }
386
386
  }
387
387
  // Company resolved — resolve project/task (US-007B order).
388
- const projectResolved = resolveProjectTask({
388
+ let projectResolved = resolveProjectTask({
389
389
  sessionId,
390
390
  env,
391
391
  trusted,
@@ -393,6 +393,14 @@ export async function reconcileObservation(obs, deps) {
393
393
  hqRoot,
394
394
  existingState: prior,
395
395
  });
396
+ // Deterministic cwd projects belong to the cwd company. A higher-precedence
397
+ // identity UID must not adopt a project from an unrelated/unverified slug.
398
+ if (projectResolved?.source === "deterministic_cwd") {
399
+ const cwdCompany = companySlugFromCwd(cwd, hqRoot);
400
+ if (!resolvedCompany.slug || cwdCompany?.toLowerCase() !== resolvedCompany.slug.toLowerCase()) {
401
+ projectResolved = null;
402
+ }
403
+ }
396
404
  const projectId = projectResolved?.projectId;
397
405
  const taskId = projectResolved?.taskId;
398
406
  // Trusted / deterministic auto-bind never asks.
@@ -475,6 +483,7 @@ export async function reconcileObservation(obs, deps) {
475
483
  outboxOp = enqueueOutbox({
476
484
  clientOperationId: obs.clientOperationId,
477
485
  sessionId,
486
+ harness: obs.identity.harness,
478
487
  companyUid: resolvedCompany.uid,
479
488
  companySlug: resolvedCompany.slug,
480
489
  projectId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.108.21",
3
+ "version": "5.108.22",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {