@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
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
4
4
|
import { suggestChannels, withChannelSuggestions } from "./channel-suggest.js";
|
|
5
|
-
import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, } from "./dm.js";
|
|
5
|
+
import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, matchContactByName, } from "./dm.js";
|
|
6
6
|
/**
|
|
7
7
|
* Human label for a channel row. Named channels (personal/company) show their
|
|
8
8
|
* name + a `hq dm <slug>` hint; unnamed group DMs are identified by their
|
|
@@ -294,6 +294,176 @@ async function runChannelsDelete(channelName, opts) {
|
|
|
294
294
|
process.exit(1);
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
// ─── Channel membership: add / remove ────────────────────────────────────────
|
|
298
|
+
/**
|
|
299
|
+
* Resolve the `<member>` argument of `hq channels add|remove` to a person or
|
|
300
|
+
* agent uid. Accepts a uid (`prs_…`, `agt_…`), an email, or a display name /
|
|
301
|
+
* @handle matched against the caller's DM contacts (which include the bots the
|
|
302
|
+
* caller owns). Pure → unit-testable. Returns an error sentence on no or
|
|
303
|
+
* ambiguous match so the caller never guesses. Feedback e165b286.
|
|
304
|
+
*/
|
|
305
|
+
export function resolveMemberTarget(contacts, raw) {
|
|
306
|
+
const target = raw.trim().replace(/^@/, "");
|
|
307
|
+
if (!target)
|
|
308
|
+
return { ok: false, error: "Name a member: a @handle, an email, or a prs_/agt_ uid." };
|
|
309
|
+
if (/^(prs|agt)_[A-Za-z0-9_-]+$/.test(target)) {
|
|
310
|
+
const known = contacts.find((c) => c.personUid === target);
|
|
311
|
+
return { ok: true, personUid: target, displayName: known?.displayName?.trim() || target };
|
|
312
|
+
}
|
|
313
|
+
if (target.includes("@")) {
|
|
314
|
+
const email = target.toLowerCase();
|
|
315
|
+
const hits = contacts.filter((c) => c.personUid && c.email?.trim().toLowerCase() === email);
|
|
316
|
+
const unique = [...new Map(hits.map((c) => [c.personUid, c])).values()];
|
|
317
|
+
if (unique.length === 1) {
|
|
318
|
+
return { ok: true, personUid: unique[0].personUid, displayName: unique[0].displayName?.trim() || email };
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
error: unique.length === 0
|
|
323
|
+
? `No contact with the email '${target}'. They must share a company with you; pass their prs_/agt_ uid to add them anyway.`
|
|
324
|
+
: `'${target}' matches ${unique.length} contacts. Pass the prs_/agt_ uid instead.`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const match = matchContactByName(contacts, target);
|
|
328
|
+
if (match?.personUid) {
|
|
329
|
+
return { ok: true, personUid: match.personUid, displayName: match.displayName?.trim() || target };
|
|
330
|
+
}
|
|
331
|
+
const names = contacts
|
|
332
|
+
.map((c) => c.displayName?.trim())
|
|
333
|
+
.filter((n) => Boolean(n))
|
|
334
|
+
.sort((a, b) => a.localeCompare(b));
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: `No contact matches '${raw.trim()}'. Contacts: ${names.slice(0, 12).join(", ") || "(none)"}` +
|
|
338
|
+
`${names.length > 12 ? ", …" : ""}. Use the exact name, an email, or a prs_/agt_ uid.`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async function fetchDmContacts(token) {
|
|
342
|
+
const res = await vaultApiFetch({ token, path: "/v1/notify/contacts" });
|
|
343
|
+
if (!res.ok)
|
|
344
|
+
return [];
|
|
345
|
+
const data = (await res.json().catch(() => ({})));
|
|
346
|
+
return Array.isArray(data.contacts) ? data.contacts : [];
|
|
347
|
+
}
|
|
348
|
+
async function resolveOneChannel(token, channelName) {
|
|
349
|
+
const channels = await fetchChannels(token);
|
|
350
|
+
const matches = matchChannelsByName(channels, channelName);
|
|
351
|
+
if (matches.length === 0) {
|
|
352
|
+
const message = `No channel named '${channelName}'. Run \`hq channels\` to see your channels.`;
|
|
353
|
+
throw new Error(withChannelSuggestions(message, suggestChannels(channels, channelName)));
|
|
354
|
+
}
|
|
355
|
+
if (matches.length > 1) {
|
|
356
|
+
const scopes = matches.map((match) => match.scope ?? "?").join(", ");
|
|
357
|
+
throw new Error(`'${channelName}' matches ${matches.length} channels (${scopes}). Name one of them.`);
|
|
358
|
+
}
|
|
359
|
+
return matches[0];
|
|
360
|
+
}
|
|
361
|
+
/** The server's refusal, as one plain sentence. */
|
|
362
|
+
export async function channelMembershipError(res, verb, fallback) {
|
|
363
|
+
const err = (await res.json().catch(() => ({})));
|
|
364
|
+
switch (err.code) {
|
|
365
|
+
case "CHANNEL_NOT_MEMBER":
|
|
366
|
+
return "Join the channel before adding members (so far you only browse it). Post once with `hq dm <channel> \"…\"`, or add yourself first with your own uid.";
|
|
367
|
+
case "INVITEE_NOT_FOUND":
|
|
368
|
+
return "That person or bot is not reachable from this company.";
|
|
369
|
+
case "CHANNEL_NOT_OWNER":
|
|
370
|
+
return verb === "add"
|
|
371
|
+
? "Only the channel owner or a company admin can add members."
|
|
372
|
+
: "Only the channel owner or a company admin can remove other members, and the channel owner cannot be removed.";
|
|
373
|
+
default: {
|
|
374
|
+
const detail = err.error ?? err.message;
|
|
375
|
+
return detail ? `${fallback} (HTTP ${res.status}): ${detail}` : `${fallback} (HTTP ${res.status})`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* `hq channels add <channel> <member…>`: POST /v1/notify/channels/{id}/members
|
|
381
|
+
* once per member. Agents (`agt_…`) are allowed; this is how an owner adds a
|
|
382
|
+
* personal bot to a channel so everyone there can @mention it. The server
|
|
383
|
+
* reports `alreadyMember` on a repeat, which is printed, not treated as an error.
|
|
384
|
+
*/
|
|
385
|
+
async function runChannelsAdd(channelName, members, opts) {
|
|
386
|
+
let failed;
|
|
387
|
+
try {
|
|
388
|
+
const token = await ensureCognitoToken();
|
|
389
|
+
const channel = await resolveOneChannel(token, channelName);
|
|
390
|
+
const contacts = await fetchDmContacts(token);
|
|
391
|
+
const results = [];
|
|
392
|
+
for (const raw of members) {
|
|
393
|
+
const target = resolveMemberTarget(contacts, raw);
|
|
394
|
+
if (!target.ok) {
|
|
395
|
+
results.push({ member: raw, ok: false, error: target.error });
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const res = await vaultApiFetch({
|
|
399
|
+
token,
|
|
400
|
+
path: `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/members`,
|
|
401
|
+
method: "POST",
|
|
402
|
+
body: { toPersonUid: target.personUid },
|
|
403
|
+
});
|
|
404
|
+
if (!res.ok) {
|
|
405
|
+
results.push({ member: raw, personUid: target.personUid, ok: false, error: await channelMembershipError(res, "add", "Could not add the member") });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const data = (await res.json().catch(() => ({})));
|
|
409
|
+
results.push({ member: target.displayName, personUid: target.personUid, ok: true, alreadyMember: data.alreadyMember === true });
|
|
410
|
+
}
|
|
411
|
+
const label = channel.name ?? channelName;
|
|
412
|
+
if (opts.json) {
|
|
413
|
+
console.log(JSON.stringify({ channelId: channel.channelId, name: label, results }, null, 2));
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
for (const r of results) {
|
|
417
|
+
if (r.ok) {
|
|
418
|
+
console.log(r.alreadyMember ? chalk.dim(`${r.member} is already in #${label}.`) : chalk.green(`Added ${r.member} to #${label}.`));
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.error(chalk.red(`${r.member}: ${r.error}`));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
failed = results.some((r) => !r.ok);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
429
|
+
failed = true;
|
|
430
|
+
}
|
|
431
|
+
if (failed)
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
/** `hq channels remove <channel> <member>`: DELETE /v1/notify/channels/{id}/members/{uid}. */
|
|
435
|
+
async function runChannelsRemove(channelName, member, opts) {
|
|
436
|
+
try {
|
|
437
|
+
const token = await ensureCognitoToken();
|
|
438
|
+
const channel = await resolveOneChannel(token, channelName);
|
|
439
|
+
// The member is in the channel, so resolve against the roster first, then contacts.
|
|
440
|
+
const roster = await fetchChannelMembers(token, channel.channelId);
|
|
441
|
+
const rosterContacts = roster.map((m) => ({ personUid: m.personUid, email: m.email, displayName: m.displayName }));
|
|
442
|
+
let target = resolveMemberTarget(rosterContacts, member);
|
|
443
|
+
if (!target.ok)
|
|
444
|
+
target = resolveMemberTarget(await fetchDmContacts(token), member);
|
|
445
|
+
if (!target.ok)
|
|
446
|
+
throw new Error(target.error);
|
|
447
|
+
const res = await vaultApiFetch({
|
|
448
|
+
token,
|
|
449
|
+
path: `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/members/${encodeURIComponent(target.personUid)}`,
|
|
450
|
+
method: "DELETE",
|
|
451
|
+
});
|
|
452
|
+
if (!res.ok)
|
|
453
|
+
throw new Error(await channelMembershipError(res, "remove", "Could not remove the member"));
|
|
454
|
+
const label = channel.name ?? channelName;
|
|
455
|
+
if (opts.json) {
|
|
456
|
+
console.log(JSON.stringify({ channelId: channel.channelId, personUid: target.personUid, removed: true }, null, 2));
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
console.log(chalk.green(`Removed ${target.displayName} from #${label}.`));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
297
467
|
export function registerChannelsCommand(program) {
|
|
298
468
|
const channels = program
|
|
299
469
|
.command("channels")
|
|
@@ -329,5 +499,19 @@ export function registerChannelsCommand(program) {
|
|
|
329
499
|
.action(async (channelName, opts) => {
|
|
330
500
|
await runChannelsDelete(channelName, opts);
|
|
331
501
|
});
|
|
502
|
+
channels
|
|
503
|
+
.command("add <channel> <member...>")
|
|
504
|
+
.description("Add people or your own bots to a channel, by @handle, email, or prs_/agt_ uid.")
|
|
505
|
+
.option("--json", "Output the per-member results as JSON.")
|
|
506
|
+
.action(async (channelName, members, opts) => {
|
|
507
|
+
await runChannelsAdd(channelName, members, opts);
|
|
508
|
+
});
|
|
509
|
+
channels
|
|
510
|
+
.command("remove <channel> <member>")
|
|
511
|
+
.description("Remove a member or bot from a channel (owner or company admin), or leave it yourself.")
|
|
512
|
+
.option("--json", "Output the result as JSON.")
|
|
513
|
+
.action(async (channelName, member, opts) => {
|
|
514
|
+
await runChannelsRemove(channelName, member, opts);
|
|
515
|
+
});
|
|
332
516
|
}
|
|
333
517
|
//# sourceMappingURL=channels.js.map
|
|
@@ -59,6 +59,8 @@ export interface DaemonCommandDeps {
|
|
|
59
59
|
/** Reads stdin for the one-shot daemon machine login; never reads env or argv credentials. */
|
|
60
60
|
readStdin?: () => Promise<string>;
|
|
61
61
|
windowsUser?: string;
|
|
62
|
+
/** The Windows user's SID; the daemon's Scheduled Task name carries it so each user on a PC has their own task. */
|
|
63
|
+
windowsSid?: string;
|
|
62
64
|
windowsHeadlessLauncher?: string;
|
|
63
65
|
systemUnitDir?: string;
|
|
64
66
|
systemRecordPath?: string;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -24,7 +24,7 @@ import { DEFAULT_SERVICES, SERVICE_DESCRIPTIONS, enabledServices, hqCloudUnits }
|
|
|
24
24
|
import { applyServiceToggle, effectiveSyncOwner, readDaemonConfig, writeDaemonConfig } from "../lib/daemon/config.js";
|
|
25
25
|
import { daemonUnitInstalled } from "../lib/daemon/handoff.js";
|
|
26
26
|
import { adoptLegacyUnits, readAdoptedUnits, restoreLegacyUnits } from "../lib/daemon/migrate.js";
|
|
27
|
-
import {
|
|
27
|
+
import { ROOT_REFUSAL, SYSTEM_UNIT_DIR, WINDOWS_START_WAIT_MS, daemonRootRefusal, installDaemonUnit, installScopeFor, classifyWindowsTask, legacyWindowsTask, readWindowsSid, uninstallDaemonUnit, windowsInteractiveSession, windowsTaskName, } from "../lib/daemon/install.js";
|
|
28
28
|
import { describeLegacyUnit, findLegacyUnits } from "../lib/daemon/legacy.js";
|
|
29
29
|
import { isDaemonProcess, liveDaemonLock, runningDaemonPid } from "../lib/daemon/lock.js";
|
|
30
30
|
import { bundledHqCloud, managedHqCloud, managedHqCloudRoot, readHqCloudCopy, readHqCloudUpdateState } from "../lib/daemon/hq-cloud.js";
|
|
@@ -132,7 +132,10 @@ async function loadDefaults(over) {
|
|
|
132
132
|
existsSync: fs.existsSync,
|
|
133
133
|
listDir: (dir) => fs.readdirSync(dir),
|
|
134
134
|
launchdJobLoaded: (label) => run("launchctl", ["print", `gui/${process.getuid?.() ?? 0}/${label}`]).status === 0,
|
|
135
|
-
|
|
135
|
+
windowsTaskOwner: (task) => classifyWindowsTask(run, task, {
|
|
136
|
+
windowsSid: over.windowsSid ?? readWindowsSid(run),
|
|
137
|
+
windowsUser: over.windowsUser ?? mesh.resolveWindowsUser(env),
|
|
138
|
+
}).state,
|
|
136
139
|
})),
|
|
137
140
|
resolveHqRoot: over.resolveHqRoot ?? ((flag) => resolveDaemonHqRoot(flag, { env, cwd: process.cwd(), home })),
|
|
138
141
|
userHome: over.userHome ?? ((user) => loginHome(user, run)),
|
|
@@ -140,6 +143,7 @@ async function loadDefaults(over) {
|
|
|
140
143
|
sleep: over.sleep,
|
|
141
144
|
interactiveSession: over.interactiveSession,
|
|
142
145
|
windowsUser: over.windowsUser ?? (platform === "win32" ? mesh.resolveWindowsUser(env) : ""),
|
|
146
|
+
windowsSid: over.windowsSid ?? (platform === "win32" ? readWindowsSid(run) : undefined),
|
|
143
147
|
windowsHeadlessLauncher: over.windowsHeadlessLauncher ?? (platform === "win32" ? mesh.resolveWindowsHeadlessLauncher(env) : undefined),
|
|
144
148
|
systemUnitDir: over.systemUnitDir ?? SYSTEM_UNIT_DIR,
|
|
145
149
|
syncModeClient: over.syncModeClient,
|
|
@@ -196,6 +200,7 @@ function hostFor(d, paths, pathEnv) {
|
|
|
196
200
|
uid: d.uid,
|
|
197
201
|
isRoot: d.isRoot,
|
|
198
202
|
windowsUser: d.windowsUser ?? "",
|
|
203
|
+
windowsSid: d.windowsSid,
|
|
199
204
|
windowsHeadlessLauncher: d.windowsHeadlessLauncher,
|
|
200
205
|
systemUnitDir: d.systemUnitDir ?? SYSTEM_UNIT_DIR,
|
|
201
206
|
env,
|
|
@@ -306,6 +311,8 @@ async function runInstall(d, opts) {
|
|
|
306
311
|
runningDaemon: () => liveDaemonLock(paths.lockPath, d.daemonAlive),
|
|
307
312
|
});
|
|
308
313
|
handOver(d, { paths, detected, runAsUser });
|
|
314
|
+
if (result.notice)
|
|
315
|
+
d.out(result.notice);
|
|
309
316
|
if (result.warning)
|
|
310
317
|
d.err(result.warning);
|
|
311
318
|
if (result.kind === "manual") {
|
|
@@ -333,7 +340,7 @@ async function runInstall(d, opts) {
|
|
|
333
340
|
? `Installed ${result.unitPath}, but the daemon did not start within ${seconds} seconds although you are signed in to Windows.`
|
|
334
341
|
: `Installed ${result.unitPath}, but the daemon did not start within ${seconds} seconds, and HQ could not check whether you are signed in to Windows.`,
|
|
335
342
|
"Check it with: hq daemon status",
|
|
336
|
-
`The task's last result: powershell -NoProfile -Command "(Get-ScheduledTaskInfo -TaskName '${
|
|
343
|
+
`The task's last result: powershell -NoProfile -Command "(Get-ScheduledTaskInfo -TaskName '${windowsTaskName({ windowsSid: d.windowsSid, windowsUser: d.windowsUser ?? "" })}').LastTaskResult"`,
|
|
337
344
|
].join("\n"));
|
|
338
345
|
}
|
|
339
346
|
if (!result.running) {
|
|
@@ -386,6 +393,8 @@ async function runUninstall(d, opts) {
|
|
|
386
393
|
}
|
|
387
394
|
const result = uninstallDaemonUnit(hostFor(d, paths, false), { scope: installScopeFor(detected.type) }, { run: d.run });
|
|
388
395
|
d.out(result.removed ? `Removed ${result.unitPath}` : "hq daemon was not installed.");
|
|
396
|
+
if (result.notice)
|
|
397
|
+
d.out(result.notice);
|
|
389
398
|
if (result.warning)
|
|
390
399
|
d.err(result.warning);
|
|
391
400
|
if (opts.restoreLegacy) {
|
|
@@ -869,7 +878,9 @@ async function runMigrate(d, opts) {
|
|
|
869
878
|
platform: d.platform,
|
|
870
879
|
home: d.home,
|
|
871
880
|
systemUnitDir: d.systemUnitDir,
|
|
881
|
+
windowsTaskName: windowsTaskName({ windowsSid: d.windowsSid, windowsUser: d.windowsUser ?? "" }),
|
|
872
882
|
windowsTaskExists: (task) => d.run("schtasks.exe", ["/Query", "/TN", task]).status === 0,
|
|
883
|
+
windowsLegacyTaskIsMine: () => legacyWindowsTask(d.run, { windowsSid: d.windowsSid, windowsUser: d.windowsUser ?? "" }).state === "mine",
|
|
873
884
|
});
|
|
874
885
|
if (!installed) {
|
|
875
886
|
throw new DaemonCommandError("hq daemon is not installed here yet. Run `hq daemon install` first, then `hq daemon migrate --apply`.");
|
|
@@ -893,7 +904,7 @@ async function runMigrate(d, opts) {
|
|
|
893
904
|
if (u.migratable)
|
|
894
905
|
d.out(`Would take over ${u.unitId} from ${describeLegacyUnit(u)}${u.path ? ` (${u.path})` : ""}`);
|
|
895
906
|
else
|
|
896
|
-
d.out(`Leaving ${u.unitId} to ${describeLegacyUnit(u)} (owned by the HQ desktop app)`);
|
|
907
|
+
d.out(`Leaving ${u.unitId} to ${describeLegacyUnit(u)} (${u.notMineReason ?? "owned by the HQ desktop app"})`);
|
|
897
908
|
}
|
|
898
909
|
d.out("Nothing was changed. `hq daemon migrate --apply` takes them over.");
|
|
899
910
|
}
|
|
@@ -299,6 +299,8 @@ export interface UsableApp {
|
|
|
299
299
|
* provider, else `--connection <id>`.
|
|
300
300
|
*/
|
|
301
301
|
selector: string;
|
|
302
|
+
/** The caller created this connection (`viewerAccess.isCreator`). */
|
|
303
|
+
ownedByCaller: boolean;
|
|
302
304
|
}
|
|
303
305
|
export interface UsableAppsResult {
|
|
304
306
|
companyUid: string;
|
|
@@ -529,6 +529,7 @@ export async function fetchUsableApps(token, companyUid) {
|
|
|
529
529
|
status: row.status,
|
|
530
530
|
providerArg,
|
|
531
531
|
selector: shared ? `--connection ${row.id}` : `--provider ${providerArg}`,
|
|
532
|
+
ownedByCaller: row.viewerAccess?.isCreator === true,
|
|
532
533
|
};
|
|
533
534
|
})
|
|
534
535
|
.sort((a, b) => compareNames(a.name, b.name))
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -147,7 +147,7 @@ export declare function checkpointInstruction(worker: Pick<ResolvedLaneWorker, "
|
|
|
147
147
|
export declare function composeWorkerBrief(worker: ResolvedLaneWorker, taskBrief: string, hqRoot: string): string;
|
|
148
148
|
/**
|
|
149
149
|
* A lane with no worker still carries the standing authorization paragraph,
|
|
150
|
-
* the foreground-only rule
|
|
150
|
+
* the foreground-only rule, the temp-home rule and the self-review rule.
|
|
151
151
|
*/
|
|
152
152
|
export declare function composeGeneralBrief(taskBrief: string, hqRoot: string): string;
|
|
153
153
|
export declare const DISPATCH_TERMINAL_REASON: "outpost_dispatch_failed";
|
package/dist/commands/lanes.js
CHANGED
|
@@ -108,7 +108,7 @@ import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
|
|
|
108
108
|
import { ensureSessionHome } from "../lib/lanes/session-relink.js";
|
|
109
109
|
import { defaultProbe, validateProfileLaneModel, validateLaneModel, } from "../lib/lanes/models.js";
|
|
110
110
|
import { readLaneClaudeCredential } from "../lib/lanes/claude-credential.js";
|
|
111
|
-
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
111
|
+
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, selfReviewRule, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
112
112
|
import { handOffInstallHere } from "../lib/daemon/handoff.js";
|
|
113
113
|
export { spawnLaneWorker };
|
|
114
114
|
export function openPullRequestsForProject(hqRoot, metadata) {
|
|
@@ -3041,11 +3041,13 @@ export function composeWorkerBrief(worker, taskBrief, hqRoot) {
|
|
|
3041
3041
|
"",
|
|
3042
3042
|
tempHomeRule(hqRoot),
|
|
3043
3043
|
"",
|
|
3044
|
+
selfReviewRule(hqRoot),
|
|
3045
|
+
"",
|
|
3044
3046
|
].join("\n");
|
|
3045
3047
|
}
|
|
3046
3048
|
/**
|
|
3047
3049
|
* A lane with no worker still carries the standing authorization paragraph,
|
|
3048
|
-
* the foreground-only rule
|
|
3050
|
+
* the foreground-only rule, the temp-home rule and the self-review rule.
|
|
3049
3051
|
*/
|
|
3050
3052
|
export function composeGeneralBrief(taskBrief, hqRoot) {
|
|
3051
3053
|
return [
|
|
@@ -3058,6 +3060,8 @@ export function composeGeneralBrief(taskBrief, hqRoot) {
|
|
|
3058
3060
|
"",
|
|
3059
3061
|
tempHomeRule(hqRoot),
|
|
3060
3062
|
"",
|
|
3063
|
+
selfReviewRule(hqRoot),
|
|
3064
|
+
"",
|
|
3061
3065
|
].join("\n");
|
|
3062
3066
|
}
|
|
3063
3067
|
function parseMaxRuntimeSeconds(raw, workerId) {
|
package/dist/commands/mesh.js
CHANGED
|
@@ -3071,6 +3071,25 @@ export function registerMeshCommand(program, deps = {}) {
|
|
|
3071
3071
|
.description("Report daemon running/MQTT/spool/held/dead-letter/outbox/flush health")
|
|
3072
3072
|
.option("--json", "Print machine-readable JSON")
|
|
3073
3073
|
.action((opts) => wrap(() => runDaemonDoctorCmd(opts))());
|
|
3074
|
+
mesh
|
|
3075
|
+
.command("push-status")
|
|
3076
|
+
.description("Report whether the owner DM push signal is live or stale, with the last event time")
|
|
3077
|
+
.option("--json", "Print machine-readable JSON")
|
|
3078
|
+
.action((opts) => wrap(() => runMeshPushStatus(opts))());
|
|
3079
|
+
}
|
|
3080
|
+
async function runMeshPushStatus(opts) {
|
|
3081
|
+
const { ownerPushStatusForAccount, readOwnerInboxPushState, formatOwnerPushStatus } = await import("../lib/mesh/live/daemon/owner-inbox-push.js");
|
|
3082
|
+
const { describeCognitoTokenSource } = await import("../utils/cognito-session.js");
|
|
3083
|
+
const { actorKind } = describeCognitoTokenSource({ home: os.homedir(), env: process.env });
|
|
3084
|
+
const status = ownerPushStatusForAccount(readOwnerInboxPushState(workMeshRoot(undefined, process.env)), Date.now(), actorKind);
|
|
3085
|
+
if (opts.json) {
|
|
3086
|
+
console.log(JSON.stringify(status, null, 2));
|
|
3087
|
+
}
|
|
3088
|
+
else {
|
|
3089
|
+
console.log(formatOwnerPushStatus(status));
|
|
3090
|
+
}
|
|
3091
|
+
if (status.status === "stale")
|
|
3092
|
+
process.exitCode = 1;
|
|
3074
3093
|
}
|
|
3075
3094
|
async function runDaemonRun(opts = {}) {
|
|
3076
3095
|
const { runMeshDaemon } = await import("../lib/mesh/live/daemon/run.js");
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* hq sources get Fetch one source by id.
|
|
12
12
|
* hq sources channels Print the canonical SOURCE_CHANNELS enum.
|
|
13
13
|
* hq sources entities List entities the caller has access to.
|
|
14
|
+
* hq sources runs Show the local hot-channel run ledger for one day.
|
|
15
|
+
* hq sources sync Run every hot channel now for the signed-in person.
|
|
14
16
|
*
|
|
15
17
|
* Defaults:
|
|
16
18
|
* --entity falls back to .hq/config.json activeCompany (per sync.ts).
|
|
@@ -18,5 +20,92 @@
|
|
|
18
20
|
* --limit 50.
|
|
19
21
|
*/
|
|
20
22
|
import { Command } from "commander";
|
|
23
|
+
import { type RunLedgerAdapterSummary, type RunLedgerEntry, type RunTrigger, type SignalsGeneratedFn } from "../lib/hot-channels/ledger.js";
|
|
24
|
+
import type { HotChannelRegistry } from "../lib/hot-channels/registry.js";
|
|
25
|
+
import { type OnDemandSyncDeps, type OnDemandSyncReport } from "../lib/hot-channels/run.js";
|
|
26
|
+
type ListFormat = "table" | "json";
|
|
27
|
+
export interface RunsOptions {
|
|
28
|
+
today?: boolean;
|
|
29
|
+
date?: string;
|
|
30
|
+
company?: string;
|
|
31
|
+
email?: string;
|
|
32
|
+
format?: ListFormat;
|
|
33
|
+
hqRoot: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RunsReport {
|
|
36
|
+
company: string;
|
|
37
|
+
personKey: string;
|
|
38
|
+
day: string;
|
|
39
|
+
adapters: RunLedgerAdapterSummary[];
|
|
40
|
+
entries: RunLedgerEntry[];
|
|
41
|
+
malformed: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads one day's run ledger (`sources/_runs/@{personKey}/{day}.jsonl` in the
|
|
45
|
+
* company folder of the local HQ tree). The person key is
|
|
46
|
+
* `audienceKey([email])` of the signed-in person. No network calls.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runRuns(options: RunsOptions, now?: Date): Promise<RunsReport>;
|
|
49
|
+
export interface SyncOptions {
|
|
50
|
+
adapter?: string;
|
|
51
|
+
company?: string;
|
|
52
|
+
hqRoot: string;
|
|
53
|
+
json?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface SyncOverrides {
|
|
56
|
+
/** Adapters to run. This build registers none, so the default is empty. */
|
|
57
|
+
registry?: HotChannelRegistry;
|
|
58
|
+
now?: () => Date;
|
|
59
|
+
/** The signals pass; defaults to the local `claude -p` pass under the user's own login. */
|
|
60
|
+
signalsGenerated?: SignalsGeneratedFn;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Network access for one sync: the catalog, the person's usable connections,
|
|
64
|
+
* and tool calls through the HQ gateway. Integration tokens stay server-side;
|
|
65
|
+
* the gateway routes each call by connection id.
|
|
66
|
+
*/
|
|
67
|
+
export declare function gatewaySyncDeps(token: string, companyUid: string): OnDemandSyncDeps;
|
|
68
|
+
export interface CompanyHotChannelSyncInput {
|
|
69
|
+
hqRoot: string;
|
|
70
|
+
company: string;
|
|
71
|
+
adapter?: string;
|
|
72
|
+
registry?: HotChannelRegistry;
|
|
73
|
+
now?: () => Date;
|
|
74
|
+
/** `scheduled` for the bot's hot-signal-sync job; `on-demand` by default. */
|
|
75
|
+
trigger?: RunTrigger;
|
|
76
|
+
catchUp?: boolean;
|
|
77
|
+
signalsGenerated?: SignalsGeneratedFn;
|
|
78
|
+
/** The signed-in person's `prs_` person uid in this company; defaults to a roster lookup by email. */
|
|
79
|
+
resolvePersonUid?: (token: string, companyUid: string, email: string) => Promise<string | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Where the signals-failure alert goes (US-012). Unset = no alert check.
|
|
82
|
+
* The company-channel post defaults to the Slack channel in the company's
|
|
83
|
+
* `settings/signals/preferences.yaml`, through the HQ integrations gateway.
|
|
84
|
+
*/
|
|
85
|
+
signalsAlert?: SignalsAlertSenders;
|
|
86
|
+
}
|
|
87
|
+
export interface SignalsAlertSenders {
|
|
88
|
+
/** The primary alert to the owner; throws when it was not delivered. */
|
|
89
|
+
sendOwnerDm: (text: string) => Promise<void>;
|
|
90
|
+
postCompanyChannel?: (text: string) => Promise<void>;
|
|
91
|
+
log?: (message: string) => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One hot-channel sync for the signed-in person in one company: each adapter
|
|
95
|
+
* once per usable connection. Writes under `companies/{company}/`: landed
|
|
96
|
+
* sources in `sources/`, index rows in `sources/_index/`, cursors in
|
|
97
|
+
* `sources/_cursors/`, one ledger line per adapter in `sources/_runs/`, and
|
|
98
|
+
* signals in `signals/@{key}/`. `hq sources sync` and the bot's scheduled
|
|
99
|
+
* hot-signal-sync job both call it. Only one run per person per company goes
|
|
100
|
+
* at a time: a second one writes a `skipped` ledger line and lands nothing.
|
|
101
|
+
*/
|
|
102
|
+
export declare function runCompanyHotChannelSync(input: CompanyHotChannelSyncInput): Promise<OnDemandSyncReport>;
|
|
103
|
+
/**
|
|
104
|
+
* Run every hot channel for the signed-in person now: each adapter once per
|
|
105
|
+
* usable connection. Writes cursors, landed sources and an `on-demand` ledger
|
|
106
|
+
* line under `companies/{company}/sources/`, and nothing else.
|
|
107
|
+
*/
|
|
108
|
+
export declare function runSync(options: SyncOptions, overrides?: SyncOverrides): Promise<OnDemandSyncReport>;
|
|
21
109
|
export declare function registerSourcesCommand(program: Command): void;
|
|
110
|
+
export {};
|
|
22
111
|
//# sourceMappingURL=sources.d.ts.map
|