@indigoai-us/hq-cli 5.298.0 → 5.300.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 +11 -0
- package/dist/command-catalog.generated.d.ts +27 -4
- package/dist/command-catalog.generated.js +34 -4
- package/dist/commands/cloud.js +12 -7
- package/dist/commands/daemon.js +1 -0
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +59 -0
- package/dist/commands/lanes-accounts.d.ts +3 -0
- package/dist/commands/lanes-accounts.js +25 -1
- package/dist/commands/lanes-message.js +68 -35
- package/dist/commands/lanes-resume.js +247 -210
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +16 -4
- package/dist/lib/daemon/legacy.d.ts +5 -2
- package/dist/lib/daemon/legacy.js +6 -4
- package/dist/lib/daemon/migrate.d.ts +10 -1
- package/dist/lib/daemon/migrate.js +22 -7
- package/dist/lib/daemon/run.js +19 -7
- package/dist/lib/daemon/supervisor.js +9 -1
- package/dist/lib/lanes/quota.d.ts +19 -0
- package/dist/lib/lanes/quota.js +106 -29
- package/dist/lib/lanes/reconcile.js +34 -5
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +4 -0
- package/dist/lib/lanes/story-switch.d.ts +21 -0
- package/dist/lib/lanes/story-switch.js +75 -0
- package/dist/lib/lanes/types.d.ts +25 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.js +25 -5
- package/dist/lib/mesh/live/daemon/install.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/install.js +13 -2
- package/dist/utils/self-update.js +249 -2
- package/dist/utils/staged-install.js +71 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.300.0] — 2026-09-30
|
|
6
|
+
- With the default-off hq-flags gate `files.share-empty-prefix-warning`, `hq files share` warns when a granted prefix has no files. A failed or timed-out list check does not block the grant.
|
|
7
|
+
|
|
8
|
+
- Lane messages and resumes that switch project or story attribution now tell the provider which story is active. Reused completion artifacts no longer close the new story.
|
|
9
|
+
- Make signed-out `hq sync` commands fail fast with `hq login` guidance when stdin or stdout is not a terminal, while keeping browser login in a terminal.
|
|
10
|
+
|
|
11
|
+
## [5.299.0] — 2026-09-30
|
|
12
|
+
- Add `hq lanes accounts clear-quota` and clear future quota marks after successful lane turns.
|
|
13
|
+
- On macOS, `hq daemon` retires a loaded legacy mesh LaunchAgent even when its plist is missing, and daemon uninstall boots the job out before deleting its plist. Mesh-owner wait logs now repeat at most every ten minutes.
|
|
14
|
+
- Restore npm command links after a failed hq-cli self-update and repair or report missing links after successful installs.
|
|
15
|
+
|
|
5
16
|
## [5.298.0] — 2026-09-30
|
|
6
17
|
- [feedback_d4a080e9-de79-4290-81c3-06ba26990a38] Preserve raw substring matches while normalizing company path searches.
|
|
7
18
|
- `hq agents` now prints the Slack app settings link and the console paste link whenever an agent is waiting on its Slack app-level token, and follows the pending actions and paste URL the server returns.
|
|
@@ -8509,10 +8509,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8509
8509
|
readonly description: "Message text";
|
|
8510
8510
|
}, {
|
|
8511
8511
|
readonly flags: "--project <slug>";
|
|
8512
|
-
readonly description: "Re-point project attribution
|
|
8512
|
+
readonly description: "Re-point project attribution; a changed story is named in the resumed prompt";
|
|
8513
8513
|
}, {
|
|
8514
8514
|
readonly flags: "--story <id>";
|
|
8515
|
-
readonly description: "Re-point story attribution
|
|
8515
|
+
readonly description: "Re-point story attribution; a changed story is named in the resumed prompt";
|
|
8516
8516
|
}, {
|
|
8517
8517
|
readonly flags: "--account <id>";
|
|
8518
8518
|
readonly description: "Move the lane's session to this account of its provider before the message resumes it";
|
|
@@ -8602,10 +8602,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8602
8602
|
readonly description: "Required explanation for resuming from failed";
|
|
8603
8603
|
}, {
|
|
8604
8604
|
readonly flags: "--project <slug>";
|
|
8605
|
-
readonly description: "Re-point project attribution
|
|
8605
|
+
readonly description: "Re-point project attribution; a changed story is named in the resumed prompt";
|
|
8606
8606
|
}, {
|
|
8607
8607
|
readonly flags: "--story <id>";
|
|
8608
|
-
readonly description: "Re-point story attribution
|
|
8608
|
+
readonly description: "Re-point story attribution; a changed story is named in the resumed prompt";
|
|
8609
8609
|
}, {
|
|
8610
8610
|
readonly flags: "--account <id>";
|
|
8611
8611
|
readonly description: "Move the lane's session to this account of its provider before resuming";
|
|
@@ -8990,6 +8990,29 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8990
8990
|
readonly description: "JSON on stdout (always)";
|
|
8991
8991
|
}];
|
|
8992
8992
|
readonly subcommands: readonly [];
|
|
8993
|
+
}, {
|
|
8994
|
+
readonly name: "clear-quota";
|
|
8995
|
+
readonly description: "Clear quota reset marks for a provider account";
|
|
8996
|
+
readonly aliases: readonly [];
|
|
8997
|
+
readonly hidden: false;
|
|
8998
|
+
readonly usage: "[options] <provider> <account-id>";
|
|
8999
|
+
readonly arguments: readonly [{
|
|
9000
|
+
readonly name: "provider";
|
|
9001
|
+
readonly required: true;
|
|
9002
|
+
readonly variadic: false;
|
|
9003
|
+
}, {
|
|
9004
|
+
readonly name: "account-id";
|
|
9005
|
+
readonly required: true;
|
|
9006
|
+
readonly variadic: false;
|
|
9007
|
+
}];
|
|
9008
|
+
readonly options: readonly [{
|
|
9009
|
+
readonly flags: "--model <model>";
|
|
9010
|
+
readonly description: "Clear only this model's reset";
|
|
9011
|
+
}, {
|
|
9012
|
+
readonly flags: "--json";
|
|
9013
|
+
readonly description: "JSON on stdout (always)";
|
|
9014
|
+
}];
|
|
9015
|
+
readonly subcommands: readonly [];
|
|
8993
9016
|
}, {
|
|
8994
9017
|
readonly name: "enable";
|
|
8995
9018
|
readonly description: "Enable an account without touching credentials";
|
|
@@ -10972,11 +10972,11 @@ export const COMMAND_CATALOG = [
|
|
|
10972
10972
|
},
|
|
10973
10973
|
{
|
|
10974
10974
|
"flags": "--project <slug>",
|
|
10975
|
-
"description": "Re-point project attribution
|
|
10975
|
+
"description": "Re-point project attribution; a changed story is named in the resumed prompt"
|
|
10976
10976
|
},
|
|
10977
10977
|
{
|
|
10978
10978
|
"flags": "--story <id>",
|
|
10979
|
-
"description": "Re-point story attribution
|
|
10979
|
+
"description": "Re-point story attribution; a changed story is named in the resumed prompt"
|
|
10980
10980
|
},
|
|
10981
10981
|
{
|
|
10982
10982
|
"flags": "--account <id>",
|
|
@@ -11094,11 +11094,11 @@ export const COMMAND_CATALOG = [
|
|
|
11094
11094
|
},
|
|
11095
11095
|
{
|
|
11096
11096
|
"flags": "--project <slug>",
|
|
11097
|
-
"description": "Re-point project attribution
|
|
11097
|
+
"description": "Re-point project attribution; a changed story is named in the resumed prompt"
|
|
11098
11098
|
},
|
|
11099
11099
|
{
|
|
11100
11100
|
"flags": "--story <id>",
|
|
11101
|
-
"description": "Re-point story attribution
|
|
11101
|
+
"description": "Re-point story attribution; a changed story is named in the resumed prompt"
|
|
11102
11102
|
},
|
|
11103
11103
|
{
|
|
11104
11104
|
"flags": "--account <id>",
|
|
@@ -11601,6 +11601,36 @@ export const COMMAND_CATALOG = [
|
|
|
11601
11601
|
],
|
|
11602
11602
|
"subcommands": []
|
|
11603
11603
|
},
|
|
11604
|
+
{
|
|
11605
|
+
"name": "clear-quota",
|
|
11606
|
+
"description": "Clear quota reset marks for a provider account",
|
|
11607
|
+
"aliases": [],
|
|
11608
|
+
"hidden": false,
|
|
11609
|
+
"usage": "[options] <provider> <account-id>",
|
|
11610
|
+
"arguments": [
|
|
11611
|
+
{
|
|
11612
|
+
"name": "provider",
|
|
11613
|
+
"required": true,
|
|
11614
|
+
"variadic": false
|
|
11615
|
+
},
|
|
11616
|
+
{
|
|
11617
|
+
"name": "account-id",
|
|
11618
|
+
"required": true,
|
|
11619
|
+
"variadic": false
|
|
11620
|
+
}
|
|
11621
|
+
],
|
|
11622
|
+
"options": [
|
|
11623
|
+
{
|
|
11624
|
+
"flags": "--model <model>",
|
|
11625
|
+
"description": "Clear only this model's reset"
|
|
11626
|
+
},
|
|
11627
|
+
{
|
|
11628
|
+
"flags": "--json",
|
|
11629
|
+
"description": "JSON on stdout (always)"
|
|
11630
|
+
}
|
|
11631
|
+
],
|
|
11632
|
+
"subcommands": []
|
|
11633
|
+
},
|
|
11604
11634
|
{
|
|
11605
11635
|
"name": "enable",
|
|
11606
11636
|
"description": "Enable an account without touching credentials",
|
package/dist/commands/cloud.js
CHANGED
|
@@ -53,6 +53,11 @@ export class SyncExitError extends Error {
|
|
|
53
53
|
this.health = opts.health ?? "failed";
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
async function ensureSyncCognitoToken() {
|
|
57
|
+
return ensureCognitoToken({
|
|
58
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
56
61
|
/**
|
|
57
62
|
* Preserve the vault's typed write-scope refusal at the CLI boundary. This is
|
|
58
63
|
* deliberately duck-typed so hq-cli remains compatible while hq-cloud and the
|
|
@@ -845,11 +850,11 @@ export function registerCloudCommands(program) {
|
|
|
845
850
|
}
|
|
846
851
|
// Upload creds are pre-vended in entityContext, but prefix filtering
|
|
847
852
|
// still needs vault-service membership/grant resolution.
|
|
848
|
-
const accessToken = await
|
|
853
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
849
854
|
scopeVaultConfig = buildVaultConfig(accessToken);
|
|
850
855
|
}
|
|
851
856
|
else {
|
|
852
|
-
const accessToken = await
|
|
857
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
853
858
|
vaultConfig = buildVaultConfig(accessToken);
|
|
854
859
|
scopeVaultConfig = vaultConfig;
|
|
855
860
|
}
|
|
@@ -1064,7 +1069,7 @@ export function registerCloudCommands(program) {
|
|
|
1064
1069
|
console.log(chalk.bold("\nHQ Sync — Pull"));
|
|
1065
1070
|
console.log(` HQ root: ${options.hqRoot}`);
|
|
1066
1071
|
console.log(` Company: ${options.company ?? "(from .hq/config.json)"}\n`);
|
|
1067
|
-
const accessToken = await
|
|
1072
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
1068
1073
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1069
1074
|
// US-011 (2026-05-21 fix): resolve the caller's sync-config for
|
|
1070
1075
|
// the targeted membership BEFORE the pull, so we can (a) emit the
|
|
@@ -1368,7 +1373,7 @@ async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, for
|
|
|
1368
1373
|
console.log("");
|
|
1369
1374
|
let result;
|
|
1370
1375
|
try {
|
|
1371
|
-
const accessToken = await
|
|
1376
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
1372
1377
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1373
1378
|
const realClient = new VaultClient(vaultConfig);
|
|
1374
1379
|
const adapter = {
|
|
@@ -1461,7 +1466,7 @@ export async function runPullPersonal(hqRoot, onConflict) {
|
|
|
1461
1466
|
console.log(` HQ root: ${hqRoot}`);
|
|
1462
1467
|
console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
|
|
1463
1468
|
try {
|
|
1464
|
-
const accessToken = await
|
|
1469
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
1465
1470
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1466
1471
|
const client = new VaultClient(vaultConfig);
|
|
1467
1472
|
const personalUid = await resolveCanonicalPersonUid({
|
|
@@ -1509,7 +1514,7 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
|
1509
1514
|
console.log("");
|
|
1510
1515
|
let result;
|
|
1511
1516
|
try {
|
|
1512
|
-
const accessToken = await
|
|
1517
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
1513
1518
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1514
1519
|
const realClient = new VaultClient(vaultConfig);
|
|
1515
1520
|
const author = resolveUploadAuthor();
|
|
@@ -1637,7 +1642,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1637
1642
|
console.log(` Target: ${personal ? "(personal)" : (company ?? "(active company)")}`);
|
|
1638
1643
|
console.log(` Strategy: ${onConflict ?? "(interactive)"}\n`);
|
|
1639
1644
|
try {
|
|
1640
|
-
const accessToken = await
|
|
1645
|
+
const accessToken = await ensureSyncCognitoToken();
|
|
1641
1646
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
1642
1647
|
const author = resolveUploadAuthor();
|
|
1643
1648
|
// Resolve the target. For --personal, look up the canonical person and
|
package/dist/commands/daemon.js
CHANGED
|
@@ -91,6 +91,7 @@ async function loadDefaults(over) {
|
|
|
91
91
|
home: dir ?? home,
|
|
92
92
|
existsSync: fs.existsSync,
|
|
93
93
|
listDir: (dir) => fs.readdirSync(dir),
|
|
94
|
+
launchdJobLoaded: (label) => run("launchctl", ["print", `gui/${process.getuid?.() ?? 0}/${label}`]).status === 0,
|
|
94
95
|
windowsTaskExists: (task) => run("schtasks.exe", ["/Query", "/TN", task]).status === 0,
|
|
95
96
|
})),
|
|
96
97
|
resolveHqRoot: over.resolveHqRoot ?? ((flag) => resolveDaemonHqRoot(flag, { env, cwd: process.cwd(), home })),
|
package/dist/commands/files.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
export declare const FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG = "files.share-empty-prefix-warning";
|
|
2
3
|
/**
|
|
3
4
|
* Parse a human-friendly duration string ("15m", "1h", "24h", "2d") into
|
|
4
5
|
* milliseconds. Returns null on parse failure. Mirrors the parser used in
|
package/dist/commands/files.js
CHANGED
|
@@ -12,7 +12,10 @@ import { fetchChannels, matchChannelsByName } from "./dm.js";
|
|
|
12
12
|
import { resolveCompanySlug, resolvePersonWithRosterFallback, } from "./people.js";
|
|
13
13
|
import { findHqRoot } from "../utils/manifest.js";
|
|
14
14
|
import { maxLength } from "../utils/table-width.js";
|
|
15
|
+
import { resolveCompanyFlagGate } from "../lib/flag-registry.js";
|
|
15
16
|
const PERSON_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
|
|
17
|
+
export const FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG = "files.share-empty-prefix-warning";
|
|
18
|
+
const FILES_SHARE_EMPTY_PREFIX_CHECK_TIMEOUT_MS = 5_000;
|
|
16
19
|
function collectMentions(value, previous) {
|
|
17
20
|
return [...previous, value];
|
|
18
21
|
}
|
|
@@ -589,6 +592,56 @@ export function registerFilesCommand(program) {
|
|
|
589
592
|
// files-browse.ts) onto the same group without re-creating it.
|
|
590
593
|
return files;
|
|
591
594
|
}
|
|
595
|
+
/** Best-effort, default-off warning after the ACL grant has succeeded. */
|
|
596
|
+
async function warnIfEmptySharePrefix(params) {
|
|
597
|
+
if (isPrivateFolderPattern(params.prefix))
|
|
598
|
+
return;
|
|
599
|
+
let enabled;
|
|
600
|
+
try {
|
|
601
|
+
enabled = await resolveCompanyFlagGate(params.companyUid, params.token, FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG, { fallback: false }, () => false);
|
|
602
|
+
}
|
|
603
|
+
catch (error) {
|
|
604
|
+
const errorClass = error instanceof Error ? error.name : "unknown error";
|
|
605
|
+
console.error(`hq: Could not resolve ${FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG} (${errorClass}); using its fallback.`);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (!enabled)
|
|
609
|
+
return;
|
|
610
|
+
const listPrefix = params.fullVault
|
|
611
|
+
? ""
|
|
612
|
+
: params.prefix.endsWith("*")
|
|
613
|
+
? params.prefix.slice(0, -1)
|
|
614
|
+
: params.prefix;
|
|
615
|
+
try {
|
|
616
|
+
const query = {
|
|
617
|
+
company: params.companyUid,
|
|
618
|
+
};
|
|
619
|
+
if (listPrefix)
|
|
620
|
+
query.prefix = listPrefix;
|
|
621
|
+
const listed = await vaultApiFetch({
|
|
622
|
+
token: params.token,
|
|
623
|
+
path: "/v1/files/list",
|
|
624
|
+
query,
|
|
625
|
+
signal: AbortSignal.timeout(FILES_SHARE_EMPTY_PREFIX_CHECK_TIMEOUT_MS),
|
|
626
|
+
});
|
|
627
|
+
if (!listed.ok)
|
|
628
|
+
throw new Error(`files list failed (${listed.status})`);
|
|
629
|
+
const body = (await listed.json());
|
|
630
|
+
const objects = Array.isArray(body.objects) ? body.objects : [];
|
|
631
|
+
const hasMatchingObject = params.fullVault || params.prefix.endsWith("*")
|
|
632
|
+
? objects.length > 0
|
|
633
|
+
: objects.some((object) => object.key === listPrefix);
|
|
634
|
+
if (!hasMatchingObject) {
|
|
635
|
+
console.warn(chalk.yellow(`Warning: no files exist under '${displayAclPattern(params.prefix)}'. ` +
|
|
636
|
+
`The recipient will see nothing until files sync there.`));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
const errorClass = error instanceof Error ? error.name : "unknown error";
|
|
641
|
+
console.warn(chalk.yellow(`Could not check whether '${displayAclPattern(params.prefix)}' has files; ` +
|
|
642
|
+
`the share succeeded (${errorClass}).`));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
592
645
|
async function resolveShareChannelTarget(params) {
|
|
593
646
|
const token = (await resolveVaultCredential()).token;
|
|
594
647
|
const channels = await fetchChannels(token);
|
|
@@ -757,6 +810,12 @@ async function runDirectGrant(params) {
|
|
|
757
810
|
}
|
|
758
811
|
const data = (await res.json());
|
|
759
812
|
const printedPrefix = data.acl?.path ?? data.acl?.prefix ?? canonicalPrefix;
|
|
813
|
+
await warnIfEmptySharePrefix({
|
|
814
|
+
token,
|
|
815
|
+
companyUid,
|
|
816
|
+
prefix: canonicalPrefix,
|
|
817
|
+
fullVault: params.fullVault === true,
|
|
818
|
+
});
|
|
760
819
|
const verb = autoCreated ? "Created ACL and granted" : "Granted";
|
|
761
820
|
if (params.fullVault) {
|
|
762
821
|
console.log(chalk.green(`${verb} ${params.permission} on the ENTIRE vault to ${principalLabel}`));
|
|
@@ -17,6 +17,9 @@ export declare function createLaneAccountsHandlers(deps: LaneAccountsHandlerDeps
|
|
|
17
17
|
host?: string;
|
|
18
18
|
company?: string;
|
|
19
19
|
}) => Promise<void>;
|
|
20
|
+
runAccountsClearQuota: (ctx: Ctx, providerRaw: string, accountIdRaw: string, opts: {
|
|
21
|
+
model?: string;
|
|
22
|
+
}) => Promise<void>;
|
|
20
23
|
runAccountsEnable: (ctx: Ctx, providerRaw: string, accountId: string) => Promise<void>;
|
|
21
24
|
runAccountsDisable: (ctx: Ctx, providerRaw: string, accountId: string, opts: {
|
|
22
25
|
reason?: string;
|
|
@@ -8,7 +8,7 @@ import { AccountResolutionError, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, PROVIDER
|
|
|
8
8
|
import { assertUniqueAccountHomes, providerCompactAfterMs } from "../lib/lanes/config.js";
|
|
9
9
|
import { resolveLaneEffort } from "../lib/lanes/effort.js";
|
|
10
10
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
11
|
-
import { accountQuotaView, loadQuotaState } from "../lib/lanes/quota.js";
|
|
11
|
+
import { accountQuotaView, clearAccountQuota, loadQuotaState } from "../lib/lanes/quota.js";
|
|
12
12
|
import { resolveLaneModel } from "../lib/lanes/spawn.js";
|
|
13
13
|
export function createLaneAccountsHandlers(deps) {
|
|
14
14
|
const { automaticBalanceMoveHistoryReason, ensureDir, laneWithRecoveredProviderSession, loadClaudeBalanceHostSnapshots, parseRemoteJson, prepareAccountMove, printJson, reconcileHostFromCtx, resolveCallerPath } = deps;
|
|
@@ -489,6 +489,29 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
489
489
|
home_source: home === account.home ? "default" : "host",
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
|
+
async function runAccountsClearQuota(ctx, providerRaw, accountIdRaw, opts) {
|
|
493
|
+
const provider = parseLaneProvider(providerRaw);
|
|
494
|
+
const account = accountIdRaw.trim();
|
|
495
|
+
if (!account) {
|
|
496
|
+
throw new AccountResolutionError("unknown_account", "account-id must be non-empty", { provider, account: accountIdRaw, enabled: [] });
|
|
497
|
+
}
|
|
498
|
+
const model = opts.model?.trim();
|
|
499
|
+
if (opts.model !== undefined && !model) {
|
|
500
|
+
throw new LanesError("invalid_model", "--model must be non-empty", {
|
|
501
|
+
provider,
|
|
502
|
+
account,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const cleared = clearAccountQuota(ctx.hqRoot(), provider, account, model === undefined ? {} : { model });
|
|
506
|
+
printJson({
|
|
507
|
+
ok: true,
|
|
508
|
+
action: "accounts.clear-quota",
|
|
509
|
+
provider,
|
|
510
|
+
account,
|
|
511
|
+
all_models_quota_reset_at: cleared.all_models_quota_reset_at,
|
|
512
|
+
model_quota_resets: cleared.model_quota_resets,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
492
515
|
async function runAccountsEnable(ctx, providerRaw, accountId) {
|
|
493
516
|
const hqRoot = ctx.hqRoot();
|
|
494
517
|
const provider = parseLaneProvider(providerRaw);
|
|
@@ -562,6 +585,7 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
562
585
|
runAccountsBalance,
|
|
563
586
|
runAccountsLogin,
|
|
564
587
|
runAccountsSetHome,
|
|
588
|
+
runAccountsClearQuota,
|
|
565
589
|
runAccountsEnable,
|
|
566
590
|
runAccountsDisable
|
|
567
591
|
};
|
|
@@ -8,6 +8,7 @@ import { dispatchLaneDelivery } from "../lib/lanes/delivery-core.js";
|
|
|
8
8
|
import { dropboxInboxHasMessages, drainDropbox, formatDropboxMessages, formatMonitorEvent, requeueDropboxMessages } from "../lib/lanes/dropbox.js";
|
|
9
9
|
import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, writeWatcherDecision } from "../lib/lanes/watcher.js";
|
|
10
10
|
import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
11
|
+
import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice } from "../lib/lanes/story-switch.js";
|
|
11
12
|
import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
12
13
|
import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
|
|
13
14
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
@@ -212,8 +213,11 @@ export function createLaneMessageHandlers(deps) {
|
|
|
212
213
|
})();
|
|
213
214
|
let accepted = false;
|
|
214
215
|
let claimedMessages = [];
|
|
216
|
+
let storySwitchRollback;
|
|
217
|
+
let storySwitchDeliveryStarted = false;
|
|
215
218
|
const markAccepted = () => {
|
|
216
219
|
accepted = true;
|
|
220
|
+
storySwitchDeliveryStarted = true;
|
|
217
221
|
opts.onAccepted?.();
|
|
218
222
|
};
|
|
219
223
|
let text = opts.text;
|
|
@@ -392,19 +396,21 @@ export function createLaneMessageHandlers(deps) {
|
|
|
392
396
|
if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
|
|
393
397
|
validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
|
|
394
398
|
}
|
|
399
|
+
const notice = storySwitchNotice(lane.project_id, lane.story_id, projectId, storyId);
|
|
400
|
+
const promptedText = notice === undefined ? text : `${notice}\n\n${text}`;
|
|
395
401
|
const framedMonitorEvent = opts.monitorEvent === undefined
|
|
396
402
|
? undefined
|
|
397
403
|
: formatMonitorEvent({
|
|
398
404
|
monitorId: opts.monitorEvent.monitor_id,
|
|
399
405
|
description: opts.monitorEvent.description,
|
|
400
406
|
sequence: opts.monitorEvent.sequence,
|
|
401
|
-
text,
|
|
407
|
+
text: promptedText,
|
|
402
408
|
});
|
|
403
|
-
const deliveryText = framedMonitorEvent ??
|
|
409
|
+
const deliveryText = framedMonitorEvent ?? promptedText;
|
|
404
410
|
const dropboxMessage = {
|
|
405
411
|
at,
|
|
406
412
|
message_id: messageId,
|
|
407
|
-
text,
|
|
413
|
+
text: promptedText,
|
|
408
414
|
project_id: projectId,
|
|
409
415
|
story_id: storyId,
|
|
410
416
|
...(opts.monitorEvent === undefined ? {} : {
|
|
@@ -414,6 +420,52 @@ export function createLaneMessageHandlers(deps) {
|
|
|
414
420
|
sequence: opts.monitorEvent.sequence,
|
|
415
421
|
}),
|
|
416
422
|
};
|
|
423
|
+
if (projectId !== lane.project_id || storyId !== lane.story_id) {
|
|
424
|
+
lane = casUpdateLane(hqRoot, laneId, (current) => {
|
|
425
|
+
const already = current.attribution_history.some((entry) => entry.message_id === messageId);
|
|
426
|
+
if (already)
|
|
427
|
+
return current;
|
|
428
|
+
const existingStory = laneStory(current, projectId, storyId);
|
|
429
|
+
storySwitchRollback = {
|
|
430
|
+
message_id: messageId,
|
|
431
|
+
project_id: current.project_id,
|
|
432
|
+
story_id: current.story_id,
|
|
433
|
+
no_story_reason: current.no_story_reason,
|
|
434
|
+
...(!existingStory ? {
|
|
435
|
+
added_story: { project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
436
|
+
} : {}),
|
|
437
|
+
story_switch_unacknowledged: current.story_switch_unacknowledged,
|
|
438
|
+
};
|
|
439
|
+
return {
|
|
440
|
+
...current,
|
|
441
|
+
project_id: projectId,
|
|
442
|
+
story_id: storyId,
|
|
443
|
+
no_story_reason: storyId ? undefined : current.no_story_reason,
|
|
444
|
+
stories: laneStory(current, projectId, storyId)
|
|
445
|
+
? laneStories(current)
|
|
446
|
+
: [
|
|
447
|
+
...laneStories(current),
|
|
448
|
+
{ project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
449
|
+
],
|
|
450
|
+
attribution_history: appendCappedHistory(current.attribution_history, {
|
|
451
|
+
project_id: projectId,
|
|
452
|
+
story_id: storyId,
|
|
453
|
+
message_id: messageId,
|
|
454
|
+
at,
|
|
455
|
+
}),
|
|
456
|
+
story_switch_pending: {
|
|
457
|
+
previous_project_id: current.project_id,
|
|
458
|
+
previous_story_id: current.story_id,
|
|
459
|
+
project_id: projectId,
|
|
460
|
+
story_id: storyId,
|
|
461
|
+
message_id: messageId,
|
|
462
|
+
at,
|
|
463
|
+
previous_artifact_keys: artifactIdentityKeys(current.last_envelope?.artifacts),
|
|
464
|
+
},
|
|
465
|
+
story_switch_unacknowledged: undefined,
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
}
|
|
417
469
|
let resumed = null;
|
|
418
470
|
let move = null;
|
|
419
471
|
let queuedOnAdmission = false;
|
|
@@ -491,6 +543,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
491
543
|
throw new LanesError("lane_not_accepting_messages", operatorMessageRefusalReason(lane.state), { lane_id: laneId, state: lane.state });
|
|
492
544
|
}
|
|
493
545
|
appendDropboxMessage(hqRoot, laneId, dropboxMessage);
|
|
546
|
+
storySwitchDeliveryStarted = true;
|
|
494
547
|
return null;
|
|
495
548
|
},
|
|
496
549
|
resume: async () => {
|
|
@@ -565,6 +618,8 @@ export function createLaneMessageHandlers(deps) {
|
|
|
565
618
|
compact: opts.compact !== false,
|
|
566
619
|
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
567
620
|
});
|
|
621
|
+
storySwitchDeliveryStarted = true;
|
|
622
|
+
deps.onLaneWorkerStarted?.(laneId, "message");
|
|
568
623
|
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
569
624
|
return started;
|
|
570
625
|
}, hireGrantContext);
|
|
@@ -576,41 +631,10 @@ export function createLaneMessageHandlers(deps) {
|
|
|
576
631
|
markAccepted();
|
|
577
632
|
}
|
|
578
633
|
// Every accepted message moves the compaction reference forward.
|
|
579
|
-
|
|
634
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => ({
|
|
580
635
|
...resetKeepAliveProgress(current),
|
|
581
636
|
last_message_at: at,
|
|
582
637
|
}));
|
|
583
|
-
if (projectId !== lane.project_id || storyId !== lane.story_id) {
|
|
584
|
-
next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
585
|
-
const already = current.attribution_history.some((entry) => entry.message_id === messageId);
|
|
586
|
-
if (already)
|
|
587
|
-
return current;
|
|
588
|
-
return {
|
|
589
|
-
...current,
|
|
590
|
-
project_id: projectId,
|
|
591
|
-
story_id: storyId,
|
|
592
|
-
no_story_reason: storyId ? undefined : current.no_story_reason,
|
|
593
|
-
stories: laneStory(current, projectId, storyId)
|
|
594
|
-
? laneStories(current)
|
|
595
|
-
: [
|
|
596
|
-
...laneStories(current),
|
|
597
|
-
{
|
|
598
|
-
project_id: projectId,
|
|
599
|
-
story_id: storyId,
|
|
600
|
-
status: "in_progress",
|
|
601
|
-
added_at: at,
|
|
602
|
-
updated_at: at,
|
|
603
|
-
},
|
|
604
|
-
],
|
|
605
|
-
attribution_history: appendCappedHistory(current.attribution_history, {
|
|
606
|
-
project_id: projectId,
|
|
607
|
-
story_id: storyId,
|
|
608
|
-
message_id: messageId,
|
|
609
|
-
at,
|
|
610
|
-
}),
|
|
611
|
-
};
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
638
|
// What this command can claim when it returns: the message is durably
|
|
615
639
|
// written (accepted). Delivery to the model is asynchronous in both
|
|
616
640
|
// mechanisms. `dropbox` delivers on the worker's next tool call, and only
|
|
@@ -660,6 +684,15 @@ export function createLaneMessageHandlers(deps) {
|
|
|
660
684
|
});
|
|
661
685
|
}
|
|
662
686
|
catch (err) {
|
|
687
|
+
if (storySwitchRollback !== undefined && !storySwitchDeliveryStarted) {
|
|
688
|
+
try {
|
|
689
|
+
casUpdateLane(hqRoot, laneId, (current) => rollbackStorySwitchAttribution(current, storySwitchRollback));
|
|
690
|
+
}
|
|
691
|
+
catch (rollbackErr) {
|
|
692
|
+
const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
693
|
+
process.stderr.write(`Could not roll back story attribution after message failure: ${rollbackMessage}\n`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
663
696
|
if (claimedMessages.length > 0 && !accepted) {
|
|
664
697
|
const error = err instanceof Error ? err.message : String(err);
|
|
665
698
|
writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {
|