@indigoai-us/hq-cli 5.131.0 → 5.133.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 +147 -0
- package/dist/command-catalog.generated.d.ts +12 -0
- package/dist/command-catalog.generated.js +16 -0
- package/dist/commands/lanes.d.ts +15 -4
- package/dist/commands/lanes.js +478 -48
- package/dist/commands/mesh.js +24 -2
- package/dist/commands/update.js +1 -1
- package/dist/lib/bot/paths.d.ts +7 -5
- package/dist/lib/bot/paths.js +8 -1
- package/dist/lib/bot/prompt.d.ts +0 -2
- package/dist/lib/bot/prompt.js +5 -11
- package/dist/lib/bot/room-policy.d.ts +1 -24
- package/dist/lib/bot/room-policy.js +2 -35
- package/dist/lib/bot/run.d.ts +7 -8
- package/dist/lib/bot/run.js +21 -33
- package/dist/lib/bot/session.d.ts +1 -1
- package/dist/lib/bot/session.js +1 -1
- package/dist/lib/lanes/accounts.d.ts +6 -0
- package/dist/lib/lanes/accounts.js +2 -1
- package/dist/lib/lanes/config.d.ts +4 -0
- package/dist/lib/lanes/config.js +9 -0
- package/dist/lib/lanes/constants.d.ts +3 -0
- package/dist/lib/lanes/constants.js +3 -0
- package/dist/lib/lanes/index.d.ts +11 -3
- package/dist/lib/lanes/index.js +6 -1
- package/dist/lib/lanes/mesh-events.js +4 -0
- package/dist/lib/lanes/models.d.ts +78 -0
- package/dist/lib/lanes/models.js +205 -0
- package/dist/lib/lanes/move-account.d.ts +56 -0
- package/dist/lib/lanes/move-account.js +193 -0
- package/dist/lib/lanes/provider-rejection.d.ts +86 -0
- package/dist/lib/lanes/provider-rejection.js +258 -0
- package/dist/lib/lanes/quota.d.ts +23 -3
- package/dist/lib/lanes/quota.js +88 -14
- package/dist/lib/lanes/reconcile-install.d.ts +24 -3
- package/dist/lib/lanes/reconcile-install.js +48 -12
- package/dist/lib/lanes/reconcile.d.ts +5 -2
- package/dist/lib/lanes/reconcile.js +135 -13
- package/dist/lib/lanes/spawn.js +26 -5
- package/dist/lib/lanes/store.js +10 -0
- package/dist/lib/lanes/types.d.ts +51 -2
- package/dist/lib/lanes/types.js +5 -3
- package/dist/lib/lanes/watcher.d.ts +27 -0
- package/dist/lib/lanes/watcher.js +68 -5
- package/dist/lib/mesh/live/backfill-held.d.ts +4 -3
- package/dist/lib/mesh/live/backfill-held.js +37 -14
- package/dist/lib/mesh/live/daemon/doctor.d.ts +6 -0
- package/dist/lib/mesh/live/daemon/doctor.js +16 -2
- package/dist/lib/mesh/live/daemon/install.d.ts +12 -0
- package/dist/lib/mesh/live/daemon/install.js +100 -7
- package/dist/lib/mesh/live/daemon/legacy-listen.d.ts +43 -0
- package/dist/lib/mesh/live/daemon/legacy-listen.js +89 -0
- package/dist/lib/mesh/live/daemon/live-cache.d.ts +26 -0
- package/dist/lib/mesh/live/daemon/live-cache.js +116 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +7 -3
- package/dist/lib/mesh/live/daemon/presence.js +10 -9
- package/dist/lib/mesh/live/daemon/run.d.ts +18 -0
- package/dist/lib/mesh/live/daemon/run.js +65 -6
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +31 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +124 -0
- package/dist/lib/mesh/live/emit-evidence.d.ts +19 -0
- package/dist/lib/mesh/live/emit-evidence.js +20 -2
- package/dist/lib/work-context/company.d.ts +16 -0
- package/dist/lib/work-context/company.js +46 -14
- package/dist/utils/self-update.js +4 -1
- package/package.json +2 -2
package/dist/lib/lanes/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Library layer for `hq lanes`. The command module should import from here.
|
|
3
3
|
*/
|
|
4
4
|
export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
|
|
5
|
+
export { MAX_LANE_DEPTH } from "./constants.js";
|
|
5
6
|
export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
|
|
6
|
-
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, writeLanesConfig, } from "./config.js";
|
|
7
|
+
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
|
|
7
8
|
export { resolveLanesHqRoot } from "./hq-root.js";
|
|
8
9
|
export { buildOutpostPickupCommand, parseOutpostPickupPayload, } from "./outpost-dispatch.js";
|
|
9
10
|
export { LANES_STORE_REL, LANE_RECORDS_DIR, PENDING_INDEX_DIR, QUESTION_RECORDS_DIR, appendCappedHistory, appendLaneAttribution, appendLaneSeniorHistory, appendQuestionRouting, createLane, createQuestion, isConflictSidecarName, laneRecordPath, lanesStoreRoot, listLaneIds, listQuestionIds, mergeLaneRecords, mergeQuestionRecords, mintLaneId, mintQuestionId, parseLaneRecord, parseQuestionRecord, pendingMarkerPath, questionRecordPath, readLane, readQuestion, resolveStoreHqRoot, sanitizeHostComponent, tryReadLane, tryReadQuestion, retryUpdateLane, retryUpdateQuestion, updateLane, updateQuestion, } from "./store.js";
|
|
@@ -17,6 +18,10 @@ export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PRO
|
|
|
17
18
|
export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readDaemonPresenceConnected, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
|
|
18
19
|
export { DEAD_HOST_MS, MAX_AGED_ADVANCES, QUESTION_AGE_MS, RECONCILE_TIMER_NAME, RECONCILE_UNIT_NAME, agedAdvanceCount, currentHopStartedAtMs, isDeadlinePassed, laneRunsHere, readTimeoutDeadline, reconcileHeartbeatPath, resolveQuestionAudience, runReconcilePass, shouldAdvanceAudienceEnded, shouldAgeQuestion, timeoutPath, } from "./reconcile.js";
|
|
19
20
|
export { RECONCILE_TIMER_INTERVAL, defaultSystemdUserDir, installReconcileTimer, providerPathEnv, renderReconcileService, renderReconcileTimer, uninstallReconcileTimer, } from "./reconcile-install.js";
|
|
21
|
+
export { unitPathEnv } from "./reconcile-install.js";
|
|
22
|
+
export { PROVIDER_REJECTED_REASON, PROVIDER_REJECTION_MESSAGE_CAP, parseClaudeResultRejection, parseCodexTurnFailed, parseGrokErrorOutput, parseProviderRejection, readProviderRejection, retryableFromStatus, } from "./provider-rejection.js";
|
|
23
|
+
export { CODEX_MODELS_CACHE_NAME, MODEL_PROBE_TIMEOUT_MS, claudeKnownModels, codexKnownModels, defaultProbe, grokKnownModels, knownModels, modelIsKnown, parseClaudeHelpModels, parseGrokModelsOutput, validateLaneModel, } from "./models.js";
|
|
24
|
+
export { SESSION_MOVE_REASON, copySessionFiles, encodeClaudeProjectDir, findCodexRollout, planClaudeSessionCopy, planCodexSessionCopy, planSessionCopy, } from "./move-account.js";
|
|
20
25
|
export { WORKER_REGISTRY_REL, personalWorkerDir, personalWorkerExists, readWorkerRegistryFile, registryPath, resolveContextBasePaths, resolveLaneWorker, resolveSkillFiles, tenancyFromRegistryPath, } from "./registry.js";
|
|
21
26
|
export { bodyStoreForLane, envelopeQuestionId, parseQuestionBodyStore, questionBodyAbsPath, questionBodyRef, raiseEnvelopeQuestions, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "./questions.js";
|
|
22
27
|
export { LANE_DELIVERY_MECHANISMS, LAUNCH_TRIGGERS, PROVIDER_SESSION_RESUME, RESUMABLE_LANE_STATES, RESUME_COMMAND_STATES, SESSION_ID_ABSENT_REASONS, claimableStatesFor, deliveryRefusalReason, hasRecordedSessionId, isResumableLaneState, isResumeCommandState, laneDeliveryMechanism, parseClaudeResultOutput, parseCodexEventOutput, parseWorkerRunOutput, planLaneLaunch, providerSupportsSessionResume, readWorkerRunResult, resumeCommandRefusal, } from "./delivery.js";
|
|
@@ -191,10 +191,14 @@ export function laneCanEmit(lane) {
|
|
|
191
191
|
return lane.company !== null && lane.company !== undefined && !!lane.thread_id;
|
|
192
192
|
}
|
|
193
193
|
function baseIdentity(lane) {
|
|
194
|
+
const lastMove = lane.account_history?.[lane.account_history.length - 1];
|
|
194
195
|
return {
|
|
195
196
|
lane_id: lane.lane_id,
|
|
196
197
|
project_id: lane.project_id,
|
|
197
198
|
story_id: lane.story_id,
|
|
199
|
+
...(lane.account !== undefined ? { account: lane.account } : {}),
|
|
200
|
+
// A moved lane names the move on every event its run emits (US-023 §8).
|
|
201
|
+
...(lastMove !== undefined ? { account_move: { from: lastMove.from, to: lastMove.to, at: lastMove.at } } : {}),
|
|
198
202
|
};
|
|
199
203
|
}
|
|
200
204
|
/**
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-provider known model ids, for validating `hq lanes create --model`.
|
|
3
|
+
*
|
|
4
|
+
* No provider adapter in this package carries a model list, so each id set
|
|
5
|
+
* is read from where the provider itself keeps it, the way the accounts
|
|
6
|
+
* probes read provider state: from the account home or the provider CLI,
|
|
7
|
+
* never from a list written into this package.
|
|
8
|
+
*
|
|
9
|
+
* - codex: `<CODEX_HOME>/models_cache.json`, the model catalog the codex CLI
|
|
10
|
+
* fetches and caches per home. Every `models[].slug` is accepted.
|
|
11
|
+
* - grok: `grok models` (run with `GROK_HOME` set to the account home),
|
|
12
|
+
* which prints `Available models:` followed by one id per line. It lists
|
|
13
|
+
* models even when the home is not logged in.
|
|
14
|
+
* - claude: `claude --help`, whose `--model` text names the accepted aliases
|
|
15
|
+
* (`'fable', 'opus', or 'sonnet'`) and says a full name is accepted too.
|
|
16
|
+
* An alias from that text or any `claude-` full name passes.
|
|
17
|
+
*
|
|
18
|
+
* A source that cannot be read (no cache yet, CLI missing, probe failed)
|
|
19
|
+
* returns null and the caller skips validation: the provider's own
|
|
20
|
+
* rejection at run time is still classified `provider_rejected`.
|
|
21
|
+
*/
|
|
22
|
+
import type { LaneProvider } from "./types.js";
|
|
23
|
+
export declare const CODEX_MODELS_CACHE_NAME = "models_cache.json";
|
|
24
|
+
export declare const MODEL_PROBE_TIMEOUT_MS = 10000;
|
|
25
|
+
export interface ProbeResult {
|
|
26
|
+
status: number | null;
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
}
|
|
30
|
+
export type ProbeCommand = (command: string, args: string[], env: NodeJS.ProcessEnv) => ProbeResult;
|
|
31
|
+
export interface KnownModelsDeps {
|
|
32
|
+
readFileSync?: (p: string, encoding: "utf8") => string;
|
|
33
|
+
probe?: ProbeCommand;
|
|
34
|
+
env?: NodeJS.ProcessEnv;
|
|
35
|
+
}
|
|
36
|
+
export interface KnownModels {
|
|
37
|
+
provider: LaneProvider;
|
|
38
|
+
/** Exact ids the provider accepts. */
|
|
39
|
+
ids: string[];
|
|
40
|
+
/** Prefixes accepted in addition to `ids` (claude full names). */
|
|
41
|
+
prefixes: string[];
|
|
42
|
+
/** Where the list came from, for the refusal and the report. */
|
|
43
|
+
source: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function defaultProbe(command: string, args: string[], env: NodeJS.ProcessEnv): ProbeResult;
|
|
46
|
+
/** Read `models[].slug` from the codex catalog cache in `home`. Null when absent or unreadable. */
|
|
47
|
+
export declare function codexKnownModels(home: string, readFileSync?: NonNullable<KnownModelsDeps["readFileSync"]>): KnownModels | null;
|
|
48
|
+
/**
|
|
49
|
+
* Parse `grok models` output:
|
|
50
|
+
*
|
|
51
|
+
* Available models:
|
|
52
|
+
* * grok-4.7 (default)
|
|
53
|
+
* - grok-4.7-build-fast
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseGrokModelsOutput(text: string): string[];
|
|
56
|
+
export declare function grokKnownModels(home: string, probe?: ProbeCommand, env?: NodeJS.ProcessEnv): KnownModels | null;
|
|
57
|
+
/**
|
|
58
|
+
* Parse the `--model` option text of `claude --help`:
|
|
59
|
+
*
|
|
60
|
+
* --model <model> Model for the current session. Provide an alias for
|
|
61
|
+
* the latest model (e.g. 'fable', 'opus', or 'sonnet')
|
|
62
|
+
* or a model's full name (e.g. 'claude-fable-5').
|
|
63
|
+
*
|
|
64
|
+
* Returns the quoted aliases and the prefix of the quoted full-name example.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseClaudeHelpModels(text: string): {
|
|
67
|
+
aliases: string[];
|
|
68
|
+
prefixes: string[];
|
|
69
|
+
};
|
|
70
|
+
export declare function claudeKnownModels(probe?: ProbeCommand, env?: NodeJS.ProcessEnv): KnownModels | null;
|
|
71
|
+
export declare function knownModels(provider: LaneProvider, home: string, deps?: KnownModelsDeps): KnownModels | null;
|
|
72
|
+
export declare function modelIsKnown(known: KnownModels, model: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Refuse a `--model` the provider does not know. Returns the source used, or
|
|
75
|
+
* null when no source was readable and the value passed unvalidated.
|
|
76
|
+
*/
|
|
77
|
+
export declare function validateLaneModel(provider: LaneProvider, model: string, home: string, deps?: KnownModelsDeps): string | null;
|
|
78
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-provider known model ids, for validating `hq lanes create --model`.
|
|
3
|
+
*
|
|
4
|
+
* No provider adapter in this package carries a model list, so each id set
|
|
5
|
+
* is read from where the provider itself keeps it, the way the accounts
|
|
6
|
+
* probes read provider state: from the account home or the provider CLI,
|
|
7
|
+
* never from a list written into this package.
|
|
8
|
+
*
|
|
9
|
+
* - codex: `<CODEX_HOME>/models_cache.json`, the model catalog the codex CLI
|
|
10
|
+
* fetches and caches per home. Every `models[].slug` is accepted.
|
|
11
|
+
* - grok: `grok models` (run with `GROK_HOME` set to the account home),
|
|
12
|
+
* which prints `Available models:` followed by one id per line. It lists
|
|
13
|
+
* models even when the home is not logged in.
|
|
14
|
+
* - claude: `claude --help`, whose `--model` text names the accepted aliases
|
|
15
|
+
* (`'fable', 'opus', or 'sonnet'`) and says a full name is accepted too.
|
|
16
|
+
* An alias from that text or any `claude-` full name passes.
|
|
17
|
+
*
|
|
18
|
+
* A source that cannot be read (no cache yet, CLI missing, probe failed)
|
|
19
|
+
* returns null and the caller skips validation: the provider's own
|
|
20
|
+
* rejection at run time is still classified `provider_rejected`.
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import { LanesError } from "./errors.js";
|
|
26
|
+
import { PROVIDER_HOME_ENV } from "./accounts.js";
|
|
27
|
+
export const CODEX_MODELS_CACHE_NAME = "models_cache.json";
|
|
28
|
+
export const MODEL_PROBE_TIMEOUT_MS = 10_000;
|
|
29
|
+
export function defaultProbe(command, args, env) {
|
|
30
|
+
const result = spawnSync(command, args, {
|
|
31
|
+
env,
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
timeout: MODEL_PROBE_TIMEOUT_MS,
|
|
34
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
35
|
+
});
|
|
36
|
+
if (result.error) {
|
|
37
|
+
return { status: null, stdout: "", stderr: result.error.message };
|
|
38
|
+
}
|
|
39
|
+
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
40
|
+
}
|
|
41
|
+
function isPlainObject(value) {
|
|
42
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
/** Read `models[].slug` from the codex catalog cache in `home`. Null when absent or unreadable. */
|
|
45
|
+
export function codexKnownModels(home, readFileSync = (p, enc) => fs.readFileSync(p, enc)) {
|
|
46
|
+
const source = path.join(home, CODEX_MODELS_CACHE_NAME);
|
|
47
|
+
let text;
|
|
48
|
+
try {
|
|
49
|
+
text = readFileSync(source, "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const code = err.code;
|
|
53
|
+
if (code === "ENOENT")
|
|
54
|
+
return null;
|
|
55
|
+
// EACCES, EISDIR, EIO: the list cannot be read, which is the same
|
|
56
|
+
// contract as a missing or corrupt cache. Reported, never an internal failure.
|
|
57
|
+
process.stderr.write(`hq lanes: cannot read ${source} (${err instanceof Error ? err.message : String(err)}); --model is not validated for codex\n`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(text);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
process.stderr.write(`hq lanes: ${source} is not JSON (${err instanceof Error ? err.message : String(err)}); --model is not validated for codex\n`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!isPlainObject(parsed) || !Array.isArray(parsed.models))
|
|
69
|
+
return null;
|
|
70
|
+
const ids = [];
|
|
71
|
+
for (const entry of parsed.models) {
|
|
72
|
+
if (isPlainObject(entry) && typeof entry.slug === "string" && entry.slug.trim().length > 0) {
|
|
73
|
+
ids.push(entry.slug.trim());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (ids.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return { provider: "codex", ids, prefixes: [], source };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse `grok models` output:
|
|
82
|
+
*
|
|
83
|
+
* Available models:
|
|
84
|
+
* * grok-4.7 (default)
|
|
85
|
+
* - grok-4.7-build-fast
|
|
86
|
+
*/
|
|
87
|
+
export function parseGrokModelsOutput(text) {
|
|
88
|
+
const ids = [];
|
|
89
|
+
let inList = false;
|
|
90
|
+
for (const raw of text.split("\n")) {
|
|
91
|
+
const line = raw.trim();
|
|
92
|
+
if (/^available models:/i.test(line)) {
|
|
93
|
+
inList = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!inList)
|
|
97
|
+
continue;
|
|
98
|
+
const match = /^[*-]\s+(\S+)/.exec(line);
|
|
99
|
+
if (match) {
|
|
100
|
+
ids.push(match[1]);
|
|
101
|
+
}
|
|
102
|
+
else if (line.length === 0 && ids.length > 0) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return ids;
|
|
107
|
+
}
|
|
108
|
+
export function grokKnownModels(home, probe = defaultProbe, env = process.env) {
|
|
109
|
+
const source = "grok models";
|
|
110
|
+
const result = probe("grok", ["models"], { ...env, [PROVIDER_HOME_ENV.grok]: home });
|
|
111
|
+
// Only a probe that exited 0 is a catalog. A wrapper or auth check can
|
|
112
|
+
// print a partial list and then fail; an id missing from that output must
|
|
113
|
+
// not refuse the model, so a failed probe skips validation.
|
|
114
|
+
if (result.status !== 0) {
|
|
115
|
+
process.stderr.write(`hq lanes: \`${source}\` failed (exit ${result.status ?? "spawn error"}${result.stderr.trim() ? `: ${result.stderr.trim().split("\n")[0]}` : ""}); --model is not validated for grok\n`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const ids = parseGrokModelsOutput(result.stdout);
|
|
119
|
+
if (ids.length === 0) {
|
|
120
|
+
process.stderr.write(`hq lanes: \`${source}\` listed no models (exit ${result.status ?? "spawn error"}${result.stderr.trim() ? `: ${result.stderr.trim().split("\n")[0]}` : ""}); --model is not validated for grok\n`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return { provider: "grok", ids, prefixes: [], source };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse the `--model` option text of `claude --help`:
|
|
127
|
+
*
|
|
128
|
+
* --model <model> Model for the current session. Provide an alias for
|
|
129
|
+
* the latest model (e.g. 'fable', 'opus', or 'sonnet')
|
|
130
|
+
* or a model's full name (e.g. 'claude-fable-5').
|
|
131
|
+
*
|
|
132
|
+
* Returns the quoted aliases and the prefix of the quoted full-name example.
|
|
133
|
+
*/
|
|
134
|
+
export function parseClaudeHelpModels(text) {
|
|
135
|
+
const start = text.indexOf("--model");
|
|
136
|
+
if (start === -1)
|
|
137
|
+
return { aliases: [], prefixes: [] };
|
|
138
|
+
const rest = text.slice(start);
|
|
139
|
+
const end = rest.search(/\n\s*-{1,2}[A-Za-z]/);
|
|
140
|
+
const block = end === -1 ? rest : rest.slice(0, end);
|
|
141
|
+
const aliases = [];
|
|
142
|
+
const prefixes = new Set();
|
|
143
|
+
// Only quoted tokens shaped like an id: the possessive in "model's" also
|
|
144
|
+
// opens a quote, and must not be read as one.
|
|
145
|
+
for (const match of block.matchAll(/'([A-Za-z0-9][A-Za-z0-9._[\]-]*)'/g)) {
|
|
146
|
+
const value = match[1];
|
|
147
|
+
const dash = value.indexOf("-");
|
|
148
|
+
if (dash > 0) {
|
|
149
|
+
prefixes.add(value.slice(0, dash + 1));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
aliases.push(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { aliases, prefixes: [...prefixes] };
|
|
156
|
+
}
|
|
157
|
+
export function claudeKnownModels(probe = defaultProbe, env = process.env) {
|
|
158
|
+
const source = "claude --help";
|
|
159
|
+
const result = probe("claude", ["--help"], env);
|
|
160
|
+
if (result.status !== 0) {
|
|
161
|
+
process.stderr.write(`hq lanes: \`${source}\` failed (exit ${result.status ?? "spawn error"}${result.stderr.trim() ? `: ${result.stderr.trim().split("\n")[0]}` : ""}); --model is not validated for claude\n`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const parsed = parseClaudeHelpModels(result.stdout);
|
|
165
|
+
if (parsed.aliases.length === 0 && parsed.prefixes.length === 0) {
|
|
166
|
+
process.stderr.write(`hq lanes: \`${source}\` named no --model aliases (exit ${result.status ?? "spawn error"}); --model is not validated for claude\n`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return { provider: "claude", ids: parsed.aliases, prefixes: parsed.prefixes, source };
|
|
170
|
+
}
|
|
171
|
+
export function knownModels(provider, home, deps = {}) {
|
|
172
|
+
switch (provider) {
|
|
173
|
+
case "codex":
|
|
174
|
+
return codexKnownModels(home, deps.readFileSync);
|
|
175
|
+
case "grok":
|
|
176
|
+
return grokKnownModels(home, deps.probe, deps.env);
|
|
177
|
+
case "claude":
|
|
178
|
+
return claudeKnownModels(deps.probe, deps.env);
|
|
179
|
+
default: {
|
|
180
|
+
const _exhaustive = provider;
|
|
181
|
+
return _exhaustive;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function modelIsKnown(known, model) {
|
|
186
|
+
if (known.ids.includes(model))
|
|
187
|
+
return true;
|
|
188
|
+
return known.prefixes.some((prefix) => model.startsWith(prefix) && model.length > prefix.length);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Refuse a `--model` the provider does not know. Returns the source used, or
|
|
192
|
+
* null when no source was readable and the value passed unvalidated.
|
|
193
|
+
*/
|
|
194
|
+
export function validateLaneModel(provider, model, home, deps = {}) {
|
|
195
|
+
const known = knownModels(provider, home, deps);
|
|
196
|
+
if (known === null)
|
|
197
|
+
return null;
|
|
198
|
+
if (modelIsKnown(known, model))
|
|
199
|
+
return known.source;
|
|
200
|
+
const accepted = [...known.ids, ...known.prefixes.map((prefix) => `${prefix}*`)];
|
|
201
|
+
const message = `Provider ${provider} does not know model ${JSON.stringify(model)} (source: ${known.source}). Accepted: ${accepted.join(", ")}`;
|
|
202
|
+
// `message` is in details too: LanesError.toJSON prints details only.
|
|
203
|
+
throw new LanesError("model_unsupported", message, { provider, model, accepted, source: known.source, message });
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-023: move a lane's provider session to another account of the same
|
|
3
|
+
* provider. A provider CLI only resumes sessions stored under its own home
|
|
4
|
+
* (`CLAUDE_CONFIG_DIR/projects/<encoded cwd>/<id>.jsonl`,
|
|
5
|
+
* `CODEX_HOME/sessions/YYYY/MM/DD/rollout-*-<id>.jsonl`), so the session
|
|
6
|
+
* files are copied into the target home first. The source copy is never
|
|
7
|
+
* moved or deleted, so the lane can be moved back. Only session transcripts
|
|
8
|
+
* are copied: a credential file name is refused by name, whatever path it
|
|
9
|
+
* arrives on.
|
|
10
|
+
*/
|
|
11
|
+
import type { LaneProvider } from "./types.js";
|
|
12
|
+
export declare const SESSION_MOVE_REASON: "operator_move";
|
|
13
|
+
export interface SessionCopyPlan {
|
|
14
|
+
provider: LaneProvider;
|
|
15
|
+
/** Source files, absolute. */
|
|
16
|
+
sources: string[];
|
|
17
|
+
/** Target files, absolute, in the same order as `sources`. */
|
|
18
|
+
targets: string[];
|
|
19
|
+
/** Source directory copied as a tree (claude subagent transcripts), or null. */
|
|
20
|
+
source_dir: string | null;
|
|
21
|
+
target_dir: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface SessionCopyResult extends SessionCopyPlan {
|
|
24
|
+
/** Target paths that were written by this call (already-present ones are not listed). */
|
|
25
|
+
copied: string[];
|
|
26
|
+
}
|
|
27
|
+
/** `claude` keys a project directory by its cwd with every non-alphanumeric character as `-`. */
|
|
28
|
+
export declare function encodeClaudeProjectDir(cwd: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Locate the claude transcript `<sessionId>.jsonl` and its optional sibling
|
|
31
|
+
* `<sessionId>/` directory under `<home>/projects/<encoded cwd>/`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function planClaudeSessionCopy(sourceHome: string, targetHome: string, cwd: string, sessionId: string): SessionCopyPlan;
|
|
34
|
+
/** Walk `<home>/sessions/YYYY/MM/DD/` for `rollout-*-<sessionId>.jsonl`. */
|
|
35
|
+
export declare function findCodexRollout(home: string, sessionId: string): string | null;
|
|
36
|
+
export declare function planCodexSessionCopy(sourceHome: string, targetHome: string, sessionId: string): SessionCopyPlan;
|
|
37
|
+
export declare function planSessionCopy(provider: LaneProvider, input: {
|
|
38
|
+
sourceHome: string;
|
|
39
|
+
targetHome: string;
|
|
40
|
+
cwd: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
}): SessionCopyPlan;
|
|
43
|
+
/** Every file a plan would write, transcript(s) first, then the subagent tree. */
|
|
44
|
+
export declare function planFiles(plan: SessionCopyPlan): Array<{
|
|
45
|
+
from: string;
|
|
46
|
+
to: string;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Execute a plan: copy the transcript(s) and the subagent tree into the
|
|
50
|
+
* target home. Every path is checked against the credential file names
|
|
51
|
+
* before the first byte is copied, so a refusal writes nothing. A file
|
|
52
|
+
* already present in the target with the same content is left alone; one
|
|
53
|
+
* that differs is replaced by the source copy. The source is never touched.
|
|
54
|
+
*/
|
|
55
|
+
export declare function copySessionFiles(plan: SessionCopyPlan): SessionCopyResult;
|
|
56
|
+
//# sourceMappingURL=move-account.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-023: move a lane's provider session to another account of the same
|
|
3
|
+
* provider. A provider CLI only resumes sessions stored under its own home
|
|
4
|
+
* (`CLAUDE_CONFIG_DIR/projects/<encoded cwd>/<id>.jsonl`,
|
|
5
|
+
* `CODEX_HOME/sessions/YYYY/MM/DD/rollout-*-<id>.jsonl`), so the session
|
|
6
|
+
* files are copied into the target home first. The source copy is never
|
|
7
|
+
* moved or deleted, so the lane can be moved back. Only session transcripts
|
|
8
|
+
* are copied: a credential file name is refused by name, whatever path it
|
|
9
|
+
* arrives on.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { PROVIDER_CREDENTIAL_FILES } from "./accounts.js";
|
|
14
|
+
import { LanesError } from "./errors.js";
|
|
15
|
+
export const SESSION_MOVE_REASON = "operator_move";
|
|
16
|
+
/** `claude` keys a project directory by its cwd with every non-alphanumeric character as `-`. */
|
|
17
|
+
export function encodeClaudeProjectDir(cwd) {
|
|
18
|
+
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
19
|
+
}
|
|
20
|
+
function credentialName(provider, file) {
|
|
21
|
+
const base = path.basename(file);
|
|
22
|
+
return Object.values(PROVIDER_CREDENTIAL_FILES).some((names) => names.includes(base)) ||
|
|
23
|
+
PROVIDER_CREDENTIAL_FILES[provider].includes(base);
|
|
24
|
+
}
|
|
25
|
+
function isDirectory(p) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.statSync(p).isDirectory();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isFile(p) {
|
|
34
|
+
try {
|
|
35
|
+
return fs.statSync(p).isFile();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function sessionMissing(provider, sessionId, searched) {
|
|
42
|
+
return new LanesError("provider_session_missing", `No ${provider} session ${sessionId} under the source account home. Searched: ${searched.join(", ")}`, { provider, session_id: sessionId, searched });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Locate the claude transcript `<sessionId>.jsonl` and its optional sibling
|
|
46
|
+
* `<sessionId>/` directory under `<home>/projects/<encoded cwd>/`.
|
|
47
|
+
*/
|
|
48
|
+
export function planClaudeSessionCopy(sourceHome, targetHome, cwd, sessionId) {
|
|
49
|
+
const rel = path.join("projects", encodeClaudeProjectDir(cwd));
|
|
50
|
+
const transcript = path.join(sourceHome, rel, `${sessionId}.jsonl`);
|
|
51
|
+
if (!isFile(transcript))
|
|
52
|
+
throw sessionMissing("claude", sessionId, [transcript]);
|
|
53
|
+
const subagents = path.join(sourceHome, rel, sessionId);
|
|
54
|
+
const hasSubagents = isDirectory(subagents);
|
|
55
|
+
return {
|
|
56
|
+
provider: "claude",
|
|
57
|
+
sources: [transcript],
|
|
58
|
+
targets: [path.join(targetHome, rel, `${sessionId}.jsonl`)],
|
|
59
|
+
source_dir: hasSubagents ? subagents : null,
|
|
60
|
+
target_dir: hasSubagents ? path.join(targetHome, rel, sessionId) : null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Walk `<home>/sessions/YYYY/MM/DD/` for `rollout-*-<sessionId>.jsonl`. */
|
|
64
|
+
export function findCodexRollout(home, sessionId) {
|
|
65
|
+
const root = path.join(home, "sessions");
|
|
66
|
+
if (!isDirectory(root))
|
|
67
|
+
return null;
|
|
68
|
+
const suffix = `-${sessionId}.jsonl`;
|
|
69
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
70
|
+
while (stack.length > 0) {
|
|
71
|
+
const { dir, depth } = stack.pop();
|
|
72
|
+
let entries;
|
|
73
|
+
try {
|
|
74
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const full = path.join(dir, entry.name);
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
if (depth < 3)
|
|
83
|
+
stack.push({ dir: full, depth: depth + 1 });
|
|
84
|
+
}
|
|
85
|
+
else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(suffix)) {
|
|
86
|
+
return full;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
export function planCodexSessionCopy(sourceHome, targetHome, sessionId) {
|
|
93
|
+
const rollout = findCodexRollout(sourceHome, sessionId);
|
|
94
|
+
if (rollout === null) {
|
|
95
|
+
throw sessionMissing("codex", sessionId, [path.join(sourceHome, "sessions", "**", `rollout-*-${sessionId}.jsonl`)]);
|
|
96
|
+
}
|
|
97
|
+
const rel = path.relative(sourceHome, rollout);
|
|
98
|
+
return {
|
|
99
|
+
provider: "codex",
|
|
100
|
+
sources: [rollout],
|
|
101
|
+
targets: [path.join(targetHome, rel)],
|
|
102
|
+
source_dir: null,
|
|
103
|
+
target_dir: null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function planSessionCopy(provider, input) {
|
|
107
|
+
switch (provider) {
|
|
108
|
+
case "claude":
|
|
109
|
+
return planClaudeSessionCopy(input.sourceHome, input.targetHome, input.cwd, input.sessionId);
|
|
110
|
+
case "codex":
|
|
111
|
+
return planCodexSessionCopy(input.sourceHome, input.targetHome, input.sessionId);
|
|
112
|
+
case "grok":
|
|
113
|
+
throw new LanesError("provider_session_not_portable", "Provider grok has no session resume, so a lane cannot move between grok accounts", { provider });
|
|
114
|
+
default: {
|
|
115
|
+
const _exhaustive = provider;
|
|
116
|
+
return _exhaustive;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function refuseCredential(provider, file) {
|
|
121
|
+
if (credentialName(provider, file)) {
|
|
122
|
+
throw new LanesError("credential_copy_refused", `Refusing to copy ${path.basename(file)}: only session transcripts move between accounts`, { provider, file: path.basename(file) });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function sameContent(a, b) {
|
|
126
|
+
try {
|
|
127
|
+
const sa = fs.statSync(a);
|
|
128
|
+
const sb = fs.statSync(b);
|
|
129
|
+
if (sa.size !== sb.size)
|
|
130
|
+
return false;
|
|
131
|
+
return fs.readFileSync(a).equals(fs.readFileSync(b));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Copy one file via a temp name and rename, so a partial copy is never a
|
|
139
|
+
* visible transcript. A target that already exists is refreshed when its
|
|
140
|
+
* content differs: the lane last ran on the source account, so the source
|
|
141
|
+
* copy is the newer one (a lane moved A→B and back to A must not resume A's
|
|
142
|
+
* pre-move transcript). Returns false when the target already matched.
|
|
143
|
+
*/
|
|
144
|
+
function copyFileAtomic(source, target) {
|
|
145
|
+
if (fs.existsSync(target) && sameContent(source, target))
|
|
146
|
+
return false;
|
|
147
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
148
|
+
const tmp = `${target}.partial.${process.pid}`;
|
|
149
|
+
fs.copyFileSync(source, tmp);
|
|
150
|
+
fs.renameSync(tmp, target);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
function listTree(sourceDir, targetDir, out) {
|
|
154
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
155
|
+
const from = path.join(sourceDir, entry.name);
|
|
156
|
+
const to = path.join(targetDir, entry.name);
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
listTree(from, to, out);
|
|
159
|
+
}
|
|
160
|
+
else if (entry.isFile()) {
|
|
161
|
+
out.push({ from, to });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Every file a plan would write, transcript(s) first, then the subagent tree. */
|
|
166
|
+
export function planFiles(plan) {
|
|
167
|
+
const files = plan.sources.map((from, i) => ({ from, to: plan.targets[i] }));
|
|
168
|
+
if (plan.source_dir !== null && plan.target_dir !== null) {
|
|
169
|
+
listTree(plan.source_dir, plan.target_dir, files);
|
|
170
|
+
}
|
|
171
|
+
return files;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Execute a plan: copy the transcript(s) and the subagent tree into the
|
|
175
|
+
* target home. Every path is checked against the credential file names
|
|
176
|
+
* before the first byte is copied, so a refusal writes nothing. A file
|
|
177
|
+
* already present in the target with the same content is left alone; one
|
|
178
|
+
* that differs is replaced by the source copy. The source is never touched.
|
|
179
|
+
*/
|
|
180
|
+
export function copySessionFiles(plan) {
|
|
181
|
+
const files = planFiles(plan);
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
refuseCredential(plan.provider, file.from);
|
|
184
|
+
refuseCredential(plan.provider, file.to);
|
|
185
|
+
}
|
|
186
|
+
const copied = [];
|
|
187
|
+
for (const file of files) {
|
|
188
|
+
if (copyFileAtomic(file.from, file.to))
|
|
189
|
+
copied.push(file.to);
|
|
190
|
+
}
|
|
191
|
+
return { ...plan, copied };
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=move-account.js.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-level turn failures read off the worker's own stdout.
|
|
3
|
+
*
|
|
4
|
+
* A worker whose turn the provider refused (a 400 for a model the account
|
|
5
|
+
* cannot use, a 402 for an exhausted balance, a 5xx) exits with partial
|
|
6
|
+
* output and no envelope. Without this module the watcher classifies that
|
|
7
|
+
* `interrupted` with `exited_with_partial_output`, and the operator has to
|
|
8
|
+
* read the raw log to learn why. Here the failure is named: the lane is
|
|
9
|
+
* `failed` with `terminal_reason: provider_rejected` and the provider's own
|
|
10
|
+
* message on `terminal_details`.
|
|
11
|
+
*
|
|
12
|
+
* A 429 is parsed here like any other status so that a codex or grok 429,
|
|
13
|
+
* which the claude-shaped quota reader cannot see, still reaches the quota
|
|
14
|
+
* path: the watcher routes a `status === QUOTA_STATUS` failure to quota.ts
|
|
15
|
+
* (reset time, account hold) and everything else to `provider_rejected`.
|
|
16
|
+
*
|
|
17
|
+
* Only the fields named below are read out of the payload. The prompt, the
|
|
18
|
+
* response body and everything else stay on disk. Nothing here logs.
|
|
19
|
+
*/
|
|
20
|
+
import type { LaneProvider } from "./types.js";
|
|
21
|
+
export declare const PROVIDER_REJECTED_REASON: "provider_rejected";
|
|
22
|
+
/** Bytes of the provider message kept on the record. The log keeps the rest. */
|
|
23
|
+
export declare const PROVIDER_REJECTION_MESSAGE_CAP = 2000;
|
|
24
|
+
/** Status the watcher routes to the quota path instead of `provider_rejected`. */
|
|
25
|
+
export declare const QUOTA_STATUS = 429;
|
|
26
|
+
export interface ProviderRejection {
|
|
27
|
+
reason: typeof PROVIDER_REJECTED_REASON;
|
|
28
|
+
provider: LaneProvider;
|
|
29
|
+
/** The provider's error message, verbatim, capped at PROVIDER_REJECTION_MESSAGE_CAP. */
|
|
30
|
+
message: string;
|
|
31
|
+
/** HTTP status the provider reported; null when the payload named none. */
|
|
32
|
+
status: number | null;
|
|
33
|
+
/** Provider error class, e.g. `invalid_request_error`; null when absent. */
|
|
34
|
+
error_type: string | null;
|
|
35
|
+
/** Derived from the status: a transient class (408, 425, 5xx) is retryable; a 4xx is not. */
|
|
36
|
+
retryable: boolean;
|
|
37
|
+
/** Which event on the stream carried the failure. */
|
|
38
|
+
event: "codex.turn.failed" | "claude.result.is_error" | "grok.error";
|
|
39
|
+
}
|
|
40
|
+
export declare function capMessage(message: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a later run could succeed without the operator changing anything.
|
|
43
|
+
* A 400 model rejection cannot; a request timeout, a too-early, or a server
|
|
44
|
+
* error can. An unknown status is not retryable: guessing "try again" on a
|
|
45
|
+
* failure we cannot classify would loop the lane.
|
|
46
|
+
*/
|
|
47
|
+
export declare function retryableFromStatus(status: number | null): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Codex `exec --json`: `{"type":"turn.failed","error":{"message":"<text>"}}`.
|
|
50
|
+
* Measured 2026-09-22 (lane 01M34HCCP1ZDN3H68ESCRZE7RZ): the message is
|
|
51
|
+
* itself a JSON string, `{"type":"error","status":400,"error":{"type":
|
|
52
|
+
* "invalid_request_error","message":"The 'terra' model is not supported when
|
|
53
|
+
* using Codex with a ChatGPT account."}}`. The inner message and status are
|
|
54
|
+
* read when it parses; otherwise the outer text is the message.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseCodexTurnFailed(text: string): ProviderRejection | null;
|
|
57
|
+
/**
|
|
58
|
+
* Claude `-p --output-format json`: the last `type: "result"` object with
|
|
59
|
+
* `is_error: true` and a numeric `api_error_status`. A result that is an
|
|
60
|
+
* error with no status is not a provider rejection: it is either the quota
|
|
61
|
+
* text shape quota.ts reads, or the worker's own failure, and keeps the
|
|
62
|
+
* existing classification.
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseClaudeResultRejection(text: string): ProviderRejection | null;
|
|
65
|
+
/**
|
|
66
|
+
* Grok `--output-format json` prints one object. Measured 2026-09-22 (lane
|
|
67
|
+
* 01M33X586J18J1B6GK0E6M4Y07): `{"type":"error","message":"Internal error:
|
|
68
|
+
* {\n \"message\": \"API error (status 402 Payment Required): Grok Build
|
|
69
|
+
* usage balance exhausted\",\n \"http_status\": 402, ...}", ...}`. The
|
|
70
|
+
* inner object carries `message` and `http_status`; when it does not parse,
|
|
71
|
+
* the status is read from `(status NNN` in the text.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseGrokErrorOutput(text: string): ProviderRejection | null;
|
|
74
|
+
/** The `terminal_details` record both the watcher and the reconcile pass write for a rejection. */
|
|
75
|
+
export declare function providerRejectionDetails(rejection: ProviderRejection, lane: {
|
|
76
|
+
model?: string;
|
|
77
|
+
account?: string;
|
|
78
|
+
}): Record<string, unknown>;
|
|
79
|
+
export declare function parseProviderRejection(provider: LaneProvider, text: string): ProviderRejection | null;
|
|
80
|
+
/**
|
|
81
|
+
* Read this run's bytes of `logs/worker.out` (past `workerOutOffset`, the
|
|
82
|
+
* same cut delivery.ts uses) and return the provider rejection they carry,
|
|
83
|
+
* or null. A missing file is null; any other read error propagates.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readProviderRejection(runDir: string, provider: LaneProvider, workerOutOffset?: number): ProviderRejection | null;
|
|
86
|
+
//# sourceMappingURL=provider-rejection.d.ts.map
|