@indigoai-us/hq-cli 5.345.159 → 5.345.161
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 +48 -0
- package/assets/scaffold/core/scripts/session-journal.sh +116 -24
- package/dist/command-catalog.generated.d.ts +222 -9
- package/dist/command-catalog.generated.js +273 -9
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agents.js +5 -11
- package/dist/commands/auth.js +6 -5
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.js +7 -7
- package/dist/commands/core-session-helper-runtime.d.ts +1 -0
- package/dist/commands/core-session-journal-native.d.ts +1 -0
- package/dist/commands/core-session-journal-native.js +158 -34
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/feedback.d.ts +12 -0
- package/dist/commands/feedback.js +52 -0
- package/dist/commands/files-browse.d.ts +8 -0
- package/dist/commands/files-browse.js +83 -1
- package/dist/commands/files.js +19 -7
- package/dist/commands/install-global.d.ts +2 -0
- package/dist/commands/install-global.js +24 -1
- package/dist/commands/integrations-connect.js +1 -0
- package/dist/commands/integrations-manage.d.ts +2 -0
- package/dist/commands/integrations-manage.js +6 -4
- package/dist/commands/lanes-link.js +5 -3
- package/dist/commands/lanes-session-start.d.ts +12 -0
- package/dist/commands/lanes-session-start.js +93 -12
- package/dist/commands/meetings.d.ts +15 -4
- package/dist/commands/meetings.js +58 -19
- package/dist/commands/skill.js +6 -6
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3236 -1988
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +21 -0
- package/dist/lanes-session-start-argv.d.ts +7 -0
- package/dist/lanes-session-start-argv.js +9 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/bot/log.d.ts +3 -2
- package/dist/lib/bot/progress.d.ts +23 -1
- package/dist/lib/bot/progress.js +43 -5
- package/dist/lib/bot/run.js +2 -0
- package/dist/lib/bot/runtime/claude.d.ts +12 -0
- package/dist/lib/bot/runtime/claude.js +20 -0
- package/dist/lib/bot/runtime/index.d.ts +10 -1
- package/dist/lib/bot/runtime/index.js +14 -2
- package/dist/lib/bot/runtime/types.d.ts +8 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.js +10 -0
- package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
- package/dist/lib/doctor/checks/ps1-shims.js +82 -0
- package/dist/lib/doctor/fix/apply.d.ts +13 -1
- package/dist/lib/doctor/fix/apply.js +38 -3
- package/dist/lib/doctor/fix/remediation.d.ts +9 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/registry.js +2 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/lanes/links.d.ts +2 -1
- package/dist/lib/lanes/links.js +235 -17
- package/dist/lib/lanes/registry-flag.d.ts +24 -0
- package/dist/lib/lanes/registry-flag.js +72 -6
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +8 -8
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/lib/windows/ps1-shims.d.ts +60 -0
- package/dist/lib/windows/ps1-shims.js +152 -0
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +8 -0
- package/dist/utils/staged-npm-install.js +13 -2
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- package/dist/utils/token-keychain.d.ts +11 -8
- package/dist/utils/token-keychain.js +36 -15
- package/package.json +8 -8
- package/scripts/install-hook-engine.mjs +12 -0
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* - optionally, the live cloud file-history listing `hq storage status`
|
|
14
14
|
* uses, to split storage into current files and old versions and to see a
|
|
15
15
|
* prune right away (Storage Lens, behind usage-limits, updates daily).
|
|
16
|
+
* With the usage statement, the listing is a separate current-storage
|
|
17
|
+
* breakdown only; the statement's month-average storage meter is billed.
|
|
16
18
|
* usage-limits also answers `vaults` when the server counts them; without it
|
|
17
19
|
* the vault meter is `not-available`.
|
|
18
20
|
* The call-count meters (syncs, gateway calls, HQ MCP calls, agent runs) have
|
|
@@ -145,6 +147,83 @@ export function dailyRollupSource(fetchRows) {
|
|
|
145
147
|
},
|
|
146
148
|
};
|
|
147
149
|
}
|
|
150
|
+
/** Statement line key (hq-pro `UsageStatementLine.key`) -> rate-card key. */
|
|
151
|
+
const STATEMENT_KEYS = new Set(BUNDLED_USAGE_METER_RATES.map(({ key }) => key));
|
|
152
|
+
/**
|
|
153
|
+
* Meter readings from a `GET /v1/billing/usage` statement body (hq-pro
|
|
154
|
+
* src/billing/usage-statement.ts, US-006). `usage` is the natural amount:
|
|
155
|
+
* counters are the month's total, gauges the mean daily level. Storage comes
|
|
156
|
+
* in bytes and is converted with the statement's own `storageBytesPerGb`.
|
|
157
|
+
* A line that is unavailable, not billed yet, or missing is `not-available`,
|
|
158
|
+
* never zero. Null when the body is not a statement.
|
|
159
|
+
*/
|
|
160
|
+
function centsField(v) {
|
|
161
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
162
|
+
}
|
|
163
|
+
export function meterReadingsFromStatement(body) {
|
|
164
|
+
const rec = body !== null && typeof body === "object" && !Array.isArray(body)
|
|
165
|
+
? body
|
|
166
|
+
: null;
|
|
167
|
+
if (!rec || !Array.isArray(rec.lines))
|
|
168
|
+
return null;
|
|
169
|
+
const bytesPerGb = typeof rec.storageBytesPerGb === "number" && rec.storageBytesPerGb > 0
|
|
170
|
+
? rec.storageBytesPerGb
|
|
171
|
+
: BYTES_PER_GB;
|
|
172
|
+
const byKey = new Map();
|
|
173
|
+
for (const raw of rec.lines) {
|
|
174
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
175
|
+
continue;
|
|
176
|
+
const line = raw;
|
|
177
|
+
if (typeof line.key !== "string" || !STATEMENT_KEYS.has(line.key))
|
|
178
|
+
continue;
|
|
179
|
+
const key = line.key;
|
|
180
|
+
const measured = line.status === "measured" || line.status === "partial";
|
|
181
|
+
const usage = line.usage;
|
|
182
|
+
if (!measured || typeof usage !== "number" || !Number.isFinite(usage) || usage < 0) {
|
|
183
|
+
byKey.set(key, { key, value: null, state: "not-available" });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const reading = { key, value: key === "storage" ? usage / bytesPerGb : usage, state: "measured" };
|
|
187
|
+
const cost = centsField(line.costCents);
|
|
188
|
+
const projected = centsField(line.projectedCostCents);
|
|
189
|
+
if (cost !== null && projected !== null) {
|
|
190
|
+
reading.accrued = {
|
|
191
|
+
monthToDateUsd: cost / 100,
|
|
192
|
+
projectedUsd: projected / 100,
|
|
193
|
+
...(line.waived === true ? { waived: true } : {}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
byKey.set(key, reading);
|
|
197
|
+
}
|
|
198
|
+
return BUNDLED_USAGE_METER_RATES.map(({ key }) => byKey.get(key) ?? { key, value: null, state: "not-available" });
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The usage statement as a source (`GET /v1/billing/usage`, US-015). The
|
|
202
|
+
* company statement is owner-gated on the server; a person statement is the
|
|
203
|
+
* caller's own. `fetchStatement` resolves the decoded body, or null when the
|
|
204
|
+
* server did not answer.
|
|
205
|
+
*/
|
|
206
|
+
export function usageStatementSource(fetchStatement) {
|
|
207
|
+
return {
|
|
208
|
+
name: "usage-statement",
|
|
209
|
+
async read(scope, now) {
|
|
210
|
+
const { start, end } = monthWindow(now);
|
|
211
|
+
const body = await fetchStatement(scope);
|
|
212
|
+
const meters = meterReadingsFromStatement(body);
|
|
213
|
+
if (meters === null) {
|
|
214
|
+
throw new Error("Could not read usage from HQ right now. Try again, or run `hq login` if you are signed out.");
|
|
215
|
+
}
|
|
216
|
+
const allowanceCents = centsField(body.allowanceCents);
|
|
217
|
+
return {
|
|
218
|
+
scope,
|
|
219
|
+
source: "usage-statement",
|
|
220
|
+
period: { start: start.toISOString(), end: end.toISOString(), asOf: now.toISOString() },
|
|
221
|
+
meters,
|
|
222
|
+
...(allowanceCents !== null ? { allowanceUsd: allowanceCents / 100 } : {}),
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
148
227
|
/**
|
|
149
228
|
* The one function every caller uses to read a month of usage. Switch the
|
|
150
229
|
* default source here when the US-005 rollup has a read route.
|
|
@@ -159,8 +238,15 @@ export async function readMonthUsage(scope, deps, source = usageLimitsSource(dep
|
|
|
159
238
|
const split = await deps.readStorageSplit(scope).catch(() => null);
|
|
160
239
|
if (!split)
|
|
161
240
|
return base;
|
|
162
|
-
//
|
|
163
|
-
//
|
|
241
|
+
// An accrued source (the usage statement, the daily rollup) bills storage as
|
|
242
|
+
// the month's average. That meter is authoritative: the live listing is a
|
|
243
|
+
// point-in-time figure, kept only as the separate `storage` breakdown that
|
|
244
|
+
// explains what is taking space. It never replaces or re-prices the meter.
|
|
245
|
+
if (base.source !== "usage-limits")
|
|
246
|
+
return { ...base, storage: split };
|
|
247
|
+
// usage-limits storage is itself a point-in-time reading (Storage Lens,
|
|
248
|
+
// updated daily). The live listing measures the same thing more freshly, so
|
|
249
|
+
// a prune shows up in the very next read.
|
|
164
250
|
const liveGb = (split.currentBytes + split.oldVersionBytes) / BYTES_PER_GB;
|
|
165
251
|
return {
|
|
166
252
|
...base,
|
|
@@ -169,7 +255,8 @@ export async function readMonthUsage(scope, deps, source = usageLimitsSource(dep
|
|
|
169
255
|
};
|
|
170
256
|
}
|
|
171
257
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
172
|
-
export function computeBill(usage, rates = BUNDLED_USAGE_METER_RATES,
|
|
258
|
+
export function computeBill(usage, rates = BUNDLED_USAGE_METER_RATES, cardAllowanceUsd = USAGE_MONTHLY_ALLOWANCE_USD) {
|
|
259
|
+
const allowanceUsd = usage.allowanceUsd ?? cardAllowanceUsd;
|
|
173
260
|
const asOf = new Date(usage.period.asOf);
|
|
174
261
|
const { start, end } = monthWindow(asOf);
|
|
175
262
|
const dayMs = 86_400_000;
|
|
@@ -187,6 +274,19 @@ export function computeBill(usage, rates = BUNDLED_USAGE_METER_RATES, allowanceU
|
|
|
187
274
|
incomplete = true;
|
|
188
275
|
return { ...pick(rate), value: null, state: reading.state, monthToDateUsd: null, projectedUsd: null };
|
|
189
276
|
}
|
|
277
|
+
if (reading.accrued) {
|
|
278
|
+
const { monthToDateUsd: m, projectedUsd: p, waived } = reading.accrued;
|
|
279
|
+
mtd += m;
|
|
280
|
+
projected += p;
|
|
281
|
+
return {
|
|
282
|
+
...pick(rate),
|
|
283
|
+
value: reading.value,
|
|
284
|
+
state: "measured",
|
|
285
|
+
monthToDateUsd: round2(m),
|
|
286
|
+
projectedUsd: round2(p),
|
|
287
|
+
...(waived ? { waivedInTrial: true } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
190
290
|
if (rate.waivedInTrial && usage.trialActive === true) {
|
|
191
291
|
return { ...pick(rate), value: reading.value, state: "measured", monthToDateUsd: 0, projectedUsd: 0, waivedInTrial: true };
|
|
192
292
|
}
|
|
@@ -24,8 +24,44 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { type UsageCard } from "./usage-card.js";
|
|
26
26
|
export type PricingPlan = "usage-only" | "current";
|
|
27
|
+
/** Whether a server supplied shared status (including an unreadable/null value). */
|
|
28
|
+
export declare function hasBillingStateField(body: unknown): boolean;
|
|
29
|
+
export type BillingStateKind = "usage" | "usage-trial" | "legacy-payer" | "legacy-free" | "enterprise" | "comped";
|
|
27
30
|
/**
|
|
28
|
-
*
|
|
31
|
+
* hq-pro's shared plan status (`billingState`, hq-pro #5088
|
|
32
|
+
* src/billing/billing-state.ts), the fields the CLI reads. Version 1 only.
|
|
33
|
+
*/
|
|
34
|
+
export interface SharedBillingState {
|
|
35
|
+
kind: BillingStateKind;
|
|
36
|
+
scope: "company" | "person";
|
|
37
|
+
view: "full" | "member";
|
|
38
|
+
trial: {
|
|
39
|
+
daysLeft: number;
|
|
40
|
+
endsAt: string;
|
|
41
|
+
} | null;
|
|
42
|
+
/** A pause the server is applying now, or null. */
|
|
43
|
+
paused: {
|
|
44
|
+
reason: string;
|
|
45
|
+
resumesOn: string | null;
|
|
46
|
+
} | null;
|
|
47
|
+
/** What this caller may do. All false in the member view. */
|
|
48
|
+
actions: {
|
|
49
|
+
addCard: boolean;
|
|
50
|
+
raiseLimit: boolean;
|
|
51
|
+
switchToUsage: boolean;
|
|
52
|
+
manageBilling: boolean;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read a known v1 `billingState` off a decoded body. An unknown version,
|
|
57
|
+
* scope, view or kind (or a malformed trial) is null: it is never read as a
|
|
58
|
+
* legacy plan. A person state is always the full view.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readBillingState(body: unknown): SharedBillingState | null;
|
|
61
|
+
export { usagePauseFromBody, type UsagePause } from "./usage-pause.js";
|
|
62
|
+
/**
|
|
63
|
+
* Read the pricing plan out of a decoded hq-pro body. Known `billingState` wins;
|
|
64
|
+
* only a server without that field uses the legacy `pricingPlan` signal;
|
|
29
65
|
* a `personalUsage` object (person summary) means usage-only. Anything else
|
|
30
66
|
* is UNKNOWN.
|
|
31
67
|
*/
|
|
@@ -40,8 +40,84 @@ function asRecord(value) {
|
|
|
40
40
|
? value
|
|
41
41
|
: null;
|
|
42
42
|
}
|
|
43
|
+
/** Whether a server supplied shared status (including an unreadable/null value). */
|
|
44
|
+
export function hasBillingStateField(body) {
|
|
45
|
+
const rec = asRecord(body);
|
|
46
|
+
return rec !== null && Object.prototype.hasOwnProperty.call(rec, "billingState");
|
|
47
|
+
}
|
|
48
|
+
const BILLING_STATE_KINDS = [
|
|
49
|
+
"usage",
|
|
50
|
+
"usage-trial",
|
|
51
|
+
"legacy-payer",
|
|
52
|
+
"legacy-free",
|
|
53
|
+
"enterprise",
|
|
54
|
+
"comped",
|
|
55
|
+
];
|
|
56
|
+
function readTrial(value) {
|
|
57
|
+
if (value === null || value === undefined)
|
|
58
|
+
return null;
|
|
59
|
+
const trial = asRecord(value);
|
|
60
|
+
if (!trial ||
|
|
61
|
+
typeof trial.daysLeft !== "number" ||
|
|
62
|
+
!Number.isFinite(trial.daysLeft) ||
|
|
63
|
+
trial.daysLeft < 0 ||
|
|
64
|
+
typeof trial.endsAt !== "string" ||
|
|
65
|
+
!Number.isFinite(Date.parse(trial.endsAt))) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return { daysLeft: trial.daysLeft, endsAt: trial.endsAt };
|
|
69
|
+
}
|
|
70
|
+
function readPaused(value) {
|
|
71
|
+
const paused = asRecord(value);
|
|
72
|
+
if (!paused || typeof paused.reason !== "string" || !paused.reason)
|
|
73
|
+
return null;
|
|
74
|
+
return {
|
|
75
|
+
reason: paused.reason,
|
|
76
|
+
resumesOn: typeof paused.resumesOn === "string" ? paused.resumesOn : null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
43
79
|
/**
|
|
44
|
-
* Read
|
|
80
|
+
* Read a known v1 `billingState` off a decoded body. An unknown version,
|
|
81
|
+
* scope, view or kind (or a malformed trial) is null: it is never read as a
|
|
82
|
+
* legacy plan. A person state is always the full view.
|
|
83
|
+
*/
|
|
84
|
+
export function readBillingState(body) {
|
|
85
|
+
const state = asRecord(asRecord(body)?.billingState);
|
|
86
|
+
if (!state || state.version !== 1)
|
|
87
|
+
return null;
|
|
88
|
+
if (state.scope !== "company" && state.scope !== "person")
|
|
89
|
+
return null;
|
|
90
|
+
if (state.view !== "full" && state.view !== "member")
|
|
91
|
+
return null;
|
|
92
|
+
if (state.scope === "person" && state.view !== "full")
|
|
93
|
+
return null;
|
|
94
|
+
if (typeof state.kind !== "string" || !BILLING_STATE_KINDS.includes(state.kind))
|
|
95
|
+
return null;
|
|
96
|
+
const trial = readTrial(state.trial);
|
|
97
|
+
if (trial === undefined)
|
|
98
|
+
return null;
|
|
99
|
+
const actions = asRecord(state.actions);
|
|
100
|
+
return {
|
|
101
|
+
kind: state.kind,
|
|
102
|
+
scope: state.scope,
|
|
103
|
+
view: state.view,
|
|
104
|
+
trial,
|
|
105
|
+
paused: readPaused(state.paused),
|
|
106
|
+
actions: {
|
|
107
|
+
addCard: actions?.addCard === true,
|
|
108
|
+
raiseLimit: actions?.raiseLimit === true,
|
|
109
|
+
switchToUsage: actions?.switchToUsage === true,
|
|
110
|
+
manageBilling: actions?.manageBilling === true,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function sharedBillingState(body) {
|
|
115
|
+
return readBillingState(body);
|
|
116
|
+
}
|
|
117
|
+
export { usagePauseFromBody } from "./usage-pause.js";
|
|
118
|
+
/**
|
|
119
|
+
* Read the pricing plan out of a decoded hq-pro body. Known `billingState` wins;
|
|
120
|
+
* only a server without that field uses the legacy `pricingPlan` signal;
|
|
45
121
|
* a `personalUsage` object (person summary) means usage-only. Anything else
|
|
46
122
|
* is UNKNOWN.
|
|
47
123
|
*/
|
|
@@ -49,6 +125,10 @@ export function pricingPlanFromBody(body) {
|
|
|
49
125
|
const rec = asRecord(body);
|
|
50
126
|
if (!rec)
|
|
51
127
|
return null;
|
|
128
|
+
if (hasBillingStateField(rec)) {
|
|
129
|
+
const state = sharedBillingState(rec);
|
|
130
|
+
return state ? (state.kind === "usage" || state.kind === "usage-trial" ? "usage-only" : "current") : null;
|
|
131
|
+
}
|
|
52
132
|
if (rec.pricingPlan === "usage-only")
|
|
53
133
|
return "usage-only";
|
|
54
134
|
if (rec.pricingPlan === "current")
|
|
@@ -62,6 +142,8 @@ export function isUsageOnly(plan) {
|
|
|
62
142
|
}
|
|
63
143
|
/** True when a company summary says a usage-only trial is running. Absent is false. */
|
|
64
144
|
export function usageTrialActiveFromBody(body) {
|
|
145
|
+
if (hasBillingStateField(body))
|
|
146
|
+
return sharedBillingState(body)?.kind === "usage-trial";
|
|
65
147
|
return asRecord(asRecord(body)?.usageTrial)?.status === "active";
|
|
66
148
|
}
|
|
67
149
|
async function defaultGetJson(token, path, query, timeoutMs) {
|
|
@@ -111,9 +193,12 @@ export function pricingPlanFromMembership(body, companyRef) {
|
|
|
111
193
|
const slug = typeof row.companySlug === "string" ? row.companySlug.toLowerCase() : "";
|
|
112
194
|
if (uid !== ref && slug !== ref)
|
|
113
195
|
continue;
|
|
114
|
-
if (row
|
|
115
|
-
|
|
116
|
-
|
|
196
|
+
if (hasBillingStateField(row)) {
|
|
197
|
+
const state = sharedBillingState(row);
|
|
198
|
+
if (!state || state.scope !== "company")
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return pricingPlanFromBody(row);
|
|
117
202
|
}
|
|
118
203
|
return null;
|
|
119
204
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage pause block on a 402 body (hq-pro src/billing/usage-pause.ts,
|
|
3
|
+
* US-024): `{ code: "usage_paused", reason, message, actionUrl }`.
|
|
4
|
+
*
|
|
5
|
+
* Every refusal hq-pro makes because of an account's usage state (the trial
|
|
6
|
+
* cap, a card needed past the monthly allowance, the monthly spend limit)
|
|
7
|
+
* carries it. A client shows `message` as written and links `actionUrl`; it
|
|
8
|
+
* adds no plan name and no upgrade offer.
|
|
9
|
+
*
|
|
10
|
+
* Leaf module (no imports) so the sync refusal tracker and the 402 decoder
|
|
11
|
+
* can share it without loading the HTTP layer.
|
|
12
|
+
*/
|
|
13
|
+
export declare const USAGE_PAUSE_CODE = "usage_paused";
|
|
14
|
+
export interface UsagePause {
|
|
15
|
+
/** `usage_trial_cap` | `usage_card_required` | `usage_spend_limit` (or a newer code). */
|
|
16
|
+
reason: string;
|
|
17
|
+
/** One plain-language paragraph from the server. */
|
|
18
|
+
message: string;
|
|
19
|
+
/** Where to act: the company or personal Billing page (https). */
|
|
20
|
+
actionUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read `usagePause` off a decoded 402 body, or off any record that carries
|
|
24
|
+
* it. Null when absent or malformed (wrong code, empty message).
|
|
25
|
+
*/
|
|
26
|
+
export declare function usagePauseFromBody(body: unknown): UsagePause | null;
|
|
27
|
+
//# sourceMappingURL=usage-pause.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage pause block on a 402 body (hq-pro src/billing/usage-pause.ts,
|
|
3
|
+
* US-024): `{ code: "usage_paused", reason, message, actionUrl }`.
|
|
4
|
+
*
|
|
5
|
+
* Every refusal hq-pro makes because of an account's usage state (the trial
|
|
6
|
+
* cap, a card needed past the monthly allowance, the monthly spend limit)
|
|
7
|
+
* carries it. A client shows `message` as written and links `actionUrl`; it
|
|
8
|
+
* adds no plan name and no upgrade offer.
|
|
9
|
+
*
|
|
10
|
+
* Leaf module (no imports) so the sync refusal tracker and the 402 decoder
|
|
11
|
+
* can share it without loading the HTTP layer.
|
|
12
|
+
*/
|
|
13
|
+
export const USAGE_PAUSE_CODE = "usage_paused";
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
16
|
+
? value
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
function httpsUrl(value) {
|
|
20
|
+
if (typeof value !== "string" || value.trim() !== value)
|
|
21
|
+
return undefined;
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
return url.protocol === "https:" && url.hostname && !url.username && !url.password
|
|
25
|
+
? value
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read `usagePause` off a decoded 402 body, or off any record that carries
|
|
34
|
+
* it. Null when absent or malformed (wrong code, empty message).
|
|
35
|
+
*/
|
|
36
|
+
export function usagePauseFromBody(body) {
|
|
37
|
+
const pause = asRecord(asRecord(body)?.usagePause);
|
|
38
|
+
if (!pause || pause.code !== USAGE_PAUSE_CODE)
|
|
39
|
+
return null;
|
|
40
|
+
const message = typeof pause.message === "string" ? pause.message.trim() : "";
|
|
41
|
+
if (!message)
|
|
42
|
+
return null;
|
|
43
|
+
const actionUrl = httpsUrl(pause.actionUrl);
|
|
44
|
+
return {
|
|
45
|
+
reason: typeof pause.reason === "string" ? pause.reason : "",
|
|
46
|
+
message,
|
|
47
|
+
...(actionUrl ? { actionUrl } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=usage-pause.js.map
|
package/dist/lib/bot/log.d.ts
CHANGED
|
@@ -6,8 +6,9 @@ export declare function ensureBotLog(dir: string): string;
|
|
|
6
6
|
export declare function rotateBotLogIfNeeded(dir: string, maxBytes?: number): void;
|
|
7
7
|
/** Strip anything that looks like a bearer token, secret, or op:// ref. */
|
|
8
8
|
export declare function redactForLog(message: string): string;
|
|
9
|
-
export
|
|
9
|
+
export type BotLogLevel = "debug" | "info" | "warn" | "error";
|
|
10
|
+
export declare function botLogLine(level: BotLogLevel, message: string, now?: () => Date): string;
|
|
10
11
|
export declare function appendBotLog(dir: string, line: string): void;
|
|
11
|
-
export type BotLogger = (level:
|
|
12
|
+
export type BotLogger = (level: BotLogLevel, message: string) => void;
|
|
12
13
|
export declare function createBotLogger(dir: string, now?: () => Date): BotLogger;
|
|
13
14
|
//# sourceMappingURL=log.d.ts.map
|
|
@@ -54,7 +54,23 @@ export interface ProgressPosterOptions {
|
|
|
54
54
|
* it, anything else (another message, the end of the turn) keeps it.
|
|
55
55
|
*/
|
|
56
56
|
dropPreToolMessages?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Called with each message dropped because a tool call followed it (see
|
|
59
|
+
* `dropPreToolMessages` and `isPreToolAside`), so the bot log can record it.
|
|
60
|
+
*/
|
|
61
|
+
onDropped?: (text: string) => void;
|
|
57
62
|
}
|
|
63
|
+
/** Below this many characters a message right before a tool call is an aside, not a progress note. */
|
|
64
|
+
export declare const PRE_TOOL_ASIDE_MAX_CHARS = 40;
|
|
65
|
+
/**
|
|
66
|
+
* True for a short assistant text that is an aside to itself when a tool call
|
|
67
|
+
* follows it ("Bind first, separately." reached a person on 2026-10-13: the
|
|
68
|
+
* model thinking out loud before a tool_use in the same assistant message).
|
|
69
|
+
* Real progress notes are full sentences addressed to the person ("Reading
|
|
70
|
+
* your agency docs now, then I'll look at the client folders.") and run past
|
|
71
|
+
* the limit; an hq-block is always content the desktop app must draw.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isPreToolAside(text: string): boolean;
|
|
58
74
|
/** Whitespace-insensitive form used to recognise repeated text. */
|
|
59
75
|
export declare function normalizeForCompare(text: string): string;
|
|
60
76
|
/** Matches {"action":"no_reply"} anywhere in a model reply, with or without preamble text. */
|
|
@@ -71,7 +87,13 @@ export declare class ProgressPoster {
|
|
|
71
87
|
private readonly workingNoticeText;
|
|
72
88
|
private readonly buffer;
|
|
73
89
|
private readonly dropPreTool;
|
|
74
|
-
|
|
90
|
+
private readonly onDropped;
|
|
91
|
+
/**
|
|
92
|
+
* Messages waiting to learn whether a tool call follows: every message when
|
|
93
|
+
* dropPreToolMessages is set, short asides (isPreToolAside) otherwise. The
|
|
94
|
+
* runtime streams one content block per event, so "followed by a tool_use
|
|
95
|
+
* in the same assistant message" is "the next event is a tool call".
|
|
96
|
+
*/
|
|
75
97
|
private candidates;
|
|
76
98
|
private pending;
|
|
77
99
|
/** In-between questions, held until the turn ends (see the header). */
|
package/dist/lib/bot/progress.js
CHANGED
|
@@ -27,6 +27,20 @@ export const PROGRESS_COALESCE_MS = 2_000;
|
|
|
27
27
|
export const PROGRESS_MIN_GAP_MS = 4_000;
|
|
28
28
|
export const WORKING_NOTICE_MS = 60_000;
|
|
29
29
|
export const WORKING_NOTICE_TEXT = "Working on it — I'll reply here when it's done.";
|
|
30
|
+
/** Below this many characters a message right before a tool call is an aside, not a progress note. */
|
|
31
|
+
export const PRE_TOOL_ASIDE_MAX_CHARS = 40;
|
|
32
|
+
/**
|
|
33
|
+
* True for a short assistant text that is an aside to itself when a tool call
|
|
34
|
+
* follows it ("Bind first, separately." reached a person on 2026-10-13: the
|
|
35
|
+
* model thinking out loud before a tool_use in the same assistant message).
|
|
36
|
+
* Real progress notes are full sentences addressed to the person ("Reading
|
|
37
|
+
* your agency docs now, then I'll look at the client folders.") and run past
|
|
38
|
+
* the limit; an hq-block is always content the desktop app must draw.
|
|
39
|
+
*/
|
|
40
|
+
export function isPreToolAside(text) {
|
|
41
|
+
const t = text.trim();
|
|
42
|
+
return t.length < PRE_TOOL_ASIDE_MAX_CHARS && !t.includes("```hq-block");
|
|
43
|
+
}
|
|
30
44
|
/** Whitespace-insensitive form used to recognise repeated text. */
|
|
31
45
|
export function normalizeForCompare(text) {
|
|
32
46
|
return text.replace(/\s+/g, "");
|
|
@@ -47,7 +61,13 @@ export class ProgressPoster {
|
|
|
47
61
|
workingNoticeText;
|
|
48
62
|
buffer;
|
|
49
63
|
dropPreTool;
|
|
50
|
-
|
|
64
|
+
onDropped;
|
|
65
|
+
/**
|
|
66
|
+
* Messages waiting to learn whether a tool call follows: every message when
|
|
67
|
+
* dropPreToolMessages is set, short asides (isPreToolAside) otherwise. The
|
|
68
|
+
* runtime streams one content block per event, so "followed by a tool_use
|
|
69
|
+
* in the same assistant message" is "the next event is a tool call".
|
|
70
|
+
*/
|
|
51
71
|
candidates = [];
|
|
52
72
|
pending = [];
|
|
53
73
|
/** In-between questions, held until the turn ends (see the header). */
|
|
@@ -82,6 +102,7 @@ export class ProgressPoster {
|
|
|
82
102
|
this.workingNoticeText = opts.workingNoticeText ?? WORKING_NOTICE_TEXT;
|
|
83
103
|
this.buffer = opts.buffer ?? false;
|
|
84
104
|
this.dropPreTool = opts.dropPreToolMessages ?? false;
|
|
105
|
+
this.onDropped = opts.onDropped ?? (() => undefined);
|
|
85
106
|
}
|
|
86
107
|
/** Arm the one-time "working on it" note. Call when the turn starts. */
|
|
87
108
|
start() {
|
|
@@ -93,6 +114,12 @@ export class ProgressPoster {
|
|
|
93
114
|
this.noticeTimer = null;
|
|
94
115
|
if (this.finished || this.said.length > 0 || this.noticeSent)
|
|
95
116
|
return;
|
|
117
|
+
// A short message is waiting to learn whether a tool call follows: if it
|
|
118
|
+
// was real, it is the model's own "working on it"; look again later.
|
|
119
|
+
if (!this.dropPreTool && this.candidates.length > 0) {
|
|
120
|
+
this.start();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
96
123
|
this.noticeSent = true;
|
|
97
124
|
this.enqueueWith(this.workingNoticeText, this.sendProgress);
|
|
98
125
|
}, this.workingNoticeMs);
|
|
@@ -106,9 +133,9 @@ export class ProgressPoster {
|
|
|
106
133
|
// silence signal, not content, and must never be posted as progress.
|
|
107
134
|
if (containsNoReplySentinel(t))
|
|
108
135
|
return;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
// The previous message was not followed by a tool call: it was real.
|
|
137
|
+
this.promoteCandidates();
|
|
138
|
+
if (this.dropPreTool || isPreToolAside(t)) {
|
|
112
139
|
this.candidates.push(t);
|
|
113
140
|
return;
|
|
114
141
|
}
|
|
@@ -116,9 +143,18 @@ export class ProgressPoster {
|
|
|
116
143
|
}
|
|
117
144
|
/** The model called a tool: whatever it wrote just before was a note to itself. */
|
|
118
145
|
tool() {
|
|
119
|
-
if (this.finished
|
|
146
|
+
if (this.finished)
|
|
120
147
|
return;
|
|
148
|
+
const dropped = this.candidates;
|
|
121
149
|
this.candidates = [];
|
|
150
|
+
for (const t of dropped) {
|
|
151
|
+
try {
|
|
152
|
+
this.onDropped(t);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* a log hook must never break the turn */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
122
158
|
}
|
|
123
159
|
promoteCandidates() {
|
|
124
160
|
const kept = this.candidates;
|
|
@@ -199,6 +235,8 @@ export class ProgressPoster {
|
|
|
199
235
|
async abandon() {
|
|
200
236
|
if (this.finished)
|
|
201
237
|
return this.chain;
|
|
238
|
+
// A message the turn ended on (no tool after it) is real.
|
|
239
|
+
this.promoteCandidates();
|
|
202
240
|
this.finished = true;
|
|
203
241
|
this.cancelNotice();
|
|
204
242
|
this.cancelFlush();
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -1118,6 +1118,8 @@ export async function runBot(deps) {
|
|
|
1118
1118
|
// reach them, and it posts no "still working" notice either — the
|
|
1119
1119
|
// desktop app shows that the bot is working.
|
|
1120
1120
|
...(isSetupBot ? { dropPreToolMessages: true } : {}),
|
|
1121
|
+
// Short asides before a tool call never reach the person either (isPreToolAside).
|
|
1122
|
+
onDropped: (text) => log("debug", `dropped a pre-tool aside for ${messageId} (${text.length} chars): ${text}`),
|
|
1121
1123
|
...deps.progress,
|
|
1122
1124
|
...(args.workingNotice === false || silenceable || isSetupBot ? { workingNoticeMs: 0 } : {}),
|
|
1123
1125
|
...(silenceable || args.suppressOutput ? { buffer: true } : {}),
|
|
@@ -27,6 +27,18 @@ export declare const FALLBACK_CLAUDE_PERMISSION_MODE = "auto";
|
|
|
27
27
|
export declare const CLAUDE_EFFORT_ENV = "HQ_BOT_CLAUDE_EFFORT";
|
|
28
28
|
export declare const DEFAULT_CLAUDE_EFFORT = "medium";
|
|
29
29
|
export declare function claudeEffort(env?: NodeJS.ProcessEnv): string;
|
|
30
|
+
/**
|
|
31
|
+
* Per-tool-call safety for a bot turn: Claude Code reads these to bound each
|
|
32
|
+
* Bash tool call (its default and the most a call may ask for). On 2026-10-13
|
|
33
|
+
* a bot-authored command ended in `cat > /dev/null`, waited on stdin that
|
|
34
|
+
* never closed, and the turn showed "working" for over 15 minutes. Set only
|
|
35
|
+
* when the environment does not already set them.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CLAUDE_BASH_DEFAULT_TIMEOUT_ENV = "BASH_DEFAULT_TIMEOUT_MS";
|
|
38
|
+
export declare const CLAUDE_BASH_MAX_TIMEOUT_ENV = "BASH_MAX_TIMEOUT_MS";
|
|
39
|
+
export declare const BOT_BASH_TIMEOUT_MS: number;
|
|
40
|
+
/** Claude's `runtimeEnv` layer: the Bash tool timeouts defaulted (pure). */
|
|
41
|
+
export declare function claudeRuntimeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
30
42
|
export declare function claudePermissionMode(env?: NodeJS.ProcessEnv, autoApprove?: boolean, explicit?: string): string;
|
|
31
43
|
/**
|
|
32
44
|
* True when Claude Code refused to start because bypassPermissions is turned
|
|
@@ -34,6 +34,25 @@ export const DEFAULT_CLAUDE_EFFORT = "medium";
|
|
|
34
34
|
export function claudeEffort(env = process.env) {
|
|
35
35
|
return env[CLAUDE_EFFORT_ENV]?.trim() || DEFAULT_CLAUDE_EFFORT;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Per-tool-call safety for a bot turn: Claude Code reads these to bound each
|
|
39
|
+
* Bash tool call (its default and the most a call may ask for). On 2026-10-13
|
|
40
|
+
* a bot-authored command ended in `cat > /dev/null`, waited on stdin that
|
|
41
|
+
* never closed, and the turn showed "working" for over 15 minutes. Set only
|
|
42
|
+
* when the environment does not already set them.
|
|
43
|
+
*/
|
|
44
|
+
export const CLAUDE_BASH_DEFAULT_TIMEOUT_ENV = "BASH_DEFAULT_TIMEOUT_MS";
|
|
45
|
+
export const CLAUDE_BASH_MAX_TIMEOUT_ENV = "BASH_MAX_TIMEOUT_MS";
|
|
46
|
+
export const BOT_BASH_TIMEOUT_MS = 10 * 60 * 1000;
|
|
47
|
+
/** Claude's `runtimeEnv` layer: the Bash tool timeouts defaulted (pure). */
|
|
48
|
+
export function claudeRuntimeEnv(env) {
|
|
49
|
+
const out = { ...env };
|
|
50
|
+
for (const key of [CLAUDE_BASH_DEFAULT_TIMEOUT_ENV, CLAUDE_BASH_MAX_TIMEOUT_ENV]) {
|
|
51
|
+
if (!out[key]?.trim())
|
|
52
|
+
out[key] = String(BOT_BASH_TIMEOUT_MS);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
37
56
|
export function claudePermissionMode(env = process.env, autoApprove = true, explicit) {
|
|
38
57
|
const raw = env[CLAUDE_PERMISSION_MODE_ENV]?.trim();
|
|
39
58
|
if (raw)
|
|
@@ -193,6 +212,7 @@ export function createClaudeRuntime(env = process.env, opts = {}) {
|
|
|
193
212
|
args.push("--session-id", scratch.newSessionId);
|
|
194
213
|
return args;
|
|
195
214
|
},
|
|
215
|
+
runtimeEnv: claudeRuntimeEnv,
|
|
196
216
|
stdinInput(input) {
|
|
197
217
|
return input.platform === "win32"
|
|
198
218
|
? JSON.stringify({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BotRuntimeId } from "../config.js";
|
|
2
|
-
import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult, type SpawnLike } from "./types.js";
|
|
2
|
+
import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult, type SpawnLike, type SpawnOptions } from "./types.js";
|
|
3
3
|
export * from "./types.js";
|
|
4
4
|
export { createClaudeRuntime, claudePermissionMode } from "./claude.js";
|
|
5
5
|
export { createCodexRuntime, codexPrompt } from "./codex.js";
|
|
@@ -19,6 +19,15 @@ export declare function runtimeFor(id: BotRuntimeId, env?: NodeJS.ProcessEnv): B
|
|
|
19
19
|
export declare function descendantPids(pid: number, listProcesses?: () => string): number[];
|
|
20
20
|
/** After a stop, wait this long for the output pipes to close before settling anyway. */
|
|
21
21
|
export declare const STOP_SETTLE_GRACE_MS = 2000;
|
|
22
|
+
/**
|
|
23
|
+
* The child's stdin: a pipe only when this process has something to write to
|
|
24
|
+
* it (a stdin prompt, which is ended right after it is written, or a
|
|
25
|
+
* protocol), otherwise closed (`ignore` = /dev/null). The prompt normally
|
|
26
|
+
* travels on argv, so the model CLI must never inherit or wait on an open
|
|
27
|
+
* stdin: a bot-authored `cat > /dev/null` on 2026-10-13 blocked a turn for
|
|
28
|
+
* over 15 minutes.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runtimeStdinMode(options: Pick<SpawnOptions, "stdin" | "protocol">): "pipe" | "ignore";
|
|
22
31
|
export declare const defaultSpawn: SpawnLike;
|
|
23
32
|
/** Time limit for the short helper commands a turn may run (`--version`, the bundled model catalog). */
|
|
24
33
|
export declare const AUX_COMMAND_TIMEOUT_MS = 20000;
|
|
@@ -71,6 +71,17 @@ function defaultListProcesses() {
|
|
|
71
71
|
}
|
|
72
72
|
/** After a stop, wait this long for the output pipes to close before settling anyway. */
|
|
73
73
|
export const STOP_SETTLE_GRACE_MS = 2_000;
|
|
74
|
+
/**
|
|
75
|
+
* The child's stdin: a pipe only when this process has something to write to
|
|
76
|
+
* it (a stdin prompt, which is ended right after it is written, or a
|
|
77
|
+
* protocol), otherwise closed (`ignore` = /dev/null). The prompt normally
|
|
78
|
+
* travels on argv, so the model CLI must never inherit or wait on an open
|
|
79
|
+
* stdin: a bot-authored `cat > /dev/null` on 2026-10-13 blocked a turn for
|
|
80
|
+
* over 15 minutes.
|
|
81
|
+
*/
|
|
82
|
+
export function runtimeStdinMode(options) {
|
|
83
|
+
return options.stdin !== undefined || options.protocol ? "pipe" : "ignore";
|
|
84
|
+
}
|
|
74
85
|
export const defaultSpawn = (command, args, options) => new Promise((resolve, reject) => {
|
|
75
86
|
let stdout = "";
|
|
76
87
|
let stderr = "";
|
|
@@ -80,7 +91,7 @@ export const defaultSpawn = (command, args, options) => new Promise((resolve, re
|
|
|
80
91
|
const child = spawn(plan.cmd, plan.args, {
|
|
81
92
|
cwd: options.cwd,
|
|
82
93
|
env: options.env,
|
|
83
|
-
stdio: [options
|
|
94
|
+
stdio: [runtimeStdinMode(options), "pipe", "pipe"],
|
|
84
95
|
...(plan.shell ? { shell: plan.shell } : {}),
|
|
85
96
|
...(plan.windowsVerbatimArguments !== undefined
|
|
86
97
|
? { windowsVerbatimArguments: plan.windowsVerbatimArguments }
|
|
@@ -347,7 +358,8 @@ export async function runRuntimeTurn(runtime, input, deps = {}) {
|
|
|
347
358
|
const args = runtime.buildArgs(remembered ? { ...turnInput, model: remembered.to } : turnInput, { outputFile, newSessionId });
|
|
348
359
|
// Enforce at the model boundary too: old plists and direct `bot run` callers
|
|
349
360
|
// may lack the guard. A bot prompt must never quit the user's desktop app.
|
|
350
|
-
const
|
|
361
|
+
const baseEnv = { ...(input.env ?? deps.env ?? process.env), HQ_NO_ENSURE_HQ_DESKTOP: "1" };
|
|
362
|
+
const env = { ...(runtime.runtimeEnv?.(baseEnv) ?? baseEnv), HQ_NO_ENSURE_HQ_DESKTOP: "1" };
|
|
351
363
|
const binary = runtime.resolveBinary?.(env, input.cwd, turnInput.platform) ?? runtime.binary;
|
|
352
364
|
const stdin = runtime.stdinInput?.(turnInput);
|
|
353
365
|
const protocol = runtime.spawnProtocol?.(turnInput);
|