@p4code/cli 0.5.5 → 0.5.7

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/bin.mjs CHANGED
@@ -19,8 +19,11 @@ import "effect/LogLevel";
19
19
  import * as Path$1 from "effect/Path";
20
20
  import * as Schema$1 from "effect/Schema";
21
21
  import * as NodePath from "node:path";
22
+ import { isAbsolute } from "node:path";
22
23
  import * as NodeChildProcess from "node:child_process";
24
+ import { spawn } from "node:child_process";
23
25
  import * as NodeFS from "node:fs";
26
+ import { constants } from "node:fs";
24
27
  import * as DateTime from "effect/DateTime";
25
28
  import * as Duration from "effect/Duration";
26
29
  import * as Option from "effect/Option";
@@ -42,6 +45,7 @@ import * as Config from "effect/Config";
42
45
  import * as References from "effect/References";
43
46
  import * as HttpServerRequest$1 from "effect/unstable/http/HttpServerRequest";
44
47
  import * as NodeCrypto from "node:crypto";
48
+ import { createHash, randomUUID } from "node:crypto";
45
49
  import * as Encoding from "effect/Encoding";
46
50
  import * as Result$1 from "effect/Result";
47
51
  import * as Crypto from "effect/Crypto";
@@ -57,6 +61,7 @@ import * as Cause from "effect/Cause";
57
61
  import * as Exit from "effect/Exit";
58
62
  import * as NodeReadline from "node:readline";
59
63
  import * as NodeFSP from "node:fs/promises";
64
+ import { access } from "node:fs/promises";
60
65
  import * as Clock from "effect/Clock";
61
66
  import * as NodeStream from "@effect/platform-node/NodeStream";
62
67
  import * as NodeHttp from "node:http";
@@ -260,7 +265,7 @@ const make$116 = () => {
260
265
  const layer$119 = Layer.sync(NetService, make$116);
261
266
  //#endregion
262
267
  //#region package.json
263
- var version$1 = "0.5.5";
268
+ var version$1 = "0.5.7";
264
269
  //#endregion
265
270
  //#region src/config.ts
266
271
  /**
@@ -7823,6 +7828,7 @@ const TaskProgressPayload = Schema$1.Struct({
7823
7828
  usage: Schema$1.optional(Schema$1.Unknown),
7824
7829
  lastToolName: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
7825
7830
  toolUseId: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
7831
+ model: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
7826
7832
  subagentType: Schema$1.optional(TrimmedNonEmptyStringSchema$1)
7827
7833
  });
7828
7834
  const TaskCompletedPayload = Schema$1.Struct({
@@ -8838,7 +8844,11 @@ const DeviceHostSummary = Schema$1.Struct({
8838
8844
  label: TrimmedNonEmptyString.check(Schema$1.isMaxLength(MAX_HOST_LABEL_LENGTH)),
8839
8845
  platforms: Schema$1.Array(DevicePlatformAvailability),
8840
8846
  hubInstalled: Schema$1.Boolean,
8841
- agentDeviceInstalled: Schema$1.Boolean
8847
+ agentDeviceInstalled: Schema$1.Boolean,
8848
+ automation: Schema$1.optional(Schema$1.Struct({
8849
+ provider: Schema$1.Literals(["apple-xcode", "xcodebuildmcp"]),
8850
+ detail: Schema$1.String
8851
+ }))
8842
8852
  });
8843
8853
  /**
8844
8854
  * Lifecycle of the helper processes on a host. Tools are installed on first
@@ -13550,6 +13560,7 @@ const ComputerUseHostReport = Schema$1.Struct({
13550
13560
  "stopped"
13551
13561
  ]),
13552
13562
  previewApp: Schema$1.optional(Schema$1.String),
13563
+ previewAppIcon: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
13553
13564
  deviceControl: Schema$1.optional(Schema$1.NullOr(ComputerUseDeviceControl))
13554
13565
  });
13555
13566
  const ComputerUseControlState = Schema$1.Struct({
@@ -13562,6 +13573,7 @@ const ComputerUseControlState = Schema$1.Struct({
13562
13573
  "stop-unconfirmed"
13563
13574
  ]),
13564
13575
  previewApp: Schema$1.optional(Schema$1.String),
13576
+ previewAppIcon: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
13565
13577
  deviceControl: Schema$1.optional(Schema$1.NullOr(ComputerUseDeviceControl))
13566
13578
  });
13567
13579
  const ComputerUseControlInput = Schema$1.Struct({
@@ -13591,6 +13603,45 @@ const ComputerUseDeviceTarget = {
13591
13603
  deviceId: DeviceId,
13592
13604
  leaseId: DeviceId
13593
13605
  };
13606
+ const DeviceElementRef = TrimmedNonEmptyString.check(Schema$1.isMaxLength(128));
13607
+ const DeviceGesture = {
13608
+ direction: Schema$1.Literals([
13609
+ "up",
13610
+ "down",
13611
+ "left",
13612
+ "right"
13613
+ ]),
13614
+ distance: Schema$1.optional(Schema$1.Number.check(Schema$1.isGreaterThan(0), Schema$1.isLessThanOrEqualTo(1)))
13615
+ };
13616
+ const ComputerUseDeviceAction = Schema$1.Union([
13617
+ Schema$1.Struct({
13618
+ action: Schema$1.Literal("tap"),
13619
+ elementRef: DeviceElementRef
13620
+ }),
13621
+ Schema$1.Struct({
13622
+ action: Schema$1.Literal("type_text"),
13623
+ elementRef: DeviceElementRef,
13624
+ text: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(16e3)),
13625
+ replaceExisting: Schema$1.optional(Schema$1.Boolean)
13626
+ }),
13627
+ Schema$1.Struct({
13628
+ action: Schema$1.Literal("swipe"),
13629
+ withinElementRef: DeviceElementRef,
13630
+ ...DeviceGesture
13631
+ }),
13632
+ Schema$1.Struct({
13633
+ action: Schema$1.Literal("drag"),
13634
+ elementRef: DeviceElementRef,
13635
+ ...DeviceGesture
13636
+ }),
13637
+ Schema$1.Struct({
13638
+ action: Schema$1.Literal("batch"),
13639
+ steps: Schema$1.Array(Schema$1.Struct({
13640
+ action: Schema$1.Literal("tap"),
13641
+ elementRef: DeviceElementRef
13642
+ })).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(100))
13643
+ })
13644
+ ]);
13594
13645
  const ComputerUseDeviceOperation = Schema$1.Union([
13595
13646
  Schema$1.Struct({ method: Schema$1.Literal("list") }),
13596
13647
  Schema$1.Struct({
@@ -13605,7 +13656,18 @@ const ComputerUseDeviceOperation = Schema$1.Union([
13605
13656
  "screenshot",
13606
13657
  "close"
13607
13658
  ]),
13608
- ...ComputerUseDeviceTarget
13659
+ ...ComputerUseDeviceTarget,
13660
+ sinceScreenHash: Schema$1.optional(TrimmedNonEmptyString)
13661
+ }),
13662
+ Schema$1.Struct({
13663
+ method: Schema$1.Literal("semantic"),
13664
+ ...ComputerUseDeviceTarget,
13665
+ action: ComputerUseDeviceAction
13666
+ }),
13667
+ Schema$1.Struct({
13668
+ method: Schema$1.Literal("recording"),
13669
+ ...ComputerUseDeviceTarget,
13670
+ action: Schema$1.Literals(["start", "stop"])
13609
13671
  }),
13610
13672
  Schema$1.Struct({
13611
13673
  method: Schema$1.Literal("command"),
@@ -13717,6 +13779,7 @@ const WS_METHODS = {
13717
13779
  serverGetProcessDiagnostics: "server.getProcessDiagnostics",
13718
13780
  serverGetProcessResourceHistory: "server.getProcessResourceHistory",
13719
13781
  serverSignalProcess: "server.signalProcess",
13782
+ threadHandoff: "thread.handoff",
13720
13783
  btwAsk: "btw.ask",
13721
13784
  btwCancel: "btw.cancel",
13722
13785
  pullRequestsList: "pullRequests.list",
@@ -14360,6 +14423,14 @@ const WsServerGetProviderUsageRpc = Rpc.make(WS_METHODS.serverGetProviderUsage,
14360
14423
  success: Schema$1.Struct({ report: Schema$1.String }),
14361
14424
  error: Schema$1.Union([TextGenerationError, EnvironmentAuthorizationError])
14362
14425
  });
14426
+ const WsThreadHandoffRpc = Rpc.make(WS_METHODS.threadHandoff, {
14427
+ payload: Schema$1.Struct({ threadId: ThreadId }),
14428
+ success: Schema$1.Struct({
14429
+ projectId: ProjectId,
14430
+ text: Schema$1.String
14431
+ }),
14432
+ error: Schema$1.Union([TextGenerationError, EnvironmentAuthorizationError])
14433
+ });
14363
14434
  const WsBtwAskRpc = Rpc.make(WS_METHODS.btwAsk, {
14364
14435
  payload: BtwAskInput,
14365
14436
  success: BtwAskResult,
@@ -14820,7 +14891,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
14820
14891
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
14821
14892
  stream: true
14822
14893
  });
14823
- const WsRpcGroup = RpcGroup.make(WsDeviceGetStateRpc, WsDeviceListRpc, WsDeviceConfigureRpc, WsDeviceOpenRpc, WsDeviceCloseRpc, WsDeviceDetailRpc, WsDeviceActionRpc, WsSubscribeDevicesRpc, WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerProviderAuthStartRpc, WsServerProviderAuthCompleteRpc, WsServerProviderAuthCancelRpc, WsServerProviderAuthLogoutRpc, WsServerProviderAuthSubscribeRpc, WsServerProviderInstallStartRpc, WsServerProviderInstallCancelRpc, WsServerProviderInstallRemoveRpc, WsServerProviderInstallSubscribeRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetHostResourcesRpc, WsServerGetResourceTelemetryRpc, WsServerGetResourceTelemetryHistoryRpc, WsServerRetryResourceTelemetryRpc, WsSubscribeResourceTelemetryRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsStackRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsComputerUseConnectRpc, WsComputerUseListRpc, WsComputerUseControlsRpc, WsComputerUseControlRpc, WsComputerUsePreviewRpc, WsComputerUseRespondRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetWorkflowScriptRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
14894
+ const WsRpcGroup = RpcGroup.make(WsDeviceGetStateRpc, WsDeviceListRpc, WsDeviceConfigureRpc, WsDeviceOpenRpc, WsDeviceCloseRpc, WsDeviceDetailRpc, WsDeviceActionRpc, WsSubscribeDevicesRpc, WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerProviderAuthStartRpc, WsServerProviderAuthCompleteRpc, WsServerProviderAuthCancelRpc, WsServerProviderAuthLogoutRpc, WsServerProviderAuthSubscribeRpc, WsServerProviderInstallStartRpc, WsServerProviderInstallCancelRpc, WsServerProviderInstallRemoveRpc, WsServerProviderInstallSubscribeRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetHostResourcesRpc, WsServerGetResourceTelemetryRpc, WsServerGetResourceTelemetryHistoryRpc, WsServerRetryResourceTelemetryRpc, WsSubscribeResourceTelemetryRpc, WsThreadHandoffRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsStackRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsComputerUseConnectRpc, WsComputerUseListRpc, WsComputerUseControlsRpc, WsComputerUseControlRpc, WsComputerUsePreviewRpc, WsComputerUseRespondRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetWorkflowScriptRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
14824
14895
  //#endregion
14825
14896
  //#region ../../packages/shared/src/oauthScope.ts
14826
14897
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -24635,7 +24706,7 @@ const MAX_APNS_REQUEST_BYTES = 4096;
24635
24706
  const CLEANUP_TIMEOUT_MS$1 = 1e3;
24636
24707
  const MAX_REQUEST_BYTES$2 = 8 * 1024;
24637
24708
  const MAX_RESPONSE_BYTES$8 = 16 * 1024;
24638
- const REQUEST_TIMEOUT_MS$6 = 8e3;
24709
+ const REQUEST_TIMEOUT_MS$7 = 8e3;
24639
24710
  const MAX_AUTH_CHARS = 8192;
24640
24711
  const unavailable$6 = () => ({
24641
24712
  outcome: "retry",
@@ -24680,7 +24751,7 @@ const sendHubApns = Effect.fn("HubPushTransport.apns")(function* (input) {
24680
24751
  finish(unavailable$6());
24681
24752
  return;
24682
24753
  }
24683
- timer = setTimeout(() => finish(unavailable$6()), REQUEST_TIMEOUT_MS$6);
24754
+ timer = setTimeout(() => finish(unavailable$6()), REQUEST_TIMEOUT_MS$7);
24684
24755
  session = NodeHttp2.connect(input.sandbox ? "https://api.sandbox.push.apple.com" : "https://api.push.apple.com", { maxHeaderListPairs: 32 });
24685
24756
  session.on("error", () => finish(unavailable$6()));
24686
24757
  session.on("close", () => finish(unavailable$6()));
@@ -24735,7 +24806,7 @@ const sendHubFcm = Effect.fn("HubPushTransport.fcm")(function* (input, fetcher =
24735
24806
  const abort = () => owner.abort();
24736
24807
  signal.addEventListener("abort", abort, { once: true });
24737
24808
  if (signal.aborted) owner.abort();
24738
- const timeout = setTimeout(abort, REQUEST_TIMEOUT_MS$6);
24809
+ const timeout = setTimeout(abort, REQUEST_TIMEOUT_MS$7);
24739
24810
  let reader;
24740
24811
  try {
24741
24812
  const response = await fetcher(`https://fcm.googleapis.com/v1/projects/${input.projectId}/messages:send`, {
@@ -24776,7 +24847,7 @@ const sendHubFcm = Effect.fn("HubPushTransport.fcm")(function* (input, fetcher =
24776
24847
  }
24777
24848
  },
24778
24849
  catch: () => unavailable$6()
24779
- }).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$6), Effect.catch(() => Effect.succeed(unavailable$6())));
24850
+ }).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$7), Effect.catch(() => Effect.succeed(unavailable$6())));
24780
24851
  });
24781
24852
  //#endregion
24782
24853
  //#region src/hub/HubNotificationRuntime.ts
@@ -41682,6 +41753,72 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
41682
41753
  if (isPersistenceError(error)) return error;
41683
41754
  return toPersistenceSqlError("ProjectionSnapshotQuery.getSnapshot:query")(error);
41684
41755
  }));
41756
+ const readHandoffThread = SqlSchema.findOneOption({
41757
+ Request: ThreadIdLookupInput,
41758
+ Result: ProjectionBtwThreadRowSchema,
41759
+ execute: ({ threadId }) => sql`
41760
+ SELECT threads.project_id AS "projectId",
41761
+ COALESCE(projects.workspace_root, threads.worktree_path, '') AS cwd
41762
+ FROM projection_threads AS threads
41763
+ LEFT JOIN projection_projects AS projects ON projects.project_id = threads.project_id
41764
+ WHERE threads.thread_id = ${threadId} AND threads.deleted_at IS NULL
41765
+ AND (threads.project_id = ${P4_CHAT_PROJECT_ID} OR projects.deleted_at IS NULL)
41766
+ `
41767
+ });
41768
+ const readHandoffRows = SqlSchema.findAll({
41769
+ Request: ThreadIdLookupInput,
41770
+ Result: Schema$1.Struct({
41771
+ threadId: ThreadId,
41772
+ role: Schema$1.String,
41773
+ text: Schema$1.String
41774
+ }),
41775
+ execute: ({ threadId }) => sql`
41776
+ WITH latest_pair AS (
41777
+ SELECT implementer_thread_id, watcher_thread_id FROM thread_pairs
41778
+ WHERE implementer_thread_id = ${threadId} OR watcher_thread_id = ${threadId}
41779
+ ORDER BY created_at DESC LIMIT 1
41780
+ ), sources AS (
41781
+ SELECT ${threadId} AS id
41782
+ UNION SELECT implementer_thread_id FROM latest_pair
41783
+ UNION SELECT watcher_thread_id FROM latest_pair
41784
+ ), ranked AS (
41785
+ SELECT messages.thread_id AS "threadId", messages.role,
41786
+ substr(messages.text, -${BTW_CONTEXT_CHARACTER_LIMIT}) AS text,
41787
+ messages.created_at, messages.message_id,
41788
+ ROW_NUMBER() OVER (PARTITION BY messages.thread_id ORDER BY messages.created_at DESC, messages.message_id DESC) AS recent,
41789
+ ROW_NUMBER() OVER (PARTITION BY messages.thread_id ORDER BY messages.created_at, messages.message_id) AS earliest
41790
+ FROM projection_thread_messages AS messages
41791
+ JOIN projection_threads AS threads ON threads.thread_id = messages.thread_id
41792
+ WHERE messages.thread_id IN (SELECT id FROM sources)
41793
+ AND threads.deleted_at IS NULL AND messages.is_streaming = 0
41794
+ AND messages.role IN ('user', 'assistant')
41795
+ ) SELECT "threadId", role, text FROM ranked
41796
+ WHERE recent <= ${BTW_CONTEXT_MESSAGE_LIMIT} OR earliest = 1
41797
+ ORDER BY created_at, message_id
41798
+ `
41799
+ });
41800
+ const getHandoffContext = Effect.fn("ProjectionSnapshotQuery.getHandoffContext")(function* (threadId) {
41801
+ return yield* sql.withTransaction(Effect.gen(function* () {
41802
+ const thread = yield* readHandoffThread({ threadId });
41803
+ if (Option.isNone(thread)) return Option.none();
41804
+ const rows = yield* readHandoffRows({ threadId });
41805
+ const sourceIds = [...new Set(rows.map((row) => row.threadId))];
41806
+ const sourceCount = Math.max(1, sourceIds.length);
41807
+ const budget = Math.floor((BTW_CONTEXT_CHARACTER_LIMIT - 2 * (sourceCount - 1)) / sourceCount);
41808
+ const transcript = sourceIds.map((sourceId) => {
41809
+ const text = rows.filter((row) => row.threadId === sourceId).map((row) => `${row.role}: ${row.text}`).join("\n\n");
41810
+ const heading = `Thread ${sourceId} (bounded history):\n`;
41811
+ const available = budget - heading.length;
41812
+ const omission = "\n[Earlier history omitted]\n";
41813
+ const beginning = Math.floor(available / 4);
41814
+ return heading + (text.length <= available ? text : text.slice(0, beginning) + omission + text.slice(-(available - beginning - 27)));
41815
+ }).join("\n\n");
41816
+ return Option.some({
41817
+ ...thread.value,
41818
+ transcript
41819
+ });
41820
+ })).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getHandoffContext:query", "ProjectionSnapshotQuery.getHandoffContext:decodeRows")));
41821
+ });
41685
41822
  const getBtwContext = Effect.fn("ProjectionSnapshotQuery.getBtwContext")(function* (threadId) {
41686
41823
  const [thread, rows] = yield* Effect.all([getBtwThreadRow({ threadId }), listBtwContextRows({ threadId })]).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getBtwContext:query", "ProjectionSnapshotQuery.getBtwContext:decodeRows")));
41687
41824
  if (Option.isNone(thread)) return Option.none();
@@ -42265,6 +42402,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
42265
42402
  getThreadPairById,
42266
42403
  getThreadShellById,
42267
42404
  getThreadDetailById,
42405
+ getHandoffContext,
42268
42406
  getBtwContext,
42269
42407
  getThreadDetailSnapshot
42270
42408
  };
@@ -45373,7 +45511,7 @@ var DeviceDiscoveryFailure = class extends Data.TaggedError("DeviceDiscoveryFail
45373
45511
  const MAX_DEVICES = 512;
45374
45512
  const MAX_RESPONSE_BYTES$6 = 1024 * 1024;
45375
45513
  const MAX_DEVICE_LABEL = 512;
45376
- const REQUEST_TIMEOUT_MS$5 = 15e3;
45514
+ const REQUEST_TIMEOUT_MS$6 = 15e3;
45377
45515
  const HubDevice = Schema$1.Struct({
45378
45516
  id: DeviceId,
45379
45517
  name: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_DEVICE_LABEL)),
@@ -45416,7 +45554,10 @@ const make$87 = Effect.fn("DeviceDiscovery.make")(function* () {
45416
45554
  });
45417
45555
  if (body.invalidUtf8 || body.truncated) return yield* new DeviceDiscoveryFailure({ message: "Invalid device discovery response." });
45418
45556
  const result = yield* decodeDeviceDiscovery(input.hostId, body.text);
45419
- const devices = [...result.devices];
45557
+ const adapterDevices = input.ready.xcode ? yield* input.ready.xcode.discover : [];
45558
+ const simulatorIds = new Set(adapterDevices.map((device) => device.id));
45559
+ const devices = [...result.devices.filter((device) => device.platform !== "ios" || device.physical || !simulatorIds.has(device.id)), ...adapterDevices];
45560
+ if (devices.length > MAX_DEVICES || new Set(devices.map((device) => device.id)).size !== devices.length || adapterDevices.some((device) => device.hostId !== input.hostId || device.platform !== "ios" || device.physical)) return yield* new DeviceDiscoveryFailure({ message: "Invalid Xcode simulator identity." });
45420
45561
  if (input.androidAvailable) {
45421
45562
  const avds = yield* input.ready.run("emulator", ["-list-avds"]);
45422
45563
  if (avds.code !== 0) return yield* new DeviceDiscoveryFailure({ message: "Android virtual device discovery failed." });
@@ -45440,7 +45581,7 @@ const make$87 = Effect.fn("DeviceDiscovery.make")(function* () {
45440
45581
  devices,
45441
45582
  partial: result.partial
45442
45583
  };
45443
- }, Effect.scoped, Effect.timeout(Duration.millis(REQUEST_TIMEOUT_MS$5)), Effect.mapError(() => new DeviceOperationError({
45584
+ }, Effect.scoped, Effect.timeout(Duration.millis(REQUEST_TIMEOUT_MS$6)), Effect.mapError(() => new DeviceOperationError({
45444
45585
  operation: "list",
45445
45586
  reason: "request_failed",
45446
45587
  cause: "Device discovery unavailable."
@@ -45744,6 +45885,675 @@ const start = Effect.fn("DeviceHostSettings.start")(function* (input) {
45744
45885
  return { refresh };
45745
45886
  });
45746
45887
  //#endregion
45888
+ //#region src/device/DeviceScreenshot.ts
45889
+ const MAX_SCREENSHOT_BYTES = 8 * 1024 * 1024;
45890
+ var DeviceScreenshotFailure = class extends Data.TaggedError("DeviceScreenshotFailure") {};
45891
+ const MAX_DIMENSION = 16384;
45892
+ const MAX_PIXELS = 64 * 1024 * 1024;
45893
+ const SCREENSHOT_TIMEOUT_MS = 2e4;
45894
+ const PNG_HEADER_BYTES = 33;
45895
+ const PNG_CHUNK_OVERHEAD = 12;
45896
+ const PNG_SIGNATURE = Buffer.from([
45897
+ 137,
45898
+ 80,
45899
+ 78,
45900
+ 71,
45901
+ 13,
45902
+ 10,
45903
+ 26,
45904
+ 10
45905
+ ]);
45906
+ function pngDimensions(png) {
45907
+ const buffer = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
45908
+ if (buffer.length < PNG_HEADER_BYTES || buffer.length > 8388608 || !buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE) || buffer.readUInt32BE(8) !== 13 || buffer.toString("ascii", 12, 16) !== "IHDR") throw new DeviceScreenshotFailure({ message: "Invalid PNG screenshot." });
45909
+ const width = buffer.readUInt32BE(16);
45910
+ const height = buffer.readUInt32BE(20);
45911
+ if (!width || !height || width > MAX_DIMENSION || height > MAX_DIMENSION || width * height > MAX_PIXELS) throw new DeviceScreenshotFailure({ message: "Screenshot dimensions exceed limits." });
45912
+ let offset = PNG_HEADER_BYTES;
45913
+ let imageData = false;
45914
+ let ended = false;
45915
+ while (offset + PNG_CHUNK_OVERHEAD <= buffer.length) {
45916
+ const length = buffer.readUInt32BE(offset);
45917
+ const type = buffer.toString("ascii", offset + 4, offset + 8);
45918
+ const next = offset + PNG_CHUNK_OVERHEAD + length;
45919
+ if (next > buffer.length || type === "IHDR") throw new DeviceScreenshotFailure({ message: "Invalid PNG chunk." });
45920
+ if (type === "IDAT") imageData = true;
45921
+ if (type === "IEND") {
45922
+ ended = length === 0 && next === buffer.length;
45923
+ break;
45924
+ }
45925
+ offset = next;
45926
+ }
45927
+ if (!imageData || !ended) throw new DeviceScreenshotFailure({ message: "Incomplete PNG screenshot." });
45928
+ return {
45929
+ width,
45930
+ height
45931
+ };
45932
+ }
45933
+ const make$82 = Effect.fn("DeviceScreenshot.make")(function* () {
45934
+ const http = yield* HttpClient.HttpClient;
45935
+ return Effect.fn("DeviceScreenshot.capture")(function* (ready, session) {
45936
+ const origin = yield* Effect.try(() => new URL(ready.hub.origin));
45937
+ if (origin.protocol !== "http:" || origin.hostname !== "127.0.0.1" || !origin.port || origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) return yield* new DeviceScreenshotFailure({ message: "Invalid owned hub." });
45938
+ const vendor = session.platform === "ios" ? "serve-sim" : "serve-emu";
45939
+ const response = yield* http.execute(HttpClientRequest.post(`${origin.origin}/vendor/${vendor}/api/screenshot?device=${encodeURIComponent(session.deviceId)}`));
45940
+ if (response.status !== 200) return yield* new DeviceScreenshotFailure({ message: "Screenshot rejected." });
45941
+ let bytes = 0;
45942
+ const chunks = [];
45943
+ yield* response.stream.pipe(Stream.runForEach((chunk) => Effect.gen(function* () {
45944
+ bytes += chunk.byteLength;
45945
+ if (bytes > 8388608) return yield* new DeviceScreenshotFailure({ message: "Screenshot too large." });
45946
+ chunks.push(chunk);
45947
+ })));
45948
+ const png = Buffer.concat(chunks, bytes);
45949
+ return {
45950
+ png,
45951
+ ...yield* Effect.try(() => pngDimensions(png))
45952
+ };
45953
+ }, Effect.scoped, Effect.timeout(Duration.millis(SCREENSHOT_TIMEOUT_MS)), Effect.mapError(() => new DeviceOperationError({
45954
+ operation: "screenshot",
45955
+ reason: "request_failed",
45956
+ cause: "Device screenshot could not be captured."
45957
+ })));
45958
+ });
45959
+ var DeviceScreenshot = class extends Context.Service()("@p4code/cli/device/DeviceScreenshot") {};
45960
+ const layer$72 = Layer.effect(DeviceScreenshot, make$82().pipe(Effect.map((capture) => ({ capture }))));
45961
+ //#endregion
45962
+ //#region src/device/XcodeMcpClient.ts
45963
+ const XCODE_BUILD_MCP_VERSION = "2.7.0";
45964
+ const PROTOCOL_VERSION = "2025-06-18";
45965
+ const REQUEST_TIMEOUT_MS$5 = 6e4;
45966
+ const MAX_MESSAGE_BYTES = 12 * 1024 * 1024;
45967
+ const MAX_TOOL_PAGES = 32;
45968
+ function objectValue(value) {
45969
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
45970
+ }
45971
+ /** One process per lease keeps semantic refs and simulator defaults isolated. Never invokes npx. */
45972
+ var XcodeMcpClient = class XcodeMcpClient {
45973
+ child;
45974
+ sequence = 0;
45975
+ buffer = "";
45976
+ closed = false;
45977
+ pending = /* @__PURE__ */ new Map();
45978
+ tools = /* @__PURE__ */ new Set();
45979
+ constructor(child) {
45980
+ this.child = child;
45981
+ child.stdout.setEncoding("utf8");
45982
+ child.stdout.on("data", (chunk) => {
45983
+ this.buffer += chunk;
45984
+ if (Buffer.byteLength(this.buffer) > MAX_MESSAGE_BYTES) {
45985
+ this.close(/* @__PURE__ */ new Error("Device MCP response exceeds the supported size."));
45986
+ return;
45987
+ }
45988
+ let boundary;
45989
+ while ((boundary = this.buffer.indexOf("\n")) >= 0) {
45990
+ const line = this.buffer.slice(0, boundary);
45991
+ this.buffer = this.buffer.slice(boundary + 1);
45992
+ try {
45993
+ const message = objectValue(JSON.parse(line));
45994
+ if (typeof message.id !== "number") continue;
45995
+ const call = this.pending.get(message.id);
45996
+ if (!call) continue;
45997
+ this.pending.delete(message.id);
45998
+ if (message.error) {
45999
+ const detail = objectValue(message.error).message;
46000
+ call.reject(new Error(typeof detail === "string" ? detail : "Device MCP request failed."));
46001
+ } else call.resolve(message.result);
46002
+ } catch {
46003
+ this.close(/* @__PURE__ */ new Error("Device MCP returned invalid stdio JSON."));
46004
+ return;
46005
+ }
46006
+ }
46007
+ });
46008
+ child.stderr.resume();
46009
+ child.on("error", () => this.close(/* @__PURE__ */ new Error("Device MCP executable could not start.")));
46010
+ child.on("exit", () => this.close(/* @__PURE__ */ new Error("Device MCP disconnected. Select the device again.")));
46011
+ child.stdin.on("error", () => this.close(/* @__PURE__ */ new Error("Device MCP input disconnected.")));
46012
+ }
46013
+ static async start(executable, cwd, environment, signal, kind = "legacy") {
46014
+ if (!isAbsolute(executable)) throw new Error("Device MCP requires an absolute executable path.");
46015
+ await access(executable, constants.X_OK).catch(() => {
46016
+ throw new Error(kind === "apple" ? "Selected Xcode bridge is missing or not executable. Check the selected Xcode installation." : "Configured XcodeBuildMCP executable is missing or not executable. Install 2.7.0 explicitly.");
46017
+ });
46018
+ signal.throwIfAborted();
46019
+ const client = new XcodeMcpClient(spawn(executable, kind === "apple" ? ["mcpbridge"] : ["mcp"], {
46020
+ cwd,
46021
+ shell: false,
46022
+ stdio: "pipe",
46023
+ env: {
46024
+ ...environment,
46025
+ ...kind === "legacy" ? {
46026
+ XCODEBUILDMCP_CWD: cwd,
46027
+ XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS: "true",
46028
+ XCODEBUILDMCP_ENABLED_WORKFLOWS: "simulator,ui-automation",
46029
+ XCODEBUILDMCP_SENTRY_DISABLED: "true"
46030
+ } : {}
46031
+ }
46032
+ }));
46033
+ try {
46034
+ const initialized = objectValue(await client.request("initialize", {
46035
+ protocolVersion: PROTOCOL_VERSION,
46036
+ capabilities: {},
46037
+ clientInfo: {
46038
+ name: "p4code-device-adapter",
46039
+ version: "1"
46040
+ }
46041
+ }, signal));
46042
+ if (kind === "apple" && objectValue(initialized.serverInfo).name !== "xcode-tools") throw new Error("Selected Xcode did not expose Apple MCP tools.");
46043
+ if (kind === "legacy" && objectValue(initialized.serverInfo).version !== "2.7.0") throw new Error(`P4 requires XcodeBuildMCP ${XCODE_BUILD_MCP_VERSION}; configured executable reported a different version.`);
46044
+ client.child.stdin.write(JSON.stringify({
46045
+ jsonrpc: "2.0",
46046
+ method: "notifications/initialized"
46047
+ }) + "\n");
46048
+ let cursor;
46049
+ const cursors = /* @__PURE__ */ new Set();
46050
+ do {
46051
+ const page = objectValue(await client.request("tools/list", cursor ? { cursor } : {}, signal));
46052
+ if (!Array.isArray(page.tools)) throw new Error("Device MCP did not advertise tools.");
46053
+ for (const tool of page.tools) {
46054
+ const name = objectValue(tool).name;
46055
+ if (typeof name === "string") client.tools.add(name);
46056
+ }
46057
+ cursor = page.nextCursor;
46058
+ if (typeof cursor === "string" && cursor.length > 0) {
46059
+ if (cursors.has(cursor) || cursors.size >= MAX_TOOL_PAGES) throw new Error("Device MCP tool discovery did not finish within its page limit.");
46060
+ cursors.add(cursor);
46061
+ }
46062
+ } while (typeof cursor === "string" && cursor.length > 0);
46063
+ return client;
46064
+ } catch (error) {
46065
+ client.close();
46066
+ throw error;
46067
+ }
46068
+ }
46069
+ request(method, params, signal) {
46070
+ signal.throwIfAborted();
46071
+ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Device MCP session is closed. Select the device again."));
46072
+ const id = ++this.sequence;
46073
+ return new Promise((resolve, reject) => {
46074
+ const abort = () => this.close(/* @__PURE__ */ new Error("Device MCP request cancelled. Select the device again."));
46075
+ const timer = setTimeout(() => this.close(/* @__PURE__ */ new Error("Device MCP timed out. Select the device again.")), REQUEST_TIMEOUT_MS$5);
46076
+ const finish = () => {
46077
+ clearTimeout(timer);
46078
+ signal.removeEventListener("abort", abort);
46079
+ };
46080
+ this.pending.set(id, {
46081
+ resolve: (result) => {
46082
+ finish();
46083
+ resolve(result);
46084
+ },
46085
+ reject: (error) => {
46086
+ finish();
46087
+ reject(error);
46088
+ }
46089
+ });
46090
+ signal.addEventListener("abort", abort, { once: true });
46091
+ this.child.stdin.write(JSON.stringify({
46092
+ jsonrpc: "2.0",
46093
+ id,
46094
+ method,
46095
+ params
46096
+ }) + "\n");
46097
+ });
46098
+ }
46099
+ async call(name, args, signal) {
46100
+ if (!this.tools.has(name)) throw new Error(`Selected device MCP service does not expose ${name}. Check adapter capabilities and Xcode setup.`);
46101
+ const redact = (error) => {
46102
+ let message = error instanceof Error ? error.message : String(error);
46103
+ for (const [key, value] of Object.entries(args)) if (/sessionkey$/i.test(key) && typeof value === "string" && value.length > 0) message = message.replaceAll(value, "[redacted]");
46104
+ return new Error(message);
46105
+ };
46106
+ try {
46107
+ const result = objectValue(await this.request("tools/call", {
46108
+ name,
46109
+ arguments: args
46110
+ }, signal));
46111
+ const structured = objectValue(result.structuredContent);
46112
+ if (result.isError === true || structured.didError === true) {
46113
+ const detail = (Array.isArray(result.content) ? result.content : []).map((part) => objectValue(part).text).filter((part) => typeof part === "string").join("\n");
46114
+ throw new Error(detail || String(structured.error ?? `${name} failed. Refresh device state before retrying.`));
46115
+ }
46116
+ return result;
46117
+ } catch (error) {
46118
+ throw redact(error);
46119
+ }
46120
+ }
46121
+ close(error = /* @__PURE__ */ new Error("Device MCP session closed.")) {
46122
+ if (this.closed) return;
46123
+ this.closed = true;
46124
+ for (const call of this.pending.values()) call.reject(error);
46125
+ this.pending.clear();
46126
+ this.child.kill("SIGTERM");
46127
+ }
46128
+ };
46129
+ //#endregion
46130
+ //#region src/device/XcodeDeviceAdapter.ts
46131
+ const SimulatorList = Schema$1.Struct({ simulators: Schema$1.Array(Schema$1.Struct({
46132
+ simulatorId: Schema$1.String,
46133
+ name: Schema$1.String,
46134
+ runtime: Schema$1.String,
46135
+ state: Schema$1.String,
46136
+ isAvailable: Schema$1.Boolean
46137
+ })) });
46138
+ const decodeSimulators = Schema$1.decodeUnknownSync(SimulatorList);
46139
+ const MAX_LEASES = 32;
46140
+ const Frame = Schema$1.Struct({
46141
+ x: Schema$1.Number,
46142
+ y: Schema$1.Number,
46143
+ width: Schema$1.Number,
46144
+ height: Schema$1.Number
46145
+ });
46146
+ const Snapshot = Schema$1.Struct({ elements: Schema$1.Array(Schema$1.Struct({
46147
+ ref: Schema$1.String,
46148
+ role: Schema$1.optional(Schema$1.String),
46149
+ frame: Frame
46150
+ })) });
46151
+ const readSnapshot = Schema$1.decodeUnknownOption(Snapshot);
46152
+ /** Retain structured semantic snapshots; callers never need screenshot round trips for refs. */
46153
+ function xcodeResultData(result) {
46154
+ const structured = objectValue(result.structuredContent);
46155
+ return objectValue(structured.data ?? structured);
46156
+ }
46157
+ function createXcodeDeviceAdapter(executable, cwd, environment) {
46158
+ const clients = /* @__PURE__ */ new Map();
46159
+ const recordings = /* @__PURE__ */ new Map();
46160
+ const snapshots = /* @__PURE__ */ new Map();
46161
+ const remember = (session, data) => {
46162
+ const snapshot = readSnapshot(data.capture);
46163
+ if (snapshot._tag === "Some") snapshots.set(session.leaseId, snapshot.value);
46164
+ const viewport = snapshots.get(session.leaseId)?.elements.find((element) => element.role === "window")?.frame;
46165
+ return {
46166
+ ...data,
46167
+ ...viewport ? { viewport } : {}
46168
+ };
46169
+ };
46170
+ const get = async (key, signal) => {
46171
+ let pending = clients.get(key);
46172
+ if (!pending) {
46173
+ if (clients.size >= MAX_LEASES) throw new Error("Close an existing Xcode device session before opening another.");
46174
+ pending = XcodeMcpClient.start(executable, cwd, environment, signal);
46175
+ clients.set(key, pending);
46176
+ pending.catch(() => {
46177
+ if (clients.get(key) === pending) clients.delete(key);
46178
+ });
46179
+ }
46180
+ return pending;
46181
+ };
46182
+ const run = (operation, execute) => Effect.tryPromise({
46183
+ try: execute,
46184
+ catch: (cause) => new DeviceOperationError({
46185
+ operation,
46186
+ reason: "request_failed",
46187
+ cause: cause instanceof Error ? cause.message : String(cause)
46188
+ })
46189
+ });
46190
+ const forSession = async (session, signal) => {
46191
+ if (session.hostId !== "local" || session.platform !== "ios" || !session.leaseId) throw new Error("Xcode adapter requires an exact local iOS Simulator lease.");
46192
+ return get(session.leaseId, signal);
46193
+ };
46194
+ const closeSession = (leaseId) => {
46195
+ const client = clients.get(leaseId);
46196
+ clients.delete(leaseId);
46197
+ const recording = recordings.get(leaseId);
46198
+ recordings.delete(leaseId);
46199
+ snapshots.delete(leaseId);
46200
+ client?.then(async (client) => {
46201
+ try {
46202
+ if (recording) await client.call("record_sim_video", {
46203
+ ...recording,
46204
+ stop: true
46205
+ }, new AbortController().signal);
46206
+ } finally {
46207
+ client.close();
46208
+ }
46209
+ }).catch(() => {});
46210
+ };
46211
+ return {
46212
+ discover: run("list", async (signal) => {
46213
+ const client = await get("discovery", signal);
46214
+ return decodeSimulators(xcodeResultData(await client.call("list_sims", {}, signal))).simulators.filter((sim) => sim.isAvailable && /iOS/i.test(sim.runtime)).map((sim) => ({
46215
+ id: sim.simulatorId,
46216
+ hostId: "local",
46217
+ name: sim.name,
46218
+ version: sim.runtime,
46219
+ booted: sim.state === "Booted",
46220
+ physical: false,
46221
+ platform: "ios"
46222
+ }));
46223
+ }),
46224
+ state: (session, sinceScreenHash) => run("state", async (signal) => {
46225
+ const client = await forSession(session, signal);
46226
+ const data = xcodeResultData(await client.call("snapshot_ui", {
46227
+ simulatorId: session.deviceId,
46228
+ ...sinceScreenHash ? { sinceScreenHash } : {}
46229
+ }, signal));
46230
+ return {
46231
+ adapter: "xcodebuildmcp",
46232
+ capabilities: [...client.tools].filter((tool) => [
46233
+ "tap",
46234
+ "type_text",
46235
+ "swipe",
46236
+ "drag",
46237
+ "batch",
46238
+ "snapshot_ui",
46239
+ "screenshot",
46240
+ "record_sim_video"
46241
+ ].includes(tool)),
46242
+ ...remember(session, data)
46243
+ };
46244
+ }),
46245
+ action: (session, input) => run("command", async (signal) => {
46246
+ const client = await forSession(session, signal);
46247
+ const { action, ...parameters } = input;
46248
+ const snapshot = snapshots.get(session.leaseId);
46249
+ const ref = input.action === "batch" ? input.steps.at(-1)?.elementRef : input.action === "swipe" ? input.withinElementRef : input.elementRef;
46250
+ const bounds = snapshot?.elements.find((element) => element.ref === ref)?.frame;
46251
+ const viewport = snapshot?.elements.find((element) => element.role === "window")?.frame;
46252
+ const data = xcodeResultData(await client.call(action, {
46253
+ ...parameters,
46254
+ simulatorId: session.deviceId
46255
+ }, signal));
46256
+ return {
46257
+ ...remember(session, data),
46258
+ ...bounds && viewport && viewport.width > 0 && viewport.height > 0 ? { pointer: {
46259
+ x: (bounds.x + bounds.width / 2 - viewport.x) / viewport.width,
46260
+ y: (bounds.y + bounds.height / 2 - viewport.y) / viewport.height
46261
+ } } : {}
46262
+ };
46263
+ }),
46264
+ screenshot: (session) => run("screenshot", async (signal) => {
46265
+ const result = await (await forSession(session, signal)).call("screenshot", {
46266
+ simulatorId: session.deviceId,
46267
+ returnFormat: "base64"
46268
+ }, signal);
46269
+ const image = (Array.isArray(result.content) ? result.content : []).map(objectValue).find((part) => part.type === "image" && part.mimeType === "image/png");
46270
+ if (typeof image?.data !== "string") throw new Error("XcodeBuildMCP did not return a PNG screenshot.");
46271
+ const png = Buffer.from(image.data, "base64");
46272
+ return {
46273
+ png,
46274
+ ...pngDimensions(png)
46275
+ };
46276
+ }),
46277
+ recording: (session, action) => run("recording", async (signal) => {
46278
+ const client = await forSession(session, signal);
46279
+ const key = session.leaseId;
46280
+ if (action === "start" && recordings.has(key)) throw new Error("This device session is already recording.");
46281
+ if (action === "stop" && !recordings.has(key)) throw new Error("This device session has no active recording.");
46282
+ const outputFile = recordings.get(key)?.outputFile ?? `${cwd}/device-recording-${randomUUID()}.mp4`;
46283
+ const result = xcodeResultData(await client.call("record_sim_video", {
46284
+ simulatorId: session.deviceId,
46285
+ ...action === "start" ? { start: true } : {
46286
+ stop: true,
46287
+ outputFile
46288
+ }
46289
+ }, signal));
46290
+ if (action === "start") recordings.set(key, {
46291
+ outputFile,
46292
+ simulatorId: session.deviceId
46293
+ });
46294
+ else recordings.delete(key);
46295
+ return result;
46296
+ }),
46297
+ closeSession,
46298
+ stop: () => {
46299
+ for (const key of clients.keys()) closeSession(key);
46300
+ }
46301
+ };
46302
+ }
46303
+ //#endregion
46304
+ //#region src/device/AppleDeviceAdapter.ts
46305
+ const MAX_HIERARCHY_BYTES = 512 * 1024;
46306
+ const MAX_SESSIONS = 32;
46307
+ const REQUIRED_DEVICE_TOOLS = [
46308
+ "DeviceInteractionStartSession",
46309
+ "DeviceInteractionSynthesize",
46310
+ "DeviceInteractionEndSession"
46311
+ ];
46312
+ const FRAME_PATTERN = /\{\{(-?[\d.]+),\s*(-?[\d.]+)\},\s*\{([\d.]+),\s*([\d.]+)\}\}/;
46313
+ const HIT_PATTERN = /hitPoint:\s*\{(-?[\d.]+),\s*(-?[\d.]+)\}/;
46314
+ const Capture = Schema$1.Struct({
46315
+ hierarchyPath: Schema$1.String,
46316
+ screenshotPath: Schema$1.String
46317
+ });
46318
+ const Session = Schema$1.Struct({
46319
+ interactionSessionKey: Schema$1.String,
46320
+ deviceUUID: Schema$1.String,
46321
+ deviceIsSimulator: Schema$1.Boolean
46322
+ });
46323
+ const Simulators = Schema$1.Struct({ devices: Schema$1.Record(Schema$1.String, Schema$1.Array(Schema$1.Struct({
46324
+ udid: Schema$1.String,
46325
+ name: Schema$1.String,
46326
+ state: Schema$1.String,
46327
+ isAvailable: Schema$1.Boolean
46328
+ }))) });
46329
+ function appleMcpData(result) {
46330
+ if (result.structuredContent) return objectValue(result.structuredContent);
46331
+ for (const block of Array.isArray(result.content) ? result.content : []) {
46332
+ const text = objectValue(block).text;
46333
+ if (typeof text !== "string") continue;
46334
+ try {
46335
+ return objectValue(JSON.parse(text));
46336
+ } catch {}
46337
+ }
46338
+ throw new Error("Apple MCP returned no structured device result.");
46339
+ }
46340
+ /** Use Apple's documented hierarchy hit points, never guessed screenshot coordinates. */
46341
+ function appleHierarchySnapshot(text, sequence) {
46342
+ const elements = text.split("\n").flatMap((line, index) => {
46343
+ const frame = line.match(FRAME_PATTERN);
46344
+ const hit = line.match(HIT_PATTERN);
46345
+ if (!frame || !hit || line.includes("isRemoteLeafPlaceholder")) return [];
46346
+ const [x, y, width, height] = frame.slice(1).map(Number);
46347
+ const [hx, hy] = hit.slice(1).map(Number);
46348
+ if (![
46349
+ x,
46350
+ y,
46351
+ width,
46352
+ height,
46353
+ hx,
46354
+ hy
46355
+ ].every((value) => value !== void 0 && Number.isFinite(value)) || width <= 0 || height <= 0) return [];
46356
+ return [{
46357
+ ref: `a${sequence}-${index}`,
46358
+ label: line.trim(),
46359
+ frame: {
46360
+ x,
46361
+ y,
46362
+ width,
46363
+ height
46364
+ },
46365
+ hitPoint: {
46366
+ x: hx,
46367
+ y: hy
46368
+ },
46369
+ role: /\b(?:UI)?Window\b/.test(line) ? "window" : "element",
46370
+ actions: ["tap"]
46371
+ }];
46372
+ });
46373
+ return {
46374
+ screenHash: createHash("sha256").update(text).digest("hex"),
46375
+ seq: sequence,
46376
+ elements
46377
+ };
46378
+ }
46379
+ function createAppleDeviceAdapter(input) {
46380
+ const sessions = /* @__PURE__ */ new Map();
46381
+ const fail = (operation) => (cause) => new DeviceOperationError({
46382
+ operation,
46383
+ reason: "request_failed",
46384
+ cause: cause instanceof Error ? cause.message : String(cause)
46385
+ });
46386
+ const call = (client, name, args) => Effect.tryPromise({
46387
+ try: (signal) => client.call(name, args, signal),
46388
+ catch: fail(name)
46389
+ });
46390
+ const closeSession = (leaseId) => {
46391
+ const pending = sessions.get(leaseId);
46392
+ sessions.delete(leaseId);
46393
+ pending?.then(async (owned) => {
46394
+ try {
46395
+ await owned.client.call("DeviceInteractionEndSession", { interactionSessionKey: owned.key }, new AbortController().signal);
46396
+ } finally {
46397
+ owned.client.close();
46398
+ }
46399
+ }).catch(() => {});
46400
+ };
46401
+ const get = (session) => Effect.tryPromise({
46402
+ try: async (signal) => {
46403
+ if (session.hostId !== "local" || session.platform !== "ios" || !session.leaseId) throw new Error("Apple MCP requires an exact local iOS device lease.");
46404
+ let pending = sessions.get(session.leaseId);
46405
+ if (!pending) {
46406
+ if (sessions.size >= MAX_SESSIONS) throw new Error("Close an existing device session first.");
46407
+ pending = (async () => {
46408
+ const client = await XcodeMcpClient.start("/usr/bin/xcrun", input.cwd, {
46409
+ ...input.environment,
46410
+ MCP_XCODE_SESSION_ID: randomUUID()
46411
+ }, signal, "apple");
46412
+ try {
46413
+ const missing = REQUIRED_DEVICE_TOOLS.filter((tool) => !client.tools.has(tool));
46414
+ if (missing.length > 0) throw new Error(`Selected Xcode lacks required device tools: ${missing.join(", ")}. No device session was opened.`);
46415
+ if (input.workspacePath) await client.call("XcodeOpenWorkspace", { path: input.workspacePath }, signal);
46416
+ const opened = Schema$1.decodeUnknownSync(Session)(appleMcpData(await client.call("DeviceInteractionStartSession", {
46417
+ deviceIdentifier: session.deviceId,
46418
+ sessionIdentifier: `P4 Device ${session.threadId.slice(0, 8)}`
46419
+ }, signal)));
46420
+ if (opened.deviceUUID.toLowerCase() !== session.deviceId.toLowerCase() || !opened.deviceIsSimulator) {
46421
+ await client.call("DeviceInteractionEndSession", { interactionSessionKey: opened.interactionSessionKey }, signal);
46422
+ throw new Error("Apple selected a different device. No interaction was performed.");
46423
+ }
46424
+ return {
46425
+ client,
46426
+ key: opened.interactionSessionKey,
46427
+ sequence: 0
46428
+ };
46429
+ } catch (error) {
46430
+ client.close();
46431
+ throw error;
46432
+ }
46433
+ })();
46434
+ sessions.set(session.leaseId, pending);
46435
+ const leaseId = session.leaseId;
46436
+ pending.catch(() => {
46437
+ if (sessions.get(leaseId) === pending) sessions.delete(leaseId);
46438
+ });
46439
+ }
46440
+ return pending;
46441
+ },
46442
+ catch: fail("open")
46443
+ });
46444
+ const capture = (owned, interactionCommand) => Effect.gen(function* () {
46445
+ const response = yield* call(owned.client, "DeviceInteractionSynthesize", {
46446
+ interactSessionKey: owned.key,
46447
+ ...interactionCommand ? { interactionCommand } : {}
46448
+ });
46449
+ const data = yield* Effect.try({
46450
+ try: () => appleMcpData(response),
46451
+ catch: fail("state")
46452
+ });
46453
+ const result = yield* Schema$1.decodeUnknownEffect(Capture)(data);
46454
+ if ((yield* input.fs.stat(result.hierarchyPath)).size > BigInt(MAX_HIERARCHY_BYTES)) return yield* fail("state")("Apple hierarchy exceeds the supported size.");
46455
+ const snapshot = appleHierarchySnapshot(yield* input.fs.readFileString(result.hierarchyPath), owned.sequence + 1);
46456
+ if (owned.snapshot?.screenHash !== snapshot.screenHash) {
46457
+ owned.snapshot = snapshot;
46458
+ owned.sequence += 1;
46459
+ }
46460
+ const currentSnapshot = owned.snapshot;
46461
+ owned.image = result.screenshotPath;
46462
+ const viewport = currentSnapshot.elements.find((element) => element.role === "window")?.frame;
46463
+ return {
46464
+ adapter: "apple-xcode",
46465
+ capabilities: [
46466
+ "snapshot_ui",
46467
+ "tap",
46468
+ "screenshot"
46469
+ ],
46470
+ capture: currentSnapshot,
46471
+ ...viewport ? { viewport } : {}
46472
+ };
46473
+ }).pipe(Effect.mapError(fail("state")));
46474
+ return {
46475
+ diagnose: Effect.gen(function* () {
46476
+ const result = yield* input.run("xcrun", [
46477
+ "mcp-server",
46478
+ "status",
46479
+ "--format",
46480
+ "json"
46481
+ ]);
46482
+ if (result.code !== 0) return "Apple MCP did not respond. Check the selected Xcode installation and MCP service.";
46483
+ const status = objectValue(yield* Schema$1.decodeUnknownEffect(Schema$1.UnknownFromJsonString)(result.stdout));
46484
+ const permission = objectValue(status.permission);
46485
+ if ((Array.isArray(permission.pendingAgentApprovals) ? permission.pendingAgentApprovals : []).some((item) => objectValue(objectValue(objectValue(objectValue(item).subject).agent)._0).name === "p4code-device-adapter")) return "Apple Xcode is waiting for approval of p4code-device-adapter and its project folder. Approve the pending request in Xcode, then select the device again. P4 cannot grant this approval automatically.";
46486
+ if (status.running === false) return "Apple MCP service is not running. Open the selected Xcode project or configure its headless service, then select the device again.";
46487
+ return "Apple MCP did not respond in time. Check Xcode agent/folder permissions and session logs before selecting the device again.";
46488
+ }).pipe(Effect.orElseSucceed(() => "Apple MCP did not respond. Check Xcode agent/folder permissions and service status.")),
46489
+ discover: Effect.gen(function* () {
46490
+ const result = yield* input.run("xcrun", [
46491
+ "simctl",
46492
+ "list",
46493
+ "devices",
46494
+ "available",
46495
+ "--json"
46496
+ ]);
46497
+ if (result.code !== 0) return yield* fail("list")("Selected Xcode cannot list simulators.");
46498
+ const parsed = yield* Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Simulators))(result.stdout).pipe(Effect.mapError(fail("list")));
46499
+ return Object.entries(parsed.devices).filter(([runtime]) => runtime.includes(".iOS-")).flatMap(([runtime, devices]) => devices.filter((device) => device.isAvailable).map((device) => ({
46500
+ hostId: "local",
46501
+ id: device.udid,
46502
+ platform: "ios",
46503
+ name: device.name,
46504
+ version: runtime.replace("com.apple.CoreSimulator.SimRuntime.", "").replaceAll("-", "."),
46505
+ physical: false,
46506
+ booted: device.state === "Booted"
46507
+ })));
46508
+ }),
46509
+ state: (session, sinceScreenHash) => Effect.gen(function* () {
46510
+ const result = yield* capture(yield* get(session));
46511
+ return sinceScreenHash === result.capture.screenHash ? {
46512
+ ...result,
46513
+ capture: {
46514
+ screenHash: result.capture.screenHash,
46515
+ seq: result.capture.seq,
46516
+ unchanged: true
46517
+ }
46518
+ } : result;
46519
+ }),
46520
+ action: (session, action) => Effect.gen(function* () {
46521
+ if (action.action !== "tap") return yield* fail("command")("This Apple MCP integration currently advertises tap only. Use only capabilities returned by getAXState().");
46522
+ const owned = yield* get(session);
46523
+ const previous = owned.snapshot;
46524
+ const element = previous?.elements.find((element) => element.ref === action.elementRef);
46525
+ if (!element) return yield* fail("command")("Stale device ref. Read getAXState() and choose a current element.");
46526
+ const fresh = yield* capture(owned);
46527
+ if (fresh.capture.screenHash !== previous.screenHash) return yield* fail("command")("Device layout changed. Read getAXState() and choose a current element; no action was performed.");
46528
+ return {
46529
+ ...yield* capture(owned, `t ${element.hitPoint.x} ${element.hitPoint.y}`),
46530
+ ...fresh.viewport ? { pointer: {
46531
+ x: (element.hitPoint.x - fresh.viewport.x) / fresh.viewport.width,
46532
+ y: (element.hitPoint.y - fresh.viewport.y) / fresh.viewport.height
46533
+ } } : {}
46534
+ };
46535
+ }),
46536
+ screenshot: (session) => Effect.gen(function* () {
46537
+ const owned = yield* get(session);
46538
+ yield* capture(owned);
46539
+ if ((yield* input.fs.stat(owned.image)).size > BigInt(8388608)) return yield* fail("screenshot")("Apple screenshot exceeds the supported size.");
46540
+ const png = Buffer.from(yield* input.fs.readFile(owned.image));
46541
+ return {
46542
+ png,
46543
+ ...yield* Effect.try({
46544
+ try: () => pngDimensions(png),
46545
+ catch: fail("screenshot")
46546
+ })
46547
+ };
46548
+ }).pipe(Effect.mapError(fail("screenshot"))),
46549
+ recording: (_session, _action) => Effect.fail(fail("recording")("Apple MCP does not advertise a recording tool. Recording is unavailable through this adapter.")),
46550
+ closeSession,
46551
+ stop: () => {
46552
+ for (const key of sessions.keys()) closeSession(key);
46553
+ }
46554
+ };
46555
+ }
46556
+ //#endregion
45747
46557
  //#region src/device/DeviceToolchain.ts
45748
46558
  /**
45749
46559
  * Pinned installs of the two external tools device support is built on.
@@ -45959,7 +46769,8 @@ function deviceHubPlatform(platforms) {
45959
46769
  const COMMAND_TIMEOUT_MS$1 = 2e4;
45960
46770
  const COMMAND_OUTPUT_LIMIT = 1024 * 1024;
45961
46771
  const COMMAND_FAILURE_CODE = 127;
45962
- const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46772
+ const APPLE_DEVICE_MCP_MINIMUM_XCODE = 27;
46773
+ const make$81 = Effect.fn("LocalDeviceHost.make")(function* () {
45963
46774
  const config = yield* ServerConfig$1;
45964
46775
  const fs = yield* FileSystem.FileSystem;
45965
46776
  const path = yield* Path$1.Path;
@@ -46005,7 +46816,19 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46005
46816
  stderr: "Device command failed.",
46006
46817
  code: COMMAND_FAILURE_CODE
46007
46818
  })));
46819
+ const appleVersion = (platform === "darwin" ? yield* run("xcrun", ["--find", "mcpbridge"]) : null)?.code === 0 ? yield* run("xcodebuild", ["-version"]) : null;
46820
+ const appleAvailable = appleVersion?.code === 0 && Number(appleVersion.stdout.match(/^Xcode (\d+)\b/m)?.[1]) >= APPLE_DEVICE_MCP_MINIMUM_XCODE;
46821
+ const legacyPath = environment.P4CODE_XCODEBUILDMCP_PATH?.trim();
46822
+ const useLegacy = environment.P4CODE_DEVICE_ADAPTER === "xcodebuildmcp";
46823
+ const xcode = appleAvailable && !useLegacy ? createAppleDeviceAdapter({
46824
+ cwd: config.stateDir,
46825
+ environment: hostEnvironment,
46826
+ fs,
46827
+ run,
46828
+ ...environment.P4CODE_APPLE_MCP_WORKSPACE ? { workspacePath: environment.P4CODE_APPLE_MCP_WORKSPACE } : {}
46829
+ }) : platform === "darwin" && legacyPath ? createXcodeDeviceAdapter(legacyPath, config.stateDir, hostEnvironment) : void 0;
46008
46830
  const stopAgent = Effect.suspend(() => {
46831
+ xcode?.stop();
46009
46832
  const owned = agent;
46010
46833
  agent = null;
46011
46834
  return owned?.stop ?? Effect.void;
@@ -46044,7 +46867,8 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46044
46867
  nodePath: process.execPath,
46045
46868
  hub: { origin: started.origin },
46046
46869
  helpers,
46047
- run
46870
+ run,
46871
+ ...xcode ? { xcode } : {}
46048
46872
  };
46049
46873
  return ready;
46050
46874
  }, Effect.onExit((exit) => Exit.isFailure(exit) ? stop : Effect.void));
@@ -46055,6 +46879,10 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46055
46879
  id: hostId,
46056
46880
  kind: "local",
46057
46881
  label: "This machine",
46882
+ ...xcode ? { automation: {
46883
+ provider: appleAvailable && !useLegacy ? "apple-xcode" : "xcodebuildmcp",
46884
+ detail: appleAvailable && !useLegacy ? "Apple Xcode MCP. Agent and folder approval is required; P4 never grants it automatically." : "Optional XcodeBuildMCP 2.7.0 executable. No automatic installation."
46885
+ } } : {},
46058
46886
  platforms: yield* Effect.all([platformAvailability("ios"), platformAvailability("android")]),
46059
46887
  hubInstalled: yield* isDeviceHubInstalled(config.baseDir).pipe(Effect.provideContext(context)),
46060
46888
  agentDeviceInstalled: yield* isAgentDeviceInstalled(config.baseDir).pipe(Effect.provideContext(context))
@@ -46066,8 +46894,9 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46066
46894
  return ready;
46067
46895
  })),
46068
46896
  ensureReady: (onPhase) => lock.withPermit(ensureHub(onPhase)),
46069
- ensureAgentReady: (onPhase) => lock.withPermit(Effect.gen(function* () {
46897
+ ensureAgentReady: (onPhase, devicePlatform) => lock.withPermit(Effect.gen(function* () {
46070
46898
  const host = yield* ensureHub(onPhase);
46899
+ if (xcode && devicePlatform === "ios") return host;
46071
46900
  if (agent && (yield* agent.isRunning)) return {
46072
46901
  ...host,
46073
46902
  agentDevice: agent.endpoint
@@ -46094,7 +46923,7 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46094
46923
  stop: lock.withPermit(stop)
46095
46924
  };
46096
46925
  });
46097
- Layer.effect(DeviceHost, make$82()).pipe(Layer.provide(layer$73));
46926
+ Layer.effect(DeviceHost, make$81()).pipe(Layer.provide(layer$73));
46098
46927
  Data.TaggedError("SshHostDiscoveryError");
46099
46928
  var SshInvalidTargetError = class extends Data.TaggedError("SshInvalidTargetError") {};
46100
46929
  var SshCommandError = class extends Data.TaggedError("SshCommandError") {};
@@ -46953,7 +47782,7 @@ const probeSshDeviceHost = Effect.fn("SshDeviceDiagnostics.probe")(function* (co
46953
47782
  //#endregion
46954
47783
  //#region src/device/SshDeviceHost.ts
46955
47784
  const DIAGNOSTICS_CACHE_MS = 1e4;
46956
- const make$81 = Effect.fn("SshDeviceHost.make")(function* (input) {
47785
+ const make$80 = Effect.fn("SshDeviceHost.make")(function* (input) {
46957
47786
  const config = yield* Schema$1.decodeUnknownEffect(SshDeviceHostConfig)({ ...input }).pipe(Effect.mapError(() => new DeviceHostError({
46958
47787
  hostId: input.id,
46959
47788
  step: "configuring SSH device host",
@@ -47095,6 +47924,14 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47095
47924
  const scope = yield* Scope.Scope;
47096
47925
  const registry = yield* make$83(input);
47097
47926
  const commands = yield* make$85();
47927
+ const adapterLeases = /* @__PURE__ */ new Map();
47928
+ const closeCommands = (sessions) => Effect.sync(() => {
47929
+ for (const session of sessions) {
47930
+ if (!session.leaseId) continue;
47931
+ adapterLeases.get(session.leaseId)?.();
47932
+ adapterLeases.delete(session.leaseId);
47933
+ }
47934
+ }).pipe(Effect.andThen(commands.close(sessions)));
47098
47935
  const threads = yield* make$84();
47099
47936
  const changes = yield* Effect.acquireRelease(PubSub.sliding(STATE_BUFFER_SIZE), PubSub.shutdown);
47100
47937
  const stateLock = yield* Semaphore.make(1);
@@ -47132,7 +47969,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47132
47969
  }));
47133
47970
  }
47134
47971
  const retireSessions = (ids) => Effect.gen(function* () {
47135
- yield* commands.close(state.sessions.filter((session) => ids.includes(session.hostId)));
47972
+ yield* closeCommands(state.sessions.filter((session) => ids.includes(session.hostId)));
47136
47973
  yield* update((current) => {
47137
47974
  for (const id of ids) hostLocks.delete(id);
47138
47975
  return {
@@ -47343,7 +48180,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47343
48180
  return yield* withHost(value.hostId, (lease) => Effect.gen(function* () {
47344
48181
  let found = yield* freshDevice(lease, value);
47345
48182
  if (access === "agent") {
47346
- yield* lease.host.ensureAgentReady(() => Effect.void);
48183
+ yield* lease.host.ensureAgentReady(() => Effect.void, value.platform);
47347
48184
  yield* registry.commit(lease, () => void 0);
47348
48185
  }
47349
48186
  if (!found.device.booted && value.boot !== false) {
@@ -47382,7 +48219,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47382
48219
  openedAt,
47383
48220
  leaseId: NodeCrypto.randomUUID()
47384
48221
  };
47385
- yield* commands.close(sessionsToClose(state.sessions, session));
48222
+ yield* closeCommands(sessionsToClose(state.sessions, session));
47386
48223
  yield* update((current) => ({
47387
48224
  ...current,
47388
48225
  sessions: [...current.sessions.filter((item) => !(item.threadId === session.threadId && item.hostId === session.hostId && item.deviceId === session.deviceId)), session]
@@ -47393,7 +48230,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47393
48230
  const shutdownWithin = Effect.fn("DeviceService.shutdownWithin")(function* (lease, value) {
47394
48231
  const { ready, device } = yield* freshDevice(lease, value);
47395
48232
  yield* input.actions.shutdown(ready, device);
47396
- yield* commands.close(state.sessions.filter((session) => session.hostId === value.hostId && session.deviceId === value.deviceId));
48233
+ yield* closeCommands(state.sessions.filter((session) => session.hostId === value.hostId && session.deviceId === value.deviceId));
47397
48234
  yield* update((current) => ({
47398
48235
  ...current,
47399
48236
  devices: current.devices.map((item) => item.hostId === value.hostId && item.id === value.deviceId ? {
@@ -47417,7 +48254,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47417
48254
  platform: session.platform
47418
48255
  });
47419
48256
  else {
47420
- yield* commands.close(sessionsToClose(state.sessions, value));
48257
+ yield* closeCommands(sessionsToClose(state.sessions, value));
47421
48258
  yield* update((current) => {
47422
48259
  const closing = sessionsToClose(current.sessions, value);
47423
48260
  return {
@@ -47460,11 +48297,16 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47460
48297
  });
47461
48298
  const validate = registry.isCurrent(lease).pipe(Effect.map((current) => current && state.sessions.includes(session)));
47462
48299
  return yield* commands.run(session, validate, Effect.gen(function* () {
47463
- const ready = yield* lease.host.ensureAgentReady(() => Effect.void);
48300
+ const ready = yield* lease.host.ensureAgentReady(() => Effect.void, session.platform);
47464
48301
  if (!(yield* validate)) return yield* new DeviceHostUnavailableError({
47465
48302
  hostId: target.hostId,
47466
48303
  reason: "Device session or agent authorization changed."
47467
48304
  });
48305
+ if (ready.xcode && session.leaseId) {
48306
+ const adapter = ready.xcode;
48307
+ const leaseId = session.leaseId;
48308
+ adapterLeases.set(leaseId, () => adapter.closeSession(leaseId));
48309
+ }
47468
48310
  return yield* execute(ready, session);
47469
48311
  }));
47470
48312
  })).pipe(Effect.mapError((error) => isDeviceHostError(error) || isDeviceHostTimeoutError(error) ? new DeviceHostUnavailableError({
@@ -47478,7 +48320,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47478
48320
  agentList: listForAccess("agent"),
47479
48321
  configure,
47480
48322
  shutdown,
47481
- retireThread: (threadId) => threads.retire(threadId).pipe(Effect.ensuring(Effect.suspend(() => commands.close(state.sessions.filter((session) => session.threadId === threadId))).pipe(Effect.ensuring(update((current) => ({
48323
+ retireThread: (threadId) => threads.retire(threadId).pipe(Effect.ensuring(Effect.suspend(() => closeCommands(state.sessions.filter((session) => session.threadId === threadId))).pipe(Effect.ensuring(update((current) => ({
47482
48324
  ...current,
47483
48325
  sessions: current.sessions.filter((session) => session.threadId !== threadId),
47484
48326
  bootingDevices: (current.bootingDevices ?? []).filter((device) => device.threadId !== threadId)
@@ -47486,7 +48328,10 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47486
48328
  restoreThread: threads.restore,
47487
48329
  withAgentDevice: (target, execute) => threads.run(target.threadId, withAgentDevice(target, execute)),
47488
48330
  withManualDevice: (target, execute) => threads.run(target.threadId, withManualDevice(target, execute)),
47489
- withAgentCommand: (target, execute) => threads.run(target.threadId, withAgentDevice(target, (ready, session) => execute(ready.agentDevice, session))),
48331
+ withAgentCommand: (target, execute) => threads.run(target.threadId, withAgentDevice(target, (ready, session) => ready.agentDevice ? execute(ready.agentDevice, session) : Effect.fail(new DeviceHostUnavailableError({
48332
+ hostId: target.hostId,
48333
+ reason: "This host uses semantic device control. Use cua.getDevice() and element refs."
48334
+ })))),
47490
48335
  open: (value) => threads.run(value.threadId, openWithAccess(value, "manual")),
47491
48336
  agentOpen: (value) => threads.run(value.threadId, openWithAccess(value, "agent")),
47492
48337
  close: (value) => closeWithAccess(value, "manual"),
@@ -47498,17 +48343,17 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47498
48343
  }))
47499
48344
  };
47500
48345
  });
47501
- const make$80 = Effect.gen(function* () {
48346
+ const make$79 = Effect.gen(function* () {
47502
48347
  const discover = yield* make$87();
47503
48348
  const actions = yield* make$86();
47504
48349
  return yield* makeWithHosts({
47505
- local: make$82(),
47506
- ssh: make$81,
48350
+ local: make$81(),
48351
+ ssh: make$80,
47507
48352
  discover,
47508
48353
  actions
47509
48354
  });
47510
48355
  }).pipe(Effect.provide(layer$73));
47511
- const layer$71 = Layer.effect(DeviceService, make$80);
48356
+ const layer$70 = Layer.effect(DeviceService, make$79);
47512
48357
  //#endregion
47513
48358
  //#region src/serverRuntimeStartup.ts
47514
48359
  var ServerRuntimeStartupError = class extends Schema$1.TaggedErrorClass()("ServerRuntimeStartupError", {
@@ -47653,7 +48498,7 @@ const startProviderRecovery = Effect.fn("startup.startProviderRecovery")(functio
47653
48498
  yield* input.recover;
47654
48499
  yield* input.startReaper;
47655
48500
  });
47656
- const make$79 = Effect.gen(function* () {
48501
+ const make$78 = Effect.gen(function* () {
47657
48502
  const serverConfig = yield* ServerConfig$1;
47658
48503
  const keybindings = yield* Keybindings;
47659
48504
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -47800,7 +48645,7 @@ const make$79 = Effect.gen(function* () {
47800
48645
  enqueueCommand: commandGate.enqueueCommand
47801
48646
  };
47802
48647
  });
47803
- const layer$70 = Layer.effect(ServerRuntimeStartup, make$79);
48648
+ const layer$69 = Layer.effect(ServerRuntimeStartup, make$78);
47804
48649
  //#endregion
47805
48650
  //#region src/serverRuntimeState.ts
47806
48651
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -47957,7 +48802,7 @@ function expandHomePath$2(input, path) {
47957
48802
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
47958
48803
  return input;
47959
48804
  }
47960
- const make$78 = Effect.gen(function* () {
48805
+ const make$77 = Effect.gen(function* () {
47961
48806
  const fileSystem = yield* FileSystem.FileSystem;
47962
48807
  const path = yield* Path$1.Path;
47963
48808
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -48014,7 +48859,7 @@ const make$78 = Effect.gen(function* () {
48014
48859
  resolveRelativePathWithinRoot
48015
48860
  });
48016
48861
  });
48017
- const layer$69 = Layer.effect(WorkspacePaths, make$78);
48862
+ const layer$68 = Layer.effect(WorkspacePaths, make$77);
48018
48863
  //#endregion
48019
48864
  //#region src/cli/project.ts
48020
48865
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -48120,7 +48965,7 @@ const projectCommandUuid = Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.
48120
48965
  operation: "generateProjectCommandId",
48121
48966
  cause
48122
48967
  })));
48123
- const ProjectCliRuntimeLive = Layer.mergeAll(layer$69, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$81), Layer.provideMerge(layerConfig)));
48968
+ const ProjectCliRuntimeLive = Layer.mergeAll(layer$68, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$81), Layer.provideMerge(layerConfig)));
48124
48969
  const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
48125
48970
  const withProjectCliSessionToken = (environmentAuth, run) => Effect.acquireUseRelease(environmentAuth.issueSession({
48126
48971
  scopes: AuthAdministrativeScopes,
@@ -48222,7 +49067,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
48222
49067
  });
48223
49068
  yield* Console.log(output);
48224
49069
  }).pipe(Effect.provide(offlineRuntimeLayer));
48225
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$69).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$118(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
49070
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$68).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$118(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
48226
49071
  });
48227
49072
  const projectAddCommand = Command.make("add", {
48228
49073
  ...projectLocationFlags,
@@ -51938,7 +52783,7 @@ const makeTerminationError$1 = (handle) => Effect.match(handle.exitCode, {
51938
52783
  //#region ../../packages/effect-acp/src/client.ts
51939
52784
  const MAX_STDERR_CALLBACK_CHARS = 32768;
51940
52785
  var AcpClient = class extends Context.Service()("effect-acp/client/AcpClient") {};
51941
- const make$77 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
52786
+ const make$76 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
51942
52787
  const coreHandlers = {};
51943
52788
  const notificationHandlers = {
51944
52789
  sessionUpdate: {
@@ -52105,7 +52950,7 @@ const layerChildProcess$1 = (handle, options = {}) => {
52105
52950
  })), Stream.drain)) : stdout
52106
52951
  };
52107
52952
  const terminationError = makeTerminationError$1(handle);
52108
- return Layer.effect(AcpClient, make$77(stdio, options, terminationError));
52953
+ return Layer.effect(AcpClient, make$76(stdio, options, terminationError));
52109
52954
  };
52110
52955
  //#endregion
52111
52956
  //#region src/provider/Drivers/AntigravitySkills.ts
@@ -53384,7 +54229,7 @@ const Reply = Schema$1.Struct({
53384
54229
  id: Schema$1.String,
53385
54230
  result: Schema$1.Struct({ ok: Schema$1.Boolean })
53386
54231
  });
53387
- const make$76 = Effect.fn("DeviceCommandProxy.make")(function* () {
54232
+ const make$75 = Effect.fn("DeviceCommandProxy.make")(function* () {
53388
54233
  const http = yield* HttpClient.HttpClient;
53389
54234
  const daemonSessions = /* @__PURE__ */ new WeakMap();
53390
54235
  return Effect.fn("DeviceCommandProxy.execute")(function* (endpoint, session, payload) {
@@ -53437,81 +54282,7 @@ const make$76 = Effect.fn("DeviceCommandProxy.make")(function* () {
53437
54282
  })));
53438
54283
  });
53439
54284
  var DeviceCommandProxy = class extends Context.Service()("@p4code/cli/device/DeviceCommandProxy") {};
53440
- const layer$68 = Layer.effect(DeviceCommandProxy, make$76().pipe(Effect.map((execute) => ({ execute }))));
53441
- //#endregion
53442
- //#region src/device/DeviceScreenshot.ts
53443
- const MAX_SCREENSHOT_BYTES = 8 * 1024 * 1024;
53444
- var DeviceScreenshotFailure = class extends Data.TaggedError("DeviceScreenshotFailure") {};
53445
- const MAX_DIMENSION = 16384;
53446
- const MAX_PIXELS = 64 * 1024 * 1024;
53447
- const SCREENSHOT_TIMEOUT_MS = 2e4;
53448
- const PNG_HEADER_BYTES = 33;
53449
- const PNG_CHUNK_OVERHEAD = 12;
53450
- const PNG_SIGNATURE = Buffer.from([
53451
- 137,
53452
- 80,
53453
- 78,
53454
- 71,
53455
- 13,
53456
- 10,
53457
- 26,
53458
- 10
53459
- ]);
53460
- function pngDimensions(png) {
53461
- const buffer = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
53462
- if (buffer.length < PNG_HEADER_BYTES || buffer.length > 8388608 || !buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE) || buffer.readUInt32BE(8) !== 13 || buffer.toString("ascii", 12, 16) !== "IHDR") throw new DeviceScreenshotFailure({ message: "Invalid PNG screenshot." });
53463
- const width = buffer.readUInt32BE(16);
53464
- const height = buffer.readUInt32BE(20);
53465
- if (!width || !height || width > MAX_DIMENSION || height > MAX_DIMENSION || width * height > MAX_PIXELS) throw new DeviceScreenshotFailure({ message: "Screenshot dimensions exceed limits." });
53466
- let offset = PNG_HEADER_BYTES;
53467
- let imageData = false;
53468
- let ended = false;
53469
- while (offset + PNG_CHUNK_OVERHEAD <= buffer.length) {
53470
- const length = buffer.readUInt32BE(offset);
53471
- const type = buffer.toString("ascii", offset + 4, offset + 8);
53472
- const next = offset + PNG_CHUNK_OVERHEAD + length;
53473
- if (next > buffer.length || type === "IHDR") throw new DeviceScreenshotFailure({ message: "Invalid PNG chunk." });
53474
- if (type === "IDAT") imageData = true;
53475
- if (type === "IEND") {
53476
- ended = length === 0 && next === buffer.length;
53477
- break;
53478
- }
53479
- offset = next;
53480
- }
53481
- if (!imageData || !ended) throw new DeviceScreenshotFailure({ message: "Incomplete PNG screenshot." });
53482
- return {
53483
- width,
53484
- height
53485
- };
53486
- }
53487
- const make$75 = Effect.fn("DeviceScreenshot.make")(function* () {
53488
- const http = yield* HttpClient.HttpClient;
53489
- return Effect.fn("DeviceScreenshot.capture")(function* (ready, session) {
53490
- const origin = yield* Effect.try(() => new URL(ready.hub.origin));
53491
- if (origin.protocol !== "http:" || origin.hostname !== "127.0.0.1" || !origin.port || origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) return yield* new DeviceScreenshotFailure({ message: "Invalid owned hub." });
53492
- const vendor = session.platform === "ios" ? "serve-sim" : "serve-emu";
53493
- const response = yield* http.execute(HttpClientRequest.post(`${origin.origin}/vendor/${vendor}/api/screenshot?device=${encodeURIComponent(session.deviceId)}`));
53494
- if (response.status !== 200) return yield* new DeviceScreenshotFailure({ message: "Screenshot rejected." });
53495
- let bytes = 0;
53496
- const chunks = [];
53497
- yield* response.stream.pipe(Stream.runForEach((chunk) => Effect.gen(function* () {
53498
- bytes += chunk.byteLength;
53499
- if (bytes > 8388608) return yield* new DeviceScreenshotFailure({ message: "Screenshot too large." });
53500
- chunks.push(chunk);
53501
- })));
53502
- const png = Buffer.concat(chunks, bytes);
53503
- return {
53504
- png,
53505
- ...yield* Effect.try(() => pngDimensions(png))
53506
- };
53507
- }, Effect.scoped, Effect.timeout(Duration.millis(SCREENSHOT_TIMEOUT_MS)), Effect.mapError(() => new DeviceOperationError({
53508
- operation: "screenshot",
53509
- reason: "request_failed",
53510
- cause: "Device screenshot could not be captured."
53511
- })));
53512
- });
53513
- var DeviceScreenshot = class extends Context.Service()("@p4code/cli/device/DeviceScreenshot") {};
53514
- const layer$67 = Layer.effect(DeviceScreenshot, make$75().pipe(Effect.map((capture) => ({ capture }))));
54285
+ const layer$67 = Layer.effect(DeviceCommandProxy, make$75().pipe(Effect.map((execute) => ({ execute }))));
53515
54286
  //#endregion
53516
54287
  //#region src/device/DeviceHubRoute.ts
53517
54288
  const DEVICE_HUB_PROXY_PREFIX = "/api/device-hub";
@@ -53715,12 +54486,12 @@ const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* (fusion
53715
54486
  const isDeviceToolUnavailableError = Schema$1.is(DeviceToolUnavailableError);
53716
54487
  const isDeviceActionUnavailableError = Schema$1.is(DeviceActionUnavailableError);
53717
54488
  /** Internal bridge; tool payloads never choose thread identity or daemon credentials. */
53718
- const withDeviceInvocation = (execute) => Effect.gen(function* () {
54489
+ const withDeviceInvocation = (execute, capability = "devices") => Effect.gen(function* () {
53719
54490
  const invocation = yield* McpInvocationContext;
53720
54491
  const service = yield* DeviceService;
53721
54492
  const lifetime = invocation.authorizationLifetime;
53722
54493
  const denied = () => new DeviceToolUnavailableError({ reason: "Device command unavailable or authorization changed." });
53723
- if (!invocation.capabilities.has("devices") || !lifetime?.revocation || lifetime.revoked) return yield* denied();
54494
+ if (!invocation.capabilities.has(capability) || !lifetime?.revocation || lifetime.revoked) return yield* denied();
53724
54495
  const work = execute(service, invocation.threadId).pipe(Effect.tap(() => lifetime.revoked ? Effect.fail(denied()) : Effect.void), Effect.mapError((error) => isDeviceToolUnavailableError(error) || isDeviceActionUnavailableError(error) ? error : denied()));
53725
54496
  return yield* Effect.raceFirst(work, Deferred.await(lifetime.revocation).pipe(Effect.andThen(Effect.fail(denied()))));
53726
54497
  });
@@ -53735,6 +54506,7 @@ const MAX_AX_BYTES = 512 * 1024;
53735
54506
  const AX_TIMEOUT_MS = 1e4;
53736
54507
  const MAX_AX_EXTENT = 32768;
53737
54508
  const MIN_AX_EXTENT = 2;
54509
+ const isToolUnavailable = Schema$1.is(DeviceToolUnavailableError);
53738
54510
  const AxFrames = Schema$1.Array(Schema$1.Struct({ frame: Schema$1.Struct({
53739
54511
  x: Schema$1.Number,
53740
54512
  y: Schema$1.Number,
@@ -53774,6 +54546,7 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53774
54546
  const state = yield* service.agentList;
53775
54547
  const device = state.devices.find((device) => device.hostId === input.hostId && device.id === input.deviceId && device.platform === input.platform);
53776
54548
  if (!device) return yield* stale();
54549
+ if (device.physical) return yield* new DeviceToolUnavailableError({ reason: "Physical-device UI control is not supported by this adapter. Pairing, trust, Developer Mode and signing must be configured separately; use supported device-management workflows." });
53777
54550
  const session = yield* service.agentOpen({
53778
54551
  threadId,
53779
54552
  hostId: input.hostId,
@@ -53811,8 +54584,16 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53811
54584
  deviceId: target.deviceId
53812
54585
  }, (ready, session) => Effect.gen(function* () {
53813
54586
  if (session.leaseId !== input.leaseId) return yield* stale();
54587
+ if (input.method === "recording") {
54588
+ if (!ready.xcode || session.platform !== "ios") return yield* new DeviceToolUnavailableError({ reason: "Recording is unavailable on this device adapter." });
54589
+ return yield* ready.xcode.recording(session, input.action).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) })));
54590
+ }
54591
+ if (input.method === "semantic") {
54592
+ if (!ready.xcode || session.platform !== "ios") return yield* new DeviceToolUnavailableError({ reason: "Semantic device actions require a supported local Xcode adapter. Use the capabilities returned by getAXState()." });
54593
+ return yield* ready.xcode.action(session, input.action).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) })));
54594
+ }
53814
54595
  if (input.method === "screenshot") {
53815
- const capture = yield* screenshots.capture(ready, session);
54596
+ const capture = yield* ready.xcode && session.platform === "ios" ? ready.xcode.screenshot(session).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) }))) : screenshots.capture(ready, session);
53816
54597
  return {
53817
54598
  mimeType: "image/png",
53818
54599
  data: capture.png.toString("base64"),
@@ -53820,6 +54601,7 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53820
54601
  height: capture.height
53821
54602
  };
53822
54603
  }
54604
+ if (ready.xcode && session.platform === "ios") return yield* ready.xcode.state(session, input.sinceScreenHash).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) })));
53823
54605
  if (session.platform !== "ios") return yield* new DeviceToolUnavailableError({ reason: "CUA accessibility state is currently supported only for iOS Simulator. Use a screenshot for this device." });
53824
54606
  const route = resolveDeviceHubRoute({
53825
54607
  target: {
@@ -53847,12 +54629,13 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53847
54629
  leaseId: session.leaseId,
53848
54630
  accessibility
53849
54631
  };
53850
- }).pipe(Effect.scoped, Effect.timeout(AX_TIMEOUT_MS), Effect.mapError((error) => new DeviceOperationError({
53851
- operation: "command",
53852
- reason: "request_failed",
53853
- cause: error.message
53854
- }))));
53855
- })).pipe(Effect.provideService(McpInvocationContext, scope), Effect.provideService(DeviceService, devices), Effect.mapError((error) => new ComputerUseError({
54632
+ }).pipe(Effect.scoped, Effect.timeoutOrElse({
54633
+ duration: AX_TIMEOUT_MS,
54634
+ orElse: () => Effect.gen(function* () {
54635
+ return yield* new DeviceToolUnavailableError({ reason: ready.xcode && "diagnose" in ready.xcode ? yield* ready.xcode.diagnose : "Device state request timed out. Check device support before selecting the device again." });
54636
+ })
54637
+ }), Effect.mapError((error) => isToolUnavailable(error) ? error : new DeviceToolUnavailableError({ reason: error instanceof Error ? error.message : String(error) }))));
54638
+ }), "computer-use").pipe(Effect.provideService(McpInvocationContext, scope), Effect.provideService(DeviceService, devices), Effect.mapError((error) => new ComputerUseError({
53856
54639
  reason: "unavailable",
53857
54640
  detail: error.message
53858
54641
  })));
@@ -54070,6 +54853,7 @@ const layer$66 = Layer.effect(ComputerUseBroker, Effect.gen(function* () {
54070
54853
  if (report.state === "active" && state.state === "active" && (report.previewApp !== void 0 || report.deviceControl !== void 0)) states.set(key, {
54071
54854
  ...state,
54072
54855
  ...report.previewApp !== void 0 ? { previewApp: report.previewApp } : {},
54856
+ ...report.previewApp !== void 0 ? { previewAppIcon: report.previewAppIcon ?? null } : {},
54073
54857
  ...report.deviceControl !== void 0 ? { deviceControl: report.deviceControl } : {}
54074
54858
  });
54075
54859
  if (report.state !== "active" || report.previewApp !== void 0 || report.deviceControl !== void 0) yield* publish();
@@ -65806,6 +66590,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
65806
66590
  context.inFlightTools.delete(index);
65807
66591
  }
65808
66592
  context.inFlightTools.clear();
66593
+ for (const toolUseId of context.subagentModels.keys()) if (!context.subagentTasks.has(toolUseId)) context.subagentModels.delete(toolUseId);
65809
66594
  for (const block of turnState.assistantTextBlockOrder) yield* completeAssistantTextBlock(context, block, {
65810
66595
  force: true,
65811
66596
  rawMethod: "claude/result",
@@ -65974,9 +66759,31 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
65974
66759
  discard: true
65975
66760
  });
65976
66761
  });
66762
+ const recordSubagentModel = Effect.fn("recordSubagentModel")(function* (context, toolUseId, model) {
66763
+ if (!toolUseId || !model || context.subagentModels.get(toolUseId) === model) return;
66764
+ const task = context.subagentTasks.get(toolUseId);
66765
+ if (!task && !Array.from(context.inFlightTools.values()).some((tool) => tool.itemId === toolUseId)) return;
66766
+ context.subagentModels.set(toolUseId, model);
66767
+ if (!task) return;
66768
+ yield* offerRuntimeEvent({
66769
+ ...yield* makeEventStamp(),
66770
+ type: "task.progress",
66771
+ provider: PROVIDER$6,
66772
+ threadId: context.session.threadId,
66773
+ ...context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {},
66774
+ payload: {
66775
+ taskId: RuntimeTaskId.make(task.taskId),
66776
+ description: task.description,
66777
+ toolUseId,
66778
+ ...task.subagentType ? { subagentType: task.subagentType } : {},
66779
+ model
66780
+ }
66781
+ });
66782
+ });
65977
66783
  const handleStreamEvent = Effect.fn("handleStreamEvent")(function* (context, message) {
65978
66784
  if (message.type !== "stream_event") return;
65979
66785
  const { event } = message;
66786
+ if (event.type === "message_start") yield* recordSubagentModel(context, message.parent_tool_use_id, event.message.model);
65980
66787
  if (event.type === "message_delta") {
65981
66788
  if (message.parent_tool_use_id !== null && message.parent_tool_use_id !== void 0) return;
65982
66789
  const snapshot = normalizeClaudeActiveTokenUsage(event.usage, context.lastKnownContextWindow, context.lastKnownTotalProcessedTokens);
@@ -66297,10 +67104,12 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66297
67104
  rawPayload: message
66298
67105
  });
66299
67106
  context.inFlightTools.delete(index);
67107
+ if (!context.subagentTasks.has(tool.itemId)) context.subagentModels.delete(tool.itemId);
66300
67108
  }
66301
67109
  });
66302
67110
  const handleAssistantMessage = Effect.fn("handleAssistantMessage")(function* (context, message) {
66303
67111
  if (message.type !== "assistant") return;
67112
+ yield* recordSubagentModel(context, message.parent_tool_use_id, message.message.model);
66304
67113
  if (!context.turnState) {
66305
67114
  const turnId = TurnId.make(yield* randomUUIDv4);
66306
67115
  const startedAt = yield* nowIso;
@@ -66407,6 +67216,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66407
67216
  }
66408
67217
  switch (message.subtype) {
66409
67218
  case "init":
67219
+ context.currentApiModelId = message.model || context.currentApiModelId;
66410
67220
  yield* offerRuntimeEvent({
66411
67221
  ...base,
66412
67222
  type: "session.configured",
@@ -66477,7 +67287,12 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66477
67287
  return;
66478
67288
  case "task_started": {
66479
67289
  const requestedModel = readString((message.tool_use_id ? Array.from(context.inFlightTools.values()).find((tool) => tool.itemId === message.tool_use_id) : void 0)?.input.model);
66480
- const model = requestedModel === "inherit" ? context.currentApiModelId : requestedModel;
67290
+ const model = (message.tool_use_id ? context.subagentModels.get(message.tool_use_id) : void 0) ?? (requestedModel === "inherit" ? context.currentApiModelId : requestedModel);
67291
+ if (message.tool_use_id) context.subagentTasks.set(message.tool_use_id, {
67292
+ taskId: message.task_id,
67293
+ description: message.description,
67294
+ ...message.subagent_type ? { subagentType: message.subagent_type } : {}
67295
+ });
66481
67296
  yield* offerRuntimeEvent({
66482
67297
  ...base,
66483
67298
  type: "task.started",
@@ -66528,6 +67343,11 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66528
67343
  ...message.tool_use_id ? { toolUseId: message.tool_use_id } : {}
66529
67344
  }
66530
67345
  });
67346
+ for (const [toolUseId, task] of context.subagentTasks) if (toolUseId === message.tool_use_id || task.taskId === message.task_id) {
67347
+ context.subagentTasks.delete(toolUseId);
67348
+ context.subagentModels.delete(toolUseId);
67349
+ }
67350
+ if (message.tool_use_id) context.subagentModels.delete(message.tool_use_id);
66531
67351
  return;
66532
67352
  case "files_persisted":
66533
67353
  yield* offerRuntimeEvent({
@@ -67228,6 +68048,8 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
67228
68048
  turns: [],
67229
68049
  inFlightTools,
67230
68050
  claudeTasks,
68051
+ subagentTasks: /* @__PURE__ */ new Map(),
68052
+ subagentModels: /* @__PURE__ */ new Map(),
67231
68053
  turnState: void 0,
67232
68054
  pendingTurnRequest: void 0,
67233
68055
  steeredMessages: /* @__PURE__ */ new Map(),
@@ -125492,6 +126314,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
125492
126314
  [WS_METHODS.serverUpdateServer, AuthOrchestrationOperateScope],
125493
126315
  [WS_METHODS.serverUpsertKeybinding, AuthOrchestrationOperateScope],
125494
126316
  [WS_METHODS.serverRemoveKeybinding, AuthOrchestrationOperateScope],
126317
+ [WS_METHODS.threadHandoff, AuthOrchestrationReadScope],
125495
126318
  [WS_METHODS.btwAsk, AuthOrchestrationReadScope],
125496
126319
  [WS_METHODS.btwCancel, AuthOrchestrationReadScope],
125497
126320
  [WS_METHODS.tasksList, AuthOrchestrationReadScope],
@@ -126344,6 +127167,27 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker, computerUseBrok
126344
127167
  operation: "getUsageReport",
126345
127168
  detail: "This provider does not report account usage."
126346
127169
  })), { "rpc.aggregate": "server" }),
127170
+ [WS_METHODS.threadHandoff]: (input) => observeRpcEffect$1(WS_METHODS.threadHandoff, Effect.gen(function* () {
127171
+ const fail = (detail) => new TextGenerationError({
127172
+ operation: "createHandoff",
127173
+ detail
127174
+ });
127175
+ if (!projectionSnapshotQuery.getHandoffContext || !textGeneration.generateBtwAnswer) return yield* fail("Handoff generation is unavailable on this server.");
127176
+ const context = yield* projectionSnapshotQuery.getHandoffContext(input.threadId).pipe(Effect.mapError(() => fail("Could not read thread history.")));
127177
+ if (Option.isNone(context)) return yield* fail("Thread or project is no longer available.");
127178
+ const settings = yield* serverSettings.getSettings.pipe(Effect.mapError(() => fail("Could not read text generation settings.")));
127179
+ const result = yield* textGeneration.generateBtwAnswer({
127180
+ cwd: context.value.projectId === P4_CHAT_PROJECT_ID ? config.chatWorkspaceDir : context.value.cwd,
127181
+ modelSelection: settings.textGenerationModelSelection,
127182
+ context: context.value.transcript,
127183
+ question: "Write a self-contained handoff for a new coding-agent thread continuing this work. Include objective, latest user constraints, decisions, completed changes, verification evidence and its limits, pending work, important paths and identifiers. Reconcile builder and watcher histories without duplicating their shared facts. Later user corrections win. Preserve explicit authorization limits; do not invent approvals or claim missing work is complete. Histories may be bounded: say when earlier context is unavailable. Return only the handoff in concise Markdown, addressed to the next agent."
127184
+ });
127185
+ if (!result.answer.trim()) return yield* fail("Model returned an empty handoff. Try again.");
127186
+ return {
127187
+ projectId: context.value.projectId,
127188
+ text: result.answer.trim()
127189
+ };
127190
+ }), { "rpc.aggregate": "thread" }),
126347
127191
  [WS_METHODS.btwAsk]: (input) => observeRpcEffect$1(WS_METHODS.btwAsk, btw ? btw.ask(input) : Effect.fail(new BtwAskError({ detail: "Contextual questions are unavailable on this server." })), { "rpc.aggregate": "server" }),
126348
127192
  [WS_METHODS.btwCancel]: (input) => observeRpcEffect$1(WS_METHODS.btwCancel, btw ? btw.cancel(input) : Effect.succeed({}), { "rpc.aggregate": "server" }),
126349
127193
  [WS_METHODS.serverGetUsageSummary]: (input) => observeRpcEffect$1(WS_METHODS.serverGetUsageSummary, usage.readSummary(input), { "rpc.aggregate": "server" }),
@@ -127104,6 +127948,13 @@ const makeProviderAdapterRegistry = Effect.fn("makeProviderAdapterRegistry")(fun
127104
127948
  });
127105
127949
  const ProviderAdapterRegistryLive = Layer.effect(ProviderAdapterRegistry, makeProviderAdapterRegistry());
127106
127950
  //#endregion
127951
+ //#region src/provider/ComputerUsePrompts.ts
127952
+ /** Routing guidance is injected only when the session has P4's Computer Use endpoint. */
127953
+ const COMPUTER_USE_ROUTING_PROMPT = "P4 Computer Use is enabled for this session through the cua_repl MCP server. For natural-language requests to inspect or interact with a native desktop app, use its js tool by default, even when the user does not mention P4 or Computer Use. Do not substitute provider-builtin Computer Use, AppleScript, shell automation, or another integration unless the user explicitly requests that path. For a named app, start directly with const app = await cua.getApp(exactNameOrBundleId); read the automatically returned state/image and stop if it answers the request. Reuse the handle and batch safe actions; do not repeat selection or unchanged state. For Device Hub, simulator, emulator, or connected-device requests, use cua.listDevices() once, then cua.getDevice({hostId,deviceId,platform}) with exact discovered IDs. Selection already emits initial device state. Reuse its handle; do not print the handle or repeat that state before acting. Never substitute macOS getApp('Simulator'). Device hosts are scoped to this provider environment and can differ from the desktop host. If device discovery, opening, inspection, or interaction reports missing permission or unavailable control, stop and report the exact limitation. Do not retry through screenshots, shell simctl, Xcode tools, or another MCP server. Do not call getState/getApp, control the P4 desktop app, or open device settings to work around a device API failure. Missing consent, unavailable devices, or unsupported operations are limitations to report, not permission to switch hosts or bypass the tool.";
127954
+ function withComputerUseRouting(input, enabled) {
127955
+ return enabled ? `<p4-computer-use>\n${COMPUTER_USE_ROUTING_PROMPT}\n</p4-computer-use>\n\n${input ?? ""}` : input;
127956
+ }
127957
+ //#endregion
127107
127958
  //#region src/mcp/McpSessionRegistry.ts
127108
127959
  var McpSessionRegistry = class extends Context.Service()("@p4code/cli/mcp/McpSessionRegistry") {};
127109
127960
  /**
@@ -127877,7 +128728,10 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
127877
128728
  ...input.modelSelection?.model ? { "provider.model": input.modelSelection.model } : {}
127878
128729
  });
127879
128730
  yield* touchActiveMcpThread(input.threadId);
127880
- const turn = yield* routed.adapter.sendTurn(input);
128731
+ const turn = yield* routed.adapter.sendTurn({
128732
+ ...input,
128733
+ input: withComputerUseRouting(input.input, !forCompaction && Boolean(readMcpProviderSession(input.threadId)?.computerUse))
128734
+ });
127881
128735
  yield* directory.upsert({
127882
128736
  threadId: input.threadId,
127883
128737
  provider: routed.adapter.provider,
@@ -128362,7 +129216,7 @@ const mcpToolkit = (toolkit) => Layer.effectDiscard(Effect.gen(function* () {
128362
129216
  //#endregion
128363
129217
  //#region src/mcp/toolkits/computerUse/tools.ts
128364
129218
  const ComputerUseJsTool = Tool.make("js", {
128365
- description: "Run JavaScript in a persistent P4 Computer Use REPL. Start with await cua.getState(). Use cua.createBrowserTab('iab', url, {visible:true}) or cua.getTab(tabId) for P4 browser tabs. Tab methods: getState(), goto(url), click({locator}), type({text,locator?,clear?}), press({key,modifiers?}), scroll({deltaY,deltaX?}). Print results with nodeRepl.write(value); emit PNG/JPEG with nodeRepl.emitImage({mimeType,data}) using base64 data. Top-level await and persistent let bindings are supported. Await each operation. Reset after timeout or host disconnect. For available native apps, use cua.getApp(exactNameOrBundleId), then getAXState(), click(elementIndex), or setValue(elementIndex,text) to replace a field value in the background. Selection and getAXState print numbered accessibility state; Several deterministic actions against distinct unchanged elements may share one snapshot. Each used handle expires; the host revalidates remaining native handles and rejects changed elements. Read fresh state after the action batch before deciding the next action. getScreenshot() emits the selected app window; getAXStateAndScreenshot() emits both. Legacy getState(), click({elementId}), type({elementId,text}), windows(), and screenshot(windowId) remain supported. Native control preserves foreground focus and the system pointer. Coordinate, key, drag, scroll, and activation input are unsupported; do not use global input or activate the app as a fallback. P4Code shows the controlled native window and a persistent independent white pointer with a blue glow in its embedded live view. Hide closes only the view; use Show live view in the Computer Use control to reopen it. Stop ends thread control; reset clears the view and handles. Google Chrome can be controlled as a native app using cua.getApp('Google Chrome'); Chrome extension/CDP tab APIs are not exposed by this host. Native app availability and macOS permission states are reported by the host; never assume native apps are supported. The host may be a different machine from the provider server. Device hub simulators are scoped to the provider environment: await cua.listDevices(), then await cua.getDevice({hostId,deviceId,platform}) using exact discovered IDs. The device handle supports getAXState() (raw iOS accessibility state), getScreenshot(), getAXStateAndScreenshot(), click([x,y]) in device coordinates from fresh AX state, typeText(text), and close(). No device key, scroll, or drag APIs are currently exposed. Device consent must already be enabled in P4 Settings. Use timeout_ms:60000 for initial device startup. Reset invalidates device handles; device viewer sessions remain under Devices until explicitly closed. Never substitute macOS Simulator.app AX for simulated-app state.",
129219
+ description: "P4 Code Computer Use: control native apps through P4Code's signed helper. Run JavaScript in a persistent REPL. On the first call, execute exactly one entry-point operation and read its automatic output before continuing. When the user names an app, execute only const app = await cua.getApp(exactNameBundleIdOrPath); this already prints its state and includes a screenshot when AX exposes no app content. Use await cua.getState() only when discovery is needed. Use cua.createBrowserTab('iab', url, {visible:true}) or cua.getTab(tabId) for P4 browser tabs. Tab methods: getState(), goto(url), click({locator}), type({text,locator?,clear?}), press({key,modifiers?}), scroll({deltaY,deltaX?}). Print results with nodeRepl.write(value); emit PNG/JPEG with nodeRepl.emitImage({mimeType,data}) using base64 data. Top-level await and persistent let bindings are supported. Await each operation. Bindings disappear after helper restart, host disconnect, reset, or tray Stop. When resuming a task, guard a remembered binding within the same js call: if (typeof app === 'undefined') { globalThis.app = await cua.getApp('Spotify'); } else { await app.getScreenshot(); }. Substitute the named app and intended operation. The missing-binding branch selects only and prints fresh state; decide the next action after reading it. Never spend a separate tool call checking whether a binding exists. Reset after timeout or host disconnect. For available native apps, use cua.getApp(exactNameBundleIdOrPath), then getAXState(), click(elementIndex), or setValue(elementIndex,text) to replace a field value in the background. Selection already prints numbered accessibility state; reuse the app handle without selecting or reading it again before the first action. Snapshot text is bounded; use getAXState({filter:'Pause'}) for case-insensitive text filtering across the snapshot, including controls omitted from the initial output. Use {emit:false} on getApp(name, options), getAXState(options), or getScreenshot(options) when processing the returned value yourself; do not print auto-emitted output again. Several deterministic actions against distinct unchanged elements may share one snapshot. Each used element index expires after one action. To click the same control again, refresh with getAXState({filter: controlLabel}) first, even inside a batch; never reuse its old index. The host revalidates remaining native handles and rejects changed elements. Read fresh state after the action batch before deciding the next action. If selection includes a screenshot because AX exposes only window controls or menus, read that image immediately and finish if it answers the request. If no image is included, use getScreenshot() to read visual content; do not repeat unchanged snapshots hoping for missing accessibility content. getScreenshot() emits the selected app window; getAXStateAndScreenshot() emits both. Legacy getState(), click({elementId}), type({elementId,text}), windows(), and screenshot(windowId) remain supported. Native control preserves foreground focus and the system pointer. Coordinate, key, drag, scroll, and activation input are unsupported; do not use global input or activate the app as a fallback. P4Code shows the controlled native window in its embedded live view. A persistent independent black pointer marks actions on the host window, outside the preview. Hide closes only the view; use Show live view in the Computer Use control to reopen it. Stop ends thread control; reset clears the view and handles. Google Chrome can be controlled as a native app using cua.getApp('Google Chrome'); Chrome extension/CDP tab APIs are not exposed by this host. Native app availability and macOS permission states are reported by the host; never assume native apps are supported. The host may be a different machine from the provider server. Device hub simulators are scoped to the provider environment: await cua.listDevices(), then await cua.getDevice({hostId,deviceId,platform}) using exact discovered IDs. Selection automatically emits initial AX state (or a screenshot on other platforms); reuse the handle without repeating that capture or printing the handle. A permission or unavailable-control error requires stopping and reporting it, never shell or alternative-tool fallback. The device handle supports getAXState() (raw iOS accessibility state), getScreenshot(), getAXStateAndScreenshot(), click([x,y]) in device coordinates from fresh AX state, typeText(text), and close(). When getAXState() reports adapter apple-xcode or xcodebuildmcp, use semantic refs from capture.elements and only advertised capabilities: click(elementRef), typeText(text,{elementRef,replaceExisting:true}), swipe({withinElementRef,direction}), drag({elementRef,direction}), batch([{action:\"tap\",elementRef},...]), and recordVideo(\"start\"|\"stop\") only when advertised in capabilities. getAXState({sinceScreenHash}) avoids unchanged snapshots. Batch only same-screen taps; action output includes updated capture. On stale-ref errors inspect fresh state and choose a current ref; never replay an action automatically. Physical-device UI control and device key input are unavailable. Device consent must already be enabled in P4 Settings. Use timeout_ms:60000 for initial device startup. Reset invalidates device handles; device viewer sessions remain under Devices until explicitly closed. Never substitute macOS Simulator.app AX for simulated-app state.",
128366
129220
  parameters: Schema$1.Struct({
128367
129221
  ...ComputerUseJsInput.fields,
128368
129222
  hostId: Schema$1.optional(TrimmedNonEmptyString).annotate({ description: "Select an available host when more than one is connected. Reset before changing hosts." })
@@ -132626,6 +133480,7 @@ function runtimeEventToActivities(event, taskTitle, compressMode) {
132626
133480
  summary: event.payload.description.trim().length > 0 ? truncateDetail(event.payload.description, 120) : "Reasoning update",
132627
133481
  payload: {
132628
133482
  taskId: event.payload.taskId,
133483
+ ...event.payload.model ? { model: event.payload.model } : {},
132629
133484
  ...event.provider === "claudeAgent" && event.payload.workflowScript?.instanceId === event.providerInstanceId ? { workflowScript: event.payload.workflowScript } : {},
132630
133485
  ...event.payload.description.trim().length > 0 ? { title: truncateDetail(event.payload.description, 120) } : {},
132631
133486
  detail: event.payload.summary ?? event.payload.description,
@@ -137341,13 +138196,13 @@ const CheckpointingLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$46), La
137341
138196
  const PortScannerLayerLive = layer$45.pipe(Layer.provide(layer$82));
137342
138197
  const TerminalLayerLive = layer$44.pipe(Layer.provide(PtyAdapterLive), Layer.provide(PortScannerLayerLive));
137343
138198
  const PreviewLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$19), Layer.provideMerge(PortScannerLayerLive));
137344
- const WorkspaceEntriesLayerLive = layer$17.pipe(Layer.provide(layer$69));
137345
- const WorkspaceFileSystemLayerLive = layer$16.pipe(Layer.provide(layer$69), Layer.provide(WorkspaceEntriesLayerLive));
137346
- const WorkspaceLayerLive = Layer.mergeAll(layer$69, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
137347
- const ProjectFaviconResolverLayerLive = layer$63.pipe(Layer.provide(layer$69), Layer.provide(layer$64));
138199
+ const WorkspaceEntriesLayerLive = layer$17.pipe(Layer.provide(layer$68));
138200
+ const WorkspaceFileSystemLayerLive = layer$16.pipe(Layer.provide(layer$68), Layer.provide(WorkspaceEntriesLayerLive));
138201
+ const WorkspaceLayerLive = Layer.mergeAll(layer$68, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
138202
+ const ProjectFaviconResolverLayerLive = layer$63.pipe(Layer.provide(layer$68), Layer.provide(layer$64));
137348
138203
  const AuthLayerLive = layer$111.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$115));
137349
138204
  const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
137350
- const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$25), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$86)), Layer.provideMerge(layer$80), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(layer$58), Layer.provideMerge(Layer.mergeAll(layer$57, layer$71.pipe(Layer.provide(layer$82)), layer$67, layer$68)), Layer.provideMerge(layer$52), Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$56)).pipe(Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(AntigravityInstallation.layer), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$60), Layer.provideMerge(layer$90.pipe(Layer.provide(layer$115))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$81), Layer.provideMerge(layer$66), Layer.provideMerge(remoteComputerUseBindingsLayer.pipe(Layer.provide(layer$86))), Layer.provideMerge(layer$74), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$5.pipe(Layer.provideMerge(LinearClientLive), Layer.provideMerge(Layer.mergeAll(LinearMcpClientLive, TrackerSecretsLive)), Layer.provideMerge(Layer.mergeAll(layer$87, layer$21).pipe(Layer.provideMerge(layer$88), Layer.provideMerge(layer$89))))), Layer.provideMerge(layer$115));
138205
+ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$25), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$86)), Layer.provideMerge(layer$80), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(layer$58), Layer.provideMerge(Layer.mergeAll(layer$57, layer$70.pipe(Layer.provide(layer$82)), layer$72, layer$67)), Layer.provideMerge(layer$52), Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$56)).pipe(Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(AntigravityInstallation.layer), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$60), Layer.provideMerge(layer$90.pipe(Layer.provide(layer$115))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$81), Layer.provideMerge(layer$66), Layer.provideMerge(remoteComputerUseBindingsLayer.pipe(Layer.provide(layer$86))), Layer.provideMerge(layer$74), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$5.pipe(Layer.provideMerge(LinearClientLive), Layer.provideMerge(Layer.mergeAll(LinearMcpClientLive, TrackerSecretsLive)), Layer.provideMerge(Layer.mergeAll(layer$87, layer$21).pipe(Layer.provideMerge(layer$88), Layer.provideMerge(layer$89))))), Layer.provideMerge(layer$115));
137351
138206
  const UsageLayerLive = layer$23.pipe(Layer.provide(layer$90.pipe(Layer.provide(layer$115))));
137352
138207
  const RuntimeDependenciesLive = layer$22.pipe(Layer.provideMerge(RuntimeCoreDependenciesLive)).pipe(Layer.provideMerge(UsageLayerLive), Layer.provideMerge(layer$12), Layer.provideMerge(layer$11), Layer.provideMerge(layer$10), Layer.provideMerge(layer$77), Layer.provideMerge(layer$79), Layer.provideMerge(layer$78), Layer.provide(layer$119));
137353
138208
  /**
@@ -137358,7 +138213,7 @@ const RuntimeDependenciesLive = layer$22.pipe(Layer.provideMerge(RuntimeCoreDepe
137358
138213
  * this costs a server with no hub nothing but a layer.
137359
138214
  */
137360
138215
  const AssetSyncLive = layer$84.pipe(Layer.provideMerge(layer$85), Layer.provideMerge(layer$86));
137361
- const RuntimeServicesLive = layer$70.pipe(Layer.provideMerge(layer$1), Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
138216
+ const RuntimeServicesLive = layer$69.pipe(Layer.provideMerge(layer$1), Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
137362
138217
  const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(pullRequestHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, mcpOAuthCallbackRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer$4.pipe(Layer.provide(layer$6)), layer$65, layer).pipe(Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionThreadRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$64), Layer.provide(layer$25), Layer.provide(PullRequestServiceLive), Layer.provide(layer$20), Layer.provide(layer$75), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
137363
138218
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
137364
138219
  const config = yield* ServerConfig$1;