@rosthq/cli 0.7.30 → 0.7.32

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.
@@ -0,0 +1,2 @@
1
+ export declare function readCliVersion(): Promise<string | null>;
2
+ //# sourceMappingURL=cli-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-version.d.ts","sourceRoot":"","sources":["../src/cli-version.ts"],"names":[],"mappings":"AAgBA,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ7D"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKrE,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAoCvE"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKrE,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAoCvE"}
package/dist/index.js CHANGED
@@ -42464,6 +42464,16 @@ var softwareDeveloperTeamRoleSchema = external_exports.enum([
42464
42464
  "qa_release",
42465
42465
  "fold_memory"
42466
42466
  ]);
42467
+ var softwareDeveloperTeamLaneSchema = external_exports.enum(["runner", "cloud"]);
42468
+ var softwareDeveloperTeamLaneOverridesSchema = external_exports.object({
42469
+ forge_lead: softwareDeveloperTeamLaneSchema.optional(),
42470
+ planner: softwareDeveloperTeamLaneSchema.optional(),
42471
+ builder: softwareDeveloperTeamLaneSchema.optional(),
42472
+ plan_conformance_reviewer: softwareDeveloperTeamLaneSchema.optional(),
42473
+ security_reviewer: softwareDeveloperTeamLaneSchema.optional(),
42474
+ qa_release: softwareDeveloperTeamLaneSchema.optional(),
42475
+ fold_memory: softwareDeveloperTeamLaneSchema.optional()
42476
+ }).strict();
42467
42477
  var softwareEnvironmentSchema = external_exports.enum(["development", "preview", "production"]);
42468
42478
  var softwareGrantStatusSchema = external_exports.enum(["active", "revoked"]);
42469
42479
  var softwareConfigValueKindSchema = external_exports.enum(["plain", "secret_ref"]);
@@ -43045,7 +43055,14 @@ var softwareDeveloperTeamInstallInputSchema = external_exports.object({
43045
43055
  // auto-activate unless the owner explicitly acknowledges that posture. Never
43046
43056
  // silently inherit a more-permissive-than-designed ceiling (invariant #10). A
43047
43057
  // tenant within the envelope (locked_down/balanced) never needs this.
43048
- acknowledge_autonomy_ceiling: external_exports.boolean().default(false)
43058
+ acknowledge_autonomy_ceiling: external_exports.boolean().default(false),
43059
+ // DER-1317 (T0.2a): optionally pin an individual role to a specific execution
43060
+ // lane. Omitted roles staff their declared default lane (phase-owning agents =
43061
+ // `runner`; the human `forge_lead` seat is never staffed with an agent, so its
43062
+ // override is inert). `.nullable()` for the same confirm-replay round-trip
43063
+ // reason as `software_project_id` above (JSONB re-parse of a human-confirmed
43064
+ // command tolerates a JSON `null` for an omitted object).
43065
+ lane_overrides: softwareDeveloperTeamLaneOverridesSchema.nullable().optional()
43049
43066
  }).strict();
43050
43067
  var softwareDeveloperTeamSeatSummarySchema = external_exports.object({
43051
43068
  role: softwareDeveloperTeamRoleSchema,
@@ -45132,7 +45149,7 @@ External connectors are being rolled out provider by provider, conservatively (r
45132
45149
  order: 48,
45133
45150
  title: "CLI and MCP installation guide",
45134
45151
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
45135
- version: "2026-07-05.1",
45152
+ version: "2026-07-05.2",
45136
45153
  public: true,
45137
45154
  audiences: ["human", "cli", "mcp", "in_app_agent"],
45138
45155
  stages: ["company_setup", "staffing"],
@@ -45869,7 +45886,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
45869
45886
  | \`rost_create_a_forge_authority_profile\` | \`software_factory.authority_profile.create\` | Define a Forge authority profile (a named seat capability preset). Owner-only; defaults to the read_only preset. | Tenant-admin | Call with \`{"name":"Builder","preset":"contributor"}\`. |
45870
45887
  | \`rost_grant_forge_seat_authority\` | \`software_factory.authority.grant\` | Grant a seat a Forge authority profile on a project (seat \u2192 project \u2192 profile). Owner-only and human-gated; emits a durable authority-change event. | Tenant-admin | Call with \`{"software_project_id":"<project-id>","seat_id":"<seat-id>","authority_profile_id":"<profile-id>"}\`; non-interactive callers receive a confirmation handoff. |
45871
45888
  | \`rost_revoke_forge_seat_authority\` | \`software_factory.authority.revoke\` | Revoke a Forge authority grant (teardown). Owner-only and human-gated. | Tenant-admin | Call with \`{"grant_id":"<grant-id>"}\`; non-interactive callers receive a confirmation handoff. |
45872
- | \`rost_install_forge_developer_team\` | \`software_factory.developer_team.install\` | Install AND activate the governed Forge Developer Team: Forge Lead (human) plus Planner, Builder, plan reviewer, security reviewer, QA/release, and Fold/Memory seats with draft Charters, first-party Forge Skills, and authority presets. Approving ALSO auto-staffs the 6 agent seats as live occupancies at a pull-request-only, observe-first posture (open pull requests, never merge/deploy without a human), steward-chained to the human lead, each with a signed manifest + passed sandbox dry-run; it sets a conservative default token budget (only if none) and, for a supplied project, a default pr_only GitHub automation policy (only if none \u2014 never widening a stricter one). Entitlement-gated and human-gated. | Tenant-admin | Call with \`{"steward_seat_id":"<human-steward-seat-id>"}\` \u2014 \`software_project_id\` is optional (when supplied it scopes authority grants to that project; the team installs tenant-wide either way). If the company autonomy ceiling is more permissive than the observe-first envelope, add \`{"acknowledge_autonomy_ceiling":true}\` to activate. Non-interactive callers receive a confirmation handoff. |
45889
+ | \`rost_install_forge_developer_team\` | \`software_factory.developer_team.install\` | Install AND activate the governed Forge Developer Team: Forge Lead (human) plus Planner, Builder, plan reviewer, security reviewer, QA/release, and Fold/Memory seats with draft Charters, first-party Forge Skills, and authority presets. Approving ALSO auto-staffs the 6 agent seats as live occupancies at a pull-request-only, observe-first posture (open pull requests, never merge/deploy without a human), steward-chained to the human lead, each with a signed manifest + passed sandbox dry-run; the 6 agent seats staff the runner lane and go live in a PARKED state \u2014 they become claimable once a runner pairs and cannot claim work before then; it sets a conservative default token budget (only if none) and, for a supplied project, a default pr_only GitHub automation policy (only if none \u2014 never widening a stricter one). Entitlement-gated and human-gated. | Tenant-admin | Call with \`{"steward_seat_id":"<human-steward-seat-id>"}\` \u2014 \`software_project_id\` is optional (when supplied it scopes authority grants to that project; the team installs tenant-wide either way). If the company autonomy ceiling is more permissive than the observe-first envelope, add \`{"acknowledge_autonomy_ceiling":true}\` to activate. Add \`{"lane_overrides":{"qa_release":"cloud"}}\` to pin a role to the cloud lane instead of the default runner lane. Non-interactive callers receive a confirmation handoff. |
45873
45890
  | \`rost_list_forge_config\` | \`software_factory.config.list\` | List active Forge configuration entries. Plain values may be returned only when they passed the non-secret guard; secret refs appear as metadata and never include the ref value. | Tenant | Call with \`{}\` or \`{"software_project_id":"<project-id>","environment":"preview"}\`. |
45874
45891
  | \`rost_set_forge_config\` | \`software_factory.config.set\` | Human-gated config create/rotation for plain values or secret refs. Secret refs store only a vault pointer and raw secret-shaped plain values are rejected. | Tenant-admin | Call with \`{"software_project_id":"<project-id>","environment":"preview","key_name":"PUBLIC_BASE_URL","value_kind":"plain","plain_value":"https://preview.example.com"}\`. |
45875
45892
  | \`rost_list_forge_secret_requests\` | \`software_factory.secret_request.list\` | List missing-secret requests and their approval Task ids/statuses. It returns request metadata only \u2014 no secret values and no vault refs. | Tenant | Call with \`{}\` or \`{"status":"pending"}\`. |
@@ -48826,21 +48843,32 @@ function helpText() {
48826
48843
  ].join("\n");
48827
48844
  }
48828
48845
 
48829
- // src/update-check.ts
48846
+ // src/cli-version.ts
48830
48847
  import { readFile as readFile2 } from "node:fs/promises";
48831
48848
  import { dirname as dirname2, resolve } from "node:path";
48832
48849
  import { fileURLToPath as fileURLToPath2 } from "node:url";
48833
- var latestPackageSchema = external_exports.object({
48850
+ var packageMetadataSchema = external_exports.object({
48834
48851
  version: external_exports.string()
48835
48852
  });
48836
- var packageMetadataSchema = external_exports.object({
48853
+ async function readCliVersion() {
48854
+ const packageJsonPath = resolve(dirname2(fileURLToPath2(import.meta.url)), "../package.json");
48855
+ try {
48856
+ const metadata = packageMetadataSchema.parse(JSON.parse(await readFile2(packageJsonPath, "utf8")));
48857
+ return metadata.version;
48858
+ } catch {
48859
+ return null;
48860
+ }
48861
+ }
48862
+
48863
+ // src/update-check.ts
48864
+ var latestPackageSchema = external_exports.object({
48837
48865
  version: external_exports.string()
48838
48866
  });
48839
48867
  async function maybePrintUpgradeNudge(io) {
48840
48868
  if (process.env.CI || process.env.VITEST || process.env.CLI_SKIP_UPDATE_CHECK === "1") {
48841
48869
  return;
48842
48870
  }
48843
- const currentVersion = await readCurrentVersion();
48871
+ const currentVersion = await readCliVersion();
48844
48872
  if (!currentVersion || currentVersion === "0.0.0") {
48845
48873
  return;
48846
48874
  }
@@ -48853,15 +48881,6 @@ async function maybePrintUpgradeNudge(io) {
48853
48881
  `
48854
48882
  );
48855
48883
  }
48856
- async function readCurrentVersion() {
48857
- const packageJsonPath = resolve(dirname2(fileURLToPath2(import.meta.url)), "../package.json");
48858
- try {
48859
- const metadata = packageMetadataSchema.parse(JSON.parse(await readFile2(packageJsonPath, "utf8")));
48860
- return metadata.version;
48861
- } catch {
48862
- return null;
48863
- }
48864
- }
48865
48884
  async function fetchLatestVersion(packageName) {
48866
48885
  const controller = new AbortController();
48867
48886
  const timeout = setTimeout(() => controller.abort(), 750);
@@ -49081,7 +49100,7 @@ var COMMAND_MANIFEST = [
49081
49100
  { "id": "software_factory.config.list", "namespace": "software_factory", "action": "config.list", "title": "List Forge config", "description": "List active Forge config metadata for project environments. Plain config values may be returned; secret refs are represented only as has_secret_ref.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "environment", "flag": "environment", "type": "enum", "required": false, "enumValues": ["development", "preview", "production"] }], "hasComplexInput": false, "help": "List active Forge config entries. Plain config may be shown; secret refs are represented only as metadata." },
49082
49101
  { "id": "software_factory.config.set", "namespace": "software_factory", "action": "config.set", "title": "Set Forge config", "description": "Create or rotate a Forge project config entry. Secret config stores only a vault ref pointer; raw values are never accepted for secret_ref entries.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "environment", "flag": "environment", "type": "enum", "required": true, "enumValues": ["development", "preview", "production"] }, { "name": "key_name", "flag": "key-name", "type": "string", "required": true }, { "name": "value_kind", "flag": "value-kind", "type": "enum", "required": true, "enumValues": ["plain", "secret_ref"] }, { "name": "plain_value", "flag": "plain-value", "type": "string", "required": false }, { "name": "vault_ref", "flag": "vault-ref", "type": "string", "required": false }], "hasComplexInput": false, "help": "Set or rotate a Forge project config key. Secret entries store only vault refs and are human-gated." },
49083
49102
  { "id": "software_factory.conformance.record_findings", "namespace": "software_factory", "action": "conformance.record_findings", "title": "Record Forge conformance findings", "description": "The plan-conformance reviewer records one or more findings against a build request; open findings route the request back to implementation. Recording is evidence, not a decision \u2014 accepting a gap is a separate human gate. Requires the Forge add-on.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "phase_run_id", "flag": "phase-run-id", "type": "string", "required": false }], "hasComplexInput": true, "help": "Record structured Forge plan-conformance findings as reviewer evidence. Open findings route the request back to implementation; accepting a gap remains a separate human gate." },
49084
- { "id": "software_factory.developer_team.install", "namespace": "software_factory", "action": "developer_team.install", "title": "Install Forge Developer Team", "description": "Install AND activate the governed Forge Developer Team: 7 seats (1 human lead + 6 agents), first-party skills, authority presets, and a conservative token budget. Approving this ACTIVATES the 6 agents as live occupancies at a pr_only, observe-first posture: they open pull requests but never merge or deploy without a human. Entitlement-gated and human-confirmed.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": true }, { "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "assign_skills", "flag": "assign-skills", "type": "boolean", "required": false }, { "name": "acknowledge_autonomy_ceiling", "flag": "acknowledge-autonomy-ceiling", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Install the governed Forge Developer Team template with seats, draft Charters, skills, project authority grants, and audit records. Tenant-admin and human-gated; creates no live agent occupancy." },
49103
+ { "id": "software_factory.developer_team.install", "namespace": "software_factory", "action": "developer_team.install", "title": "Install Forge Developer Team", "description": "Install AND activate the governed Forge Developer Team: 7 seats (1 human lead + 6 agents), first-party skills, authority presets, and a conservative token budget. Approving this ACTIVATES the 6 agents as live occupancies at a pr_only, observe-first posture: they open pull requests but never merge or deploy without a human. Entitlement-gated and human-confirmed.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": true }, { "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "assign_skills", "flag": "assign-skills", "type": "boolean", "required": false }, { "name": "acknowledge_autonomy_ceiling", "flag": "acknowledge-autonomy-ceiling", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Install the governed Forge Developer Team template with seats, draft Charters, skills, project authority grants, and audit records. Tenant-admin and human-gated; creates no live agent occupancy." },
49085
49104
  { "id": "software_factory.gate.decide", "namespace": "software_factory", "action": "gate.decide", "title": "Decide a Forge gate", "description": "A human approves or rejects a Forge gate. The resolver is recorded (never an agent \u2014 invariant #7); sensitive gates (security sign-off, merge/deploy, authority change, production deploy) also write a linked human decision. Requires confirmation.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "gate_id", "flag": "gate-id", "type": "string", "required": true }, { "name": "decision", "flag": "decision", "type": "enum", "required": true, "enumValues": ["approve", "reject"] }, { "name": "rationale", "flag": "rationale", "type": "string", "required": false }], "hasComplexInput": false, "help": "A human approves or rejects a Forge gate; an agent caller routes to /approvals and can never self-approve. Sensitive gates record a human decision." },
49086
49105
  { "id": "software_factory.github.installation_token.create", "namespace": "software_factory", "action": "github.installation_token.create", "title": "Create a Forge GitHub installation token", "description": "Mint a short-lived GitHub App installation token for one bound repository after server-side policy evaluation. The token is returned once and never persisted.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "repository_id", "flag": "repository-id", "type": "integer", "required": true }, { "name": "requested_actions", "flag": "requested-actions", "type": "array", "required": true, "itemType": "string" }, { "name": "requested_automation_mode", "flag": "requested-automation-mode", "type": "enum", "required": false, "enumValues": ["plan_only", "pr_only", "merge_after_checks", "deploy_preview", "deploy_production"] }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": false }], "hasComplexInput": false, "help": "Mint a short-lived GitHub App installation token for one bound repository after policy evaluation; token material is returned once and never stored." },
49087
49106
  { "id": "software_factory.github.installation.bind", "namespace": "software_factory", "action": "github.installation.bind", "title": "Bind Forge GitHub repositories to projects", "description": "Bind already-connected GitHub repositories to Forge projects. Owner-only and human-gated; operates on repositories already verified and connected in this tenant, so no GitHub re-authorization is required. Available any time, add or re-map bindings.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": false, "fields": [{ "name": "app_slug", "flag": "app-slug", "type": "string", "required": false }, { "name": "installation_id", "flag": "installation-id", "type": "integer", "required": true }], "hasComplexInput": true, "help": "Bind already-connected GitHub repositories to Forge projects from the Forge GitHub settings flow. Owner-only and human-gated; no GitHub re-authorization required." },
@@ -51540,7 +51559,7 @@ function pendingConfirmationFromError(error51) {
51540
51559
 
51541
51560
  // src/doctor.ts
51542
51561
  import { execFile as execFileCallback2 } from "node:child_process";
51543
- import { access, readFile as readFile3 } from "node:fs/promises";
51562
+ import { access } from "node:fs/promises";
51544
51563
  import { delimiter } from "node:path";
51545
51564
  import { promisify as promisify2 } from "node:util";
51546
51565
  var execFile2 = promisify2(execFileCallback2);
@@ -51551,7 +51570,7 @@ async function runDoctor(options) {
51551
51570
  const config2 = loadConfig(env);
51552
51571
  lines.push(`app_url: ${config2.appUrl}`);
51553
51572
  lines.push(`supabase_url: ${config2.supabaseUrl}`);
51554
- const currentVersion = await readCurrentVersion2();
51573
+ const currentVersion = await readCliVersion();
51555
51574
  lines.push(`version: ${currentVersion ?? "unknown"}`);
51556
51575
  const pathStatus = await checkPath(env);
51557
51576
  lines.push(pathStatus);
@@ -51598,15 +51617,6 @@ async function probeCli(command) {
51598
51617
  return "not found";
51599
51618
  }
51600
51619
  }
51601
- async function readCurrentVersion2() {
51602
- try {
51603
- const url2 = new URL("../package.json", import.meta.url);
51604
- const parsed = JSON.parse(await readFile3(url2, "utf8"));
51605
- return typeof parsed.version === "string" ? parsed.version : null;
51606
- } catch {
51607
- return null;
51608
- }
51609
- }
51610
51620
  async function fetchLatestVersion2(packageName, env) {
51611
51621
  if (env.CI || env.VITEST || env.CLI_SKIP_UPDATE_CHECK === "1" || env[`${cliBrand.envPrefix}_CLI_NO_UPDATE_CHECK`] === "1") {
51612
51622
  return null;
@@ -51662,7 +51672,7 @@ function parseSemver2(value) {
51662
51672
  // src/runner-serve.ts
51663
51673
  import { execFile as execFileCallback3, spawn } from "node:child_process";
51664
51674
  import { createHash } from "node:crypto";
51665
- import { chmod, mkdir as mkdir2, readFile as readFile4, rename, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
51675
+ import { chmod, mkdir as mkdir2, readFile as readFile3, rename, rm as rm3, statfs, writeFile as writeFile2 } from "node:fs/promises";
51666
51676
  import { cpus, homedir as homedir2, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
51667
51677
  import path4 from "node:path";
51668
51678
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -51917,6 +51927,8 @@ var RUNNER_AICOS_TURN_TOOLS = [
51917
51927
  async function runRunnerServe(options) {
51918
51928
  const env = options.env ?? process.env;
51919
51929
  const fetchImpl = options.fetchImpl ?? fetch;
51930
+ const cliVersion = await readCliVersion();
51931
+ const homeDir = options.homeDir ?? homedir2();
51920
51932
  let config2;
51921
51933
  try {
51922
51934
  config2 = parseServeArgs(options.args, options.appUrl, env);
@@ -51955,13 +51967,13 @@ ${usage()}
51955
51967
  let failed = false;
51956
51968
  let activeSessions = 0;
51957
51969
  do {
51958
- const detectedCapabilities = await detectCapabilities();
51970
+ const detectedCapabilities = await detectCapabilities(cliVersion, env, homeDir);
51959
51971
  const capabilities = capabilitiesForConfiguredRuntime(detectedCapabilities, config2.runtime);
51960
51972
  const localRuntime = config2.execute ? effectiveExecutionRuntime(config2.runtime, capabilities) : null;
51961
51973
  try {
51962
51974
  const heartbeat = await post2(fetchImpl, options.appUrl, "/api/runner/heartbeat", {
51963
51975
  capabilities,
51964
- telemetry: detectTelemetry(capabilities, env, activeSessions)
51976
+ telemetry: detectTelemetry(capabilities, env, activeSessions, cliVersion)
51965
51977
  }, state.runner_secret);
51966
51978
  if (heartbeat.status !== 200) {
51967
51979
  options.io.stderr.write(`heartbeat ${heartbeat.status}: ${redactForLog(JSON.stringify(heartbeat.json))}
@@ -52008,17 +52020,28 @@ async function waitForNextHeartbeat(fetchImpl, appUrl2, state, config2, io, runt
52008
52020
  }
52009
52021
  }
52010
52022
  }
52011
- function detectTelemetry(capabilities, env, activeSessions) {
52012
- const accountAlias = (env.RUNNER_MODEL_ACCOUNT_ALIAS ?? env.ROST_RUNNER_MODEL_ACCOUNT_ALIAS ?? "").trim() || "default";
52023
+ function resolveAccountAlias(env) {
52024
+ return (env.RUNNER_MODEL_ACCOUNT_ALIAS ?? env.ROST_RUNNER_MODEL_ACCOUNT_ALIAS ?? "").trim() || "default";
52025
+ }
52026
+ function capabilitiesForAnalytics(capabilities) {
52027
+ return {
52028
+ ...capabilities,
52029
+ claude: { ...capabilities.claude, account_alias: null },
52030
+ codex: { ...capabilities.codex, account_alias: null }
52031
+ };
52032
+ }
52033
+ function detectTelemetry(capabilities, env, activeSessions, cliVersion) {
52013
52034
  const usageState = env.RUNNER_MODEL_ACCOUNT_STATE ?? env.ROST_RUNNER_MODEL_ACCOUNT_STATE ?? "unknown";
52014
52035
  return {
52015
52036
  os: platform(),
52016
52037
  cpu_count: cpus().length,
52017
52038
  memory_mb: Math.round(totalmem() / (1024 * 1024)),
52018
- runner_version: env.npm_package_version ?? null,
52039
+ // DER-1319: sourced from the bundled package.json, not env.npm_package_version (undefined
52040
+ // under launchd — prod runners were silently reporting a null version).
52041
+ runner_version: cliVersion,
52019
52042
  active_sessions: activeSessions,
52020
- installed_runtimes: capabilities,
52021
- account_alias: accountAlias,
52043
+ installed_runtimes: capabilitiesForAnalytics(capabilities),
52044
+ account_alias: resolveAccountAlias(env),
52022
52045
  usage_limit_observation: {
52023
52046
  state: usageState,
52024
52047
  source: "runner_env_or_unknown"
@@ -52180,7 +52203,7 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
52180
52203
  }
52181
52204
  async function loadState(filePath, expectedTenantId) {
52182
52205
  try {
52183
- const parsed = JSON.parse(await readFile4(filePath, "utf8"));
52206
+ const parsed = JSON.parse(await readFile3(filePath, "utf8"));
52184
52207
  if (typeof parsed.runner_id === "string" && typeof parsed.runner_secret === "string") {
52185
52208
  if (expectedTenantId && parsed.tenant_id !== expectedTenantId) {
52186
52209
  return null;
@@ -52231,9 +52254,25 @@ async function startPairing(client, config2) {
52231
52254
  }
52232
52255
  return userCode;
52233
52256
  }
52234
- async function detectCapabilities() {
52235
- const [claude, codex] = await Promise.all([probe("claude"), probe("codex")]);
52236
- return { claude, codex, turn_executions: { report_supported: true } };
52257
+ async function detectCapabilities(cliVersion, env, homeDir) {
52258
+ const [claude, codex, git, gh, forgeDiskFreeMb] = await Promise.all([
52259
+ probe("claude"),
52260
+ probe("codex"),
52261
+ probeGit(),
52262
+ probeGh(),
52263
+ probeForgeDiskFreeMb(homeDir)
52264
+ ]);
52265
+ const accountAlias = resolveAccountAlias(env);
52266
+ return {
52267
+ claude: { ...claude, account_alias: accountAlias, rate_limited_until: null },
52268
+ codex: { ...codex, account_alias: accountAlias, rate_limited_until: null },
52269
+ turn_executions: { report_supported: true },
52270
+ ...cliVersion ? { forge_build: cliVersion } : {},
52271
+ git,
52272
+ gh,
52273
+ scheduler: { max_parallel_sessions_estimate: null },
52274
+ host: { forge_disk_free_mb: forgeDiskFreeMb }
52275
+ };
52237
52276
  }
52238
52277
  async function probe(command) {
52239
52278
  try {
@@ -52243,6 +52282,43 @@ async function probe(command) {
52243
52282
  return { installed: false, version: null, auth_state: "unknown" };
52244
52283
  }
52245
52284
  }
52285
+ async function probeGit() {
52286
+ try {
52287
+ const { stdout, stderr } = await execFile3("git", ["--version"], { timeout: 1500 });
52288
+ const raw = (stdout || stderr).trim();
52289
+ return { installed: true, version: /(\d+\.\d+(?:\.\d+)?)/.exec(raw)?.[1] ?? (raw || null) };
52290
+ } catch {
52291
+ return { installed: false, version: null };
52292
+ }
52293
+ }
52294
+ async function probeGh() {
52295
+ try {
52296
+ const { stdout, stderr } = await execFile3("gh", ["--version"], { timeout: 1500 });
52297
+ const raw = (stdout || stderr).trim().split("\n")[0]?.trim() ?? "";
52298
+ const authenticated = await probeGhAuth();
52299
+ return { installed: true, version: /(\d+\.\d+\.\d+)/.exec(raw)?.[1] ?? (raw || null), authenticated };
52300
+ } catch {
52301
+ return { installed: false, version: null, authenticated: false };
52302
+ }
52303
+ }
52304
+ async function probeGhAuth() {
52305
+ try {
52306
+ await execFile3("gh", ["auth", "status"], { timeout: 1500 });
52307
+ return true;
52308
+ } catch {
52309
+ return false;
52310
+ }
52311
+ }
52312
+ async function probeForgeDiskFreeMb(homeDir) {
52313
+ const dir = path4.join(homeDir, ".rost", "forge");
52314
+ try {
52315
+ await mkdir2(dir, { recursive: true, mode: 448 });
52316
+ const stats = await statfs(dir);
52317
+ return Math.floor(stats.bavail * stats.bsize / (1024 * 1024));
52318
+ } catch {
52319
+ return null;
52320
+ }
52321
+ }
52246
52322
  async function post2(fetchImpl, appUrl2, pathName, body, token) {
52247
52323
  const headers = { "content-type": "application/json" };
52248
52324
  if (token) {
@@ -52568,7 +52644,7 @@ function usage() {
52568
52644
  // src/runner-service.ts
52569
52645
  import { execFile as execFileCallback4 } from "node:child_process";
52570
52646
  import { realpathSync as realpathSync2 } from "node:fs";
52571
- import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
52647
+ import { access as access2, mkdir as mkdir3, readFile as readFile4, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
52572
52648
  import { homedir as homedir3 } from "node:os";
52573
52649
  import path5 from "node:path";
52574
52650
  import { promisify as promisify4 } from "node:util";
@@ -53121,7 +53197,7 @@ async function fileExists(file2) {
53121
53197
  }
53122
53198
  async function readTail(file2, maxBytes) {
53123
53199
  try {
53124
- const content = await readFile5(file2, "utf8");
53200
+ const content = await readFile4(file2, "utf8");
53125
53201
  return content.length > maxBytes ? content.slice(content.length - maxBytes) : content;
53126
53202
  } catch {
53127
53203
  return null;