@tiangong-ai/cli 0.0.45 → 0.0.47

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.
@@ -10,6 +10,7 @@ import { inspectResearchContext } from "./context.js";
10
10
  import { inspectCapabilityCredentialEnvironment, loadCapabilityCredentialMapForIds, reconcileCapabilityCredentialEnvironment, setCapabilityCredentialValue, } from "./credentials.js";
11
11
  import { configureExternalSkillProfile, configureTiangongDatabaseCapability, doctorExternalCapabilities, EXTERNAL_SKILL_CONTEXT_PROFILE, EXTERNAL_SKILL_MEDIA_PROFILE, EXTERNAL_SKILL_PROFILE, reconcileSetupManagedCapabilities, } from "./external-skills.js";
12
12
  import { appendJournalEvent } from "./journal.js";
13
+ import { researchPlatformCapabilities } from "./platform-capabilities.js";
13
14
  import { validateResearchPolicyPack } from "./research-policy.js";
14
15
  import { configuredResearchSecrets, isSensitiveEnvironmentName, sanitizeResearchRecord, sanitizeResearchText, } from "./sanitization.js";
15
16
  import { inspectResearchSetupCatalog, RESEARCH_SETUP_CREDENTIALS, RESEARCH_SETUP_INSTALLER, RESEARCH_SETUP_SETTINGS, RESEARCH_SETUP_SKILLS, resolveSetupSkills, setupSkill, setupSource, setupTargetRoot, verifyResearchSetupRuntimeContract, } from "./setup-catalog.js";
@@ -1245,13 +1246,8 @@ export async function doctorResearchSetup(workspace, options = {}) {
1245
1246
  return `${command} is executable (${sanitizeResearchText(result.stdout.trim()).slice(0, 120)}).`;
1246
1247
  });
1247
1248
  }
1248
- if (plan.reviewerExecution.transport === "sandbox-bridge") {
1249
- await setupDoctorCheck(checks, "platform-sandbox", "runtime", async () => {
1250
- const status = await inspectReviewerBridgeStatus(root);
1251
- return `Owner-controlled reviewer sidecar is ready; version=${status.packageVersion}; key=${status.keyFingerprint.slice(0, 12)}.`;
1252
- });
1253
- }
1254
- else if (platform() === "win32") {
1249
+ const platformCapabilities = researchPlatformCapabilities(platform());
1250
+ if (!platformCapabilities.nativeReviewerExecution) {
1255
1251
  const production = plan.workspace.mode === "production-research";
1256
1252
  checks.push({
1257
1253
  id: "platform-sandbox",
@@ -1259,24 +1255,30 @@ export async function doctorResearchSetup(workspace, options = {}) {
1259
1255
  scope: "research-core",
1260
1256
  status: production ? "fail" : "warn",
1261
1257
  detail: production
1262
- ? "Production research execution is unsupported on Windows because no approved capsule sandbox is available."
1263
- : "Windows can inspect and smoke-test setup state, but native research execution requires an approved macOS or Linux capsule sandbox.",
1258
+ ? `Production research execution is unsupported on ${platformCapabilities.platform} because no approved capsule sandbox is available.`
1259
+ : `${platformCapabilities.platform} is configuration/smoke-only; it can inspect setup state but cannot start a native reviewer or reviewer sidecar.`,
1264
1260
  minimumAction: production
1265
1261
  ? "Run production research on macOS with sandbox-exec or Linux with Bubblewrap."
1266
- : "Use macOS or Linux before switching this workspace to production-research mode.",
1262
+ : "Use macOS or Linux before switching this workspace to production-research mode or starting a reviewer sidecar.",
1267
1263
  blocking: production,
1268
1264
  requiredFor: production ? ["setup", "research-core"] : [],
1269
1265
  });
1270
1266
  }
1267
+ else if (plan.reviewerExecution.transport === "sandbox-bridge") {
1268
+ await setupDoctorCheck(checks, "platform-sandbox", "runtime", async () => {
1269
+ const status = await inspectReviewerBridgeStatus(root);
1270
+ return `Owner-controlled reviewer sidecar is ready; version=${status.packageVersion}; key=${status.keyFingerprint.slice(0, 12)}.`;
1271
+ });
1272
+ }
1271
1273
  else {
1272
1274
  await setupDoctorCheck(checks, "platform-sandbox", "runtime", async () => {
1273
- if (platform() === "darwin") {
1275
+ if (platformCapabilities.nativeIsolationProvider === "sandbox-exec") {
1274
1276
  const info = await lstat("/usr/bin/sandbox-exec").catch(() => undefined);
1275
1277
  if (!info?.isFile())
1276
1278
  throw new Error("/usr/bin/sandbox-exec is unavailable");
1277
1279
  return "macOS sandbox-exec is available.";
1278
1280
  }
1279
- if (platform() === "linux") {
1281
+ if (platformCapabilities.nativeIsolationProvider === "bubblewrap") {
1280
1282
  const result = await runner({
1281
1283
  command: "bwrap",
1282
1284
  args: ["--version"],
@@ -1288,7 +1290,7 @@ export async function doctorResearchSetup(workspace, options = {}) {
1288
1290
  throw new Error("Bubblewrap is unavailable");
1289
1291
  return "Linux Bubblewrap is available.";
1290
1292
  }
1291
- throw new Error("Research execution is unsupported on this platform");
1293
+ throw new Error(`Research execution is unsupported on ${platformCapabilities.platform}`);
1292
1294
  });
1293
1295
  }
1294
1296
  checks.push({
@@ -1700,47 +1702,48 @@ export async function checkResearchSetupUpdates(workspace, environment = process
1700
1702
  export async function clearStaleSetupLock(workspace) {
1701
1703
  const root = requireAbsoluteWorkspace(resolve(workspace));
1702
1704
  const lockPath = workspacePaths(root).setupLock;
1703
- const info = await lstat(lockPath).catch(() => undefined);
1704
- if (!info)
1705
- return;
1706
- if (!info.isFile() || info.isSymbolicLink()) {
1707
- throw setupError({
1708
- code: "RESEARCH_SETUP_LOCK_INVALID",
1709
- step: "retry",
1710
- reason: "Setup lock is not a regular file.",
1711
- minimumAction: "Inspect the lock path manually; it will not be removed automatically.",
1712
- retryCommand: `tiangong-ai research setup status --workspace ${root} --json`,
1713
- exitCode: 3,
1714
- });
1715
- }
1716
- let payload;
1705
+ let release;
1717
1706
  try {
1718
- payload = JSON.parse(await readFile(lockPath, "utf8"));
1719
- }
1720
- catch {
1721
- payload = null;
1707
+ release = await acquireFileLock(lockPath, {
1708
+ operation: "research.setup.lock-recovery",
1709
+ acquiredAt: new Date().toISOString(),
1710
+ });
1722
1711
  }
1723
- if (isObject(payload) && payload.hostname === hostname() && typeof payload.pid === "number") {
1724
- try {
1725
- process.kill(payload.pid, 0);
1726
- throw setupError({
1727
- code: "RESEARCH_SETUP_LOCK_ACTIVE",
1728
- step: "retry",
1729
- reason: `Setup lock belongs to live process ${payload.pid}.`,
1730
- minimumAction: "Wait for the active setup process to finish; do not clear its lock.",
1731
- retryCommand: `tiangong-ai research setup status --workspace ${root} --json`,
1712
+ catch (error) {
1713
+ if (error instanceof CliError && error.code === "RESEARCH_WORKSPACE_LOCKED") {
1714
+ const details = isObject(error.details) ? error.details : {};
1715
+ if (details.ownerState === "alive") {
1716
+ throw new CliError("Setup is already running in another live process.", {
1717
+ code: "RESEARCH_SETUP_LOCK_ACTIVE",
1718
+ exitCode: 3,
1719
+ details: {
1720
+ ownerState: "alive",
1721
+ operation: typeof details.operation === "string" ? details.operation : null,
1722
+ acquiredAt: typeof details.acquiredAt === "string" ? details.acquiredAt : null,
1723
+ minimumAction: "Wait for the active setup operation to finish, then retry; do not delete lock state manually.",
1724
+ },
1725
+ });
1726
+ }
1727
+ throw new CliError("Setup lock ownership cannot yet be verified safely.", {
1728
+ code: "RESEARCH_SETUP_LOCK_UNVERIFIABLE",
1732
1729
  exitCode: 3,
1730
+ details: {
1731
+ ownerState: "unknown",
1732
+ retryAfterSeconds: typeof details.retryAfterSeconds === "number" ? details.retryAfterSeconds : null,
1733
+ minimumAction: "Wait for the reported lease interval and retry; do not delete lock state manually.",
1734
+ },
1733
1735
  });
1734
1736
  }
1735
- catch (error) {
1736
- if (error instanceof CliError)
1737
- throw error;
1738
- const code = error.code;
1739
- if (code !== "ESRCH")
1740
- throw error;
1741
- }
1737
+ throw new CliError("Setup lock state is unreadable or unsafe.", {
1738
+ code: "RESEARCH_SETUP_LOCK_INVALID",
1739
+ exitCode: 3,
1740
+ details: {
1741
+ ownerState: "unknown",
1742
+ minimumAction: "Inspect workspace filesystem permissions and lock-state integrity; do not delete lock state manually.",
1743
+ },
1744
+ });
1742
1745
  }
1743
- await rm(lockPath);
1746
+ await release();
1744
1747
  }
1745
1748
  function parseResearchSetupPlan(value) {
1746
1749
  if (!isObject(value) ||