@cotal-ai/cli 0.152.0 → 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.
Files changed (55) hide show
  1. package/dist/chat/chat-view.js +2 -2
  2. package/dist/chat/chat-view.js.map +1 -1
  3. package/dist/chat/controller.d.ts +4 -3
  4. package/dist/chat/controller.d.ts.map +1 -1
  5. package/dist/chat/controller.js +13 -12
  6. package/dist/chat/controller.js.map +1 -1
  7. package/dist/chat/plain.js +1 -1
  8. package/dist/chat/plain.js.map +1 -1
  9. package/dist/chat/view.d.ts.map +1 -1
  10. package/dist/chat/view.js +6 -0
  11. package/dist/chat/view.js.map +1 -1
  12. package/dist/command.d.ts.map +1 -1
  13. package/dist/command.js +9 -8
  14. package/dist/command.js.map +1 -1
  15. package/dist/commands/down-live-lease.d.ts.map +1 -1
  16. package/dist/commands/down-live-lease.js +19 -6
  17. package/dist/commands/down-live-lease.js.map +1 -1
  18. package/dist/commands/join.d.ts.map +1 -1
  19. package/dist/commands/join.js +26 -2
  20. package/dist/commands/join.js.map +1 -1
  21. package/dist/commands/mint.js +1 -1
  22. package/dist/commands/mint.js.map +1 -1
  23. package/dist/commands/reset-space-apply.d.ts.map +1 -1
  24. package/dist/commands/reset-space-apply.js +5 -2
  25. package/dist/commands/reset-space-apply.js.map +1 -1
  26. package/dist/commands/service-registry.d.ts.map +1 -1
  27. package/dist/commands/service-registry.js +7 -5
  28. package/dist/commands/service-registry.js.map +1 -1
  29. package/dist/commands/space.d.ts +3 -3
  30. package/dist/commands/space.d.ts.map +1 -1
  31. package/dist/commands/space.js +11 -8
  32. package/dist/commands/space.js.map +1 -1
  33. package/dist/commands/spawn.d.ts.map +1 -1
  34. package/dist/commands/spawn.js +11 -1
  35. package/dist/commands/spawn.js.map +1 -1
  36. package/dist/commands/stack-up.d.ts +2 -2
  37. package/dist/commands/stack-up.d.ts.map +1 -1
  38. package/dist/commands/stack-up.js +14 -6
  39. package/dist/commands/stack-up.js.map +1 -1
  40. package/dist/commands/status.d.ts +3 -2
  41. package/dist/commands/status.d.ts.map +1 -1
  42. package/dist/commands/status.js +49 -20
  43. package/dist/commands/status.js.map +1 -1
  44. package/dist/commands/up.d.ts.map +1 -1
  45. package/dist/commands/up.js +18 -7
  46. package/dist/commands/up.js.map +1 -1
  47. package/dist/commands/update.d.ts +1 -0
  48. package/dist/commands/update.d.ts.map +1 -1
  49. package/dist/commands/update.js +3 -3
  50. package/dist/commands/update.js.map +1 -1
  51. package/dist/lib/manifest/prepare.d.ts.map +1 -1
  52. package/dist/lib/manifest/prepare.js +4 -0
  53. package/dist/lib/manifest/prepare.js.map +1 -1
  54. package/dist/lib/team-agent-removal.d.ts +1 -1
  55. package/package.json +16 -4
@@ -1,8 +1,8 @@
1
1
  import { accessSync, constants, existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { CotalEndpoint, 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, 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";
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, 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
@@ -326,7 +327,23 @@ async function readMachine(selected, gaps, hints) {
326
327
  let defaults = null;
327
328
  try {
328
329
  const context = { preferences: cachedPreferences(), space: selected.ok ? selected.target.space : undefined };
329
- defaults = { harness: resolvePreference("harness", context).value, persona: resolvePreference("persona", context).value };
330
+ const persona = resolvePreference("persona", context);
331
+ defaults = { harness: resolvePreference("harness", context).value, persona: persona.value };
332
+ if (selected.ok) {
333
+ const path = agentFilePath(selected.target.root, persona.value);
334
+ try {
335
+ const definition = loadLaunchPersonaFile(path);
336
+ defaults.harness = resolvePreference("harness", { ...context, pin: definition.agent }).value;
337
+ }
338
+ catch (error) {
339
+ // A bare spawn writes the shipped, unpinned default only when the product default is absent.
340
+ // Every other failed read leaves the effective harness unknown, never the saved preference.
341
+ if (error.code !== "ENOENT" || persona.winner?.source !== "default") {
342
+ defaults.harness = null;
343
+ gaps.add("DEFAULT_PERSONA_UNREADABLE", "the default launch persona could not be read and validated; its effective harness is unknown", { path });
344
+ }
345
+ }
346
+ }
330
347
  }
331
348
  catch (e) {
332
349
  // The refusal names the file and its problem, and its remedy is the next step.
@@ -460,7 +477,7 @@ async function expectedDeliveryHolder(target) {
460
477
  *
461
478
  * `leased` says whether the read found a delivery lease record. An open mesh runs no delivery
462
479
  * daemon, so there a missing lease is the normal state rather than an unbound responder. */
463
- async function readResponderAxis(selected) {
480
+ async function readResponderAxis(selected, preflight) {
464
481
  let leased = false;
465
482
  if (!selected.ok)
466
483
  return { state: "unknown", leased };
@@ -468,8 +485,7 @@ async function readResponderAxis(selected) {
468
485
  if (target.mode === "user")
469
486
  return { state: "unknown", leased };
470
487
  try {
471
- const preflight = await preflightTarget(target);
472
- if (!preflight.ok)
488
+ if (!preflight?.ok)
473
489
  return { state: "unknown", leased };
474
490
  const id = newIdentity();
475
491
  const creds = target.auth ? await mintCreds(target.auth, id, "observer") : undefined;
@@ -913,7 +929,7 @@ function deliveryDetail(live, responder) {
913
929
  * with its own recorded TLS requirement. A bare TCP/INFO probe would green-light a plaintext broker
914
930
  * that has substituted for a TLS-required mesh — the FAIL1 attack — so a list that only asks "is
915
931
  * anything listening" cannot report on the one property the record claims. */
916
- async function readMeshes(cwd, selected, all, gaps, offline) {
932
+ async function readMeshes(cwd, selected, all, gaps, offline, preflight) {
917
933
  // A registry file that cannot be read is named, and the readable records are still listed and
918
934
  // checked. Only an absent directory or pointer is an empty registry or no current mesh.
919
935
  const unreadable = [];
@@ -955,6 +971,18 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
955
971
  const chosen = selected.ok ? selected.target : undefined;
956
972
  // A `--server` that names another address is the only broker dialed for the selected space.
957
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
+ };
958
986
  const items = await Promise.all(entries.map(async (m) => ({
959
987
  space: m.space,
960
988
  server: displayStatusBroker(m.server, m.space, gaps),
@@ -965,7 +993,7 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
965
993
  eventsRequired: m.policy?.events === "required",
966
994
  modeChecked: m.unchecked !== true,
967
995
  current: m.space === current,
968
- check: checked(m) ? ((await isReachable(m.server, m.tlsRequired ? { tls: true } : {})) ? "reachable" : "unreachable") : "not-checked",
996
+ check: await check(m),
969
997
  catalog: m.origin === "catalog" ? { name: m.catalogName && m.catalogName !== m.space ? m.catalogName : null, fetchedAt: m.catalogFetchedAt ?? null, error: m.catalogError ?? null } : null,
970
998
  })));
971
999
  // The prepared spaces folder, or a record in it, that cannot be read is named the same way.
@@ -1037,7 +1065,7 @@ function rootStoreProblem(root) {
1037
1065
  return undefined;
1038
1066
  }
1039
1067
  }
1040
- async function readSelected(selected, responder, folder, gaps, hints) {
1068
+ async function readSelected(selected, responder, folder, gaps, hints, preflight) {
1041
1069
  const cmd = displayCmd();
1042
1070
  if (!selected.ok) {
1043
1071
  const e = selected.error;
@@ -1168,7 +1196,8 @@ async function readSelected(selected, responder, folder, gaps, hints) {
1168
1196
  hints.push(moved.remedy);
1169
1197
  return { resolved: false, code: "moved-folder", message: moved.message };
1170
1198
  }
1171
- const preflight = await preflightTarget(target);
1199
+ if (preflight === undefined)
1200
+ throw new Error("status: selected non-user target has no preflight observation");
1172
1201
  if (!preflight.ok) {
1173
1202
  // A `--server` address that is not the one recorded for the space says nothing about whether its
1174
1203
  // stack runs, so Next names that address as the refusal of other commands does. A stopped stack
@@ -1393,7 +1422,7 @@ function folderSection(f, selected) {
1393
1422
  return { title: "This Folder", blocks: [{ kind: "fields", rows }] };
1394
1423
  }
1395
1424
  function meshesSections(m) {
1396
- 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") };
1397
1426
  const blocks = [];
1398
1427
  if (!m.items.length && !m.unreadable.length)
1399
1428
  blocks.push({ kind: "lines", lines: [cell("none", "hint")] });
@@ -1602,7 +1631,7 @@ function connectionCell(s) {
1602
1631
  return stateCell("reachable", "ok");
1603
1632
  const states = {
1604
1633
  unreachable: "unreachable",
1605
- "slow-link": "connection timed out",
1634
+ "slow-link": "not answering",
1606
1635
  "broker-stopped": `broker process ${s.brokerPid} is stopped`,
1607
1636
  "tls-trust": "TLS could not be verified",
1608
1637
  "tls-unsupported": "listener does not offer required TLS",
@@ -1639,7 +1668,7 @@ function spaceStateCell(r, entry) {
1639
1668
  return connectionCell(r.selected);
1640
1669
  if (entry.check === "not-checked")
1641
1670
  return "";
1642
- 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");
1643
1672
  }
1644
1673
  /**
1645
1674
  * The spaces this machine knows: the selected one first with its connection state, then one line
@@ -1727,7 +1756,7 @@ function selectedOverview(r) {
1727
1756
  ...(r.stackManager ? [["Manager", r.stackManager.endpoint === "answered" ? stateCell("answering", "ok") : r.stackManager.endpoint === "not-answering" ? stateCell(`not answering · stack-manager pid ${r.stackManager.pid}`, "failed") : stateCell("endpoint not checked", "hint")]] : []),
1728
1757
  ["Access", s.mode === "user" ? "user sign-in" : s.mode === "auth" ? "static auth" : "open"],
1729
1758
  ["Project", s.rootGone ? stateCell(`${s.root} · folder is gone`, "failed") : s.root],
1730
- ["Default agent", r.machine.defaults ? `${r.machine.defaults.harness ?? "application not set"} · persona ${r.machine.defaults.persona ?? "not set"}` : stateCell("saved defaults could not be read", "failed")],
1759
+ ["Default agent", r.machine.defaults ? `${r.machine.defaults.harness ?? "application unresolved"} · persona ${r.machine.defaults.persona ?? "not set"}` : stateCell("saved defaults could not be read", "failed")],
1731
1760
  ["Persona catalog", s.personas.default === "valid" ? "default available" : stateCell(s.personas.default === "invalid" ? "default is invalid" : s.personas.default === "unreadable" ? "could not be read" : "no default persona", s.personas.default === "invalid" || s.personas.default === "unreadable" ? "failed" : "hint")],
1732
1761
  ];
1733
1762
  if (s.tlsRequired)
@@ -2287,15 +2316,15 @@ async function managerHealth(target, context, credential) {
2287
2316
  }
2288
2317
  catch (e) {
2289
2318
  // A no-responder service rail or an absent manager registry is definitive no-service evidence.
2290
- // The lease and PID answer whether that missing service belongs to an extant component (not
2291
- // serving) or an absent one; any other failed probe remains a refusal.
2319
+ // A dead local PID is absent even while its lease awaits expiry. Without a PID record, the
2320
+ // lease still identifies an extant component; any other failed probe remains a refusal.
2292
2321
  const noService = managerNoService(e);
2293
2322
  facts.push(noService ? "serve no answer" : `serve probe refused: ${e.message}`);
2294
2323
  if (!noService)
2295
2324
  return { name: "manager", verdict: "refused", facts };
2296
2325
  const liveRecord = record.kind === "live";
2297
2326
  const hasLease = facts.some((fact) => fact.startsWith("lease holder "));
2298
- return { name: "manager", verdict: liveRecord || hasLease ? "not-serving" : "absent", facts };
2327
+ return { name: "manager", verdict: liveRecord || (record.kind !== "dead" && hasLease) ? "not-serving" : "absent", facts };
2299
2328
  }
2300
2329
  finally {
2301
2330
  await close?.();