@indigoai-us/hq-cli 5.124.0 → 5.126.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 +149 -0
- package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
- package/assets/bot-workers/setup/skills/your-business.md +270 -0
- package/assets/bot-workers/setup/worker.yaml +189 -58
- package/dist/command-catalog.generated.d.ts +414 -5
- package/dist/command-catalog.generated.js +531 -5
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/bot.d.ts +10 -0
- package/dist/commands/bot.js +6 -0
- package/dist/commands/lanes.d.ts +75 -0
- package/dist/commands/lanes.js +2032 -0
- package/dist/commands/login.js +49 -6
- package/dist/commands/mesh.js +133 -28
- package/dist/commands/onboard-create-identity.d.ts +26 -0
- package/dist/commands/onboard-create-identity.js +23 -0
- package/dist/commands/onboard.js +13 -4
- package/dist/lib/bot/progress.d.ts +6 -0
- package/dist/lib/bot/progress.js +16 -0
- package/dist/lib/bot/runtime/grok.d.ts +30 -7
- package/dist/lib/bot/runtime/grok.js +86 -8
- package/dist/lib/bot/runtime/index.js +15 -8
- package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
- package/dist/lib/bot/runtime/messages-stream.js +49 -3
- package/dist/lib/doctor/checks/work-context.js +4 -2
- package/dist/lib/lanes/accounts.d.ts +43 -0
- package/dist/lib/lanes/accounts.js +128 -0
- package/dist/lib/lanes/config.d.ts +56 -0
- package/dist/lib/lanes/config.js +351 -0
- package/dist/lib/lanes/errors.d.ts +39 -0
- package/dist/lib/lanes/errors.js +72 -0
- package/dist/lib/lanes/index.d.ts +19 -0
- package/dist/lib/lanes/index.js +12 -0
- package/dist/lib/lanes/liveness.d.ts +89 -0
- package/dist/lib/lanes/liveness.js +273 -0
- package/dist/lib/lanes/registry.d.ts +103 -0
- package/dist/lib/lanes/registry.js +247 -0
- package/dist/lib/lanes/store.d.ts +64 -0
- package/dist/lib/lanes/store.js +627 -0
- package/dist/lib/lanes/throttle.d.ts +89 -0
- package/dist/lib/lanes/throttle.js +297 -0
- package/dist/lib/lanes/types.d.ts +193 -0
- package/dist/lib/lanes/types.js +217 -0
- package/dist/lib/mesh/live/backfill-held.d.ts +2 -0
- package/dist/lib/mesh/live/backfill-held.js +8 -4
- package/dist/lib/mesh/live/daemon/board-refresh.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/board-refresh.js +6 -0
- package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/config.js +75 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +21 -1
- package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
- package/dist/lib/mesh/live/daemon/index.js +3 -3
- package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
- package/dist/lib/mesh/live/daemon/install.js +444 -2
- package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/paths.js +2 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +11 -0
- package/dist/lib/mesh/live/daemon/run.js +129 -10
- package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
- package/dist/lib/mesh/live/flush.d.ts +3 -0
- package/dist/lib/mesh/live/flush.js +11 -0
- package/dist/lib/scan-packages/index.js +17 -12
- package/dist/lib/work-context/company.d.ts +8 -0
- package/dist/lib/work-context/company.js +62 -18
- package/dist/lib/work-context/config.d.ts +55 -0
- package/dist/lib/work-context/config.js +333 -67
- package/dist/lib/work-context/contract.d.ts +2 -0
- package/dist/lib/work-context/default-company.d.ts +43 -0
- package/dist/lib/work-context/default-company.js +83 -0
- package/dist/lib/work-context/outbox.d.ts +5 -1
- package/dist/lib/work-context/outbox.js +5 -0
- package/dist/lib/work-context/reconcile.d.ts +4 -0
- package/dist/lib/work-context/reconcile.js +86 -9
- package/dist/lib/work-context/state.d.ts +2 -0
- package/dist/lib/work-context/state.js +14 -0
- package/dist/lib/work-context/windows-acl.d.ts +123 -0
- package/dist/lib/work-context/windows-acl.js +425 -0
- package/dist/utils/pack-contributions.js +5 -4
- package/package.json +1 -1
|
@@ -1,23 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grok CLI headless adapter.
|
|
3
3
|
*
|
|
4
|
-
* grok -p <prompt> --output-format streaming
|
|
4
|
+
* grok -p <prompt> --output-format <streaming format> --always-approve
|
|
5
5
|
* --no-subagents --cwd <cwd> [-m m] [-r <sessionId>]
|
|
6
6
|
*
|
|
7
|
-
* stdout is NDJSON
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* stdout is NDJSON. Older grok (before 0.2.56) wrote Anthropic Messages
|
|
8
|
+
* shapes under `--output-format streaming-messages-json` (see
|
|
9
|
+
* messages-stream.ts). grok 0.2.56 dropped that value and emits token
|
|
10
|
+
* events under `streaming-json`:
|
|
11
|
+
*
|
|
12
|
+
* {"type":"thought","data":…}
|
|
13
|
+
* {"type":"text","data":"Hi"}
|
|
14
|
+
* {"type":"end","stopReason":"EndTurn","sessionId":…}
|
|
15
|
+
*
|
|
16
|
+
* The format is probed from `grok --help` once per process (and cached).
|
|
17
|
+
* If an older grok still lists `streaming-messages-json`, that is used.
|
|
18
|
+
* If the probe misses and grok rejects the old value, the turn retries
|
|
19
|
+
* with `streaming-json`. The older `{ text, sessionId }` envelope is
|
|
20
|
+
* still accepted.
|
|
12
21
|
*/
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
13
23
|
import { messagesStreamEvents, messagesStreamFailure, messagesStreamResult } from "./messages-stream.js";
|
|
14
24
|
import { parseJsonLine, RuntimeError } from "./types.js";
|
|
25
|
+
export const GROK_OUTPUT_FORMAT_LEGACY = "streaming-messages-json";
|
|
26
|
+
export const GROK_OUTPUT_FORMAT_STREAMING_JSON = "streaming-json";
|
|
27
|
+
let cachedOutputFormat;
|
|
28
|
+
export function resetGrokOutputFormatCache() {
|
|
29
|
+
cachedOutputFormat = undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Pick the streaming `--output-format` grok's `--help` says it accepts. */
|
|
32
|
+
export function grokOutputFormatFromHelp(helpText) {
|
|
33
|
+
const section = /--output-format\s*<OUTPUT_FORMAT>[\s\S]*?\[possible values:\s*([^\]]+)\]/i.exec(helpText);
|
|
34
|
+
const values = (section?.[1] ?? "")
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((s) => s.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
if (values.includes(GROK_OUTPUT_FORMAT_LEGACY))
|
|
39
|
+
return GROK_OUTPUT_FORMAT_LEGACY;
|
|
40
|
+
if (values.includes(GROK_OUTPUT_FORMAT_STREAMING_JSON))
|
|
41
|
+
return GROK_OUTPUT_FORMAT_STREAMING_JSON;
|
|
42
|
+
if (helpText.includes(GROK_OUTPUT_FORMAT_LEGACY))
|
|
43
|
+
return GROK_OUTPUT_FORMAT_LEGACY;
|
|
44
|
+
if (/\bstreaming-json\b/.test(helpText))
|
|
45
|
+
return GROK_OUTPUT_FORMAT_STREAMING_JSON;
|
|
46
|
+
return GROK_OUTPUT_FORMAT_LEGACY;
|
|
47
|
+
}
|
|
48
|
+
function defaultGrokHelp() {
|
|
49
|
+
try {
|
|
50
|
+
return execFileSync("grok", ["--help"], {
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
timeout: 5_000,
|
|
53
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const e = err;
|
|
58
|
+
return `${e.stdout ?? ""}\n${e.stderr ?? ""}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Probe `grok --help` once and cache the streaming format. */
|
|
62
|
+
export function probeGrokOutputFormat(readHelp = defaultGrokHelp) {
|
|
63
|
+
if (cachedOutputFormat)
|
|
64
|
+
return cachedOutputFormat;
|
|
65
|
+
let help;
|
|
66
|
+
try {
|
|
67
|
+
help = readHelp();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
help = "";
|
|
71
|
+
}
|
|
72
|
+
cachedOutputFormat = grokOutputFormatFromHelp(help);
|
|
73
|
+
return cachedOutputFormat;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* When grok 0.2.56 rejects the pre-0.2.56 flag, rewrite argv to
|
|
77
|
+
* `streaming-json` and remember that for later turns.
|
|
78
|
+
*/
|
|
79
|
+
export function grokRetryArgsAfterInvalidOutputFormat(args, stderr) {
|
|
80
|
+
if (!/invalid value ['"]streaming-messages-json['"]/.test(stderr))
|
|
81
|
+
return undefined;
|
|
82
|
+
const i = args.indexOf("--output-format");
|
|
83
|
+
if (i < 0 || args[i + 1] === GROK_OUTPUT_FORMAT_STREAMING_JSON)
|
|
84
|
+
return undefined;
|
|
85
|
+
const next = args.slice();
|
|
86
|
+
next[i + 1] = GROK_OUTPUT_FORMAT_STREAMING_JSON;
|
|
87
|
+
cachedOutputFormat = GROK_OUTPUT_FORMAT_STREAMING_JSON;
|
|
88
|
+
return next;
|
|
89
|
+
}
|
|
15
90
|
export function grokPrompt(input) {
|
|
16
91
|
if (input.sessionId)
|
|
17
92
|
return input.prompt;
|
|
18
93
|
return `${input.systemPrompt}\n\n---\n\nMessage from your owner:\n\n${input.prompt}`;
|
|
19
94
|
}
|
|
20
|
-
export function createGrokRuntime() {
|
|
95
|
+
export function createGrokRuntime(opts = {}) {
|
|
96
|
+
const format = opts.grokHelp !== undefined
|
|
97
|
+
? grokOutputFormatFromHelp(opts.grokHelp)
|
|
98
|
+
: (cachedOutputFormat ?? GROK_OUTPUT_FORMAT_LEGACY);
|
|
21
99
|
return {
|
|
22
100
|
id: "grok",
|
|
23
101
|
binary: "grok",
|
|
@@ -26,7 +104,7 @@ export function createGrokRuntime() {
|
|
|
26
104
|
"-p",
|
|
27
105
|
grokPrompt(input),
|
|
28
106
|
"--output-format",
|
|
29
|
-
|
|
107
|
+
format,
|
|
30
108
|
"--always-approve",
|
|
31
109
|
"--no-subagents",
|
|
32
110
|
"--cwd",
|
|
@@ -5,7 +5,7 @@ import * as os from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { createClaudeRuntime } from "./claude.js";
|
|
7
7
|
import { createCodexRuntime } from "./codex.js";
|
|
8
|
-
import { createGrokRuntime } from "./grok.js";
|
|
8
|
+
import { createGrokRuntime, grokRetryArgsAfterInvalidOutputFormat, probeGrokOutputFormat } from "./grok.js";
|
|
9
9
|
import { parseJsonLine, RuntimeError, turnTimeoutMs, stderrExcerpt, } from "./types.js";
|
|
10
10
|
export * from "./types.js";
|
|
11
11
|
export { createClaudeRuntime, claudePermissionMode } from "./claude.js";
|
|
@@ -16,6 +16,7 @@ export function runtimeFor(id, env = process.env) {
|
|
|
16
16
|
return createClaudeRuntime(env);
|
|
17
17
|
if (id === "codex")
|
|
18
18
|
return createCodexRuntime();
|
|
19
|
+
probeGrokOutputFormat();
|
|
19
20
|
return createGrokRuntime();
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
@@ -220,15 +221,21 @@ export async function runRuntimeTurn(runtime, input, deps = {}) {
|
|
|
220
221
|
}
|
|
221
222
|
: undefined;
|
|
222
223
|
const timeoutMs = deps.timeoutMs ?? turnTimeoutMs(env);
|
|
224
|
+
const spawnOptions = {
|
|
225
|
+
cwd: input.cwd,
|
|
226
|
+
env,
|
|
227
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
228
|
+
...(onStdoutLine ? { onStdoutLine } : {}),
|
|
229
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
230
|
+
};
|
|
223
231
|
let result;
|
|
224
232
|
try {
|
|
225
|
-
result = await spawnImpl(binary, args,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
233
|
+
result = await spawnImpl(binary, args, spawnOptions);
|
|
234
|
+
if (runtime.id === "grok" && result.code !== 0 && !input.signal?.aborted) {
|
|
235
|
+
const retryArgs = grokRetryArgsAfterInvalidOutputFormat(args, result.stderr);
|
|
236
|
+
if (retryArgs)
|
|
237
|
+
result = await spawnImpl(binary, retryArgs, spawnOptions);
|
|
238
|
+
}
|
|
232
239
|
}
|
|
233
240
|
catch (err) {
|
|
234
241
|
const code = err.code;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The NDJSON stream Claude Code (`--output-format stream-json --verbose`) and
|
|
3
|
-
* Grok (`--output-format streaming-messages-json`) both write: Anthropic
|
|
3
|
+
* older Grok (`--output-format streaming-messages-json`) both write: Anthropic
|
|
4
4
|
* Messages wire shapes, one event per line.
|
|
5
5
|
*
|
|
6
6
|
* {"type":"system","subtype":"init","session_id":…}
|
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
* {"type":"user","message":{"content":[{"type":"tool_result",…}]}}
|
|
10
10
|
* {"type":"result","subtype":"success","is_error":false,"result":"<last message>","session_id":…}
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* grok 0.2.56 dropped `streaming-messages-json` for `streaming-json`, which
|
|
13
|
+
* is token deltas rather than whole messages:
|
|
14
|
+
*
|
|
15
|
+
* {"type":"thought","data":…}
|
|
16
|
+
* {"type":"text","data":"Hi"}
|
|
17
|
+
* {"type":"end","stopReason":"EndTurn","sessionId":…}
|
|
18
|
+
*
|
|
19
|
+
* Text `data` chunks are concatenated for the final reply and never emitted
|
|
20
|
+
* as progress (they are token deltas). Recorded from Claude Code 2.1.258 and
|
|
21
|
+
* grok (model grok-4.6) on 2026-09-12, plus grok 0.2.56 on 2026-09-21;
|
|
13
22
|
* fixtures in runtime/fixtures/. Claude sends one content block per assistant
|
|
14
|
-
* event; Grok sends a whole message (thinking + text + tool_use) per event.
|
|
23
|
+
* event; older Grok sends a whole message (thinking + text + tool_use) per event.
|
|
15
24
|
*/
|
|
16
25
|
import { type RuntimeStreamEvent, type RuntimeTurnResult } from "./types.js";
|
|
17
26
|
/** Progress events in one stream line: top-level assistant text and tool starts only. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The NDJSON stream Claude Code (`--output-format stream-json --verbose`) and
|
|
3
|
-
* Grok (`--output-format streaming-messages-json`) both write: Anthropic
|
|
3
|
+
* older Grok (`--output-format streaming-messages-json`) both write: Anthropic
|
|
4
4
|
* Messages wire shapes, one event per line.
|
|
5
5
|
*
|
|
6
6
|
* {"type":"system","subtype":"init","session_id":…}
|
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
* {"type":"user","message":{"content":[{"type":"tool_result",…}]}}
|
|
10
10
|
* {"type":"result","subtype":"success","is_error":false,"result":"<last message>","session_id":…}
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* grok 0.2.56 dropped `streaming-messages-json` for `streaming-json`, which
|
|
13
|
+
* is token deltas rather than whole messages:
|
|
14
|
+
*
|
|
15
|
+
* {"type":"thought","data":…}
|
|
16
|
+
* {"type":"text","data":"Hi"}
|
|
17
|
+
* {"type":"end","stopReason":"EndTurn","sessionId":…}
|
|
18
|
+
*
|
|
19
|
+
* Text `data` chunks are concatenated for the final reply and never emitted
|
|
20
|
+
* as progress (they are token deltas). Recorded from Claude Code 2.1.258 and
|
|
21
|
+
* grok (model grok-4.6) on 2026-09-12, plus grok 0.2.56 on 2026-09-21;
|
|
13
22
|
* fixtures in runtime/fixtures/. Claude sends one content block per assistant
|
|
14
|
-
* event; Grok sends a whole message (thinking + text + tool_use) per event.
|
|
23
|
+
* event; older Grok sends a whole message (thinking + text + tool_use) per event.
|
|
15
24
|
*/
|
|
16
25
|
import { parseJsonLine, RuntimeError } from "./types.js";
|
|
17
26
|
/** Progress events in one stream line: top-level assistant text and tool starts only. */
|
|
@@ -48,6 +57,9 @@ export function messagesStreamEvents(line) {
|
|
|
48
57
|
* RuntimeError for an error result, or when there is nothing usable.
|
|
49
58
|
*/
|
|
50
59
|
export function messagesStreamResult(stdout, runtimeName) {
|
|
60
|
+
const token = grokTokenStreamResult(stdout, runtimeName);
|
|
61
|
+
if (token)
|
|
62
|
+
return token;
|
|
51
63
|
let result = null;
|
|
52
64
|
let sessionId;
|
|
53
65
|
let lastText;
|
|
@@ -78,6 +90,38 @@ export function messagesStreamResult(stdout, runtimeName) {
|
|
|
78
90
|
sessionId = result.session_id;
|
|
79
91
|
return { text, sessionId };
|
|
80
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* grok 0.2.56 `--output-format streaming-json`: concatenate text deltas and
|
|
95
|
+
* take sessionId from the terminal `end` event. Throws on `error`. Null when
|
|
96
|
+
* the stream is not this shape (so the Anthropic Messages parser can run).
|
|
97
|
+
*/
|
|
98
|
+
function grokTokenStreamResult(stdout, runtimeName) {
|
|
99
|
+
let text = "";
|
|
100
|
+
let sessionId;
|
|
101
|
+
let saw = false;
|
|
102
|
+
for (const line of stdout.split("\n")) {
|
|
103
|
+
const ev = parseJsonLine(line);
|
|
104
|
+
if (!ev)
|
|
105
|
+
continue;
|
|
106
|
+
if (ev.type === "text" && typeof ev.data === "string") {
|
|
107
|
+
saw = true;
|
|
108
|
+
text += ev.data;
|
|
109
|
+
}
|
|
110
|
+
else if (ev.type === "thought") {
|
|
111
|
+
saw = true;
|
|
112
|
+
}
|
|
113
|
+
else if (ev.type === "end") {
|
|
114
|
+
saw = true;
|
|
115
|
+
if (typeof ev.sessionId === "string")
|
|
116
|
+
sessionId = ev.sessionId;
|
|
117
|
+
}
|
|
118
|
+
else if (ev.type === "error" && typeof ev.message === "string") {
|
|
119
|
+
const detail = ev.message;
|
|
120
|
+
throw new RuntimeError(`${runtimeName} reported an error: ${detail.slice(0, 200)}`, detail.slice(0, 400), null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return saw ? { text, sessionId } : null;
|
|
124
|
+
}
|
|
81
125
|
/** The error text of an `is_error` result event, if the stream has one. */
|
|
82
126
|
export function messagesStreamFailure(stdout) {
|
|
83
127
|
let reason;
|
|
@@ -85,6 +129,8 @@ export function messagesStreamFailure(stdout) {
|
|
|
85
129
|
const ev = parseJsonLine(line);
|
|
86
130
|
if (ev?.type === "result" && ev.is_error === true && typeof ev.result === "string" && ev.result.trim())
|
|
87
131
|
reason = ev.result.trim();
|
|
132
|
+
if (ev?.type === "error" && typeof ev.message === "string" && ev.message.trim())
|
|
133
|
+
reason = ev.message.trim();
|
|
88
134
|
}
|
|
89
135
|
return reason;
|
|
90
136
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { readDeviceConfig,
|
|
9
|
+
import { readDeviceConfig, } from "../../work-context/config.js";
|
|
10
10
|
import { outboxStats, listOutboxOperations } from "../../work-context/outbox.js";
|
|
11
11
|
import { workContextRoot, workContextSessionsDir } from "../../work-context/paths.js";
|
|
12
12
|
import { countJsonlLines, deadLetterNonEmpty, } from "../../mesh/live/spool.js";
|
|
@@ -96,7 +96,9 @@ function runChecks(deps) {
|
|
|
96
96
|
// Default-company mode + migration capability (US-017B)
|
|
97
97
|
try {
|
|
98
98
|
const cfg = readDeviceConfig({ root });
|
|
99
|
-
const def =
|
|
99
|
+
const def = cfg.defaultCompany?.enabled && cfg.defaultCompany.slug
|
|
100
|
+
? cfg.defaultCompany
|
|
101
|
+
: null;
|
|
100
102
|
const mode = def?.enabled
|
|
101
103
|
? `enabled slug=${def.slug}`
|
|
102
104
|
: "disabled (dark / unset)";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider account resolution (spec §5.5).
|
|
3
|
+
*
|
|
4
|
+
* Naming an account and omitting one mean different things: an explicit
|
|
5
|
+
* disabled id is refused; omitting rotates past a disabled default.
|
|
6
|
+
* Status reports `configured` and `logged_in` separately. logged_in is
|
|
7
|
+
* derived from whether <home>/auth.json EXISTS — this module never reads,
|
|
8
|
+
* prints, copies, or hashes that file.
|
|
9
|
+
*/
|
|
10
|
+
import type { LaneProvider, LanesConfig } from "./types.js";
|
|
11
|
+
export declare const PROVIDER_HOME_ENV: Record<LaneProvider, string>;
|
|
12
|
+
export declare const AUTH_JSON_NAME = "auth.json";
|
|
13
|
+
export interface AccountDeps {
|
|
14
|
+
existsSync: (p: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedAccount {
|
|
17
|
+
provider: LaneProvider;
|
|
18
|
+
id: string;
|
|
19
|
+
home: string;
|
|
20
|
+
enabled: true;
|
|
21
|
+
envVar: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AccountStatus {
|
|
24
|
+
provider: LaneProvider;
|
|
25
|
+
id: string;
|
|
26
|
+
home: string;
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
disabled_reason?: string;
|
|
29
|
+
/** Present in personal/lanes/config.json. */
|
|
30
|
+
configured: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* True iff `<home>/auth.json` exists. Never inspects the file contents.
|
|
33
|
+
*/
|
|
34
|
+
logged_in: boolean;
|
|
35
|
+
envVar: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function resolveAccount(provider: LaneProvider, requestedId: string | undefined, config: LanesConfig): ResolvedAccount;
|
|
38
|
+
export declare function authJsonPath(home: string): string;
|
|
39
|
+
export declare function accountStatus(provider: LaneProvider, accountId: string, config: LanesConfig, deps?: AccountDeps): AccountStatus;
|
|
40
|
+
export declare function listAccountStatuses(provider: LaneProvider, config: LanesConfig, deps?: AccountDeps): AccountStatus[];
|
|
41
|
+
/** Environment assignment the worker process should receive. */
|
|
42
|
+
export declare function accountEnv(resolved: ResolvedAccount): Record<string, string>;
|
|
43
|
+
//# sourceMappingURL=accounts.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider account resolution (spec §5.5).
|
|
3
|
+
*
|
|
4
|
+
* Naming an account and omitting one mean different things: an explicit
|
|
5
|
+
* disabled id is refused; omitting rotates past a disabled default.
|
|
6
|
+
* Status reports `configured` and `logged_in` separately. logged_in is
|
|
7
|
+
* derived from whether <home>/auth.json EXISTS — this module never reads,
|
|
8
|
+
* prints, copies, or hashes that file.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { AccountResolutionError } from "./errors.js";
|
|
13
|
+
export const PROVIDER_HOME_ENV = {
|
|
14
|
+
claude: "CLAUDE_CONFIG_DIR",
|
|
15
|
+
codex: "CODEX_HOME",
|
|
16
|
+
grok: "GROK_HOME",
|
|
17
|
+
};
|
|
18
|
+
export const AUTH_JSON_NAME = "auth.json";
|
|
19
|
+
function defaultAccountDeps() {
|
|
20
|
+
return { existsSync: (p) => fs.existsSync(p) };
|
|
21
|
+
}
|
|
22
|
+
function enabledIds(provider) {
|
|
23
|
+
return Object.entries(provider.accounts)
|
|
24
|
+
.filter(([, account]) => account.enabled)
|
|
25
|
+
.map(([id]) => id);
|
|
26
|
+
}
|
|
27
|
+
function rotateEnabled(providerCfg, skipId) {
|
|
28
|
+
for (const [id, account] of Object.entries(providerCfg.accounts)) {
|
|
29
|
+
if (!account.enabled)
|
|
30
|
+
continue;
|
|
31
|
+
if (skipId !== undefined && id === skipId)
|
|
32
|
+
continue;
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
export function resolveAccount(provider, requestedId, config) {
|
|
38
|
+
const providerCfg = config.providers[provider];
|
|
39
|
+
const envVar = PROVIDER_HOME_ENV[provider];
|
|
40
|
+
const enabled = enabledIds(providerCfg);
|
|
41
|
+
if (requestedId !== undefined && requestedId.length > 0) {
|
|
42
|
+
const account = providerCfg.accounts[requestedId];
|
|
43
|
+
if (account === undefined) {
|
|
44
|
+
throw new AccountResolutionError("unknown_account", `Unknown account ${JSON.stringify(requestedId)} for ${provider}. Enabled: ${enabled.join(", ") || "(none)"}`, {
|
|
45
|
+
provider,
|
|
46
|
+
account: requestedId,
|
|
47
|
+
enabled,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (!account.enabled) {
|
|
51
|
+
throw new AccountResolutionError("account_disabled", `Account ${JSON.stringify(requestedId)} is disabled${account.disabled_reason ? `: ${account.disabled_reason}` : ""}`, {
|
|
52
|
+
account: requestedId,
|
|
53
|
+
reason: account.disabled_reason ?? "",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
provider,
|
|
58
|
+
id: requestedId,
|
|
59
|
+
home: account.home,
|
|
60
|
+
enabled: true,
|
|
61
|
+
envVar,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const defaultId = providerCfg.default_account;
|
|
65
|
+
const defaultAccount = providerCfg.accounts[defaultId];
|
|
66
|
+
if (defaultAccount?.enabled) {
|
|
67
|
+
return {
|
|
68
|
+
provider,
|
|
69
|
+
id: defaultId,
|
|
70
|
+
home: defaultAccount.home,
|
|
71
|
+
enabled: true,
|
|
72
|
+
envVar,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const rotated = rotateEnabled(providerCfg, defaultAccount ? defaultId : undefined);
|
|
76
|
+
if (rotated === undefined) {
|
|
77
|
+
throw new AccountResolutionError("no_enabled_account", `No enabled account for provider ${provider}`, { provider });
|
|
78
|
+
}
|
|
79
|
+
const rotatedAccount = providerCfg.accounts[rotated];
|
|
80
|
+
return {
|
|
81
|
+
provider,
|
|
82
|
+
id: rotated,
|
|
83
|
+
home: rotatedAccount.home,
|
|
84
|
+
enabled: true,
|
|
85
|
+
envVar,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function authJsonPath(home) {
|
|
89
|
+
return path.join(home, AUTH_JSON_NAME);
|
|
90
|
+
}
|
|
91
|
+
export function accountStatus(provider, accountId, config, deps = defaultAccountDeps()) {
|
|
92
|
+
const envVar = PROVIDER_HOME_ENV[provider];
|
|
93
|
+
const account = config.providers[provider].accounts[accountId];
|
|
94
|
+
if (account === undefined) {
|
|
95
|
+
return {
|
|
96
|
+
provider,
|
|
97
|
+
id: accountId,
|
|
98
|
+
home: "",
|
|
99
|
+
enabled: false,
|
|
100
|
+
configured: false,
|
|
101
|
+
logged_in: false,
|
|
102
|
+
envVar,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const authPath = authJsonPath(account.home);
|
|
106
|
+
const logged_in = deps.existsSync(authPath);
|
|
107
|
+
const status = {
|
|
108
|
+
provider,
|
|
109
|
+
id: accountId,
|
|
110
|
+
home: account.home,
|
|
111
|
+
enabled: account.enabled,
|
|
112
|
+
configured: true,
|
|
113
|
+
logged_in,
|
|
114
|
+
envVar,
|
|
115
|
+
};
|
|
116
|
+
if (account.disabled_reason !== undefined) {
|
|
117
|
+
status.disabled_reason = account.disabled_reason;
|
|
118
|
+
}
|
|
119
|
+
return status;
|
|
120
|
+
}
|
|
121
|
+
export function listAccountStatuses(provider, config, deps = defaultAccountDeps()) {
|
|
122
|
+
return Object.keys(config.providers[provider].accounts).map((id) => accountStatus(provider, id, config, deps));
|
|
123
|
+
}
|
|
124
|
+
/** Environment assignment the worker process should receive. */
|
|
125
|
+
export function accountEnv(resolved) {
|
|
126
|
+
return { [resolved.envVar]: resolved.home };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load and validate personal/lanes/config.json (spec §5.5).
|
|
3
|
+
*
|
|
4
|
+
* A missing file is the documented defaults, not an error. A `providers` key
|
|
5
|
+
* outside {claude, codex, grok} is rejected with the accepted list. Account
|
|
6
|
+
* homes are canonicalised with realpath; two account ids for one provider
|
|
7
|
+
* that resolve to the same home are refused (identity-keyed-cap rule).
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { type LaneProvider, type LanesConfig, type LivenessWindows, type ProviderConfig } from "./types.js";
|
|
11
|
+
export declare const LANES_CONFIG_REL: string;
|
|
12
|
+
export declare const DEFAULT_MAX_QUEUED = 3;
|
|
13
|
+
export declare const DEFAULT_CPU_BUSY_PAUSE = 0.85;
|
|
14
|
+
export declare const DEFAULT_CPU_BUSY_RESUME = 0.7;
|
|
15
|
+
export declare const DEFAULT_MIN_AVAILABLE_MEM_MB = 1024;
|
|
16
|
+
/** Spec §9: last event within 10 minutes → live. */
|
|
17
|
+
export declare const DEFAULT_LIVE_WITHIN_MS: number;
|
|
18
|
+
/** Spec §9: turn_start with no turn_end after 2 hours → lost. */
|
|
19
|
+
export declare const DEFAULT_TURN_START_LOST_MS: number;
|
|
20
|
+
/** Spec §9: no event for 12 hours (and no session_end) → lost. */
|
|
21
|
+
export declare const DEFAULT_SILENT_LOST_MS: number;
|
|
22
|
+
export declare const DEFAULT_LIVENESS_WINDOWS: LivenessWindows;
|
|
23
|
+
export declare const DEFAULT_PROVIDER_MAX_RUNNING: Record<LaneProvider, number>;
|
|
24
|
+
export declare const DEFAULT_ACCOUNT_HOMES: Record<LaneProvider, string>;
|
|
25
|
+
export interface ConfigLoadDeps {
|
|
26
|
+
homedir: () => string;
|
|
27
|
+
realpathSync: (p: string) => string;
|
|
28
|
+
existsSync: (p: string) => boolean;
|
|
29
|
+
readFileSync: (p: string, encoding: "utf8") => string;
|
|
30
|
+
}
|
|
31
|
+
export declare function expandHome(home: string, homedir: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical principal for an account home: realpath when the path exists,
|
|
34
|
+
* otherwise resolve existing ancestors and rejoin the missing tail so two
|
|
35
|
+
* not-yet-created spellings of the same directory still collide.
|
|
36
|
+
*/
|
|
37
|
+
export declare function canonicalizeHome(home: string, deps?: Pick<ConfigLoadDeps, "homedir" | "realpathSync" | "existsSync">): string;
|
|
38
|
+
export declare function documentedDefaultConfig(deps?: ConfigLoadDeps): LanesConfig;
|
|
39
|
+
export declare function parseLanesConfig(raw: unknown, deps?: ConfigLoadDeps): LanesConfig;
|
|
40
|
+
export declare function lanesConfigPath(hqRoot: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Load operator config. Missing file → documented defaults. Unreadable or
|
|
43
|
+
* invalid JSON is an error (never a silent default).
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadLanesConfig(hqRoot: string, deps?: ConfigLoadDeps): LanesConfig;
|
|
46
|
+
export interface ConfigWriteDeps {
|
|
47
|
+
mkdirSync: typeof fs.mkdirSync;
|
|
48
|
+
writeFileSync: typeof fs.writeFileSync;
|
|
49
|
+
renameSync: typeof fs.renameSync;
|
|
50
|
+
unlinkSync: typeof fs.unlinkSync;
|
|
51
|
+
}
|
|
52
|
+
/** Persist operator config. Homes are stored as already-canonicalised paths. */
|
|
53
|
+
export declare function writeLanesConfig(hqRoot: string, config: LanesConfig, deps?: ConfigWriteDeps): void;
|
|
54
|
+
/** Account running cap, defaulting to the provider ceiling when unset. */
|
|
55
|
+
export declare function accountMaxRunning(provider: ProviderConfig, accountId: string): number;
|
|
56
|
+
//# sourceMappingURL=config.d.ts.map
|