@indigoai-us/hq-cli 5.125.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 +118 -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 +407 -4
- package/dist/command-catalog.generated.js +522 -4
- 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/mesh.js +37 -9
- 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/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/daemon/config.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/config.js +75 -0
- 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/scan-packages/index.js +17 -12
- package/dist/lib/work-context/config.d.ts +20 -0
- package/dist/lib/work-context/config.js +26 -5
- package/dist/lib/work-context/reconcile.js +28 -7
- 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
package/dist/commands/mesh.js
CHANGED
|
@@ -10,7 +10,7 @@ import { findHqRoot } from "../utils/manifest.js";
|
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { loadCachedTokens, isMachineIdentity } from "../utils/cognito-session.js";
|
|
12
12
|
import * as readline from "node:readline/promises";
|
|
13
|
-
import { STORY_STATUSES, appendThreadEvent,
|
|
13
|
+
import { STORY_STATUSES, appendThreadEvent, ensureProjectThread, eventPayload, listActiveMembershipCompanies, listActiveThreads, patchStoryStatus, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
14
14
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
15
15
|
import { clearDefaultCompany, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
16
16
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
@@ -29,7 +29,7 @@ import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
|
29
29
|
import { readLegacyBacklog } from "../lib/mesh/live/emit-replay.js";
|
|
30
30
|
import { resolveMeshEmitMode, writeMeshConfigMode, } from "../lib/mesh/live/daemon/mode.js";
|
|
31
31
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
32
|
-
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, detectPlatform, formatDaemonDoctor, installDaemonService, readDaemonState, runMeshDaemon, uninstallDaemonService, daemonDir, } from "../lib/mesh/live/daemon/index.js";
|
|
32
|
+
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, findAncestorHqRoot, resolveInstallHqRoot, detectPlatform, formatDaemonDoctor, installDaemonService, readDaemonState, runMeshDaemon, uninstallDaemonService, daemonDir, } from "../lib/mesh/live/daemon/index.js";
|
|
33
33
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
34
34
|
import { formatMigrateConfirmation, submitSessionMigration, } from "../lib/work-context/migrate.js";
|
|
35
35
|
import { formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, submitOrganizeDecision, } from "../lib/work-context/organize.js";
|
|
@@ -93,7 +93,10 @@ async function runEvent(verb, opts) {
|
|
|
93
93
|
summary: opts.summary,
|
|
94
94
|
});
|
|
95
95
|
const eventKind = verb === "start" ? "claim" : verb;
|
|
96
|
-
const
|
|
96
|
+
const claimant = eventKind === "claim"
|
|
97
|
+
? await resolveMeshPrincipalUid(token, loadCachedTokens())
|
|
98
|
+
: "";
|
|
99
|
+
const event = await appendThreadEvent(token, company.companyUid, ensured.threadId, eventKind, eventPayload(eventKind, opts, claimant));
|
|
97
100
|
if (opts.json) {
|
|
98
101
|
console.log(JSON.stringify({
|
|
99
102
|
ok: true,
|
|
@@ -1227,11 +1230,13 @@ export function registerMeshCommand(program) {
|
|
|
1227
1230
|
daemon
|
|
1228
1231
|
.command("run")
|
|
1229
1232
|
.description("Start the single-instance daemon (pid lock, spool watch, presence)")
|
|
1230
|
-
.
|
|
1233
|
+
.option("--hq-root <path>", "HQ tree root (else HQ_ROOT / persisted trusted roots); the Windows task passes this explicitly")
|
|
1234
|
+
.action((opts) => wrap(() => runDaemonRun(opts))());
|
|
1231
1235
|
daemon
|
|
1232
1236
|
.command("install")
|
|
1233
|
-
.description("Install LaunchAgent (macOS)
|
|
1237
|
+
.description("Install LaunchAgent (macOS), systemd user unit (Linux), or Scheduled Task (Windows)")
|
|
1234
1238
|
.option("--system-user <user>", "Write a root-owned unit template for agent boxes (e.g. ec2-user)")
|
|
1239
|
+
.option("--hq-root <path>", "HQ root to record for the service (else HQ_ROOT, else the nearest HQ root above cwd)")
|
|
1235
1240
|
.option("--json", "Print machine-readable JSON")
|
|
1236
1241
|
.action((opts) => wrap(() => runDaemonInstall(opts))());
|
|
1237
1242
|
daemon
|
|
@@ -1250,19 +1255,24 @@ export function registerMeshCommand(program) {
|
|
|
1250
1255
|
.option("--json", "Print machine-readable JSON")
|
|
1251
1256
|
.action((opts) => wrap(() => runDaemonDoctorCmd(opts))());
|
|
1252
1257
|
}
|
|
1253
|
-
async function runDaemonRun() {
|
|
1254
|
-
const
|
|
1258
|
+
async function runDaemonRun(opts = {}) {
|
|
1259
|
+
const hqRoot = opts.hqRoot?.trim() || undefined;
|
|
1260
|
+
const handle = await runMeshDaemon(hqRoot ? { hqRoot } : {});
|
|
1255
1261
|
// Stay alive until signal handlers call stop + exit.
|
|
1256
1262
|
await new Promise(() => {
|
|
1257
1263
|
void handle;
|
|
1258
1264
|
});
|
|
1259
1265
|
}
|
|
1260
1266
|
async function runDaemonInstall(opts) {
|
|
1267
|
+
// Verified root only: a repo's own .claude/ must never become the daemon's
|
|
1268
|
+
// HQ root (that path is persisted as trusted and used for attribution).
|
|
1269
|
+
const hqRoot = resolveInstallHqRoot({ explicit: opts.hqRoot });
|
|
1261
1270
|
const result = installDaemonService({
|
|
1262
1271
|
platform: detectPlatform(),
|
|
1263
1272
|
paths: buildInstallPaths({
|
|
1264
1273
|
systemUser: opts.systemUser,
|
|
1265
|
-
hqRoot:
|
|
1274
|
+
hqRoot: hqRoot.root,
|
|
1275
|
+
hqRootSource: hqRoot.source,
|
|
1266
1276
|
}),
|
|
1267
1277
|
systemUser: opts.systemUser,
|
|
1268
1278
|
});
|
|
@@ -1273,10 +1283,15 @@ async function runDaemonInstall(opts) {
|
|
|
1273
1283
|
platform: result.platform,
|
|
1274
1284
|
dest: result.dest,
|
|
1275
1285
|
message: result.message,
|
|
1286
|
+
hqRoot: hqRoot.root,
|
|
1287
|
+
hqRootSource: hqRoot.source,
|
|
1288
|
+
...(result.notice ? { notice: result.notice } : {}),
|
|
1276
1289
|
}, null, 2));
|
|
1277
1290
|
return;
|
|
1278
1291
|
}
|
|
1279
1292
|
console.log(result.message);
|
|
1293
|
+
if (result.notice)
|
|
1294
|
+
console.log(result.notice);
|
|
1280
1295
|
}
|
|
1281
1296
|
async function runDaemonUninstall(opts) {
|
|
1282
1297
|
const result = uninstallDaemonService({
|
|
@@ -1310,12 +1325,21 @@ async function runDaemonStatus(opts) {
|
|
|
1310
1325
|
running: result.running ?? false,
|
|
1311
1326
|
dest: result.dest,
|
|
1312
1327
|
message: result.message,
|
|
1328
|
+
...(result.lastResult !== undefined ? { lastResult: result.lastResult } : {}),
|
|
1329
|
+
...(result.lastRunFailed !== undefined ? { lastRunFailed: result.lastRunFailed } : {}),
|
|
1330
|
+
...(result.recordedHqRoot !== undefined ? { hqRoot: result.recordedHqRoot } : {}),
|
|
1331
|
+
...(result.rootValid !== undefined ? { rootValid: result.rootValid } : {}),
|
|
1332
|
+
...(result.notice ? { notice: result.notice } : {}),
|
|
1313
1333
|
mqttState: state?.mqttState,
|
|
1314
1334
|
presenceRefusal,
|
|
1315
1335
|
}, null, 2));
|
|
1316
1336
|
return;
|
|
1317
1337
|
}
|
|
1318
1338
|
console.log(result.message);
|
|
1339
|
+
if (result.recordedHqRoot)
|
|
1340
|
+
console.log(`hq root: ${result.recordedHqRoot}`);
|
|
1341
|
+
if (result.notice)
|
|
1342
|
+
console.log(result.notice);
|
|
1319
1343
|
if (presenceRefusal) {
|
|
1320
1344
|
console.log(`presence refusal: ${presenceRefusal.code} (HTTP ${presenceRefusal.status}); next retry at ${presenceRefusal.nextRetryAt}`);
|
|
1321
1345
|
}
|
|
@@ -1324,7 +1348,11 @@ async function runDaemonStatus(opts) {
|
|
|
1324
1348
|
}
|
|
1325
1349
|
}
|
|
1326
1350
|
async function runDaemonDoctorCmd(opts) {
|
|
1327
|
-
|
|
1351
|
+
// Same predicate as install: a repo's .claude/ is not an HQ root. Doctor
|
|
1352
|
+
// only reads, so an unresolvable root is reported as absent, not fatal.
|
|
1353
|
+
const report = collectDaemonDoctor({
|
|
1354
|
+
hqRoot: process.env.HQ_ROOT?.trim() || findAncestorHqRoot(process.cwd()) || undefined,
|
|
1355
|
+
});
|
|
1328
1356
|
if (opts.json) {
|
|
1329
1357
|
console.log(JSON.stringify({ ok: !report.unhealthy, ...report }, null, 2));
|
|
1330
1358
|
return;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who a new company is created for, when `hq onboard create-company` is run
|
|
3
|
+
* without `--email` / `--person-name`.
|
|
4
|
+
*
|
|
5
|
+
* The signed-in account already carries both on its ID token. Asking the
|
|
6
|
+
* person to type their own name again — which is what the setup bot had to do,
|
|
7
|
+
* because `hq whoami` shows only the email — is a question HQ can answer
|
|
8
|
+
* itself. Explicit flags still win.
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateCompanyIdentity {
|
|
11
|
+
email: string;
|
|
12
|
+
personName: string;
|
|
13
|
+
}
|
|
14
|
+
export type CreateCompanyIdentityResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
identity: CreateCompanyIdentity;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
/** Pure: flags first, then the ID token's claims. */
|
|
22
|
+
export declare function resolveCreateCompanyIdentity(flags: {
|
|
23
|
+
email?: string;
|
|
24
|
+
personName?: string;
|
|
25
|
+
}, claims: Record<string, unknown>): CreateCompanyIdentityResult;
|
|
26
|
+
//# sourceMappingURL=onboard-create-identity.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3
|
+
}
|
|
4
|
+
/** Pure: flags first, then the ID token's claims. */
|
|
5
|
+
export function resolveCreateCompanyIdentity(flags, claims) {
|
|
6
|
+
const email = text(flags.email) || text(claims.email);
|
|
7
|
+
const personName = text(flags.personName) ||
|
|
8
|
+
text(claims.name) ||
|
|
9
|
+
[text(claims.given_name), text(claims.family_name)].filter(Boolean).join(" ");
|
|
10
|
+
const missing = [];
|
|
11
|
+
if (!email)
|
|
12
|
+
missing.push("--email");
|
|
13
|
+
if (!personName)
|
|
14
|
+
missing.push("--person-name");
|
|
15
|
+
if (missing.length > 0) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
message: `Your HQ sign-in does not say ${missing.length === 2 ? "your email or name" : missing[0] === "--email" ? "your email" : "your name"}. Pass ${missing.join(" and ")}.`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { ok: true, identity: { email, personName } };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=onboard-create-identity.js.map
|
package/dist/commands/onboard.js
CHANGED
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import chalk from "chalk";
|
|
22
22
|
import { runOnboardCli, readCheckpoint } from "../lib/onboarding/index.js";
|
|
23
|
-
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
23
|
+
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoIdToken, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
24
24
|
import { createDefaultVaultClient } from "./cloud-provision.js";
|
|
25
25
|
import { detectOnboardingIdentityMismatch } from "./onboard-identity-guard.js";
|
|
26
26
|
import { planOnboardJoin } from "./onboard-join.js";
|
|
27
27
|
import { buildCreateCompanyWarning } from "./onboard-warning.js";
|
|
28
|
+
import { resolveCreateCompanyIdentity } from "./onboard-create-identity.js";
|
|
29
|
+
import { peekIdToken } from "../utils/id-token.js";
|
|
28
30
|
import { addBotToNewCompany, botToAddAfterCreateCompany } from "./onboard-bot-membership.js";
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
32
|
// Command registration
|
|
@@ -40,11 +42,18 @@ export function registerOnboardCommand(program) {
|
|
|
40
42
|
"a same-named company makes a separate one you'd be alone in.")
|
|
41
43
|
.requiredOption("--slug <slug>", "Company slug (used as bucket name suffix)")
|
|
42
44
|
.requiredOption("--name <name>", "Company display name")
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
+
.option("--email <email>", "Your email (default: your HQ sign-in's email; must match it)")
|
|
46
|
+
.option("--person-name <name>", "Your display name (default: the name on your HQ sign-in)")
|
|
45
47
|
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
46
|
-
.action(async (
|
|
48
|
+
.action(async (flags) => {
|
|
47
49
|
try {
|
|
50
|
+
const idToken = await ensureCognitoIdToken();
|
|
51
|
+
const resolved = resolveCreateCompanyIdentity(flags, peekIdToken(idToken));
|
|
52
|
+
if (!resolved.ok) {
|
|
53
|
+
console.error(chalk.red(`\n✗ ${resolved.message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const options = { ...flags, ...resolved.identity };
|
|
48
57
|
console.log(chalk.bold(`\nHQ Onboard — Create Company`));
|
|
49
58
|
console.log(` Company: ${options.name} (${options.slug})`);
|
|
50
59
|
console.log(` Person: ${options.personName} <${options.email}>`);
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* - the final answer is posted only when it adds something: text already
|
|
16
16
|
* posted (or about to be) is never repeated, including Grok's final result
|
|
17
17
|
* that is just every message concatenated
|
|
18
|
+
* - an in-between message that asks the person something (it has a "?") is
|
|
19
|
+
* held, not posted: the model often asks, keeps working, then asks the same
|
|
20
|
+
* thing again in its final answer, and the person read the question twice.
|
|
21
|
+
* It goes out only if the turn ends without a final answer of its own
|
|
18
22
|
*
|
|
19
23
|
* Posts are sent strictly in order. A failed post is logged by the caller's
|
|
20
24
|
* `send` and does not stop the turn.
|
|
@@ -46,6 +50,8 @@ export declare class ProgressPoster {
|
|
|
46
50
|
private readonly workingNoticeMs;
|
|
47
51
|
private readonly workingNoticeText;
|
|
48
52
|
private pending;
|
|
53
|
+
/** In-between questions, held until the turn ends (see the header). */
|
|
54
|
+
private heldQuestions;
|
|
49
55
|
/** Every message the model has said this turn (posted or pending), in order. */
|
|
50
56
|
private readonly said;
|
|
51
57
|
private flushTimer;
|
package/dist/lib/bot/progress.js
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* - the final answer is posted only when it adds something: text already
|
|
16
16
|
* posted (or about to be) is never repeated, including Grok's final result
|
|
17
17
|
* that is just every message concatenated
|
|
18
|
+
* - an in-between message that asks the person something (it has a "?") is
|
|
19
|
+
* held, not posted: the model often asks, keeps working, then asks the same
|
|
20
|
+
* thing again in its final answer, and the person read the question twice.
|
|
21
|
+
* It goes out only if the turn ends without a final answer of its own
|
|
18
22
|
*
|
|
19
23
|
* Posts are sent strictly in order. A failed post is logged by the caller's
|
|
20
24
|
* `send` and does not stop the turn.
|
|
@@ -37,6 +41,8 @@ export class ProgressPoster {
|
|
|
37
41
|
workingNoticeMs;
|
|
38
42
|
workingNoticeText;
|
|
39
43
|
pending = [];
|
|
44
|
+
/** In-between questions, held until the turn ends (see the header). */
|
|
45
|
+
heldQuestions = [];
|
|
40
46
|
/** Every message the model has said this turn (posted or pending), in order. */
|
|
41
47
|
said = [];
|
|
42
48
|
flushTimer = null;
|
|
@@ -75,6 +81,10 @@ export class ProgressPoster {
|
|
|
75
81
|
return;
|
|
76
82
|
this.cancelNotice();
|
|
77
83
|
this.said.push(t);
|
|
84
|
+
if (t.includes("?")) {
|
|
85
|
+
this.heldQuestions.push(t);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
this.pending.push(t);
|
|
79
89
|
this.scheduleFlush();
|
|
80
90
|
}
|
|
@@ -97,8 +107,12 @@ export class ProgressPoster {
|
|
|
97
107
|
this.cancelNotice();
|
|
98
108
|
this.cancelFlush();
|
|
99
109
|
const final = finalText.trim();
|
|
110
|
+
const held = this.heldQuestions;
|
|
111
|
+
this.heldQuestions = [];
|
|
100
112
|
if (final && !this.repeatsSaid(final))
|
|
101
113
|
this.pending.push(final);
|
|
114
|
+
else if (held.length > 0)
|
|
115
|
+
this.pending.push(...held);
|
|
102
116
|
if (this.pending.length === 0 && this.delivered === 0 && !final)
|
|
103
117
|
this.pending.push("(no reply)");
|
|
104
118
|
this.flushNow();
|
|
@@ -114,6 +128,8 @@ export class ProgressPoster {
|
|
|
114
128
|
this.finished = true;
|
|
115
129
|
this.cancelNotice();
|
|
116
130
|
this.cancelFlush();
|
|
131
|
+
this.pending.push(...this.heldQuestions);
|
|
132
|
+
this.heldQuestions = [];
|
|
117
133
|
this.flushNow();
|
|
118
134
|
return this.chain;
|
|
119
135
|
}
|
|
@@ -1,16 +1,39 @@
|
|
|
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
|
*/
|
|
13
22
|
import { type BotRuntime, type RuntimeTurnInput } from "./types.js";
|
|
23
|
+
export declare const GROK_OUTPUT_FORMAT_LEGACY = "streaming-messages-json";
|
|
24
|
+
export declare const GROK_OUTPUT_FORMAT_STREAMING_JSON = "streaming-json";
|
|
25
|
+
export declare function resetGrokOutputFormatCache(): void;
|
|
26
|
+
/** Pick the streaming `--output-format` grok's `--help` says it accepts. */
|
|
27
|
+
export declare function grokOutputFormatFromHelp(helpText: string): string;
|
|
28
|
+
/** Probe `grok --help` once and cache the streaming format. */
|
|
29
|
+
export declare function probeGrokOutputFormat(readHelp?: () => string): string;
|
|
30
|
+
/**
|
|
31
|
+
* When grok 0.2.56 rejects the pre-0.2.56 flag, rewrite argv to
|
|
32
|
+
* `streaming-json` and remember that for later turns.
|
|
33
|
+
*/
|
|
34
|
+
export declare function grokRetryArgsAfterInvalidOutputFormat(args: string[], stderr: string): string[] | undefined;
|
|
14
35
|
export declare function grokPrompt(input: RuntimeTurnInput): string;
|
|
15
|
-
export declare function createGrokRuntime(
|
|
36
|
+
export declare function createGrokRuntime(opts?: {
|
|
37
|
+
grokHelp?: string;
|
|
38
|
+
}): BotRuntime;
|
|
16
39
|
//# sourceMappingURL=grok.d.ts.map
|
|
@@ -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
|
}
|
|
@@ -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
|