agent-transport-system 0.3.1 → 0.3.3

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/dist/ats.js CHANGED
@@ -24,11 +24,11 @@ import wrapAnsi from "wrap-ansi";
24
24
  import { Box, Container, Editor, Key, ProcessTerminal, TUI, Text, getEditorKeybindings, matchesKey } from "@mariozechner/pi-tui";
25
25
 
26
26
  //#region package.json
27
- var version$1 = "0.3.1";
27
+ var version$1 = "0.3.3";
28
28
  var package_default = {
29
29
  name: "agent-transport-system",
30
30
  version: version$1,
31
- atsReleaseDate: "2026-03-12",
31
+ atsReleaseDate: "2026-04-17",
32
32
  description: "Agent Transport System CLI - https://ats.sh",
33
33
  license: "MIT",
34
34
  type: "module",
@@ -4319,7 +4319,7 @@ const daemonRouteObservationSummarySchema = strictObject({
4319
4319
  });
4320
4320
 
4321
4321
  //#endregion
4322
- //#region ../../packages/schemas/dist/entry-brief-LY-buFs9.js
4322
+ //#region ../../packages/schemas/dist/entry-brief-B2jV7t7E.js
4323
4323
  const providerConversationExternalImportCapabilitySchema = _enum(["unsupported", "by_ref_id"]);
4324
4324
  const providerConversationResumeCapabilitySchema = _enum(["unsupported", "by_ref_id"]);
4325
4325
  const providerConversationDiscoverCapabilitySchema = literal("unsupported");
@@ -6576,6 +6576,7 @@ const REPLY_READINESS_REASON_CODE_VALUES = [
6576
6576
  "runtime.adapter.unsupported",
6577
6577
  "controller.bootstrap.failed",
6578
6578
  "controller.gateway.unhealthy",
6579
+ "local_agents.none_needed",
6579
6580
  "route.offline",
6580
6581
  "route.not_registered"
6581
6582
  ];
@@ -11423,6 +11424,10 @@ function buildSkillsSharedNotes(context) {
11423
11424
  //#endregion
11424
11425
  //#region src/command-entry-checks/command-check-requirements.ts
11425
11426
  const SKIP_ALL_CHECKS = {
11427
+ disclaimer: {
11428
+ mode: "skip",
11429
+ stage: "before-entry"
11430
+ },
11426
11431
  cliUpdate: {
11427
11432
  mode: "skip",
11428
11433
  stage: "before-entry"
@@ -11452,6 +11457,10 @@ const OPTIONAL_CLI_UPDATE = { cliUpdate: {
11452
11457
  mode: "optional",
11453
11458
  stage: "before-entry"
11454
11459
  } };
11460
+ const REQUIRED_DISCLAIMER = { disclaimer: {
11461
+ mode: "required",
11462
+ stage: "before-entry"
11463
+ } };
11455
11464
  const AUTO_FIX_LOCAL_RUNTIME = { localRuntimeState: {
11456
11465
  mode: "auto-fix",
11457
11466
  stage: "before-entry"
@@ -11480,10 +11489,6 @@ const STATUS_ONLY_SELECTED_PROFILE = { selectedProfile: {
11480
11489
  mode: "status-only",
11481
11490
  stage: "status-only"
11482
11491
  } };
11483
- const OPTIONAL_SERVICE_AFTER_PROFILE = { service: {
11484
- mode: "optional",
11485
- stage: "after-profile"
11486
- } };
11487
11492
  const STATUS_ONLY_SERVICE = { service: {
11488
11493
  mode: "status-only",
11489
11494
  stage: "status-only"
@@ -11494,77 +11499,77 @@ function buildCommandChecks(...parts) {
11494
11499
  const COMMAND_CHECK_REQUIREMENTS = Object.freeze({
11495
11500
  root: buildCommandChecks(OPTIONAL_CLI_UPDATE),
11496
11501
  view: buildCommandChecks(OPTIONAL_CLI_UPDATE),
11497
- start: buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11498
- "repair.local-state": buildCommandChecks(),
11499
- uninstall: buildCommandChecks(),
11500
- reset: buildCommandChecks(),
11501
- upgrade: buildCommandChecks(),
11502
- "auth.menu": buildCommandChecks(),
11503
- "auth.status": buildCommandChecks(),
11504
- "auth.login": buildCommandChecks(),
11505
- "auth.logout": buildCommandChecks(),
11506
- "skills.menu": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11507
- "skills.install": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11508
- "skills.uninstall": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11509
- "skills.list": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11510
- "skills.check": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11511
- "skills.update": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11512
- "agents.menu": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11513
- "agents.detect": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11514
- "agents.list": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11515
- "agents.manage": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11516
- "agents.enable": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11517
- "agents.repair": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11518
- "agents.disable": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11519
- "agents.show": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11520
- "agents.custom.list": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11521
- "agents.custom.add": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11522
- "agents.custom.update": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11523
- "agents.custom.remove": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11524
- "agents.config": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11525
- "service.menu": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11526
- "service.run": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11527
- "service.status": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11528
- "service.snapshot": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11529
- "service.interrupt": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11530
- "service.cancel": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11531
- "service.trace": buildCommandChecks(OPTIONAL_CLI_UPDATE, REQUIRED_SIGN_IN, REQUIRED_SELECTED_PROFILE),
11532
- "service.install": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11533
- "service.uninstall": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11534
- "service.reinstall": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11535
- "service.stop": buildCommandChecks(OPTIONAL_CLI_UPDATE),
11536
- "space.menu": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11537
- "space.create": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE, OPTIONAL_SERVICE_AFTER_PROFILE),
11538
- "space.delete": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11539
- "space.conversation.bind": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11540
- "space.conversation.clear": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11541
- "space.conversation.status": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11542
- "space.join": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE, OPTIONAL_SERVICE_AFTER_PROFILE),
11543
- "space.add-members": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11544
- "space.remove-members": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11545
- "space.watch": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE, OPTIONAL_SERVICE_AFTER_PROFILE),
11546
- "space.send": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11547
- "space.history": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11548
- "space.contract": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11549
- "space.contract.set": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11550
- "space.status": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11551
- "space.updates": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11552
- "space.result": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11553
- "space.list": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11554
- "space.password": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11555
- "space.guide": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11556
- "space.guide.set": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11557
- "space.guide.clear": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11558
- doctor: buildCommandChecks(OPTIONAL_CLI_UPDATE, OPTIONAL_SIGN_IN, STATUS_ONLY_SERVICE),
11559
- whoami: buildCommandChecks(OPTIONAL_CLI_UPDATE),
11560
- "profile.menu": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11561
- "profile.create": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11562
- "profile.list": buildCommandChecks(OPTIONAL_CLI_UPDATE, REQUIRED_SIGN_IN),
11563
- "profile.ready": buildCommandChecks(OPTIONAL_CLI_UPDATE, STATUS_ONLY_SIGN_IN, STATUS_ONLY_SELECTED_PROFILE),
11564
- "profile.set": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11565
- "profile.show": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11566
- "profile.update": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11567
- "profile.delete": buildCommandChecks(OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN)
11502
+ start: buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11503
+ "repair.local-state": buildCommandChecks(REQUIRED_DISCLAIMER),
11504
+ uninstall: buildCommandChecks(REQUIRED_DISCLAIMER),
11505
+ reset: buildCommandChecks(REQUIRED_DISCLAIMER),
11506
+ upgrade: buildCommandChecks(REQUIRED_DISCLAIMER),
11507
+ "auth.menu": buildCommandChecks(REQUIRED_DISCLAIMER),
11508
+ "auth.status": buildCommandChecks(REQUIRED_DISCLAIMER),
11509
+ "auth.login": buildCommandChecks(REQUIRED_DISCLAIMER),
11510
+ "auth.logout": buildCommandChecks(REQUIRED_DISCLAIMER),
11511
+ "skills.menu": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11512
+ "skills.install": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11513
+ "skills.uninstall": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11514
+ "skills.list": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11515
+ "skills.check": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11516
+ "skills.update": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11517
+ "agents.menu": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11518
+ "agents.detect": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11519
+ "agents.list": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11520
+ "agents.manage": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11521
+ "agents.enable": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11522
+ "agents.repair": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11523
+ "agents.disable": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11524
+ "agents.show": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11525
+ "agents.custom.list": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11526
+ "agents.custom.add": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11527
+ "agents.custom.update": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11528
+ "agents.custom.remove": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11529
+ "agents.config": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11530
+ "service.menu": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11531
+ "service.run": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11532
+ "service.status": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11533
+ "service.snapshot": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11534
+ "service.interrupt": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11535
+ "service.cancel": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME),
11536
+ "service.trace": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, REQUIRED_SIGN_IN, REQUIRED_SELECTED_PROFILE),
11537
+ "service.install": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11538
+ "service.uninstall": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11539
+ "service.reinstall": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11540
+ "service.stop": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11541
+ "space.menu": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11542
+ "space.create": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11543
+ "space.delete": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11544
+ "space.conversation.bind": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11545
+ "space.conversation.clear": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11546
+ "space.conversation.status": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11547
+ "space.join": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11548
+ "space.add-members": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11549
+ "space.remove-members": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11550
+ "space.watch": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11551
+ "space.send": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11552
+ "space.history": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11553
+ "space.contract": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11554
+ "space.contract.set": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11555
+ "space.status": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11556
+ "space.updates": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11557
+ "space.result": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11558
+ "space.list": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11559
+ "space.password": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11560
+ "space.guide": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11561
+ "space.guide.set": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11562
+ "space.guide.clear": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN, CONFIRM_SELECTED_PROFILE),
11563
+ doctor: buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, OPTIONAL_SIGN_IN, STATUS_ONLY_SERVICE),
11564
+ whoami: buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE),
11565
+ "profile.menu": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11566
+ "profile.create": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11567
+ "profile.list": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, REQUIRED_SIGN_IN),
11568
+ "profile.ready": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, STATUS_ONLY_SIGN_IN, STATUS_ONLY_SELECTED_PROFILE),
11569
+ "profile.set": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11570
+ "profile.show": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11571
+ "profile.update": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN),
11572
+ "profile.delete": buildCommandChecks(REQUIRED_DISCLAIMER, OPTIONAL_CLI_UPDATE, AUTO_FIX_LOCAL_RUNTIME, REQUIRED_SIGN_IN)
11568
11573
  });
11569
11574
  function getCommandCheckRequirements(entryName) {
11570
11575
  return COMMAND_CHECK_REQUIREMENTS[entryName];
@@ -14670,6 +14675,9 @@ async function getConfiguredDaemonDispatchLimits() {
14670
14675
  async function getConfiguredOnboardingConfig() {
14671
14676
  return (await readRuntimeConfig())?.onboarding ?? null;
14672
14677
  }
14678
+ async function getConfiguredOnboardingDisclaimerAcceptedAt() {
14679
+ return (await getConfiguredOnboardingConfig())?.disclaimerAcceptedAt ?? null;
14680
+ }
14673
14681
  async function getConfiguredStartV1OnboardingState() {
14674
14682
  return (await getConfiguredOnboardingConfig())?.startV1 ?? null;
14675
14683
  }
@@ -14734,10 +14742,23 @@ async function updateConfiguredDeviceRuntimeState(updater) {
14734
14742
  async function updateConfiguredStartV1OnboardingState(updater) {
14735
14743
  await withRuntimeConfigLock(async () => {
14736
14744
  const previous = await readRuntimeConfig();
14737
- const previousStartV1 = previous?.onboarding?.startV1 ?? null;
14745
+ const previousOnboarding = previous?.onboarding ?? null;
14746
+ const previousStartV1 = previousOnboarding?.startV1 ?? null;
14738
14747
  const nextStartV1 = updater(previousStartV1);
14739
14748
  if (JSON.stringify(previousStartV1) === JSON.stringify(nextStartV1)) return;
14740
- await writeRuntimeConfig(buildNextConfig(previous, { onboarding: nextStartV1 ? { startV1: nextStartV1 } : null }));
14749
+ await writeRuntimeConfig(buildNextConfig(previous, { onboarding: nextStartV1 || previousOnboarding?.disclaimerAcceptedAt ? {
14750
+ ...previousOnboarding?.disclaimerAcceptedAt ? { disclaimerAcceptedAt: previousOnboarding.disclaimerAcceptedAt } : {},
14751
+ ...nextStartV1 ? { startV1: nextStartV1 } : {}
14752
+ } : null }));
14753
+ });
14754
+ }
14755
+ async function updateConfiguredOnboardingConfig(updater) {
14756
+ await withRuntimeConfigLock(async () => {
14757
+ const previous = await readRuntimeConfig();
14758
+ const previousOnboarding = previous?.onboarding ?? null;
14759
+ const nextOnboarding = updater(previousOnboarding);
14760
+ if (JSON.stringify(previousOnboarding) === JSON.stringify(nextOnboarding)) return;
14761
+ await writeRuntimeConfig(buildNextConfig(previous, { onboarding: nextOnboarding }));
14741
14762
  });
14742
14763
  }
14743
14764
  function buildNextConfig(previous, overrides) {
@@ -14867,9 +14888,14 @@ function parseRuntimeDeviceRuntimeStateObject(value) {
14867
14888
  }
14868
14889
  function parseRuntimeOnboardingConfig(value) {
14869
14890
  if (!value || typeof value !== "object") return;
14870
- const startV1 = parseRuntimeStartV1OnboardingState(value.startV1);
14871
- if (!startV1) return;
14872
- return { startV1 };
14891
+ const obj = value;
14892
+ const disclaimerAcceptedAt = toTrimmedStringOrUndefined(obj.disclaimerAcceptedAt);
14893
+ const startV1 = parseRuntimeStartV1OnboardingState(obj.startV1);
14894
+ if (!(disclaimerAcceptedAt || startV1)) return;
14895
+ return {
14896
+ ...disclaimerAcceptedAt ? { disclaimerAcceptedAt } : {},
14897
+ ...startV1 ? { startV1 } : {}
14898
+ };
14873
14899
  }
14874
14900
  function parseRuntimeStartV1OnboardingState(value) {
14875
14901
  if (!value || typeof value !== "object") return;
@@ -16611,8 +16637,77 @@ function createPresenter(context) {
16611
16637
 
16612
16638
  //#endregion
16613
16639
  //#region src/system/daemon-install-attention.ts
16640
+ const DAEMON_INSTALL_DECISION_CHOICE = {
16641
+ install: "install",
16642
+ notNow: "not_now"
16643
+ };
16614
16644
  const DAEMON_INSTALL_COMMAND = "ats service install";
16615
16645
  const DAEMON_UPGRADE_COMMAND = "ats upgrade";
16646
+ const DAEMON_DECISION_RECOMMENDED_HINT = "Recommended";
16647
+ const DAEMON_INSTALL_DECISION_COPY = {
16648
+ missing: {
16649
+ question: "ATS Service is missing. Install it now?",
16650
+ explanation: "Without it, local agents on this device can't reply from Space.",
16651
+ primaryLabel: "Yes, install it for me",
16652
+ deferred: "ATS Service is still missing. Local agents on this device can't reply from Space until you install it later with `ats service install`.",
16653
+ failed: "ATS Service could not be installed. Local agents on this device still can't reply from Space. Run `ats service install`."
16654
+ },
16655
+ outdated: {
16656
+ question: "ATS Service needs an update. Fix it now?",
16657
+ explanation: "Until ATS fixes it, local agents on this device may not be able to reply from Space.",
16658
+ primaryLabel: "Yes, fix it for me",
16659
+ deferred: "ATS Service still needs an update. Local agents on this device may not be able to reply from Space until ATS fixes it. Run `ats service install` later.",
16660
+ failed: "ATS Service was not updated. Local agents on this device may not be able to reply from Space. Run `ats service install`."
16661
+ }
16662
+ };
16663
+ function buildDaemonInstallDecisionPromptConfig(input) {
16664
+ const copy = DAEMON_INSTALL_DECISION_COPY[input.kind];
16665
+ const detailLine = input.kind === "outdated" && typeof input.installedVersion === "string" && typeof input.expectedVersion === "string" ? `Installed v${input.installedVersion}, expected v${input.expectedVersion}.` : null;
16666
+ return {
16667
+ message: [
16668
+ copy.question,
16669
+ copy.explanation,
16670
+ detailLine
16671
+ ].filter((line) => typeof line === "string" && line.length > 0).join("\n"),
16672
+ initialValue: DAEMON_INSTALL_DECISION_CHOICE.install,
16673
+ options: [{
16674
+ value: DAEMON_INSTALL_DECISION_CHOICE.install,
16675
+ label: copy.primaryLabel,
16676
+ hint: DAEMON_DECISION_RECOMMENDED_HINT
16677
+ }, {
16678
+ value: DAEMON_INSTALL_DECISION_CHOICE.notNow,
16679
+ label: "Not now"
16680
+ }]
16681
+ };
16682
+ }
16683
+ async function promptForDaemonInstallDecision(input) {
16684
+ const choice = await select(buildDaemonInstallDecisionPromptConfig(input));
16685
+ if (isCancel(choice)) return "cancelled";
16686
+ return choice === DAEMON_INSTALL_DECISION_CHOICE.install ? DAEMON_INSTALL_DECISION_CHOICE.install : DAEMON_INSTALL_DECISION_CHOICE.notNow;
16687
+ }
16688
+ async function runDaemonInstallDecisionPrompt(input) {
16689
+ const decision = await promptForDaemonInstallDecision({
16690
+ kind: input.kind,
16691
+ installedVersion: input.installedVersion,
16692
+ expectedVersion: input.expectedVersion
16693
+ });
16694
+ if (decision === DAEMON_INSTALL_DECISION_CHOICE.install) try {
16695
+ await input.installService();
16696
+ return "install";
16697
+ } catch {
16698
+ await input.emitLine(formatDaemonInstallFailedMessage(input.kind));
16699
+ return "failed";
16700
+ }
16701
+ if (decision === "cancelled") return "cancelled";
16702
+ await input.emitLine(formatDaemonInstallDeferredMessage(input.kind));
16703
+ return decision;
16704
+ }
16705
+ function formatDaemonInstallDeferredMessage(kind) {
16706
+ return formatInlineAtsCliCommands(DAEMON_INSTALL_DECISION_COPY[kind].deferred);
16707
+ }
16708
+ function formatDaemonInstallFailedMessage(kind) {
16709
+ return formatInlineAtsCliCommands(DAEMON_INSTALL_DECISION_COPY[kind].failed);
16710
+ }
16616
16711
  function formatDaemonServiceMissingRecommendation() {
16617
16712
  return "ATS Service is not installed yet. Local agents on this device can't reply from Space until it is installed.";
16618
16713
  }
@@ -19712,6 +19807,12 @@ function getStepOrderIndex$1(stepId) {
19712
19807
  if (index === -1) throw new Error(`unknown start session step "${stepId}"`);
19713
19808
  return index;
19714
19809
  }
19810
+ function resolveServiceParticipationRequirement(input) {
19811
+ if (input.humanOnlyParticipation) return "not_needed";
19812
+ if (input.explicitSelectedLocalAgents) return input.selectedAgentProfilesHaveRunnableLocalSetup ? "offer_service_gate" : "agent_setup_needed";
19813
+ if (input.deviceLocalDemand === "keep_running") return "offer_service_gate";
19814
+ return "not_needed";
19815
+ }
19715
19816
  function buildDaemonRouteObservationPresentation$1(observation) {
19716
19817
  if (observation.status === "online") return {
19717
19818
  detail: "ATS server can see an active daemon route for this profile.",
@@ -19774,6 +19875,7 @@ function formatStartReplyReadinessReason(reasonCode) {
19774
19875
  case "runtime.adapter.unsupported": return "This agent setup can't reply from this device";
19775
19876
  case "controller.bootstrap.failed": return "ATS couldn't finish setting up OpenClaw on this device";
19776
19877
  case "controller.gateway.unhealthy": return "OpenClaw is not ready on this device yet";
19878
+ case "local_agents.none_needed": return "This device does not need ATS Service until you choose local agents to bring into a space";
19777
19879
  case "route.offline": return "ATS server can't currently see an active daemon route for this profile";
19778
19880
  case "route.not_registered": return "ATS is still setting up replies for this profile on this device";
19779
19881
  default: return reasonCode ? "ATS can't confirm reply status yet" : null;
@@ -26911,6 +27013,7 @@ function emitStructuredReconcileEvidence(eventName, payload) {
26911
27013
 
26912
27014
  //#endregion
26913
27015
  //#region src/system/device-runtime-state-sync.ts
27016
+ const NO_LOCAL_AGENTS_NEED_ATS_SERVICE_REASON = "No local agents on this device need ATS Service right now";
26914
27017
  async function syncDeviceRuntimeStateProjection(input) {
26915
27018
  const mode = input?.mode ?? "check";
26916
27019
  const currentControllers = await readCurrentControllers();
@@ -26950,7 +27053,7 @@ async function syncDeviceRuntimeStateProjection(input) {
26950
27053
  disabledControllerIds: [],
26951
27054
  repairRequiredControllerIds: [],
26952
27055
  repairedControllerIds: [],
26953
- reason: "no bound agent controller requires local projection"
27056
+ reason: NO_LOCAL_AGENTS_NEED_ATS_SERVICE_REASON
26954
27057
  });
26955
27058
  const gap = resolveProjectionGap({
26956
27059
  currentControllers,
@@ -28039,7 +28142,7 @@ function normalizeOptionalString$14(value) {
28039
28142
  }
28040
28143
  function resolveZeroLocalValueReason(projection) {
28041
28144
  if (!projection) return "no wakeable local agent routes are available";
28042
- if (projection.status === "ok" && projection.projectedControllerIds.length === 0) return "no bound agent controller currently needs local projection";
28145
+ if (projection.status === "ok" && projection.projectedControllerIds.length === 0) return NO_LOCAL_AGENTS_NEED_ATS_SERVICE_REASON;
28043
28146
  if (projection.status === "degraded") return "projected local controllers are not currently wakeable";
28044
28147
  return "no wakeable local agent routes are available";
28045
28148
  }
@@ -28117,7 +28220,28 @@ function resolveBootstrapCommandUnavailableSummary(message) {
28117
28220
 
28118
28221
  //#endregion
28119
28222
  //#region src/system/reply-readiness.ts
28120
- const REPLY_READINESS_REASON_PRIORITY = [...REPLY_READINESS_REASON_CODE_VALUES];
28223
+ const REPLY_READINESS_REASON_PRIORITY = [
28224
+ "profile.inactive",
28225
+ "profile.unbound",
28226
+ "controller.binding.disabled",
28227
+ "projection.missing",
28228
+ "controller.registry.disabled",
28229
+ "controller.launch.choice_required",
28230
+ "controller.launch.needs_repair",
28231
+ "workspace.missing",
28232
+ "workspace.invalid",
28233
+ "runtime.adapter.unsupported",
28234
+ "controller.bootstrap.failed",
28235
+ "controller.gateway.unhealthy",
28236
+ "service.not_installed",
28237
+ "service.drifted",
28238
+ "service.not_running",
28239
+ "dispatch.storage_not_ready",
28240
+ "service.gateway_chain_unhealthy",
28241
+ "local_agents.none_needed",
28242
+ "route.offline",
28243
+ "route.not_registered"
28244
+ ];
28121
28245
  const MANUAL_SPACE_PARTICIPATION_REASON = "You can still join the space and send messages yourself";
28122
28246
  async function resolveDeviceReplyReadinessStatus(input = {}) {
28123
28247
  const status = input.status ?? await getDaemonStatus();
@@ -28472,13 +28596,17 @@ function resolveDeviceReplyReadinessFromRuntimeProjection(runtimeProjection) {
28472
28596
  return null;
28473
28597
  }
28474
28598
  function createRouteNotRegisteredReplyReadinessStatus(input) {
28599
+ const projectionWithoutLocalDemand = input.localDemand?.projection ?? input.runtimeProjection;
28475
28600
  return createDeviceReplyReadinessStatus({
28476
- reasonCode: "route.not_registered",
28477
- reasonText: normalizeReplyReadinessReason(input.localDemand?.reason ?? input.runtimeProjection?.reason) ?? "ATS is still setting up replies on this device",
28478
- replyReadiness: input.runtimeProjection?.status === "skipped" ? "unknown" : "blocked",
28601
+ reasonCode: input.runtimeProjection?.status === "skipped" || hasNoProjectedLocalControllers(projectionWithoutLocalDemand) ? "local_agents.none_needed" : "route.not_registered",
28602
+ reasonText: normalizeOptionalText$17(input.localDemand?.reason ?? input.runtimeProjection?.reason) ?? "ATS is still setting up replies on this device",
28603
+ replyReadiness: input.runtimeProjection?.status === "skipped" || hasNoProjectedLocalControllers(projectionWithoutLocalDemand) ? "unknown" : "blocked",
28479
28604
  wakeableRouteCount: 0
28480
28605
  });
28481
28606
  }
28607
+ function hasNoProjectedLocalControllers(projection) {
28608
+ return projection?.status === "ok" && projection.projectedControllerIds.length === 0;
28609
+ }
28482
28610
  function createDeviceReplyReadinessStatus(input) {
28483
28611
  return {
28484
28612
  reasonCodes: sortReplyReadinessReasonCodes(input.reasonCode ? [input.reasonCode] : []),
@@ -28531,6 +28659,7 @@ function describeReplyReadinessReason(reasonCode) {
28531
28659
  case "runtime.adapter.unsupported": return "the local agent controller setup is not supported";
28532
28660
  case "controller.bootstrap.failed": return "ATS couldn't finish setting up the local OpenClaw agent for this profile";
28533
28661
  case "controller.gateway.unhealthy": return "the local OpenClaw Gateway is not healthy on this device";
28662
+ case "local_agents.none_needed": return "no local agents on this device need ATS Service right now";
28534
28663
  case "route.offline": return "ATS server can't currently see an active daemon route for this profile";
28535
28664
  case "route.not_registered": return "ATS is still setting up replies for this profile on this device";
28536
28665
  default: return "ATS can't confirm local reply readiness yet";
@@ -30889,7 +31018,7 @@ const COMMAND_ENTRY_COPY_BY_NAME = Object.freeze({
30889
31018
  signInAction: "creating a space",
30890
31019
  profileAction: "creating a space",
30891
31020
  cliUpdateAction: "creating a space",
30892
- serviceAction: "creating a space"
31021
+ serviceAction: null
30893
31022
  },
30894
31023
  "space.delete": {
30895
31024
  signInAction: "deleting a space",
@@ -30919,7 +31048,7 @@ const COMMAND_ENTRY_COPY_BY_NAME = Object.freeze({
30919
31048
  signInAction: "joining a space",
30920
31049
  profileAction: "joining a space",
30921
31050
  cliUpdateAction: "joining a space",
30922
- serviceAction: "joining a space"
31051
+ serviceAction: null
30923
31052
  },
30924
31053
  "space.add-members": {
30925
31054
  signInAction: "adding members to a space",
@@ -30937,7 +31066,7 @@ const COMMAND_ENTRY_COPY_BY_NAME = Object.freeze({
30937
31066
  signInAction: "watching a space",
30938
31067
  profileAction: "watching a space",
30939
31068
  cliUpdateAction: "watching a space",
30940
- serviceAction: "watching a space"
31069
+ serviceAction: null
30941
31070
  },
30942
31071
  "space.send": {
30943
31072
  signInAction: "sending a message",
@@ -31346,6 +31475,82 @@ async function runCliUpdateCheck(input) {
31346
31475
  };
31347
31476
  }
31348
31477
 
31478
+ //#endregion
31479
+ //#region src/onboarding/disclaimer.ts
31480
+ const ATS_ACCEPT_DISCLAIMER_ENV = "ATS_ACCEPT_DISCLAIMER";
31481
+ const ATS_ONBOARDING_DISCLAIMER_ACCEPT = "__onboarding_disclaimer_accept__";
31482
+ const ATS_ONBOARDING_DISCLAIMER_DECLINE = "__onboarding_disclaimer_decline__";
31483
+ const ATS_ONBOARDING_DISCLAIMER_TEXT = "ATSD is experimental and changes rapidly. Breaking changes may occur. You are fully responsible for data security and sensitive information handling. Use ATSD only in trusted environments.";
31484
+ async function runOnboardingDisclaimerGate(input) {
31485
+ if (await getConfiguredOnboardingDisclaimerAcceptedAt().catch(() => null)) return true;
31486
+ if (shouldBypassDisclaimerWithEnv()) {
31487
+ await persistOnboardingDisclaimerAccepted();
31488
+ return true;
31489
+ }
31490
+ if (input.runtime.resolvedView === "agent") {
31491
+ input.presenter.data({
31492
+ code: input.codePrefix,
31493
+ payload: {
31494
+ acceptedAt: null,
31495
+ text: ATS_ONBOARDING_DISCLAIMER_TEXT,
31496
+ mode: "agent_notice",
31497
+ requiresConfirmation: false
31498
+ }
31499
+ });
31500
+ await persistOnboardingDisclaimerAccepted();
31501
+ return true;
31502
+ }
31503
+ note(`⚠️ ${ATS_ONBOARDING_DISCLAIMER_TEXT}`, "Disclaimer");
31504
+ if (!input.interactive) {
31505
+ input.presenter.line({
31506
+ code: `${input.codePrefix}.auto_continue`,
31507
+ text: "Non-interactive session: disclaimer displayed and ATS continues."
31508
+ });
31509
+ await persistOnboardingDisclaimerAccepted();
31510
+ return true;
31511
+ }
31512
+ const decision = await select({
31513
+ message: "Do you accept and continue?",
31514
+ options: [{
31515
+ value: ATS_ONBOARDING_DISCLAIMER_ACCEPT,
31516
+ label: "Yes, continue"
31517
+ }, {
31518
+ value: ATS_ONBOARDING_DISCLAIMER_DECLINE,
31519
+ label: "No, exit"
31520
+ }],
31521
+ initialValue: ATS_ONBOARDING_DISCLAIMER_ACCEPT
31522
+ });
31523
+ if (isCancel(decision) || decision === ATS_ONBOARDING_DISCLAIMER_DECLINE) {
31524
+ cancel("Cancelled.");
31525
+ return false;
31526
+ }
31527
+ await persistOnboardingDisclaimerAccepted();
31528
+ return true;
31529
+ }
31530
+ function shouldBypassDisclaimerWithEnv() {
31531
+ return String(process.env[ATS_ACCEPT_DISCLAIMER_ENV] ?? "").trim() === "1";
31532
+ }
31533
+ async function persistOnboardingDisclaimerAccepted() {
31534
+ const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
31535
+ await updateConfiguredOnboardingConfig((current) => ({
31536
+ ...current ?? {},
31537
+ disclaimerAcceptedAt: acceptedAt
31538
+ }));
31539
+ }
31540
+
31541
+ //#endregion
31542
+ //#region src/command-entry-checks/check-disclaimer.ts
31543
+ async function runDisclaimerCheck(input) {
31544
+ if (input.requirement.mode === "skip") return { status: "continue" };
31545
+ if (await runOnboardingDisclaimerGate({
31546
+ runtime: input.context.runtime,
31547
+ presenter: input.context.presenter,
31548
+ interactive: input.context.allowPrompt,
31549
+ codePrefix: "command.entry.disclaimer"
31550
+ })) return { status: "continue" };
31551
+ return { status: "cancelled" };
31552
+ }
31553
+
31349
31554
  //#endregion
31350
31555
  //#region src/config/storage-version.ts
31351
31556
  const STORAGE_VERSION_SCHEMA = "ats-local-state-v1";
@@ -46783,7 +46988,7 @@ function resolveServiceParticipationReadiness(input) {
46783
46988
  };
46784
46989
  }
46785
46990
  if (reasonCodes.includes("service.not_installed")) {
46786
- const nextSteps = formatAtsCliCommands(["ats service install", "ats service run --mode foreground"]);
46991
+ const nextSteps = formatAtsCliCommands(["ats service install", "ats service run"]);
46787
46992
  return {
46788
46993
  serviceReadiness: "not_installed",
46789
46994
  summaryText: "Not installed",
@@ -46794,7 +46999,7 @@ function resolveServiceParticipationReadiness(input) {
46794
46999
  };
46795
47000
  }
46796
47001
  if (reasonCodes.includes("service.not_running")) {
46797
- const nextSteps = [formatAtsCliCommand("ats service run --mode foreground")];
47002
+ const nextSteps = [formatAtsCliCommand("ats service run")];
46798
47003
  return {
46799
47004
  serviceReadiness: "not_running",
46800
47005
  summaryText: "Not running",
@@ -46812,6 +47017,21 @@ function resolveServiceParticipationReadiness(input) {
46812
47017
  nextSteps: [],
46813
47018
  runtimeHeadline
46814
47019
  };
47020
+ if (reasonCodes.includes("local_agents.none_needed")) {
47021
+ const nextSteps = formatAtsCliCommands([
47022
+ "ats start",
47023
+ "ats service install",
47024
+ "ats service run"
47025
+ ]);
47026
+ return {
47027
+ serviceReadiness: "unknown",
47028
+ summaryText: "Not needed yet",
47029
+ detailText: input.deviceReplyReadiness.reasonText,
47030
+ nextStepSummary: "If you later choose local agents to bring into a space, rerun `ats start` or enable ATS Service directly.",
47031
+ nextSteps,
47032
+ runtimeHeadline
47033
+ };
47034
+ }
46815
47035
  const nextSteps = [formatAtsCliCommand("ats service status")];
46816
47036
  return {
46817
47037
  serviceReadiness: "unknown",
@@ -47268,7 +47488,10 @@ async function runDaemon(input) {
47268
47488
  return;
47269
47489
  }
47270
47490
  if (canUseInteractivePrompts(runtime)) {
47271
- await runInteractiveDaemonMenu({ view: runtime.resolvedView });
47491
+ await runInteractiveDaemonMenu({
47492
+ gatewayUrl: input.gatewayUrl,
47493
+ view: runtime.resolvedView
47494
+ });
47272
47495
  return;
47273
47496
  }
47274
47497
  presenter.line({
@@ -48494,6 +48717,7 @@ async function runDaemonStatusFlow(input, options = {}) {
48494
48717
  await runDaemonStatus(input);
48495
48718
  if (!(options.offerStartPrompt && interactiveHuman)) return "completed";
48496
48719
  return await maybeOfferDaemonStartAfterStatus({
48720
+ gatewayUrl: input.gatewayUrl,
48497
48721
  view: "human",
48498
48722
  ownerUserId
48499
48723
  });
@@ -49656,6 +49880,7 @@ async function runDaemonBackgroundStartForDecision(input) {
49656
49880
  const previousExitCode = process.exitCode;
49657
49881
  const result = await runDaemonRun({
49658
49882
  agentOverviewHandled: true,
49883
+ gatewayUrl: input.gatewayUrl,
49659
49884
  mode: "background",
49660
49885
  view: input.view
49661
49886
  });
@@ -50738,7 +50963,10 @@ async function handleInteractiveDaemonMenuChoice(input, choice) {
50738
50963
  }
50739
50964
  if (choice === DAEMON_MENU_CHOICE.reinstall) {
50740
50965
  const result = await runDaemonReinstall(input, { suppressInstallOutput: true });
50741
- if (isDaemonReinstallCompletedAndStopped(result)) return await maybeOfferDaemonStartAfterStatus({ view: input.view }) === "cancelled";
50966
+ if (isDaemonReinstallCompletedAndStopped(result)) return await maybeOfferDaemonStartAfterStatus({
50967
+ gatewayUrl: input.gatewayUrl,
50968
+ view: input.view
50969
+ }) === "cancelled";
50742
50970
  return result.result === "cancelled";
50743
50971
  }
50744
50972
  return await runDaemonUninstall(input) === "cancelled";
@@ -50749,7 +50977,10 @@ async function maybeOfferDaemonStartAfterStatus(input) {
50749
50977
  localDemand: input.localDemand
50750
50978
  })).offerStartPrompt) return "completed";
50751
50979
  const presenter = createPresenter(await resolveRuntimeContext({ view: input.view }));
50752
- const decision = await runDaemonStartDecisionPrompt({ startService: async () => await runDaemonBackgroundStartForDecision({ view: input.view }) });
50980
+ const decision = await runDaemonStartDecisionPrompt({ startService: async () => await runDaemonBackgroundStartForDecision({
50981
+ gatewayUrl: input.gatewayUrl,
50982
+ view: input.view
50983
+ }) });
50753
50984
  if (decision === "cancelled") return "cancelled";
50754
50985
  if (decision === "deferred_with_card") return "completed";
50755
50986
  if (decision === "not_now") {
@@ -50851,7 +51082,7 @@ function mapLocalControllerStatusReason(reason) {
50851
51082
  const projectedControllersDisabledReason = "one or more projected controllers are disabled locally";
50852
51083
  if (reason.includes("already exists")) return "setup is already prepared";
50853
51084
  if (reason.includes("authentication is not valid")) return "sign in required";
50854
- if (reason.includes("no bound agent controller requires local projection")) return "no local agent is configured for this device";
51085
+ if (reason.includes(NO_LOCAL_AGENTS_NEED_ATS_SERVICE_REASON)) return "no local agent is configured for this device";
50855
51086
  if (reason.includes(missingOrDisabledReason)) return "some local agents are missing or turned off";
50856
51087
  if (reason.includes("is missing for one or more bound controllers")) return "some local agents are missing";
50857
51088
  if (reason.includes(projectedControllersDisabledReason)) return "some local agent entries are disabled";
@@ -51057,6 +51288,183 @@ async function ensureDaemonVersionForCliStartup(input) {
51057
51288
  };
51058
51289
  }
51059
51290
 
51291
+ //#endregion
51292
+ //#region src/system/daemon-service-participation-gate.ts
51293
+ function shouldRequireDaemonServiceParticipation(localDemand) {
51294
+ return localDemand?.decision === "keep_running";
51295
+ }
51296
+ async function resolveDaemonServiceParticipationStatus(input) {
51297
+ const expectedVersion = input?.expectedVersion ?? resolveCurrentDaemonExpectedVersion();
51298
+ const inventory = await inspectDaemonServiceInventory({ expectedVersion });
51299
+ if (!(inventory.installStatus.installed && inventory.installStatus.state)) return {
51300
+ kind: "missing",
51301
+ expectedVersion
51302
+ };
51303
+ const installedVersion = inventory.installStatus.state.daemonVersion;
51304
+ if (installedVersion !== expectedVersion) return {
51305
+ kind: "outdated",
51306
+ expectedVersion,
51307
+ installedVersion
51308
+ };
51309
+ const repairAnomalies = inventory.anomalies.filter((anomaly) => DAEMON_SERVICE_REPAIR_ANOMALIES.has(anomaly.code));
51310
+ if (repairAnomalies.length > 0) return {
51311
+ kind: "needs_repair",
51312
+ expectedVersion,
51313
+ installedVersion,
51314
+ runtimeStatus: inventory.runtimeStatus.status,
51315
+ detail: repairAnomalies[0]?.message ?? "ATS found conflicting local service state on this device.",
51316
+ anomalyCodes: repairAnomalies.map((anomaly) => anomaly.code)
51317
+ };
51318
+ if (inventory.runtimeStatus.status !== "running") return {
51319
+ kind: "not_running",
51320
+ expectedVersion,
51321
+ installedVersion,
51322
+ runtimeStatus: inventory.runtimeStatus.status
51323
+ };
51324
+ return {
51325
+ kind: "ready",
51326
+ expectedVersion,
51327
+ installedVersion,
51328
+ runtimeStatus: inventory.runtimeStatus.status
51329
+ };
51330
+ }
51331
+ async function runDaemonServiceParticipationGate(input) {
51332
+ if (!shouldRunDaemonServiceParticipationGate(input)) return { status: "aligned" };
51333
+ const initialStatus = await resolveDaemonServiceParticipationStatus();
51334
+ if (initialStatus.kind === "ready") return { status: "aligned" };
51335
+ const codePrefix = resolveDaemonServiceParticipationCodePrefix(input.intent);
51336
+ const installStageResult = await runDaemonServiceParticipationInstallStage({
51337
+ input,
51338
+ codePrefix,
51339
+ status: initialStatus
51340
+ });
51341
+ if (installStageResult.result) return installStageResult.result;
51342
+ return await runDaemonServiceParticipationActionStage({
51343
+ input,
51344
+ codePrefix,
51345
+ status: installStageResult.status
51346
+ });
51347
+ }
51348
+ function shouldRunDaemonServiceParticipationGate(input) {
51349
+ return input.resolvedView === "human" && input.allowPrompt && (input.forcePrompt === true || shouldRequireDaemonServiceParticipation(input.localDemand));
51350
+ }
51351
+ async function runDaemonServiceParticipationInstallStage(input) {
51352
+ if (input.status.kind !== "missing" && input.status.kind !== "outdated") return { status: input.status };
51353
+ const installResult = await runDaemonInstallDecisionPrompt({
51354
+ kind: input.status.kind,
51355
+ ...input.status.kind === "outdated" ? { installedVersion: input.status.installedVersion } : {},
51356
+ expectedVersion: input.status.expectedVersion,
51357
+ emitLine: (text) => {
51358
+ input.input.presenter.line({
51359
+ code: `${input.codePrefix}.install`,
51360
+ text
51361
+ });
51362
+ },
51363
+ installService: async () => {
51364
+ await runDaemonInstall({ view: input.input.view }, { suppressHumanOutput: shouldSuppressHumanInstallOutput(input.input.intent) });
51365
+ }
51366
+ });
51367
+ if (installResult === "cancelled") return {
51368
+ status: input.status,
51369
+ result: { status: "cancelled" }
51370
+ };
51371
+ if (installResult === "failed") return {
51372
+ status: input.status,
51373
+ result: { status: "needs_attention" }
51374
+ };
51375
+ if (installResult === "not_now" || !shouldStartAfterDaemonInstall(input.input.intent)) return {
51376
+ status: input.status,
51377
+ result: { status: "declined" }
51378
+ };
51379
+ const refreshedStatus = await resolveDaemonServiceParticipationStatus();
51380
+ if (refreshedStatus.kind === "ready") return {
51381
+ status: refreshedStatus,
51382
+ result: { status: "aligned" }
51383
+ };
51384
+ if (refreshedStatus.kind === "missing" || refreshedStatus.kind === "outdated") {
51385
+ renderDaemonStartNeedsAttention({
51386
+ presenter: input.input.presenter,
51387
+ codePrefix: input.codePrefix,
51388
+ summary: refreshedStatus.kind === "missing" ? formatDaemonInstallFailedMessage("missing") : formatDaemonInstallFailedMessage("outdated")
51389
+ });
51390
+ return {
51391
+ status: refreshedStatus,
51392
+ result: { status: "needs_attention" }
51393
+ };
51394
+ }
51395
+ return { status: refreshedStatus };
51396
+ }
51397
+ async function runDaemonServiceParticipationActionStage(input) {
51398
+ if (input.status.kind === "needs_repair") return mapDaemonBackgroundStartResult(await runDaemonBackgroundStartForDecision({
51399
+ gatewayUrl: input.input.gatewayUrl,
51400
+ view: input.input.view
51401
+ }), {
51402
+ presenter: input.input.presenter,
51403
+ codePrefix: input.codePrefix
51404
+ });
51405
+ if (input.status.kind !== "not_running") return { status: "aligned" };
51406
+ return mapDaemonBackgroundStartResult(await runDaemonStartDecisionPrompt({ startService: async () => await runDaemonBackgroundStartForDecision({
51407
+ gatewayUrl: input.input.gatewayUrl,
51408
+ view: input.input.view
51409
+ }) }), {
51410
+ presenter: input.input.presenter,
51411
+ codePrefix: input.codePrefix
51412
+ });
51413
+ }
51414
+ function resolveDaemonServiceParticipationCodePrefix(intent) {
51415
+ switch (intent) {
51416
+ case "start": return "start.service";
51417
+ case "space_create_join": return "space.create_join.service";
51418
+ case "space_join": return "space.join.service";
51419
+ case "space_add_members": return "space.add_members.service";
51420
+ default: throw new Error(`Unknown daemon service participation intent: ${intent}`);
51421
+ }
51422
+ }
51423
+ function shouldStartAfterDaemonInstall(_intent) {
51424
+ return true;
51425
+ }
51426
+ function shouldSuppressHumanInstallOutput(intent) {
51427
+ return intent !== "start";
51428
+ }
51429
+ function mapDaemonBackgroundStartResult(result, input) {
51430
+ if (result === "started" || result === "deferred_with_card") return { status: "aligned" };
51431
+ if (result === "cancelled") return { status: "cancelled" };
51432
+ if (result === "not_now") {
51433
+ renderDaemonStartDeferred({
51434
+ presenter: input.presenter,
51435
+ codePrefix: input.codePrefix
51436
+ });
51437
+ return { status: "declined" };
51438
+ }
51439
+ if (result === "failed") {
51440
+ renderDaemonStartNeedsAttention({
51441
+ presenter: input.presenter,
51442
+ codePrefix: input.codePrefix,
51443
+ summary: "ATS could not finish starting ATS Service."
51444
+ });
51445
+ return { status: "needs_attention" };
51446
+ }
51447
+ return { status: "needs_attention" };
51448
+ }
51449
+ function renderDaemonStartDeferred(input) {
51450
+ const card = buildDaemonStartDeferredCard();
51451
+ renderInfoCard({
51452
+ presenter: input.presenter,
51453
+ title: card.title,
51454
+ codePrefix: `${input.codePrefix}.deferred`,
51455
+ rows: card.rows
51456
+ });
51457
+ }
51458
+ function renderDaemonStartNeedsAttention(input) {
51459
+ const card = buildDaemonStartNeedsAttentionCard({ summary: input.summary });
51460
+ renderInfoCard({
51461
+ presenter: input.presenter,
51462
+ title: card.title,
51463
+ codePrefix: `${input.codePrefix}.failed`,
51464
+ rows: card.rows
51465
+ });
51466
+ }
51467
+
51060
51468
  //#endregion
51061
51469
  //#region src/command-entry-checks/check-service.ts
51062
51470
  const FIX_SERVICE = "__command_entry_fix_service__";
@@ -51069,10 +51477,9 @@ async function runServiceCheck(input) {
51069
51477
  shouldAutoRepair: input.requirement.mode !== "status-only",
51070
51478
  view: input.context.runtime.resolvedView
51071
51479
  });
51072
- const shouldSuppressStandaloneHumanServiceCard = shouldDeferHumanServiceCardToJoinQuickCheck(input.context);
51073
51480
  if (serviceStatus.kind === "ready") return { status: "continue" };
51074
51481
  if (input.requirement.mode === "status-only") {
51075
- if (!shouldSuppressStandaloneHumanServiceCard) emitServiceStatus({
51482
+ emitServiceStatus({
51076
51483
  context: input.context,
51077
51484
  serviceStatus,
51078
51485
  blocking: false
@@ -51080,22 +51487,21 @@ async function runServiceCheck(input) {
51080
51487
  return { status: "continue" };
51081
51488
  }
51082
51489
  if (input.context.runtime.resolvedView !== "human" || !input.context.allowPrompt) {
51083
- if (!shouldSuppressStandaloneHumanServiceCard) emitServiceStatus({
51490
+ emitServiceStatus({
51084
51491
  context: input.context,
51085
51492
  serviceStatus,
51086
51493
  blocking: false
51087
51494
  });
51088
51495
  return { status: "continue" };
51089
51496
  }
51090
- if (serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") {
51091
- if (!shouldSuppressStandaloneHumanServiceCard) emitServiceStatus({
51497
+ if (serviceStatus.kind === "needs_repair" || serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") {
51498
+ emitServiceStatus({
51092
51499
  context: input.context,
51093
51500
  serviceStatus,
51094
51501
  blocking: false
51095
51502
  });
51096
51503
  return { status: "continue" };
51097
51504
  }
51098
- if (shouldSuppressStandaloneHumanServiceCard) return { status: "continue" };
51099
51505
  const copy = getCommandEntryCopy(input.context.entryName);
51100
51506
  showHumanCheckCard({
51101
51507
  presenter: input.context.presenter,
@@ -51127,15 +51533,27 @@ async function runServiceCheck(input) {
51127
51533
  return { status: "cancelled" };
51128
51534
  }
51129
51535
  if (selected === CONTINUE_WITHOUT_SERVICE) return { status: "continue" };
51130
- if (!await fixServiceStatus(serviceStatus)) return { status: "continue" };
51536
+ if (!await fixServiceStatus({
51537
+ gatewayUrl: input.context.gatewayUrl,
51538
+ serviceStatus
51539
+ })) return { status: "continue" };
51131
51540
  return { status: "continue" };
51132
51541
  }
51133
- function shouldDeferHumanServiceCardToJoinQuickCheck(context) {
51134
- return context.entryName === "space.join" && context.runtime.resolvedView === "human";
51135
- }
51136
51542
  async function resolveServiceStatus(input) {
51137
51543
  const expectedVersion = resolveCurrentDaemonExpectedVersion();
51138
- const currentStatus = await readCurrentServiceStatus(expectedVersion);
51544
+ const currentStatus = await resolveDaemonServiceParticipationStatus({ expectedVersion }).catch((error) => {
51545
+ warnServiceCheckIssue("resolveDaemonServiceParticipationStatus failed", {
51546
+ error,
51547
+ expectedVersion,
51548
+ view: input.view
51549
+ });
51550
+ return {
51551
+ kind: "not_running",
51552
+ expectedVersion,
51553
+ installedVersion: null,
51554
+ runtimeStatus: null
51555
+ };
51556
+ });
51139
51557
  if (currentStatus.kind === "ready") return currentStatus;
51140
51558
  if (input.shouldAutoRepair) {
51141
51559
  const preflight = await runDaemonStartupPreflight({
@@ -51158,38 +51576,30 @@ async function resolveServiceStatus(input) {
51158
51576
  detail: buildStartupPreflightAttentionDetail(preflight)
51159
51577
  };
51160
51578
  }
51161
- return await readCurrentServiceStatus(expectedVersion);
51162
- }
51163
- async function readCurrentServiceStatus(expectedVersion) {
51164
- const daemonStatus = await getDaemonStatus();
51165
- if (!(daemonStatus.installed && daemonStatus.state)) return { kind: "missing" };
51166
- if (daemonStatus.state.daemonVersion !== expectedVersion) return {
51167
- kind: "outdated",
51168
- installedVersion: daemonStatus.state.daemonVersion
51169
- };
51170
- const runtime = await readCurrentDaemonRuntimeContext({ status: daemonStatus }).catch((error) => {
51171
- warnServiceCheckIssue("readCurrentDaemonRuntimeContext failed", {
51172
- daemonStatus,
51173
- error
51579
+ return await resolveDaemonServiceParticipationStatus({ expectedVersion }).catch((error) => {
51580
+ warnServiceCheckIssue("resolveDaemonServiceParticipationStatus failed", {
51581
+ error,
51582
+ expectedVersion,
51583
+ view: input.view
51174
51584
  });
51175
- return null;
51585
+ return {
51586
+ kind: "not_running",
51587
+ expectedVersion,
51588
+ installedVersion: null,
51589
+ runtimeStatus: null
51590
+ };
51176
51591
  });
51177
- if (!runtime || runtime.effectiveRuntimeStatus !== "running") return {
51178
- kind: "not_running",
51179
- runtimeStatus: runtime?.runtimeStatus ?? null
51180
- };
51181
- return { kind: "ready" };
51182
51592
  }
51183
- async function fixServiceStatus(serviceStatus) {
51593
+ async function fixServiceStatus(input) {
51184
51594
  const expectedVersion = resolveCurrentDaemonExpectedVersion();
51185
- if (serviceStatus.kind === "missing") {
51595
+ if (input.serviceStatus.kind === "missing") {
51186
51596
  await ensureDaemonInstalledForCliStartup({
51187
51597
  daemonVersion: expectedVersion,
51188
51598
  mode: "always"
51189
51599
  });
51190
51600
  return true;
51191
51601
  }
51192
- if (serviceStatus.kind === "outdated") {
51602
+ if (input.serviceStatus.kind === "outdated") {
51193
51603
  const daemonStatus = await getDaemonStatus();
51194
51604
  await ensureDaemonVersionForCliStartup({
51195
51605
  daemonVersion: expectedVersion,
@@ -51198,8 +51608,15 @@ async function fixServiceStatus(serviceStatus) {
51198
51608
  });
51199
51609
  return true;
51200
51610
  }
51201
- if (serviceStatus.kind === "not_running") return await runDaemonBackgroundStartForDecision({ view: "human" }) === "started";
51202
- if (serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") {
51611
+ if (input.serviceStatus.kind === "not_running") return await runDaemonBackgroundStartForDecision({
51612
+ gatewayUrl: input.gatewayUrl ?? void 0,
51613
+ view: "human"
51614
+ }) === "started";
51615
+ if (input.serviceStatus.kind === "needs_repair") return await runDaemonBackgroundStartForDecision({
51616
+ gatewayUrl: input.gatewayUrl ?? void 0,
51617
+ view: "human"
51618
+ }) === "started";
51619
+ if (input.serviceStatus.kind === "repair_blocked" || input.serviceStatus.kind === "repair_failed") {
51203
51620
  await runDaemonStatusSafely();
51204
51621
  return false;
51205
51622
  }
@@ -51248,6 +51665,7 @@ function emitServiceStatus(input) {
51248
51665
  function buildServiceStatusText(serviceStatus) {
51249
51666
  if (serviceStatus.kind === "missing") return "ATS Service is not installed on this device.";
51250
51667
  if (serviceStatus.kind === "outdated") return `ATS Service is on ${serviceStatus.installedVersion}. ${resolveCurrentDaemonExpectedVersion()} is needed.`;
51668
+ if (serviceStatus.kind === "needs_repair") return "ATS Service needs cleanup before local agents on this device can reply from Space.";
51251
51669
  if (serviceStatus.kind === "repair_blocked") return "ATS Service needs manual repair before ATS can align it automatically.";
51252
51670
  if (serviceStatus.kind === "repair_failed") return "ATS tried to repair ATS Service automatically, but the service still needs attention.";
51253
51671
  return "ATS Service is installed but not running.";
@@ -51255,17 +51673,19 @@ function buildServiceStatusText(serviceStatus) {
51255
51673
  function buildServiceNextText(serviceStatus, action) {
51256
51674
  if (serviceStatus.kind === "missing") return action ? `Install ATS Service now, or continue ${action} without it.` : "Install ATS Service now, or continue without it.";
51257
51675
  if (serviceStatus.kind === "outdated") return "Update ATS Service now to match the current ATS CLI.";
51676
+ if (serviceStatus.kind === "needs_repair") return "Repair ATS Service now so local agents on this device can reply from Space again.";
51258
51677
  if (serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") return "Run `ats service status` first. If ATS still says the service needs repair, run `ats service reinstall`.";
51259
51678
  return action ? `Start ATS Service now, or continue ${action} without it.` : "Start ATS Service now, or continue without it.";
51260
51679
  }
51261
51680
  function buildServiceFixLabel(serviceStatus) {
51262
51681
  if (serviceStatus.kind === "missing") return "Install ATS Service now (Recommended)";
51263
51682
  if (serviceStatus.kind === "outdated") return "Update ATS Service now (Recommended)";
51683
+ if (serviceStatus.kind === "needs_repair") return "Repair ATS Service now (Recommended)";
51264
51684
  if (serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") return "Open ATS Service diagnostics (Recommended)";
51265
51685
  return "Start ATS Service now (Recommended)";
51266
51686
  }
51267
51687
  function buildServiceAgentNextSteps(serviceStatus) {
51268
- if (serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") return formatAtsCliCommands(["ats service status", "ats service reinstall"]);
51688
+ if (serviceStatus.kind === "needs_repair" || serviceStatus.kind === "repair_blocked" || serviceStatus.kind === "repair_failed") return formatAtsCliCommands(["ats service status", "ats service reinstall"]);
51269
51689
  return formatAtsCliCommands(["ats service install", "ats service run"]);
51270
51690
  }
51271
51691
  function buildStartupPreflightAttentionDetail(preflight) {
@@ -54683,6 +55103,7 @@ async function runSkillsCheck(input) {
54683
55103
  //#endregion
54684
55104
  //#region src/command-entry-checks/run-command-entry-flow.ts
54685
55105
  const CHECK_RUNNERS = {
55106
+ disclaimer: runDisclaimerCheck,
54686
55107
  cliUpdate: runCliUpdateCheck,
54687
55108
  signIn: runSignInCheck,
54688
55109
  selectedProfile: runSelectedProfileCheck,
@@ -54697,6 +55118,7 @@ const CHECK_STAGES = [
54697
55118
  "status-only"
54698
55119
  ];
54699
55120
  const CHECK_ORDER = [
55121
+ "disclaimer",
54700
55122
  "cliUpdate",
54701
55123
  "localRuntimeState",
54702
55124
  "signIn",
@@ -60578,6 +61000,7 @@ function buildServiceReplyReadinessGuidance(input) {
60578
61000
  }
60579
61001
  function buildReplyReadinessGuidanceSummary(reasonCode) {
60580
61002
  switch (reasonCode) {
61003
+ case "local_agents.none_needed": return "This device does not need ATS Service until you choose local agents to bring into a space.";
60581
61004
  case "profile.inactive": return "Choose an active agent profile first.";
60582
61005
  case "profile.unbound": return "This profile is not connected to an agent yet.";
60583
61006
  case "controller.binding.disabled": return "This profile's agent is turned off.";
@@ -60606,8 +61029,15 @@ function buildReplyReadinessNextSteps(input) {
60606
61029
  controllerRef
60607
61030
  }) ?? controllerRef ?? "<agent-id>";
60608
61031
  switch (input.reasonCode) {
61032
+ case "local_agents.none_needed": return [
61033
+ formatAtsCliCommand("ats start"),
61034
+ formatAtsCliCommand("ats service install"),
61035
+ formatAtsCliCommand("ats service run")
61036
+ ];
60609
61037
  case "profile.inactive": return [formatAtsCliCommand("ats profile list"), formatAtsCliCommand("ats profile set <agent-profile-id>")];
60610
- case "profile.unbound": return [formatAtsCliCommand(`ats profile update ${profileId} --controller-kind builtin --controller-ref ${controllerRef ?? "<controller-ref>"} --controller-enabled true`), formatAtsCliCommand(`ats space join <space-id> --profile ${profileId}`)];
61038
+ case "profile.unbound":
61039
+ if (controllerRef) return [formatAtsCliCommand(`ats profile update ${profileId} --controller-kind builtin --controller-ref ${controllerRef} --controller-enabled true`), formatAtsCliCommand(`ats space join <space-id> --profile ${profileId}`)];
61040
+ return [formatAtsCliCommand("ats start"), formatAtsCliCommand("ats agents list")];
60611
61041
  case "controller.binding.disabled": return [formatAtsCliCommand(`ats profile update ${profileId} --controller-enabled true`)];
60612
61042
  case "projection.missing": return [formatAtsCliCommand("ats doctor --repair")];
60613
61043
  case "controller.registry.disabled": return [formatAtsCliCommand(`ats agents enable --agent ${agentCommandId}`), formatAtsCliCommand("ats doctor --repair")];
@@ -62628,19 +63058,6 @@ function normalizeOptionalString$5(value) {
62628
63058
 
62629
63059
  //#endregion
62630
63060
  //#region src/start-foundation/selectors.ts
62631
- const BACKGROUND_HELPER_REASON_CODES = new Set([
62632
- "background_helper.not_installed",
62633
- "background_helper.installed_outdated",
62634
- "background_helper.not_running",
62635
- "background_helper.stale",
62636
- "background_helper.unknown"
62637
- ]);
62638
- function isCliStartBackgroundHelperReasonCode(reasonCode) {
62639
- return BACKGROUND_HELPER_REASON_CODES.has(reasonCode);
62640
- }
62641
- function isCliStartBackgroundHelperSignal(signal) {
62642
- return signal.actionId === "device_check" && isCliStartBackgroundHelperReasonCode(signal.reasonCode);
62643
- }
62644
63061
  function findCliStartSelectedProfileReadinessEntry(input) {
62645
63062
  return input.foundation.foundationState.readModel.profileReadiness.find((entry) => entry.profileId === input.profileId) ?? null;
62646
63063
  }
@@ -62650,8 +63067,13 @@ function readCliStartDeviceProjection(foundation) {
62650
63067
  function isCliStartNextActionBlocked(foundation) {
62651
63068
  return foundation.foundationState.decision.status === "blocking";
62652
63069
  }
62653
- function hasCliStartBackgroundHelperBlockingOrAttentionSignal(foundation) {
62654
- return [...foundation.foundationState.decision.blocking, ...foundation.foundationState.decision.attention].some(isCliStartBackgroundHelperSignal);
63070
+ function hasCliStartSelectedLocalAgents(foundation) {
63071
+ return foundation.foundationState.readModel.session.value?.agentSelection.mode === "selected_agents";
63072
+ }
63073
+ function readCliStartSelectedLocalAgentIds(foundation) {
63074
+ const agentSelection = foundation.foundationState.readModel.session.value?.agentSelection;
63075
+ if (!agentSelection || agentSelection.mode !== "selected_agents") return [];
63076
+ return agentSelection.selectedAgentIds;
62655
63077
  }
62656
63078
 
62657
63079
  //#endregion
@@ -62945,12 +63367,6 @@ function collectStartLocalBackgroundHelperReadiness() {
62945
63367
  async function resolveStartLocalBackgroundHelperDemand(input) {
62946
63368
  return await resolveDaemonLocalServiceDemand(input);
62947
63369
  }
62948
- async function runStartLocalBackgroundHelperInstall(input) {
62949
- await runDaemonInstall({ view: input.view });
62950
- }
62951
- async function runStartLocalBackgroundHelperBackgroundStart(input) {
62952
- return await runDaemonBackgroundStartForDecision({ view: input.view });
62953
- }
62954
63370
 
62955
63371
  //#endregion
62956
63372
  //#region src/start-local/skills-local.ts
@@ -62993,49 +63409,13 @@ async function collectStartLocalReadiness(input) {
62993
63409
 
62994
63410
  //#endregion
62995
63411
  //#region src/commands/start-auth-and-readiness.ts
62996
- const START_DISCLAIMER_ACCEPT = "__start_disclaimer_accept__";
62997
- const START_DISCLAIMER_DECLINE = "__start_disclaimer_decline__";
62998
- const START_V1_DISCLAIMER_VERSION = "start-v1-2026-03-02";
62999
- const START_V1_DISCLAIMER_TEXT = "ATSD is experimental and changes rapidly. Breaking changes may occur. You are fully responsible for data security and sensitive information handling. Use ATSD only in trusted environments.";
63000
63412
  async function runStartDisclaimerStep(input) {
63001
- if ((await getConfiguredStartV1OnboardingState().catch(() => null))?.disclaimerAcceptedVersion === START_V1_DISCLAIMER_VERSION) return true;
63002
- if (input.runtime.resolvedView === "agent") {
63003
- input.presenter.data({
63004
- code: "start.disclaimer",
63005
- payload: {
63006
- version: START_V1_DISCLAIMER_VERSION,
63007
- text: START_V1_DISCLAIMER_TEXT,
63008
- mode: "agent_notice",
63009
- requiresConfirmation: false
63010
- }
63011
- });
63012
- return true;
63013
- }
63014
- note(`⚠️ ${START_V1_DISCLAIMER_TEXT}`, "Disclaimer");
63015
- if (!input.interactive) {
63016
- input.presenter.line({
63017
- code: "start.disclaimer.auto_continue",
63018
- text: "Non-interactive session: disclaimer displayed and onboarding continues."
63019
- });
63020
- return true;
63021
- }
63022
- const decision = await select({
63023
- message: "Do you accept and continue?",
63024
- options: [{
63025
- value: START_DISCLAIMER_ACCEPT,
63026
- label: "Yes, continue"
63027
- }, {
63028
- value: START_DISCLAIMER_DECLINE,
63029
- label: "No, exit"
63030
- }],
63031
- initialValue: START_DISCLAIMER_ACCEPT
63413
+ return await runOnboardingDisclaimerGate({
63414
+ runtime: input.runtime,
63415
+ presenter: input.presenter,
63416
+ interactive: input.interactive,
63417
+ codePrefix: "start.disclaimer"
63032
63418
  });
63033
- if (isCancel(decision) || decision === START_DISCLAIMER_DECLINE) {
63034
- cancel("Cancelled.");
63035
- return false;
63036
- }
63037
- await persistStartDisclaimerAccepted();
63038
- return true;
63039
63419
  }
63040
63420
  async function markStartV1Completed() {
63041
63421
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -63122,12 +63502,6 @@ function formatReadinessLine(input) {
63122
63502
  if (!input.detail) return line;
63123
63503
  return `${line} ${pc.dim(input.detail)}`;
63124
63504
  }
63125
- async function persistStartDisclaimerAccepted() {
63126
- await updateConfiguredStartV1OnboardingState((current) => ({
63127
- ...current ?? {},
63128
- disclaimerAcceptedVersion: START_V1_DISCLAIMER_VERSION
63129
- }));
63130
- }
63131
63505
  function evaluateStartAuthentication(input) {
63132
63506
  return evaluateCommandAuthentication({ gatewayUrl: input.gatewayUrl });
63133
63507
  }
@@ -63932,41 +64306,20 @@ async function runStartSkillsStep(input) {
63932
64306
  }
63933
64307
  }
63934
64308
  async function runStartAgentsStep(input) {
63935
- const builtinCandidates = (await listStartLocalBuiltinAgentCandidates()).sort(compareStartAgentCandidates);
63936
- const selectableCandidates = builtinCandidates.filter((candidate) => candidate.selectable);
63937
- const payload = buildStartAgentsPayload({
63938
- builtinCandidates,
63939
- selectableCandidates
63940
- });
63941
- const onboardingState = await getConfiguredStartV1OnboardingState().catch(() => null);
63942
- const preconfiguredAgentSelection = resolvePreconfiguredStartAgentSelection({
63943
- builtinCandidates,
63944
- localAgentsSetupCompleted: typeof onboardingState?.localAgentsSetupCompletedAt === "string" && onboardingState.localAgentsSetupCompletedAt.length > 0,
63945
- selectableCandidates
63946
- });
64309
+ const agentSelectionState = await resolveStartAgentSelectionState();
63947
64310
  if (input.runtime.resolvedView === "agent") {
63948
64311
  input.presenter.data({
63949
64312
  code: "start.agents",
63950
- payload
64313
+ payload: agentSelectionState.payload
64314
+ });
64315
+ return createStartAgentsStepResult(agentSelectionState.preconfiguredAgentSelection ?? {
64316
+ mode: "unresolved",
64317
+ selectedAgentIds: []
63951
64318
  });
63952
- if (preconfiguredAgentSelection) return {
63953
- agentSelection: preconfiguredAgentSelection,
63954
- allowBackFromProfile: false
63955
- };
63956
- return {
63957
- agentSelection: {
63958
- mode: "unresolved",
63959
- selectedAgentIds: []
63960
- },
63961
- allowBackFromProfile: false
63962
- };
63963
64319
  }
63964
- if (preconfiguredAgentSelection) return {
63965
- agentSelection: preconfiguredAgentSelection,
63966
- allowBackFromProfile: false
63967
- };
64320
+ if (agentSelectionState.preconfiguredAgentSelection) return createStartAgentsStepResult(agentSelectionState.preconfiguredAgentSelection);
63968
64321
  if (!input.interactive) {
63969
- if (selectableCandidates.length > 0) input.presenter.line({
64322
+ if (agentSelectionState.selectableCandidates.length > 0) input.presenter.line({
63970
64323
  code: "start.agents.non_interactive",
63971
64324
  text: formatInlineAtsCliCommands("⚠️ Agent selection still needs a decision. Run `ats start` interactively or use `ats agents enable --all`, `ats agents enable --agent <id...>`, or `ats agents enable --agent <id> --install <install-id>` before continuing.")
63972
64325
  });
@@ -63978,69 +64331,135 @@ async function runStartAgentsStep(input) {
63978
64331
  allowBackFromProfile: false
63979
64332
  };
63980
64333
  }
64334
+ return await runInteractiveStartAgentsSelectionLoop({
64335
+ builtinCandidates: agentSelectionState.builtinCandidates,
64336
+ profile: input.profile,
64337
+ presenter: input.presenter,
64338
+ view: input.view
64339
+ });
64340
+ }
64341
+ async function resolveStartAgentSelectionState() {
64342
+ const builtinCandidates = (await listStartLocalBuiltinAgentCandidates()).sort(compareStartAgentCandidates);
64343
+ const selectableCandidates = builtinCandidates.filter((candidate) => candidate.selectable);
64344
+ const onboardingState = await getConfiguredStartV1OnboardingState().catch(() => null);
64345
+ const localAgentsSetupCompleted = typeof onboardingState?.localAgentsSetupCompletedAt === "string" && onboardingState.localAgentsSetupCompletedAt.length > 0;
64346
+ return {
64347
+ builtinCandidates,
64348
+ selectableCandidates,
64349
+ payload: buildStartAgentsPayload({
64350
+ builtinCandidates,
64351
+ selectableCandidates
64352
+ }),
64353
+ preconfiguredAgentSelection: resolvePreconfiguredStartAgentSelection({
64354
+ builtinCandidates,
64355
+ localAgentsSetupCompleted,
64356
+ selectableCandidates
64357
+ })
64358
+ };
64359
+ }
64360
+ function createStartAgentsStepResult(agentSelection, allowBackFromProfile = false) {
64361
+ return {
64362
+ agentSelection,
64363
+ allowBackFromProfile
64364
+ };
64365
+ }
64366
+ async function runInteractiveStartAgentsSelectionLoop(input) {
63981
64367
  for (;;) {
63982
64368
  const localAgentDecision = await promptStartLocalAgentDecision();
63983
64369
  if (localAgentDecision === "cancelled" || localAgentDecision === "exit") {
63984
64370
  cancel("Cancelled.");
63985
64371
  return "cancelled";
63986
64372
  }
63987
- if (localAgentDecision === "no_agents") {
63988
- await syncStartLocalAgentSelection({
63989
- builtinCandidates,
63990
- selectedAgentIds: [],
63991
- profile: input.profile,
63992
- view: input.view
64373
+ if (localAgentDecision === "no_agents") return await completeStartLocalAgentSelection({
64374
+ builtinCandidates: input.builtinCandidates,
64375
+ profile: input.profile,
64376
+ presenter: input.presenter,
64377
+ selectedAgentIds: [],
64378
+ view: input.view
64379
+ });
64380
+ const repairedBuiltinCandidates = await maybeRepairStartLocalAgentsBeforeSelection({
64381
+ builtinCandidates: input.builtinCandidates,
64382
+ profile: input.profile,
64383
+ view: input.view
64384
+ });
64385
+ if (repairedBuiltinCandidates === "cancelled") {
64386
+ cancel("Cancelled.");
64387
+ return "cancelled";
64388
+ }
64389
+ if (repairedBuiltinCandidates.filter((candidate) => candidate.selectable).length === 0) {
64390
+ input.presenter.line({
64391
+ code: "start.agents.none_ready_after_repair",
64392
+ text: formatInlineAtsCliCommands("No local agents are ready to use on this device yet. Repair one with `ats agents repair --agent <id>` or keep local agents off for now.")
63993
64393
  });
63994
- await persistStartLocalAgentsSetupCompleted();
63995
- emitStartLocalAgentsSetupSaved(input.presenter);
63996
- return {
63997
- agentSelection: {
63998
- mode: "no_agents",
63999
- selectedAgentIds: []
64000
- },
64001
- allowBackFromProfile: true
64002
- };
64394
+ continue;
64003
64395
  }
64004
- const selectedAgentIds = await promptStartAgentSelection(builtinCandidates);
64396
+ const selectedAgentIds = await promptStartAgentSelection(repairedBuiltinCandidates);
64005
64397
  if (selectedAgentIds === "cancelled") {
64006
64398
  cancel("Cancelled.");
64007
64399
  return "cancelled";
64008
64400
  }
64009
64401
  if (selectedAgentIds === "back") continue;
64010
- if (selectedAgentIds.length === 0) {
64011
- await syncStartLocalAgentSelection({
64012
- builtinCandidates,
64013
- selectedAgentIds,
64014
- profile: input.profile,
64015
- view: input.view
64016
- });
64017
- await persistStartLocalAgentsSetupCompleted();
64018
- emitStartLocalAgentsSetupSaved(input.presenter);
64019
- return {
64020
- agentSelection: {
64021
- mode: "no_agents",
64022
- selectedAgentIds: []
64023
- },
64024
- allowBackFromProfile: true
64025
- };
64026
- }
64027
- await syncStartLocalAgentSelection({
64028
- builtinCandidates,
64029
- selectedAgentIds,
64402
+ if (selectedAgentIds.length === 0) return await completeStartLocalAgentSelection({
64403
+ builtinCandidates: input.builtinCandidates,
64030
64404
  profile: input.profile,
64405
+ presenter: input.presenter,
64406
+ selectedAgentIds: [],
64407
+ view: input.view
64408
+ });
64409
+ return await completeStartLocalAgentSelection({
64410
+ builtinCandidates: repairedBuiltinCandidates,
64411
+ profile: input.profile,
64412
+ presenter: input.presenter,
64413
+ selectedAgentIds,
64031
64414
  view: input.view
64032
64415
  });
64033
- await persistStartLocalAgentsSetupCompleted();
64034
- emitStartLocalAgentsSetupSaved(input.presenter);
64035
- return {
64036
- agentSelection: {
64037
- mode: "selected_agents",
64038
- selectedAgentIds
64039
- },
64040
- allowBackFromProfile: true
64041
- };
64042
64416
  }
64043
64417
  }
64418
+ async function completeStartLocalAgentSelection(input) {
64419
+ await syncStartLocalAgentSelection({
64420
+ builtinCandidates: input.builtinCandidates,
64421
+ selectedAgentIds: input.selectedAgentIds,
64422
+ profile: input.profile,
64423
+ view: input.view
64424
+ });
64425
+ await persistStartLocalAgentsSetupCompleted();
64426
+ emitStartLocalAgentsSetupSaved(input.presenter);
64427
+ return createStartAgentsStepResult(input.selectedAgentIds.length === 0 ? {
64428
+ mode: "no_agents",
64429
+ selectedAgentIds: []
64430
+ } : {
64431
+ mode: "selected_agents",
64432
+ selectedAgentIds: [...input.selectedAgentIds]
64433
+ }, true);
64434
+ }
64435
+ async function maybeRepairStartLocalAgentsBeforeSelection(input) {
64436
+ const repairableCandidates = input.builtinCandidates.filter((candidate) => candidate.detected && !candidate.selectable && candidate.selectableReason === "needs_repair");
64437
+ if (repairableCandidates.length === 0) return input.builtinCandidates;
64438
+ const repairDecision = await select({
64439
+ message: buildStartLocalAgentRepairPromptMessage(repairableCandidates),
64440
+ initialValue: "repair_now",
64441
+ options: [{
64442
+ value: "repair_now",
64443
+ label: "Repair now"
64444
+ }, {
64445
+ value: "skip_for_now",
64446
+ label: "Skip for now"
64447
+ }]
64448
+ });
64449
+ if (isCancel(repairDecision)) return "cancelled";
64450
+ if (repairDecision !== "repair_now") return input.builtinCandidates;
64451
+ await runAgentsRepair({
64452
+ agent: repairableCandidates.map((candidate) => candidate.agentId),
64453
+ profile: input.profile,
64454
+ view: input.view
64455
+ });
64456
+ return await listStartLocalBuiltinAgentCandidates();
64457
+ }
64458
+ function buildStartLocalAgentRepairPromptMessage(candidates) {
64459
+ const displayNames = candidates.map((candidate) => candidate.displayName);
64460
+ if (displayNames.length === 1) return `${displayNames[0]} needs repair on this device before ATS can use it. Repair it now?`;
64461
+ return `${displayNames.join(", ")} need repair on this device before ATS can use them. Repair them now?`;
64462
+ }
64044
64463
  function buildStartAgentsPayload(input) {
64045
64464
  return {
64046
64465
  candidates: input.builtinCandidates.map((candidate) => ({
@@ -64072,11 +64491,17 @@ function resolveSavedStartAgentSelection(selectableCandidates) {
64072
64491
  };
64073
64492
  }
64074
64493
  function resolvePreconfiguredStartAgentSelection(input) {
64075
- if (input.builtinCandidates.length === 0 || input.selectableCandidates.length === 0) return {
64494
+ const hasRepairableCandidates = input.builtinCandidates.some((candidate) => candidate.detected && !candidate.selectable && candidate.selectableReason === "needs_repair");
64495
+ if (input.builtinCandidates.length === 0) return {
64076
64496
  mode: "no_agents",
64077
64497
  selectedAgentIds: []
64078
64498
  };
64499
+ if (input.selectableCandidates.length === 0 && hasRepairableCandidates) return null;
64079
64500
  if (!input.localAgentsSetupCompleted) return null;
64501
+ if (input.selectableCandidates.length === 0) return {
64502
+ mode: "no_agents",
64503
+ selectedAgentIds: []
64504
+ };
64080
64505
  return resolveSavedStartAgentSelection([...input.selectableCandidates]);
64081
64506
  }
64082
64507
  async function persistStartLocalAgentsSetupCompleted() {
@@ -64292,10 +64717,14 @@ function resolveStartBackgroundHelperShellState(input) {
64292
64717
  runtimeStatus: input.readiness.service.runtime?.status ?? "unknown"
64293
64718
  };
64294
64719
  }
64295
- function resolveStartServiceRecommendation(input) {
64296
- if (input.backgroundHelper.status === "installed_outdated") return "recommended";
64297
- if (input.backgroundHelper.status === "installed_current") return input.localDemand?.decision === "keep_running" ? "recommended" : "optional";
64298
- return hasCliStartBackgroundHelperBlockingOrAttentionSignal(input.foundation) ? "recommended" : "optional";
64720
+ function haveRunnableSelectedLocalAgentSetup(input) {
64721
+ const selectedAgentIds = readCliStartSelectedLocalAgentIds(input.foundation);
64722
+ if (selectedAgentIds.length === 0) return false;
64723
+ const candidateById = new Map(input.readiness.agents.candidates.map((candidate) => [candidate.agentId, candidate]));
64724
+ return selectedAgentIds.every((selectedAgentId) => {
64725
+ const candidate = candidateById.get(selectedAgentId);
64726
+ return candidate?.enabled === true && candidate.selectable === true;
64727
+ });
64299
64728
  }
64300
64729
  function emitStartServiceNonInteractiveLine(input) {
64301
64730
  const text = input.backgroundHelper.status === "installed_current" ? formatInlineAtsCliCommands("⚠️ ATS Service: Installed and up to date, but not currently running. Start it manually with `ats service run`.") : formatInlineAtsCliCommands(input.backgroundHelper.status === "not_installed" ? `⚠️ ATS Service: ${formatDaemonServiceMissingStatus()} ${formatDaemonServiceMissingAction()}` : `⚠️ ATS Service: ${formatDaemonServiceOutdatedStatus({
@@ -64307,78 +64736,47 @@ function emitStartServiceNonInteractiveLine(input) {
64307
64736
  text
64308
64737
  });
64309
64738
  }
64310
- function emitStartServiceSkippedLine(input) {
64311
- const text = resolveStartServiceSkippedText(input);
64312
- input.presenter.line({
64313
- code: "start.service.skipped",
64314
- text: formatInlineAtsCliCommands(text)
64739
+
64740
+ //#endregion
64741
+ //#region src/system/service-participation-policy.ts
64742
+ function resolveCliServiceParticipationRequirement(input) {
64743
+ return resolveServiceParticipationRequirement({
64744
+ intent: input.intent,
64745
+ humanOnlyParticipation: input.humanOnlyParticipation,
64746
+ explicitSelectedLocalAgents: input.explicitSelectedLocalAgents,
64747
+ selectedAgentProfilesHaveRunnableLocalSetup: input.selectedAgentProfilesHaveRunnableLocalSetup,
64748
+ deviceLocalDemand: mapDaemonLocalServiceDemandToSharedValue(input.localDemand)
64315
64749
  });
64316
64750
  }
64317
- function renderStartServiceDeferredCard(input) {
64318
- const card = buildDaemonStartDeferredCard();
64319
- renderInfoCard({
64320
- presenter: input.presenter,
64321
- title: card.title,
64322
- codePrefix: "start.service.deferred",
64323
- rows: card.rows
64324
- });
64751
+ function isExplicitLocalAgentProfile(profile) {
64752
+ return profile.profileKind === "agent";
64325
64753
  }
64326
- function renderStartServiceFailureCard(input) {
64327
- const card = buildDaemonStartNeedsAttentionCard({ summary: "ATS could not finish starting ATS Service." });
64328
- renderInfoCard({
64329
- presenter: input.presenter,
64330
- title: card.title,
64331
- codePrefix: "start.service.failed",
64332
- rows: card.rows
64754
+ function hasRunnableLocalAgentSetup(profile) {
64755
+ return hasRunnableLocalAgentParticipationCandidate({
64756
+ profileKind: profile.profileKind === "agent" ? "agent" : "human",
64757
+ controllerRef: profile.controllerBinding?.controllerRef,
64758
+ controllerEnabled: profile.controllerBinding?.controllerEnabled
64333
64759
  });
64334
64760
  }
64335
- async function promptForStartServiceInstallDecision(input) {
64336
- const actionLabel = input.kind === "outdated" ? "Update ATS Service now" : "Install ATS Service now";
64337
- const statusLine = input.kind === "outdated" ? formatDaemonServiceOutdatedStatus({
64338
- installedVersion: input.installedVersion,
64339
- expectedVersion: input.expectedVersion
64340
- }) : formatDaemonServiceMissingStatus();
64341
- const explanation = input.recommendation === "recommended" ? "ATS Service lets local agents on this device reply from Space, so we recommend it for this setup." : "You can keep going without ATS Service, but local agents on this device won't be able to reply from Space.";
64342
- const detailLine = input.kind === "outdated" && input.installedVersion ? `Installed v${input.installedVersion}, expected v${input.expectedVersion}.` : null;
64343
- const decision = await select({
64344
- message: [
64345
- input.kind === "outdated" ? "ATS Service needs an update. Fix it now?" : "ATS Service is missing. Install it now?",
64346
- explanation,
64347
- `Current status: ${statusLine}`,
64348
- detailLine
64349
- ].filter((line) => typeof line === "string" && line.length > 0).join("\n"),
64350
- initialValue: "fix_now",
64351
- options: [{
64352
- value: "fix_now",
64353
- label: actionLabel,
64354
- hint: input.recommendation === "recommended" ? "Recommended" : "Optional"
64355
- }, {
64356
- value: "not_now",
64357
- label: "Not now"
64358
- }]
64359
- });
64360
- if (isCancel(decision)) return "cancelled";
64361
- return decision === "fix_now" ? "fix_now" : "not_now";
64761
+ function hasAnyExplicitLocalAgentCandidates(candidates) {
64762
+ return candidates.some((candidate) => candidate.profileKind === "agent");
64362
64763
  }
64363
- function resolveStartServiceSkippedText(input) {
64364
- if (input.outcome === "failed_install") return input.backgroundHelper.status === "installed_outdated" ? "ATS Service was not updated. Local agents on this device may not be able to reply from Space. Run `ats service install`." : "ATS Service could not be installed. Local agents on this device still can't reply from Space. Run `ats service install`.";
64365
- return input.backgroundHelper.status === "installed_outdated" ? "ATS Service still needs an update. Local agents on this device may not be able to reply from Space until ATS fixes it. Run `ats service install` later." : "ATS Service is still missing. Local agents on this device can't reply from Space until you install it later with `ats service install`.";
64764
+ function haveAllExplicitLocalAgentCandidatesRunnable(candidates) {
64765
+ const agentCandidates = candidates.filter((candidate) => candidate.profileKind === "agent");
64766
+ if (agentCandidates.length === 0) return false;
64767
+ return agentCandidates.every((candidate) => hasRunnableLocalAgentParticipationCandidate(candidate));
64366
64768
  }
64367
- async function promptForStartServiceStartDecision(input) {
64368
- const decision = await select({
64369
- message: ["ATS Service is not running. Start it now?", input.recommendation === "recommended" ? "ATS Service lets local agents on this device reply from Space, so we recommend it for this setup." : "You can keep going without ATS Service, but local agents on this device won't be able to reply from Space."].join("\n"),
64370
- initialValue: "fix_now",
64371
- options: [{
64372
- value: "fix_now",
64373
- label: "Start ATS Service now",
64374
- hint: input.recommendation === "recommended" ? "Recommended" : "Optional"
64375
- }, {
64376
- value: "not_now",
64377
- label: "Not now"
64378
- }]
64379
- });
64380
- if (isCancel(decision)) return "cancelled";
64381
- return decision === "fix_now" ? "fix_now" : "not_now";
64769
+ function hasRunnableLocalAgentParticipationCandidate(candidate) {
64770
+ if (candidate.profileKind !== "agent") return false;
64771
+ return candidate.controllerEnabled === true && typeof candidate.controllerRef === "string" && candidate.controllerRef.trim().length > 0;
64772
+ }
64773
+ function mapDaemonLocalServiceDemandToSharedValue(localDemand) {
64774
+ switch (localDemand?.decision) {
64775
+ case "keep_running":
64776
+ case "allow_auto_stop":
64777
+ case "indeterminate": return localDemand.decision;
64778
+ default: return "indeterminate";
64779
+ }
64382
64780
  }
64383
64781
 
64384
64782
  //#endregion
@@ -64388,6 +64786,7 @@ async function runStartServiceStep(input) {
64388
64786
  foundation: input.foundation,
64389
64787
  readiness: input.readiness
64390
64788
  });
64789
+ const selectedLocalAgentsRequireService = hasCliStartSelectedLocalAgents(input.foundation);
64391
64790
  const payload = buildStartServicePayload({
64392
64791
  readiness: input.readiness,
64393
64792
  backgroundHelper
@@ -64399,15 +64798,7 @@ async function runStartServiceStep(input) {
64399
64798
  });
64400
64799
  return "continue";
64401
64800
  }
64402
- if (backgroundHelper.status === "installed_current") return await runInstalledCurrentStartServiceStep({
64403
- ...input,
64404
- backgroundHelper
64405
- });
64406
- const recommendation = resolveStartServiceRecommendation({
64407
- backgroundHelper,
64408
- foundation: input.foundation,
64409
- localDemand: null
64410
- });
64801
+ if (backgroundHelper.status === "installed_current" && backgroundHelper.runtimeStatus === "running") return "continue";
64411
64802
  if (!input.interactive) {
64412
64803
  emitStartServiceNonInteractiveLine({
64413
64804
  presenter: input.presenter,
@@ -64415,78 +64806,47 @@ async function runStartServiceStep(input) {
64415
64806
  });
64416
64807
  return "continue";
64417
64808
  }
64418
- const decision = await promptForStartServiceInstallDecision({
64419
- kind: backgroundHelper.status === "installed_outdated" ? "outdated" : "missing",
64420
- installedVersion: backgroundHelper.installedVersion ?? void 0,
64421
- expectedVersion: backgroundHelper.expectedVersion,
64422
- recommendation
64809
+ const localDemand = await resolveStartLocalBackgroundHelperDemand({ ownerUserId: input.ownerUserId }).catch(() => null);
64810
+ const currentProfileIsExplicitLocalAgent = isExplicitLocalAgentProfile(input.profile);
64811
+ const selectedLocalAgentsHaveRunnableLocalSetup = !selectedLocalAgentsRequireService || haveRunnableSelectedLocalAgentSetup({
64812
+ foundation: input.foundation,
64813
+ readiness: input.readiness
64423
64814
  });
64424
- if (decision === "cancelled") {
64425
- cancel("Cancelled.");
64426
- return "cancelled";
64427
- }
64428
- if (decision === "fix_now") {
64429
- try {
64430
- await runStartLocalBackgroundHelperInstall({ view: input.view });
64431
- } catch {
64432
- emitStartServiceSkippedLine({
64433
- presenter: input.presenter,
64434
- backgroundHelper,
64435
- outcome: "failed_install"
64436
- });
64437
- return "needs_attention";
64438
- }
64439
- return "continue";
64440
- }
64441
- emitStartServiceSkippedLine({
64442
- presenter: input.presenter,
64443
- backgroundHelper,
64444
- outcome: "not_fixed"
64815
+ const currentProfileHasRunnableLocalSetup = !currentProfileIsExplicitLocalAgent || hasRunnableLocalAgentSetup(input.profile);
64816
+ const serviceRequirement = resolveCliServiceParticipationRequirement({
64817
+ intent: "start",
64818
+ humanOnlyParticipation: !(selectedLocalAgentsRequireService || currentProfileIsExplicitLocalAgent) && localDemand?.decision !== "keep_running",
64819
+ explicitSelectedLocalAgents: selectedLocalAgentsRequireService || currentProfileIsExplicitLocalAgent,
64820
+ selectedAgentProfilesHaveRunnableLocalSetup: selectedLocalAgentsHaveRunnableLocalSetup && currentProfileHasRunnableLocalSetup,
64821
+ localDemand
64445
64822
  });
64446
- return "continue";
64447
- }
64448
- async function runInstalledCurrentStartServiceStep(input) {
64449
- const localDemand = input.runtime.resolvedView === "human" && input.interactive ? await resolveStartLocalBackgroundHelperDemand({ ownerUserId: input.ownerUserId }).catch(() => null) : null;
64450
- if (input.backgroundHelper.runtimeStatus === "running") return "continue";
64451
- if (localDemand && localDemand.decision !== "keep_running") return "continue";
64452
- if (!input.interactive) {
64453
- emitStartServiceNonInteractiveLine({
64454
- presenter: input.presenter,
64455
- backgroundHelper: input.backgroundHelper
64823
+ if (serviceRequirement === "not_needed") return "continue";
64824
+ if (serviceRequirement === "agent_setup_needed") {
64825
+ input.presenter.line({
64826
+ code: "start.service.agent_setup_needed",
64827
+ text: `Finish local agent setup before you bring this profile into a space. Use \`ats profile ready --profile ${input.profile.atsProfileId}\` or rerun \`ats start\`.`
64456
64828
  });
64457
64829
  return "continue";
64458
64830
  }
64459
- return await handleInstalledCurrentStartServiceDecision({
64831
+ const gateResult = await runDaemonServiceParticipationGate({
64832
+ intent: "start",
64460
64833
  presenter: input.presenter,
64461
- recommendation: resolveStartServiceRecommendation({
64462
- backgroundHelper: input.backgroundHelper,
64463
- foundation: input.foundation,
64464
- localDemand
64465
- }),
64834
+ resolvedView: input.runtime.resolvedView,
64835
+ allowPrompt: input.interactive,
64836
+ gatewayUrl: input.gatewayUrl,
64837
+ localDemand,
64838
+ forcePrompt: selectedLocalAgentsRequireService || currentProfileIsExplicitLocalAgent,
64466
64839
  view: input.view
64467
64840
  });
64468
- }
64469
- async function handleInstalledCurrentStartServiceDecision(input) {
64470
- const decision = await promptForStartServiceStartDecision({ recommendation: input.recommendation });
64471
- if (decision === "cancelled") {
64841
+ if (gateResult.status === "cancelled") {
64472
64842
  cancel("Cancelled.");
64473
64843
  return "cancelled";
64474
64844
  }
64475
- if (decision === "not_now") {
64476
- renderStartServiceDeferredCard({ presenter: input.presenter });
64477
- return "continue";
64478
- }
64479
- const startResult = await runStartLocalBackgroundHelperBackgroundStart({ view: input.view }).catch(() => "failed");
64480
- if (startResult === "cancelled") {
64481
- cancel("Cancelled.");
64482
- return "cancelled";
64483
- }
64484
- if (startResult === "deferred_with_card") return "continue";
64485
- if (startResult === "failed") {
64486
- renderStartServiceFailureCard({ presenter: input.presenter });
64487
- return "needs_attention";
64488
- }
64489
- if (startResult === "failed_with_card") return "needs_attention";
64845
+ if (gateResult.status === "needs_attention") return "needs_attention";
64846
+ if (gateResult.status === "declined") input.presenter.line({
64847
+ code: "start.service.declined",
64848
+ text: "ATS will continue without wakeable local agent participation on this device. To enable background replies later, rerun `ats start` or use `ats service install` and `ats service run`."
64849
+ });
64490
64850
  return "continue";
64491
64851
  }
64492
64852
 
@@ -64623,6 +64983,7 @@ async function runStartHandoff(input) {
64623
64983
  const serviceStepResult = await runHandoffLocalStepWithFailedProgressSync({
64624
64984
  run: async () => await runStartServiceStep({
64625
64985
  foundation,
64986
+ gatewayUrl,
64626
64987
  runtime: input.runtime,
64627
64988
  presenter: input.presenter,
64628
64989
  interactive: true,
@@ -71960,6 +72321,11 @@ async function emitSpaceCommandPreflight(input) {
71960
72321
  },
71961
72322
  serviceRepairGuidance: resolveSpacePreflightRepairGuidance({ serviceParticipationReadiness })
71962
72323
  };
72324
+ if (!shouldEmitSpaceCommandPreflightOutput(input.emitOutput)) return {
72325
+ deviceReplyReadiness: replyReadinessSnapshot.deviceReplyReadiness,
72326
+ payload,
72327
+ serviceParticipationReadiness
72328
+ };
71963
72329
  if (input.resolvedView === "human") {
71964
72330
  const authLabel = buildHumanPreflightAuthLabel(payload.auth);
71965
72331
  const agentReplyReadinessLabel = payload.agentReplyReadiness && input.profile.profileKind === "agent" && shouldSurfaceSenderReplyReadinessInPreflightHeadline(input.command) ? formatReplyReadinessSummary(payload.agentReplyReadiness) : null;
@@ -71971,7 +72337,8 @@ async function emitSpaceCommandPreflight(input) {
71971
72337
  profileName: payload.profile.profileName,
71972
72338
  agentReplyReadinessLabel,
71973
72339
  guidance: payload.serviceRepairGuidance,
71974
- serviceParticipationReadiness
72340
+ serviceParticipationReadiness,
72341
+ humanServiceGuidanceMode: input.humanServiceGuidanceMode ?? "show"
71975
72342
  });
71976
72343
  return {
71977
72344
  deviceReplyReadiness: replyReadinessSnapshot.deviceReplyReadiness,
@@ -72029,7 +72396,8 @@ function emitHumanSpaceCommandPreflight(input) {
72029
72396
  emitSpacePreflightRepairGuidance({
72030
72397
  presenter: input.presenter,
72031
72398
  command: input.command,
72032
- guidance: input.guidance
72399
+ guidance: input.guidance,
72400
+ mode: input.humanServiceGuidanceMode
72033
72401
  });
72034
72402
  return;
72035
72403
  }
@@ -72040,7 +72408,8 @@ function emitHumanSpaceCommandPreflight(input) {
72040
72408
  emitSpacePreflightRepairGuidance({
72041
72409
  presenter: input.presenter,
72042
72410
  command: input.command,
72043
- guidance: input.guidance
72411
+ guidance: input.guidance,
72412
+ mode: input.humanServiceGuidanceMode
72044
72413
  });
72045
72414
  }
72046
72415
  function shouldSurfaceSenderReplyReadinessInPreflightHeadline(command) {
@@ -72108,7 +72477,7 @@ function toSpacePreflightAgentReplyReadiness(input) {
72108
72477
  };
72109
72478
  }
72110
72479
  function emitSpacePreflightRepairGuidance(input) {
72111
- if (!input.guidance) return;
72480
+ if (!input.guidance || input.mode === "suppress") return;
72112
72481
  input.presenter.line({
72113
72482
  code: `space.${input.command}.preflight.next_step`,
72114
72483
  text: `Next step: ${input.guidance.summary}`,
@@ -72118,6 +72487,9 @@ function emitSpacePreflightRepairGuidance(input) {
72118
72487
  }
72119
72488
  });
72120
72489
  }
72490
+ function shouldEmitSpaceCommandPreflightOutput(emitOutput) {
72491
+ return emitOutput !== false;
72492
+ }
72121
72493
  function emitSpaceFailureNextStep(input) {
72122
72494
  const guidance = resolveSpaceFailureGuidance(input.error);
72123
72495
  if (!guidance) return;
@@ -79347,15 +79719,31 @@ function emitSpaceJoinLockBlocked(input) {
79347
79719
 
79348
79720
  //#endregion
79349
79721
  //#region src/space/join-entry.ts
79350
- const NOT_INSTALLED_DAEMON_STATUS = {
79351
- installed: false,
79352
- state: null
79353
- };
79354
79722
  const LOCAL_SERVICE_ONLY_JOIN_BLOCK_REASON_CODES = new Set([
79355
79723
  "service.drifted",
79356
79724
  "service.not_installed",
79357
79725
  "service.not_running"
79358
79726
  ]);
79727
+ const LOCAL_PARTICIPATION_ONLY_JOIN_BLOCK_REASON_CODES = new Set([
79728
+ "service.not_installed",
79729
+ "service.drifted",
79730
+ "service.not_running",
79731
+ "dispatch.storage_not_ready",
79732
+ "service.gateway_chain_unhealthy",
79733
+ "profile.unbound",
79734
+ "controller.binding.disabled",
79735
+ "projection.missing",
79736
+ "controller.registry.disabled",
79737
+ "controller.launch.choice_required",
79738
+ "controller.launch.needs_repair",
79739
+ "workspace.missing",
79740
+ "workspace.invalid",
79741
+ "runtime.adapter.unsupported",
79742
+ "controller.bootstrap.failed",
79743
+ "controller.gateway.unhealthy",
79744
+ "route.offline",
79745
+ "route.not_registered"
79746
+ ]);
79359
79747
  async function prepareSpaceJoinEntryContext(input) {
79360
79748
  const runtime = await resolveRuntimeContext({
79361
79749
  profile: input.profile,
@@ -79458,99 +79846,134 @@ async function prepareSpaceJoinServiceState(input) {
79458
79846
  };
79459
79847
  }
79460
79848
  async function assertSpaceJoinTargetPreflightReady(input) {
79849
+ const { explicitLocalAgentProfile, serviceRequirement } = resolveSpaceJoinLocalParticipationState(input);
79850
+ let emittedMembershipOnlyGuidance = false;
79851
+ let emittedServiceGuidance = false;
79852
+ if (serviceRequirement === "agent_setup_needed") {
79853
+ input.presenter.line({
79854
+ code: "space.join.local_participation.pending",
79855
+ text: buildSpaceJoinMembershipOnlyMessage({
79856
+ atsProfile: input.atsProfile,
79857
+ reasonCodes: ["profile.unbound"]
79858
+ })
79859
+ });
79860
+ emittedMembershipOnlyGuidance = true;
79861
+ }
79862
+ const shouldManageServiceParticipation = serviceRequirement === "offer_service_gate";
79461
79863
  const runPreflight = async () => await emitSpaceCommandPreflight({
79462
79864
  presenter: input.presenter,
79463
79865
  command: "join",
79464
79866
  profile: input.atsProfile,
79465
79867
  baseUrl: input.baseUrl,
79466
- resolvedView: input.resolvedView
79467
- });
79468
- let preflight = await runPreflight();
79469
- const blockingLocalServiceOutcome = await maybeHandleBlockingLocalServiceAttentionBeforeSpaceJoin({
79470
- localDemand: input.localDemand ?? null,
79471
- preflight,
79472
- presenter: input.presenter,
79473
79868
  resolvedView: input.resolvedView,
79474
- view: input.view
79869
+ humanServiceGuidanceMode: shouldManageServiceParticipation || serviceRequirement === "not_needed" ? "suppress" : "show"
79475
79870
  });
79476
- if (blockingLocalServiceOutcome === "recovered") preflight = await runPreflight();
79477
- if (preflight.deviceReplyReadiness.replyReadiness === "blocked") {
79478
- if (blockingLocalServiceOutcome === "continue_without_service" || blockingLocalServiceOutcome === "recovered" && isBlockingLocalServiceJoinAttention(preflight)) return;
79479
- if (await maybeOfferDaemonStartBeforeSpaceJoin({
79480
- localDemand: input.localDemand ?? null,
79871
+ let preflight = await runPreflight();
79872
+ if (shouldManageServiceParticipation) {
79873
+ const gateResult = await runSpaceJoinServiceParticipationGate({
79874
+ allowPrompt: input.allowPrompt,
79875
+ explicitLocalAgentProfile,
79876
+ gatewayUrl: input.baseUrl,
79877
+ localDemand: input.localDemand,
79481
79878
  presenter: input.presenter,
79879
+ resolvedView: input.resolvedView,
79880
+ serviceIntent: input.serviceIntent,
79482
79881
  view: input.view
79483
- })) preflight = await runPreflight();
79484
- if (preflight.deviceReplyReadiness.replyReadiness === "blocked") throw new Error(preflight.deviceReplyReadiness.reasonText);
79485
- return;
79882
+ });
79883
+ if (gateResult.status === "cancelled") return "cancelled";
79884
+ preflight = await emitSpaceCommandPreflight({
79885
+ presenter: input.presenter,
79886
+ command: "join",
79887
+ profile: input.atsProfile,
79888
+ baseUrl: input.baseUrl,
79889
+ resolvedView: input.resolvedView,
79890
+ humanServiceGuidanceMode: "suppress",
79891
+ emitOutput: false
79892
+ });
79893
+ if (gateResult.status === "declined") {
79894
+ input.presenter.line({
79895
+ code: "space.join.service.declined",
79896
+ text: "ATS will continue joining this space without wakeable local agent participation on this device. To enable background replies later, rerun `ats start` or use `ats service install` and `ats service run`."
79897
+ });
79898
+ emittedServiceGuidance = true;
79899
+ }
79486
79900
  }
79487
- if (blockingLocalServiceOutcome !== "not_applicable") return;
79488
- await maybeOfferDaemonStartBeforeSpaceJoin({
79489
- localDemand: input.localDemand ?? null,
79901
+ const blockedJoinResult = handleBlockedSpaceJoinLocalParticipation({
79902
+ atsProfile: input.atsProfile,
79903
+ explicitLocalAgentProfile,
79904
+ preflight,
79490
79905
  presenter: input.presenter,
79491
- view: input.view
79906
+ serviceRequirement,
79907
+ emittedMembershipOnlyGuidance,
79908
+ emittedServiceGuidance
79492
79909
  });
79910
+ if (blockedJoinResult) return blockedJoinResult;
79911
+ if (preflight.deviceReplyReadiness.replyReadiness === "blocked") throw new Error(preflight.deviceReplyReadiness.reasonText);
79912
+ return "continue";
79493
79913
  }
79494
- async function maybeHandleBlockingLocalServiceAttentionBeforeSpaceJoin(input) {
79495
- if (input.resolvedView !== "human" || !isBlockingLocalServiceJoinAttention(input.preflight)) return "not_applicable";
79496
- if (input.preflight.deviceReplyReadiness.reasonCodes.includes("service.drifted")) return await maybeOfferDaemonRepairBeforeSpaceJoin({
79497
- localDemand: input.localDemand ?? null,
79498
- view: input.view
79499
- }) ? "recovered" : "continue_without_service";
79500
- return await maybeOfferDaemonStartBeforeSpaceJoin({
79501
- localDemand: input.localDemand ?? null,
79914
+ function resolveSpaceJoinLocalParticipationState(input) {
79915
+ const explicitLocalAgentProfile = isExplicitLocalAgentProfile(input.atsProfile);
79916
+ return {
79917
+ explicitLocalAgentProfile,
79918
+ serviceRequirement: resolveCliServiceParticipationRequirement({
79919
+ intent: input.serviceIntent ?? "space_join",
79920
+ humanOnlyParticipation: !explicitLocalAgentProfile && input.localDemand?.decision !== "keep_running",
79921
+ explicitSelectedLocalAgents: explicitLocalAgentProfile,
79922
+ selectedAgentProfilesHaveRunnableLocalSetup: hasRunnableLocalAgentSetup(input.atsProfile),
79923
+ localDemand: input.localDemand ?? null
79924
+ })
79925
+ };
79926
+ }
79927
+ async function runSpaceJoinServiceParticipationGate(input) {
79928
+ return await runDaemonServiceParticipationGate({
79929
+ intent: input.serviceIntent ?? "space_join",
79502
79930
  presenter: input.presenter,
79931
+ resolvedView: input.resolvedView,
79932
+ allowPrompt: input.allowPrompt ?? true,
79933
+ gatewayUrl: input.gatewayUrl,
79934
+ localDemand: input.localDemand ?? null,
79935
+ forcePrompt: input.explicitLocalAgentProfile,
79503
79936
  view: input.view
79504
- }) ? "recovered" : "continue_without_service";
79505
- }
79506
- function isBlockingLocalServiceJoinAttention(preflight) {
79507
- const { deviceReplyReadiness } = preflight;
79508
- return deviceReplyReadiness.replyReadiness === "blocked" && deviceReplyReadiness.reasonCodes.length > 0 && deviceReplyReadiness.reasonCodes.every((reasonCode) => LOCAL_SERVICE_ONLY_JOIN_BLOCK_REASON_CODES.has(reasonCode));
79509
- }
79510
- async function maybeOfferDaemonRepairBeforeSpaceJoin(input) {
79511
- if (!isHumanInteractiveJoinRuntime(await resolveRuntimeContext({ view: input.view }))) return false;
79512
- if (input.localDemand?.decision !== "keep_running") return false;
79513
- return await runDaemonBackgroundStartForDecision({ view: input.view }) === "started";
79514
- }
79515
- async function maybeOfferDaemonStartBeforeSpaceJoin(input) {
79516
- const runtime = await resolveRuntimeContext({ view: input.view });
79517
- if (runtime.resolvedView !== "human" || !canUseInteractivePrompts(runtime)) return false;
79518
- if (input.localDemand?.decision !== "keep_running") return false;
79519
- const daemonStatus = await getDaemonStatus().catch(() => NOT_INSTALLED_DAEMON_STATUS);
79520
- if (!daemonStatus.installed) return false;
79521
- const runtimeState = await resolveDaemonRuntimeState(daemonStatus);
79522
- if (!runtimeState) return false;
79523
- const { serviceManagerStatus } = await resolveDaemonServiceManagerForRuntimeState({
79524
- runtimeState,
79525
- status: daemonStatus
79526
79937
  });
79527
- if (!shouldOfferDaemonStartDecision(getEffectiveDaemonRuntimeStatus({
79528
- runtimeState,
79529
- serviceManagerStatus
79530
- }))) return false;
79531
- const decision = await runDaemonStartDecisionPrompt({ startService: async () => await runDaemonBackgroundStartForDecision({ view: input.view }) });
79532
- if (decision === "not_now") {
79533
- const card = buildDaemonStartDeferredCard();
79534
- renderInfoCard({
79535
- presenter: input.presenter,
79536
- title: card.title,
79537
- codePrefix: "space.join.service.deferred",
79538
- rows: card.rows
79938
+ }
79939
+ function handleBlockedSpaceJoinLocalParticipation(input) {
79940
+ if (input.preflight.deviceReplyReadiness.replyReadiness !== "blocked" || !isBlockingLocalParticipationJoinAttention(input.preflight)) return null;
79941
+ if (input.explicitLocalAgentProfile) {
79942
+ if (!input.emittedMembershipOnlyGuidance) input.presenter.line({
79943
+ code: "space.join.local_participation.pending",
79944
+ text: buildSpaceJoinMembershipOnlyMessage({
79945
+ atsProfile: input.atsProfile,
79946
+ reasonCodes: input.preflight.deviceReplyReadiness.reasonCodes
79947
+ })
79539
79948
  });
79540
- return false;
79949
+ return "continue";
79541
79950
  }
79542
- if (decision === "cancelled" || decision === "deferred_with_card") return false;
79543
- if (decision === "failed") {
79544
- const card = buildDaemonStartNeedsAttentionCard({ summary: "ATS could not finish starting ATS Service." });
79545
- renderInfoCard({
79546
- presenter: input.presenter,
79547
- title: card.title,
79548
- codePrefix: "space.join.service.failed",
79549
- rows: card.rows
79951
+ if (input.serviceRequirement === "not_needed") {
79952
+ input.presenter.line({
79953
+ code: "space.join.service.not_needed",
79954
+ text: "ATS will keep joining this space without wakeable local agent participation on this device. If you decide to bring local agents later, rerun `ats start` or use `ats service install` and `ats service run`."
79550
79955
  });
79551
- return false;
79956
+ return "continue";
79552
79957
  }
79553
- return decision === "started";
79958
+ if (!input.emittedServiceGuidance) input.presenter.line({
79959
+ code: "space.join.service.pending",
79960
+ text: "ATS will keep joining this space without wakeable local agent participation on this device until ATS Service is ready. To enable background replies later, rerun `ats start` or use `ats service install` and `ats service run`."
79961
+ });
79962
+ return "continue";
79963
+ }
79964
+ function isBlockingLocalParticipationJoinAttention(preflight) {
79965
+ const { deviceReplyReadiness } = preflight;
79966
+ return deviceReplyReadiness.replyReadiness === "blocked" && deviceReplyReadiness.reasonCodes.length > 0 && deviceReplyReadiness.reasonCodes.every((reasonCode) => LOCAL_PARTICIPATION_ONLY_JOIN_BLOCK_REASON_CODES.has(reasonCode));
79967
+ }
79968
+ function buildSpaceJoinMembershipOnlyMessage(input) {
79969
+ const profileName = input.atsProfile.profileName;
79970
+ const profileId = input.atsProfile.atsProfileId;
79971
+ const reasonCodes = input.reasonCodes;
79972
+ const setupCommand = `ats profile ready --profile ${profileId}`;
79973
+ if (reasonCodes.includes("profile.unbound")) return `${profileName} will join this space, but it is not connected to a local agent on this device yet. It can appear here now, but it will not wake or reply from this device until you finish setup. Use \`${setupCommand}\` or rerun \`ats start\`.`;
79974
+ if (reasonCodes.some((reasonCode) => LOCAL_SERVICE_ONLY_JOIN_BLOCK_REASON_CODES.has(reasonCode)) || reasonCodes.includes("dispatch.storage_not_ready") || reasonCodes.includes("service.gateway_chain_unhealthy") || reasonCodes.includes("route.offline") || reasonCodes.includes("route.not_registered")) return `${profileName} will join this space, but it will not wake or reply from this device until ATS Service is ready. Use \`ats service install\`, \`ats service run\`, or rerun \`ats start\`.`;
79975
+ if (reasonCodes.includes("controller.launch.needs_repair") || reasonCodes.includes("controller.launch.choice_required") || reasonCodes.includes("workspace.missing") || reasonCodes.includes("workspace.invalid")) return `${profileName} will join this space, but it is not ready to wake or reply from this device yet. Use \`${setupCommand}\` or rerun \`ats start\`.`;
79976
+ return `${profileName} will join this space, but local setup on this device is still incomplete. It can appear here now, but it will not wake or reply from this device until you finish setup. Use \`${setupCommand}\` or rerun \`ats start\`.`;
79554
79977
  }
79555
79978
  function buildJoinTargetMissingMessage(input) {
79556
79979
  return formatMessage(input.resolvedView, "space.target.missing", { command: "join" });
@@ -79902,14 +80325,15 @@ function normalizeSpaceJoinMembershipCandidate(value) {
79902
80325
  ownerProfileName: normalizeOptionalString$2(candidate.ownerProfileName),
79903
80326
  ownerUserId: normalizeOptionalString$2(candidate.ownerUserId),
79904
80327
  ownerName: normalizeOptionalString$2(candidate.ownerName),
79905
- controllerRef: normalizeOptionalString$2(candidate.controllerRef)
80328
+ controllerRef: normalizeOptionalString$2(candidate.controllerRef),
80329
+ controllerEnabled: candidate.controllerEnabled === true
79906
80330
  };
79907
80331
  }
79908
80332
  function resolveSpaceJoinMembershipProfileKind(value) {
79909
80333
  return value === "human" || value === "agent" ? value : null;
79910
80334
  }
79911
80335
  function normalizeOptionalString$2(value) {
79912
- return typeof value === "string" ? value : null;
80336
+ return typeof value === "string" ? value : void 0;
79913
80337
  }
79914
80338
  function buildLocallyCreatedPendingSpaceJoinMembershipCandidate(profile) {
79915
80339
  return {
@@ -79918,8 +80342,9 @@ function buildLocallyCreatedPendingSpaceJoinMembershipCandidate(profile) {
79918
80342
  profileName: profile.profileName,
79919
80343
  profileKind: profile.profileKind === "agent" ? "agent" : "human",
79920
80344
  ownerUserId: profile.ownerUserId,
79921
- ownerName: typeof profile.ownerName === "string" ? profile.ownerName : null,
79922
- controllerRef: profile.controllerBinding?.controllerRef ?? null,
80345
+ ownerName: typeof profile.ownerName === "string" ? profile.ownerName : void 0,
80346
+ controllerRef: profile.controllerBinding?.controllerRef,
80347
+ controllerEnabled: profile.controllerBinding?.controllerEnabled === true,
79923
80348
  alreadyJoined: false,
79924
80349
  needsActivation: false
79925
80350
  };
@@ -80131,9 +80556,16 @@ async function applySelectedSpaceAddMembersBeforeJoin(input, dependencies) {
80131
80556
  });
80132
80557
  }
80133
80558
  if (confirmedCandidates.candidates.length === 0) return "completed";
80134
- const result = await dependencies.applySpaceAddMembers({
80559
+ const servicePreparationResult = await dependencies.prepareSpaceMemberCandidatesLocalParticipation({
80135
80560
  actorProfile: input.actorProfile,
80136
80561
  candidates: confirmedCandidates.candidates,
80562
+ presenter: input.presenter,
80563
+ resolvedView: input.resolvedView
80564
+ });
80565
+ if (servicePreparationResult.status === "cancelled") return input.cancelResult;
80566
+ const result = await dependencies.applySpaceAddMembers({
80567
+ actorProfile: input.actorProfile,
80568
+ candidates: servicePreparationResult.candidates,
80137
80569
  space: input.space,
80138
80570
  baseUrl: input.baseUrl,
80139
80571
  password: input.password
@@ -80149,7 +80581,7 @@ async function applySelectedSpaceAddMembersBeforeJoin(input, dependencies) {
80149
80581
  presenter: input.presenter,
80150
80582
  resolvedView: input.resolvedView,
80151
80583
  space: input.space,
80152
- selectedCount: confirmedCandidates.candidates.length,
80584
+ selectedCount: servicePreparationResult.candidates.length,
80153
80585
  result,
80154
80586
  joinNoticeSummary
80155
80587
  });
@@ -80659,6 +81091,7 @@ function buildPostCreateJoinRequest(input) {
80659
81091
  ...typeof input.postCreateJoin?.mentionAlias === "string" ? { mentionAlias: input.postCreateJoin.mentionAlias } : {},
80660
81092
  ...typeof input.postCreateJoin?.rawStream === "boolean" ? { rawStream: input.postCreateJoin.rawStream } : {},
80661
81093
  ...typeof input.postCreateJoin?.skipBringProfilesPrompt === "boolean" ? { skipBringProfilesPrompt: input.postCreateJoin.skipBringProfilesPrompt } : {},
81094
+ serviceIntent: input.postCreateJoin?.serviceIntent ?? "space_create_join",
80662
81095
  entryChecksHandled: true
80663
81096
  };
80664
81097
  }
@@ -80688,7 +81121,8 @@ async function runSpaceCreate(input) {
80688
81121
  command: "create",
80689
81122
  profile: atsProfile,
80690
81123
  baseUrl,
80691
- resolvedView: contextRuntime.resolvedView
81124
+ resolvedView: contextRuntime.resolvedView,
81125
+ humanServiceGuidanceMode: isHumanInteractiveSpaceRuntime(contextRuntime) ? "suppress" : "show"
80692
81126
  });
80693
81127
  await ensureSpaceCreateAuthReady({ baseUrl });
80694
81128
  const createInput = await resolveSpaceCreateInput({
@@ -81788,9 +82222,22 @@ async function runSpaceAddMembersForTarget(input) {
81788
82222
  selectedProfileIds
81789
82223
  });
81790
82224
  if (selectedCandidates.length === 0) return { status: "completed" };
82225
+ const servicePreparationResult = await ensureSpaceMemberCandidatesLocalParticipationReady({
82226
+ atsProfile: input.atsProfile,
82227
+ candidates: selectedCandidates,
82228
+ presenter: input.presenter,
82229
+ resolvedView: input.runtime.resolvedView,
82230
+ allowPrompt: input.allowPrompt,
82231
+ view: input.view
82232
+ });
82233
+ if (servicePreparationResult.status === "cancelled") return input.allowBackToCaller ? {
82234
+ status: "repeat_target",
82235
+ authenticatedGatewayUrl
82236
+ } : { status: "cancelled" };
82237
+ const preparedCandidates = servicePreparationResult.candidates;
81791
82238
  const result = await applySpaceAddMembers({
81792
82239
  actorProfile: input.atsProfile,
81793
- candidates: selectedCandidates,
82240
+ candidates: preparedCandidates,
81794
82241
  space: input.target.space,
81795
82242
  baseUrl: targetBaseUrl,
81796
82243
  password: input.password
@@ -81806,7 +82253,7 @@ async function runSpaceAddMembersForTarget(input) {
81806
82253
  presenter: input.presenter,
81807
82254
  resolvedView: input.runtime.resolvedView,
81808
82255
  space: input.target.space,
81809
- selectedCount: selectedCandidates.length,
82256
+ selectedCount: preparedCandidates.length,
81810
82257
  result,
81811
82258
  joinNoticeSummary
81812
82259
  });
@@ -81894,6 +82341,159 @@ async function runSpaceRemoveMembersForTarget(input) {
81894
82341
  function resolveSelectedSpaceMemberCandidates(input) {
81895
82342
  return input.selectedProfileIds.map((profileId) => input.candidates.find((candidate) => candidate.profileId === profileId)).filter((candidate) => Boolean(candidate));
81896
82343
  }
82344
+ async function ensureSpaceMemberCandidatesLocalParticipationReady(input) {
82345
+ let preparedCandidates = await refreshSpaceMemberCandidatesLocalReadiness(input.candidates);
82346
+ const repairableCandidates = preparedCandidates.filter(isSpaceMemberCandidateRepairableLocalParticipationCandidate);
82347
+ if (repairableCandidates.length > 0 && input.resolvedView === "human" && input.allowPrompt) {
82348
+ const repairNow = await confirm({
82349
+ message: buildSpaceMemberRepairPromptMessage(repairableCandidates),
82350
+ active: "Repair now",
82351
+ inactive: "Not now",
82352
+ initialValue: true
82353
+ });
82354
+ if (isCancel(repairNow)) {
82355
+ cancel("Cancelled.");
82356
+ return { status: "cancelled" };
82357
+ }
82358
+ if (repairNow) {
82359
+ await repairSpaceMemberCandidatesLocalParticipation({
82360
+ atsProfile: input.atsProfile,
82361
+ candidates: repairableCandidates,
82362
+ view: input.view
82363
+ });
82364
+ preparedCandidates = await refreshSpaceMemberCandidatesLocalReadiness(preparedCandidates);
82365
+ }
82366
+ }
82367
+ const wakeableLocalCandidates = preparedCandidates.filter(isSpaceMemberCandidateRunnableLocalParticipationCandidate);
82368
+ const explicitSelectedLocalAgents = hasAnyExplicitLocalAgentCandidates(wakeableLocalCandidates);
82369
+ if (resolveCliServiceParticipationRequirement({
82370
+ intent: "space_add_members",
82371
+ humanOnlyParticipation: !explicitSelectedLocalAgents,
82372
+ explicitSelectedLocalAgents,
82373
+ selectedAgentProfilesHaveRunnableLocalSetup: haveAllExplicitLocalAgentCandidatesRunnable(wakeableLocalCandidates),
82374
+ localDemand: input.resolvedView === "human" ? await resolveDaemonLocalServiceDemand({ ownerUserId: input.atsProfile.ownerUserId }).catch(() => null) : null
82375
+ }) === "not_needed") return {
82376
+ status: "continue",
82377
+ candidates: preparedCandidates
82378
+ };
82379
+ if (!(input.resolvedView === "human" && input.allowPrompt)) {
82380
+ input.presenter.line({
82381
+ code: "space.add_members.service.deferred",
82382
+ text: "ATS will continue without wakeable local agent participation on this device. To enable background replies later, rerun `ats start` or use `ats service install` and `ats service run`."
82383
+ });
82384
+ return {
82385
+ status: "continue",
82386
+ candidates: preparedCandidates
82387
+ };
82388
+ }
82389
+ const gateResult = await runDaemonServiceParticipationGate({
82390
+ intent: "space_add_members",
82391
+ presenter: input.presenter,
82392
+ resolvedView: input.resolvedView,
82393
+ allowPrompt: input.allowPrompt,
82394
+ gatewayUrl: input.gatewayUrl,
82395
+ localDemand: await resolveDaemonLocalServiceDemand({ ownerUserId: input.atsProfile.ownerUserId }).catch(() => null),
82396
+ forcePrompt: explicitSelectedLocalAgents,
82397
+ view: input.view
82398
+ });
82399
+ if (gateResult.status === "cancelled") return { status: "cancelled" };
82400
+ if (gateResult.status !== "aligned") input.presenter.line({
82401
+ code: "space.add_members.service.declined",
82402
+ text: "ATS will continue adding these profiles without wakeable local agent participation on this device. To enable background replies later, rerun `ats start` or use `ats service install` and `ats service run`."
82403
+ });
82404
+ return {
82405
+ status: "continue",
82406
+ candidates: preparedCandidates
82407
+ };
82408
+ }
82409
+ async function repairSpaceMemberCandidatesLocalParticipation(input) {
82410
+ if (input.candidates.some((candidate) => candidate.localParticipationReasonCodes?.some((reasonCode) => reasonCode === "projection.missing" || reasonCode === "controller.launch.needs_repair"))) await syncDeviceRuntimeStateProjection({ mode: "repair" });
82411
+ const repairableAgentIds = [...new Set(input.candidates.map((candidate) => candidate.localTargetLaunchStatus === "needs_repair" ? candidate.localTargetAgentId ?? null : null).filter((value) => Boolean(value)))];
82412
+ if (repairableAgentIds.length > 0) await runAgentsRepair({
82413
+ agent: repairableAgentIds,
82414
+ profile: input.atsProfile.atsProfileId,
82415
+ view: input.view
82416
+ });
82417
+ if (input.candidates.some((candidate) => candidate.localParticipationReasonCodes?.some((reasonCode) => reasonCode === "workspace.missing" || reasonCode === "workspace.invalid"))) await syncProfileWorkspaceState({ mode: "repair" });
82418
+ if (input.candidates.some((candidate) => candidate.localParticipationReasonCodes?.some((reasonCode) => reasonCode === "dispatch.storage_not_ready" || reasonCode === "service.gateway_chain_unhealthy"))) await runDoctor({
82419
+ profile: input.atsProfile.atsProfileId,
82420
+ repair: true,
82421
+ view: input.view,
82422
+ agentOverviewHandled: true
82423
+ });
82424
+ }
82425
+ async function refreshSpaceMemberCandidatesLocalReadiness(candidates) {
82426
+ const localTargetStates = await listAgentTargetStates().catch(() => []);
82427
+ if (localTargetStates.length === 0) return await Promise.all(candidates.map(async (candidate) => {
82428
+ return await enrichSpaceMemberCandidateLocalParticipationReadiness({
82429
+ ...candidate,
82430
+ localTargetDisplayName: candidate.localTargetDisplayName ?? null,
82431
+ localTargetLaunchStatus: candidate.localTargetLaunchStatus ?? null,
82432
+ localTargetSelectable: candidate.localTargetSelectable ?? false
82433
+ });
82434
+ }));
82435
+ const localTargetStateById = new Map(localTargetStates.map((state) => [state.agentId, state]));
82436
+ return await Promise.all(candidates.map(async (candidate) => {
82437
+ const localTargetAgentId = resolveSpaceMemberCandidateLocalTargetAgentId({
82438
+ profileKind: candidate.profileKind,
82439
+ controllerRef: candidate.controllerRef,
82440
+ controllerEnabled: candidate.controllerEnabled
82441
+ });
82442
+ const localTargetState = localTargetAgentId ? localTargetStateById.get(localTargetAgentId) ?? null : null;
82443
+ return await enrichSpaceMemberCandidateLocalParticipationReadiness({
82444
+ ...candidate,
82445
+ localTargetAgentId,
82446
+ localTargetDisplayName: localTargetState?.displayName ?? null,
82447
+ localTargetLaunchStatus: localTargetState?.launchStatus ?? null,
82448
+ localTargetSelectable: localTargetState?.selectable ?? false
82449
+ });
82450
+ }));
82451
+ }
82452
+ async function enrichSpaceMemberCandidateLocalParticipationReadiness(candidate) {
82453
+ if (candidate.profileKind !== "agent" || !candidate.atsProfile || candidate.controllerEnabled !== true || typeof candidate.controllerRef !== "string" || candidate.controllerRef.trim().length === 0) return {
82454
+ ...candidate,
82455
+ localParticipationReadiness: null,
82456
+ localParticipationReasonCodes: [],
82457
+ localParticipationReasonText: null
82458
+ };
82459
+ let snapshot = null;
82460
+ try {
82461
+ snapshot = await resolveCurrentReplyReadinessSnapshot({
82462
+ checkGatewayChain: false,
82463
+ ownerUserId: candidate.atsProfile.ownerUserId,
82464
+ profile: candidate.atsProfile
82465
+ });
82466
+ } catch {
82467
+ snapshot = null;
82468
+ }
82469
+ if (!snapshot) return {
82470
+ ...candidate,
82471
+ localParticipationReadiness: null,
82472
+ localParticipationReasonCodes: [],
82473
+ localParticipationReasonText: null
82474
+ };
82475
+ const readinessSummary = resolveServiceParticipationReadiness({
82476
+ deviceReplyReadiness: snapshot.deviceReplyReadiness,
82477
+ runtimeStatus: snapshot.displayRuntimeStatus ?? snapshot.runtimeStatus
82478
+ });
82479
+ return {
82480
+ ...candidate,
82481
+ localParticipationReadiness: readinessSummary.serviceReadiness,
82482
+ localParticipationReasonCodes: [...snapshot.deviceReplyReadiness.reasonCodes],
82483
+ localParticipationReasonText: snapshot.deviceReplyReadiness.reasonText
82484
+ };
82485
+ }
82486
+ function isSpaceMemberCandidateRunnableLocalParticipationCandidate(candidate) {
82487
+ return candidate.profileKind === "agent" && candidate.controllerEnabled === true && typeof candidate.controllerRef === "string" && candidate.controllerRef.trim().length > 0 && candidate.localTargetSelectable === true && (candidate.localParticipationReadiness === "ready" || candidate.localParticipationReadiness === "not_running" || candidate.localParticipationReadiness === "not_installed");
82488
+ }
82489
+ function isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate) {
82490
+ return candidate.profileKind === "agent" && candidate.controllerEnabled === true && typeof candidate.controllerRef === "string" && candidate.controllerRef.trim().length > 0 && (typeof candidate.localTargetAgentId === "string" && candidate.localTargetAgentId.trim().length > 0 && candidate.localTargetLaunchStatus === "needs_repair" || candidate.localParticipationReadiness === "needs_repair");
82491
+ }
82492
+ function buildSpaceMemberRepairPromptMessage(candidates) {
82493
+ const displayNames = candidates.map((candidate) => candidate.localTargetDisplayName || candidate.localTargetAgentId || candidate.profileName);
82494
+ if (displayNames.length === 1) return `${displayNames[0]} needs repair on this device before ATS can use it for background replies. Repair it now?`;
82495
+ return `${displayNames.join(", ")} need repair on this device before ATS can use them for background replies. Repair them now?`;
82496
+ }
81897
82497
  async function runSpaceJoin(input) {
81898
82498
  let failureRuntime = null;
81899
82499
  let failurePresenter = null;
@@ -81934,7 +82534,8 @@ async function runSpaceJoin(input) {
81934
82534
  role: input.role,
81935
82535
  roleInstructions: input.roleInstructions,
81936
82536
  mentionAlias: input.mentionAlias,
81937
- rawStream: input.rawStream === true
82537
+ rawStream: input.rawStream === true,
82538
+ serviceIntent: input.serviceIntent ?? "space_join"
81938
82539
  });
81939
82540
  } catch (error) {
81940
82541
  const runtime = failureRuntime ?? await resolveRuntimeContext({
@@ -81981,7 +82582,8 @@ async function runSpaceJoinTargetLoop(input) {
81981
82582
  rawStream: input.rawStream,
81982
82583
  skipBringProfilesPrompt: input.skipBringProfilesPrompt,
81983
82584
  output: input.output,
81984
- view: input.view
82585
+ view: input.view,
82586
+ serviceIntent: "space_create_join"
81985
82587
  }
81986
82588
  });
81987
82589
  }
@@ -81993,14 +82595,16 @@ async function runSpaceJoinTargetLoop(input) {
81993
82595
  space: target.space
81994
82596
  }), "reading an existing local space config before join");
81995
82597
  const targetBaseUrl = input.hasExplicitBaseUrlInput ? input.baseUrl : target.baseUrl ?? input.baseUrl;
81996
- await assertSpaceJoinTargetPreflightReady({
82598
+ if (await assertSpaceJoinTargetPreflightReady({
81997
82599
  atsProfile: input.atsProfile,
81998
82600
  baseUrl: targetBaseUrl,
82601
+ allowPrompt: input.canPromptPreJoinActions,
81999
82602
  localDemand: input.localDemand,
82000
82603
  presenter: input.presenter,
82001
82604
  resolvedView: input.runtime.resolvedView,
82605
+ serviceIntent: input.serviceIntent,
82002
82606
  view: input.view
82003
- });
82607
+ }) === "cancelled") return "cancelled";
82004
82608
  authenticatedGatewayUrl = await ensureSpaceCommandGatewayAuthenticationOrCancel({
82005
82609
  runtime: input.runtime,
82006
82610
  gatewayUrl: targetBaseUrl,
@@ -82042,6 +82646,15 @@ async function runSpaceJoinTargetLoop(input) {
82042
82646
  resolveCreateProfileName,
82043
82647
  resolveAgentProfileSetup,
82044
82648
  persistAgentTransportSelection,
82649
+ prepareSpaceMemberCandidatesLocalParticipation: async ({ actorProfile, candidates, presenter, resolvedView }) => await ensureSpaceMemberCandidatesLocalParticipationReady({
82650
+ atsProfile: actorProfile,
82651
+ candidates,
82652
+ gatewayUrl: targetBaseUrl,
82653
+ presenter,
82654
+ resolvedView,
82655
+ allowPrompt: input.canPromptPreJoinActions,
82656
+ view: input.view
82657
+ }),
82045
82658
  applySpaceAddMembers,
82046
82659
  persistSpaceAddMembersJoinNotices,
82047
82660
  emitSpaceAddMembersResult
@@ -83053,6 +83666,8 @@ async function resolveSpaceRemoveMembersTarget(input) {
83053
83666
  }
83054
83667
  async function resolveSpaceMemberCandidatesForAdd(input) {
83055
83668
  const profiles = await listAtsProfiles();
83669
+ const localTargetStates = await listAgentTargetStates().catch(() => []);
83670
+ const localTargetStateById = new Map(localTargetStates.map((state) => [state.agentId, state]));
83056
83671
  const ownerProfileNameByOwnerUserId = buildOwnerProfileNameByOwnerUserId(profiles);
83057
83672
  const candidateProfiles = profiles.filter((profile) => profile.status === "active" && isSpaceMembershipProfileKind(profile.profileKind) && profile.atsProfileId !== input.currentProfile.atsProfileId).filter((profile) => typeof input.currentOwnerUserId !== "string" || input.currentOwnerUserId.trim().length === 0 || profile.ownerUserId === input.currentOwnerUserId);
83058
83673
  if (candidateProfiles.length === 0) return [];
@@ -83062,24 +83677,55 @@ async function resolveSpaceMemberCandidatesForAdd(input) {
83062
83677
  ...input.password === void 0 ? {} : { spacePassword: input.password }
83063
83678
  });
83064
83679
  const memberProfileIds = new Set([...membersSnapshot.humans, ...membersSnapshot.agents].map((member) => member.profileId));
83065
- return candidateProfiles.map((profile) => {
83066
- return {
83067
- profileId: profile.atsProfileId,
83068
- profileHandle: profile.profileHandle,
83069
- profileName: profile.profileName,
83070
- profileKind: profile.profileKind,
83071
- ownerProfileName: ownerProfileNameByOwnerUserId.get(profile.ownerUserId),
83072
- ownerUserId: profile.ownerUserId,
83073
- ownerName: profile.ownerName,
83074
- controllerRef: profile.controllerBinding?.controllerRef,
83075
- alreadyJoined: memberProfileIds.has(profile.atsProfileId),
83076
- needsActivation: false
83077
- };
83078
- }).sort((left, right) => {
83680
+ return await refreshSpaceMemberCandidatesLocalReadiness(candidateProfiles.map((profile) => buildSpaceMemberCandidateFromProfile({
83681
+ profile,
83682
+ localTargetStateById,
83683
+ ownerProfileName: ownerProfileNameByOwnerUserId.get(profile.ownerUserId) ?? void 0,
83684
+ alreadyJoined: memberProfileIds.has(profile.atsProfileId)
83685
+ })).sort((left, right) => {
83079
83686
  const byName = left.profileName.localeCompare(right.profileName);
83080
83687
  if (byName !== 0) return byName;
83081
83688
  return left.profileId.localeCompare(right.profileId);
83689
+ }));
83690
+ }
83691
+ function buildSpaceMemberCandidateFromProfile(input) {
83692
+ const controllerRef = input.profile.controllerBinding?.controllerRef;
83693
+ const controllerEnabled = input.profile.controllerBinding?.controllerEnabled;
83694
+ const localTargetAgentId = resolveSpaceMemberCandidateLocalTargetAgentId({
83695
+ profileKind: input.profile.profileKind,
83696
+ controllerRef,
83697
+ controllerEnabled
83082
83698
  });
83699
+ const localTargetState = localTargetAgentId ? input.localTargetStateById.get(localTargetAgentId) ?? null : null;
83700
+ return {
83701
+ atsProfile: input.profile,
83702
+ profileId: input.profile.atsProfileId,
83703
+ profileHandle: input.profile.profileHandle,
83704
+ profileName: input.profile.profileName,
83705
+ profileKind: input.profile.profileKind,
83706
+ bindingState: input.profile.bindingState,
83707
+ ownerProfileName: input.ownerProfileName,
83708
+ ownerUserId: input.profile.ownerUserId,
83709
+ ownerName: input.profile.ownerName,
83710
+ controllerRef,
83711
+ controllerEnabled,
83712
+ localTargetAgentId,
83713
+ localTargetDisplayName: localTargetState?.displayName ?? null,
83714
+ localTargetLaunchStatus: localTargetState?.launchStatus ?? null,
83715
+ localTargetSelectable: localTargetState?.selectable ?? false,
83716
+ localParticipationReadiness: null,
83717
+ localParticipationReasonCodes: [],
83718
+ localParticipationReasonText: null,
83719
+ alreadyJoined: input.alreadyJoined,
83720
+ needsActivation: false
83721
+ };
83722
+ }
83723
+ function resolveSpaceMemberCandidateLocalTargetAgentId(input) {
83724
+ if (input.profileKind !== "agent" || input.controllerEnabled !== true || typeof input.controllerRef !== "string") return null;
83725
+ const controllerRef = input.controllerRef.trim();
83726
+ if (!controllerRef) return null;
83727
+ if (controllerRef.startsWith("builtin:")) return normalizeBuiltinControllerAgentId(controllerRef) || null;
83728
+ return controllerRef;
83083
83729
  }
83084
83730
  async function resolveSpaceMemberCandidatesForRemove(input) {
83085
83731
  const membersSnapshot = await createCliSpaceApi(input.baseUrl).getMembersSnapshot({
@@ -83429,6 +84075,7 @@ async function applySpaceAddMembers(input) {
83429
84075
  return result;
83430
84076
  }
83431
84077
  function emitSpaceAddMembersResult(input) {
84078
+ const localParticipationFollowUps = input.result.added.map((candidate) => buildSpaceMemberJoinedFollowUp(candidate)).filter((followUp) => followUp !== null);
83432
84079
  if (input.resolvedView === "agent") {
83433
84080
  input.presenter.data({
83434
84081
  code: "space.add_members",
@@ -83449,7 +84096,11 @@ function emitSpaceAddMembersResult(input) {
83449
84096
  recordedCount: input.joinNoticeSummary.recordedCount,
83450
84097
  failedCount: input.joinNoticeSummary.failedCount,
83451
84098
  ...input.joinNoticeSummary.firstFailure ? { firstFailure: input.joinNoticeSummary.firstFailure } : {}
83452
- }
84099
+ },
84100
+ localParticipationFollowUps: localParticipationFollowUps.map((followUp) => ({
84101
+ profileId: followUp.profileId,
84102
+ message: followUp.text
84103
+ }))
83453
84104
  }
83454
84105
  });
83455
84106
  return;
@@ -83467,6 +84118,10 @@ function emitSpaceAddMembersResult(input) {
83467
84118
  sanitize: true,
83468
84119
  minContentWidth: 56
83469
84120
  });
84121
+ for (const followUp of localParticipationFollowUps) input.presenter.line({
84122
+ code: "space.add_members.local_participation_follow_up",
84123
+ text: followUp.text
84124
+ });
83470
84125
  if (input.result.failed.length > 0) for (const failure of input.result.failed) input.presenter.line({
83471
84126
  code: "space.add_members.failed",
83472
84127
  text: `${failure.candidate.profileId}: ${toErrorMessage$2(failure.error)}`
@@ -83486,6 +84141,22 @@ function emitSpaceAddMembersResult(input) {
83486
84141
  firstFailure: input.joinNoticeSummary.firstFailure
83487
84142
  });
83488
84143
  }
84144
+ function buildSpaceMemberJoinedFollowUp(candidate) {
84145
+ if (candidate.profileKind !== "agent") return null;
84146
+ if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) {
84147
+ const agentId = candidate.localTargetAgentId;
84148
+ const repairCommand = agentId ? `ats agents repair --agent ${agentId}` : "ats start";
84149
+ return {
84150
+ profileId: candidate.profileId,
84151
+ text: `${candidate.profileName} joined this space, but ${(candidate.localTargetDisplayName || candidate.localTargetAgentId || "this local agent").trim()} still needs repair on this device before it can wake or reply in the background. Use \`${repairCommand}\` or rerun \`ats start\`.`
84152
+ };
84153
+ }
84154
+ if (!isSpaceMemberCandidateRunnableLocalParticipationCandidate(candidate)) return {
84155
+ profileId: candidate.profileId,
84156
+ text: `${candidate.profileName} joined this space, but it is not connected to a local agent on this device yet. It can appear here now, but it will not wake or reply from this device until you finish setup. Use \`ats profile ready --profile ${candidate.profileId}\` or rerun \`ats start\`.`
84157
+ };
84158
+ return null;
84159
+ }
83489
84160
  async function applySpaceRemoveMembers(input) {
83490
84161
  const result = {
83491
84162
  removed: [],
@@ -83826,7 +84497,7 @@ function formatSpaceMemberCandidateLabel(candidate) {
83826
84497
  function formatSpaceMemberCandidateHint(candidate) {
83827
84498
  if (candidate.alreadyJoined) return "Already in this space";
83828
84499
  if (candidate.needsActivation) return "Needs mention setup";
83829
- return "Selectable";
84500
+ return resolveSpaceMemberCandidateStatusLabel(candidate) ?? "Selectable";
83830
84501
  }
83831
84502
  function buildHumanSpaceMemberCandidateSearchItem(candidate) {
83832
84503
  return {
@@ -83838,10 +84509,12 @@ function buildHumanSpaceMemberCandidateSearchItem(candidate) {
83838
84509
  };
83839
84510
  }
83840
84511
  function buildHumanSpaceMemberCandidateDetail(candidate) {
83841
- let status = null;
83842
- if (candidate.alreadyJoined === true) status = "already in this space";
83843
- else if (candidate.needsActivation) status = "needs mention setup";
83844
- return status ? `ID ${sanitizeSingleLinePromptText(candidate.profileId)} · ${sanitizeSingleLinePromptText(status)}` : `ID ${sanitizeSingleLinePromptText(candidate.profileId)}`;
84512
+ const statuses = [];
84513
+ if (candidate.alreadyJoined === true) statuses.push("already in this space");
84514
+ else if (candidate.needsActivation) statuses.push("needs mention setup");
84515
+ const agentStatuses = resolveSpaceMemberCandidateStatusBadges(candidate);
84516
+ statuses.push(...agentStatuses.map((status) => status.toLowerCase()));
84517
+ return statuses.length > 0 ? `ID ${sanitizeSingleLinePromptText(candidate.profileId)} · ${statuses.map((status) => sanitizeSingleLinePromptText(status)).join(" · ")}` : `ID ${sanitizeSingleLinePromptText(candidate.profileId)}`;
83845
84518
  }
83846
84519
  function buildHumanSpaceMemberCandidateSearchText(candidate) {
83847
84520
  return [
@@ -83852,10 +84525,26 @@ function buildHumanSpaceMemberCandidateSearchText(candidate) {
83852
84525
  sanitizeSingleLinePromptText(normalizeOptionalString$1(candidate.ownerName)),
83853
84526
  sanitizeSingleLinePromptText(normalizeOptionalString$1(candidate.ownerUserId)),
83854
84527
  sanitizeSingleLinePromptText(normalizeOptionalString$1(candidate.controllerRef)),
84528
+ ...resolveSpaceMemberCandidateStatusBadges(candidate),
83855
84529
  candidate.alreadyJoined ? "already in this space" : null,
83856
84530
  candidate.needsActivation ? "needs mention setup" : null
83857
84531
  ].filter((value) => typeof value === "string").join("\n");
83858
84532
  }
84533
+ function resolveSpaceMemberCandidateStatusLabel(candidate) {
84534
+ const statuses = resolveSpaceMemberCandidateStatusBadges(candidate);
84535
+ if (statuses.length === 0) return null;
84536
+ return statuses.join(" · ");
84537
+ }
84538
+ function resolveSpaceMemberCandidateStatusBadges(candidate) {
84539
+ if (candidate.profileKind !== "agent") return [];
84540
+ if (isSpaceMemberCandidateBackgroundReplyReady(candidate)) return ["Ready for background replies"];
84541
+ if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) return ["Needs repair"];
84542
+ if (candidate.controllerEnabled === true && typeof candidate.controllerRef === "string" && candidate.controllerRef.trim().length > 0) return ["Can join only"];
84543
+ return ["Not connected", "Can join only"];
84544
+ }
84545
+ function isSpaceMemberCandidateBackgroundReplyReady(candidate) {
84546
+ return candidate.profileKind === "agent" && candidate.controllerEnabled === true && typeof candidate.controllerRef === "string" && candidate.controllerRef.trim().length > 0 && candidate.localTargetSelectable === true && candidate.localParticipationReadiness === "ready";
84547
+ }
83859
84548
  function normalizeRequestedMemberIds(value) {
83860
84549
  if (!Array.isArray(value)) return [];
83861
84550
  const ids = [];
@@ -87071,11 +87760,13 @@ async function runStart(input) {
87071
87760
  const presenter = createPresenter(runtime);
87072
87761
  const interactive = canUseInteractivePrompts(runtime);
87073
87762
  if (interactive && runtime.resolvedView === "human") intro(`\n${renderAtsHeader()}`);
87074
- if (!await runStartDisclaimerStep({
87075
- runtime,
87076
- presenter,
87077
- interactive
87078
- })) return;
87763
+ if (!input.entryChecksHandled) {
87764
+ if (!await runStartDisclaimerStep({
87765
+ runtime,
87766
+ presenter,
87767
+ interactive
87768
+ })) return;
87769
+ }
87079
87770
  if (input.startSession) {
87080
87771
  await runStartHandoff({
87081
87772
  ...input,
@@ -87159,6 +87850,7 @@ async function runStart(input) {
87159
87850
  });
87160
87851
  const serviceStepResult = await runStartServiceStep({
87161
87852
  foundation,
87853
+ gatewayUrl,
87162
87854
  runtime,
87163
87855
  presenter,
87164
87856
  interactive,
@@ -91712,7 +92404,8 @@ async function runStartFromCliOptions(opts) {
91712
92404
  baseUrl: opts.gatewayUrl,
91713
92405
  startSession: opts.startSession,
91714
92406
  skillsMode: opts.skillsMode,
91715
- view: opts.view
92407
+ view: opts.view,
92408
+ entryChecksHandled: true
91716
92409
  });
91717
92410
  }
91718
92411
  function withSpaceProfileOption(cmd) {