@cotal-ai/cli 0.153.1 → 0.154.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.
@@ -2,7 +2,7 @@ import { accessSync, constants, existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { CotalEndpoint, agentFilePath, dialerFor, EpEnvelopeError, formatAge, isReachable, mintCreds, mintLifecycleUid, resolveService, realDirNoSymlink, invokeRepairingSplit, standaloneConnectOpts, newIdentity, idFromCreds, principalKey, DEV_OWNER, unansweredRequest, resolveAuthProvider, personaDir, presenceText, } from "@cotal-ai/core";
5
- import { stoppedLoopbackListener, accountInventory, assessMaintenanceClaim, authDir, isCommandFailure, readMaintenanceJournal, recordsElsewhereError, recordsElsewhereText, restoreRolledBackRuns, targetErrorSpace, brokerAddressForDisplay, canonicalRoot, CLI_USER_ACTOR, codeSpan, CommandFailure, deliveryCredsKey, DELIVERY_PIDFILE, extensionsDir, findCotalRoot, folderSpaceOverSelection, meshesForRoot, resolveReadsLiveness, stoppedStack, unsettledStackOperation, withdrawOperationNote, hasUserAuthState, homeCotalDir, cachedPreferences, loadLaunchPersonaFile, resolvePreference, isMeshRegistryError, isWorkspaceTargetError, jsonFlag, loadManagerInstanceIdentity, loadSoleSpaceAuth, localProcessPath, localProcessVisible, MANAGER_PIDFILE, oneLine, PREFERENCE_KEYS, parsePid, plainFlag, preflightTarget, presentationOf, principalFromBearer, probeLiveness, probeWebReadiness, readCurrent, readMeshRegistry, readPidfile, preparedRecordSkipped, readPreparedSpaces, readRenewalRecord, renderWorkspaceError, resolveMeshTarget, serverFlag, spaceFlag, userAuthStateDir, WEB_PIDFILE, WEB_READINESS_HEADER, workspaceSecretStore, writeResult, workspaceErrorParts, preferencesPath, preferencesFolderWarning, cotalDirLink, movedProject, spaceStreamsAbsent, spaceStreamsExpected, resolveControlTarget, } from "@cotal-ai/workspace";
5
+ import { stoppedLoopbackListener, accountInventory, assessMaintenanceClaim, authDir, isCommandFailure, readMaintenanceJournal, recordsElsewhereError, recordsElsewhereText, restoreRolledBackRuns, targetErrorSpace, brokerAddressForDisplay, brokerCheck, canonicalRoot, CLI_USER_ACTOR, codeSpan, CommandFailure, deliveryCredsKey, DELIVERY_PIDFILE, extensionsDir, findCotalRoot, folderSpaceOverSelection, meshesForRoot, resolveReadsLiveness, stoppedStack, unsettledStackOperation, withdrawOperationNote, hasUserAuthState, homeCotalDir, cachedPreferences, loadLaunchPersonaFile, resolvePreference, isMeshRegistryError, isWorkspaceTargetError, jsonFlag, loadManagerInstanceIdentity, loadSoleSpaceAuth, localProcessPath, localProcessVisible, MANAGER_PIDFILE, oneLine, PREFERENCE_KEYS, parsePid, plainFlag, preflightTarget, presentationOf, principalFromBearer, probeLiveness, probeWebReadiness, readCurrent, readMeshRegistry, readPidfile, preparedRecordSkipped, readPreparedSpaces, readRenewalRecord, renderWorkspaceError, resolveMeshTarget, serverFlag, spaceFlag, userAuthStateDir, WEB_PIDFILE, WEB_READINESS_HEADER, workspaceSecretStore, writeResult, workspaceErrorParts, preferencesPath, preferencesFolderWarning, cotalDirLink, movedProject, spaceStreamsAbsent, spaceStreamsExpected, resolveControlTarget, } from "@cotal-ai/workspace";
6
6
  import { localProcessSurface } from "../ext-loader.js";
7
7
  import { cliVersion, cliProvenance, readExtensionVersions } from "../lib/version.js";
8
8
  import { agentSkillsSkew } from "../lib/agent-skills.js";
@@ -162,10 +162,11 @@ export async function status(args) {
162
162
  // THE RESPONDER AXIS (#1576), read ONCE and threaded into the rows that would otherwise claim
163
163
  // health they never checked. It is read before the folder section because that section renders
164
164
  // the delivery process row, and a live pid means nothing without it.
165
- const responder = await readResponderAxis(selected);
165
+ const preflight = selected.ok && selected.target.mode !== "user" ? await preflightTarget(selected.target) : undefined;
166
+ const responder = await readResponderAxis(selected, preflight);
166
167
  const folder = blocked ? unreadFolder(root, blocked) : await readFolder(root, selected, values, responder.state, gaps, hints);
167
- const meshes = await readMeshes(cwd, selected, Boolean(values.all), gaps, offline);
168
- const target = await readSelected(selected, responder, folder, gaps, hints);
168
+ const meshes = await readMeshes(cwd, selected, Boolean(values.all), gaps, offline, preflight);
169
+ const target = await readSelected(selected, responder, folder, gaps, hints, preflight);
169
170
  // The note under the heading sends here for this folder's own stack cut off with no recorded
170
171
  // outcome under another selection. A blocked one is named with its block under `This folder's
171
172
  // space` and its step first in Next, so that note is left out here, with its --json warning. One
@@ -476,7 +477,7 @@ async function expectedDeliveryHolder(target) {
476
477
  *
477
478
  * `leased` says whether the read found a delivery lease record. An open mesh runs no delivery
478
479
  * daemon, so there a missing lease is the normal state rather than an unbound responder. */
479
- async function readResponderAxis(selected) {
480
+ async function readResponderAxis(selected, preflight) {
480
481
  let leased = false;
481
482
  if (!selected.ok)
482
483
  return { state: "unknown", leased };
@@ -484,8 +485,7 @@ async function readResponderAxis(selected) {
484
485
  if (target.mode === "user")
485
486
  return { state: "unknown", leased };
486
487
  try {
487
- const preflight = await preflightTarget(target);
488
- if (!preflight.ok)
488
+ if (!preflight?.ok)
489
489
  return { state: "unknown", leased };
490
490
  const id = newIdentity();
491
491
  const creds = target.auth ? await mintCreds(target.auth, id, "observer") : undefined;
@@ -929,7 +929,7 @@ function deliveryDetail(live, responder) {
929
929
  * with its own recorded TLS requirement. A bare TCP/INFO probe would green-light a plaintext broker
930
930
  * that has substituted for a TLS-required mesh — the FAIL1 attack — so a list that only asks "is
931
931
  * anything listening" cannot report on the one property the record claims. */
932
- async function readMeshes(cwd, selected, all, gaps, offline) {
932
+ async function readMeshes(cwd, selected, all, gaps, offline, preflight) {
933
933
  // A registry file that cannot be read is named, and the readable records are still listed and
934
934
  // checked. Only an absent directory or pointer is an empty registry or no current mesh.
935
935
  const unreadable = [];
@@ -971,6 +971,18 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
971
971
  const chosen = selected.ok ? selected.target : undefined;
972
972
  // A `--server` that names another address is the only broker dialed for the selected space.
973
973
  const checked = (m) => all || (m.space === chosen?.space && m.server === chosen.server);
974
+ const check = async (m) => {
975
+ if (!checked(m))
976
+ return "not-checked";
977
+ if (preflight && m.space === chosen?.space && m.server === chosen.server) {
978
+ if (preflight.ok)
979
+ return "reachable";
980
+ if (preflight.kind === "slow-link" || preflight.kind === "broker-stopped" || preflight.kind === "unreachable")
981
+ return preflight.kind;
982
+ return preflight.kind === "tls-trust" || preflight.kind === "tls-unsupported" ? "unreachable" : "reachable";
983
+ }
984
+ return brokerCheck(m.server, m.tlsRequired);
985
+ };
974
986
  const items = await Promise.all(entries.map(async (m) => ({
975
987
  space: m.space,
976
988
  server: displayStatusBroker(m.server, m.space, gaps),
@@ -981,7 +993,7 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
981
993
  eventsRequired: m.policy?.events === "required",
982
994
  modeChecked: m.unchecked !== true,
983
995
  current: m.space === current,
984
- check: checked(m) ? ((await isReachable(m.server, m.tlsRequired ? { tls: true } : {})) ? "reachable" : "unreachable") : "not-checked",
996
+ check: await check(m),
985
997
  catalog: m.origin === "catalog" ? { name: m.catalogName && m.catalogName !== m.space ? m.catalogName : null, fetchedAt: m.catalogFetchedAt ?? null, error: m.catalogError ?? null } : null,
986
998
  })));
987
999
  // The prepared spaces folder, or a record in it, that cannot be read is named the same way.
@@ -1053,7 +1065,7 @@ function rootStoreProblem(root) {
1053
1065
  return undefined;
1054
1066
  }
1055
1067
  }
1056
- async function readSelected(selected, responder, folder, gaps, hints) {
1068
+ async function readSelected(selected, responder, folder, gaps, hints, preflight) {
1057
1069
  const cmd = displayCmd();
1058
1070
  if (!selected.ok) {
1059
1071
  const e = selected.error;
@@ -1184,7 +1196,8 @@ async function readSelected(selected, responder, folder, gaps, hints) {
1184
1196
  hints.push(moved.remedy);
1185
1197
  return { resolved: false, code: "moved-folder", message: moved.message };
1186
1198
  }
1187
- const preflight = await preflightTarget(target);
1199
+ if (preflight === undefined)
1200
+ throw new Error("status: selected non-user target has no preflight observation");
1188
1201
  if (!preflight.ok) {
1189
1202
  // A `--server` address that is not the one recorded for the space says nothing about whether its
1190
1203
  // stack runs, so Next names that address as the refusal of other commands does. A stopped stack
@@ -1409,7 +1422,7 @@ function folderSection(f, selected) {
1409
1422
  return { title: "This Folder", blocks: [{ kind: "fields", rows }] };
1410
1423
  }
1411
1424
  function meshesSections(m) {
1412
- const check = { reachable: cell("reachable", "ok"), unreachable: cell("down", "failed"), "not-checked": cell("not checked", "hint") };
1425
+ const check = { reachable: cell("reachable", "ok"), unreachable: cell("down", "failed"), "slow-link": cell("not answering", "failed"), "broker-stopped": cell("broker process is stopped", "failed"), "not-checked": cell("not checked", "hint") };
1413
1426
  const blocks = [];
1414
1427
  if (!m.items.length && !m.unreadable.length)
1415
1428
  blocks.push({ kind: "lines", lines: [cell("none", "hint")] });
@@ -1618,7 +1631,7 @@ function connectionCell(s) {
1618
1631
  return stateCell("reachable", "ok");
1619
1632
  const states = {
1620
1633
  unreachable: "unreachable",
1621
- "slow-link": "connection timed out",
1634
+ "slow-link": "not answering",
1622
1635
  "broker-stopped": `broker process ${s.brokerPid} is stopped`,
1623
1636
  "tls-trust": "TLS could not be verified",
1624
1637
  "tls-unsupported": "listener does not offer required TLS",
@@ -1655,7 +1668,7 @@ function spaceStateCell(r, entry) {
1655
1668
  return connectionCell(r.selected);
1656
1669
  if (entry.check === "not-checked")
1657
1670
  return "";
1658
- return stateCell(entry.check === "reachable" ? "reachable" : "unreachable", entry.check === "reachable" ? "ok" : "failed");
1671
+ return stateCell(entry.check === "slow-link" ? "not answering" : entry.check === "broker-stopped" ? "broker process is stopped" : entry.check, entry.check === "reachable" ? "ok" : "failed");
1659
1672
  }
1660
1673
  /**
1661
1674
  * The spaces this machine knows: the selected one first with its connection state, then one line