@indigoai-us/hq-cli 5.345.92 → 5.345.94
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 +17 -0
- package/dist/command-catalog.generated.d.ts +252 -0
- package/dist/command-catalog.generated.js +306 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/capacity.d.ts +71 -0
- package/dist/commands/capacity.js +666 -0
- package/dist/commands/integrations-connect.js +64 -5
- package/dist/commands/lanes-accounts.d.ts +1 -0
- package/dist/commands/lanes-accounts.js +6 -0
- package/dist/lib/bot/api.d.ts +21 -0
- package/dist/lib/bot/api.js +26 -0
- package/dist/lib/bot/prompt.js +2 -1
- package/dist/lib/bot/room-mentions.d.ts +26 -0
- package/dist/lib/bot/room-mentions.js +40 -0
- package/dist/lib/bot/run.js +44 -6
- package/dist/lib/capacity/accounts.d.ts +36 -0
- package/dist/lib/capacity/accounts.js +90 -0
- package/dist/lib/capacity/collect.d.ts +33 -0
- package/dist/lib/capacity/collect.js +164 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +145 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +1211 -0
- package/dist/lib/capacity/collectors/claude-statusline.d.ts +72 -0
- package/dist/lib/capacity/collectors/claude-statusline.js +436 -0
- package/dist/lib/capacity/collectors/codex.d.ts +120 -0
- package/dist/lib/capacity/collectors/codex.js +1134 -0
- package/dist/lib/capacity/collectors/grok.d.ts +86 -0
- package/dist/lib/capacity/collectors/grok.js +588 -0
- package/dist/lib/capacity/lane-login.d.ts +29 -0
- package/dist/lib/capacity/lane-login.js +91 -0
- package/dist/lib/capacity/ledger.d.ts +399 -0
- package/dist/lib/capacity/ledger.js +1441 -0
- package/dist/lib/capacity/project.d.ts +12 -0
- package/dist/lib/capacity/project.js +264 -0
- package/dist/lib/capacity/read.d.ts +62 -0
- package/dist/lib/capacity/read.js +264 -0
- package/dist/lib/capacity/refusal.d.ts +30 -0
- package/dist/lib/capacity/refusal.js +183 -0
- package/dist/lib/capacity/scheduler.d.ts +76 -0
- package/dist/lib/capacity/scheduler.js +800 -0
- package/dist/lib/capacity/sign-in.d.ts +103 -0
- package/dist/lib/capacity/sign-in.js +517 -0
- package/dist/lib/capacity/widget.d.ts +22 -0
- package/dist/lib/capacity/widget.js +418 -0
- package/dist/lib/lanes/spawn.js +12 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +5 -0
- package/dist/lib/mesh/live/daemon/run.js +29 -1
- package/package.json +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq capacity ingest refusal` — one inferred exhausted reading, then one
|
|
3
|
+
* now.json rebuild. No network and no provider call.
|
|
4
|
+
*
|
|
5
|
+
* A refusal stays exhausted until its resetsAt passes (then reset-pending).
|
|
6
|
+
* materialize keeps the newest observedAt, so a later fetched or observed
|
|
7
|
+
* reading below 100% replaces the inference before that reset.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { LedgerCorrupt, LedgerMissing, PROVIDER_TOKEN_LEAK, appendReading, readNow, rebuildNow, } from "./ledger.js";
|
|
11
|
+
export const REFUSAL_PROVIDERS = ["claude", "codex", "grok"];
|
|
12
|
+
export const REFUSAL_PARSER_VERSION = "capacity-refusal.1";
|
|
13
|
+
export const REFUSAL_DEFAULT_LIMIT_ID = "weekly";
|
|
14
|
+
export const REFUSAL_HELP = [
|
|
15
|
+
"workflow-runner and /conduct call this after a provider failure has been",
|
|
16
|
+
"classified as a rate-limit refusal (policy",
|
|
17
|
+
"indigo-provider-failures-classified-not-masked-as-timeout). Do not call it",
|
|
18
|
+
"for a timeout that was not classified as a rate-limit refusal:",
|
|
19
|
+
"",
|
|
20
|
+
" hq capacity ingest refusal --provider <claude|codex|grok> [--limit-id <id>] [--resets-at <iso|epoch>] [--account <hash>]",
|
|
21
|
+
"",
|
|
22
|
+
"When --limit-id is omitted, the provider's weekly_all limit id in now.json",
|
|
23
|
+
"is used, or weekly when that view has none. This command does not wire",
|
|
24
|
+
"those callers.",
|
|
25
|
+
].join("\n");
|
|
26
|
+
export class CapacityUsageError extends Error {
|
|
27
|
+
exitCode = 2;
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "CapacityUsageError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function ingestRefusal(input, options) {
|
|
34
|
+
const provider = requireProvider(input.provider);
|
|
35
|
+
const accountArg = optionalTokenFree(input.accountId, "--account must be a non-empty hash");
|
|
36
|
+
const resetArg = input.resetsAt === undefined ? undefined : parseReset(input.resetsAt);
|
|
37
|
+
const limitArg = optionalTokenFree(input.limitId, "--limit-id must be a non-empty id");
|
|
38
|
+
const view = loadView(options);
|
|
39
|
+
const matched = selectLimit(view, provider, limitArg, accountArg);
|
|
40
|
+
const limitId = limitArg ?? matched?.limitId ?? REFUSAL_DEFAULT_LIMIT_ID;
|
|
41
|
+
const accountId = accountArg ?? matched?.accountId ?? "unknown";
|
|
42
|
+
const now = options?.now ? options.now() : new Date();
|
|
43
|
+
const observedAt = now.toISOString();
|
|
44
|
+
const resetsAt = resolveRefusalReset(resetArg, matched?.resetsAt, now);
|
|
45
|
+
const reading = buildReading({
|
|
46
|
+
provider,
|
|
47
|
+
accountId,
|
|
48
|
+
limitId,
|
|
49
|
+
observedAt,
|
|
50
|
+
resetsAt,
|
|
51
|
+
matched,
|
|
52
|
+
});
|
|
53
|
+
const rebuilt = appendReading(reading, options);
|
|
54
|
+
const row = rebuilt.providers
|
|
55
|
+
.find((entry) => entry.provider === provider)
|
|
56
|
+
?.limits.find((limit) => limit.accountId === accountId && limit.limitId === limitId);
|
|
57
|
+
if (!row)
|
|
58
|
+
throw new Error("refusal reading was not materialized");
|
|
59
|
+
return {
|
|
60
|
+
provider,
|
|
61
|
+
accountId,
|
|
62
|
+
limitId,
|
|
63
|
+
source: "refusal",
|
|
64
|
+
confidence: "inferred",
|
|
65
|
+
observedAt,
|
|
66
|
+
state: row.state,
|
|
67
|
+
resetsAt: row.resetsAt,
|
|
68
|
+
readingId: row.readingId,
|
|
69
|
+
usedPercent: row.usedPercent,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const REFUSAL_DEFAULT_TTL_MS = 60 * 60 * 1000;
|
|
73
|
+
/** Ignore an inherited reset that is already past. With none known, expire in one hour. */
|
|
74
|
+
function resolveRefusalReset(explicit, inherited, now) {
|
|
75
|
+
if (explicit !== undefined)
|
|
76
|
+
return explicit;
|
|
77
|
+
if (inherited) {
|
|
78
|
+
const at = Date.parse(inherited);
|
|
79
|
+
if (!Number.isNaN(at) && at > now.getTime())
|
|
80
|
+
return inherited;
|
|
81
|
+
}
|
|
82
|
+
return new Date(now.getTime() + REFUSAL_DEFAULT_TTL_MS).toISOString();
|
|
83
|
+
}
|
|
84
|
+
function requireProvider(value) {
|
|
85
|
+
const provider = value?.trim() ?? "";
|
|
86
|
+
if (!provider)
|
|
87
|
+
throw new CapacityUsageError("--provider is required");
|
|
88
|
+
if (!REFUSAL_PROVIDERS.includes(provider)) {
|
|
89
|
+
throw new CapacityUsageError(`provider must be claude, codex, or grok, got '${provider}'`);
|
|
90
|
+
}
|
|
91
|
+
return provider;
|
|
92
|
+
}
|
|
93
|
+
function optionalTokenFree(value, emptyMessage) {
|
|
94
|
+
if (value === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const trimmed = value.trim();
|
|
97
|
+
if (!trimmed)
|
|
98
|
+
throw new CapacityUsageError(emptyMessage);
|
|
99
|
+
if (PROVIDER_TOKEN_LEAK.test(trimmed))
|
|
100
|
+
throw new CapacityUsageError("value was refused");
|
|
101
|
+
return trimmed;
|
|
102
|
+
}
|
|
103
|
+
function parseReset(raw) {
|
|
104
|
+
const trimmed = raw.trim();
|
|
105
|
+
if (!trimmed || PROVIDER_TOKEN_LEAK.test(trimmed)) {
|
|
106
|
+
throw new CapacityUsageError("--resets-at must be a UTC timestamp or epoch");
|
|
107
|
+
}
|
|
108
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
109
|
+
const value = Number(trimmed);
|
|
110
|
+
if (!Number.isFinite(value))
|
|
111
|
+
throw new CapacityUsageError(`--resets-at must be a UTC timestamp or epoch, got '${trimmed}'`);
|
|
112
|
+
const ms = value < 1e12 ? value * 1000 : value;
|
|
113
|
+
const date = new Date(ms);
|
|
114
|
+
if (Number.isNaN(date.getTime())) {
|
|
115
|
+
throw new CapacityUsageError(`--resets-at must be a UTC timestamp or epoch, got '${trimmed}'`);
|
|
116
|
+
}
|
|
117
|
+
return date.toISOString();
|
|
118
|
+
}
|
|
119
|
+
const parsed = Date.parse(trimmed);
|
|
120
|
+
if (Number.isNaN(parsed)) {
|
|
121
|
+
throw new CapacityUsageError(`--resets-at must be a UTC timestamp or epoch, got '${trimmed}'`);
|
|
122
|
+
}
|
|
123
|
+
return new Date(parsed).toISOString();
|
|
124
|
+
}
|
|
125
|
+
function loadView(options) {
|
|
126
|
+
try {
|
|
127
|
+
return readNow(options);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof LedgerMissing)
|
|
131
|
+
return null;
|
|
132
|
+
if (err instanceof LedgerCorrupt)
|
|
133
|
+
return rebuildNow(options);
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function selectLimit(view, provider, limitId, accountId) {
|
|
138
|
+
const limits = view?.providers.find((entry) => entry.provider === provider)?.limits ?? [];
|
|
139
|
+
const scoped = accountId ? limits.filter((limit) => limit.accountId === accountId) : limits;
|
|
140
|
+
if (limitId)
|
|
141
|
+
return scoped.find((limit) => limit.limitId === limitId) ?? null;
|
|
142
|
+
return scoped.find((limit) => limit.kind === "weekly_all") ?? null;
|
|
143
|
+
}
|
|
144
|
+
function buildReading(input) {
|
|
145
|
+
const matched = input.matched;
|
|
146
|
+
const kind = matched?.kind ?? (input.limitId === REFUSAL_DEFAULT_LIMIT_ID ? "weekly_all" : "unknown");
|
|
147
|
+
const reading = {
|
|
148
|
+
readingId: readingId(input.provider, input.accountId, input.limitId, input.observedAt, input.resetsAt),
|
|
149
|
+
observedAt: input.observedAt,
|
|
150
|
+
provider: input.provider,
|
|
151
|
+
accountId: input.accountId,
|
|
152
|
+
source: "refusal",
|
|
153
|
+
cliVersion: matched?.cliVersion ?? "unknown",
|
|
154
|
+
confidence: "inferred",
|
|
155
|
+
parserVersion: REFUSAL_PARSER_VERSION,
|
|
156
|
+
limit: {
|
|
157
|
+
limitId: input.limitId,
|
|
158
|
+
kind,
|
|
159
|
+
scope: matched?.scope ?? null,
|
|
160
|
+
usedPercent: null,
|
|
161
|
+
windowMinutes: matched?.windowMinutes ?? null,
|
|
162
|
+
resetsAt: input.resetsAt,
|
|
163
|
+
balance: matched?.balance ?? null,
|
|
164
|
+
planType: matched?.planType ?? null,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
if (matched?.hasCredits !== undefined)
|
|
168
|
+
reading.limit.hasCredits = matched.hasCredits;
|
|
169
|
+
if (matched?.unlimited !== undefined)
|
|
170
|
+
reading.limit.unlimited = matched.unlimited;
|
|
171
|
+
if (matched?.allowancePercentOfWeekly !== undefined) {
|
|
172
|
+
reading.limit.allowancePercentOfWeekly = matched.allowancePercentOfWeekly;
|
|
173
|
+
}
|
|
174
|
+
return reading;
|
|
175
|
+
}
|
|
176
|
+
function readingId(provider, accountId, limitId, observedAt, resetsAt) {
|
|
177
|
+
const digest = createHash("sha256")
|
|
178
|
+
.update([provider, accountId, limitId, observedAt, resetsAt ?? ""].join("\0"), "utf8")
|
|
179
|
+
.digest("hex")
|
|
180
|
+
.slice(0, 20);
|
|
181
|
+
return `rdg_${digest}`;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ingestCodexResult, isCodexProcessRunning, latestCodexSessionWriteAt, readCodexAppServer, readInstalledCodexVersion, shouldReadAppServer, tailCodexSessions } from "./collectors/codex.js";
|
|
2
|
+
import { consumeClaudeStatuslineSpool } from "./collectors/claude-statusline.js";
|
|
3
|
+
import { collectClaudeOAuth } from "./collectors/claude-oauth.js";
|
|
4
|
+
import { collectGrok } from "./collectors/grok.js";
|
|
5
|
+
export declare const ENABLE_TICK_MS = 60000;
|
|
6
|
+
export declare const REBUILD_TICK_MS = 60000;
|
|
7
|
+
export declare const POLL_TICK_MS: number;
|
|
8
|
+
export declare const CODEX_DIR_POLL_MS = 15000;
|
|
9
|
+
export declare const SPOOL_POLL_MS = 15000;
|
|
10
|
+
export interface CapacityTimers {
|
|
11
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
12
|
+
clearTimeout(handle: unknown): void;
|
|
13
|
+
}
|
|
14
|
+
export interface CapacityWatch {
|
|
15
|
+
close(): void;
|
|
16
|
+
on?(event: "error", listener: (err: unknown) => void): unknown;
|
|
17
|
+
}
|
|
18
|
+
export type CapacityWatchFn = (target: string, options: {
|
|
19
|
+
persistent?: boolean;
|
|
20
|
+
recursive?: boolean;
|
|
21
|
+
}, listener: (event: string, filename: string | Buffer | null) => void) => CapacityWatch;
|
|
22
|
+
export interface CapacitySentryEvent {
|
|
23
|
+
provider: string;
|
|
24
|
+
source: string;
|
|
25
|
+
cliVersion: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CapacityCollectors {
|
|
28
|
+
tailCodexSessions: typeof tailCodexSessions;
|
|
29
|
+
ingestCodexResult: typeof ingestCodexResult;
|
|
30
|
+
shouldReadAppServer: typeof shouldReadAppServer;
|
|
31
|
+
isCodexProcessRunning: typeof isCodexProcessRunning;
|
|
32
|
+
readCodexAppServer: typeof readCodexAppServer;
|
|
33
|
+
latestCodexSessionWriteAt: typeof latestCodexSessionWriteAt;
|
|
34
|
+
readInstalledCodexVersion: typeof readInstalledCodexVersion;
|
|
35
|
+
consumeClaudeStatuslineSpool: typeof consumeClaudeStatuslineSpool;
|
|
36
|
+
collectClaudeOAuth: typeof collectClaudeOAuth;
|
|
37
|
+
collectGrok: typeof collectGrok;
|
|
38
|
+
readInstalledClaudeVersion: () => string | Promise<string>;
|
|
39
|
+
readInstalledGrokVersion: () => string | Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
export interface StartCapacitySchedulerOptions {
|
|
42
|
+
timers: CapacityTimers;
|
|
43
|
+
root: string;
|
|
44
|
+
collectors?: Partial<CapacityCollectors>;
|
|
45
|
+
now?: () => Date;
|
|
46
|
+
log?: (line: string) => void;
|
|
47
|
+
home?: string;
|
|
48
|
+
sessionsDir?: string;
|
|
49
|
+
watch?: CapacityWatchFn;
|
|
50
|
+
/** When omitted, recursive fs.watch is used on darwin and win32. */
|
|
51
|
+
recursive?: boolean;
|
|
52
|
+
ps?: () => string;
|
|
53
|
+
sentry?: (event: CapacitySentryEvent) => void;
|
|
54
|
+
selfTest?: (provider: string, version: string) => void | {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Production daemon sets this. Tests omit it and keep the single default
|
|
59
|
+
* home, so collector mocks stay one call per provider.
|
|
60
|
+
*/
|
|
61
|
+
enumerateAccounts?: boolean;
|
|
62
|
+
/** HQ root that holds personal/lanes/config.json. */
|
|
63
|
+
hqRoot?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface CapacitySchedulerHandle {
|
|
66
|
+
stop: () => void;
|
|
67
|
+
ticks: () => number;
|
|
68
|
+
}
|
|
69
|
+
export declare function defaultCodexSessionsDir(home?: string): string;
|
|
70
|
+
export declare function capacityFixtureDir(): string;
|
|
71
|
+
/** Parse the pinned fixtures once. Missing fixtures are a skip, not a failure. */
|
|
72
|
+
export declare function runParserSelfTest(provider: string, version: string, fixturesDir?: string): {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
};
|
|
75
|
+
export declare function startCapacityScheduler(options: StartCapacitySchedulerOptions): CapacitySchedulerHandle;
|
|
76
|
+
//# sourceMappingURL=scheduler.d.ts.map
|