@p4code/cli 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.6";
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
@@ -13591,6 +13601,45 @@ const ComputerUseDeviceTarget = {
13591
13601
  deviceId: DeviceId,
13592
13602
  leaseId: DeviceId
13593
13603
  };
13604
+ const DeviceElementRef = TrimmedNonEmptyString.check(Schema$1.isMaxLength(128));
13605
+ const DeviceGesture = {
13606
+ direction: Schema$1.Literals([
13607
+ "up",
13608
+ "down",
13609
+ "left",
13610
+ "right"
13611
+ ]),
13612
+ distance: Schema$1.optional(Schema$1.Number.check(Schema$1.isGreaterThan(0), Schema$1.isLessThanOrEqualTo(1)))
13613
+ };
13614
+ const ComputerUseDeviceAction = Schema$1.Union([
13615
+ Schema$1.Struct({
13616
+ action: Schema$1.Literal("tap"),
13617
+ elementRef: DeviceElementRef
13618
+ }),
13619
+ Schema$1.Struct({
13620
+ action: Schema$1.Literal("type_text"),
13621
+ elementRef: DeviceElementRef,
13622
+ text: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(16e3)),
13623
+ replaceExisting: Schema$1.optional(Schema$1.Boolean)
13624
+ }),
13625
+ Schema$1.Struct({
13626
+ action: Schema$1.Literal("swipe"),
13627
+ withinElementRef: DeviceElementRef,
13628
+ ...DeviceGesture
13629
+ }),
13630
+ Schema$1.Struct({
13631
+ action: Schema$1.Literal("drag"),
13632
+ elementRef: DeviceElementRef,
13633
+ ...DeviceGesture
13634
+ }),
13635
+ Schema$1.Struct({
13636
+ action: Schema$1.Literal("batch"),
13637
+ steps: Schema$1.Array(Schema$1.Struct({
13638
+ action: Schema$1.Literal("tap"),
13639
+ elementRef: DeviceElementRef
13640
+ })).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(100))
13641
+ })
13642
+ ]);
13594
13643
  const ComputerUseDeviceOperation = Schema$1.Union([
13595
13644
  Schema$1.Struct({ method: Schema$1.Literal("list") }),
13596
13645
  Schema$1.Struct({
@@ -13605,7 +13654,18 @@ const ComputerUseDeviceOperation = Schema$1.Union([
13605
13654
  "screenshot",
13606
13655
  "close"
13607
13656
  ]),
13608
- ...ComputerUseDeviceTarget
13657
+ ...ComputerUseDeviceTarget,
13658
+ sinceScreenHash: Schema$1.optional(TrimmedNonEmptyString)
13659
+ }),
13660
+ Schema$1.Struct({
13661
+ method: Schema$1.Literal("semantic"),
13662
+ ...ComputerUseDeviceTarget,
13663
+ action: ComputerUseDeviceAction
13664
+ }),
13665
+ Schema$1.Struct({
13666
+ method: Schema$1.Literal("recording"),
13667
+ ...ComputerUseDeviceTarget,
13668
+ action: Schema$1.Literals(["start", "stop"])
13609
13669
  }),
13610
13670
  Schema$1.Struct({
13611
13671
  method: Schema$1.Literal("command"),
@@ -13717,6 +13777,7 @@ const WS_METHODS = {
13717
13777
  serverGetProcessDiagnostics: "server.getProcessDiagnostics",
13718
13778
  serverGetProcessResourceHistory: "server.getProcessResourceHistory",
13719
13779
  serverSignalProcess: "server.signalProcess",
13780
+ threadHandoff: "thread.handoff",
13720
13781
  btwAsk: "btw.ask",
13721
13782
  btwCancel: "btw.cancel",
13722
13783
  pullRequestsList: "pullRequests.list",
@@ -14360,6 +14421,14 @@ const WsServerGetProviderUsageRpc = Rpc.make(WS_METHODS.serverGetProviderUsage,
14360
14421
  success: Schema$1.Struct({ report: Schema$1.String }),
14361
14422
  error: Schema$1.Union([TextGenerationError, EnvironmentAuthorizationError])
14362
14423
  });
14424
+ const WsThreadHandoffRpc = Rpc.make(WS_METHODS.threadHandoff, {
14425
+ payload: Schema$1.Struct({ threadId: ThreadId }),
14426
+ success: Schema$1.Struct({
14427
+ projectId: ProjectId,
14428
+ text: Schema$1.String
14429
+ }),
14430
+ error: Schema$1.Union([TextGenerationError, EnvironmentAuthorizationError])
14431
+ });
14363
14432
  const WsBtwAskRpc = Rpc.make(WS_METHODS.btwAsk, {
14364
14433
  payload: BtwAskInput,
14365
14434
  success: BtwAskResult,
@@ -14820,7 +14889,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
14820
14889
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
14821
14890
  stream: true
14822
14891
  });
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);
14892
+ 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
14893
  //#endregion
14825
14894
  //#region ../../packages/shared/src/oauthScope.ts
14826
14895
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -24635,7 +24704,7 @@ const MAX_APNS_REQUEST_BYTES = 4096;
24635
24704
  const CLEANUP_TIMEOUT_MS$1 = 1e3;
24636
24705
  const MAX_REQUEST_BYTES$2 = 8 * 1024;
24637
24706
  const MAX_RESPONSE_BYTES$8 = 16 * 1024;
24638
- const REQUEST_TIMEOUT_MS$6 = 8e3;
24707
+ const REQUEST_TIMEOUT_MS$7 = 8e3;
24639
24708
  const MAX_AUTH_CHARS = 8192;
24640
24709
  const unavailable$6 = () => ({
24641
24710
  outcome: "retry",
@@ -24680,7 +24749,7 @@ const sendHubApns = Effect.fn("HubPushTransport.apns")(function* (input) {
24680
24749
  finish(unavailable$6());
24681
24750
  return;
24682
24751
  }
24683
- timer = setTimeout(() => finish(unavailable$6()), REQUEST_TIMEOUT_MS$6);
24752
+ timer = setTimeout(() => finish(unavailable$6()), REQUEST_TIMEOUT_MS$7);
24684
24753
  session = NodeHttp2.connect(input.sandbox ? "https://api.sandbox.push.apple.com" : "https://api.push.apple.com", { maxHeaderListPairs: 32 });
24685
24754
  session.on("error", () => finish(unavailable$6()));
24686
24755
  session.on("close", () => finish(unavailable$6()));
@@ -24735,7 +24804,7 @@ const sendHubFcm = Effect.fn("HubPushTransport.fcm")(function* (input, fetcher =
24735
24804
  const abort = () => owner.abort();
24736
24805
  signal.addEventListener("abort", abort, { once: true });
24737
24806
  if (signal.aborted) owner.abort();
24738
- const timeout = setTimeout(abort, REQUEST_TIMEOUT_MS$6);
24807
+ const timeout = setTimeout(abort, REQUEST_TIMEOUT_MS$7);
24739
24808
  let reader;
24740
24809
  try {
24741
24810
  const response = await fetcher(`https://fcm.googleapis.com/v1/projects/${input.projectId}/messages:send`, {
@@ -24776,7 +24845,7 @@ const sendHubFcm = Effect.fn("HubPushTransport.fcm")(function* (input, fetcher =
24776
24845
  }
24777
24846
  },
24778
24847
  catch: () => unavailable$6()
24779
- }).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$6), Effect.catch(() => Effect.succeed(unavailable$6())));
24848
+ }).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$7), Effect.catch(() => Effect.succeed(unavailable$6())));
24780
24849
  });
24781
24850
  //#endregion
24782
24851
  //#region src/hub/HubNotificationRuntime.ts
@@ -41682,6 +41751,72 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
41682
41751
  if (isPersistenceError(error)) return error;
41683
41752
  return toPersistenceSqlError("ProjectionSnapshotQuery.getSnapshot:query")(error);
41684
41753
  }));
41754
+ const readHandoffThread = SqlSchema.findOneOption({
41755
+ Request: ThreadIdLookupInput,
41756
+ Result: ProjectionBtwThreadRowSchema,
41757
+ execute: ({ threadId }) => sql`
41758
+ SELECT threads.project_id AS "projectId",
41759
+ COALESCE(projects.workspace_root, threads.worktree_path, '') AS cwd
41760
+ FROM projection_threads AS threads
41761
+ LEFT JOIN projection_projects AS projects ON projects.project_id = threads.project_id
41762
+ WHERE threads.thread_id = ${threadId} AND threads.deleted_at IS NULL
41763
+ AND (threads.project_id = ${P4_CHAT_PROJECT_ID} OR projects.deleted_at IS NULL)
41764
+ `
41765
+ });
41766
+ const readHandoffRows = SqlSchema.findAll({
41767
+ Request: ThreadIdLookupInput,
41768
+ Result: Schema$1.Struct({
41769
+ threadId: ThreadId,
41770
+ role: Schema$1.String,
41771
+ text: Schema$1.String
41772
+ }),
41773
+ execute: ({ threadId }) => sql`
41774
+ WITH latest_pair AS (
41775
+ SELECT implementer_thread_id, watcher_thread_id FROM thread_pairs
41776
+ WHERE implementer_thread_id = ${threadId} OR watcher_thread_id = ${threadId}
41777
+ ORDER BY created_at DESC LIMIT 1
41778
+ ), sources AS (
41779
+ SELECT ${threadId} AS id
41780
+ UNION SELECT implementer_thread_id FROM latest_pair
41781
+ UNION SELECT watcher_thread_id FROM latest_pair
41782
+ ), ranked AS (
41783
+ SELECT messages.thread_id AS "threadId", messages.role,
41784
+ substr(messages.text, -${BTW_CONTEXT_CHARACTER_LIMIT}) AS text,
41785
+ messages.created_at, messages.message_id,
41786
+ ROW_NUMBER() OVER (PARTITION BY messages.thread_id ORDER BY messages.created_at DESC, messages.message_id DESC) AS recent,
41787
+ ROW_NUMBER() OVER (PARTITION BY messages.thread_id ORDER BY messages.created_at, messages.message_id) AS earliest
41788
+ FROM projection_thread_messages AS messages
41789
+ JOIN projection_threads AS threads ON threads.thread_id = messages.thread_id
41790
+ WHERE messages.thread_id IN (SELECT id FROM sources)
41791
+ AND threads.deleted_at IS NULL AND messages.is_streaming = 0
41792
+ AND messages.role IN ('user', 'assistant')
41793
+ ) SELECT "threadId", role, text FROM ranked
41794
+ WHERE recent <= ${BTW_CONTEXT_MESSAGE_LIMIT} OR earliest = 1
41795
+ ORDER BY created_at, message_id
41796
+ `
41797
+ });
41798
+ const getHandoffContext = Effect.fn("ProjectionSnapshotQuery.getHandoffContext")(function* (threadId) {
41799
+ return yield* sql.withTransaction(Effect.gen(function* () {
41800
+ const thread = yield* readHandoffThread({ threadId });
41801
+ if (Option.isNone(thread)) return Option.none();
41802
+ const rows = yield* readHandoffRows({ threadId });
41803
+ const sourceIds = [...new Set(rows.map((row) => row.threadId))];
41804
+ const sourceCount = Math.max(1, sourceIds.length);
41805
+ const budget = Math.floor((BTW_CONTEXT_CHARACTER_LIMIT - 2 * (sourceCount - 1)) / sourceCount);
41806
+ const transcript = sourceIds.map((sourceId) => {
41807
+ const text = rows.filter((row) => row.threadId === sourceId).map((row) => `${row.role}: ${row.text}`).join("\n\n");
41808
+ const heading = `Thread ${sourceId} (bounded history):\n`;
41809
+ const available = budget - heading.length;
41810
+ const omission = "\n[Earlier history omitted]\n";
41811
+ const beginning = Math.floor(available / 4);
41812
+ return heading + (text.length <= available ? text : text.slice(0, beginning) + omission + text.slice(-(available - beginning - 27)));
41813
+ }).join("\n\n");
41814
+ return Option.some({
41815
+ ...thread.value,
41816
+ transcript
41817
+ });
41818
+ })).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getHandoffContext:query", "ProjectionSnapshotQuery.getHandoffContext:decodeRows")));
41819
+ });
41685
41820
  const getBtwContext = Effect.fn("ProjectionSnapshotQuery.getBtwContext")(function* (threadId) {
41686
41821
  const [thread, rows] = yield* Effect.all([getBtwThreadRow({ threadId }), listBtwContextRows({ threadId })]).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getBtwContext:query", "ProjectionSnapshotQuery.getBtwContext:decodeRows")));
41687
41822
  if (Option.isNone(thread)) return Option.none();
@@ -42265,6 +42400,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
42265
42400
  getThreadPairById,
42266
42401
  getThreadShellById,
42267
42402
  getThreadDetailById,
42403
+ getHandoffContext,
42268
42404
  getBtwContext,
42269
42405
  getThreadDetailSnapshot
42270
42406
  };
@@ -45373,7 +45509,7 @@ var DeviceDiscoveryFailure = class extends Data.TaggedError("DeviceDiscoveryFail
45373
45509
  const MAX_DEVICES = 512;
45374
45510
  const MAX_RESPONSE_BYTES$6 = 1024 * 1024;
45375
45511
  const MAX_DEVICE_LABEL = 512;
45376
- const REQUEST_TIMEOUT_MS$5 = 15e3;
45512
+ const REQUEST_TIMEOUT_MS$6 = 15e3;
45377
45513
  const HubDevice = Schema$1.Struct({
45378
45514
  id: DeviceId,
45379
45515
  name: Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(MAX_DEVICE_LABEL)),
@@ -45416,7 +45552,10 @@ const make$87 = Effect.fn("DeviceDiscovery.make")(function* () {
45416
45552
  });
45417
45553
  if (body.invalidUtf8 || body.truncated) return yield* new DeviceDiscoveryFailure({ message: "Invalid device discovery response." });
45418
45554
  const result = yield* decodeDeviceDiscovery(input.hostId, body.text);
45419
- const devices = [...result.devices];
45555
+ const adapterDevices = input.ready.xcode ? yield* input.ready.xcode.discover : [];
45556
+ const simulatorIds = new Set(adapterDevices.map((device) => device.id));
45557
+ const devices = [...result.devices.filter((device) => device.platform !== "ios" || device.physical || !simulatorIds.has(device.id)), ...adapterDevices];
45558
+ 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
45559
  if (input.androidAvailable) {
45421
45560
  const avds = yield* input.ready.run("emulator", ["-list-avds"]);
45422
45561
  if (avds.code !== 0) return yield* new DeviceDiscoveryFailure({ message: "Android virtual device discovery failed." });
@@ -45440,7 +45579,7 @@ const make$87 = Effect.fn("DeviceDiscovery.make")(function* () {
45440
45579
  devices,
45441
45580
  partial: result.partial
45442
45581
  };
45443
- }, Effect.scoped, Effect.timeout(Duration.millis(REQUEST_TIMEOUT_MS$5)), Effect.mapError(() => new DeviceOperationError({
45582
+ }, Effect.scoped, Effect.timeout(Duration.millis(REQUEST_TIMEOUT_MS$6)), Effect.mapError(() => new DeviceOperationError({
45444
45583
  operation: "list",
45445
45584
  reason: "request_failed",
45446
45585
  cause: "Device discovery unavailable."
@@ -45744,6 +45883,675 @@ const start = Effect.fn("DeviceHostSettings.start")(function* (input) {
45744
45883
  return { refresh };
45745
45884
  });
45746
45885
  //#endregion
45886
+ //#region src/device/DeviceScreenshot.ts
45887
+ const MAX_SCREENSHOT_BYTES = 8 * 1024 * 1024;
45888
+ var DeviceScreenshotFailure = class extends Data.TaggedError("DeviceScreenshotFailure") {};
45889
+ const MAX_DIMENSION = 16384;
45890
+ const MAX_PIXELS = 64 * 1024 * 1024;
45891
+ const SCREENSHOT_TIMEOUT_MS = 2e4;
45892
+ const PNG_HEADER_BYTES = 33;
45893
+ const PNG_CHUNK_OVERHEAD = 12;
45894
+ const PNG_SIGNATURE = Buffer.from([
45895
+ 137,
45896
+ 80,
45897
+ 78,
45898
+ 71,
45899
+ 13,
45900
+ 10,
45901
+ 26,
45902
+ 10
45903
+ ]);
45904
+ function pngDimensions(png) {
45905
+ const buffer = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
45906
+ 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." });
45907
+ const width = buffer.readUInt32BE(16);
45908
+ const height = buffer.readUInt32BE(20);
45909
+ if (!width || !height || width > MAX_DIMENSION || height > MAX_DIMENSION || width * height > MAX_PIXELS) throw new DeviceScreenshotFailure({ message: "Screenshot dimensions exceed limits." });
45910
+ let offset = PNG_HEADER_BYTES;
45911
+ let imageData = false;
45912
+ let ended = false;
45913
+ while (offset + PNG_CHUNK_OVERHEAD <= buffer.length) {
45914
+ const length = buffer.readUInt32BE(offset);
45915
+ const type = buffer.toString("ascii", offset + 4, offset + 8);
45916
+ const next = offset + PNG_CHUNK_OVERHEAD + length;
45917
+ if (next > buffer.length || type === "IHDR") throw new DeviceScreenshotFailure({ message: "Invalid PNG chunk." });
45918
+ if (type === "IDAT") imageData = true;
45919
+ if (type === "IEND") {
45920
+ ended = length === 0 && next === buffer.length;
45921
+ break;
45922
+ }
45923
+ offset = next;
45924
+ }
45925
+ if (!imageData || !ended) throw new DeviceScreenshotFailure({ message: "Incomplete PNG screenshot." });
45926
+ return {
45927
+ width,
45928
+ height
45929
+ };
45930
+ }
45931
+ const make$82 = Effect.fn("DeviceScreenshot.make")(function* () {
45932
+ const http = yield* HttpClient.HttpClient;
45933
+ return Effect.fn("DeviceScreenshot.capture")(function* (ready, session) {
45934
+ const origin = yield* Effect.try(() => new URL(ready.hub.origin));
45935
+ 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." });
45936
+ const vendor = session.platform === "ios" ? "serve-sim" : "serve-emu";
45937
+ const response = yield* http.execute(HttpClientRequest.post(`${origin.origin}/vendor/${vendor}/api/screenshot?device=${encodeURIComponent(session.deviceId)}`));
45938
+ if (response.status !== 200) return yield* new DeviceScreenshotFailure({ message: "Screenshot rejected." });
45939
+ let bytes = 0;
45940
+ const chunks = [];
45941
+ yield* response.stream.pipe(Stream.runForEach((chunk) => Effect.gen(function* () {
45942
+ bytes += chunk.byteLength;
45943
+ if (bytes > 8388608) return yield* new DeviceScreenshotFailure({ message: "Screenshot too large." });
45944
+ chunks.push(chunk);
45945
+ })));
45946
+ const png = Buffer.concat(chunks, bytes);
45947
+ return {
45948
+ png,
45949
+ ...yield* Effect.try(() => pngDimensions(png))
45950
+ };
45951
+ }, Effect.scoped, Effect.timeout(Duration.millis(SCREENSHOT_TIMEOUT_MS)), Effect.mapError(() => new DeviceOperationError({
45952
+ operation: "screenshot",
45953
+ reason: "request_failed",
45954
+ cause: "Device screenshot could not be captured."
45955
+ })));
45956
+ });
45957
+ var DeviceScreenshot = class extends Context.Service()("@p4code/cli/device/DeviceScreenshot") {};
45958
+ const layer$72 = Layer.effect(DeviceScreenshot, make$82().pipe(Effect.map((capture) => ({ capture }))));
45959
+ //#endregion
45960
+ //#region src/device/XcodeMcpClient.ts
45961
+ const XCODE_BUILD_MCP_VERSION = "2.7.0";
45962
+ const PROTOCOL_VERSION = "2025-06-18";
45963
+ const REQUEST_TIMEOUT_MS$5 = 6e4;
45964
+ const MAX_MESSAGE_BYTES = 12 * 1024 * 1024;
45965
+ const MAX_TOOL_PAGES = 32;
45966
+ function objectValue(value) {
45967
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
45968
+ }
45969
+ /** One process per lease keeps semantic refs and simulator defaults isolated. Never invokes npx. */
45970
+ var XcodeMcpClient = class XcodeMcpClient {
45971
+ child;
45972
+ sequence = 0;
45973
+ buffer = "";
45974
+ closed = false;
45975
+ pending = /* @__PURE__ */ new Map();
45976
+ tools = /* @__PURE__ */ new Set();
45977
+ constructor(child) {
45978
+ this.child = child;
45979
+ child.stdout.setEncoding("utf8");
45980
+ child.stdout.on("data", (chunk) => {
45981
+ this.buffer += chunk;
45982
+ if (Buffer.byteLength(this.buffer) > MAX_MESSAGE_BYTES) {
45983
+ this.close(/* @__PURE__ */ new Error("Device MCP response exceeds the supported size."));
45984
+ return;
45985
+ }
45986
+ let boundary;
45987
+ while ((boundary = this.buffer.indexOf("\n")) >= 0) {
45988
+ const line = this.buffer.slice(0, boundary);
45989
+ this.buffer = this.buffer.slice(boundary + 1);
45990
+ try {
45991
+ const message = objectValue(JSON.parse(line));
45992
+ if (typeof message.id !== "number") continue;
45993
+ const call = this.pending.get(message.id);
45994
+ if (!call) continue;
45995
+ this.pending.delete(message.id);
45996
+ if (message.error) {
45997
+ const detail = objectValue(message.error).message;
45998
+ call.reject(new Error(typeof detail === "string" ? detail : "Device MCP request failed."));
45999
+ } else call.resolve(message.result);
46000
+ } catch {
46001
+ this.close(/* @__PURE__ */ new Error("Device MCP returned invalid stdio JSON."));
46002
+ return;
46003
+ }
46004
+ }
46005
+ });
46006
+ child.stderr.resume();
46007
+ child.on("error", () => this.close(/* @__PURE__ */ new Error("Device MCP executable could not start.")));
46008
+ child.on("exit", () => this.close(/* @__PURE__ */ new Error("Device MCP disconnected. Select the device again.")));
46009
+ child.stdin.on("error", () => this.close(/* @__PURE__ */ new Error("Device MCP input disconnected.")));
46010
+ }
46011
+ static async start(executable, cwd, environment, signal, kind = "legacy") {
46012
+ if (!isAbsolute(executable)) throw new Error("Device MCP requires an absolute executable path.");
46013
+ await access(executable, constants.X_OK).catch(() => {
46014
+ 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.");
46015
+ });
46016
+ signal.throwIfAborted();
46017
+ const client = new XcodeMcpClient(spawn(executable, kind === "apple" ? ["mcpbridge"] : ["mcp"], {
46018
+ cwd,
46019
+ shell: false,
46020
+ stdio: "pipe",
46021
+ env: {
46022
+ ...environment,
46023
+ ...kind === "legacy" ? {
46024
+ XCODEBUILDMCP_CWD: cwd,
46025
+ XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS: "true",
46026
+ XCODEBUILDMCP_ENABLED_WORKFLOWS: "simulator,ui-automation",
46027
+ XCODEBUILDMCP_SENTRY_DISABLED: "true"
46028
+ } : {}
46029
+ }
46030
+ }));
46031
+ try {
46032
+ const initialized = objectValue(await client.request("initialize", {
46033
+ protocolVersion: PROTOCOL_VERSION,
46034
+ capabilities: {},
46035
+ clientInfo: {
46036
+ name: "p4code-device-adapter",
46037
+ version: "1"
46038
+ }
46039
+ }, signal));
46040
+ if (kind === "apple" && objectValue(initialized.serverInfo).name !== "xcode-tools") throw new Error("Selected Xcode did not expose Apple MCP tools.");
46041
+ 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.`);
46042
+ client.child.stdin.write(JSON.stringify({
46043
+ jsonrpc: "2.0",
46044
+ method: "notifications/initialized"
46045
+ }) + "\n");
46046
+ let cursor;
46047
+ const cursors = /* @__PURE__ */ new Set();
46048
+ do {
46049
+ const page = objectValue(await client.request("tools/list", cursor ? { cursor } : {}, signal));
46050
+ if (!Array.isArray(page.tools)) throw new Error("Device MCP did not advertise tools.");
46051
+ for (const tool of page.tools) {
46052
+ const name = objectValue(tool).name;
46053
+ if (typeof name === "string") client.tools.add(name);
46054
+ }
46055
+ cursor = page.nextCursor;
46056
+ if (typeof cursor === "string" && cursor.length > 0) {
46057
+ if (cursors.has(cursor) || cursors.size >= MAX_TOOL_PAGES) throw new Error("Device MCP tool discovery did not finish within its page limit.");
46058
+ cursors.add(cursor);
46059
+ }
46060
+ } while (typeof cursor === "string" && cursor.length > 0);
46061
+ return client;
46062
+ } catch (error) {
46063
+ client.close();
46064
+ throw error;
46065
+ }
46066
+ }
46067
+ request(method, params, signal) {
46068
+ signal.throwIfAborted();
46069
+ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Device MCP session is closed. Select the device again."));
46070
+ const id = ++this.sequence;
46071
+ return new Promise((resolve, reject) => {
46072
+ const abort = () => this.close(/* @__PURE__ */ new Error("Device MCP request cancelled. Select the device again."));
46073
+ const timer = setTimeout(() => this.close(/* @__PURE__ */ new Error("Device MCP timed out. Select the device again.")), REQUEST_TIMEOUT_MS$5);
46074
+ const finish = () => {
46075
+ clearTimeout(timer);
46076
+ signal.removeEventListener("abort", abort);
46077
+ };
46078
+ this.pending.set(id, {
46079
+ resolve: (result) => {
46080
+ finish();
46081
+ resolve(result);
46082
+ },
46083
+ reject: (error) => {
46084
+ finish();
46085
+ reject(error);
46086
+ }
46087
+ });
46088
+ signal.addEventListener("abort", abort, { once: true });
46089
+ this.child.stdin.write(JSON.stringify({
46090
+ jsonrpc: "2.0",
46091
+ id,
46092
+ method,
46093
+ params
46094
+ }) + "\n");
46095
+ });
46096
+ }
46097
+ async call(name, args, signal) {
46098
+ if (!this.tools.has(name)) throw new Error(`Selected device MCP service does not expose ${name}. Check adapter capabilities and Xcode setup.`);
46099
+ const redact = (error) => {
46100
+ let message = error instanceof Error ? error.message : String(error);
46101
+ for (const [key, value] of Object.entries(args)) if (/sessionkey$/i.test(key) && typeof value === "string" && value.length > 0) message = message.replaceAll(value, "[redacted]");
46102
+ return new Error(message);
46103
+ };
46104
+ try {
46105
+ const result = objectValue(await this.request("tools/call", {
46106
+ name,
46107
+ arguments: args
46108
+ }, signal));
46109
+ const structured = objectValue(result.structuredContent);
46110
+ if (result.isError === true || structured.didError === true) {
46111
+ const detail = (Array.isArray(result.content) ? result.content : []).map((part) => objectValue(part).text).filter((part) => typeof part === "string").join("\n");
46112
+ throw new Error(detail || String(structured.error ?? `${name} failed. Refresh device state before retrying.`));
46113
+ }
46114
+ return result;
46115
+ } catch (error) {
46116
+ throw redact(error);
46117
+ }
46118
+ }
46119
+ close(error = /* @__PURE__ */ new Error("Device MCP session closed.")) {
46120
+ if (this.closed) return;
46121
+ this.closed = true;
46122
+ for (const call of this.pending.values()) call.reject(error);
46123
+ this.pending.clear();
46124
+ this.child.kill("SIGTERM");
46125
+ }
46126
+ };
46127
+ //#endregion
46128
+ //#region src/device/XcodeDeviceAdapter.ts
46129
+ const SimulatorList = Schema$1.Struct({ simulators: Schema$1.Array(Schema$1.Struct({
46130
+ simulatorId: Schema$1.String,
46131
+ name: Schema$1.String,
46132
+ runtime: Schema$1.String,
46133
+ state: Schema$1.String,
46134
+ isAvailable: Schema$1.Boolean
46135
+ })) });
46136
+ const decodeSimulators = Schema$1.decodeUnknownSync(SimulatorList);
46137
+ const MAX_LEASES = 32;
46138
+ const Frame = Schema$1.Struct({
46139
+ x: Schema$1.Number,
46140
+ y: Schema$1.Number,
46141
+ width: Schema$1.Number,
46142
+ height: Schema$1.Number
46143
+ });
46144
+ const Snapshot = Schema$1.Struct({ elements: Schema$1.Array(Schema$1.Struct({
46145
+ ref: Schema$1.String,
46146
+ role: Schema$1.optional(Schema$1.String),
46147
+ frame: Frame
46148
+ })) });
46149
+ const readSnapshot = Schema$1.decodeUnknownOption(Snapshot);
46150
+ /** Retain structured semantic snapshots; callers never need screenshot round trips for refs. */
46151
+ function xcodeResultData(result) {
46152
+ const structured = objectValue(result.structuredContent);
46153
+ return objectValue(structured.data ?? structured);
46154
+ }
46155
+ function createXcodeDeviceAdapter(executable, cwd, environment) {
46156
+ const clients = /* @__PURE__ */ new Map();
46157
+ const recordings = /* @__PURE__ */ new Map();
46158
+ const snapshots = /* @__PURE__ */ new Map();
46159
+ const remember = (session, data) => {
46160
+ const snapshot = readSnapshot(data.capture);
46161
+ if (snapshot._tag === "Some") snapshots.set(session.leaseId, snapshot.value);
46162
+ const viewport = snapshots.get(session.leaseId)?.elements.find((element) => element.role === "window")?.frame;
46163
+ return {
46164
+ ...data,
46165
+ ...viewport ? { viewport } : {}
46166
+ };
46167
+ };
46168
+ const get = async (key, signal) => {
46169
+ let pending = clients.get(key);
46170
+ if (!pending) {
46171
+ if (clients.size >= MAX_LEASES) throw new Error("Close an existing Xcode device session before opening another.");
46172
+ pending = XcodeMcpClient.start(executable, cwd, environment, signal);
46173
+ clients.set(key, pending);
46174
+ pending.catch(() => {
46175
+ if (clients.get(key) === pending) clients.delete(key);
46176
+ });
46177
+ }
46178
+ return pending;
46179
+ };
46180
+ const run = (operation, execute) => Effect.tryPromise({
46181
+ try: execute,
46182
+ catch: (cause) => new DeviceOperationError({
46183
+ operation,
46184
+ reason: "request_failed",
46185
+ cause: cause instanceof Error ? cause.message : String(cause)
46186
+ })
46187
+ });
46188
+ const forSession = async (session, signal) => {
46189
+ if (session.hostId !== "local" || session.platform !== "ios" || !session.leaseId) throw new Error("Xcode adapter requires an exact local iOS Simulator lease.");
46190
+ return get(session.leaseId, signal);
46191
+ };
46192
+ const closeSession = (leaseId) => {
46193
+ const client = clients.get(leaseId);
46194
+ clients.delete(leaseId);
46195
+ const recording = recordings.get(leaseId);
46196
+ recordings.delete(leaseId);
46197
+ snapshots.delete(leaseId);
46198
+ client?.then(async (client) => {
46199
+ try {
46200
+ if (recording) await client.call("record_sim_video", {
46201
+ ...recording,
46202
+ stop: true
46203
+ }, new AbortController().signal);
46204
+ } finally {
46205
+ client.close();
46206
+ }
46207
+ }).catch(() => {});
46208
+ };
46209
+ return {
46210
+ discover: run("list", async (signal) => {
46211
+ const client = await get("discovery", signal);
46212
+ return decodeSimulators(xcodeResultData(await client.call("list_sims", {}, signal))).simulators.filter((sim) => sim.isAvailable && /iOS/i.test(sim.runtime)).map((sim) => ({
46213
+ id: sim.simulatorId,
46214
+ hostId: "local",
46215
+ name: sim.name,
46216
+ version: sim.runtime,
46217
+ booted: sim.state === "Booted",
46218
+ physical: false,
46219
+ platform: "ios"
46220
+ }));
46221
+ }),
46222
+ state: (session, sinceScreenHash) => run("state", async (signal) => {
46223
+ const client = await forSession(session, signal);
46224
+ const data = xcodeResultData(await client.call("snapshot_ui", {
46225
+ simulatorId: session.deviceId,
46226
+ ...sinceScreenHash ? { sinceScreenHash } : {}
46227
+ }, signal));
46228
+ return {
46229
+ adapter: "xcodebuildmcp",
46230
+ capabilities: [...client.tools].filter((tool) => [
46231
+ "tap",
46232
+ "type_text",
46233
+ "swipe",
46234
+ "drag",
46235
+ "batch",
46236
+ "snapshot_ui",
46237
+ "screenshot",
46238
+ "record_sim_video"
46239
+ ].includes(tool)),
46240
+ ...remember(session, data)
46241
+ };
46242
+ }),
46243
+ action: (session, input) => run("command", async (signal) => {
46244
+ const client = await forSession(session, signal);
46245
+ const { action, ...parameters } = input;
46246
+ const snapshot = snapshots.get(session.leaseId);
46247
+ const ref = input.action === "batch" ? input.steps.at(-1)?.elementRef : input.action === "swipe" ? input.withinElementRef : input.elementRef;
46248
+ const bounds = snapshot?.elements.find((element) => element.ref === ref)?.frame;
46249
+ const viewport = snapshot?.elements.find((element) => element.role === "window")?.frame;
46250
+ const data = xcodeResultData(await client.call(action, {
46251
+ ...parameters,
46252
+ simulatorId: session.deviceId
46253
+ }, signal));
46254
+ return {
46255
+ ...remember(session, data),
46256
+ ...bounds && viewport && viewport.width > 0 && viewport.height > 0 ? { pointer: {
46257
+ x: (bounds.x + bounds.width / 2 - viewport.x) / viewport.width,
46258
+ y: (bounds.y + bounds.height / 2 - viewport.y) / viewport.height
46259
+ } } : {}
46260
+ };
46261
+ }),
46262
+ screenshot: (session) => run("screenshot", async (signal) => {
46263
+ const result = await (await forSession(session, signal)).call("screenshot", {
46264
+ simulatorId: session.deviceId,
46265
+ returnFormat: "base64"
46266
+ }, signal);
46267
+ const image = (Array.isArray(result.content) ? result.content : []).map(objectValue).find((part) => part.type === "image" && part.mimeType === "image/png");
46268
+ if (typeof image?.data !== "string") throw new Error("XcodeBuildMCP did not return a PNG screenshot.");
46269
+ const png = Buffer.from(image.data, "base64");
46270
+ return {
46271
+ png,
46272
+ ...pngDimensions(png)
46273
+ };
46274
+ }),
46275
+ recording: (session, action) => run("recording", async (signal) => {
46276
+ const client = await forSession(session, signal);
46277
+ const key = session.leaseId;
46278
+ if (action === "start" && recordings.has(key)) throw new Error("This device session is already recording.");
46279
+ if (action === "stop" && !recordings.has(key)) throw new Error("This device session has no active recording.");
46280
+ const outputFile = recordings.get(key)?.outputFile ?? `${cwd}/device-recording-${randomUUID()}.mp4`;
46281
+ const result = xcodeResultData(await client.call("record_sim_video", {
46282
+ simulatorId: session.deviceId,
46283
+ ...action === "start" ? { start: true } : {
46284
+ stop: true,
46285
+ outputFile
46286
+ }
46287
+ }, signal));
46288
+ if (action === "start") recordings.set(key, {
46289
+ outputFile,
46290
+ simulatorId: session.deviceId
46291
+ });
46292
+ else recordings.delete(key);
46293
+ return result;
46294
+ }),
46295
+ closeSession,
46296
+ stop: () => {
46297
+ for (const key of clients.keys()) closeSession(key);
46298
+ }
46299
+ };
46300
+ }
46301
+ //#endregion
46302
+ //#region src/device/AppleDeviceAdapter.ts
46303
+ const MAX_HIERARCHY_BYTES = 512 * 1024;
46304
+ const MAX_SESSIONS = 32;
46305
+ const REQUIRED_DEVICE_TOOLS = [
46306
+ "DeviceInteractionStartSession",
46307
+ "DeviceInteractionSynthesize",
46308
+ "DeviceInteractionEndSession"
46309
+ ];
46310
+ const FRAME_PATTERN = /\{\{(-?[\d.]+),\s*(-?[\d.]+)\},\s*\{([\d.]+),\s*([\d.]+)\}\}/;
46311
+ const HIT_PATTERN = /hitPoint:\s*\{(-?[\d.]+),\s*(-?[\d.]+)\}/;
46312
+ const Capture = Schema$1.Struct({
46313
+ hierarchyPath: Schema$1.String,
46314
+ screenshotPath: Schema$1.String
46315
+ });
46316
+ const Session = Schema$1.Struct({
46317
+ interactionSessionKey: Schema$1.String,
46318
+ deviceUUID: Schema$1.String,
46319
+ deviceIsSimulator: Schema$1.Boolean
46320
+ });
46321
+ const Simulators = Schema$1.Struct({ devices: Schema$1.Record(Schema$1.String, Schema$1.Array(Schema$1.Struct({
46322
+ udid: Schema$1.String,
46323
+ name: Schema$1.String,
46324
+ state: Schema$1.String,
46325
+ isAvailable: Schema$1.Boolean
46326
+ }))) });
46327
+ function appleMcpData(result) {
46328
+ if (result.structuredContent) return objectValue(result.structuredContent);
46329
+ for (const block of Array.isArray(result.content) ? result.content : []) {
46330
+ const text = objectValue(block).text;
46331
+ if (typeof text !== "string") continue;
46332
+ try {
46333
+ return objectValue(JSON.parse(text));
46334
+ } catch {}
46335
+ }
46336
+ throw new Error("Apple MCP returned no structured device result.");
46337
+ }
46338
+ /** Use Apple's documented hierarchy hit points, never guessed screenshot coordinates. */
46339
+ function appleHierarchySnapshot(text, sequence) {
46340
+ const elements = text.split("\n").flatMap((line, index) => {
46341
+ const frame = line.match(FRAME_PATTERN);
46342
+ const hit = line.match(HIT_PATTERN);
46343
+ if (!frame || !hit || line.includes("isRemoteLeafPlaceholder")) return [];
46344
+ const [x, y, width, height] = frame.slice(1).map(Number);
46345
+ const [hx, hy] = hit.slice(1).map(Number);
46346
+ if (![
46347
+ x,
46348
+ y,
46349
+ width,
46350
+ height,
46351
+ hx,
46352
+ hy
46353
+ ].every((value) => value !== void 0 && Number.isFinite(value)) || width <= 0 || height <= 0) return [];
46354
+ return [{
46355
+ ref: `a${sequence}-${index}`,
46356
+ label: line.trim(),
46357
+ frame: {
46358
+ x,
46359
+ y,
46360
+ width,
46361
+ height
46362
+ },
46363
+ hitPoint: {
46364
+ x: hx,
46365
+ y: hy
46366
+ },
46367
+ role: /\b(?:UI)?Window\b/.test(line) ? "window" : "element",
46368
+ actions: ["tap"]
46369
+ }];
46370
+ });
46371
+ return {
46372
+ screenHash: createHash("sha256").update(text).digest("hex"),
46373
+ seq: sequence,
46374
+ elements
46375
+ };
46376
+ }
46377
+ function createAppleDeviceAdapter(input) {
46378
+ const sessions = /* @__PURE__ */ new Map();
46379
+ const fail = (operation) => (cause) => new DeviceOperationError({
46380
+ operation,
46381
+ reason: "request_failed",
46382
+ cause: cause instanceof Error ? cause.message : String(cause)
46383
+ });
46384
+ const call = (client, name, args) => Effect.tryPromise({
46385
+ try: (signal) => client.call(name, args, signal),
46386
+ catch: fail(name)
46387
+ });
46388
+ const closeSession = (leaseId) => {
46389
+ const pending = sessions.get(leaseId);
46390
+ sessions.delete(leaseId);
46391
+ pending?.then(async (owned) => {
46392
+ try {
46393
+ await owned.client.call("DeviceInteractionEndSession", { interactionSessionKey: owned.key }, new AbortController().signal);
46394
+ } finally {
46395
+ owned.client.close();
46396
+ }
46397
+ }).catch(() => {});
46398
+ };
46399
+ const get = (session) => Effect.tryPromise({
46400
+ try: async (signal) => {
46401
+ if (session.hostId !== "local" || session.platform !== "ios" || !session.leaseId) throw new Error("Apple MCP requires an exact local iOS device lease.");
46402
+ let pending = sessions.get(session.leaseId);
46403
+ if (!pending) {
46404
+ if (sessions.size >= MAX_SESSIONS) throw new Error("Close an existing device session first.");
46405
+ pending = (async () => {
46406
+ const client = await XcodeMcpClient.start("/usr/bin/xcrun", input.cwd, {
46407
+ ...input.environment,
46408
+ MCP_XCODE_SESSION_ID: randomUUID()
46409
+ }, signal, "apple");
46410
+ try {
46411
+ const missing = REQUIRED_DEVICE_TOOLS.filter((tool) => !client.tools.has(tool));
46412
+ if (missing.length > 0) throw new Error(`Selected Xcode lacks required device tools: ${missing.join(", ")}. No device session was opened.`);
46413
+ if (input.workspacePath) await client.call("XcodeOpenWorkspace", { path: input.workspacePath }, signal);
46414
+ const opened = Schema$1.decodeUnknownSync(Session)(appleMcpData(await client.call("DeviceInteractionStartSession", {
46415
+ deviceIdentifier: session.deviceId,
46416
+ sessionIdentifier: `P4 Device ${session.threadId.slice(0, 8)}`
46417
+ }, signal)));
46418
+ if (opened.deviceUUID.toLowerCase() !== session.deviceId.toLowerCase() || !opened.deviceIsSimulator) {
46419
+ await client.call("DeviceInteractionEndSession", { interactionSessionKey: opened.interactionSessionKey }, signal);
46420
+ throw new Error("Apple selected a different device. No interaction was performed.");
46421
+ }
46422
+ return {
46423
+ client,
46424
+ key: opened.interactionSessionKey,
46425
+ sequence: 0
46426
+ };
46427
+ } catch (error) {
46428
+ client.close();
46429
+ throw error;
46430
+ }
46431
+ })();
46432
+ sessions.set(session.leaseId, pending);
46433
+ const leaseId = session.leaseId;
46434
+ pending.catch(() => {
46435
+ if (sessions.get(leaseId) === pending) sessions.delete(leaseId);
46436
+ });
46437
+ }
46438
+ return pending;
46439
+ },
46440
+ catch: fail("open")
46441
+ });
46442
+ const capture = (owned, interactionCommand) => Effect.gen(function* () {
46443
+ const response = yield* call(owned.client, "DeviceInteractionSynthesize", {
46444
+ interactSessionKey: owned.key,
46445
+ ...interactionCommand ? { interactionCommand } : {}
46446
+ });
46447
+ const data = yield* Effect.try({
46448
+ try: () => appleMcpData(response),
46449
+ catch: fail("state")
46450
+ });
46451
+ const result = yield* Schema$1.decodeUnknownEffect(Capture)(data);
46452
+ if ((yield* input.fs.stat(result.hierarchyPath)).size > BigInt(MAX_HIERARCHY_BYTES)) return yield* fail("state")("Apple hierarchy exceeds the supported size.");
46453
+ const snapshot = appleHierarchySnapshot(yield* input.fs.readFileString(result.hierarchyPath), owned.sequence + 1);
46454
+ if (owned.snapshot?.screenHash !== snapshot.screenHash) {
46455
+ owned.snapshot = snapshot;
46456
+ owned.sequence += 1;
46457
+ }
46458
+ const currentSnapshot = owned.snapshot;
46459
+ owned.image = result.screenshotPath;
46460
+ const viewport = currentSnapshot.elements.find((element) => element.role === "window")?.frame;
46461
+ return {
46462
+ adapter: "apple-xcode",
46463
+ capabilities: [
46464
+ "snapshot_ui",
46465
+ "tap",
46466
+ "screenshot"
46467
+ ],
46468
+ capture: currentSnapshot,
46469
+ ...viewport ? { viewport } : {}
46470
+ };
46471
+ }).pipe(Effect.mapError(fail("state")));
46472
+ return {
46473
+ diagnose: Effect.gen(function* () {
46474
+ const result = yield* input.run("xcrun", [
46475
+ "mcp-server",
46476
+ "status",
46477
+ "--format",
46478
+ "json"
46479
+ ]);
46480
+ if (result.code !== 0) return "Apple MCP did not respond. Check the selected Xcode installation and MCP service.";
46481
+ const status = objectValue(yield* Schema$1.decodeUnknownEffect(Schema$1.UnknownFromJsonString)(result.stdout));
46482
+ const permission = objectValue(status.permission);
46483
+ 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.";
46484
+ 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.";
46485
+ return "Apple MCP did not respond in time. Check Xcode agent/folder permissions and session logs before selecting the device again.";
46486
+ }).pipe(Effect.orElseSucceed(() => "Apple MCP did not respond. Check Xcode agent/folder permissions and service status.")),
46487
+ discover: Effect.gen(function* () {
46488
+ const result = yield* input.run("xcrun", [
46489
+ "simctl",
46490
+ "list",
46491
+ "devices",
46492
+ "available",
46493
+ "--json"
46494
+ ]);
46495
+ if (result.code !== 0) return yield* fail("list")("Selected Xcode cannot list simulators.");
46496
+ const parsed = yield* Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(Simulators))(result.stdout).pipe(Effect.mapError(fail("list")));
46497
+ return Object.entries(parsed.devices).filter(([runtime]) => runtime.includes(".iOS-")).flatMap(([runtime, devices]) => devices.filter((device) => device.isAvailable).map((device) => ({
46498
+ hostId: "local",
46499
+ id: device.udid,
46500
+ platform: "ios",
46501
+ name: device.name,
46502
+ version: runtime.replace("com.apple.CoreSimulator.SimRuntime.", "").replaceAll("-", "."),
46503
+ physical: false,
46504
+ booted: device.state === "Booted"
46505
+ })));
46506
+ }),
46507
+ state: (session, sinceScreenHash) => Effect.gen(function* () {
46508
+ const result = yield* capture(yield* get(session));
46509
+ return sinceScreenHash === result.capture.screenHash ? {
46510
+ ...result,
46511
+ capture: {
46512
+ screenHash: result.capture.screenHash,
46513
+ seq: result.capture.seq,
46514
+ unchanged: true
46515
+ }
46516
+ } : result;
46517
+ }),
46518
+ action: (session, action) => Effect.gen(function* () {
46519
+ if (action.action !== "tap") return yield* fail("command")("This Apple MCP integration currently advertises tap only. Use only capabilities returned by getAXState().");
46520
+ const owned = yield* get(session);
46521
+ const previous = owned.snapshot;
46522
+ const element = previous?.elements.find((element) => element.ref === action.elementRef);
46523
+ if (!element) return yield* fail("command")("Stale device ref. Read getAXState() and choose a current element.");
46524
+ const fresh = yield* capture(owned);
46525
+ if (fresh.capture.screenHash !== previous.screenHash) return yield* fail("command")("Device layout changed. Read getAXState() and choose a current element; no action was performed.");
46526
+ return {
46527
+ ...yield* capture(owned, `t ${element.hitPoint.x} ${element.hitPoint.y}`),
46528
+ ...fresh.viewport ? { pointer: {
46529
+ x: (element.hitPoint.x - fresh.viewport.x) / fresh.viewport.width,
46530
+ y: (element.hitPoint.y - fresh.viewport.y) / fresh.viewport.height
46531
+ } } : {}
46532
+ };
46533
+ }),
46534
+ screenshot: (session) => Effect.gen(function* () {
46535
+ const owned = yield* get(session);
46536
+ yield* capture(owned);
46537
+ if ((yield* input.fs.stat(owned.image)).size > BigInt(8388608)) return yield* fail("screenshot")("Apple screenshot exceeds the supported size.");
46538
+ const png = Buffer.from(yield* input.fs.readFile(owned.image));
46539
+ return {
46540
+ png,
46541
+ ...yield* Effect.try({
46542
+ try: () => pngDimensions(png),
46543
+ catch: fail("screenshot")
46544
+ })
46545
+ };
46546
+ }).pipe(Effect.mapError(fail("screenshot"))),
46547
+ recording: (_session, _action) => Effect.fail(fail("recording")("Apple MCP does not advertise a recording tool. Recording is unavailable through this adapter.")),
46548
+ closeSession,
46549
+ stop: () => {
46550
+ for (const key of sessions.keys()) closeSession(key);
46551
+ }
46552
+ };
46553
+ }
46554
+ //#endregion
45747
46555
  //#region src/device/DeviceToolchain.ts
45748
46556
  /**
45749
46557
  * Pinned installs of the two external tools device support is built on.
@@ -45959,7 +46767,8 @@ function deviceHubPlatform(platforms) {
45959
46767
  const COMMAND_TIMEOUT_MS$1 = 2e4;
45960
46768
  const COMMAND_OUTPUT_LIMIT = 1024 * 1024;
45961
46769
  const COMMAND_FAILURE_CODE = 127;
45962
- const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46770
+ const APPLE_DEVICE_MCP_MINIMUM_XCODE = 27;
46771
+ const make$81 = Effect.fn("LocalDeviceHost.make")(function* () {
45963
46772
  const config = yield* ServerConfig$1;
45964
46773
  const fs = yield* FileSystem.FileSystem;
45965
46774
  const path = yield* Path$1.Path;
@@ -46005,7 +46814,19 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46005
46814
  stderr: "Device command failed.",
46006
46815
  code: COMMAND_FAILURE_CODE
46007
46816
  })));
46817
+ const appleVersion = (platform === "darwin" ? yield* run("xcrun", ["--find", "mcpbridge"]) : null)?.code === 0 ? yield* run("xcodebuild", ["-version"]) : null;
46818
+ const appleAvailable = appleVersion?.code === 0 && Number(appleVersion.stdout.match(/^Xcode (\d+)\b/m)?.[1]) >= APPLE_DEVICE_MCP_MINIMUM_XCODE;
46819
+ const legacyPath = environment.P4CODE_XCODEBUILDMCP_PATH?.trim();
46820
+ const useLegacy = environment.P4CODE_DEVICE_ADAPTER === "xcodebuildmcp";
46821
+ const xcode = appleAvailable && !useLegacy ? createAppleDeviceAdapter({
46822
+ cwd: config.stateDir,
46823
+ environment: hostEnvironment,
46824
+ fs,
46825
+ run,
46826
+ ...environment.P4CODE_APPLE_MCP_WORKSPACE ? { workspacePath: environment.P4CODE_APPLE_MCP_WORKSPACE } : {}
46827
+ }) : platform === "darwin" && legacyPath ? createXcodeDeviceAdapter(legacyPath, config.stateDir, hostEnvironment) : void 0;
46008
46828
  const stopAgent = Effect.suspend(() => {
46829
+ xcode?.stop();
46009
46830
  const owned = agent;
46010
46831
  agent = null;
46011
46832
  return owned?.stop ?? Effect.void;
@@ -46044,7 +46865,8 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46044
46865
  nodePath: process.execPath,
46045
46866
  hub: { origin: started.origin },
46046
46867
  helpers,
46047
- run
46868
+ run,
46869
+ ...xcode ? { xcode } : {}
46048
46870
  };
46049
46871
  return ready;
46050
46872
  }, Effect.onExit((exit) => Exit.isFailure(exit) ? stop : Effect.void));
@@ -46055,6 +46877,10 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46055
46877
  id: hostId,
46056
46878
  kind: "local",
46057
46879
  label: "This machine",
46880
+ ...xcode ? { automation: {
46881
+ provider: appleAvailable && !useLegacy ? "apple-xcode" : "xcodebuildmcp",
46882
+ 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."
46883
+ } } : {},
46058
46884
  platforms: yield* Effect.all([platformAvailability("ios"), platformAvailability("android")]),
46059
46885
  hubInstalled: yield* isDeviceHubInstalled(config.baseDir).pipe(Effect.provideContext(context)),
46060
46886
  agentDeviceInstalled: yield* isAgentDeviceInstalled(config.baseDir).pipe(Effect.provideContext(context))
@@ -46066,8 +46892,9 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46066
46892
  return ready;
46067
46893
  })),
46068
46894
  ensureReady: (onPhase) => lock.withPermit(ensureHub(onPhase)),
46069
- ensureAgentReady: (onPhase) => lock.withPermit(Effect.gen(function* () {
46895
+ ensureAgentReady: (onPhase, devicePlatform) => lock.withPermit(Effect.gen(function* () {
46070
46896
  const host = yield* ensureHub(onPhase);
46897
+ if (xcode && devicePlatform === "ios") return host;
46071
46898
  if (agent && (yield* agent.isRunning)) return {
46072
46899
  ...host,
46073
46900
  agentDevice: agent.endpoint
@@ -46094,7 +46921,7 @@ const make$82 = Effect.fn("LocalDeviceHost.make")(function* () {
46094
46921
  stop: lock.withPermit(stop)
46095
46922
  };
46096
46923
  });
46097
- Layer.effect(DeviceHost, make$82()).pipe(Layer.provide(layer$73));
46924
+ Layer.effect(DeviceHost, make$81()).pipe(Layer.provide(layer$73));
46098
46925
  Data.TaggedError("SshHostDiscoveryError");
46099
46926
  var SshInvalidTargetError = class extends Data.TaggedError("SshInvalidTargetError") {};
46100
46927
  var SshCommandError = class extends Data.TaggedError("SshCommandError") {};
@@ -46953,7 +47780,7 @@ const probeSshDeviceHost = Effect.fn("SshDeviceDiagnostics.probe")(function* (co
46953
47780
  //#endregion
46954
47781
  //#region src/device/SshDeviceHost.ts
46955
47782
  const DIAGNOSTICS_CACHE_MS = 1e4;
46956
- const make$81 = Effect.fn("SshDeviceHost.make")(function* (input) {
47783
+ const make$80 = Effect.fn("SshDeviceHost.make")(function* (input) {
46957
47784
  const config = yield* Schema$1.decodeUnknownEffect(SshDeviceHostConfig)({ ...input }).pipe(Effect.mapError(() => new DeviceHostError({
46958
47785
  hostId: input.id,
46959
47786
  step: "configuring SSH device host",
@@ -47095,6 +47922,14 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47095
47922
  const scope = yield* Scope.Scope;
47096
47923
  const registry = yield* make$83(input);
47097
47924
  const commands = yield* make$85();
47925
+ const adapterLeases = /* @__PURE__ */ new Map();
47926
+ const closeCommands = (sessions) => Effect.sync(() => {
47927
+ for (const session of sessions) {
47928
+ if (!session.leaseId) continue;
47929
+ adapterLeases.get(session.leaseId)?.();
47930
+ adapterLeases.delete(session.leaseId);
47931
+ }
47932
+ }).pipe(Effect.andThen(commands.close(sessions)));
47098
47933
  const threads = yield* make$84();
47099
47934
  const changes = yield* Effect.acquireRelease(PubSub.sliding(STATE_BUFFER_SIZE), PubSub.shutdown);
47100
47935
  const stateLock = yield* Semaphore.make(1);
@@ -47132,7 +47967,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47132
47967
  }));
47133
47968
  }
47134
47969
  const retireSessions = (ids) => Effect.gen(function* () {
47135
- yield* commands.close(state.sessions.filter((session) => ids.includes(session.hostId)));
47970
+ yield* closeCommands(state.sessions.filter((session) => ids.includes(session.hostId)));
47136
47971
  yield* update((current) => {
47137
47972
  for (const id of ids) hostLocks.delete(id);
47138
47973
  return {
@@ -47343,7 +48178,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47343
48178
  return yield* withHost(value.hostId, (lease) => Effect.gen(function* () {
47344
48179
  let found = yield* freshDevice(lease, value);
47345
48180
  if (access === "agent") {
47346
- yield* lease.host.ensureAgentReady(() => Effect.void);
48181
+ yield* lease.host.ensureAgentReady(() => Effect.void, value.platform);
47347
48182
  yield* registry.commit(lease, () => void 0);
47348
48183
  }
47349
48184
  if (!found.device.booted && value.boot !== false) {
@@ -47382,7 +48217,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47382
48217
  openedAt,
47383
48218
  leaseId: NodeCrypto.randomUUID()
47384
48219
  };
47385
- yield* commands.close(sessionsToClose(state.sessions, session));
48220
+ yield* closeCommands(sessionsToClose(state.sessions, session));
47386
48221
  yield* update((current) => ({
47387
48222
  ...current,
47388
48223
  sessions: [...current.sessions.filter((item) => !(item.threadId === session.threadId && item.hostId === session.hostId && item.deviceId === session.deviceId)), session]
@@ -47393,7 +48228,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47393
48228
  const shutdownWithin = Effect.fn("DeviceService.shutdownWithin")(function* (lease, value) {
47394
48229
  const { ready, device } = yield* freshDevice(lease, value);
47395
48230
  yield* input.actions.shutdown(ready, device);
47396
- yield* commands.close(state.sessions.filter((session) => session.hostId === value.hostId && session.deviceId === value.deviceId));
48231
+ yield* closeCommands(state.sessions.filter((session) => session.hostId === value.hostId && session.deviceId === value.deviceId));
47397
48232
  yield* update((current) => ({
47398
48233
  ...current,
47399
48234
  devices: current.devices.map((item) => item.hostId === value.hostId && item.id === value.deviceId ? {
@@ -47417,7 +48252,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47417
48252
  platform: session.platform
47418
48253
  });
47419
48254
  else {
47420
- yield* commands.close(sessionsToClose(state.sessions, value));
48255
+ yield* closeCommands(sessionsToClose(state.sessions, value));
47421
48256
  yield* update((current) => {
47422
48257
  const closing = sessionsToClose(current.sessions, value);
47423
48258
  return {
@@ -47460,11 +48295,16 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47460
48295
  });
47461
48296
  const validate = registry.isCurrent(lease).pipe(Effect.map((current) => current && state.sessions.includes(session)));
47462
48297
  return yield* commands.run(session, validate, Effect.gen(function* () {
47463
- const ready = yield* lease.host.ensureAgentReady(() => Effect.void);
48298
+ const ready = yield* lease.host.ensureAgentReady(() => Effect.void, session.platform);
47464
48299
  if (!(yield* validate)) return yield* new DeviceHostUnavailableError({
47465
48300
  hostId: target.hostId,
47466
48301
  reason: "Device session or agent authorization changed."
47467
48302
  });
48303
+ if (ready.xcode && session.leaseId) {
48304
+ const adapter = ready.xcode;
48305
+ const leaseId = session.leaseId;
48306
+ adapterLeases.set(leaseId, () => adapter.closeSession(leaseId));
48307
+ }
47468
48308
  return yield* execute(ready, session);
47469
48309
  }));
47470
48310
  })).pipe(Effect.mapError((error) => isDeviceHostError(error) || isDeviceHostTimeoutError(error) ? new DeviceHostUnavailableError({
@@ -47478,7 +48318,7 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47478
48318
  agentList: listForAccess("agent"),
47479
48319
  configure,
47480
48320
  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) => ({
48321
+ retireThread: (threadId) => threads.retire(threadId).pipe(Effect.ensuring(Effect.suspend(() => closeCommands(state.sessions.filter((session) => session.threadId === threadId))).pipe(Effect.ensuring(update((current) => ({
47482
48322
  ...current,
47483
48323
  sessions: current.sessions.filter((session) => session.threadId !== threadId),
47484
48324
  bootingDevices: (current.bootingDevices ?? []).filter((device) => device.threadId !== threadId)
@@ -47486,7 +48326,10 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47486
48326
  restoreThread: threads.restore,
47487
48327
  withAgentDevice: (target, execute) => threads.run(target.threadId, withAgentDevice(target, execute)),
47488
48328
  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))),
48329
+ withAgentCommand: (target, execute) => threads.run(target.threadId, withAgentDevice(target, (ready, session) => ready.agentDevice ? execute(ready.agentDevice, session) : Effect.fail(new DeviceHostUnavailableError({
48330
+ hostId: target.hostId,
48331
+ reason: "This host uses semantic device control. Use cua.getDevice() and element refs."
48332
+ })))),
47490
48333
  open: (value) => threads.run(value.threadId, openWithAccess(value, "manual")),
47491
48334
  agentOpen: (value) => threads.run(value.threadId, openWithAccess(value, "agent")),
47492
48335
  close: (value) => closeWithAccess(value, "manual"),
@@ -47498,17 +48341,17 @@ const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* (input)
47498
48341
  }))
47499
48342
  };
47500
48343
  });
47501
- const make$80 = Effect.gen(function* () {
48344
+ const make$79 = Effect.gen(function* () {
47502
48345
  const discover = yield* make$87();
47503
48346
  const actions = yield* make$86();
47504
48347
  return yield* makeWithHosts({
47505
- local: make$82(),
47506
- ssh: make$81,
48348
+ local: make$81(),
48349
+ ssh: make$80,
47507
48350
  discover,
47508
48351
  actions
47509
48352
  });
47510
48353
  }).pipe(Effect.provide(layer$73));
47511
- const layer$71 = Layer.effect(DeviceService, make$80);
48354
+ const layer$70 = Layer.effect(DeviceService, make$79);
47512
48355
  //#endregion
47513
48356
  //#region src/serverRuntimeStartup.ts
47514
48357
  var ServerRuntimeStartupError = class extends Schema$1.TaggedErrorClass()("ServerRuntimeStartupError", {
@@ -47653,7 +48496,7 @@ const startProviderRecovery = Effect.fn("startup.startProviderRecovery")(functio
47653
48496
  yield* input.recover;
47654
48497
  yield* input.startReaper;
47655
48498
  });
47656
- const make$79 = Effect.gen(function* () {
48499
+ const make$78 = Effect.gen(function* () {
47657
48500
  const serverConfig = yield* ServerConfig$1;
47658
48501
  const keybindings = yield* Keybindings;
47659
48502
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -47800,7 +48643,7 @@ const make$79 = Effect.gen(function* () {
47800
48643
  enqueueCommand: commandGate.enqueueCommand
47801
48644
  };
47802
48645
  });
47803
- const layer$70 = Layer.effect(ServerRuntimeStartup, make$79);
48646
+ const layer$69 = Layer.effect(ServerRuntimeStartup, make$78);
47804
48647
  //#endregion
47805
48648
  //#region src/serverRuntimeState.ts
47806
48649
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -47957,7 +48800,7 @@ function expandHomePath$2(input, path) {
47957
48800
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
47958
48801
  return input;
47959
48802
  }
47960
- const make$78 = Effect.gen(function* () {
48803
+ const make$77 = Effect.gen(function* () {
47961
48804
  const fileSystem = yield* FileSystem.FileSystem;
47962
48805
  const path = yield* Path$1.Path;
47963
48806
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -48014,7 +48857,7 @@ const make$78 = Effect.gen(function* () {
48014
48857
  resolveRelativePathWithinRoot
48015
48858
  });
48016
48859
  });
48017
- const layer$69 = Layer.effect(WorkspacePaths, make$78);
48860
+ const layer$68 = Layer.effect(WorkspacePaths, make$77);
48018
48861
  //#endregion
48019
48862
  //#region src/cli/project.ts
48020
48863
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -48120,7 +48963,7 @@ const projectCommandUuid = Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.
48120
48963
  operation: "generateProjectCommandId",
48121
48964
  cause
48122
48965
  })));
48123
- const ProjectCliRuntimeLive = Layer.mergeAll(layer$69, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$81), Layer.provideMerge(layerConfig)));
48966
+ const ProjectCliRuntimeLive = Layer.mergeAll(layer$68, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$81), Layer.provideMerge(layerConfig)));
48124
48967
  const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
48125
48968
  const withProjectCliSessionToken = (environmentAuth, run) => Effect.acquireUseRelease(environmentAuth.issueSession({
48126
48969
  scopes: AuthAdministrativeScopes,
@@ -48222,7 +49065,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
48222
49065
  });
48223
49066
  yield* Console.log(output);
48224
49067
  }).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)))));
49068
+ }).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
49069
  });
48227
49070
  const projectAddCommand = Command.make("add", {
48228
49071
  ...projectLocationFlags,
@@ -51938,7 +52781,7 @@ const makeTerminationError$1 = (handle) => Effect.match(handle.exitCode, {
51938
52781
  //#region ../../packages/effect-acp/src/client.ts
51939
52782
  const MAX_STDERR_CALLBACK_CHARS = 32768;
51940
52783
  var AcpClient = class extends Context.Service()("effect-acp/client/AcpClient") {};
51941
- const make$77 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
52784
+ const make$76 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
51942
52785
  const coreHandlers = {};
51943
52786
  const notificationHandlers = {
51944
52787
  sessionUpdate: {
@@ -52105,7 +52948,7 @@ const layerChildProcess$1 = (handle, options = {}) => {
52105
52948
  })), Stream.drain)) : stdout
52106
52949
  };
52107
52950
  const terminationError = makeTerminationError$1(handle);
52108
- return Layer.effect(AcpClient, make$77(stdio, options, terminationError));
52951
+ return Layer.effect(AcpClient, make$76(stdio, options, terminationError));
52109
52952
  };
52110
52953
  //#endregion
52111
52954
  //#region src/provider/Drivers/AntigravitySkills.ts
@@ -53384,7 +54227,7 @@ const Reply = Schema$1.Struct({
53384
54227
  id: Schema$1.String,
53385
54228
  result: Schema$1.Struct({ ok: Schema$1.Boolean })
53386
54229
  });
53387
- const make$76 = Effect.fn("DeviceCommandProxy.make")(function* () {
54230
+ const make$75 = Effect.fn("DeviceCommandProxy.make")(function* () {
53388
54231
  const http = yield* HttpClient.HttpClient;
53389
54232
  const daemonSessions = /* @__PURE__ */ new WeakMap();
53390
54233
  return Effect.fn("DeviceCommandProxy.execute")(function* (endpoint, session, payload) {
@@ -53437,81 +54280,7 @@ const make$76 = Effect.fn("DeviceCommandProxy.make")(function* () {
53437
54280
  })));
53438
54281
  });
53439
54282
  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 }))));
54283
+ const layer$67 = Layer.effect(DeviceCommandProxy, make$75().pipe(Effect.map((execute) => ({ execute }))));
53515
54284
  //#endregion
53516
54285
  //#region src/device/DeviceHubRoute.ts
53517
54286
  const DEVICE_HUB_PROXY_PREFIX = "/api/device-hub";
@@ -53715,12 +54484,12 @@ const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* (fusion
53715
54484
  const isDeviceToolUnavailableError = Schema$1.is(DeviceToolUnavailableError);
53716
54485
  const isDeviceActionUnavailableError = Schema$1.is(DeviceActionUnavailableError);
53717
54486
  /** Internal bridge; tool payloads never choose thread identity or daemon credentials. */
53718
- const withDeviceInvocation = (execute) => Effect.gen(function* () {
54487
+ const withDeviceInvocation = (execute, capability = "devices") => Effect.gen(function* () {
53719
54488
  const invocation = yield* McpInvocationContext;
53720
54489
  const service = yield* DeviceService;
53721
54490
  const lifetime = invocation.authorizationLifetime;
53722
54491
  const denied = () => new DeviceToolUnavailableError({ reason: "Device command unavailable or authorization changed." });
53723
- if (!invocation.capabilities.has("devices") || !lifetime?.revocation || lifetime.revoked) return yield* denied();
54492
+ if (!invocation.capabilities.has(capability) || !lifetime?.revocation || lifetime.revoked) return yield* denied();
53724
54493
  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
54494
  return yield* Effect.raceFirst(work, Deferred.await(lifetime.revocation).pipe(Effect.andThen(Effect.fail(denied()))));
53726
54495
  });
@@ -53735,6 +54504,7 @@ const MAX_AX_BYTES = 512 * 1024;
53735
54504
  const AX_TIMEOUT_MS = 1e4;
53736
54505
  const MAX_AX_EXTENT = 32768;
53737
54506
  const MIN_AX_EXTENT = 2;
54507
+ const isToolUnavailable = Schema$1.is(DeviceToolUnavailableError);
53738
54508
  const AxFrames = Schema$1.Array(Schema$1.Struct({ frame: Schema$1.Struct({
53739
54509
  x: Schema$1.Number,
53740
54510
  y: Schema$1.Number,
@@ -53774,6 +54544,7 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53774
54544
  const state = yield* service.agentList;
53775
54545
  const device = state.devices.find((device) => device.hostId === input.hostId && device.id === input.deviceId && device.platform === input.platform);
53776
54546
  if (!device) return yield* stale();
54547
+ 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
54548
  const session = yield* service.agentOpen({
53778
54549
  threadId,
53779
54550
  hostId: input.hostId,
@@ -53811,8 +54582,16 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53811
54582
  deviceId: target.deviceId
53812
54583
  }, (ready, session) => Effect.gen(function* () {
53813
54584
  if (session.leaseId !== input.leaseId) return yield* stale();
54585
+ if (input.method === "recording") {
54586
+ if (!ready.xcode || session.platform !== "ios") return yield* new DeviceToolUnavailableError({ reason: "Recording is unavailable on this device adapter." });
54587
+ return yield* ready.xcode.recording(session, input.action).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) })));
54588
+ }
54589
+ if (input.method === "semantic") {
54590
+ 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()." });
54591
+ return yield* ready.xcode.action(session, input.action).pipe(Effect.mapError((error) => new DeviceToolUnavailableError({ reason: String(error.cause) })));
54592
+ }
53814
54593
  if (input.method === "screenshot") {
53815
- const capture = yield* screenshots.capture(ready, session);
54594
+ 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
54595
  return {
53817
54596
  mimeType: "image/png",
53818
54597
  data: capture.png.toString("base64"),
@@ -53820,6 +54599,7 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53820
54599
  height: capture.height
53821
54600
  };
53822
54601
  }
54602
+ 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
54603
  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
54604
  const route = resolveDeviceHubRoute({
53825
54605
  target: {
@@ -53847,12 +54627,13 @@ const makeComputerUseDevices = Effect.fn("ComputerUseDevices.make")(function* ()
53847
54627
  leaseId: session.leaseId,
53848
54628
  accessibility
53849
54629
  };
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({
54630
+ }).pipe(Effect.scoped, Effect.timeoutOrElse({
54631
+ duration: AX_TIMEOUT_MS,
54632
+ orElse: () => Effect.gen(function* () {
54633
+ 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." });
54634
+ })
54635
+ }), Effect.mapError((error) => isToolUnavailable(error) ? error : new DeviceToolUnavailableError({ reason: error instanceof Error ? error.message : String(error) }))));
54636
+ }), "computer-use").pipe(Effect.provideService(McpInvocationContext, scope), Effect.provideService(DeviceService, devices), Effect.mapError((error) => new ComputerUseError({
53856
54637
  reason: "unavailable",
53857
54638
  detail: error.message
53858
54639
  })));
@@ -65806,6 +66587,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
65806
66587
  context.inFlightTools.delete(index);
65807
66588
  }
65808
66589
  context.inFlightTools.clear();
66590
+ for (const toolUseId of context.subagentModels.keys()) if (!context.subagentTasks.has(toolUseId)) context.subagentModels.delete(toolUseId);
65809
66591
  for (const block of turnState.assistantTextBlockOrder) yield* completeAssistantTextBlock(context, block, {
65810
66592
  force: true,
65811
66593
  rawMethod: "claude/result",
@@ -65974,9 +66756,31 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
65974
66756
  discard: true
65975
66757
  });
65976
66758
  });
66759
+ const recordSubagentModel = Effect.fn("recordSubagentModel")(function* (context, toolUseId, model) {
66760
+ if (!toolUseId || !model || context.subagentModels.get(toolUseId) === model) return;
66761
+ const task = context.subagentTasks.get(toolUseId);
66762
+ if (!task && !Array.from(context.inFlightTools.values()).some((tool) => tool.itemId === toolUseId)) return;
66763
+ context.subagentModels.set(toolUseId, model);
66764
+ if (!task) return;
66765
+ yield* offerRuntimeEvent({
66766
+ ...yield* makeEventStamp(),
66767
+ type: "task.progress",
66768
+ provider: PROVIDER$6,
66769
+ threadId: context.session.threadId,
66770
+ ...context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {},
66771
+ payload: {
66772
+ taskId: RuntimeTaskId.make(task.taskId),
66773
+ description: task.description,
66774
+ toolUseId,
66775
+ ...task.subagentType ? { subagentType: task.subagentType } : {},
66776
+ model
66777
+ }
66778
+ });
66779
+ });
65977
66780
  const handleStreamEvent = Effect.fn("handleStreamEvent")(function* (context, message) {
65978
66781
  if (message.type !== "stream_event") return;
65979
66782
  const { event } = message;
66783
+ if (event.type === "message_start") yield* recordSubagentModel(context, message.parent_tool_use_id, event.message.model);
65980
66784
  if (event.type === "message_delta") {
65981
66785
  if (message.parent_tool_use_id !== null && message.parent_tool_use_id !== void 0) return;
65982
66786
  const snapshot = normalizeClaudeActiveTokenUsage(event.usage, context.lastKnownContextWindow, context.lastKnownTotalProcessedTokens);
@@ -66297,10 +67101,12 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66297
67101
  rawPayload: message
66298
67102
  });
66299
67103
  context.inFlightTools.delete(index);
67104
+ if (!context.subagentTasks.has(tool.itemId)) context.subagentModels.delete(tool.itemId);
66300
67105
  }
66301
67106
  });
66302
67107
  const handleAssistantMessage = Effect.fn("handleAssistantMessage")(function* (context, message) {
66303
67108
  if (message.type !== "assistant") return;
67109
+ yield* recordSubagentModel(context, message.parent_tool_use_id, message.message.model);
66304
67110
  if (!context.turnState) {
66305
67111
  const turnId = TurnId.make(yield* randomUUIDv4);
66306
67112
  const startedAt = yield* nowIso;
@@ -66407,6 +67213,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66407
67213
  }
66408
67214
  switch (message.subtype) {
66409
67215
  case "init":
67216
+ context.currentApiModelId = message.model || context.currentApiModelId;
66410
67217
  yield* offerRuntimeEvent({
66411
67218
  ...base,
66412
67219
  type: "session.configured",
@@ -66477,7 +67284,12 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66477
67284
  return;
66478
67285
  case "task_started": {
66479
67286
  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;
67287
+ const model = (message.tool_use_id ? context.subagentModels.get(message.tool_use_id) : void 0) ?? (requestedModel === "inherit" ? context.currentApiModelId : requestedModel);
67288
+ if (message.tool_use_id) context.subagentTasks.set(message.tool_use_id, {
67289
+ taskId: message.task_id,
67290
+ description: message.description,
67291
+ ...message.subagent_type ? { subagentType: message.subagent_type } : {}
67292
+ });
66481
67293
  yield* offerRuntimeEvent({
66482
67294
  ...base,
66483
67295
  type: "task.started",
@@ -66528,6 +67340,11 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
66528
67340
  ...message.tool_use_id ? { toolUseId: message.tool_use_id } : {}
66529
67341
  }
66530
67342
  });
67343
+ for (const [toolUseId, task] of context.subagentTasks) if (toolUseId === message.tool_use_id || task.taskId === message.task_id) {
67344
+ context.subagentTasks.delete(toolUseId);
67345
+ context.subagentModels.delete(toolUseId);
67346
+ }
67347
+ if (message.tool_use_id) context.subagentModels.delete(message.tool_use_id);
66531
67348
  return;
66532
67349
  case "files_persisted":
66533
67350
  yield* offerRuntimeEvent({
@@ -67228,6 +68045,8 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
67228
68045
  turns: [],
67229
68046
  inFlightTools,
67230
68047
  claudeTasks,
68048
+ subagentTasks: /* @__PURE__ */ new Map(),
68049
+ subagentModels: /* @__PURE__ */ new Map(),
67231
68050
  turnState: void 0,
67232
68051
  pendingTurnRequest: void 0,
67233
68052
  steeredMessages: /* @__PURE__ */ new Map(),
@@ -125492,6 +126311,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
125492
126311
  [WS_METHODS.serverUpdateServer, AuthOrchestrationOperateScope],
125493
126312
  [WS_METHODS.serverUpsertKeybinding, AuthOrchestrationOperateScope],
125494
126313
  [WS_METHODS.serverRemoveKeybinding, AuthOrchestrationOperateScope],
126314
+ [WS_METHODS.threadHandoff, AuthOrchestrationReadScope],
125495
126315
  [WS_METHODS.btwAsk, AuthOrchestrationReadScope],
125496
126316
  [WS_METHODS.btwCancel, AuthOrchestrationReadScope],
125497
126317
  [WS_METHODS.tasksList, AuthOrchestrationReadScope],
@@ -126344,6 +127164,27 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker, computerUseBrok
126344
127164
  operation: "getUsageReport",
126345
127165
  detail: "This provider does not report account usage."
126346
127166
  })), { "rpc.aggregate": "server" }),
127167
+ [WS_METHODS.threadHandoff]: (input) => observeRpcEffect$1(WS_METHODS.threadHandoff, Effect.gen(function* () {
127168
+ const fail = (detail) => new TextGenerationError({
127169
+ operation: "createHandoff",
127170
+ detail
127171
+ });
127172
+ if (!projectionSnapshotQuery.getHandoffContext || !textGeneration.generateBtwAnswer) return yield* fail("Handoff generation is unavailable on this server.");
127173
+ const context = yield* projectionSnapshotQuery.getHandoffContext(input.threadId).pipe(Effect.mapError(() => fail("Could not read thread history.")));
127174
+ if (Option.isNone(context)) return yield* fail("Thread or project is no longer available.");
127175
+ const settings = yield* serverSettings.getSettings.pipe(Effect.mapError(() => fail("Could not read text generation settings.")));
127176
+ const result = yield* textGeneration.generateBtwAnswer({
127177
+ cwd: context.value.projectId === P4_CHAT_PROJECT_ID ? config.chatWorkspaceDir : context.value.cwd,
127178
+ modelSelection: settings.textGenerationModelSelection,
127179
+ context: context.value.transcript,
127180
+ 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."
127181
+ });
127182
+ if (!result.answer.trim()) return yield* fail("Model returned an empty handoff. Try again.");
127183
+ return {
127184
+ projectId: context.value.projectId,
127185
+ text: result.answer.trim()
127186
+ };
127187
+ }), { "rpc.aggregate": "thread" }),
126347
127188
  [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
127189
  [WS_METHODS.btwCancel]: (input) => observeRpcEffect$1(WS_METHODS.btwCancel, btw ? btw.cancel(input) : Effect.succeed({}), { "rpc.aggregate": "server" }),
126349
127190
  [WS_METHODS.serverGetUsageSummary]: (input) => observeRpcEffect$1(WS_METHODS.serverGetUsageSummary, usage.readSummary(input), { "rpc.aggregate": "server" }),
@@ -127104,6 +127945,13 @@ const makeProviderAdapterRegistry = Effect.fn("makeProviderAdapterRegistry")(fun
127104
127945
  });
127105
127946
  const ProviderAdapterRegistryLive = Layer.effect(ProviderAdapterRegistry, makeProviderAdapterRegistry());
127106
127947
  //#endregion
127948
+ //#region src/provider/ComputerUsePrompts.ts
127949
+ /** Routing guidance is injected only when the session has P4's Computer Use endpoint. */
127950
+ 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.";
127951
+ function withComputerUseRouting(input, enabled) {
127952
+ return enabled ? `<p4-computer-use>\n${COMPUTER_USE_ROUTING_PROMPT}\n</p4-computer-use>\n\n${input ?? ""}` : input;
127953
+ }
127954
+ //#endregion
127107
127955
  //#region src/mcp/McpSessionRegistry.ts
127108
127956
  var McpSessionRegistry = class extends Context.Service()("@p4code/cli/mcp/McpSessionRegistry") {};
127109
127957
  /**
@@ -127877,7 +128725,10 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
127877
128725
  ...input.modelSelection?.model ? { "provider.model": input.modelSelection.model } : {}
127878
128726
  });
127879
128727
  yield* touchActiveMcpThread(input.threadId);
127880
- const turn = yield* routed.adapter.sendTurn(input);
128728
+ const turn = yield* routed.adapter.sendTurn({
128729
+ ...input,
128730
+ input: withComputerUseRouting(input.input, !forCompaction && Boolean(readMcpProviderSession(input.threadId)?.computerUse))
128731
+ });
127881
128732
  yield* directory.upsert({
127882
128733
  threadId: input.threadId,
127883
128734
  provider: routed.adapter.provider,
@@ -128362,7 +129213,7 @@ const mcpToolkit = (toolkit) => Layer.effectDiscard(Effect.gen(function* () {
128362
129213
  //#endregion
128363
129214
  //#region src/mcp/toolkits/computerUse/tools.ts
128364
129215
  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.",
129216
+ 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 and a persistent independent black pointer with a thin contrast edge 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. 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
129217
  parameters: Schema$1.Struct({
128367
129218
  ...ComputerUseJsInput.fields,
128368
129219
  hostId: Schema$1.optional(TrimmedNonEmptyString).annotate({ description: "Select an available host when more than one is connected. Reset before changing hosts." })
@@ -132626,6 +133477,7 @@ function runtimeEventToActivities(event, taskTitle, compressMode) {
132626
133477
  summary: event.payload.description.trim().length > 0 ? truncateDetail(event.payload.description, 120) : "Reasoning update",
132627
133478
  payload: {
132628
133479
  taskId: event.payload.taskId,
133480
+ ...event.payload.model ? { model: event.payload.model } : {},
132629
133481
  ...event.provider === "claudeAgent" && event.payload.workflowScript?.instanceId === event.providerInstanceId ? { workflowScript: event.payload.workflowScript } : {},
132630
133482
  ...event.payload.description.trim().length > 0 ? { title: truncateDetail(event.payload.description, 120) } : {},
132631
133483
  detail: event.payload.summary ?? event.payload.description,
@@ -137341,13 +138193,13 @@ const CheckpointingLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$46), La
137341
138193
  const PortScannerLayerLive = layer$45.pipe(Layer.provide(layer$82));
137342
138194
  const TerminalLayerLive = layer$44.pipe(Layer.provide(PtyAdapterLive), Layer.provide(PortScannerLayerLive));
137343
138195
  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));
138196
+ const WorkspaceEntriesLayerLive = layer$17.pipe(Layer.provide(layer$68));
138197
+ const WorkspaceFileSystemLayerLive = layer$16.pipe(Layer.provide(layer$68), Layer.provide(WorkspaceEntriesLayerLive));
138198
+ const WorkspaceLayerLive = Layer.mergeAll(layer$68, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
138199
+ const ProjectFaviconResolverLayerLive = layer$63.pipe(Layer.provide(layer$68), Layer.provide(layer$64));
137348
138200
  const AuthLayerLive = layer$111.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$115));
137349
138201
  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));
138202
+ 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
138203
  const UsageLayerLive = layer$23.pipe(Layer.provide(layer$90.pipe(Layer.provide(layer$115))));
137352
138204
  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
138205
  /**
@@ -137358,7 +138210,7 @@ const RuntimeDependenciesLive = layer$22.pipe(Layer.provideMerge(RuntimeCoreDepe
137358
138210
  * this costs a server with no hub nothing but a layer.
137359
138211
  */
137360
138212
  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));
138213
+ const RuntimeServicesLive = layer$69.pipe(Layer.provideMerge(layer$1), Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
137362
138214
  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
138215
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
137364
138216
  const config = yield* ServerConfig$1;