@indigoai-us/hq-cli 5.244.0 → 5.246.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.
@@ -690,32 +690,46 @@ export async function reconcileObservation(obs, deps) {
690
690
  registerClassification = "needs_project";
691
691
  diagnostics.push(`company rebind to ${resolvedCompany.slug ?? resolvedCompany.uid}; cleared project`);
692
692
  }
693
+ // Acked register operations for this session. A register that did not come
694
+ // back bound yields no binding episode, so its receipt (not the session's
695
+ // bindingEpisodeId) is what identifies an already acknowledged binding.
696
+ const ackedRegisters = prior
697
+ ? listOutboxOperations(deps.root).filter((operation) => operation.kind === "register" &&
698
+ operation.delivery === "acked" &&
699
+ operation.sessionId === sessionId &&
700
+ Boolean(operation.receiptId))
701
+ : [];
693
702
  const stateClassification = registerClassification === "unresolved" &&
694
703
  prior?.contextStatus === "needs_project" &&
695
704
  prior.companyUid === resolvedCompany.uid &&
696
- prior.bindingEpisodeId
705
+ (prior.bindingEpisodeId ||
706
+ ackedRegisters.some((operation) => operation.companyUid === resolvedCompany.uid))
697
707
  ? "needs_project"
698
708
  : registerClassification;
699
709
  // Re-observing an already acknowledged binding is normal for hooks and the
700
710
  // resident daemon. A fresh clientOperationId must not turn that observation
701
711
  // into a second semantic register operation: the prior receipt is the stable
702
712
  // identity for this exact company/project/task binding.
703
- const acknowledgedPrior = prior?.bindingEpisodeId &&
713
+ const priorBindingIsBound = Boolean(projectId && taskId);
714
+ const acknowledgedReceipt = prior &&
704
715
  prior.companyUid === resolvedCompany.uid &&
705
716
  prior.companySlug === resolvedCompany.slug &&
706
717
  prior.projectId === projectId &&
707
- prior.taskId === taskId &&
708
- listOutboxOperations(deps.root).some((operation) => operation.kind === "register" &&
709
- operation.delivery === "acked" &&
710
- operation.sessionId === sessionId &&
711
- operation.receiptId === prior.bindingEpisodeId &&
718
+ prior.taskId === taskId
719
+ ? ackedRegisters.find((operation) => (priorBindingIsBound
720
+ ? operation.receiptId === prior.bindingEpisodeId
721
+ : !prior.bindingEpisodeId || operation.receiptId === prior.bindingEpisodeId) &&
712
722
  operation.companyUid === resolvedCompany.uid &&
713
723
  operation.companySlug === resolvedCompany.slug &&
714
724
  operation.projectId === projectId &&
715
- operation.taskId === taskId)
716
- ? prior
717
- : null;
725
+ operation.taskId === taskId)?.receiptId
726
+ : undefined;
727
+ const acknowledgedPrior = acknowledgedReceipt ? prior : null;
718
728
  if (acknowledgedPrior) {
729
+ // A partial (not bound) acknowledged register carries no server episode.
730
+ const retainedEpisodeId = priorBindingIsBound
731
+ ? acknowledgedPrior.bindingEpisodeId
732
+ : undefined;
719
733
  // Activity is local progress, not binding identity. Retain the receipt
720
734
  // without another register while carrying hook counters forward so the
721
735
  // normal clarification threshold still advances.
@@ -727,7 +741,7 @@ export async function reconcileObservation(obs, deps) {
727
741
  contextStatus: stateClassification,
728
742
  projectId,
729
743
  taskId,
730
- bindingEpisodeId: acknowledgedPrior.bindingEpisodeId,
744
+ bindingEpisodeId: retainedEpisodeId,
731
745
  updatedAt: nowIso,
732
746
  startedAt,
733
747
  ...(typeof toolWrites === "number" ? { toolWrites } : {}),
@@ -735,7 +749,7 @@ export async function reconcileObservation(obs, deps) {
735
749
  ...(decision ? { decision } : {}),
736
750
  ...bindStateFields,
737
751
  ...transcriptFields,
738
- }, prior), deps.root);
752
+ }, prior, { preserveBindingEpisode: priorBindingIsBound }), deps.root);
739
753
  const kind = registerClassification === "bound"
740
754
  ? "bound"
741
755
  : registerClassification === "needs_task"
@@ -743,7 +757,7 @@ export async function reconcileObservation(obs, deps) {
743
757
  : registerClassification === "needs_project"
744
758
  ? "needs_project"
745
759
  : "queued";
746
- diagnostics.push(`binding unchanged; retained receipt=${acknowledgedPrior.bindingEpisodeId}`);
760
+ diagnostics.push(`binding unchanged; retained receipt=${acknowledgedReceipt}`);
747
761
  return {
748
762
  result: resultOf({
749
763
  kind,
@@ -755,7 +769,7 @@ export async function reconcileObservation(obs, deps) {
755
769
  companySlug: resolvedCompany.slug,
756
770
  projectId,
757
771
  taskId,
758
- bindingEpisodeId: acknowledgedPrior.bindingEpisodeId,
772
+ ...(retainedEpisodeId ? { bindingEpisodeId: retainedEpisodeId } : {}),
759
773
  ...bindResultFields,
760
774
  }),
761
775
  exitCode: EXIT_OK,
@@ -174,4 +174,12 @@ export declare function authoritativeCompanyFromState(state: SessionStateFile |
174
174
  slug?: string;
175
175
  uid?: string;
176
176
  } | null;
177
+ /**
178
+ * Apply `hq mesh context bind-project`: an operator's local project choice.
179
+ *
180
+ * The server issues no binding episode for this local choice, so any prior
181
+ * bindingEpisodeId (older clients stored a register receipt there) is cleared.
182
+ * Carrying it forward makes the server drop every later session-event batch.
183
+ */
184
+ export declare function applyOperatorProjectBinding(state: SessionStateFile, projectId: string, now?: () => Date): SessionStateFile;
177
185
  //# sourceMappingURL=state.d.ts.map
@@ -367,6 +367,19 @@ export function writeAcknowledgedRegisterBinding(binding, root, now = () => new
367
367
  const acknowledgementStrength = acknowledged === "bound" ? 3 : acknowledged === "needs_task" ? 2 : 1;
368
368
  const priorStrength = bindingStrength(prior);
369
369
  const preservePriorBinding = Boolean(prior.companyUid === binding.companyUid && priorStrength > acknowledgementStrength);
370
+ // The server issues no binding episode for a register that did not come
371
+ // back bound (unresolved / needs_project / needs_task). Its receipt must
372
+ // never stand in for one: session-event batches carry bindingEpisodeId and
373
+ // the server drops batches whose episode it does not recognise. Older
374
+ // clients stored that receipt here, so also repair a prior value that is
375
+ // exactly this partial register's receipt.
376
+ const receiptEpisode = acknowledged === "bound" ? binding.receiptId : undefined;
377
+ const priorEpisode = acknowledged !== "bound" && prior.bindingEpisodeId === binding.receiptId
378
+ ? undefined
379
+ : prior.bindingEpisodeId;
380
+ const sameBindingAsPrior = prior.companyUid === binding.companyUid &&
381
+ prior.projectId === binding.projectId &&
382
+ prior.taskId === binding.taskId;
370
383
  const next = mergeLocalSessionFields({
371
384
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
372
385
  sessionId: binding.sessionId,
@@ -376,10 +389,10 @@ export function writeAcknowledgedRegisterBinding(binding, root, now = () => new
376
389
  projectId: preservePriorBinding ? prior.projectId : binding.projectId,
377
390
  taskId: preservePriorBinding ? prior.taskId : binding.taskId,
378
391
  bindingEpisodeId: preservePriorBinding
379
- ? prior.bindingEpisodeId ?? binding.receiptId
380
- : binding.receiptId,
392
+ ? priorEpisode ?? receiptEpisode
393
+ : receiptEpisode ?? (sameBindingAsPrior ? priorEpisode : undefined),
381
394
  updatedAt: now().toISOString(),
382
- }, prior);
395
+ }, prior, { preserveBindingEpisode: false });
383
396
  if (prior.companyUid === next.companyUid &&
384
397
  prior.companySlug === next.companySlug &&
385
398
  prior.contextStatus === next.contextStatus &&
@@ -418,4 +431,24 @@ export function authoritativeCompanyFromState(state) {
418
431
  return null;
419
432
  return { slug: state.companySlug, uid: state.companyUid };
420
433
  }
434
+ /**
435
+ * Apply `hq mesh context bind-project`: an operator's local project choice.
436
+ *
437
+ * The server issues no binding episode for this local choice, so any prior
438
+ * bindingEpisodeId (older clients stored a register receipt there) is cleared.
439
+ * Carrying it forward makes the server drop every later session-event batch.
440
+ */
441
+ export function applyOperatorProjectBinding(state, projectId, now = () => new Date()) {
442
+ return {
443
+ ...state,
444
+ contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
445
+ contextStatus: state.taskId ? "bound" : "needs_task",
446
+ projectId,
447
+ bindingEpisodeId: undefined,
448
+ projectGate: undefined,
449
+ projectCandidates: undefined,
450
+ projectBindReason: `operator selected ${projectId}`,
451
+ updatedAt: now().toISOString(),
452
+ };
453
+ }
421
454
  //# sourceMappingURL=state.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.244.0",
3
+ "version": "5.246.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@aws-sdk/client-iot-data-plane": "^3.1096.0",
46
46
  "@aws-sdk/client-s3": "^3.1049.0",
47
- "@indigoai-us/hq-cloud": "~6.18.5",
47
+ "@indigoai-us/hq-cloud": "~6.18.10",
48
48
  "@indigoai-us/hq-flags-client": "^0.1.2",
49
49
  "@sentry/node": "^10.49.0",
50
50
  "@tobilu/qmd": "2.5.3",