@indigoai-us/hq-cli 5.224.0 → 5.226.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 +46 -1
- package/dist/command-catalog.generated.d.ts +94 -0
- package/dist/command-catalog.generated.js +120 -0
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +6 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +17 -2
- package/dist/commands/cloud.d.ts +10 -0
- package/dist/commands/cloud.js +52 -47
- package/dist/commands/lanes.d.ts +5 -0
- package/dist/commands/lanes.js +498 -312
- package/dist/commands/staff/reset-test-account.d.ts +110 -0
- package/dist/commands/staff/reset-test-account.js +268 -0
- package/dist/commands/staff/test-account.d.ts +51 -0
- package/dist/commands/staff/test-account.js +196 -0
- package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +24 -0
- package/dist/lib/bot/inbox-poll-backoff-flag.js +132 -0
- package/dist/lib/bot/run.d.ts +3 -1
- package/dist/lib/bot/run.js +89 -11
- package/dist/lib/doctor/client-health-flag-token.d.ts +15 -0
- package/dist/lib/doctor/client-health-flag-token.js +66 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +1 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.js +16 -5
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +1 -0
- package/dist/lib/doctor/client-health-sentry-flag.js +17 -6
- package/dist/lib/lanes/dropbox.d.ts +9 -0
- package/dist/lib/lanes/dropbox.js +70 -1
- package/dist/lib/lanes/reconcile.d.ts +2 -0
- package/dist/lib/lanes/reconcile.js +12 -0
- package/dist/lib/lanes/watcher-main.js +16 -0
- package/dist/lib/lanes/watcher.d.ts +4 -1
- package/dist/lib/lanes/watcher.js +77 -3
- package/dist/utils/cognito-session.d.ts +8 -0
- package/dist/utils/cognito-session.js +198 -3
- package/dist/utils/upload-author.d.ts +15 -0
- package/dist/utils/upload-author.js +39 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.226.0] — 2026-09-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq staff reset-test-account <email>` wipes a designated test account through
|
|
10
|
+
the hq-pro admin API. Defaults to a dry-run that lists what would be deleted;
|
|
11
|
+
`--execute` prompts for the typed email confirmation before wiping, and
|
|
12
|
+
`--yes` skips the prompt. Refuses to run for callers whose stored email is
|
|
13
|
+
not on the staff list.
|
|
14
|
+
- `hq staff test-account add|remove|list` manages the designated-test-account
|
|
15
|
+
registry through the same admin API. `add` and `remove` require staff
|
|
16
|
+
callers; `list` returns the current registry entries.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `hq lanes message` now probes the worker PID and start time before queueing.
|
|
21
|
+
Watcher and reconcile recover accepted inbox messages through the same
|
|
22
|
+
resume path after classifying an exited worker; undrained messages on done,
|
|
23
|
+
killed, retired, or superseded lanes are archived as undeliverable.
|
|
24
|
+
- Failed queued-message recovery restores unaccepted claims to the inbox and
|
|
25
|
+
records the failure. Recovery also sees leftover draining and inflight files,
|
|
26
|
+
and synchronous classification shares watcher ownership to prevent duplicate
|
|
27
|
+
repair and resume work.
|
|
28
|
+
- `hq agent enroll --name <bot>` now works on a computer where a person is signed in. It enrolls into `~/.hq-agent/<bot>/` and refuses only if that folder already holds an agent. Before, it refused because of the person's login and told you to pass `--name`, which you already had. Enrolling with no name on such a computer (the default local folder) had the same bug.
|
|
29
|
+
- Pushes made as an agent now record the agent as the uploader. Before, on a computer where a person was also signed in, the files were stamped with the person's email and id.
|
|
30
|
+
- When the server refuses some paths in a push (for example, another member's file in a private folder `foo/`), the warning now says the server refused them, in `hq sync push`, `hq sync now` and `hq sync push --all`. Before, it said the files were outside your write access and to ask an admin for a grant.
|
|
31
|
+
|
|
32
|
+
## [5.225.0] — 2026-09-26
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Local bots can use the default-off `agents.inbox-poll-backoff-local-cli`
|
|
37
|
+
gate to increase idle inbox polling from 2 seconds to a capped 30-second
|
|
38
|
+
backoff. New messages and completed turns reset the cadence.
|
|
39
|
+
|
|
5
40
|
## [5.224.0] — 2026-09-26
|
|
6
41
|
|
|
7
42
|
### Added
|
|
@@ -12,7 +47,13 @@
|
|
|
12
47
|
|
|
13
48
|
### Fixed
|
|
14
49
|
|
|
15
|
-
- When `hq daemon pin-hq-cloud` names a version that is not installed yet, the
|
|
50
|
+
- When `hq daemon pin-hq-cloud` names a version that is not installed yet, the
|
|
51
|
+
daemon keeps running the newest installed hq-cloud instead of falling back
|
|
52
|
+
to the older copy bundled with the CLI, and logs a warning naming the
|
|
53
|
+
missing version. The updater then installs the pinned version and the
|
|
54
|
+
daemon switches to it. Before, a pin that did not match the installed copy
|
|
55
|
+
silently ran the bundled copy, which on one outpost meant a months-old sync
|
|
56
|
+
runner without recent fixes.
|
|
16
57
|
|
|
17
58
|
## [5.223.0] — 2026-09-26
|
|
18
59
|
|
|
@@ -26,6 +67,10 @@
|
|
|
26
67
|
shares one local account probe across matching lanes per poll. Quota and
|
|
27
68
|
model failures keep their existing account-selection path and reason.
|
|
28
69
|
|
|
70
|
+
- Client-health flag checks refresh expired person identity and use machine
|
|
71
|
+
identity when available. Identity refresh has a separate bound, and failed
|
|
72
|
+
lookups keep the existing doctor fallback path.
|
|
73
|
+
|
|
29
74
|
## [5.222.0] — 2026-09-26
|
|
30
75
|
|
|
31
76
|
- Checkpoint ontology, behind `checkpoint.ontology.multi-writer` (off by
|
|
@@ -7961,6 +7961,22 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
7961
7961
|
readonly description: "JSON on stdout (always)";
|
|
7962
7962
|
}];
|
|
7963
7963
|
readonly subcommands: readonly [];
|
|
7964
|
+
}, {
|
|
7965
|
+
readonly name: "_recover-queued-messages";
|
|
7966
|
+
readonly description: "";
|
|
7967
|
+
readonly aliases: readonly [];
|
|
7968
|
+
readonly hidden: true;
|
|
7969
|
+
readonly usage: "[options] <lane-id>";
|
|
7970
|
+
readonly arguments: readonly [{
|
|
7971
|
+
readonly name: "lane-id";
|
|
7972
|
+
readonly required: true;
|
|
7973
|
+
readonly variadic: false;
|
|
7974
|
+
}];
|
|
7975
|
+
readonly options: readonly [{
|
|
7976
|
+
readonly flags: "--json";
|
|
7977
|
+
readonly description: "JSON on stdout (always)";
|
|
7978
|
+
}];
|
|
7979
|
+
readonly subcommands: readonly [];
|
|
7964
7980
|
}, {
|
|
7965
7981
|
readonly name: "_monitor-message";
|
|
7966
7982
|
readonly description: "";
|
|
@@ -9490,5 +9506,83 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9490
9506
|
}];
|
|
9491
9507
|
readonly subcommands: readonly [];
|
|
9492
9508
|
}];
|
|
9509
|
+
}, {
|
|
9510
|
+
readonly name: "staff";
|
|
9511
|
+
readonly description: "Staff-only commands (super-admin required)";
|
|
9512
|
+
readonly aliases: readonly [];
|
|
9513
|
+
readonly hidden: false;
|
|
9514
|
+
readonly usage: "[options] [command]";
|
|
9515
|
+
readonly arguments: readonly [];
|
|
9516
|
+
readonly options: readonly [];
|
|
9517
|
+
readonly subcommands: readonly [{
|
|
9518
|
+
readonly name: "reset-test-account";
|
|
9519
|
+
readonly description: "Reset a designated real-email test account (dry-run by default). Wraps POST /v1/admin/test-account/reset — super-admin only.";
|
|
9520
|
+
readonly aliases: readonly [];
|
|
9521
|
+
readonly hidden: false;
|
|
9522
|
+
readonly usage: "[options] <email>";
|
|
9523
|
+
readonly arguments: readonly [{
|
|
9524
|
+
readonly name: "email";
|
|
9525
|
+
readonly required: true;
|
|
9526
|
+
readonly variadic: false;
|
|
9527
|
+
}];
|
|
9528
|
+
readonly options: readonly [{
|
|
9529
|
+
readonly flags: "--execute";
|
|
9530
|
+
readonly description: "Actually delete artifacts. Prompts for a typed-email confirmation.";
|
|
9531
|
+
}, {
|
|
9532
|
+
readonly flags: "-y, --yes";
|
|
9533
|
+
readonly description: "Skip the typed-email confirmation prompt (still sends confirmEmail).";
|
|
9534
|
+
}];
|
|
9535
|
+
readonly subcommands: readonly [];
|
|
9536
|
+
}, {
|
|
9537
|
+
readonly name: "test-account";
|
|
9538
|
+
readonly description: "Manage the designated test-account registry (super-admin only). Wraps /v1/admin/test-account-registry.";
|
|
9539
|
+
readonly aliases: readonly [];
|
|
9540
|
+
readonly hidden: false;
|
|
9541
|
+
readonly usage: "[options] [command]";
|
|
9542
|
+
readonly arguments: readonly [];
|
|
9543
|
+
readonly options: readonly [];
|
|
9544
|
+
readonly subcommands: readonly [{
|
|
9545
|
+
readonly name: "add";
|
|
9546
|
+
readonly description: "Add an email to the registry so `hq staff reset-test-account` will sweep it.";
|
|
9547
|
+
readonly aliases: readonly [];
|
|
9548
|
+
readonly hidden: false;
|
|
9549
|
+
readonly usage: "[options] <email>";
|
|
9550
|
+
readonly arguments: readonly [{
|
|
9551
|
+
readonly name: "email";
|
|
9552
|
+
readonly required: true;
|
|
9553
|
+
readonly variadic: false;
|
|
9554
|
+
}];
|
|
9555
|
+
readonly options: readonly [{
|
|
9556
|
+
readonly flags: "--note <text>";
|
|
9557
|
+
readonly description: "Short reason recorded on the row";
|
|
9558
|
+
}];
|
|
9559
|
+
readonly subcommands: readonly [];
|
|
9560
|
+
}, {
|
|
9561
|
+
readonly name: "remove";
|
|
9562
|
+
readonly description: "Soft-delete a registry entry (row stays with active: false).";
|
|
9563
|
+
readonly aliases: readonly [];
|
|
9564
|
+
readonly hidden: false;
|
|
9565
|
+
readonly usage: "[options] <email>";
|
|
9566
|
+
readonly arguments: readonly [{
|
|
9567
|
+
readonly name: "email";
|
|
9568
|
+
readonly required: true;
|
|
9569
|
+
readonly variadic: false;
|
|
9570
|
+
}];
|
|
9571
|
+
readonly options: readonly [];
|
|
9572
|
+
readonly subcommands: readonly [];
|
|
9573
|
+
}, {
|
|
9574
|
+
readonly name: "list";
|
|
9575
|
+
readonly description: "List active registry entries.";
|
|
9576
|
+
readonly aliases: readonly [];
|
|
9577
|
+
readonly hidden: false;
|
|
9578
|
+
readonly usage: "[options]";
|
|
9579
|
+
readonly arguments: readonly [];
|
|
9580
|
+
readonly options: readonly [{
|
|
9581
|
+
readonly flags: "--include-inactive";
|
|
9582
|
+
readonly description: "Also list soft-deleted rows (active: false).";
|
|
9583
|
+
}];
|
|
9584
|
+
readonly subcommands: readonly [];
|
|
9585
|
+
}];
|
|
9586
|
+
}];
|
|
9493
9587
|
}];
|
|
9494
9588
|
//# sourceMappingURL=command-catalog.generated.d.ts.map
|
|
@@ -10296,6 +10296,27 @@ export const COMMAND_CATALOG = [
|
|
|
10296
10296
|
],
|
|
10297
10297
|
"subcommands": []
|
|
10298
10298
|
},
|
|
10299
|
+
{
|
|
10300
|
+
"name": "_recover-queued-messages",
|
|
10301
|
+
"description": "",
|
|
10302
|
+
"aliases": [],
|
|
10303
|
+
"hidden": true,
|
|
10304
|
+
"usage": "[options] <lane-id>",
|
|
10305
|
+
"arguments": [
|
|
10306
|
+
{
|
|
10307
|
+
"name": "lane-id",
|
|
10308
|
+
"required": true,
|
|
10309
|
+
"variadic": false
|
|
10310
|
+
}
|
|
10311
|
+
],
|
|
10312
|
+
"options": [
|
|
10313
|
+
{
|
|
10314
|
+
"flags": "--json",
|
|
10315
|
+
"description": "JSON on stdout (always)"
|
|
10316
|
+
}
|
|
10317
|
+
],
|
|
10318
|
+
"subcommands": []
|
|
10319
|
+
},
|
|
10299
10320
|
{
|
|
10300
10321
|
"name": "_monitor-message",
|
|
10301
10322
|
"description": "",
|
|
@@ -12273,6 +12294,105 @@ export const COMMAND_CATALOG = [
|
|
|
12273
12294
|
"subcommands": []
|
|
12274
12295
|
}
|
|
12275
12296
|
]
|
|
12297
|
+
},
|
|
12298
|
+
{
|
|
12299
|
+
"name": "staff",
|
|
12300
|
+
"description": "Staff-only commands (super-admin required)",
|
|
12301
|
+
"aliases": [],
|
|
12302
|
+
"hidden": false,
|
|
12303
|
+
"usage": "[options] [command]",
|
|
12304
|
+
"arguments": [],
|
|
12305
|
+
"options": [],
|
|
12306
|
+
"subcommands": [
|
|
12307
|
+
{
|
|
12308
|
+
"name": "reset-test-account",
|
|
12309
|
+
"description": "Reset a designated real-email test account (dry-run by default). Wraps POST /v1/admin/test-account/reset — super-admin only.",
|
|
12310
|
+
"aliases": [],
|
|
12311
|
+
"hidden": false,
|
|
12312
|
+
"usage": "[options] <email>",
|
|
12313
|
+
"arguments": [
|
|
12314
|
+
{
|
|
12315
|
+
"name": "email",
|
|
12316
|
+
"required": true,
|
|
12317
|
+
"variadic": false
|
|
12318
|
+
}
|
|
12319
|
+
],
|
|
12320
|
+
"options": [
|
|
12321
|
+
{
|
|
12322
|
+
"flags": "--execute",
|
|
12323
|
+
"description": "Actually delete artifacts. Prompts for a typed-email confirmation."
|
|
12324
|
+
},
|
|
12325
|
+
{
|
|
12326
|
+
"flags": "-y, --yes",
|
|
12327
|
+
"description": "Skip the typed-email confirmation prompt (still sends confirmEmail)."
|
|
12328
|
+
}
|
|
12329
|
+
],
|
|
12330
|
+
"subcommands": []
|
|
12331
|
+
},
|
|
12332
|
+
{
|
|
12333
|
+
"name": "test-account",
|
|
12334
|
+
"description": "Manage the designated test-account registry (super-admin only). Wraps /v1/admin/test-account-registry.",
|
|
12335
|
+
"aliases": [],
|
|
12336
|
+
"hidden": false,
|
|
12337
|
+
"usage": "[options] [command]",
|
|
12338
|
+
"arguments": [],
|
|
12339
|
+
"options": [],
|
|
12340
|
+
"subcommands": [
|
|
12341
|
+
{
|
|
12342
|
+
"name": "add",
|
|
12343
|
+
"description": "Add an email to the registry so `hq staff reset-test-account` will sweep it.",
|
|
12344
|
+
"aliases": [],
|
|
12345
|
+
"hidden": false,
|
|
12346
|
+
"usage": "[options] <email>",
|
|
12347
|
+
"arguments": [
|
|
12348
|
+
{
|
|
12349
|
+
"name": "email",
|
|
12350
|
+
"required": true,
|
|
12351
|
+
"variadic": false
|
|
12352
|
+
}
|
|
12353
|
+
],
|
|
12354
|
+
"options": [
|
|
12355
|
+
{
|
|
12356
|
+
"flags": "--note <text>",
|
|
12357
|
+
"description": "Short reason recorded on the row"
|
|
12358
|
+
}
|
|
12359
|
+
],
|
|
12360
|
+
"subcommands": []
|
|
12361
|
+
},
|
|
12362
|
+
{
|
|
12363
|
+
"name": "remove",
|
|
12364
|
+
"description": "Soft-delete a registry entry (row stays with active: false).",
|
|
12365
|
+
"aliases": [],
|
|
12366
|
+
"hidden": false,
|
|
12367
|
+
"usage": "[options] <email>",
|
|
12368
|
+
"arguments": [
|
|
12369
|
+
{
|
|
12370
|
+
"name": "email",
|
|
12371
|
+
"required": true,
|
|
12372
|
+
"variadic": false
|
|
12373
|
+
}
|
|
12374
|
+
],
|
|
12375
|
+
"options": [],
|
|
12376
|
+
"subcommands": []
|
|
12377
|
+
},
|
|
12378
|
+
{
|
|
12379
|
+
"name": "list",
|
|
12380
|
+
"description": "List active registry entries.",
|
|
12381
|
+
"aliases": [],
|
|
12382
|
+
"hidden": false,
|
|
12383
|
+
"usage": "[options]",
|
|
12384
|
+
"arguments": [],
|
|
12385
|
+
"options": [
|
|
12386
|
+
{
|
|
12387
|
+
"flags": "--include-inactive",
|
|
12388
|
+
"description": "Also list soft-deleted rows (active: false)."
|
|
12389
|
+
}
|
|
12390
|
+
],
|
|
12391
|
+
"subcommands": []
|
|
12392
|
+
}
|
|
12393
|
+
]
|
|
12394
|
+
}
|
|
12395
|
+
]
|
|
12276
12396
|
}
|
|
12277
12397
|
];
|
|
12278
12398
|
//# sourceMappingURL=command-catalog.generated.js.map
|
|
@@ -432,6 +432,24 @@ export declare const REGISTRATION_PLAN: readonly [{
|
|
|
432
432
|
readonly parent: "program";
|
|
433
433
|
readonly module: "./commands/report.js";
|
|
434
434
|
readonly exportName: "registerReportCommand";
|
|
435
|
+
}, {
|
|
436
|
+
readonly type: "group";
|
|
437
|
+
readonly root: "staff";
|
|
438
|
+
readonly id: "staff";
|
|
439
|
+
readonly name: "staff";
|
|
440
|
+
readonly description: "Staff-only commands (super-admin required)";
|
|
441
|
+
}, {
|
|
442
|
+
readonly type: "registrar";
|
|
443
|
+
readonly root: "staff";
|
|
444
|
+
readonly parent: "staff";
|
|
445
|
+
readonly module: "./commands/staff/reset-test-account.js";
|
|
446
|
+
readonly exportName: "registerResetTestAccountCommand";
|
|
447
|
+
}, {
|
|
448
|
+
readonly type: "registrar";
|
|
449
|
+
readonly root: "staff";
|
|
450
|
+
readonly parent: "staff";
|
|
451
|
+
readonly module: "./commands/staff/test-account.js";
|
|
452
|
+
readonly exportName: "registerTestAccountCommand";
|
|
435
453
|
}];
|
|
436
454
|
export declare function registerAllCommands(program: Command): Promise<void>;
|
|
437
455
|
export declare function registerCommandRoot(program: Command, root: string): Promise<boolean>;
|
|
@@ -74,6 +74,12 @@ export const REGISTRATION_PLAN = [
|
|
|
74
74
|
{ type: "registrar", root: "daemon", parent: "program", module: "./commands/daemon.js", exportName: "registerDaemonCommand", hidden: true },
|
|
75
75
|
{ type: "registrar", root: "bot", parent: "program", module: "./commands/bot.js", exportName: "registerBotCommand" },
|
|
76
76
|
{ type: "registrar", root: "report", parent: "program", module: "./commands/report.js", exportName: "registerReportCommand" },
|
|
77
|
+
// onboarding-test-accounts US-007 — super-admin staff CLI wrappers over the
|
|
78
|
+
// hq-pro admin surface. New wrappers land under this group; the reset
|
|
79
|
+
// command mirrors POST /v1/admin/test-account/reset.
|
|
80
|
+
{ type: "group", root: "staff", id: "staff", name: "staff", description: "Staff-only commands (super-admin required)" },
|
|
81
|
+
{ type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/reset-test-account.js", exportName: "registerResetTestAccountCommand" },
|
|
82
|
+
{ type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/test-account.js", exportName: "registerTestAccountCommand" },
|
|
77
83
|
];
|
|
78
84
|
async function executePlan(program, steps) {
|
|
79
85
|
const parents = new Map([["program", program]]);
|
|
@@ -87,6 +87,8 @@ export interface EnrollDeps {
|
|
|
87
87
|
env?: NodeJS.ProcessEnv;
|
|
88
88
|
hostInfo?: HostInfo;
|
|
89
89
|
generateKeyPair?: typeof generateHostKeyPair;
|
|
90
|
+
/** Home directory the enroll target resolves under (defaults to os.homedir()). */
|
|
91
|
+
home?: string;
|
|
90
92
|
}
|
|
91
93
|
export interface EnrollOptions {
|
|
92
94
|
code: string;
|
|
@@ -59,6 +59,17 @@ export function detectExistingIdentity(paths, env = process.env) {
|
|
|
59
59
|
}
|
|
60
60
|
return { kind: "none" };
|
|
61
61
|
}
|
|
62
|
+
/** Machine identity already enrolled in a local agent directory, if any. */
|
|
63
|
+
function existingLocalIdentity(paths) {
|
|
64
|
+
if (!machineCredsFileExists(paths))
|
|
65
|
+
return { kind: "none" };
|
|
66
|
+
const existing = readExternalMachineCreds(paths);
|
|
67
|
+
return {
|
|
68
|
+
kind: "machine",
|
|
69
|
+
file: paths.machineCredsPath,
|
|
70
|
+
...(existing ? { agentUid: existing.entityUid } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
62
73
|
export function refusalMessage(existing) {
|
|
63
74
|
switch (existing.kind) {
|
|
64
75
|
case "none":
|
|
@@ -221,7 +232,7 @@ export async function enrollHost(opts, deps = {}) {
|
|
|
221
232
|
const env = deps.env ?? process.env;
|
|
222
233
|
const target = deps.paths
|
|
223
234
|
? { paths: deps.paths, local: false }
|
|
224
|
-
: resolveEnrollTarget({ name: opts.name, replace: opts.replace }, env);
|
|
235
|
+
: resolveEnrollTarget({ name: opts.name, replace: opts.replace }, env, deps.home);
|
|
225
236
|
const paths = target.paths;
|
|
226
237
|
const doFetch = deps.fetch ?? fetch;
|
|
227
238
|
const apiBaseUrl = (opts.apiBaseUrl ?? env.HQ_VAULT_API_URL ?? DEFAULT_VAULT_API_URL).replace(/\/+$/, "");
|
|
@@ -229,7 +240,11 @@ export async function enrollHost(opts, deps = {}) {
|
|
|
229
240
|
if (!isWellFormedEnrollmentCode(code)) {
|
|
230
241
|
throw new EnrollError(`Enrollment code must be 24 base32 characters (shown as XXXX-XXXX-XXXX-XXXX-XXXX-XXXX); got ${code.length}.`);
|
|
231
242
|
}
|
|
232
|
-
|
|
243
|
+
// A local target sits in its own ~/.hq-agent/<name>/ beside the person's
|
|
244
|
+
// login by design, so only an identity already in that directory blocks it.
|
|
245
|
+
const existing = target.local
|
|
246
|
+
? existingLocalIdentity(paths)
|
|
247
|
+
: detectExistingIdentity(paths, env);
|
|
233
248
|
const refusal = refusalMessage(existing);
|
|
234
249
|
if (refusal && !opts.replace)
|
|
235
250
|
throw new EnrollError(refusal);
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -61,6 +61,14 @@ export declare function scopeExcludedWarning(count: number): string | null;
|
|
|
61
61
|
* dropped files — helps debug ACL vs scope-excluded mismatches (feedback_7e9c535e).
|
|
62
62
|
*/
|
|
63
63
|
export declare function scopeExcludedPrefixDetail(prefixSet: readonly string[] | undefined): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Every warning line for a push that dropped files, shared by `push`, `now`
|
|
66
|
+
* and `push --all`. With a client prefix scope the files fell outside the
|
|
67
|
+
* caller's write prefixes; without one (prefixSet undefined) the server
|
|
68
|
+
* refused them path by path — typically another member's child in a private
|
|
69
|
+
* folder foo/ — and grant advice would point the reader the wrong way.
|
|
70
|
+
*/
|
|
71
|
+
export declare function scopeExcludedLines(count: number, prefixSet: readonly string[] | undefined): string[];
|
|
64
72
|
/**
|
|
65
73
|
* Bound foreground waits for a watcher/manual sync that currently owns the
|
|
66
74
|
* per-root operation lock. The cloud engine reads this environment value on
|
|
@@ -231,6 +239,8 @@ export interface PushAllOptions {
|
|
|
231
239
|
export interface PushAllRow {
|
|
232
240
|
slug: string;
|
|
233
241
|
result?: ShareCallResult;
|
|
242
|
+
/** Client write-prefix scope this push ran under; undefined = none applied. */
|
|
243
|
+
prefixSet?: readonly string[];
|
|
234
244
|
error?: string;
|
|
235
245
|
}
|
|
236
246
|
export interface PushAllResult {
|
package/dist/commands/cloud.js
CHANGED
|
@@ -16,8 +16,9 @@ import { isAccessDeniedError, accessDeniedKeyOf, formatAccessDenied, } from "../
|
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import * as fs from "fs";
|
|
18
18
|
import * as path from "path";
|
|
19
|
-
import { share, sync, getStateDir, listJournals,
|
|
19
|
+
import { share, sync, getStateDir, listJournals, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
20
20
|
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
21
|
+
import { resolveUploadAuthor } from "../utils/upload-author.js";
|
|
21
22
|
import { companyFolderExceedsThreshold, emitNarrowHint, isStrictRefusal, resolveBannerLevel, resolveNarrowHintPresentationLevel, resolveNarrowHintMinBytes, } from "../lib/narrow-hint-banner.js";
|
|
22
23
|
import { beginSyncHealthReport, } from "../utils/client-health.js";
|
|
23
24
|
/**
|
|
@@ -115,11 +116,39 @@ export function scopeExcludedWarning(count) {
|
|
|
115
116
|
* dropped files — helps debug ACL vs scope-excluded mismatches (feedback_7e9c535e).
|
|
116
117
|
*/
|
|
117
118
|
export function scopeExcludedPrefixDetail(prefixSet) {
|
|
118
|
-
|
|
119
|
+
// No client-side prefix scope applied: the server refused the paths one by
|
|
120
|
+
// one, and scopeExcludedLines' primary line already says so.
|
|
121
|
+
if (prefixSet === undefined)
|
|
122
|
+
return null;
|
|
123
|
+
if (prefixSet.length === 0) {
|
|
119
124
|
return " Granted write prefixes: (none — shared-mode membership with no explicit grants)";
|
|
120
125
|
}
|
|
121
126
|
return ` Granted write prefixes: ${prefixSet.join(", ")}`;
|
|
122
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Every warning line for a push that dropped files, shared by `push`, `now`
|
|
130
|
+
* and `push --all`. With a client prefix scope the files fell outside the
|
|
131
|
+
* caller's write prefixes; without one (prefixSet undefined) the server
|
|
132
|
+
* refused them path by path — typically another member's child in a private
|
|
133
|
+
* folder foo/ — and grant advice would point the reader the wrong way.
|
|
134
|
+
*/
|
|
135
|
+
export function scopeExcludedLines(count, prefixSet) {
|
|
136
|
+
if (count <= 0)
|
|
137
|
+
return [];
|
|
138
|
+
if (prefixSet === undefined) {
|
|
139
|
+
return [
|
|
140
|
+
`⚠ ${count} file(s) were NOT uploaded — the server refused them. ` +
|
|
141
|
+
`A common cause is a file someone else created in a private folder ` +
|
|
142
|
+
`(foo/): only its creator can change it. They were skipped, not ` +
|
|
143
|
+
`synced. Re-run with --json to list them.`,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
const lines = [scopeExcludedWarning(count)];
|
|
147
|
+
const detail = scopeExcludedPrefixDetail(prefixSet);
|
|
148
|
+
if (detail)
|
|
149
|
+
lines.push(detail);
|
|
150
|
+
return lines;
|
|
151
|
+
}
|
|
123
152
|
/**
|
|
124
153
|
* Resolve the `propagateDeletePolicy` for share() calls.
|
|
125
154
|
*
|
|
@@ -445,7 +474,13 @@ export async function pushAll(options, deps) {
|
|
|
445
474
|
result.bytesUploaded += r.bytesUploaded;
|
|
446
475
|
result.filesDeleted += r.filesDeleted;
|
|
447
476
|
result.filesExcludedByScope += r.filesExcludedByScope;
|
|
448
|
-
result.perCompany.push({
|
|
477
|
+
result.perCompany.push({
|
|
478
|
+
slug: entry.slug,
|
|
479
|
+
result: r,
|
|
480
|
+
...(entry.shareOptions.prefixSet !== undefined
|
|
481
|
+
? { prefixSet: entry.shareOptions.prefixSet }
|
|
482
|
+
: {}),
|
|
483
|
+
});
|
|
449
484
|
}
|
|
450
485
|
catch (err) {
|
|
451
486
|
const message = formatSyncFailure(err);
|
|
@@ -803,7 +838,7 @@ export function registerCloudCommands(program) {
|
|
|
803
838
|
// every row renders `—`. Resolved best-effort from the cached
|
|
804
839
|
// idToken — pre-vended `--creds-from-stdin` paths still get author
|
|
805
840
|
// attribution as long as the caller is logged in locally.
|
|
806
|
-
const author =
|
|
841
|
+
const author = resolveUploadAuthor();
|
|
807
842
|
let pushPrefixSet;
|
|
808
843
|
if (!personalMode && scopeVaultConfig) {
|
|
809
844
|
const scopeClient = new VaultClient(scopeVaultConfig);
|
|
@@ -850,10 +885,9 @@ export function registerCloudCommands(program) {
|
|
|
850
885
|
if (result.filesExcludedByScope > 0) {
|
|
851
886
|
log(chalk.yellow(`\n⚠ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ` +
|
|
852
887
|
`${result.filesSkipped} skipped, ${result.filesExcludedByScope} scope-excluded)`));
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
log(chalk.yellow(prefixDetail));
|
|
888
|
+
for (const line of scopeExcludedLines(result.filesExcludedByScope, pushPrefixSet)) {
|
|
889
|
+
log(chalk.yellow(line));
|
|
890
|
+
}
|
|
857
891
|
}
|
|
858
892
|
else {
|
|
859
893
|
log(chalk.green(`\n✓ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ${result.filesSkipped} skipped)`));
|
|
@@ -1342,7 +1376,7 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
|
1342
1376
|
const accessToken = await ensureCognitoToken();
|
|
1343
1377
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1344
1378
|
const realClient = new VaultClient(vaultConfig);
|
|
1345
|
-
const author =
|
|
1379
|
+
const author = resolveUploadAuthor();
|
|
1346
1380
|
const adapter = {
|
|
1347
1381
|
listMyMemberships: () => realClient.listMyMemberships(),
|
|
1348
1382
|
listPersonEntities: () => realClient.entity.listByType("person"),
|
|
@@ -1420,8 +1454,11 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
|
1420
1454
|
console.log(errored > 0 || result.filesExcludedByScope > 0
|
|
1421
1455
|
? chalk.yellow(summary)
|
|
1422
1456
|
: chalk.green(summary));
|
|
1423
|
-
|
|
1424
|
-
|
|
1457
|
+
for (const row of result.perCompany) {
|
|
1458
|
+
const excluded = row.result?.filesExcludedByScope ?? 0;
|
|
1459
|
+
for (const line of scopeExcludedLines(excluded, row.prefixSet)) {
|
|
1460
|
+
console.log(chalk.yellow(`${row.slug}: ${line}`));
|
|
1461
|
+
}
|
|
1425
1462
|
}
|
|
1426
1463
|
if (errored > 0)
|
|
1427
1464
|
throw new SyncExitError(1);
|
|
@@ -1434,7 +1471,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1434
1471
|
try {
|
|
1435
1472
|
const accessToken = await ensureCognitoToken();
|
|
1436
1473
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1437
|
-
const author =
|
|
1474
|
+
const author = resolveUploadAuthor();
|
|
1438
1475
|
// Resolve the target. For --personal, look up the canonical person and
|
|
1439
1476
|
// route paths/journal through personalMode. Otherwise use the company arg
|
|
1440
1477
|
// (or fall back to the active company inside share()/sync()).
|
|
@@ -1499,10 +1536,9 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1499
1536
|
`${pushResult.filesDeleted} deleted, ${pushResult.filesExcludedByScope} scope-excluded` +
|
|
1500
1537
|
(pushResult.aborted ? " — aborted" : ""));
|
|
1501
1538
|
if (pushResult.filesExcludedByScope > 0) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
console.log(chalk.yellow(prefixDetail));
|
|
1539
|
+
for (const line of scopeExcludedLines(pushResult.filesExcludedByScope, nowPushPrefixSet)) {
|
|
1540
|
+
console.log(chalk.yellow(line));
|
|
1541
|
+
}
|
|
1506
1542
|
}
|
|
1507
1543
|
if (pushResult.aborted) {
|
|
1508
1544
|
console.log(chalk.yellow("\n⚠ Sync now aborted on push leg; pull skipped."));
|
|
@@ -1689,35 +1725,4 @@ async function readAllStdin() {
|
|
|
1689
1725
|
}
|
|
1690
1726
|
return Buffer.concat(chunks).toString("utf8");
|
|
1691
1727
|
}
|
|
1692
|
-
/**
|
|
1693
|
-
* Resolve the syncing user's `UploadAuthor` (sub + email) from the cached
|
|
1694
|
-
* Cognito idToken. Returns `undefined` when no tokens are cached or the
|
|
1695
|
-
* token is missing the required claims — share() then skips the metadata
|
|
1696
|
-
* stamp gracefully (not an error).
|
|
1697
|
-
*
|
|
1698
|
-
* We deliberately decode the JWT here instead of verifying it: Cognito
|
|
1699
|
-
* already verified at issuance, and we only use the public claims to
|
|
1700
|
-
* label the upload's S3 user metadata (no auth decision rides on it).
|
|
1701
|
-
*/
|
|
1702
|
-
function resolveUploadAuthorFromCache() {
|
|
1703
|
-
const tokens = loadCachedTokens();
|
|
1704
|
-
if (!tokens?.idToken)
|
|
1705
|
-
return undefined;
|
|
1706
|
-
const parts = tokens.idToken.split(".");
|
|
1707
|
-
if (parts.length !== 3)
|
|
1708
|
-
return undefined;
|
|
1709
|
-
try {
|
|
1710
|
-
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1711
|
-
const padded = payload + "=".repeat((4 - (payload.length % 4)) % 4);
|
|
1712
|
-
const json = Buffer.from(padded, "base64").toString("utf-8");
|
|
1713
|
-
const claims = JSON.parse(json);
|
|
1714
|
-
if (claims.sub && claims.email) {
|
|
1715
|
-
return { userSub: claims.sub, email: claims.email };
|
|
1716
|
-
}
|
|
1717
|
-
return undefined;
|
|
1718
|
-
}
|
|
1719
|
-
catch {
|
|
1720
|
-
return undefined;
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
1728
|
//# sourceMappingURL=cloud.js.map
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.
|
|
|
15
15
|
import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
16
16
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
17
17
|
import { type RunCompactionFn } from "../lib/lanes/compaction.js";
|
|
18
|
+
import type { WatcherLoopDeps } from "../lib/lanes/watcher.js";
|
|
18
19
|
import { type KnownModelsDeps, type ProbeCommand } from "../lib/lanes/models.js";
|
|
19
20
|
import { spawnLaneWorker, type KillProcessFn, type ProveDetachedFn, type SpawnProcessFn } from "../lib/lanes/spawn.js";
|
|
20
21
|
export { spawnLaneWorker };
|
|
@@ -56,6 +57,10 @@ export interface LanesCommandDeps {
|
|
|
56
57
|
proveDetached?: ProveDetachedFn;
|
|
57
58
|
killProcess?: KillProcessFn;
|
|
58
59
|
isAlive?: (pid: number) => boolean;
|
|
60
|
+
/** Test seam for worker PID identity checks. */
|
|
61
|
+
readStarttime?: (pid: number) => number | null;
|
|
62
|
+
/** Test seams for synchronous dead-worker classification and repair. */
|
|
63
|
+
watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair">;
|
|
59
64
|
stopSleepMs?: (ms: number) => void;
|
|
60
65
|
livenessDeps?: LivenessDeps;
|
|
61
66
|
presenceConnected?: boolean;
|