agent-transport-system 0.7.4 → 0.7.6

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
@@ -27,7 +27,7 @@ import wrapAnsi from "wrap-ansi";
27
27
  import { Box, Container, Editor, Key, ProcessTerminal, TUI, Text, getEditorKeybindings, matchesKey } from "@mariozechner/pi-tui";
28
28
 
29
29
  //#region package.json
30
- var version = "0.7.4";
30
+ var version = "0.7.6";
31
31
  var package_default = {
32
32
  $schema: "https://www.schemastore.org/package.json",
33
33
  name: "agent-transport-system",
@@ -2492,6 +2492,14 @@ const SPACE_ADD_MEMBERS_PROGRESS_PHASES = [
2492
2492
  factSource: "space_membership_api_write",
2493
2493
  summary: "Adding selected profiles to the Space."
2494
2494
  },
2495
+ {
2496
+ stage: "add_members",
2497
+ phase: "confirm_membership_projection",
2498
+ owner: "space",
2499
+ waitingFor: "Space membership and Wake availability projection",
2500
+ factSource: "space_membership_projection",
2501
+ summary: "Confirming the Space can see newly added wakeable members before returning."
2502
+ },
2495
2503
  {
2496
2504
  stage: "record_join_notices",
2497
2505
  phase: "write_join_notices",
@@ -2502,6 +2510,14 @@ const SPACE_ADD_MEMBERS_PROGRESS_PHASES = [
2502
2510
  }
2503
2511
  ];
2504
2512
  const SPACE_ADD_MEMBERS_PROGRESS_OVERVIEW_STAGES = SPACE_ADD_MEMBERS_PROGRESS_PHASES.map((phase) => `${phase.stage}/${phase.phase}`);
2513
+ const SPACE_ADD_MEMBERS_AGENT_OVERVIEW_STAGES = [
2514
+ "check_arguments",
2515
+ "authenticate",
2516
+ "check_profile",
2517
+ "resolve_target",
2518
+ "add_members/write_membership",
2519
+ "record_join_notices/write_join_notices"
2520
+ ];
2505
2521
  function resolveSpaceAddMembersProgressPhase(input) {
2506
2522
  const contract = SPACE_ADD_MEMBERS_PROGRESS_PHASES.find((phase) => phase.stage === input.stage && phase.phase === input.phase);
2507
2523
  if (!contract) throw new Error(`unknown space add-members progress phase: ${input.stage}/${input.phase}`);
@@ -3265,7 +3281,7 @@ const COMMAND_ENTRY_CONTRACTS = {
3265
3281
  },
3266
3282
  notes: ["space is single-select; members support one or many selections.", "already-joined profiles are skipped automatically."],
3267
3283
  pattern: getSpaceMemberSelectionPatternContract("add-members"),
3268
- stages: SPACE_ADD_MEMBERS_PROGRESS_OVERVIEW_STAGES,
3284
+ stages: [...SPACE_ADD_MEMBERS_AGENT_OVERVIEW_STAGES],
3269
3285
  summary: "Use `ats space add-members --profile <profile-id> <space-id> --member <profile-id...> --view agent` with explicit acting profile, Space ID, and member selection."
3270
3286
  },
3271
3287
  buildHints: (context) => getSpaceMemberSelectionPatternContract("add-members").buildHints?.({
@@ -28706,11 +28722,11 @@ async function readSystemLayoutWithRetry(path) {
28706
28722
  }
28707
28723
  const parsed = raw ? parseSystemLayout(raw) : null;
28708
28724
  if (parsed) return parsed;
28709
- if (attempt < SYSTEM_LAYOUT_READ_RETRY_COUNT - 1) await delay(SYSTEM_LAYOUT_READ_RETRY_DELAY_MS);
28725
+ if (attempt < SYSTEM_LAYOUT_READ_RETRY_COUNT - 1) await delay$1(SYSTEM_LAYOUT_READ_RETRY_DELAY_MS);
28710
28726
  }
28711
28727
  return null;
28712
28728
  }
28713
- function delay(ms) {
28729
+ function delay$1(ms) {
28714
28730
  return new Promise((resolve) => {
28715
28731
  setTimeout(resolve, ms);
28716
28732
  });
@@ -60432,13 +60448,7 @@ function renderProfileSummaryCard(input) {
60432
60448
  });
60433
60449
  }
60434
60450
  function buildAgentCreateNextSteps(profile) {
60435
- if (profile.profileKind === "agent") return [
60436
- formatAtsCliCommand(`ats setup --profile ${profile.atsProfileId} --local-agent <local-agent-id> --view agent`),
60437
- formatAtsCliCommand(`ats setup --profile ${profile.atsProfileId} --view agent`),
60438
- formatAtsCliCommand(`ats service status --profile ${profile.atsProfileId} --view agent`),
60439
- formatAtsCliCommand(`ats profiles show ${profile.atsProfileId} --view agent`),
60440
- formatAtsCliCommand(`ats whoami --profile ${profile.atsProfileId} --view agent`)
60441
- ];
60451
+ if (profile.profileKind === "agent") return [formatAtsCliCommand(`ats space add-members <space-id> --member ${profile.atsProfileId} --profile <human-profile-id> --view agent`)];
60442
60452
  return [
60443
60453
  formatAtsCliCommand("ats profiles list --view agent"),
60444
60454
  formatAtsCliCommand(`ats profiles set ${profile.atsProfileId} --view agent`),
@@ -60452,28 +60462,24 @@ function buildAgentCreateWakeReadinessData(profile) {
60452
60462
  return {
60453
60463
  identityCreated: true,
60454
60464
  wakeReadinessVerified: false,
60455
- wakeReadinessNote: "This command created the Agent Profile identity. It did not verify that this profile can be woken from a Space or reply from this computer."
60465
+ wakeReadinessNote: "Profile creation creates the Space identity. Add it to a Space when the user wants this teammate to participate."
60456
60466
  };
60457
60467
  }
60458
60468
  function buildProfileCreateWakeReadinessRows(input) {
60459
60469
  if (input.state !== "created" || input.profile.profileKind !== "agent") return [];
60460
60470
  return [{
60461
- label: "Wake availability",
60462
- value: "Not verified by profile creation"
60471
+ label: "Space identity",
60472
+ value: "Created"
60463
60473
  }, {
60464
60474
  label: "Next",
60465
- value: `Run ${formatAtsCliCommand(`ats setup --profile ${input.profile.atsProfileId} --local-agent <local-agent-id>`)} before expecting this Agent Profile to reply from a Space.`
60475
+ value: "Add this profile to a Space when the user wants this teammate to participate."
60466
60476
  }];
60467
60477
  }
60468
60478
  function buildAgentUpdateNextSteps(profile) {
60479
+ if (profile.profileKind === "agent") return [formatAtsCliCommand(`ats profiles show ${profile.atsProfileId} --view agent`), formatAtsCliCommand(`ats space add-members <space-id> --member ${profile.atsProfileId} --profile <human-profile-id> --view agent`)];
60469
60480
  return [
60470
60481
  formatAtsCliCommand(`ats profiles show ${profile.atsProfileId} --view agent`),
60471
60482
  formatAtsCliCommand(`ats profiles update ${profile.atsProfileId} --name <new-name> --view agent`),
60472
- formatAtsCliCommand(`ats setup --profile ${profile.atsProfileId} --local-agent <local-agent-id> --view agent`),
60473
- formatAtsCliCommand(`ats setup --profile ${profile.atsProfileId} --view agent`),
60474
- formatAtsCliCommand(`ats profiles update ${profile.atsProfileId} --workspace-mode ats-managed --view agent`),
60475
- formatAtsCliCommand(`ats profiles update ${profile.atsProfileId} --workspace-mode custom --workspace-path /absolute/path --view agent`),
60476
- formatAtsCliCommand(`ats whoami --profile ${profile.atsProfileId} --view agent`),
60477
60483
  formatAtsCliCommand("ats profiles list --view agent")
60478
60484
  ];
60479
60485
  }
@@ -68222,7 +68228,14 @@ function isAgentProfile(profile) {
68222
68228
  //#region src/commands/setup.ts
68223
68229
  const ROUTE_CATALOG_SYNC_MAX_ATTEMPTS = 8;
68224
68230
  const ROUTE_CATALOG_SYNC_RETRY_DELAY_MS = 750;
68231
+ const SETUP_COMMAND_LOCK_PROFILE = "setup";
68232
+ const SETUP_COMMAND_LOCK_KEY = "setup-command";
68225
68233
  async function runSetup(input) {
68234
+ return await withSetupCommandLock(input, async () => {
68235
+ await runSetupUnlocked(input);
68236
+ });
68237
+ }
68238
+ async function runSetupUnlocked(input) {
68226
68239
  if (shouldUseSetupLocalAgentConnection(input)) {
68227
68240
  const setupResult = await runSetupLocalAgentConnection(input);
68228
68241
  if (setupResult.status !== "completed") return;
@@ -68326,6 +68339,33 @@ async function runSetup(input) {
68326
68339
  view: runtime.resolvedView
68327
68340
  });
68328
68341
  }
68342
+ async function withSetupCommandLock(input, run) {
68343
+ try {
68344
+ return await runWithHeldLock({
68345
+ lock: await acquireLock({
68346
+ atsProfileId: SETUP_COMMAND_LOCK_PROFILE,
68347
+ key: SETUP_COMMAND_LOCK_KEY,
68348
+ meta: {
68349
+ command: "ats setup",
68350
+ path: shouldUseSetupLocalAgentConnection(input) ? "delegated_or_profile_setup" : "local_setup",
68351
+ view: input.view ?? null
68352
+ }
68353
+ }),
68354
+ run,
68355
+ releaseContext: {
68356
+ atsProfileId: SETUP_COMMAND_LOCK_PROFILE,
68357
+ component: "setup_command",
68358
+ lockKey: SETUP_COMMAND_LOCK_KEY
68359
+ }
68360
+ });
68361
+ } catch (error) {
68362
+ if (isAtsLockError(error)) throw new Error(buildSetupCommandLockMessage(error));
68363
+ throw error;
68364
+ }
68365
+ }
68366
+ function buildSetupCommandLockMessage(error) {
68367
+ return `Another ATS setup is already running on this computer. Wait for it to finish, then run setup again.${error.metaRaw ? ` Existing setup: ${error.metaRaw}` : ""}`;
68368
+ }
68329
68369
  function shouldUseSetupLocalAgentConnection(input) {
68330
68370
  return Boolean(normalizeOptionalString$5(input.startSession) ?? normalizeOptionalString$5(input.humanProfile) ?? normalizeOptionalString$5(input.profile) ?? (input.agent && input.agent.length > 0 ? "agent" : null));
68331
68371
  }
@@ -69434,7 +69474,7 @@ const STAGES_BY_COMMAND = {
69434
69474
  "resolve_target",
69435
69475
  "read_or_update_guide"
69436
69476
  ],
69437
- add_members: SPACE_ADD_MEMBERS_PROGRESS_OVERVIEW_STAGES,
69477
+ add_members: SPACE_ADD_MEMBERS_AGENT_OVERVIEW_STAGES,
69438
69478
  remove_members: [
69439
69479
  "check_arguments",
69440
69480
  "authenticate",
@@ -83740,13 +83780,13 @@ function handleBlockedSpaceJoinLocalParticipation(input) {
83740
83780
  if (input.assessmentState === "not_needed") {
83741
83781
  input.presenter.line({
83742
83782
  code: "space.join.service.not_needed",
83743
- text: formatInlineAtsCliCommands("ATS will keep joining this space without local Wake participation on this device. Space membership is separate from whether a local agent can reply from this computer. If you later want local agents from this device to reply, run `ats service status` and follow its recovery guidance.")
83783
+ text: formatInlineAtsCliCommands("ATS will join this space. Local agent participation on this device can be checked later with `ats service status`.")
83744
83784
  });
83745
83785
  return "continue";
83746
83786
  }
83747
83787
  input.presenter.line({
83748
83788
  code: "space.join.service.pending",
83749
- text: formatInlineAtsCliCommands("ATS will keep joining this space without local Wake participation on this device. Space membership is separate from whether a local agent can reply from this computer. Run `ats service status` to inspect local participation before expecting background replies.")
83789
+ text: formatInlineAtsCliCommands("ATS will join this space. Local agent participation on this device can be checked separately with `ats service status`.")
83750
83790
  });
83751
83791
  return "continue";
83752
83792
  }
@@ -83759,10 +83799,10 @@ function buildSpaceJoinMembershipOnlyMessage(input) {
83759
83799
  const profileId = input.atsProfile.atsProfileId;
83760
83800
  const reasonCodes = input.reasonCodes;
83761
83801
  const statusCommand = formatAtsCliCommand(`ats service status --profile ${profileId}`);
83762
- 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 choose a local agent in ATS Web.`;
83763
- 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 formatInlineAtsCliCommands(`${profileName} will join this space, but it will not wake or reply from this device yet. Space membership is separate from local Wake participation. Use \`${statusCommand}\` to check this device; if local participation is unhealthy, follow the status or repair guidance before expecting background replies.`);
83802
+ if (reasonCodes.includes("profile.unbound")) return `${profileName} will join this space. Choose its local agent in ATS Web when you want it to handle work from this device.`;
83803
+ 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 formatInlineAtsCliCommands(`${profileName} will join this space. ATS has not confirmed its local reply path on this device yet. Use \`${statusCommand}\` to check this device.`);
83764
83804
  if (reasonCodes.includes("controller.launch.needs_repair") || reasonCodes.includes("controller.launch.choice_required") || reasonCodes.includes("workspace.missing") || reasonCodes.includes("workspace.invalid")) return formatInlineAtsCliCommands(`${profileName} will join this space, but its local agent setup on this device needs attention. Use \`ats agents list\` to find the local agent, then run \`ats agents repair --agent <agent-id>\`.`);
83765
- 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 check Local Agents in ATS Web.`;
83805
+ return `${profileName} will join this space. Check Local Agents in ATS Web when you want it to handle work from this device.`;
83766
83806
  }
83767
83807
  function buildJoinTargetMissingMessage(input) {
83768
83808
  return formatMessage(input.resolvedView, "space.target.missing", { command: "join" });
@@ -84931,6 +84971,8 @@ const TRAILING_SLASHES_RE = /\/+$/u;
84931
84971
  const SPACE_CREATE_AUTH_CHECK_TIMEOUT_MS = 8e3;
84932
84972
  const SPACE_CREATE_AUTH_CHECK_LOOPBACK_TIMEOUT_MS = 1500;
84933
84973
  const SPACE_LOADING_SPINNER_MESSAGE = "Loading spaces...";
84974
+ const SPACE_ADD_MEMBERS_PROJECTION_WAIT_MAX_ATTEMPTS = 3;
84975
+ const SPACE_ADD_MEMBERS_PROJECTION_WAIT_DELAY_MS = 500;
84934
84976
  function requireReadableSpaceConfigList(result, operation) {
84935
84977
  if (result.kind === "corrupt") throw toSpaceConfigListIntegrityError({
84936
84978
  operation,
@@ -85010,6 +85052,8 @@ const SPACE_MEMBER_PROFILE_RECONNECT_REASON_CODES = new Set([
85010
85052
  "local_service.identity_mismatch"
85011
85053
  ]);
85012
85054
  const SPACE_MEMBER_SERVICE_STATUS_REASON_CODES = new Set([
85055
+ "agent_profile_binding.local_service_not_connected",
85056
+ "agent_profile_binding.execution_readiness_pending",
85013
85057
  "route.offline",
85014
85058
  "route.not_registered",
85015
85059
  "service.gateway_chain_unhealthy",
@@ -85133,6 +85177,7 @@ async function runSpaceCreate(input) {
85133
85177
  guide: typeof parsed.meta?.guide === "string" ? parsed.meta.guide : createInput.value.guide
85134
85178
  });
85135
85179
  emitSpaceCreateSuccessOutput({
85180
+ baseUrl,
85136
85181
  presenter: contextPresenter,
85137
85182
  parsed,
85138
85183
  outputMode,
@@ -85151,6 +85196,7 @@ async function runSpaceCreate(input) {
85151
85196
  const joinNow = isHumanInteractiveSpaceRuntime(contextRuntime) ? Boolean(createInput.value.joinInput) : await resolveJoinAfterCreate(createInput.value.joinInput, contextInteractive);
85152
85197
  if (contextRuntime.resolvedView === "agent") {
85153
85198
  emitSpaceCreateAgentFollowUp({
85199
+ baseUrl,
85154
85200
  presenter: contextPresenter,
85155
85201
  profileId: atsProfile.atsProfileId,
85156
85202
  spaceId: parsed.spaceId,
@@ -85186,12 +85232,14 @@ async function runSpaceCreate(input) {
85186
85232
  }
85187
85233
  }
85188
85234
  function emitSpaceCreateAgentFollowUp(input) {
85235
+ const spaceUrl = buildSpaceWebUrl(input.baseUrl, input.spaceId);
85189
85236
  const historyCommand = formatAtsCliCommand(`ats space history ${input.spaceId} --profile ${input.profileId} --kind text --brief --limit 20 --view agent`);
85190
85237
  const sendCommand = formatAtsCliCommand(`ats space send ${input.spaceId} --profile ${input.profileId} "hello" --view agent`);
85191
85238
  input.presenter.data({
85192
85239
  code: "space.create.next_commands",
85193
85240
  payload: {
85194
85241
  spaceId: input.spaceId,
85242
+ spaceUrl,
85195
85243
  joinRequested: input.joinRequested,
85196
85244
  joinedByCreate: true,
85197
85245
  note: "Agent View create returns after creating the Space. Use explicit follow-up commands instead of opening a long-running join session.",
@@ -85278,6 +85326,9 @@ function renderHumanSpaceCreateCard(input) {
85278
85326
  const rows = [{
85279
85327
  label: "🆔 Space ID",
85280
85328
  value: input.spaceId
85329
+ }, {
85330
+ label: "🔗 Space URL",
85331
+ value: input.spaceUrl
85281
85332
  }];
85282
85333
  if (input.passwordProtected) {
85283
85334
  rows.push({
@@ -85563,6 +85614,19 @@ function toAuthEndpoint(baseUrl, pathname) {
85563
85614
  url.hash = "";
85564
85615
  return url.toString();
85565
85616
  }
85617
+ function buildSpaceWebUrl(baseUrl, spaceId) {
85618
+ const url = new URL(baseUrl);
85619
+ if (isLoopbackHostname(url.hostname) && url.port === "8080") url.port = "3000";
85620
+ if (url.hostname === "gateway.ats.sh") {
85621
+ url.hostname = "ats.sh";
85622
+ url.port = "";
85623
+ url.protocol = "https:";
85624
+ }
85625
+ url.pathname = `/app/spaces/${encodeURIComponent(spaceId)}`;
85626
+ url.search = "";
85627
+ url.hash = "";
85628
+ return url.toString();
85629
+ }
85566
85630
  function resolveSpaceCreateAuthCheckTimeoutMs(endpoint) {
85567
85631
  const url = parseUrlOrNull(endpoint);
85568
85632
  if (url && isLoopbackHostname(url.hostname)) return SPACE_CREATE_AUTH_CHECK_LOOPBACK_TIMEOUT_MS;
@@ -85579,10 +85643,14 @@ function isLoopbackHostname(hostname) {
85579
85643
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
85580
85644
  }
85581
85645
  function emitSpaceCreateSuccessOutput(input) {
85646
+ const spaceUrl = buildSpaceWebUrl(input.baseUrl, input.parsed.spaceId);
85582
85647
  if (input.outputMode === "ndjson") {
85583
85648
  input.presenter.data({
85584
85649
  code: "space.create.response",
85585
- payload: input.parsed
85650
+ payload: {
85651
+ ...input.parsed,
85652
+ spaceUrl
85653
+ }
85586
85654
  });
85587
85655
  return;
85588
85656
  }
@@ -85590,6 +85658,7 @@ function emitSpaceCreateSuccessOutput(input) {
85590
85658
  renderHumanSpaceCreateCard({
85591
85659
  presenter: input.presenter,
85592
85660
  spaceId: input.parsed.spaceId,
85661
+ spaceUrl,
85593
85662
  passwordProtected: input.passwordProtected
85594
85663
  });
85595
85664
  return;
@@ -85599,6 +85668,14 @@ function emitSpaceCreateSuccessOutput(input) {
85599
85668
  text: formatMessage(input.resolvedView, "space.create.created", { spaceId: input.parsed.spaceId }),
85600
85669
  data: { spaceId: input.parsed.spaceId }
85601
85670
  });
85671
+ input.presenter.line({
85672
+ code: "space.create.url",
85673
+ text: `Space URL: ${spaceUrl}`,
85674
+ data: {
85675
+ spaceId: input.parsed.spaceId,
85676
+ spaceUrl
85677
+ }
85678
+ });
85602
85679
  if (!input.passwordProtected) return;
85603
85680
  input.presenter.line({
85604
85681
  code: "space.password.warning",
@@ -86057,7 +86134,7 @@ async function runSpaceAddMembers(input) {
86057
86134
  resolvedView: runtime.resolvedView,
86058
86135
  interactive
86059
86136
  });
86060
- await emitSpaceCommandPreflight({
86137
+ if (shouldRunSpaceAddMembersDiagnosticPreflight(input, explicitMemberIds)) await emitSpaceCommandPreflight({
86061
86138
  presenter,
86062
86139
  command: "add_members",
86063
86140
  profile: atsProfile,
@@ -86249,6 +86326,12 @@ async function runSpaceLeave(input) {
86249
86326
  throw error;
86250
86327
  }
86251
86328
  }
86329
+ function shouldRunSpaceAddMembersDiagnosticPreflight(input, explicitMemberIds) {
86330
+ return input.details === true || input.all === true || explicitMemberIds.length === 0;
86331
+ }
86332
+ function shouldUseFastExplicitSpaceAddMembers(input) {
86333
+ return input.details !== true && input.explicitMemberIds.length > 0;
86334
+ }
86252
86335
  async function runSpaceAddMembersForTarget(input) {
86253
86336
  const targetBaseUrl = input.hasExplicitBaseUrlInput ? input.baseUrl : input.target.baseUrl ?? input.baseUrl;
86254
86337
  const authenticatedGatewayUrl = await ensureSpaceCommandGatewayAuthenticationOrCancel({
@@ -86266,6 +86349,7 @@ async function runSpaceAddMembersForTarget(input) {
86266
86349
  };
86267
86350
  const explicitMemberIds = normalizeRequestedMemberIds(input.explicitMembers);
86268
86351
  const progress = input.details || explicitMemberIds.length === 0 ? addMembersProgress : void 0;
86352
+ const includeLocalParticipation = progress !== void 0 || explicitMemberIds.length === 0;
86269
86353
  const candidates = await resolveSpaceMemberCandidatesForAdd({
86270
86354
  currentProfile: input.atsProfile,
86271
86355
  currentOwnerUserId: input.atsProfile.ownerUserId,
@@ -86273,7 +86357,12 @@ async function runSpaceAddMembersForTarget(input) {
86273
86357
  baseUrl: targetBaseUrl,
86274
86358
  password: input.password,
86275
86359
  explicitMemberIds,
86276
- progress
86360
+ progress,
86361
+ fastExplicitMembers: shouldUseFastExplicitSpaceAddMembers({
86362
+ details: input.details,
86363
+ explicitMemberIds
86364
+ }),
86365
+ includeLocalParticipation
86277
86366
  });
86278
86367
  if (candidates.length === 0 && explicitMemberIds.length === 0) {
86279
86368
  input.presenter.line({
@@ -86305,15 +86394,10 @@ async function runSpaceAddMembersForTarget(input) {
86305
86394
  selectedProfileIds
86306
86395
  });
86307
86396
  if (selectedCandidates.length === 0) return { status: "completed" };
86308
- emitSpaceAddMembersProgressIfRequested(progress, {
86309
- stage: "check_local_wake_readiness",
86310
- phase: "start",
86311
- summary: "Checking whether selected agent profiles can wake from this computer. Members can still be added if local Wake setup needs attention.",
86312
- profileIds: selectedCandidates.map((candidate) => candidate.profileId)
86313
- });
86314
- const servicePreparationResult = await ensureSpaceMemberCandidatesLocalParticipationReady({
86397
+ const servicePreparationResult = await prepareSpaceAddMembersLocalParticipationIfRequested({
86315
86398
  atsProfile: input.atsProfile,
86316
86399
  candidates: selectedCandidates,
86400
+ enabled: includeLocalParticipation,
86317
86401
  progress,
86318
86402
  presenter: input.presenter,
86319
86403
  resolvedView: input.runtime.resolvedView,
@@ -86340,6 +86424,14 @@ async function runSpaceAddMembersForTarget(input) {
86340
86424
  baseUrl: targetBaseUrl,
86341
86425
  password: input.password
86342
86426
  });
86427
+ await waitForAddedMembersWakeProjection({
86428
+ actorProfile: input.atsProfile,
86429
+ baseUrl: targetBaseUrl,
86430
+ password: input.password,
86431
+ progress,
86432
+ result,
86433
+ space: input.target.space
86434
+ });
86343
86435
  emitSpaceAddMembersProgressIfRequested(progress, {
86344
86436
  stage: "record_join_notices",
86345
86437
  phase: "write_join_notices",
@@ -86375,6 +86467,28 @@ async function runSpaceAddMembersForTarget(input) {
86375
86467
  });
86376
86468
  return { status: "completed" };
86377
86469
  }
86470
+ async function prepareSpaceAddMembersLocalParticipationIfRequested(input) {
86471
+ if (!input.enabled) return {
86472
+ status: "continue",
86473
+ candidates: input.candidates
86474
+ };
86475
+ emitSpaceAddMembersProgressIfRequested(input.progress, {
86476
+ stage: "check_local_wake_readiness",
86477
+ phase: "start",
86478
+ summary: "Checking whether selected agent profiles can handle work from this computer. Members can still be added if local setup needs attention.",
86479
+ profileIds: input.candidates.map((candidate) => candidate.profileId)
86480
+ });
86481
+ return await ensureSpaceMemberCandidatesLocalParticipationReady({
86482
+ atsProfile: input.atsProfile,
86483
+ candidates: input.candidates,
86484
+ progress: input.progress,
86485
+ presenter: input.presenter,
86486
+ resolvedView: input.resolvedView,
86487
+ allowPrompt: input.allowPrompt,
86488
+ spaceId: input.spaceId,
86489
+ view: input.view
86490
+ });
86491
+ }
86378
86492
  function emitSpaceAddMembersProgressIfRequested(progress, input) {
86379
86493
  if (!progress) return;
86380
86494
  emitAgentSpaceAddMembersProgress({
@@ -86423,13 +86537,13 @@ function buildSpaceAddMembersLocalWakePlanSummary(plan) {
86423
86537
  if (assessment.profileKind !== "agent") continue;
86424
86538
  counts.set(assessment.localParticipationState, (counts.get(assessment.localParticipationState) ?? 0) + 1);
86425
86539
  }
86426
- return `Local Wake plan: ${[
86427
- formatLocalWakePlanCount(counts.get("ready") ?? 0, "available for local Wake"),
86540
+ return `Local agent plan: ${[
86541
+ formatLocalWakePlanCount(counts.get("ready") ?? 0, "can handle work from this device"),
86428
86542
  formatLocalWakePlanCount(counts.get("needs_agent_prepare") ?? 0, "need agent preparation"),
86429
86543
  formatLocalWakePlanCount(counts.get("needs_ats_service") ?? 0, "need ATS Service"),
86430
86544
  formatLocalWakePlanCount(counts.get("membership_only") ?? 0, "will be added as members only"),
86431
- formatLocalWakePlanCount(counts.get("not_needed") ?? 0, "do not need local Wake setup")
86432
- ].filter((part) => Boolean(part)).join(", ")}. Adding members is separate from making them available for Wake on this computer.`;
86545
+ formatLocalWakePlanCount(counts.get("not_needed") ?? 0, "do not need local agent setup")
86546
+ ].filter((part) => Boolean(part)).join(", ")}. Adding members is separate from local agent participation on this computer.`;
86433
86547
  }
86434
86548
  function formatLocalWakePlanCount(count, label) {
86435
86549
  return count > 0 ? `${String(count)} ${label}` : null;
@@ -86592,7 +86706,7 @@ async function ensureSpaceMemberCandidatesLocalParticipationReady(input) {
86592
86706
  candidates: preparedCandidates.map(toActionParticipationCandidateFromSpaceMemberCandidate)
86593
86707
  }).shouldOfferServiceGate) input.presenter.line({
86594
86708
  code: input.resolvedView === "human" && input.allowPrompt ? "space.add_members.service.declined" : "space.add_members.service.deferred",
86595
- text: formatInlineAtsCliCommands("ATS will continue adding these profiles without local Wake participation on this device. Space membership is separate from whether a local agent can reply from this computer. Run `ats service status` to inspect local participation before expecting background replies.")
86709
+ text: formatInlineAtsCliCommands("ATS will add these profiles to the Space. Local agent participation can be checked separately with `ats service status`.")
86596
86710
  });
86597
86711
  return {
86598
86712
  status: "continue",
@@ -86666,17 +86780,18 @@ function emitSpaceMemberJoinOnlyWarnings(input) {
86666
86780
  }
86667
86781
  function buildSpaceMemberJoinOnlyWarning(candidate) {
86668
86782
  if (candidate.profileKind !== "agent") return null;
86783
+ if (candidate.localParticipationChecked !== true) return null;
86669
86784
  if (isSpaceMemberCandidateBackgroundReplyReady(candidate)) return null;
86670
86785
  const profileLabel = resolveSpaceMemberTerminalProfileLabel(candidate.profileName);
86671
- if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) return `${profileLabel} can join this space now, but ${resolveSpaceMemberTerminalLocalTargetLabel({
86786
+ if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) return `${profileLabel} can join this space now. ${resolveSpaceMemberTerminalLocalTargetLabel({
86672
86787
  displayName: candidate.localTargetDisplayName,
86673
86788
  agentId: candidate.localTargetAgentId
86674
- })} still needs local setup on this device before it can wake or reply in the background. Use \`${buildSpaceMemberRepairCommand({
86789
+ })} still needs local setup on this device before it can handle work from the Space. Use \`${buildSpaceMemberRepairCommand({
86675
86790
  agentId: candidate.localTargetAgentId,
86676
86791
  profileId: candidate.profileId
86677
- })}\` before expecting background replies.`;
86792
+ })}\` to repair the local agent.`;
86678
86793
  const nextAction = resolveSpaceMemberJoinOnlyNextAction(candidate);
86679
- return `${profileLabel} can join this space now, but it will not wake or reply from this device yet. ${resolveSpaceMemberJoinOnlyReason(candidate)} ${formatSpaceMemberRecoveryNextAction(nextAction)}`;
86794
+ return `${profileLabel} can join this space now. ATS has not confirmed its local reply path on this device yet. ${resolveSpaceMemberJoinOnlyReason(candidate)} ${formatSpaceMemberRecoveryNextAction(nextAction)}`;
86680
86795
  }
86681
86796
  function resolveSpaceMemberJoinOnlyNextAction(candidate) {
86682
86797
  if (candidate.localParticipationReadiness === "not_running") return {
@@ -86754,6 +86869,7 @@ async function refreshSpaceMemberCandidatesLocalReadiness(candidates, progress,
86754
86869
  async function enrichSpaceMemberCandidateLocalParticipationReadiness(candidate, progress, options = {}) {
86755
86870
  if (candidate.profileKind !== "agent" || !candidate.atsProfile || candidate.controllerEnabled !== true || typeof candidate.controllerRef !== "string" || candidate.controllerRef.trim().length === 0) return {
86756
86871
  ...candidate,
86872
+ localParticipationChecked: true,
86757
86873
  localParticipationReadiness: null,
86758
86874
  localParticipationReasonCodes: [],
86759
86875
  localParticipationReasonText: null
@@ -86792,6 +86908,7 @@ async function enrichSpaceMemberCandidateLocalParticipationReadiness(candidate,
86792
86908
  }
86793
86909
  if (!snapshot) return {
86794
86910
  ...candidate,
86911
+ localParticipationChecked: true,
86795
86912
  localParticipationReadiness: null,
86796
86913
  localParticipationReasonCodes: [],
86797
86914
  localParticipationReasonText: null
@@ -86803,6 +86920,7 @@ async function enrichSpaceMemberCandidateLocalParticipationReadiness(candidate,
86803
86920
  });
86804
86921
  return {
86805
86922
  ...candidate,
86923
+ localParticipationChecked: true,
86806
86924
  localParticipationReadiness: readinessSummary.serviceReadiness,
86807
86925
  localParticipationReasonCodes: [...snapshot.deviceReplyReadiness.reasonCodes],
86808
86926
  localParticipationReasonText: snapshot.deviceReplyReadiness.reasonText
@@ -88079,6 +88197,9 @@ function mapSpaceLeaveFailureToGuideError(input) {
88079
88197
  return input.error instanceof Error ? input.error : new Error(String(input.error));
88080
88198
  }
88081
88199
  async function resolveSpaceMemberCandidatesForAdd(input) {
88200
+ const explicitMemberIds = normalizeRequestedMemberIds(input.explicitMemberIds);
88201
+ const includeLocalParticipation = input.includeLocalParticipation !== false;
88202
+ if (input.fastExplicitMembers === true && explicitMemberIds.length > 0) return explicitMemberIds.map(buildFastExplicitSpaceMemberCandidate);
88082
88203
  if (input.progress) emitAgentSpaceAddMembersProgress({
88083
88204
  ...input.progress,
88084
88205
  stage: "resolve_candidates",
@@ -88086,23 +88207,19 @@ async function resolveSpaceMemberCandidatesForAdd(input) {
88086
88207
  summary: "Reading active local profile identities for this Space."
88087
88208
  });
88088
88209
  const profiles = await listAtsProfiles();
88089
- if (input.progress) emitAgentSpaceAddMembersProgress({
88210
+ if (input.progress && includeLocalParticipation) emitAgentSpaceAddMembersProgress({
88090
88211
  ...input.progress,
88091
88212
  stage: "resolve_candidates",
88092
88213
  phase: "read_local_targets",
88093
88214
  summary: "Reading local agent controller state for candidate profiles before selection."
88094
88215
  });
88095
- const localTargetStates = input.progress ? await runSpaceAddMembersProgressHeartbeat({
88096
- ...input.progress,
88097
- stage: "resolve_candidates",
88098
- phase: "read_local_targets_wait",
88099
- summary: "Still reading local agent controller state for candidate profiles before selection.",
88100
- operation: async () => await listAgentTargetStates().catch(() => [])
88101
- }) : await listAgentTargetStates().catch(() => []);
88216
+ const localTargetStates = await listSpaceAddMembersLocalTargetStatesIfRequested({
88217
+ includeLocalParticipation,
88218
+ progress: input.progress
88219
+ });
88102
88220
  const localTargetStateById = new Map(localTargetStates.map((state) => [state.agentId, state]));
88103
88221
  const ownerProfileNameByOwnerUserId = buildOwnerProfileNameByOwnerUserId(profiles);
88104
88222
  const candidateProfiles = profiles.filter((profile) => profile.status === "active" && isSpaceMembershipProfileKind(profile.profileKind) && profile.atsProfileId !== input.currentProfile.atsProfileId).filter((profile) => {
88105
- const explicitMemberIds = input.explicitMemberIds ?? [];
88106
88223
  if (explicitMemberIds.length > 0 && !explicitMemberIds.includes(profile.atsProfileId)) return false;
88107
88224
  return typeof input.currentOwnerUserId !== "string" || input.currentOwnerUserId.trim().length === 0 || profile.ownerUserId === input.currentOwnerUserId;
88108
88225
  });
@@ -88119,29 +88236,58 @@ async function resolveSpaceMemberCandidatesForAdd(input) {
88119
88236
  ...input.password === void 0 ? {} : { spacePassword: input.password }
88120
88237
  });
88121
88238
  const memberProfileIds = new Set([...membersSnapshot.humans, ...membersSnapshot.agents].map((member) => member.profileId));
88122
- return await refreshSpaceMemberCandidatesLocalReadiness((await Promise.all(candidateProfiles.map((profile) => buildSpaceMemberCandidateFromProfile({
88239
+ const candidates = (await Promise.all(candidateProfiles.map((profile) => buildSpaceMemberCandidateFromProfile({
88123
88240
  baseUrl: input.baseUrl,
88124
88241
  profile,
88125
88242
  localTargetStateById,
88126
88243
  ownerProfileName: ownerProfileNameByOwnerUserId.get(profile.ownerUserId) ?? void 0,
88127
88244
  alreadyJoined: memberProfileIds.has(profile.atsProfileId),
88128
- progress: input.progress
88245
+ progress: input.progress,
88246
+ includeLocalParticipation
88129
88247
  })))).sort((left, right) => {
88130
88248
  const byName = left.profileName.localeCompare(right.profileName);
88131
88249
  if (byName !== 0) return byName;
88132
88250
  return left.profileId.localeCompare(right.profileId);
88133
- }), input.progress, { stage: "resolve_candidates" });
88251
+ });
88252
+ if (!includeLocalParticipation) return candidates;
88253
+ return await refreshSpaceMemberCandidatesLocalReadiness(candidates, input.progress, { stage: "resolve_candidates" });
88134
88254
  }
88135
- async function buildSpaceMemberCandidateFromProfile(input) {
88136
- if (input.progress) emitAgentSpaceAddMembersProgress({
88255
+ function buildFastExplicitSpaceMemberCandidate(profileId) {
88256
+ return {
88257
+ profileId,
88258
+ profileName: profileId,
88259
+ profileKind: profileId.startsWith("agt_") ? "agent" : "human",
88260
+ targetMentionLabel: null,
88261
+ localTargetAgentId: null,
88262
+ localTargetDisplayName: null,
88263
+ localTargetLaunchStatus: null,
88264
+ localTargetSelectable: false,
88265
+ localParticipationReadiness: null,
88266
+ localParticipationChecked: false,
88267
+ localParticipationReasonCodes: [],
88268
+ localParticipationReasonText: null,
88269
+ alreadyJoined: false,
88270
+ needsActivation: false
88271
+ };
88272
+ }
88273
+ async function listSpaceAddMembersLocalTargetStatesIfRequested(input) {
88274
+ if (!input.includeLocalParticipation) return [];
88275
+ if (!input.progress) return await listAgentTargetStates().catch(() => []);
88276
+ return await runSpaceAddMembersProgressHeartbeat({
88137
88277
  ...input.progress,
88138
88278
  stage: "resolve_candidates",
88139
- phase: "read_runtime_capability",
88140
- summary: `Reading runtime capability projection for ${resolveSpaceMemberTerminalProfileLabel(input.profile.profileName)}.`,
88141
- profileId: input.profile.atsProfileId,
88142
- profileIds: [input.profile.atsProfileId]
88279
+ phase: "read_local_targets_wait",
88280
+ summary: "Still reading local agent controller state for candidate profiles before selection.",
88281
+ operation: async () => await listAgentTargetStates().catch(() => [])
88143
88282
  });
88144
- const runtimeCapability = input.progress ? await runSpaceAddMembersProgressHeartbeat({
88283
+ }
88284
+ async function resolveSpaceMemberCandidateRuntimeCapabilityIfRequested(input) {
88285
+ if (!input.includeLocalParticipation) return null;
88286
+ if (!input.progress) return await resolveAgentProfilePrimaryRuntimeCapability({
88287
+ baseUrl: input.baseUrl,
88288
+ profile: input.profile
88289
+ }).catch(() => null);
88290
+ return await runSpaceAddMembersProgressHeartbeat({
88145
88291
  ...input.progress,
88146
88292
  stage: "resolve_candidates",
88147
88293
  phase: "read_runtime_capability_wait",
@@ -88152,10 +88298,24 @@ async function buildSpaceMemberCandidateFromProfile(input) {
88152
88298
  baseUrl: input.baseUrl,
88153
88299
  profile: input.profile
88154
88300
  }).catch(() => null)
88155
- }) : await resolveAgentProfilePrimaryRuntimeCapability({
88301
+ });
88302
+ }
88303
+ async function buildSpaceMemberCandidateFromProfile(input) {
88304
+ const includeLocalParticipation = input.includeLocalParticipation !== false;
88305
+ if (input.progress && includeLocalParticipation) emitAgentSpaceAddMembersProgress({
88306
+ ...input.progress,
88307
+ stage: "resolve_candidates",
88308
+ phase: "read_runtime_capability",
88309
+ summary: `Reading runtime capability projection for ${resolveSpaceMemberTerminalProfileLabel(input.profile.profileName)}.`,
88310
+ profileId: input.profile.atsProfileId,
88311
+ profileIds: [input.profile.atsProfileId]
88312
+ });
88313
+ const runtimeCapability = await resolveSpaceMemberCandidateRuntimeCapabilityIfRequested({
88156
88314
  baseUrl: input.baseUrl,
88157
- profile: input.profile
88158
- }).catch(() => null);
88315
+ includeLocalParticipation,
88316
+ profile: input.profile,
88317
+ progress: input.progress
88318
+ });
88159
88319
  const controllerRef = runtimeCapability?.capabilityRef ?? void 0;
88160
88320
  const controllerEnabled = runtimeCapability?.bindingState === "enabled_primary" && runtimeCapability.routable;
88161
88321
  const localTargetAgentId = resolveSpaceMemberCandidateLocalTargetAgentId({
@@ -88180,6 +88340,7 @@ async function buildSpaceMemberCandidateFromProfile(input) {
88180
88340
  localTargetLaunchStatus: localTargetState?.launchStatus ?? null,
88181
88341
  localTargetSelectable: localTargetState?.selectable ?? false,
88182
88342
  localParticipationReadiness: null,
88343
+ localParticipationChecked: false,
88183
88344
  localParticipationReasonCodes: [],
88184
88345
  localParticipationReasonText: null,
88185
88346
  alreadyJoined: input.alreadyJoined,
@@ -88508,14 +88669,19 @@ async function applySpaceAddMembers(input) {
88508
88669
  }
88509
88670
  const addedProfileIds = new Set(response.addedMemberIds);
88510
88671
  const alreadyMemberProfileIds = new Set(response.alreadyMemberIds);
88672
+ const responseItemByProfileId = new Map(response.items.map((item) => [item.profileId, item]));
88511
88673
  const lastJoinedAt = (/* @__PURE__ */ new Date()).toISOString();
88512
88674
  const spaceName = normalizeOptionalString$1(input.spaceName);
88513
88675
  for (const candidate of selectedCandidates) {
88676
+ const nextCandidate = hydrateSpaceMemberCandidateFromResponseItem({
88677
+ candidate,
88678
+ item: responseItemByProfileId.get(candidate.profileId)
88679
+ });
88514
88680
  if (addedProfileIds.has(candidate.profileId)) {
88515
- result.added.push(candidate);
88681
+ result.added.push(nextCandidate);
88516
88682
  continue;
88517
88683
  }
88518
- if (alreadyMemberProfileIds.has(candidate.profileId)) result.alreadyJoined.push(candidate);
88684
+ if (alreadyMemberProfileIds.has(candidate.profileId)) result.alreadyJoined.push(nextCandidate);
88519
88685
  }
88520
88686
  for (const candidate of result.added) try {
88521
88687
  await upsertProfileSpaceHistory({
@@ -88542,6 +88708,63 @@ async function applySpaceAddMembers(input) {
88542
88708
  }
88543
88709
  return result;
88544
88710
  }
88711
+ function hydrateSpaceMemberCandidateFromResponseItem(input) {
88712
+ if (!input.item) return input.candidate;
88713
+ return {
88714
+ ...input.candidate,
88715
+ profileName: input.item.profileName,
88716
+ profileKind: input.item.profileKind,
88717
+ ownerUserId: normalizeOptionalString$1(input.item.ownerUserId) ?? void 0,
88718
+ ownerName: normalizeOptionalString$1(input.item.ownerName) ?? void 0,
88719
+ targetMentionLabel: resolvePrimaryMentionLabelFromSpaceMemberItem(input.item),
88720
+ ...input.item.profileKind === "agent" ? { controllerRef: normalizeOptionalString$1(input.item.controllerRef) ?? input.candidate.controllerRef } : {}
88721
+ };
88722
+ }
88723
+ function resolvePrimaryMentionLabelFromSpaceMemberItem(item) {
88724
+ const mentionAlias = normalizeOptionalString$1(item.mentionAlias);
88725
+ if (mentionAlias) return mentionAlias;
88726
+ return resolveSingleSpaceMentionLabels({
88727
+ mentionAlias,
88728
+ profileId: item.profileId,
88729
+ profileName: item.profileName
88730
+ }).primaryMentionLabel;
88731
+ }
88732
+ async function waitForAddedMembersWakeProjection(input) {
88733
+ const expectedWakeableProfileIds = resolveExpectedWakeProjectionProfileIds(input.result);
88734
+ if (expectedWakeableProfileIds.length === 0) return;
88735
+ emitSpaceAddMembersProgressIfRequested(input.progress, {
88736
+ stage: "add_members",
88737
+ phase: "confirm_membership_projection",
88738
+ summary: "Confirming the Space can see newly added wakeable members before returning.",
88739
+ profileIds: expectedWakeableProfileIds
88740
+ });
88741
+ const expected = new Set(expectedWakeableProfileIds);
88742
+ for (let attempt = 0; attempt < SPACE_ADD_MEMBERS_PROJECTION_WAIT_MAX_ATTEMPTS; attempt += 1) {
88743
+ if (attempt > 0) await delay(SPACE_ADD_MEMBERS_PROJECTION_WAIT_DELAY_MS);
88744
+ let membersSnapshot;
88745
+ try {
88746
+ membersSnapshot = await createCliSpaceApi(input.baseUrl).getMembersSnapshot({
88747
+ spaceId: input.space,
88748
+ requestContext: buildAtsRequestContextFromProfile({ atsProfile: input.actorProfile }),
88749
+ ...input.password === void 0 ? {} : { spacePassword: input.password }
88750
+ });
88751
+ } catch {
88752
+ return;
88753
+ }
88754
+ if (hasExpectedWakeProjection(membersSnapshot, expected)) return;
88755
+ }
88756
+ }
88757
+ function resolveExpectedWakeProjectionProfileIds(result) {
88758
+ return result.added.filter((candidate) => candidate.profileKind === "agent" && candidate.localParticipationReadiness === "ready").map((candidate) => candidate.profileId);
88759
+ }
88760
+ function hasExpectedWakeProjection(membersSnapshot, expectedProfileIds) {
88761
+ const wakeableProfileIds = new Set(membersSnapshot.agents.filter((member) => member.status === "active" && member.publicReplyPresence?.status === "ready").map((member) => member.profileId));
88762
+ for (const profileId of expectedProfileIds) if (!wakeableProfileIds.has(profileId)) return false;
88763
+ return true;
88764
+ }
88765
+ async function delay(ms) {
88766
+ await new Promise((resolve) => setTimeout(resolve, ms));
88767
+ }
88545
88768
  function emitSpaceAddMembersResult(input) {
88546
88769
  const localParticipationFollowUps = input.result.added.map((candidate) => buildSpaceMemberJoinedFollowUp(candidate)).filter((followUp) => followUp !== null);
88547
88770
  if (input.resolvedView === "agent") {
@@ -88612,6 +88835,7 @@ function emitSpaceAddMembersResult(input) {
88612
88835
  }
88613
88836
  function buildSpaceMemberJoinedFollowUp(candidate) {
88614
88837
  if (candidate.profileKind !== "agent") return null;
88838
+ if (candidate.localParticipationChecked !== true) return null;
88615
88839
  if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) {
88616
88840
  const repairCommand = buildSpaceMemberRepairCommand({
88617
88841
  agentId: candidate.localTargetAgentId,
@@ -88625,7 +88849,7 @@ function buildSpaceMemberJoinedFollowUp(candidate) {
88625
88849
  return {
88626
88850
  profileId: candidate.profileId,
88627
88851
  reasonCodes: candidate.localParticipationReasonCodes ?? [],
88628
- text: `${profileLabel} joined this space, but ${agentLabel} still needs repair on this device before it can wake or reply in the background. Use \`${repairCommand}\` before expecting background replies.`
88852
+ text: `${profileLabel} joined this space. ${agentLabel} still needs repair on this device before it can handle work from the Space. Use \`${repairCommand}\` to repair the local agent.`
88629
88853
  };
88630
88854
  }
88631
88855
  if (!isSpaceMemberCandidateRunnableLocalParticipationCandidate(candidate)) {
@@ -88634,7 +88858,7 @@ function buildSpaceMemberJoinedFollowUp(candidate) {
88634
88858
  return {
88635
88859
  profileId: candidate.profileId,
88636
88860
  reasonCodes: candidate.localParticipationReasonCodes ?? [],
88637
- text: `${profileLabel} 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 choose a local agent. ${formatSpaceMemberRecoveryNextAction(nextAction)}`
88861
+ text: `${profileLabel} joined this space. ATS has not confirmed its local reply path on this device yet. ${formatSpaceMemberRecoveryNextAction(nextAction)}`
88638
88862
  };
88639
88863
  }
88640
88864
  return null;
@@ -88869,8 +89093,8 @@ async function persistSpaceAddMembersJoinNotices(input) {
88869
89093
  failedCount: 0
88870
89094
  };
88871
89095
  const failures = [];
88872
- let targetMentionLabelByProfileId;
88873
- try {
89096
+ let targetMentionLabelByProfileId = buildKnownMentionLabelByProfileId(input.addedMembers);
89097
+ if (!hasMentionLabelsForAllMembers(input.addedMembers, targetMentionLabelByProfileId)) try {
88874
89098
  targetMentionLabelByProfileId = buildSpacePrimaryMentionLabelByProfileId(await createCliSpaceApi(input.baseUrl).getMembersSnapshot({
88875
89099
  spaceId: input.space,
88876
89100
  requestContext: buildAtsRequestContextFromProfile({ atsProfile: input.actorProfile }),
@@ -88916,6 +89140,17 @@ async function persistSpaceAddMembersJoinNotices(input) {
88916
89140
  ...failures[0] ? { firstFailure: toStructuredGatewayFailure(failures[0].error) } : {}
88917
89141
  };
88918
89142
  }
89143
+ function buildKnownMentionLabelByProfileId(members) {
89144
+ const labelsByProfileId = /* @__PURE__ */ new Map();
89145
+ for (const member of members) {
89146
+ const targetMentionLabel = normalizeOptionalString$1(member.targetMentionLabel);
89147
+ if (targetMentionLabel) labelsByProfileId.set(member.profileId, targetMentionLabel);
89148
+ }
89149
+ return labelsByProfileId;
89150
+ }
89151
+ function hasMentionLabelsForAllMembers(members, labelsByProfileId) {
89152
+ return members.every((member) => labelsByProfileId.has(member.profileId));
89153
+ }
88919
89154
  async function postSpaceAddMemberJoinNotice(input) {
88920
89155
  const requestContext = buildAtsRequestContextFromProfile({ atsProfile: input.actorProfile });
88921
89156
  await createCliSpaceApi(input.baseUrl).postNormalMessage({
@@ -89101,8 +89336,9 @@ function resolveSpaceMemberCandidateStatusLabel(candidate) {
89101
89336
  const TRAILING_PERIOD_RE = /\.$/u;
89102
89337
  function resolveSpaceMemberCandidateStatusBadges(candidate) {
89103
89338
  if (candidate.profileKind !== "agent") return [];
89339
+ if (candidate.localParticipationChecked !== true) return [];
89104
89340
  if (isSpaceMemberCandidateBackgroundReplyReady(candidate)) return ["Local setup connected"];
89105
- if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) return ["Needs repair before background replies"];
89341
+ if (isSpaceMemberCandidateRepairableLocalParticipationCandidate(candidate)) return ["Needs repair"];
89106
89342
  return [resolveSpaceMemberJoinOnlyReason(candidate).replace(TRAILING_PERIOD_RE, ""), "Joins only for now"];
89107
89343
  }
89108
89344
  function isSpaceMemberCandidateBackgroundReplyReady(candidate) {
@@ -89126,7 +89362,7 @@ function resolveSpaceMemberProfileConnectionAction(candidate) {
89126
89362
  kind: "instruction"
89127
89363
  };
89128
89364
  if (candidate.controllerEnabled !== true || typeof candidate.controllerRef !== "string" || candidate.controllerRef.trim().length === 0 || reasonCodes.some((reasonCode) => SPACE_MEMBER_PROFILE_CONNECTION_REASON_CODES.has(reasonCode))) return {
89129
- instruction: "Open this Agent in ATS Web and choose the local agent that should handle Wake work.",
89365
+ instruction: "Open this Agent in ATS Web and choose the local agent that should handle work from the Space.",
89130
89366
  kind: "instruction"
89131
89367
  };
89132
89368
  if (reasonCodes.includes("controller.binding.disabled")) return {
@@ -89139,9 +89375,9 @@ function hasSpaceMemberReasonCode(candidate, reasonCodes) {
89139
89375
  return (candidate.localParticipationReasonCodes ?? []).some((reasonCode) => reasonCodes.has(reasonCode));
89140
89376
  }
89141
89377
  function formatSpaceMemberRecoveryNextAction(action) {
89142
- if (action.kind === "instruction") return `${action.instruction} Do this before expecting background replies.`;
89143
- if (action.command === "ats agents list") return formatInlineAtsCliCommands("Use `ats agents list` to find the local agent that needs repair before expecting background replies.");
89144
- return `Use \`${formatAtsCliCommand(action.command)}\` before expecting background replies.`;
89378
+ if (action.kind === "instruction") return action.instruction;
89379
+ if (action.command === "ats agents list") return formatInlineAtsCliCommands("Use `ats agents list` to find the local agent that needs repair.");
89380
+ return `Use \`${formatAtsCliCommand(action.command)}\` to check the local reply path.`;
89145
89381
  }
89146
89382
  function resolveSpaceMemberTerminalProfileLabel(profileName) {
89147
89383
  return sanitizeSingleLinePromptText(normalizeOptionalString$1(profileName)) || "This agent";