@indigoai-us/hq-cli 5.121.2 → 5.122.1

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.
@@ -91,6 +91,20 @@ export interface SessionStateFile {
91
91
  transcriptMarker?: "from transcript";
92
92
  updatedAt: string;
93
93
  }
94
+ /**
95
+ * Metadata from a register operation that the control plane has acknowledged.
96
+ * Kept structural here so the state projection does not need a runtime
97
+ * dependency on the outbox implementation.
98
+ */
99
+ export interface AcknowledgedRegisterBinding {
100
+ kind: "register";
101
+ sessionId: string;
102
+ companyUid?: string;
103
+ companySlug?: string;
104
+ projectId?: string;
105
+ taskId?: string;
106
+ receiptId: string;
107
+ }
94
108
  /** Allowlisted keys that may appear in the durable state projection. */
95
109
  export declare const SESSION_STATE_ALLOWLIST: Set<string>;
96
110
  export declare function readSessionState(sessionId: string, root: string): SessionStateFile | null;
@@ -102,6 +116,15 @@ export declare function projectSessionState(state: SessionStateFile): SessionSta
102
116
  * and reconcile updates do not clobber each other.
103
117
  */
104
118
  export declare function mergeLocalSessionFields(next: SessionStateFile, prior: SessionStateFile | null): SessionStateFile;
119
+ /**
120
+ * Upgrade the local projection after the server acknowledges a register.
121
+ *
122
+ * A receipt makes the operation's company authoritative, but it must never
123
+ * erase an explicit untracked choice, a conflicting existing company, or a
124
+ * more specific binding for the same company. This is also used to repair
125
+ * state files created by older clients before this write-back existed.
126
+ */
127
+ export declare function writeAcknowledgedRegisterBinding(binding: AcknowledgedRegisterBinding, root: string, now?: () => Date): SessionStateFile | null;
105
128
  /** True when a durable state file was written by the hook / reconcile path. */
106
129
  export declare function isHookWrittenSessionState(state: SessionStateFile | null): boolean;
107
130
  /** Authoritative prior scope usable for company precedence. */
@@ -136,6 +136,73 @@ export function mergeLocalSessionFields(next, prior) {
136
136
  }
137
137
  return out;
138
138
  }
139
+ function acknowledgedStatus(binding) {
140
+ if (!binding.companyUid)
141
+ return null;
142
+ if (binding.projectId && binding.taskId)
143
+ return "bound";
144
+ if (binding.projectId)
145
+ return "needs_task";
146
+ return "needs_project";
147
+ }
148
+ function bindingStrength(state) {
149
+ if (state.contextStatus === "bound" && state.projectId && state.taskId)
150
+ return 3;
151
+ if (state.contextStatus === "needs_task" && state.projectId)
152
+ return 2;
153
+ if (state.contextStatus === "needs_project")
154
+ return 1;
155
+ return 0;
156
+ }
157
+ /**
158
+ * Upgrade the local projection after the server acknowledges a register.
159
+ *
160
+ * A receipt makes the operation's company authoritative, but it must never
161
+ * erase an explicit untracked choice, a conflicting existing company, or a
162
+ * more specific binding for the same company. This is also used to repair
163
+ * state files created by older clients before this write-back existed.
164
+ */
165
+ export function writeAcknowledgedRegisterBinding(binding, root, now = () => new Date()) {
166
+ const acknowledged = acknowledgedStatus(binding);
167
+ if (!acknowledged || !isSafeWorkContextSegment(binding.sessionId))
168
+ return null;
169
+ const prior = readSessionState(binding.sessionId, root);
170
+ // Reconcile always writes the local projection before network delivery. Do
171
+ // not manufacture session files when replay sees an orphaned legacy outbox
172
+ // record with no corresponding tracked session.
173
+ if (!prior)
174
+ return null;
175
+ if (prior.contextStatus === "untracked" ||
176
+ (prior.companyUid && prior.companyUid !== binding.companyUid)) {
177
+ return prior;
178
+ }
179
+ const acknowledgementStrength = acknowledged === "bound" ? 3 : acknowledged === "needs_task" ? 2 : 1;
180
+ const priorStrength = bindingStrength(prior);
181
+ const preservePriorBinding = Boolean(prior.companyUid === binding.companyUid && priorStrength > acknowledgementStrength);
182
+ const next = mergeLocalSessionFields({
183
+ contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
184
+ sessionId: binding.sessionId,
185
+ companyUid: binding.companyUid,
186
+ companySlug: binding.companySlug ?? prior.companySlug,
187
+ contextStatus: preservePriorBinding ? prior.contextStatus : acknowledged,
188
+ projectId: preservePriorBinding ? prior.projectId : binding.projectId,
189
+ taskId: preservePriorBinding ? prior.taskId : binding.taskId,
190
+ bindingEpisodeId: preservePriorBinding
191
+ ? prior.bindingEpisodeId ?? binding.receiptId
192
+ : binding.receiptId,
193
+ updatedAt: now().toISOString(),
194
+ }, prior);
195
+ if (prior.companyUid === next.companyUid &&
196
+ prior.companySlug === next.companySlug &&
197
+ prior.contextStatus === next.contextStatus &&
198
+ prior.projectId === next.projectId &&
199
+ prior.taskId === next.taskId &&
200
+ prior.bindingEpisodeId === next.bindingEpisodeId) {
201
+ return prior;
202
+ }
203
+ writeSessionState(next, root);
204
+ return next;
205
+ }
139
206
  /** True when a durable state file was written by the hook / reconcile path. */
140
207
  export function isHookWrittenSessionState(state) {
141
208
  if (!state)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.121.2",
3
+ "version": "5.122.1",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {