@indigoai-us/hq-cli 5.345.178 → 5.345.180
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 +39 -0
- package/dist/command-catalog.generated.d.ts +197 -1
- package/dist/command-catalog.generated.js +241 -1
- package/dist/commands/bot-jobs.d.ts +11 -9
- package/dist/commands/bot-jobs.js +56 -21
- package/dist/commands/bot.d.ts +37 -1
- package/dist/commands/bot.js +289 -13
- package/dist/commands/channels.d.ts +18 -1
- package/dist/commands/channels.js +185 -1
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +15 -4
- package/dist/commands/integrations-core.d.ts +2 -0
- package/dist/commands/integrations-core.js +1 -0
- package/dist/commands/lanes.d.ts +1 -1
- package/dist/commands/lanes.js +6 -2
- package/dist/commands/mesh.js +19 -0
- package/dist/commands/sources.d.ts +89 -0
- package/dist/commands/sources.js +363 -1
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/config.d.ts +8 -0
- package/dist/lib/bot/fleet-local.d.ts +1 -1
- package/dist/lib/bot/fleet-local.js +82 -30
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +109 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +177 -0
- package/dist/lib/bot/local-jobs.d.ts +40 -4
- package/dist/lib/bot/local-jobs.js +72 -5
- package/dist/lib/bot/run.d.ts +15 -0
- package/dist/lib/bot/run.js +99 -1
- package/dist/lib/bot/runtime-auth.d.ts +119 -0
- package/dist/lib/bot/runtime-auth.js +251 -0
- package/dist/lib/daemon/catalog.d.ts +1 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/handoff.d.ts +4 -0
- package/dist/lib/daemon/handoff.js +6 -2
- package/dist/lib/daemon/install.d.ts +70 -2
- package/dist/lib/daemon/install.js +185 -27
- package/dist/lib/daemon/legacy.d.ts +9 -3
- package/dist/lib/daemon/legacy.js +11 -4
- package/dist/lib/daemon/migrate.js +1 -1
- package/dist/lib/daemon/run.d.ts +5 -2
- package/dist/lib/daemon/run.js +74 -22
- package/dist/lib/doctor/checks/anywhere.d.ts +6 -0
- package/dist/lib/doctor/checks/anywhere.js +35 -6
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hot-channels/adapters/__fixtures__/dummy.d.ts +33 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/dummy.js +53 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.d.ts +38 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.js +64 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +67 -0
- package/dist/lib/hot-channels/adapters/attio.js +374 -0
- package/dist/lib/hot-channels/adapters/gmail.d.ts +111 -0
- package/dist/lib/hot-channels/adapters/gmail.js +413 -0
- package/dist/lib/hot-channels/adapters/index.d.ts +23 -0
- package/dist/lib/hot-channels/adapters/index.js +39 -0
- package/dist/lib/hot-channels/adapters/linear.d.ts +79 -0
- package/dist/lib/hot-channels/adapters/linear.js +321 -0
- package/dist/lib/hot-channels/adapters/meeting-providers.d.ts +86 -0
- package/dist/lib/hot-channels/adapters/meeting-providers.js +253 -0
- package/dist/lib/hot-channels/adapters/meetings.d.ts +81 -0
- package/dist/lib/hot-channels/adapters/meetings.js +525 -0
- package/dist/lib/hot-channels/adapters/slack.d.ts +70 -0
- package/dist/lib/hot-channels/adapters/slack.js +277 -0
- package/dist/lib/hot-channels/alert.d.ts +78 -0
- package/dist/lib/hot-channels/alert.js +179 -0
- package/dist/lib/hot-channels/audience.d.ts +15 -0
- package/dist/lib/hot-channels/audience.js +25 -0
- package/dist/lib/hot-channels/contract.d.ts +146 -0
- package/dist/lib/hot-channels/contract.js +20 -0
- package/dist/lib/hot-channels/cursors.d.ts +41 -0
- package/dist/lib/hot-channels/cursors.js +84 -0
- package/dist/lib/hot-channels/index.d.ts +10 -0
- package/dist/lib/hot-channels/index.js +10 -0
- package/dist/lib/hot-channels/landing.d.ts +83 -0
- package/dist/lib/hot-channels/landing.js +175 -0
- package/dist/lib/hot-channels/ledger.d.ts +101 -0
- package/dist/lib/hot-channels/ledger.js +203 -0
- package/dist/lib/hot-channels/loop.d.ts +74 -0
- package/dist/lib/hot-channels/loop.js +221 -0
- package/dist/lib/hot-channels/prompts/signals-pass.md +22 -0
- package/dist/lib/hot-channels/registry.d.ts +28 -0
- package/dist/lib/hot-channels/registry.js +121 -0
- package/dist/lib/hot-channels/run.d.ts +137 -0
- package/dist/lib/hot-channels/run.js +324 -0
- package/dist/lib/hot-channels/signals-pass.d.ts +150 -0
- package/dist/lib/hot-channels/signals-pass.js +581 -0
- package/dist/lib/hot-channels/sync-lock.d.ts +51 -0
- package/dist/lib/hot-channels/sync-lock.js +224 -0
- package/dist/lib/install/hook-target.d.ts +6 -2
- package/dist/lib/install/hook-target.js +9 -4
- package/dist/lib/lanes/accounts.d.ts +1 -17
- package/dist/lib/lanes/accounts.js +5 -45
- package/dist/lib/lanes/loop.d.ts +10 -0
- package/dist/lib/lanes/loop.js +18 -1
- package/dist/lib/lanes/provider-home.d.ts +24 -0
- package/dist/lib/lanes/provider-home.js +52 -0
- package/dist/lib/lanes/spawn.d.ts +12 -0
- package/dist/lib/lanes/spawn.js +27 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +13 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
- package/dist/lib/mesh/live/daemon/run.js +31 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +2 -0
- package/dist/lib/sync-core/flag.d.ts +1 -1
- package/dist/lib/sync-core/flag.js +1 -1
- package/dist/lib/sync-core/reconcile.d.ts +1 -1
- package/dist/lib/sync-core/reconcile.js +9 -7
- package/dist/lib/sync-core/release.d.ts +3 -3
- package/dist/lib/sync-core/release.js +4 -4
- package/dist/lib/sync-core/rollout-state.d.ts +1 -0
- package/dist/lib/sync-core/rollout-state.js +1 -0
- package/package.json +8 -8
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contract-2 subscriber for the personal work topic.
|
|
3
3
|
* Owns live-cache.json writes. Presence publish stays on PresenceClient.
|
|
4
|
+
* On a person (prs_) account it also subscribes to the owner DM topic and
|
|
5
|
+
* keeps the content-free owner-inbox-push.json counter.
|
|
4
6
|
*/
|
|
5
7
|
import * as fs from "node:fs";
|
|
6
8
|
import * as path from "node:path";
|
|
@@ -10,6 +12,7 @@ import { presignIotWssUrl } from "./presign.js";
|
|
|
10
12
|
import { applyProjectViewPush, parseProjectViewPush } from "./live-cache.js";
|
|
11
13
|
import { backoffDelayMs, RECONNECT_MIN_DELAY_MS } from "./presence.js";
|
|
12
14
|
import { AGENT_INBOX_PUSH_STATE_BASENAME, SYNC_WAKE_STATE_BASENAME } from "../paths.js";
|
|
15
|
+
import { clearOwnerInboxPushState, parseOwnerDoorbell, readOwnerInboxPushState, writeOwnerInboxPushState, } from "./owner-inbox-push.js";
|
|
13
16
|
export { AGENT_INBOX_PUSH_STATE_BASENAME, SYNC_WAKE_STATE_BASENAME } from "../paths.js";
|
|
14
17
|
const INBOX_PUSH_HEARTBEAT_MS = 5_000;
|
|
15
18
|
const SYNC_WAKE_KEYS = new Set(["v", "type", "vault", "head", "rootId"]);
|
|
@@ -57,6 +60,13 @@ function readVaultSequences(raw) {
|
|
|
57
60
|
}
|
|
58
61
|
return vaults;
|
|
59
62
|
}
|
|
63
|
+
const OWNER_SEEN_EVENT_LIMIT = 500;
|
|
64
|
+
/**
|
|
65
|
+
* Owner DM subscribe retries per connection. The owner signal is best-effort:
|
|
66
|
+
* after this many failures it stays off until the next broker reconnect, and
|
|
67
|
+
* the work topic keeps running.
|
|
68
|
+
*/
|
|
69
|
+
export const OWNER_SUBSCRIBE_MAX_RETRIES = 6;
|
|
60
70
|
function readWakeSequence(meshRoot, basename, label, onInfo) {
|
|
61
71
|
const target = path.join(meshRoot, basename);
|
|
62
72
|
try {
|
|
@@ -163,6 +173,39 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
163
173
|
let syncWakeSequence = syncWakeState.sequence;
|
|
164
174
|
const syncWakeVaults = { ...syncWakeState.vaults };
|
|
165
175
|
const syncStateWritable = syncWakeState.writable;
|
|
176
|
+
let ownerSubscribed = false;
|
|
177
|
+
let ownerUid;
|
|
178
|
+
// Carry the owner counter forward across restarts. The file holds counts
|
|
179
|
+
// and timestamps only.
|
|
180
|
+
const priorOwnerState = readOwnerInboxPushState(opts.meshRoot, opts.onInfo);
|
|
181
|
+
let ownerKnown = priorOwnerState !== null;
|
|
182
|
+
let ownerState = {
|
|
183
|
+
connected: false,
|
|
184
|
+
heartbeatAtMs: priorOwnerState?.heartbeatAtMs ?? 0,
|
|
185
|
+
count: priorOwnerState?.count ?? 0,
|
|
186
|
+
...(priorOwnerState?.lastAt ? { lastAt: priorOwnerState.lastAt } : {}),
|
|
187
|
+
...(priorOwnerState?.lastType ? { lastType: priorOwnerState.lastType } : {}),
|
|
188
|
+
};
|
|
189
|
+
const seenOwnerEventIds = new Set();
|
|
190
|
+
let ownerRetryTimer = null;
|
|
191
|
+
let ownerRetryAttempt = 0;
|
|
192
|
+
// Each distinct owner subscribe failure is logged once per daemon lifetime.
|
|
193
|
+
const ownerFailuresLogged = new Set();
|
|
194
|
+
let ownerGiveUpLogged = false;
|
|
195
|
+
const clearOwnerRetry = () => {
|
|
196
|
+
if (ownerRetryTimer !== null)
|
|
197
|
+
timers.clearTimeout(ownerRetryTimer);
|
|
198
|
+
ownerRetryTimer = null;
|
|
199
|
+
};
|
|
200
|
+
const reportOwnerSubscribeFailure = (err) => {
|
|
201
|
+
// Log the error name only; the message can carry the topic and its uid.
|
|
202
|
+
const key = `${err.name}:${err.message}`;
|
|
203
|
+
if (ownerFailuresLogged.has(key))
|
|
204
|
+
return;
|
|
205
|
+
ownerFailuresLogged.add(key);
|
|
206
|
+
opts.onInfo?.(`owner inbox push subscribe failed (${err.name}); retrying with backoff, ` +
|
|
207
|
+
`up to ${OWNER_SUBSCRIBE_MAX_RETRIES} times per connection`);
|
|
208
|
+
};
|
|
166
209
|
const clearInboxHeartbeat = () => {
|
|
167
210
|
if (heartbeatTimer !== null)
|
|
168
211
|
timers.clearTimeout(heartbeatTimer);
|
|
@@ -199,30 +242,79 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
199
242
|
const persistSyncState = (connected, durable = true) => syncStateWritable
|
|
200
243
|
? persistState(SYNC_WAKE_STATE_BASENAME, "sync wake", connected, syncWakeSequence, undefined, durable, syncWakeVaults)
|
|
201
244
|
: undefined;
|
|
245
|
+
const persistOwnerState = (connected) => {
|
|
246
|
+
if (!ownerKnown)
|
|
247
|
+
return;
|
|
248
|
+
ownerState = {
|
|
249
|
+
...ownerState,
|
|
250
|
+
connected,
|
|
251
|
+
heartbeatAtMs: now().getTime(),
|
|
252
|
+
};
|
|
253
|
+
if (connected && ownerUid)
|
|
254
|
+
ownerState.principalUid = ownerUid;
|
|
255
|
+
else
|
|
256
|
+
delete ownerState.principalUid;
|
|
257
|
+
try {
|
|
258
|
+
writeOwnerInboxPushState(opts.meshRoot, ownerState);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
const name = error instanceof Error ? error.name : "UnknownError";
|
|
262
|
+
opts.onInfo?.(`owner inbox push state write failed (${name})`);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
202
265
|
const scheduleInboxHeartbeat = () => {
|
|
203
|
-
if (stopped || (!inboxSubscribed && !syncSubscribed))
|
|
266
|
+
if (stopped || (!inboxSubscribed && !syncSubscribed && !ownerSubscribed))
|
|
204
267
|
return;
|
|
205
268
|
clearInboxHeartbeat();
|
|
206
269
|
heartbeatTimer = timers.setTimeout(() => {
|
|
207
270
|
heartbeatTimer = null;
|
|
208
|
-
if (!stopped && (inboxSubscribed || syncSubscribed)) {
|
|
271
|
+
if (!stopped && (inboxSubscribed || syncSubscribed || ownerSubscribed)) {
|
|
209
272
|
if (inboxSubscribed)
|
|
210
273
|
persistInboxState(true, false);
|
|
211
274
|
if (syncSubscribed)
|
|
212
275
|
persistSyncState(true, false);
|
|
276
|
+
if (ownerSubscribed)
|
|
277
|
+
persistOwnerState(true);
|
|
213
278
|
scheduleInboxHeartbeat();
|
|
214
279
|
}
|
|
215
280
|
}, INBOX_PUSH_HEARTBEAT_MS);
|
|
216
281
|
};
|
|
282
|
+
const bumpOwnerCount = (type) => {
|
|
283
|
+
ownerState = {
|
|
284
|
+
...ownerState,
|
|
285
|
+
count: ownerState.count + 1,
|
|
286
|
+
lastAt: now().toISOString(),
|
|
287
|
+
lastType: type,
|
|
288
|
+
};
|
|
289
|
+
persistOwnerState(true);
|
|
290
|
+
};
|
|
291
|
+
const rememberOwnerEvent = (eventId) => {
|
|
292
|
+
if (!eventId)
|
|
293
|
+
return true;
|
|
294
|
+
if (seenOwnerEventIds.has(eventId))
|
|
295
|
+
return false;
|
|
296
|
+
seenOwnerEventIds.add(eventId);
|
|
297
|
+
if (seenOwnerEventIds.size > OWNER_SEEN_EVENT_LIMIT) {
|
|
298
|
+
const oldest = seenOwnerEventIds.values().next().value;
|
|
299
|
+
if (oldest !== undefined)
|
|
300
|
+
seenOwnerEventIds.delete(oldest);
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
};
|
|
217
304
|
const connect = async () => {
|
|
218
305
|
if (stopped)
|
|
219
306
|
return;
|
|
220
307
|
inboxSubscribed = false;
|
|
221
308
|
syncSubscribed = false;
|
|
222
309
|
actorUid = undefined;
|
|
310
|
+
ownerSubscribed = false;
|
|
311
|
+
ownerUid = undefined;
|
|
312
|
+
ownerRetryAttempt = 0;
|
|
313
|
+
clearOwnerRetry();
|
|
223
314
|
clearInboxHeartbeat();
|
|
224
315
|
persistInboxState(false);
|
|
225
316
|
persistSyncState(false);
|
|
317
|
+
persistOwnerState(false);
|
|
226
318
|
const gen = ++generation;
|
|
227
319
|
let bundle;
|
|
228
320
|
try {
|
|
@@ -241,7 +333,25 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
241
333
|
: undefined;
|
|
242
334
|
const syncTopic = syncStateWritable ? `hq/${bundle.actorUid}/sync` : undefined;
|
|
243
335
|
actorUid = inboxTopic || syncTopic ? bundle.actorUid : undefined;
|
|
244
|
-
|
|
336
|
+
const ownerTopic = bundle.actorUid.startsWith("prs_") &&
|
|
337
|
+
bundle.topics.dm === `hq/${bundle.actorUid}/dm`
|
|
338
|
+
? bundle.topics.dm
|
|
339
|
+
: undefined;
|
|
340
|
+
const ownerActorUid = ownerTopic ? bundle.actorUid : undefined;
|
|
341
|
+
ownerUid = ownerActorUid;
|
|
342
|
+
if (!ownerTopic && ownerKnown) {
|
|
343
|
+
// Not a person account (or no owner DM topic): drop a file left by an
|
|
344
|
+
// earlier person login so push-status reports off rather than stale.
|
|
345
|
+
try {
|
|
346
|
+
clearOwnerInboxPushState(opts.meshRoot);
|
|
347
|
+
ownerKnown = false;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const name = error instanceof Error ? error.name : "UnknownError";
|
|
351
|
+
opts.onInfo?.(`owner inbox push state clear failed (${name})`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!topic && !inboxTopic && !syncTopic && !ownerTopic) {
|
|
245
355
|
opts.onInfo?.("work cache subscribe skipped: credentials have no supported topic");
|
|
246
356
|
return;
|
|
247
357
|
}
|
|
@@ -257,6 +367,8 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
257
367
|
if (stopped || gen !== generation)
|
|
258
368
|
return;
|
|
259
369
|
reconnectLogged = false;
|
|
370
|
+
// The owner DM topic is best-effort and is not counted here: its failure
|
|
371
|
+
// retries on its own and never ends the connection.
|
|
260
372
|
let pendingSubscriptions = Number(Boolean(topic)) + Number(Boolean(inboxTopic)) +
|
|
261
373
|
Number(Boolean(syncTopic));
|
|
262
374
|
let subscriptionFailed = false;
|
|
@@ -282,6 +394,46 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
282
394
|
if (pendingSubscriptions === 0)
|
|
283
395
|
attempt = 0;
|
|
284
396
|
};
|
|
397
|
+
if (pendingSubscriptions === 0)
|
|
398
|
+
attempt = 0;
|
|
399
|
+
const subscribeOwner = () => {
|
|
400
|
+
if (!ownerTopic)
|
|
401
|
+
return;
|
|
402
|
+
next.subscribe(ownerTopic, { qos: 1 }, (err) => {
|
|
403
|
+
if (stopped || gen !== generation)
|
|
404
|
+
return;
|
|
405
|
+
if (err) {
|
|
406
|
+
ownerSubscribed = false;
|
|
407
|
+
ownerUid = undefined;
|
|
408
|
+
persistOwnerState(false);
|
|
409
|
+
reportOwnerSubscribeFailure(err);
|
|
410
|
+
if (ownerRetryAttempt >= OWNER_SUBSCRIBE_MAX_RETRIES) {
|
|
411
|
+
if (!ownerGiveUpLogged) {
|
|
412
|
+
ownerGiveUpLogged = true;
|
|
413
|
+
opts.onInfo?.(`owner inbox push subscribe gave up after ${OWNER_SUBSCRIBE_MAX_RETRIES} retries; ` +
|
|
414
|
+
"owner push stays off until the next reconnect");
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const delay = backoffDelayMs(ownerRetryAttempt, 1000, 60_000, random, 0, RECONNECT_MIN_DELAY_MS);
|
|
419
|
+
ownerRetryAttempt += 1;
|
|
420
|
+
clearOwnerRetry();
|
|
421
|
+
ownerRetryTimer = timers.setTimeout(() => {
|
|
422
|
+
ownerRetryTimer = null;
|
|
423
|
+
if (stopped || gen !== generation)
|
|
424
|
+
return;
|
|
425
|
+
subscribeOwner();
|
|
426
|
+
}, delay);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
ownerRetryAttempt = 0;
|
|
430
|
+
ownerSubscribed = true;
|
|
431
|
+
ownerKnown = true;
|
|
432
|
+
ownerUid = ownerActorUid;
|
|
433
|
+
persistOwnerState(true);
|
|
434
|
+
scheduleInboxHeartbeat();
|
|
435
|
+
});
|
|
436
|
+
};
|
|
285
437
|
if (topic) {
|
|
286
438
|
next.subscribe(topic, { qos: 1 }, (err) => finishSubscription(err));
|
|
287
439
|
}
|
|
@@ -320,6 +472,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
320
472
|
finishSubscription();
|
|
321
473
|
});
|
|
322
474
|
}
|
|
475
|
+
subscribeOwner();
|
|
323
476
|
});
|
|
324
477
|
next.on("message", (_topic, payload) => {
|
|
325
478
|
if (stopped || gen !== generation)
|
|
@@ -344,6 +497,48 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
344
497
|
persistSyncState(true);
|
|
345
498
|
return;
|
|
346
499
|
}
|
|
500
|
+
if (ownerTopic && _topic === ownerTopic) {
|
|
501
|
+
if (!ownerSubscribed)
|
|
502
|
+
return;
|
|
503
|
+
// Read only type, channelId and eventId. Message text, poster name
|
|
504
|
+
// and email are never stored, logged or forwarded.
|
|
505
|
+
const doorbell = parseOwnerDoorbell(payload);
|
|
506
|
+
if (!doorbell)
|
|
507
|
+
return;
|
|
508
|
+
if (doorbell.channelId) {
|
|
509
|
+
const scope = opts.ownerChannelScope;
|
|
510
|
+
if (!scope)
|
|
511
|
+
return;
|
|
512
|
+
if (doorbell.eventId && seenOwnerEventIds.has(doorbell.eventId))
|
|
513
|
+
return;
|
|
514
|
+
const channelId = doorbell.channelId;
|
|
515
|
+
void scope
|
|
516
|
+
.isInScope(channelId)
|
|
517
|
+
.catch((error) => {
|
|
518
|
+
const name = error instanceof Error ? error.name : "UnknownError";
|
|
519
|
+
opts.onInfo?.(`owner channel scope check failed (${name})`);
|
|
520
|
+
return false;
|
|
521
|
+
})
|
|
522
|
+
.then((inScope) => {
|
|
523
|
+
if (!inScope || stopped || gen !== generation || !ownerSubscribed)
|
|
524
|
+
return;
|
|
525
|
+
// Record the event only once it counts, so a redelivery after a
|
|
526
|
+
// failed or out-of-scope check is evaluated again.
|
|
527
|
+
if (!rememberOwnerEvent(doorbell.eventId))
|
|
528
|
+
return;
|
|
529
|
+
bumpOwnerCount(doorbell.type);
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
// A channel event must name its channel; without one its company is
|
|
534
|
+
// unknown, so it is dropped.
|
|
535
|
+
if (doorbell.type === "channel")
|
|
536
|
+
return;
|
|
537
|
+
if (!rememberOwnerEvent(doorbell.eventId))
|
|
538
|
+
return;
|
|
539
|
+
bumpOwnerCount(doorbell.type);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
347
542
|
if (_topic !== topic)
|
|
348
543
|
return;
|
|
349
544
|
const push = parseProjectViewPush(payload);
|
|
@@ -360,9 +555,13 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
360
555
|
inboxSubscribed = false;
|
|
361
556
|
syncSubscribed = false;
|
|
362
557
|
actorUid = undefined;
|
|
558
|
+
ownerSubscribed = false;
|
|
559
|
+
ownerUid = undefined;
|
|
560
|
+
clearOwnerRetry();
|
|
363
561
|
clearInboxHeartbeat();
|
|
364
562
|
persistInboxState(false);
|
|
365
563
|
persistSyncState(false);
|
|
564
|
+
persistOwnerState(false);
|
|
366
565
|
opts.onInfo?.("work cache mqtt error");
|
|
367
566
|
schedule();
|
|
368
567
|
try {
|
|
@@ -378,9 +577,13 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
378
577
|
inboxSubscribed = false;
|
|
379
578
|
syncSubscribed = false;
|
|
380
579
|
actorUid = undefined;
|
|
580
|
+
ownerSubscribed = false;
|
|
581
|
+
ownerUid = undefined;
|
|
582
|
+
clearOwnerRetry();
|
|
381
583
|
clearInboxHeartbeat();
|
|
382
584
|
persistInboxState(false);
|
|
383
585
|
persistSyncState(false);
|
|
586
|
+
persistOwnerState(false);
|
|
384
587
|
schedule();
|
|
385
588
|
});
|
|
386
589
|
};
|
|
@@ -408,12 +611,16 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
408
611
|
generation += 1;
|
|
409
612
|
if (timer !== null)
|
|
410
613
|
timers.clearTimeout(timer);
|
|
614
|
+
clearOwnerRetry();
|
|
411
615
|
clearInboxHeartbeat();
|
|
412
616
|
inboxSubscribed = false;
|
|
413
617
|
syncSubscribed = false;
|
|
414
618
|
actorUid = undefined;
|
|
619
|
+
ownerSubscribed = false;
|
|
620
|
+
ownerUid = undefined;
|
|
415
621
|
persistInboxState(false);
|
|
416
622
|
persistSyncState(false);
|
|
623
|
+
persistOwnerState(false);
|
|
417
624
|
const current = client;
|
|
418
625
|
client = null;
|
|
419
626
|
if (!current)
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
6
6
|
export declare const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
7
7
|
export declare const SYNC_WAKE_STATE_BASENAME = "sync-wake.json";
|
|
8
|
+
/** Content-free counter file written by the mesh MQTT owner DM subscriber. */
|
|
9
|
+
export declare const OWNER_INBOX_PUSH_STATE_BASENAME = "owner-inbox-push.json";
|
|
8
10
|
export declare function workMeshRoot(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
9
11
|
export declare function workMeshSpoolPath(root?: string): string;
|
|
10
12
|
export declare function workMeshHeldPath(root?: string): string;
|
|
@@ -8,6 +8,8 @@ const WORK_MESH_DIRNAME = "work-mesh";
|
|
|
8
8
|
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
9
9
|
export const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
10
10
|
export const SYNC_WAKE_STATE_BASENAME = "sync-wake.json";
|
|
11
|
+
/** Content-free counter file written by the mesh MQTT owner DM subscriber. */
|
|
12
|
+
export const OWNER_INBOX_PUSH_STATE_BASENAME = "owner-inbox-push.json";
|
|
11
13
|
export function workMeshRoot(home = os.homedir(), env = process.env) {
|
|
12
14
|
const override = env.HQ_WORK_MESH_ROOT?.trim();
|
|
13
15
|
if (override)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FlagClientOptions, type FlagResolution } from "@indigoai-us/hq-flags-client";
|
|
2
|
-
export declare const SYNC_RUST_ENGINE_ROLLOUT_FLAG = "sync.rust-engine-rollout";
|
|
2
|
+
export declare const SYNC_RUST_ENGINE_ROLLOUT_FLAG = "sync.rust-engine-rollout-v2";
|
|
3
3
|
export interface RolloutFlagClient {
|
|
4
4
|
ready(): Promise<void>;
|
|
5
5
|
explain(flagKey: string, options?: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createFlagClient, readLocalOverride } from "@indigoai-us/hq-flags-client";
|
|
2
2
|
import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
3
|
-
export const SYNC_RUST_ENGINE_ROLLOUT_FLAG = "sync.rust-engine-rollout";
|
|
3
|
+
export const SYNC_RUST_ENGINE_ROLLOUT_FLAG = "sync.rust-engine-rollout-v2";
|
|
4
4
|
const READ_TIMEOUT_MS = 5_000;
|
|
5
5
|
export class RolloutFlagReadError extends Error {
|
|
6
6
|
}
|
|
@@ -26,6 +26,6 @@ export interface SyncCoreReconcileResult {
|
|
|
26
26
|
kept: string[];
|
|
27
27
|
warnings: string[];
|
|
28
28
|
}
|
|
29
|
-
export declare function
|
|
29
|
+
export declare function fullModeHold(vault: SyncCoreVault, release: string): string | undefined;
|
|
30
30
|
export declare function reconcileSyncCore(deps: SyncCoreReconcileDeps): Promise<SyncCoreReconcileResult>;
|
|
31
31
|
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -3,17 +3,18 @@ import * as path from "node:path";
|
|
|
3
3
|
import { readCompaniesManifestMap } from "../work-context/repo-remote.js";
|
|
4
4
|
import { readVaultSyncSwitches, writeVaultSyncSwitches } from "../vault-sync-switches.js";
|
|
5
5
|
import { clearEngineStatus, engineFirstCycle, isSyncCoreVaultUid, removeEngineOwnership, syncCoreEngineHome, writeEngineHome, } from "./engine-home.js";
|
|
6
|
-
import {
|
|
6
|
+
import { SYNC_CORE_FULL_RELEASE, SYNC_CORE_RELEASE, fullModeAllowed, installedSyncCoreRelease, syncCoreBinaryPath, } from "./release.js";
|
|
7
7
|
import { readSyncCoreSkips } from "./skip.js";
|
|
8
|
+
import { SYNC_RUST_ENGINE_ROLLOUT_FLAG } from "./flag.js";
|
|
8
9
|
import { readSyncCoreRolloutState, syncCoreRolloutStatePath, writeSyncCoreRolloutState, } from "./rollout-state.js";
|
|
9
10
|
function message(error) {
|
|
10
11
|
return error instanceof Error ? error.message : String(error);
|
|
11
12
|
}
|
|
12
|
-
export function
|
|
13
|
-
if (
|
|
13
|
+
export function fullModeHold(vault, release) {
|
|
14
|
+
if (fullModeAllowed(release))
|
|
14
15
|
return undefined;
|
|
15
|
-
const needed =
|
|
16
|
-
return
|
|
16
|
+
const needed = SYNC_CORE_FULL_RELEASE ? `engine release ${SYNC_CORE_FULL_RELEASE} or later` : "an engine release cleared for full sync";
|
|
17
|
+
return `${vault.kind} vaults move to the new sync engine with ${needed} (${release} installed); staying on the current sync`;
|
|
17
18
|
}
|
|
18
19
|
function localCompanies(hqRoot, memberships) {
|
|
19
20
|
const manifest = readCompaniesManifestMap(hqRoot) ?? {};
|
|
@@ -78,7 +79,7 @@ export async function reconcileSyncCore(deps) {
|
|
|
78
79
|
on = await deps.readRolloutFlag(company.uid);
|
|
79
80
|
}
|
|
80
81
|
catch (error) {
|
|
81
|
-
on = previousByUid.has(company.uid);
|
|
82
|
+
on = previous?.rolloutFlagKey === SYNC_RUST_ENGINE_ROLLOUT_FLAG && previousByUid.has(company.uid);
|
|
82
83
|
warnings.push(`${message(error)}; keeping ${company.slug} ${on ? "on the new engine" : "on the current sync"}`);
|
|
83
84
|
}
|
|
84
85
|
if (on) {
|
|
@@ -132,7 +133,7 @@ export async function reconcileSyncCore(deps) {
|
|
|
132
133
|
const held = [];
|
|
133
134
|
const engineRelease = installedRelease ?? release;
|
|
134
135
|
for (let i = desired.length - 1; i >= 0; i -= 1) {
|
|
135
|
-
const reason =
|
|
136
|
+
const reason = fullModeHold(desired[i], engineRelease);
|
|
136
137
|
if (!reason)
|
|
137
138
|
continue;
|
|
138
139
|
held.unshift({ uid: desired[i].uid, reason });
|
|
@@ -200,6 +201,7 @@ export async function reconcileSyncCore(deps) {
|
|
|
200
201
|
}
|
|
201
202
|
const state = {
|
|
202
203
|
version: 1,
|
|
204
|
+
rolloutFlagKey: SYNC_RUST_ENGINE_ROLLOUT_FLAG,
|
|
203
205
|
readAt: at,
|
|
204
206
|
release: installedRelease ?? release,
|
|
205
207
|
binaryPath,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare const SYNC_CORE_RELEASE = "v0.1.
|
|
2
|
-
export declare const
|
|
1
|
+
export declare const SYNC_CORE_RELEASE = "v0.1.23";
|
|
2
|
+
export declare const SYNC_CORE_FULL_RELEASE: string | null;
|
|
3
3
|
export declare function compareSyncCoreReleases(a: string, b: string): number;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function fullModeAllowed(release: string): boolean;
|
|
5
5
|
export declare const SYNC_CORE_DOWNLOAD_BASE = "https://indigoai-public.s3.amazonaws.com/hq-sync-core";
|
|
6
6
|
export declare const SYNC_CORE_DOWNLOAD_BASE_ENV = "HQ_SYNC_CORE_DOWNLOAD_BASE";
|
|
7
7
|
export declare function syncCoreBinaryPath(home?: string): string;
|
|
@@ -3,8 +3,8 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
-
export const SYNC_CORE_RELEASE = "v0.1.
|
|
7
|
-
export const
|
|
6
|
+
export const SYNC_CORE_RELEASE = "v0.1.23";
|
|
7
|
+
export const SYNC_CORE_FULL_RELEASE = "v0.1.23";
|
|
8
8
|
export function compareSyncCoreReleases(a, b) {
|
|
9
9
|
const parse = (release) => release.replace(/^v/, "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
10
10
|
const [x, y] = [parse(a), parse(b)];
|
|
@@ -15,8 +15,8 @@ export function compareSyncCoreReleases(a, b) {
|
|
|
15
15
|
}
|
|
16
16
|
return 0;
|
|
17
17
|
}
|
|
18
|
-
export function
|
|
19
|
-
return
|
|
18
|
+
export function fullModeAllowed(release) {
|
|
19
|
+
return SYNC_CORE_FULL_RELEASE !== null && compareSyncCoreReleases(release, SYNC_CORE_FULL_RELEASE) >= 0;
|
|
20
20
|
}
|
|
21
21
|
export const SYNC_CORE_DOWNLOAD_BASE = "https://indigoai-public.s3.amazonaws.com/hq-sync-core";
|
|
22
22
|
export const SYNC_CORE_DOWNLOAD_BASE_ENV = "HQ_SYNC_CORE_DOWNLOAD_BASE";
|
|
@@ -100,6 +100,7 @@ export function readSyncCoreRolloutState(home) {
|
|
|
100
100
|
return null;
|
|
101
101
|
return {
|
|
102
102
|
version: 1,
|
|
103
|
+
...(typeof obj.rolloutFlagKey === "string" ? { rolloutFlagKey: obj.rolloutFlagKey } : {}),
|
|
103
104
|
readAt: obj.readAt,
|
|
104
105
|
release: obj.release,
|
|
105
106
|
binaryPath: obj.binaryPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.180",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
53
53
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
54
54
|
"@aws-sdk/dsql-signer": "^3.1146.0",
|
|
55
|
-
"@indigoai-us/hq-cloud": "6.18.
|
|
55
|
+
"@indigoai-us/hq-cloud": "6.18.105",
|
|
56
56
|
"@indigoai-us/hq-flags-client": "^0.1.2",
|
|
57
57
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
58
58
|
"@sentry/node": "^10.49.0",
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.180",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.180",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.180",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.180",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.180",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.180"
|
|
123
123
|
}
|
|
124
124
|
}
|