@indigoai-us/hq-cli 5.124.0 → 5.126.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.
- package/CHANGELOG.md +149 -0
- package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
- package/assets/bot-workers/setup/skills/your-business.md +270 -0
- package/assets/bot-workers/setup/worker.yaml +189 -58
- package/dist/command-catalog.generated.d.ts +414 -5
- package/dist/command-catalog.generated.js +531 -5
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/bot.d.ts +10 -0
- package/dist/commands/bot.js +6 -0
- package/dist/commands/lanes.d.ts +75 -0
- package/dist/commands/lanes.js +2032 -0
- package/dist/commands/login.js +49 -6
- package/dist/commands/mesh.js +133 -28
- package/dist/commands/onboard-create-identity.d.ts +26 -0
- package/dist/commands/onboard-create-identity.js +23 -0
- package/dist/commands/onboard.js +13 -4
- package/dist/lib/bot/progress.d.ts +6 -0
- package/dist/lib/bot/progress.js +16 -0
- package/dist/lib/bot/runtime/grok.d.ts +30 -7
- package/dist/lib/bot/runtime/grok.js +86 -8
- package/dist/lib/bot/runtime/index.js +15 -8
- package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
- package/dist/lib/bot/runtime/messages-stream.js +49 -3
- package/dist/lib/doctor/checks/work-context.js +4 -2
- package/dist/lib/lanes/accounts.d.ts +43 -0
- package/dist/lib/lanes/accounts.js +128 -0
- package/dist/lib/lanes/config.d.ts +56 -0
- package/dist/lib/lanes/config.js +351 -0
- package/dist/lib/lanes/errors.d.ts +39 -0
- package/dist/lib/lanes/errors.js +72 -0
- package/dist/lib/lanes/index.d.ts +19 -0
- package/dist/lib/lanes/index.js +12 -0
- package/dist/lib/lanes/liveness.d.ts +89 -0
- package/dist/lib/lanes/liveness.js +273 -0
- package/dist/lib/lanes/registry.d.ts +103 -0
- package/dist/lib/lanes/registry.js +247 -0
- package/dist/lib/lanes/store.d.ts +64 -0
- package/dist/lib/lanes/store.js +627 -0
- package/dist/lib/lanes/throttle.d.ts +89 -0
- package/dist/lib/lanes/throttle.js +297 -0
- package/dist/lib/lanes/types.d.ts +193 -0
- package/dist/lib/lanes/types.js +217 -0
- package/dist/lib/mesh/live/backfill-held.d.ts +2 -0
- package/dist/lib/mesh/live/backfill-held.js +8 -4
- package/dist/lib/mesh/live/daemon/board-refresh.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/board-refresh.js +6 -0
- package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/config.js +75 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +21 -1
- package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
- package/dist/lib/mesh/live/daemon/index.js +3 -3
- package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
- package/dist/lib/mesh/live/daemon/install.js +444 -2
- package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/paths.js +2 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +11 -0
- package/dist/lib/mesh/live/daemon/run.js +129 -10
- package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
- package/dist/lib/mesh/live/flush.d.ts +3 -0
- package/dist/lib/mesh/live/flush.js +11 -0
- package/dist/lib/scan-packages/index.js +17 -12
- package/dist/lib/work-context/company.d.ts +8 -0
- package/dist/lib/work-context/company.js +62 -18
- package/dist/lib/work-context/config.d.ts +55 -0
- package/dist/lib/work-context/config.js +333 -67
- package/dist/lib/work-context/contract.d.ts +2 -0
- package/dist/lib/work-context/default-company.d.ts +43 -0
- package/dist/lib/work-context/default-company.js +83 -0
- package/dist/lib/work-context/outbox.d.ts +5 -1
- package/dist/lib/work-context/outbox.js +5 -0
- package/dist/lib/work-context/reconcile.d.ts +4 -0
- package/dist/lib/work-context/reconcile.js +86 -9
- package/dist/lib/work-context/state.d.ts +2 -0
- package/dist/lib/work-context/state.js +14 -0
- package/dist/lib/work-context/windows-acl.d.ts +123 -0
- package/dist/lib/work-context/windows-acl.js +425 -0
- package/dist/utils/pack-contributions.js +5 -4
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { browserLogin, loadCachedTokens, isExpiring } from '@indigoai-us/hq-cloud';
|
|
6
6
|
import { cognitoConfigForLoginProvider } from '../utils/login-provider.js';
|
|
7
|
+
import * as readline from "node:readline/promises";
|
|
8
|
+
import { listActiveMembershipCompanies } from "../lib/mesh/api.js";
|
|
9
|
+
import { setDefaultCompany } from "../lib/work-context/config.js";
|
|
10
|
+
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
11
|
+
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
7
12
|
function peekIdToken(idToken) {
|
|
8
13
|
try {
|
|
9
14
|
const payload = idToken.split('.')[1];
|
|
@@ -24,21 +29,59 @@ export function registerLoginCommand(program) {
|
|
|
24
29
|
.description('Authenticate with HQ via Cognito')
|
|
25
30
|
.option('--provider <provider>', 'OAuth provider to use: google, microsoft, or picker')
|
|
26
31
|
.action(async (options) => {
|
|
32
|
+
let accessToken;
|
|
27
33
|
try {
|
|
28
34
|
const existing = loadCachedTokens();
|
|
29
35
|
if (existing && !isExpiring(existing, 120)) {
|
|
30
36
|
const who = peekIdToken(existing.idToken).email ?? 'cached session';
|
|
31
37
|
console.log(chalk.green(`Already logged in as ${who}. Run \`hq logout\` to switch accounts.`));
|
|
32
|
-
|
|
38
|
+
accessToken = existing.accessToken;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log('Opening browser for authentication...');
|
|
42
|
+
const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
|
|
43
|
+
const who = peekIdToken(tokens.idToken).email ?? 'HQ';
|
|
44
|
+
console.log(chalk.green(`Logged in as ${who}`));
|
|
45
|
+
accessToken = tokens.accessToken;
|
|
33
46
|
}
|
|
34
|
-
console.log('Opening browser for authentication...');
|
|
35
|
-
const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
|
|
36
|
-
const who = peekIdToken(tokens.idToken).email ?? 'HQ';
|
|
37
|
-
console.log(chalk.green(`Logged in as ${who}`));
|
|
38
47
|
}
|
|
39
48
|
catch (error) {
|
|
40
49
|
console.error(chalk.red('Login failed:'), error instanceof Error ? error.message : 'Unknown error');
|
|
41
|
-
process.
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const root = workContextRoot();
|
|
55
|
+
const activation = await ensureDefaultCompany({
|
|
56
|
+
root,
|
|
57
|
+
listMemberships: () => listActiveMembershipCompanies(accessToken),
|
|
58
|
+
log: (message) => console.error(chalk.dim(message)),
|
|
59
|
+
});
|
|
60
|
+
if (activation.status === "needs_choice" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
61
|
+
const choices = activation.memberships
|
|
62
|
+
.map((membership, index) => `${index + 1}. ${membership.slug}`)
|
|
63
|
+
.join("\n");
|
|
64
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
65
|
+
try {
|
|
66
|
+
const answer = await rl.question(`Choose this device's default company:\n${choices}\nSelection: `);
|
|
67
|
+
const selected = activation.memberships[Number.parseInt(answer, 10) - 1];
|
|
68
|
+
if (selected) {
|
|
69
|
+
await setDefaultCompany(selected.slug, {
|
|
70
|
+
root,
|
|
71
|
+
validateMembership: (candidate) => candidate === selected.slug ? { uid: selected.uid } : false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
rl.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (activation.status === "needs_choice") {
|
|
80
|
+
console.error(chalk.yellow("Multiple active memberships. Run `hq mesh context default set <slug>` to choose this device's default company."));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(chalk.yellow('Default-company setup failed:'), error instanceof Error ? error.message : 'Unknown error');
|
|
42
85
|
}
|
|
43
86
|
});
|
|
44
87
|
}
|
package/dist/commands/mesh.js
CHANGED
|
@@ -10,15 +10,17 @@ import { findHqRoot } from "../utils/manifest.js";
|
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { loadCachedTokens, isMachineIdentity } from "../utils/cognito-session.js";
|
|
12
12
|
import * as readline from "node:readline/promises";
|
|
13
|
-
import { STORY_STATUSES, appendThreadEvent,
|
|
13
|
+
import { STORY_STATUSES, appendThreadEvent, ensureProjectThread, eventPayload, listActiveMembershipCompanies, listActiveThreads, patchStoryStatus, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
14
14
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
15
|
-
import { clearDefaultCompany,
|
|
15
|
+
import { clearDefaultCompany, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
16
|
+
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
16
17
|
import { DefaultCompanyUnavailableError, } from "../lib/work-context/errors.js";
|
|
17
18
|
import { isValidSessionId } from "../lib/mesh/live/session-identity.js";
|
|
18
19
|
import { CLI_KIND_TO_SCHEMA, resolveEnqueueSessionId, } from "../lib/mesh/live/index.js";
|
|
19
20
|
import { flushSessionEvents } from "../lib/mesh/live/flush.js";
|
|
20
21
|
import { backfillHeldSessions } from "../lib/mesh/live/backfill-held.js";
|
|
21
22
|
import { createSessionEventsPoster, resolveVaultApiBase, } from "../lib/mesh/live/session-events-client.js";
|
|
23
|
+
const MEMBERSHIP_VALIDATION_TIMEOUT_MS = 5_000;
|
|
22
24
|
import { buildEmitEvidence } from "../lib/mesh/live/emit-evidence.js";
|
|
23
25
|
import { createEmitPoster, buildMeshEmitEvent } from "../lib/mesh/live/emit-client.js";
|
|
24
26
|
import { emitEvents } from "../lib/mesh/live/emit.js";
|
|
@@ -27,7 +29,7 @@ import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
|
27
29
|
import { readLegacyBacklog } from "../lib/mesh/live/emit-replay.js";
|
|
28
30
|
import { resolveMeshEmitMode, writeMeshConfigMode, } from "../lib/mesh/live/daemon/mode.js";
|
|
29
31
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
30
|
-
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, detectPlatform, formatDaemonDoctor, installDaemonService, readDaemonState, runMeshDaemon, uninstallDaemonService, daemonDir, } from "../lib/mesh/live/daemon/index.js";
|
|
32
|
+
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, findAncestorHqRoot, resolveInstallHqRoot, detectPlatform, formatDaemonDoctor, installDaemonService, readDaemonState, runMeshDaemon, uninstallDaemonService, daemonDir, } from "../lib/mesh/live/daemon/index.js";
|
|
31
33
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
32
34
|
import { formatMigrateConfirmation, submitSessionMigration, } from "../lib/work-context/migrate.js";
|
|
33
35
|
import { formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, submitOrganizeDecision, } from "../lib/work-context/organize.js";
|
|
@@ -91,7 +93,10 @@ async function runEvent(verb, opts) {
|
|
|
91
93
|
summary: opts.summary,
|
|
92
94
|
});
|
|
93
95
|
const eventKind = verb === "start" ? "claim" : verb;
|
|
94
|
-
const
|
|
96
|
+
const claimant = eventKind === "claim"
|
|
97
|
+
? await resolveMeshPrincipalUid(token, loadCachedTokens())
|
|
98
|
+
: "";
|
|
99
|
+
const event = await appendThreadEvent(token, company.companyUid, ensured.threadId, eventKind, eventPayload(eventKind, opts, claimant));
|
|
95
100
|
if (opts.json) {
|
|
96
101
|
console.log(JSON.stringify({
|
|
97
102
|
ok: true,
|
|
@@ -153,25 +158,73 @@ async function runContextReconcile(opts) {
|
|
|
153
158
|
let fetchCandidates;
|
|
154
159
|
let validateMembership;
|
|
155
160
|
let offline = Boolean(opts.offline);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
let token;
|
|
162
|
+
try {
|
|
163
|
+
// Offline is outbox-only delivery. It still refreshes a device default's
|
|
164
|
+
// token-backed membership so a revoked default cannot be preflighted.
|
|
165
|
+
token = await requireToken();
|
|
166
|
+
const sessionToken = token;
|
|
167
|
+
validateMembership = async (candidate) => {
|
|
168
|
+
const membership = await new Promise((resolve, reject) => {
|
|
169
|
+
const timer = setTimeout(() => reject(new Error("membership validation timed out")), MEMBERSHIP_VALIDATION_TIMEOUT_MS);
|
|
170
|
+
void resolveActiveMembershipCompany(sessionToken, candidate).then((value) => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
resolve(value);
|
|
173
|
+
}, (error) => {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
reject(error);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
if (!membership || typeof membership.companyUid !== "string" || !membership.companyUid) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return { uid: membership.companyUid };
|
|
182
|
+
};
|
|
183
|
+
if (!offline) {
|
|
184
|
+
deliver = createWorkSessionDeliverer({ token: sessionToken });
|
|
185
|
+
fetchCandidates = createCandidatesFetcher({ token: sessionToken });
|
|
167
186
|
}
|
|
168
|
-
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
if (!offline) {
|
|
169
190
|
offline = true;
|
|
170
191
|
if (!opts.machine) {
|
|
171
192
|
console.error(chalk.yellow("No Cognito session; reconciling offline (outbox only)."));
|
|
172
193
|
}
|
|
173
194
|
}
|
|
174
195
|
}
|
|
196
|
+
if (token) {
|
|
197
|
+
const activation = await ensureDefaultCompany({
|
|
198
|
+
root,
|
|
199
|
+
listMemberships: () => listActiveMembershipCompanies(token),
|
|
200
|
+
log: (message) => console.error(chalk.dim(message)),
|
|
201
|
+
});
|
|
202
|
+
if (activation.status === "needs_choice") {
|
|
203
|
+
const interactive = !opts.machine && Boolean(process.stdin.isTTY && process.stdout.isTTY) && !isMachineIdentity();
|
|
204
|
+
if (interactive) {
|
|
205
|
+
const choices = activation.memberships
|
|
206
|
+
.map((membership, index) => `${index + 1}. ${membership.slug}`)
|
|
207
|
+
.join("\n");
|
|
208
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
209
|
+
try {
|
|
210
|
+
const answer = await rl.question(`Choose this device's default company:\n${choices}\nSelection: `);
|
|
211
|
+
const selected = activation.memberships[Number.parseInt(answer, 10) - 1];
|
|
212
|
+
if (selected) {
|
|
213
|
+
await setDefaultCompany(selected.slug, {
|
|
214
|
+
root,
|
|
215
|
+
validateMembership: (candidate) => candidate === selected.slug ? { uid: selected.uid } : false,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
rl.close();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (!opts.machine) {
|
|
224
|
+
console.error(chalk.yellow("Multiple active memberships. Run `hq mesh context default set <slug>` to choose this device's default company."));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
175
228
|
const outcome = await reconcileObservation(obs, {
|
|
176
229
|
root,
|
|
177
230
|
env: process.env,
|
|
@@ -295,6 +348,7 @@ async function runContextBackfillHeld(opts) {
|
|
|
295
348
|
const parsedLimit = opts.limit ? Number.parseInt(opts.limit, 10) : 0;
|
|
296
349
|
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 0;
|
|
297
350
|
const companyHint = opts.company?.trim() || undefined;
|
|
351
|
+
const useDeviceDefault = opts.useDeviceDefault === true || readDeviceConfig({ root }).repairHeldWithDefault === true;
|
|
298
352
|
// Build the reconcile network seam exactly like `hq mesh context reconcile`.
|
|
299
353
|
// Not needed in --dry-run (reconcile is never invoked then). Explicit types
|
|
300
354
|
// because these are captured in the reconcile closure below (no control-flow
|
|
@@ -331,6 +385,7 @@ async function runContextBackfillHeld(opts) {
|
|
|
331
385
|
// Mirror the reconcile closure's non-forcing hint in the dry-run predictor
|
|
332
386
|
// so `--dry-run` and the real run report the same counts.
|
|
333
387
|
remoteOwnerHint: companyHint || undefined,
|
|
388
|
+
useDeviceDefault,
|
|
334
389
|
reconcile: (obs) => reconcileObservation(
|
|
335
390
|
// Pass --company as a non-forcing hint (remoteOwnerSlug feeds
|
|
336
391
|
// deterministic resolution BELOW the identity-file / device default,
|
|
@@ -342,6 +397,8 @@ async function runContextBackfillHeld(opts) {
|
|
|
342
397
|
fetchCandidates,
|
|
343
398
|
offline,
|
|
344
399
|
validateMembership,
|
|
400
|
+
allowDeviceDefault: useDeviceDefault,
|
|
401
|
+
deviceDefaultConfidence: useDeviceDefault,
|
|
345
402
|
}),
|
|
346
403
|
});
|
|
347
404
|
if (opts.json) {
|
|
@@ -364,16 +421,34 @@ async function runContextBackfillHeld(opts) {
|
|
|
364
421
|
async function runContextDefaultGet(opts) {
|
|
365
422
|
const root = workContextHomeRoot();
|
|
366
423
|
const cfg = readDeviceConfig({ root });
|
|
367
|
-
const current =
|
|
424
|
+
const current = cfg.defaultCompany?.enabled && cfg.defaultCompany.slug
|
|
425
|
+
? cfg.defaultCompany
|
|
426
|
+
: null;
|
|
427
|
+
const memberships = cfg.defaultCompanyMemberships?.memberships ?? [];
|
|
428
|
+
const disabled = isDefaultCompanyDisabled({ root });
|
|
429
|
+
const source = disabled ? "disabled" : current?.source ?? (current ? "chosen" : "unset");
|
|
368
430
|
if (opts.json) {
|
|
369
|
-
console.log(JSON.stringify({
|
|
431
|
+
console.log(JSON.stringify({
|
|
432
|
+
ok: true,
|
|
433
|
+
slug: current?.slug ?? null,
|
|
434
|
+
uid: current?.uid ?? null,
|
|
435
|
+
enabled: current?.enabled ?? false,
|
|
436
|
+
source,
|
|
437
|
+
needsChoice: !current && !disabled && memberships.length > 1,
|
|
438
|
+
memberships,
|
|
439
|
+
repairHeldWithDefault: cfg.repairHeldWithDefault === true,
|
|
440
|
+
}, null, 2));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (disabled) {
|
|
444
|
+
console.log("Default company disabled on this device.");
|
|
370
445
|
return;
|
|
371
446
|
}
|
|
372
447
|
if (!current) {
|
|
373
448
|
console.log("No device default company (default-company mode may be dark).");
|
|
374
449
|
return;
|
|
375
450
|
}
|
|
376
|
-
console.log(`Default company: ${current.slug}${current.uid ? ` (${current.uid})` : ""} enabled=${current.enabled}`);
|
|
451
|
+
console.log(`Default company: ${current.slug}${current.uid ? ` (${current.uid})` : ""} enabled=${current.enabled} source=${source}`);
|
|
377
452
|
}
|
|
378
453
|
async function runContextDefaultSet(slug, opts) {
|
|
379
454
|
const root = workContextHomeRoot();
|
|
@@ -434,11 +509,14 @@ async function runContextDefaultSet(slug, opts) {
|
|
|
434
509
|
return { uid: membership.companyUid };
|
|
435
510
|
},
|
|
436
511
|
});
|
|
512
|
+
const configured = opts.repairHeldWithDefault
|
|
513
|
+
? setRepairHeldWithDefault(true, { root })
|
|
514
|
+
: cfg;
|
|
437
515
|
if (opts.json) {
|
|
438
|
-
console.log(JSON.stringify({ ok: true, config:
|
|
516
|
+
console.log(JSON.stringify({ ok: true, config: configured, migrationWarning }, null, 2));
|
|
439
517
|
return;
|
|
440
518
|
}
|
|
441
|
-
const stored =
|
|
519
|
+
const stored = configured.defaultCompany;
|
|
442
520
|
console.log(`Default company set to ${stored?.slug ?? slug}${stored?.uid ? ` (${stored.uid})` : ""}`);
|
|
443
521
|
if (migrationWarning)
|
|
444
522
|
console.error(chalk.yellow(migrationWarning));
|
|
@@ -1035,7 +1113,8 @@ export function registerMeshCommand(program) {
|
|
|
1035
1113
|
.option("--dry-run", "Report what would be reconciled; write nothing, no network")
|
|
1036
1114
|
.option("--offline", "Reconcile outbox-only (no candidates/register network)")
|
|
1037
1115
|
.option("--limit <n>", "Cap sessions processed (default 0 = no cap)")
|
|
1038
|
-
.option("--company <slug>", "Optional non-forcing company hint
|
|
1116
|
+
.option("--company <slug>", "Optional non-forcing company hint")
|
|
1117
|
+
.option("--use-device-default", "Opt in to historical repair using the device default (lower-confidence attribution)")
|
|
1039
1118
|
.option("--json", "Print machine-readable JSON")
|
|
1040
1119
|
.action((opts) => wrap(() => runContextBackfillHeld(opts))());
|
|
1041
1120
|
context
|
|
@@ -1090,6 +1169,7 @@ export function registerMeshCommand(program) {
|
|
|
1090
1169
|
.description("Set the device default company slug after verifying membership")
|
|
1091
1170
|
.argument("<slug>", "Company slug")
|
|
1092
1171
|
.option("--allow-without-migration", "Deprecated compatibility no-op; selected-company membership is always verified")
|
|
1172
|
+
.option("--repair-held-with-default", "Persist opt-in for lower-confidence historical held-session repair")
|
|
1093
1173
|
.option("--company <slug|uid>", "Deprecated no-op; the selected company is verified directly")
|
|
1094
1174
|
.option("--json", "Print machine-readable JSON")
|
|
1095
1175
|
.action((slug, opts) => wrap(() => runContextDefaultSet(slug, opts))());
|
|
@@ -1150,11 +1230,13 @@ export function registerMeshCommand(program) {
|
|
|
1150
1230
|
daemon
|
|
1151
1231
|
.command("run")
|
|
1152
1232
|
.description("Start the single-instance daemon (pid lock, spool watch, presence)")
|
|
1153
|
-
.
|
|
1233
|
+
.option("--hq-root <path>", "HQ tree root (else HQ_ROOT / persisted trusted roots); the Windows task passes this explicitly")
|
|
1234
|
+
.action((opts) => wrap(() => runDaemonRun(opts))());
|
|
1154
1235
|
daemon
|
|
1155
1236
|
.command("install")
|
|
1156
|
-
.description("Install LaunchAgent (macOS)
|
|
1237
|
+
.description("Install LaunchAgent (macOS), systemd user unit (Linux), or Scheduled Task (Windows)")
|
|
1157
1238
|
.option("--system-user <user>", "Write a root-owned unit template for agent boxes (e.g. ec2-user)")
|
|
1239
|
+
.option("--hq-root <path>", "HQ root to record for the service (else HQ_ROOT, else the nearest HQ root above cwd)")
|
|
1158
1240
|
.option("--json", "Print machine-readable JSON")
|
|
1159
1241
|
.action((opts) => wrap(() => runDaemonInstall(opts))());
|
|
1160
1242
|
daemon
|
|
@@ -1173,19 +1255,24 @@ export function registerMeshCommand(program) {
|
|
|
1173
1255
|
.option("--json", "Print machine-readable JSON")
|
|
1174
1256
|
.action((opts) => wrap(() => runDaemonDoctorCmd(opts))());
|
|
1175
1257
|
}
|
|
1176
|
-
async function runDaemonRun() {
|
|
1177
|
-
const
|
|
1258
|
+
async function runDaemonRun(opts = {}) {
|
|
1259
|
+
const hqRoot = opts.hqRoot?.trim() || undefined;
|
|
1260
|
+
const handle = await runMeshDaemon(hqRoot ? { hqRoot } : {});
|
|
1178
1261
|
// Stay alive until signal handlers call stop + exit.
|
|
1179
1262
|
await new Promise(() => {
|
|
1180
1263
|
void handle;
|
|
1181
1264
|
});
|
|
1182
1265
|
}
|
|
1183
1266
|
async function runDaemonInstall(opts) {
|
|
1267
|
+
// Verified root only: a repo's own .claude/ must never become the daemon's
|
|
1268
|
+
// HQ root (that path is persisted as trusted and used for attribution).
|
|
1269
|
+
const hqRoot = resolveInstallHqRoot({ explicit: opts.hqRoot });
|
|
1184
1270
|
const result = installDaemonService({
|
|
1185
1271
|
platform: detectPlatform(),
|
|
1186
1272
|
paths: buildInstallPaths({
|
|
1187
1273
|
systemUser: opts.systemUser,
|
|
1188
|
-
hqRoot:
|
|
1274
|
+
hqRoot: hqRoot.root,
|
|
1275
|
+
hqRootSource: hqRoot.source,
|
|
1189
1276
|
}),
|
|
1190
1277
|
systemUser: opts.systemUser,
|
|
1191
1278
|
});
|
|
@@ -1196,10 +1283,15 @@ async function runDaemonInstall(opts) {
|
|
|
1196
1283
|
platform: result.platform,
|
|
1197
1284
|
dest: result.dest,
|
|
1198
1285
|
message: result.message,
|
|
1286
|
+
hqRoot: hqRoot.root,
|
|
1287
|
+
hqRootSource: hqRoot.source,
|
|
1288
|
+
...(result.notice ? { notice: result.notice } : {}),
|
|
1199
1289
|
}, null, 2));
|
|
1200
1290
|
return;
|
|
1201
1291
|
}
|
|
1202
1292
|
console.log(result.message);
|
|
1293
|
+
if (result.notice)
|
|
1294
|
+
console.log(result.notice);
|
|
1203
1295
|
}
|
|
1204
1296
|
async function runDaemonUninstall(opts) {
|
|
1205
1297
|
const result = uninstallDaemonService({
|
|
@@ -1233,12 +1325,21 @@ async function runDaemonStatus(opts) {
|
|
|
1233
1325
|
running: result.running ?? false,
|
|
1234
1326
|
dest: result.dest,
|
|
1235
1327
|
message: result.message,
|
|
1328
|
+
...(result.lastResult !== undefined ? { lastResult: result.lastResult } : {}),
|
|
1329
|
+
...(result.lastRunFailed !== undefined ? { lastRunFailed: result.lastRunFailed } : {}),
|
|
1330
|
+
...(result.recordedHqRoot !== undefined ? { hqRoot: result.recordedHqRoot } : {}),
|
|
1331
|
+
...(result.rootValid !== undefined ? { rootValid: result.rootValid } : {}),
|
|
1332
|
+
...(result.notice ? { notice: result.notice } : {}),
|
|
1236
1333
|
mqttState: state?.mqttState,
|
|
1237
1334
|
presenceRefusal,
|
|
1238
1335
|
}, null, 2));
|
|
1239
1336
|
return;
|
|
1240
1337
|
}
|
|
1241
1338
|
console.log(result.message);
|
|
1339
|
+
if (result.recordedHqRoot)
|
|
1340
|
+
console.log(`hq root: ${result.recordedHqRoot}`);
|
|
1341
|
+
if (result.notice)
|
|
1342
|
+
console.log(result.notice);
|
|
1242
1343
|
if (presenceRefusal) {
|
|
1243
1344
|
console.log(`presence refusal: ${presenceRefusal.code} (HTTP ${presenceRefusal.status}); next retry at ${presenceRefusal.nextRetryAt}`);
|
|
1244
1345
|
}
|
|
@@ -1247,7 +1348,11 @@ async function runDaemonStatus(opts) {
|
|
|
1247
1348
|
}
|
|
1248
1349
|
}
|
|
1249
1350
|
async function runDaemonDoctorCmd(opts) {
|
|
1250
|
-
|
|
1351
|
+
// Same predicate as install: a repo's .claude/ is not an HQ root. Doctor
|
|
1352
|
+
// only reads, so an unresolvable root is reported as absent, not fatal.
|
|
1353
|
+
const report = collectDaemonDoctor({
|
|
1354
|
+
hqRoot: process.env.HQ_ROOT?.trim() || findAncestorHqRoot(process.cwd()) || undefined,
|
|
1355
|
+
});
|
|
1251
1356
|
if (opts.json) {
|
|
1252
1357
|
console.log(JSON.stringify({ ok: !report.unhealthy, ...report }, null, 2));
|
|
1253
1358
|
return;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who a new company is created for, when `hq onboard create-company` is run
|
|
3
|
+
* without `--email` / `--person-name`.
|
|
4
|
+
*
|
|
5
|
+
* The signed-in account already carries both on its ID token. Asking the
|
|
6
|
+
* person to type their own name again — which is what the setup bot had to do,
|
|
7
|
+
* because `hq whoami` shows only the email — is a question HQ can answer
|
|
8
|
+
* itself. Explicit flags still win.
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateCompanyIdentity {
|
|
11
|
+
email: string;
|
|
12
|
+
personName: string;
|
|
13
|
+
}
|
|
14
|
+
export type CreateCompanyIdentityResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
identity: CreateCompanyIdentity;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
/** Pure: flags first, then the ID token's claims. */
|
|
22
|
+
export declare function resolveCreateCompanyIdentity(flags: {
|
|
23
|
+
email?: string;
|
|
24
|
+
personName?: string;
|
|
25
|
+
}, claims: Record<string, unknown>): CreateCompanyIdentityResult;
|
|
26
|
+
//# sourceMappingURL=onboard-create-identity.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3
|
+
}
|
|
4
|
+
/** Pure: flags first, then the ID token's claims. */
|
|
5
|
+
export function resolveCreateCompanyIdentity(flags, claims) {
|
|
6
|
+
const email = text(flags.email) || text(claims.email);
|
|
7
|
+
const personName = text(flags.personName) ||
|
|
8
|
+
text(claims.name) ||
|
|
9
|
+
[text(claims.given_name), text(claims.family_name)].filter(Boolean).join(" ");
|
|
10
|
+
const missing = [];
|
|
11
|
+
if (!email)
|
|
12
|
+
missing.push("--email");
|
|
13
|
+
if (!personName)
|
|
14
|
+
missing.push("--person-name");
|
|
15
|
+
if (missing.length > 0) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
message: `Your HQ sign-in does not say ${missing.length === 2 ? "your email or name" : missing[0] === "--email" ? "your email" : "your name"}. Pass ${missing.join(" and ")}.`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { ok: true, identity: { email, personName } };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=onboard-create-identity.js.map
|
package/dist/commands/onboard.js
CHANGED
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import chalk from "chalk";
|
|
22
22
|
import { runOnboardCli, readCheckpoint } from "../lib/onboarding/index.js";
|
|
23
|
-
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
23
|
+
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoIdToken, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
24
24
|
import { createDefaultVaultClient } from "./cloud-provision.js";
|
|
25
25
|
import { detectOnboardingIdentityMismatch } from "./onboard-identity-guard.js";
|
|
26
26
|
import { planOnboardJoin } from "./onboard-join.js";
|
|
27
27
|
import { buildCreateCompanyWarning } from "./onboard-warning.js";
|
|
28
|
+
import { resolveCreateCompanyIdentity } from "./onboard-create-identity.js";
|
|
29
|
+
import { peekIdToken } from "../utils/id-token.js";
|
|
28
30
|
import { addBotToNewCompany, botToAddAfterCreateCompany } from "./onboard-bot-membership.js";
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
32
|
// Command registration
|
|
@@ -40,11 +42,18 @@ export function registerOnboardCommand(program) {
|
|
|
40
42
|
"a same-named company makes a separate one you'd be alone in.")
|
|
41
43
|
.requiredOption("--slug <slug>", "Company slug (used as bucket name suffix)")
|
|
42
44
|
.requiredOption("--name <name>", "Company display name")
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
+
.option("--email <email>", "Your email (default: your HQ sign-in's email; must match it)")
|
|
46
|
+
.option("--person-name <name>", "Your display name (default: the name on your HQ sign-in)")
|
|
45
47
|
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
46
|
-
.action(async (
|
|
48
|
+
.action(async (flags) => {
|
|
47
49
|
try {
|
|
50
|
+
const idToken = await ensureCognitoIdToken();
|
|
51
|
+
const resolved = resolveCreateCompanyIdentity(flags, peekIdToken(idToken));
|
|
52
|
+
if (!resolved.ok) {
|
|
53
|
+
console.error(chalk.red(`\n✗ ${resolved.message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const options = { ...flags, ...resolved.identity };
|
|
48
57
|
console.log(chalk.bold(`\nHQ Onboard — Create Company`));
|
|
49
58
|
console.log(` Company: ${options.name} (${options.slug})`);
|
|
50
59
|
console.log(` Person: ${options.personName} <${options.email}>`);
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* - the final answer is posted only when it adds something: text already
|
|
16
16
|
* posted (or about to be) is never repeated, including Grok's final result
|
|
17
17
|
* that is just every message concatenated
|
|
18
|
+
* - an in-between message that asks the person something (it has a "?") is
|
|
19
|
+
* held, not posted: the model often asks, keeps working, then asks the same
|
|
20
|
+
* thing again in its final answer, and the person read the question twice.
|
|
21
|
+
* It goes out only if the turn ends without a final answer of its own
|
|
18
22
|
*
|
|
19
23
|
* Posts are sent strictly in order. A failed post is logged by the caller's
|
|
20
24
|
* `send` and does not stop the turn.
|
|
@@ -46,6 +50,8 @@ export declare class ProgressPoster {
|
|
|
46
50
|
private readonly workingNoticeMs;
|
|
47
51
|
private readonly workingNoticeText;
|
|
48
52
|
private pending;
|
|
53
|
+
/** In-between questions, held until the turn ends (see the header). */
|
|
54
|
+
private heldQuestions;
|
|
49
55
|
/** Every message the model has said this turn (posted or pending), in order. */
|
|
50
56
|
private readonly said;
|
|
51
57
|
private flushTimer;
|
package/dist/lib/bot/progress.js
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* - the final answer is posted only when it adds something: text already
|
|
16
16
|
* posted (or about to be) is never repeated, including Grok's final result
|
|
17
17
|
* that is just every message concatenated
|
|
18
|
+
* - an in-between message that asks the person something (it has a "?") is
|
|
19
|
+
* held, not posted: the model often asks, keeps working, then asks the same
|
|
20
|
+
* thing again in its final answer, and the person read the question twice.
|
|
21
|
+
* It goes out only if the turn ends without a final answer of its own
|
|
18
22
|
*
|
|
19
23
|
* Posts are sent strictly in order. A failed post is logged by the caller's
|
|
20
24
|
* `send` and does not stop the turn.
|
|
@@ -37,6 +41,8 @@ export class ProgressPoster {
|
|
|
37
41
|
workingNoticeMs;
|
|
38
42
|
workingNoticeText;
|
|
39
43
|
pending = [];
|
|
44
|
+
/** In-between questions, held until the turn ends (see the header). */
|
|
45
|
+
heldQuestions = [];
|
|
40
46
|
/** Every message the model has said this turn (posted or pending), in order. */
|
|
41
47
|
said = [];
|
|
42
48
|
flushTimer = null;
|
|
@@ -75,6 +81,10 @@ export class ProgressPoster {
|
|
|
75
81
|
return;
|
|
76
82
|
this.cancelNotice();
|
|
77
83
|
this.said.push(t);
|
|
84
|
+
if (t.includes("?")) {
|
|
85
|
+
this.heldQuestions.push(t);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
this.pending.push(t);
|
|
79
89
|
this.scheduleFlush();
|
|
80
90
|
}
|
|
@@ -97,8 +107,12 @@ export class ProgressPoster {
|
|
|
97
107
|
this.cancelNotice();
|
|
98
108
|
this.cancelFlush();
|
|
99
109
|
const final = finalText.trim();
|
|
110
|
+
const held = this.heldQuestions;
|
|
111
|
+
this.heldQuestions = [];
|
|
100
112
|
if (final && !this.repeatsSaid(final))
|
|
101
113
|
this.pending.push(final);
|
|
114
|
+
else if (held.length > 0)
|
|
115
|
+
this.pending.push(...held);
|
|
102
116
|
if (this.pending.length === 0 && this.delivered === 0 && !final)
|
|
103
117
|
this.pending.push("(no reply)");
|
|
104
118
|
this.flushNow();
|
|
@@ -114,6 +128,8 @@ export class ProgressPoster {
|
|
|
114
128
|
this.finished = true;
|
|
115
129
|
this.cancelNotice();
|
|
116
130
|
this.cancelFlush();
|
|
131
|
+
this.pending.push(...this.heldQuestions);
|
|
132
|
+
this.heldQuestions = [];
|
|
117
133
|
this.flushNow();
|
|
118
134
|
return this.chain;
|
|
119
135
|
}
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grok CLI headless adapter.
|
|
3
3
|
*
|
|
4
|
-
* grok -p <prompt> --output-format streaming
|
|
4
|
+
* grok -p <prompt> --output-format <streaming format> --always-approve
|
|
5
5
|
* --no-subagents --cwd <cwd> [-m m] [-r <sessionId>]
|
|
6
6
|
*
|
|
7
|
-
* stdout is NDJSON
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* stdout is NDJSON. Older grok (before 0.2.56) wrote Anthropic Messages
|
|
8
|
+
* shapes under `--output-format streaming-messages-json` (see
|
|
9
|
+
* messages-stream.ts). grok 0.2.56 dropped that value and emits token
|
|
10
|
+
* events under `streaming-json`:
|
|
11
|
+
*
|
|
12
|
+
* {"type":"thought","data":…}
|
|
13
|
+
* {"type":"text","data":"Hi"}
|
|
14
|
+
* {"type":"end","stopReason":"EndTurn","sessionId":…}
|
|
15
|
+
*
|
|
16
|
+
* The format is probed from `grok --help` once per process (and cached).
|
|
17
|
+
* If an older grok still lists `streaming-messages-json`, that is used.
|
|
18
|
+
* If the probe misses and grok rejects the old value, the turn retries
|
|
19
|
+
* with `streaming-json`. The older `{ text, sessionId }` envelope is
|
|
20
|
+
* still accepted.
|
|
12
21
|
*/
|
|
13
22
|
import { type BotRuntime, type RuntimeTurnInput } from "./types.js";
|
|
23
|
+
export declare const GROK_OUTPUT_FORMAT_LEGACY = "streaming-messages-json";
|
|
24
|
+
export declare const GROK_OUTPUT_FORMAT_STREAMING_JSON = "streaming-json";
|
|
25
|
+
export declare function resetGrokOutputFormatCache(): void;
|
|
26
|
+
/** Pick the streaming `--output-format` grok's `--help` says it accepts. */
|
|
27
|
+
export declare function grokOutputFormatFromHelp(helpText: string): string;
|
|
28
|
+
/** Probe `grok --help` once and cache the streaming format. */
|
|
29
|
+
export declare function probeGrokOutputFormat(readHelp?: () => string): string;
|
|
30
|
+
/**
|
|
31
|
+
* When grok 0.2.56 rejects the pre-0.2.56 flag, rewrite argv to
|
|
32
|
+
* `streaming-json` and remember that for later turns.
|
|
33
|
+
*/
|
|
34
|
+
export declare function grokRetryArgsAfterInvalidOutputFormat(args: string[], stderr: string): string[] | undefined;
|
|
14
35
|
export declare function grokPrompt(input: RuntimeTurnInput): string;
|
|
15
|
-
export declare function createGrokRuntime(
|
|
36
|
+
export declare function createGrokRuntime(opts?: {
|
|
37
|
+
grokHelp?: string;
|
|
38
|
+
}): BotRuntime;
|
|
16
39
|
//# sourceMappingURL=grok.d.ts.map
|