@sideboard-ai/core 0.1.295 → 0.1.297

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/agents/claude-runner.cjs +55 -0
  2. package/dist/agents/claude-runner.js +57 -1
  3. package/dist/agents/cursor-runner.js +2 -2
  4. package/dist/{agents-S55DBAVO.js → agents-DZTPUKVF.js} +4 -4
  5. package/dist/{agents-EFHYZUZT.js → agents-ICHYDSCD.js} +5 -5
  6. package/dist/{chunk-ABLHTRRG.js → chunk-2O3ICR77.js} +3 -3
  7. package/dist/{chunk-KCP6IVRI.js → chunk-67BWWLEB.js} +4 -4
  8. package/dist/{chunk-RWIYYFID.js → chunk-6GTABVNG.js} +4 -4
  9. package/dist/{chunk-YEIPPZ5W.js → chunk-BGEECLKH.js} +2 -2
  10. package/dist/{chunk-ER6BGGME.js → chunk-J54UPHXZ.js} +1 -1
  11. package/dist/{chunk-ZEP6JIRF.js → chunk-JWXJXEA3.js} +1 -1
  12. package/dist/{chunk-NYBFYFK7.js → chunk-QDA6NXRA.js} +1 -0
  13. package/dist/{chunk-YFKAC4LB.js → chunk-QT36F4TD.js} +54 -25
  14. package/dist/{chunk-YRXV3E7J.js → chunk-TVN4TDFB.js} +9 -0
  15. package/dist/{chunk-6SGYGYH7.js → chunk-VXNZG4J6.js} +1 -1
  16. package/dist/{chunk-XXT3C2EF.js → chunk-W2A63ZOK.js} +176 -39
  17. package/dist/{chunk-E7G7RY6T.js → chunk-WQREAX35.js} +2 -2
  18. package/dist/{chunk-QB5MDOHL.js → chunk-WZLV4WNS.js} +9 -0
  19. package/dist/{chunk-HWIIRVYE.js → chunk-ZXWRNA4G.js} +4 -4
  20. package/dist/{conductor-GURU5XD7.js → conductor-HJFDYIZD.js} +4 -4
  21. package/dist/{conductor-HT2UANDY.js → conductor-ZXDIA6OF.js} +4 -4
  22. package/dist/{coordinator-prompt-AROMFCP2.js → coordinator-prompt-ILHSVPOG.js} +3 -3
  23. package/dist/{coordinator-prompt-K6XHUG3N.js → coordinator-prompt-L3V2CMWX.js} +3 -3
  24. package/dist/{global-workspace-4WAU4PAL.js → global-workspace-47G453ZU.js} +3 -3
  25. package/dist/{global-workspace-PX4GUVLW.js → global-workspace-NMRUGGFF.js} +3 -3
  26. package/dist/index.cjs +1014 -698
  27. package/dist/index.d.cts +25 -4
  28. package/dist/index.d.ts +25 -4
  29. package/dist/index.js +282 -119
  30. package/dist/mcp/run-stdio.cjs +201 -118
  31. package/dist/mcp/run-stdio.js +46 -8
  32. package/dist/{orchestrator-VNSEXL62.js → orchestrator-MR7TCIPP.js} +6 -6
  33. package/dist/{orchestrator-VDYZSZWY.js → orchestrator-ZQU62CES.js} +7 -7
  34. package/dist/{thread-store-DPKESDFR.js → thread-store-H6D6MDUM.js} +1 -1
  35. package/dist/{thread-store-X6S2GIR5.js → thread-store-SADLLKRE.js} +1 -1
  36. package/dist/{workspaces-MFU3IGAP.js → workspaces-PGY4UWZ3.js} +3 -3
  37. package/dist/{workspaces-U7SSD27C.js → workspaces-WGZPRQJX.js} +3 -3
  38. package/dist/{worktree-TQKASYPZ.js → worktree-3MF5J5XZ.js} +2 -2
  39. package/dist/{worktree-2VSHRI72.js → worktree-JZXDAKQF.js} +2 -2
  40. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19,17 +19,20 @@ import {
19
19
  agentBlockFields,
20
20
  agentGitPrompt,
21
21
  alignQueueAttachments,
22
+ answerToolPermission,
22
23
  appendQueuedItem,
23
24
  applyIssueListWindow,
24
25
  applyPromptCacheTtlEnv,
25
26
  applyThreadIntoMain,
26
27
  applyWorkspaceTags,
27
28
  armSchedules,
29
+ askToolPermission,
28
30
  assembleHomeBoard,
29
31
  boardPinIdentity,
30
32
  captureTurnBaseline,
31
33
  chatBlockedSuffix,
32
34
  claimDesktopHost,
35
+ clampBlockReason,
33
36
  clampMcpIssueLimit,
34
37
  classifyWorktreeColumn,
35
38
  classifyWorktreeListBadges,
@@ -160,6 +163,7 @@ import {
160
163
  reviewLabelForRepo,
161
164
  scheduleNeedsCaffeinate,
162
165
  schedulesPath,
166
+ setToolPermissionPublisher,
163
167
  shiftQueuedItem,
164
168
  shouldNotifyOrchestrator,
165
169
  shouldRemoveWorktreeOnTeardown,
@@ -185,8 +189,9 @@ import {
185
189
  worktreeAgentBlock,
186
190
  worktreeBoardStatus,
187
191
  worktreeCleanupSettings,
192
+ writeAgentBlock,
188
193
  writeWorktreeFile
189
- } from "./chunk-XXT3C2EF.js";
194
+ } from "./chunk-W2A63ZOK.js";
190
195
  import {
191
196
  LEGACY_PLAN_FILE_REL,
192
197
  PLAN_FILE_NAME,
@@ -282,7 +287,7 @@ import {
282
287
  userMcpNamesToDisable,
283
288
  worktreeMcpToolNames,
284
289
  writeInjectedMcpConfig
285
- } from "./chunk-RWIYYFID.js";
290
+ } from "./chunk-6GTABVNG.js";
286
291
  import {
287
292
  brightsyMcpServerName,
288
293
  connectBrightsyTeam,
@@ -410,7 +415,7 @@ import {
410
415
  workspaceAgentChatSummaries,
411
416
  worktreeRunPortsPath,
412
417
  wrapReviewSkillMarkdown
413
- } from "./chunk-KCP6IVRI.js";
418
+ } from "./chunk-67BWWLEB.js";
414
419
  import {
415
420
  STDERR_EVENT_MAX_CHARS,
416
421
  STDOUT_EVENT_MAX_CHARS,
@@ -610,7 +615,7 @@ import {
610
615
  stageBuffersAsAttachments,
611
616
  syncWorkspacesFromThreads,
612
617
  takenTeamSlugsForOrchestration
613
- } from "./chunk-YEIPPZ5W.js";
618
+ } from "./chunk-BGEECLKH.js";
614
619
  import {
615
620
  GITHUB_PR_BODY_MAX_CHARS,
616
621
  GITHUB_PR_BODY_SAFE_CHARS,
@@ -698,7 +703,7 @@ import {
698
703
  suggestSlug,
699
704
  switchWorktreeBranch,
700
705
  warmGithubAgentAuth
701
- } from "./chunk-ZEP6JIRF.js";
706
+ } from "./chunk-JWXJXEA3.js";
702
707
  import {
703
708
  FAMOUS_SOCCER_TEAMS,
704
709
  allocateTeamName,
@@ -730,7 +735,7 @@ import {
730
735
  worktreeNameFromPath,
731
736
  worktreeSlugForTicket,
732
737
  writeThread
733
- } from "./chunk-QB5MDOHL.js";
738
+ } from "./chunk-WZLV4WNS.js";
734
739
  import "./chunk-2P4XKWV2.js";
735
740
  import {
736
741
  BRIGHTSY_OAUTH_CLIENT_ID,
@@ -754,7 +759,7 @@ import {
754
759
  import {
755
760
  clipMcpToolResult,
756
761
  cursorToolErrorText
757
- } from "./chunk-ER6BGGME.js";
762
+ } from "./chunk-J54UPHXZ.js";
758
763
  import "./chunk-AXQ4ZWHK.js";
759
764
  import {
760
765
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
@@ -819,7 +824,7 @@ import {
819
824
  rpcNotify,
820
825
  rpcResult,
821
826
  writeAgentRuntimeMetadata
822
- } from "./chunk-NYBFYFK7.js";
827
+ } from "./chunk-QDA6NXRA.js";
823
828
  import {
824
829
  AGENT_DONE_SOUNDS,
825
830
  CHAT_TEXT_SCALES,
@@ -5049,6 +5054,124 @@ function registerScheduleTools(server) {
5049
5054
  // src/agent-rpc/handlers.ts
5050
5055
  import { randomUUID as randomUUID3 } from "crypto";
5051
5056
  import { isAbsolute } from "path";
5057
+
5058
+ // src/remote/phone-permission.ts
5059
+ var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
5060
+ var DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "allow_session"]);
5061
+ var push = null;
5062
+ function setPhonePermissionPush(next) {
5063
+ push = next;
5064
+ }
5065
+ function notifyPhonePermission(chatId, prompt) {
5066
+ const id = chatId.trim();
5067
+ if (!id) return;
5068
+ push?.(id, prompt);
5069
+ }
5070
+ function permissionViewFromBlock(block) {
5071
+ if (!block || block.source !== "permission") return null;
5072
+ const prompt = block.permission;
5073
+ if (!prompt?.id || !prompt.title) return null;
5074
+ return {
5075
+ id: prompt.id,
5076
+ title: prompt.title,
5077
+ detail: typeof prompt.detail === "string" ? prompt.detail : "",
5078
+ ...prompt.path ? { path: prompt.path } : {},
5079
+ allowSession: prompt.allowSession !== false,
5080
+ ...prompt.defaultToNo ? { defaultToNo: true } : {}
5081
+ };
5082
+ }
5083
+ function phonePermissionView(chatId) {
5084
+ return permissionViewFromBlock(readThread(chatId)?.agentBlock);
5085
+ }
5086
+ function textField(value, key) {
5087
+ const raw = value[key];
5088
+ return typeof raw === "string" ? raw.trim() : "";
5089
+ }
5090
+ function takePhonePermission(text5) {
5091
+ if (!text5.startsWith(PHONE_CONTROL_PREFIX)) return null;
5092
+ let parsed;
5093
+ try {
5094
+ parsed = JSON.parse(text5.slice(PHONE_CONTROL_PREFIX.length));
5095
+ } catch {
5096
+ return null;
5097
+ }
5098
+ if (!parsed || typeof parsed !== "object") return null;
5099
+ if (parsed.op !== "permission") return null;
5100
+ const chatId = textField(parsed, "chatId");
5101
+ const id = textField(parsed, "id");
5102
+ const decision = textField(parsed, "decision");
5103
+ if (!chatId || !id || !DECISIONS.has(decision)) return "invalid";
5104
+ return { op: "permission", chatId, id, decision };
5105
+ }
5106
+ function dispatchPhonePermission(text5, emit) {
5107
+ const cmd = takePhonePermission(text5);
5108
+ if (cmd == null) return false;
5109
+ if (cmd === "invalid") {
5110
+ emit({ op: "error", message: "invalid phone command" });
5111
+ return true;
5112
+ }
5113
+ if (!answerToolPermission(cmd.id, cmd.decision)) {
5114
+ emit({ op: "error", chatId: cmd.chatId, message: "That request already finished." });
5115
+ emit({ op: "permission", chatId: cmd.chatId, prompt: phonePermissionView(cmd.chatId) });
5116
+ }
5117
+ return true;
5118
+ }
5119
+
5120
+ // src/agent-rpc/permission-handler.ts
5121
+ setToolPermissionPublisher({
5122
+ show(item) {
5123
+ const thread = readThread(item.threadId);
5124
+ if (!thread || thread.status === "archived") return false;
5125
+ writeAgentBlock(item.threadId, {
5126
+ source: "permission",
5127
+ reason: clampBlockReason(item.prompt.title),
5128
+ at: (/* @__PURE__ */ new Date()).toISOString(),
5129
+ permission: item.prompt
5130
+ });
5131
+ notifyPhonePermission(item.threadId, item.prompt);
5132
+ return true;
5133
+ },
5134
+ clear(threadId, id) {
5135
+ const thread = readThread(threadId);
5136
+ if (thread?.agentBlock?.source === "permission" && thread.agentBlock.permission?.id === id) {
5137
+ updateThread(threadId, { agentBlock: null });
5138
+ notifyPhonePermission(threadId, null);
5139
+ }
5140
+ }
5141
+ });
5142
+ function asRecord(value) {
5143
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
5144
+ return value;
5145
+ }
5146
+ function str(value) {
5147
+ return typeof value === "string" ? value : "";
5148
+ }
5149
+ function invalidParams(message) {
5150
+ return Object.assign(new Error(message), { rpcCode: -32602 });
5151
+ }
5152
+ var handlePermissionAsk = async (params, _ctx) => {
5153
+ const rec = asRecord(params);
5154
+ const threadId = str(rec.threadId).trim();
5155
+ const toolName = str(rec.toolName).trim();
5156
+ if (!threadId) throw invalidParams("threadId is required");
5157
+ if (!toolName) throw invalidParams("toolName is required");
5158
+ const input = asRecord(rec.input);
5159
+ const ask = {
5160
+ threadId,
5161
+ toolName,
5162
+ input: Object.keys(input).length > 0 ? input : void 0,
5163
+ blockedPath: str(rec.blockedPath).trim() || void 0,
5164
+ title: str(rec.title).trim() || void 0,
5165
+ description: str(rec.description).trim() || void 0,
5166
+ displayName: str(rec.displayName).trim() || void 0,
5167
+ suggestions: rec.suggestions,
5168
+ suppressAlwaysAllow: rec.suppressAlwaysAllow === true,
5169
+ defaultToNo: rec.defaultToNo === true
5170
+ };
5171
+ return askToolPermission(ask);
5172
+ };
5173
+
5174
+ // src/agent-rpc/handlers.ts
5052
5175
  function presentArtifactResult(input) {
5053
5176
  const id = input.artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${randomUUID3().slice(0, 8)}`;
5054
5177
  return {
@@ -5062,20 +5185,20 @@ function presentArtifactResult(input) {
5062
5185
  message: input.type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
5063
5186
  };
5064
5187
  }
5065
- function asRecord(value) {
5188
+ function asRecord2(value) {
5066
5189
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
5067
5190
  return value;
5068
5191
  }
5069
- function str(value) {
5192
+ function str2(value) {
5070
5193
  return typeof value === "string" ? value : "";
5071
5194
  }
5072
- function invalidParams(message) {
5195
+ function invalidParams2(message) {
5073
5196
  return Object.assign(new Error(message), { rpcCode: -32602 });
5074
5197
  }
5075
5198
  function jobCwd(rec) {
5076
- const raw = str(rec.cwd).trim();
5077
- if (!raw) throw invalidParams("cwd is required");
5078
- if (!isAbsolute(raw)) throw invalidParams("cwd must be an absolute path");
5199
+ const raw = str2(rec.cwd).trim();
5200
+ if (!raw) throw invalidParams2("cwd is required");
5201
+ if (!isAbsolute(raw)) throw invalidParams2("cwd must be an absolute path");
5079
5202
  return raw;
5080
5203
  }
5081
5204
  async function handleRuntimePing(_params, _ctx) {
@@ -5086,33 +5209,33 @@ async function handleRuntimePing(_params, _ctx) {
5086
5209
  };
5087
5210
  }
5088
5211
  async function handlePresentArtifact(params, _ctx) {
5089
- const rec = asRecord(params);
5090
- const title = str(rec.title).trim();
5091
- const type = str(rec.type).trim();
5212
+ const rec = asRecord2(params);
5213
+ const title = str2(rec.title).trim();
5214
+ const type = str2(rec.type).trim();
5092
5215
  const allowed = ["html", "svg", "markdown", "react", "log"];
5093
- if (!title) throw invalidParams("title is required");
5216
+ if (!title) throw invalidParams2("title is required");
5094
5217
  if (!allowed.includes(type)) {
5095
- throw invalidParams("type must be html, svg, markdown, react, or log");
5218
+ throw invalidParams2("type must be html, svg, markdown, react, or log");
5096
5219
  }
5097
- const statusRaw = str(rec.status).trim();
5220
+ const statusRaw = str2(rec.status).trim();
5098
5221
  const status = ["running", "ok", "failed", "idle"].includes(
5099
5222
  statusRaw
5100
5223
  ) ? statusRaw : void 0;
5101
- const modeRaw = str(rec.mode).trim();
5224
+ const modeRaw = str2(rec.mode).trim();
5102
5225
  const mode = modeRaw === "append" || modeRaw === "replace" ? modeRaw : void 0;
5103
5226
  return presentArtifactResult({
5104
5227
  title,
5105
5228
  type,
5106
- artifact_id: str(rec.artifact_id) || void 0,
5229
+ artifact_id: str2(rec.artifact_id) || void 0,
5107
5230
  status,
5108
- phase: str(rec.phase) || void 0,
5231
+ phase: str2(rec.phase) || void 0,
5109
5232
  mode
5110
5233
  });
5111
5234
  }
5112
5235
  async function handleJobWait(params, ctx, waitFn = waitForDetachedJob) {
5113
- const rec = asRecord(params);
5114
- const id = str(rec.id).trim();
5115
- if (!id) throw invalidParams("id is required");
5236
+ const rec = asRecord2(params);
5237
+ const id = str2(rec.id).trim();
5238
+ if (!id) throw invalidParams2("id is required");
5116
5239
  const cwd = jobCwd(rec);
5117
5240
  const deadline = Date.now() + AGENT_RPC_JOB_HOLD_MAX_MS;
5118
5241
  let last;
@@ -5134,14 +5257,14 @@ async function handleJobWait(params, ctx, waitFn = waitForDetachedJob) {
5134
5257
  };
5135
5258
  }
5136
5259
  async function handleJobStop(params, _ctx) {
5137
- const rec = asRecord(params);
5138
- const id = str(rec.id).trim();
5139
- if (!id) throw invalidParams("id is required");
5140
- const reason = str(rec.reason).trim() || void 0;
5260
+ const rec = asRecord2(params);
5261
+ const id = str2(rec.id).trim();
5262
+ if (!id) throw invalidParams2("id is required");
5263
+ const reason = str2(rec.reason).trim() || void 0;
5141
5264
  return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
5142
5265
  }
5143
5266
  async function defaultTurnWait(ref, timeoutMs) {
5144
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-VDYZSZWY.js");
5267
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-ZQU62CES.js");
5145
5268
  const orch = getOrchestrator2();
5146
5269
  const thread = await orch.waitForTurn(ref, timeoutMs, {
5147
5270
  resolveIfStillRunning: true
@@ -5162,9 +5285,9 @@ async function defaultTurnWait(ref, timeoutMs) {
5162
5285
  });
5163
5286
  }
5164
5287
  async function handleTurnWait(params, ctx, waitFn = defaultTurnWait) {
5165
- const rec = asRecord(params);
5166
- const ref = str(rec.ref).trim();
5167
- if (!ref) throw invalidParams("ref is required");
5288
+ const rec = asRecord2(params);
5289
+ const ref = str2(rec.ref).trim();
5290
+ if (!ref) throw invalidParams2("ref is required");
5168
5291
  const requested = typeof rec.timeoutMs === "number" && Number.isFinite(rec.timeoutMs) ? rec.timeoutMs : void 0;
5169
5292
  const holdMax = requested !== void 0 ? Math.min(Math.max(1e3, Math.floor(requested)), AGENT_RPC_JOB_HOLD_MAX_MS) : AGENT_RPC_JOB_HOLD_MAX_MS;
5170
5293
  const deadline = Date.now() + holdMax;
@@ -5197,7 +5320,8 @@ function createAgentRpcDispatcher() {
5197
5320
  "ui.presentArtifact": handlePresentArtifact,
5198
5321
  "job.wait": handleJobWait,
5199
5322
  "job.stop": handleJobStop,
5200
- "turn.wait": handleTurnWait
5323
+ "turn.wait": handleTurnWait,
5324
+ "permission.ask": handlePermissionAsk
5201
5325
  };
5202
5326
  }
5203
5327
 
@@ -8863,7 +8987,7 @@ var BAKED_REMOTE_RELAY_URL = `wss://relay.sideboard.cloud${REMOTE_RELAY_PATH}`;
8863
8987
  function remoteRelayUrl() {
8864
8988
  return process.env.SIDEBOARD_REMOTE_RELAY_URL?.trim() || BAKED_REMOTE_RELAY_URL;
8865
8989
  }
8866
- function str2(value) {
8990
+ function str3(value) {
8867
8991
  return typeof value === "string" ? value.trim() : "";
8868
8992
  }
8869
8993
  function questions(value) {
@@ -8872,14 +8996,14 @@ function questions(value) {
8872
8996
  for (const item of value) {
8873
8997
  if (!item || typeof item !== "object") return null;
8874
8998
  const row2 = item;
8875
- const question = str2(row2.question);
8999
+ const question = str3(row2.question);
8876
9000
  if (!question || !Array.isArray(row2.options)) return null;
8877
9001
  const options = [];
8878
9002
  for (const opt of row2.options) {
8879
9003
  if (!opt || typeof opt !== "object") return null;
8880
- const label = str2(opt.label);
9004
+ const label = str3(opt.label);
8881
9005
  if (!label) return null;
8882
- const description = str2(opt.description);
9006
+ const description = str3(opt.description);
8883
9007
  options.push(description ? { label, description } : { label });
8884
9008
  }
8885
9009
  if (options.length === 0) return null;
@@ -8900,11 +9024,11 @@ function parseRemoteClientMessage(raw) {
8900
9024
  if (msg.type === "host_pair") return { type: "host_pair" };
8901
9025
  if (msg.type === "stop") return { type: "stop" };
8902
9026
  if (msg.type === "host_register") {
8903
- const deviceId = str2(parsed.deviceId);
8904
- const hostSecret = str2(parsed.hostSecret);
8905
- const deviceLabel = str2(parsed.deviceLabel);
9027
+ const deviceId = str3(parsed.deviceId);
9028
+ const hostSecret = str3(parsed.hostSecret);
9029
+ const deviceLabel = str3(parsed.deviceLabel);
8906
9030
  if (!deviceId || !hostSecret || !deviceLabel) return null;
8907
- const accountToken = str2(parsed.accountToken);
9031
+ const accountToken = str3(parsed.accountToken);
8908
9032
  return {
8909
9033
  type: "host_register",
8910
9034
  deviceId,
@@ -8914,12 +9038,12 @@ function parseRemoteClientMessage(raw) {
8914
9038
  };
8915
9039
  }
8916
9040
  if (msg.type === "pair") {
8917
- const code = str2(parsed.code).toUpperCase();
9041
+ const code = str3(parsed.code).toUpperCase();
8918
9042
  if (!code) return null;
8919
9043
  return { type: "pair", code };
8920
9044
  }
8921
9045
  if (msg.type === "resume") {
8922
- const sessionToken = str2(parsed.sessionToken);
9046
+ const sessionToken = str3(parsed.sessionToken);
8923
9047
  if (!sessionToken) return null;
8924
9048
  return { type: "resume", sessionToken };
8925
9049
  }
@@ -8930,19 +9054,19 @@ function parseRemoteClientMessage(raw) {
8930
9054
  return { type: "list_hosts", sessionTokens };
8931
9055
  }
8932
9056
  if (msg.type === "prompt") {
8933
- const text5 = str2(parsed.text);
9057
+ const text5 = str3(parsed.text);
8934
9058
  if (!text5) return null;
8935
9059
  return { type: "prompt", text: text5 };
8936
9060
  }
8937
9061
  if (msg.type === "assistant") {
8938
- return { type: "assistant", text: str2(parsed.text) };
9062
+ return { type: "assistant", text: str3(parsed.text) };
8939
9063
  }
8940
9064
  if (msg.type === "ask_user") {
8941
9065
  const parsedQuestions = questions(parsed.questions);
8942
9066
  if (!parsedQuestions?.length) return null;
8943
9067
  return {
8944
9068
  type: "ask_user",
8945
- text: str2(parsed.text),
9069
+ text: str3(parsed.text),
8946
9070
  questions: parsedQuestions
8947
9071
  };
8948
9072
  }
@@ -8968,7 +9092,7 @@ function parseRemoteServerMessage(raw) {
8968
9092
  for (const item of raw2) {
8969
9093
  if (!item || typeof item !== "object") return null;
8970
9094
  const row2 = item;
8971
- const sessionToken = str2(row2.sessionToken);
9095
+ const sessionToken = str3(row2.sessionToken);
8972
9096
  if (!sessionToken || typeof row2.deviceId !== "string" || typeof row2.deviceLabel !== "string") return null;
8973
9097
  if (typeof row2.online !== "boolean") return null;
8974
9098
  hosts.push({
@@ -8982,43 +9106,43 @@ function parseRemoteServerMessage(raw) {
8982
9106
  }
8983
9107
  if (type === "stop") return { type: "stop" };
8984
9108
  if (type === "registered") {
8985
- const deviceId = str2(parsed.deviceId);
8986
- const deviceLabel = str2(parsed.deviceLabel);
9109
+ const deviceId = str3(parsed.deviceId);
9110
+ const deviceLabel = str3(parsed.deviceLabel);
8987
9111
  if (!deviceId || !deviceLabel) return null;
8988
9112
  return { type: "registered", deviceId, deviceLabel };
8989
9113
  }
8990
9114
  if (type === "pairing_code") {
8991
- const code = str2(parsed.code);
9115
+ const code = str3(parsed.code);
8992
9116
  const expiresAt = parsed.expiresAt;
8993
9117
  if (!code || typeof expiresAt !== "number") return null;
8994
9118
  return { type: "pairing_code", code, expiresAt };
8995
9119
  }
8996
9120
  if (type === "inbound") {
8997
- const text5 = str2(parsed.text);
9121
+ const text5 = str3(parsed.text);
8998
9122
  if (!text5) return null;
8999
9123
  return { type: "inbound", text: text5 };
9000
9124
  }
9001
9125
  if (type === "paired") {
9002
- const deviceId = str2(parsed.deviceId);
9003
- const deviceLabel = str2(parsed.deviceLabel);
9004
- const sessionToken = str2(parsed.sessionToken);
9126
+ const deviceId = str3(parsed.deviceId);
9127
+ const deviceLabel = str3(parsed.deviceLabel);
9128
+ const sessionToken = str3(parsed.sessionToken);
9005
9129
  if (!deviceId || !deviceLabel || !sessionToken) return null;
9006
9130
  return { type: "paired", deviceId, deviceLabel, sessionToken };
9007
9131
  }
9008
9132
  if (type === "assistant") {
9009
- return { type: "assistant", text: str2(parsed.text) };
9133
+ return { type: "assistant", text: str3(parsed.text) };
9010
9134
  }
9011
9135
  if (type === "ask_user") {
9012
9136
  const parsedQuestions = questions(parsed.questions);
9013
9137
  if (!parsedQuestions?.length) return null;
9014
9138
  return {
9015
9139
  type: "ask_user",
9016
- text: str2(parsed.text),
9140
+ text: str3(parsed.text),
9017
9141
  questions: parsedQuestions
9018
9142
  };
9019
9143
  }
9020
9144
  if (type === "error") {
9021
- const message = str2(parsed.message) || "remote error";
9145
+ const message = str3(parsed.message) || "remote error";
9022
9146
  return { type: "error", message };
9023
9147
  }
9024
9148
  return null;
@@ -9446,10 +9570,10 @@ var MIN_MD_CHARS = 120;
9446
9570
  function oneLine(value, max) {
9447
9571
  return value.replace(/[`\r\n]/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
9448
9572
  }
9449
- function asRecord2(value) {
9573
+ function asRecord3(value) {
9450
9574
  if (typeof value === "string") {
9451
9575
  try {
9452
- return asRecord2(JSON.parse(value));
9576
+ return asRecord3(JSON.parse(value));
9453
9577
  } catch {
9454
9578
  return void 0;
9455
9579
  }
@@ -9457,7 +9581,7 @@ function asRecord2(value) {
9457
9581
  if (value && typeof value === "object" && !Array.isArray(value)) return value;
9458
9582
  return void 0;
9459
9583
  }
9460
- function str3(value) {
9584
+ function str4(value) {
9461
9585
  return typeof value === "string" ? value : "";
9462
9586
  }
9463
9587
  function normalizedBody(content) {
@@ -9524,7 +9648,7 @@ function toolContent(input) {
9524
9648
  if (!input) return "";
9525
9649
  const direct = input.content;
9526
9650
  if (typeof direct === "string") return direct;
9527
- const nested = asRecord2(direct);
9651
+ const nested = asRecord3(direct);
9528
9652
  if (!nested) return "";
9529
9653
  for (const key of ["html", "markup", "text", "source", "content", "code"]) {
9530
9654
  if (typeof nested[key] === "string") return nested[key];
@@ -9547,13 +9671,13 @@ function phoneArtifactText(text5, parts, limit = Number.POSITIVE_INFINITY) {
9547
9671
  const extras = [];
9548
9672
  for (const part of parts ?? []) {
9549
9673
  if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
9550
- const input = asRecord2(part.input);
9674
+ const input = asRecord3(part.input);
9551
9675
  const content = toolContent(input);
9552
9676
  const hint = artifactHint(content);
9553
9677
  if (hint && hints.has(hint)) continue;
9554
9678
  const kind = kindFor("", content);
9555
- const title = oneLine(str3(input?.title), 80) || (kind ? titleFor(kind, content) : "Artifact");
9556
- if (!str3(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
9679
+ const title = oneLine(str4(input?.title), 80) || (kind ? titleFor(kind, content) : "Artifact");
9680
+ if (!str4(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
9557
9681
  if (hint) hints.add(hint);
9558
9682
  extras.push(marker(title, hint));
9559
9683
  }
@@ -9566,11 +9690,11 @@ function phoneArtifactText(text5, parts, limit = Number.POSITIVE_INFINITY) {
9566
9690
  ${tail}` : tail;
9567
9691
  }
9568
9692
  function parseOpenArtifact(value, chatId) {
9569
- const record = asRecord2(value);
9693
+ const record = asRecord3(value);
9570
9694
  if (!record) return "invalid";
9571
- const title = oneLine(str3(record.title), 200);
9695
+ const title = oneLine(str4(record.title), 200);
9572
9696
  if (!title) return "invalid";
9573
- const hint = oneLine(str3(record.hint), 200);
9697
+ const hint = oneLine(str4(record.hint), 200);
9574
9698
  return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
9575
9699
  }
9576
9700
  var PHONE_ARTIFACT_MAX_CHARS = 18e4;
@@ -9589,13 +9713,13 @@ function phoneArtifactDocuments(sources) {
9589
9713
  }
9590
9714
  for (const part of source.parts ?? []) {
9591
9715
  if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
9592
- const input = asRecord2(part.input);
9716
+ const input = asRecord3(part.input);
9593
9717
  const content = toolContent(input);
9594
9718
  const hint = artifactHint(content);
9595
9719
  if (hint && hints.has(hint)) continue;
9596
- const typed = kindFor(str3(input?.type), content) ?? kindFor("", content);
9597
- const title = oneLine(str3(input?.title), 80) || (typed ? titleFor(typed, content) : "Artifact");
9598
- if (!str3(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
9720
+ const typed = kindFor(str4(input?.type), content) ?? kindFor("", content);
9721
+ const title = oneLine(str4(input?.title), 80) || (typed ? titleFor(typed, content) : "Artifact");
9722
+ if (!str4(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
9599
9723
  if (hint) hints.add(hint);
9600
9724
  docs.push({ title, content, kind: typed ?? "text" });
9601
9725
  }
@@ -9640,13 +9764,21 @@ function pickArtifactMatch(items, request) {
9640
9764
  }
9641
9765
 
9642
9766
  // src/remote/phone-panes.ts
9767
+ var PHONE_CHANGE_SCOPES = ["last_turn", "uncommitted", "staged", "unstaged", "commits"];
9768
+ var PHONE_CHANGE_SCOPE_DEFAULT = "commits";
9769
+ function phoneChangeScope(value) {
9770
+ return typeof value === "string" && PHONE_CHANGE_SCOPES.includes(value) ? value : void 0;
9771
+ }
9772
+ function phoneCommitSha(value) {
9773
+ return typeof value === "string" && /^[0-9a-f]{4,40}$/i.test(value) ? value : void 0;
9774
+ }
9643
9775
  var MAX_ROWS = 200;
9644
9776
  var MAX_QUERY = 200;
9645
9777
  function parsePhonePane(value, chatId) {
9646
9778
  const pane = value && typeof value === "object" ? value.pane : void 0;
9647
9779
  if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
9648
- const scopeRaw = value.scope;
9649
- const scope = scopeRaw === "uncommitted" || scopeRaw === "commits" ? scopeRaw : void 0;
9780
+ const scope = phoneChangeScope(value.scope);
9781
+ const commitSha = phoneCommitSha(value.commitSha);
9650
9782
  const queryRaw = value.query;
9651
9783
  const query = typeof queryRaw === "string" ? queryRaw.trim().slice(0, MAX_QUERY) : "";
9652
9784
  return {
@@ -9654,6 +9786,7 @@ function parsePhonePane(value, chatId) {
9654
9786
  chatId,
9655
9787
  pane,
9656
9788
  ...scope && pane === "changes" ? { scope } : {},
9789
+ ...commitSha && pane === "changes" && scope === "commits" ? { commitSha } : {},
9657
9790
  ...query && pane === "files" ? { query } : {}
9658
9791
  };
9659
9792
  }
@@ -9662,10 +9795,10 @@ function filterPhoneFiles(paths, query) {
9662
9795
  if (!q) return paths;
9663
9796
  return paths.filter((path2) => path2.toLowerCase().includes(q));
9664
9797
  }
9665
- async function readPhonePane(thread, pane, scope, query) {
9798
+ async function readPhonePane(thread, pane, scope, query, commitSha) {
9666
9799
  try {
9667
9800
  if (pane === "files") return await readFiles(thread.id, query);
9668
- if (pane === "changes") return await readChanges(thread.id, scope ?? "commits");
9801
+ if (pane === "changes") return await readChanges(thread.id, scope ?? PHONE_CHANGE_SCOPE_DEFAULT, commitSha);
9669
9802
  return await readChecks(thread.id);
9670
9803
  } catch (err) {
9671
9804
  const message = err instanceof Error ? err.message : String(err);
@@ -9682,9 +9815,14 @@ async function readFiles(threadId, query) {
9682
9815
  ...note ? { note } : {}
9683
9816
  };
9684
9817
  }
9685
- async function readChanges(threadId, scope) {
9686
- const diff = await getOrchestrator().diff(threadId, { includePatches: false, scope });
9818
+ async function readChanges(threadId, scope, commitSha) {
9819
+ const diff = await getOrchestrator().diff(threadId, {
9820
+ includePatches: false,
9821
+ scope,
9822
+ commitSha: scope === "commits" ? commitSha ?? null : null
9823
+ });
9687
9824
  const shown = diff.files.slice(0, MAX_ROWS);
9825
+ const selected = diff.commits.find((commit) => commit.sha === commitSha || commit.shortSha === commitSha);
9688
9826
  return {
9689
9827
  rows: shown.map((file) => {
9690
9828
  const detail = changeDetail(file.additions, file.deletions);
@@ -9695,7 +9833,13 @@ async function readChanges(threadId, scope) {
9695
9833
  ...detail ? { detail } : {}
9696
9834
  };
9697
9835
  }),
9698
- ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {}
9836
+ ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {},
9837
+ scope: diff.scope,
9838
+ commitSha: selected?.sha ?? null,
9839
+ ...diff.base ? { base: diff.base } : {},
9840
+ hasLastTurnBase: diff.hasLastTurnBase,
9841
+ commits: diff.commits.slice(0, 40),
9842
+ scopeStats: diff.scopeStats
9699
9843
  };
9700
9844
  }
9701
9845
  function changeDetail(additions, deletions) {
@@ -9704,13 +9848,14 @@ function changeDetail(additions, deletions) {
9704
9848
  if (add === 0 && del === 0) return void 0;
9705
9849
  return `+${add} \u2212${del}`;
9706
9850
  }
9707
- async function readPhoneChangePatch(threadId, path2) {
9851
+ async function readPhoneChangePatch(threadId, path2, scope = PHONE_CHANGE_SCOPE_DEFAULT, commitSha) {
9708
9852
  const diff = await getOrchestrator().diff(threadId, {
9709
9853
  path: path2,
9710
- scope: "commits",
9854
+ scope,
9855
+ commitSha: scope === "commits" ? commitSha ?? null : null,
9711
9856
  includePatches: true,
9712
9857
  includeMeta: false,
9713
- includeUntracked: false
9858
+ includeUntracked: scope === "uncommitted" || scope === "unstaged" || scope === "last_turn" || scope === "commits" && !commitSha
9714
9859
  });
9715
9860
  const file = diff.files.find((row2) => row2.path === path2) ?? diff.files[0];
9716
9861
  if (!file) return null;
@@ -10055,7 +10200,7 @@ function listPhoneHistory(opts) {
10055
10200
  }
10056
10201
 
10057
10202
  // src/remote/phone-chats.ts
10058
- var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
10203
+ var PHONE_CONTROL_PREFIX2 = "\0sb.phone\n";
10059
10204
  var PREVIEW_LIMIT2 = 90;
10060
10205
  var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
10061
10206
  var MAX_PHONE_FILES = 8;
@@ -10063,13 +10208,13 @@ var MAX_PHONE_FILE_CHARS = Math.ceil(8 * 1024 * 1024 * 4 / 3);
10063
10208
  var MAX_PHONE_FILES_CHARS = Math.ceil(20 * 1024 * 1024 * 4 / 3);
10064
10209
  var MAX_DICTATION_CHARS = Math.ceil(12 * 1024 * 1024 * 4 / 3);
10065
10210
  function encodePhoneControl(payload) {
10066
- return PHONE_CONTROL_PREFIX + JSON.stringify(payload);
10211
+ return PHONE_CONTROL_PREFIX2 + JSON.stringify(payload);
10067
10212
  }
10068
10213
  function takePhoneControl(text5) {
10069
- if (!text5.startsWith(PHONE_CONTROL_PREFIX)) return null;
10214
+ if (!text5.startsWith(PHONE_CONTROL_PREFIX2)) return null;
10070
10215
  let parsed;
10071
10216
  try {
10072
- parsed = JSON.parse(text5.slice(PHONE_CONTROL_PREFIX.length));
10217
+ parsed = JSON.parse(text5.slice(PHONE_CONTROL_PREFIX2.length));
10073
10218
  } catch {
10074
10219
  return "invalid";
10075
10220
  }
@@ -10080,14 +10225,14 @@ function parsePhoneControl(value) {
10080
10225
  const op = value.op;
10081
10226
  if (op === "list") return { op: "list" };
10082
10227
  if (op === "history") {
10083
- const query = textField(value, "query");
10084
- const after = textField(value, "after");
10228
+ const query = textField2(value, "query");
10229
+ const after = textField2(value, "after");
10085
10230
  return { op: "history", ...query ? { query } : {}, ...after ? { after } : {} };
10086
10231
  }
10087
10232
  if (op === "sources") {
10088
- const repoPath = textField(value, "repoPath");
10233
+ const repoPath = textField2(value, "repoPath");
10089
10234
  if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
10090
- const query = textField(value, "query");
10235
+ const query = textField2(value, "query");
10091
10236
  return { op: "sources", repoPath, ...query ? { query } : {} };
10092
10237
  }
10093
10238
  if (op === "models") {
@@ -10096,7 +10241,7 @@ function parsePhoneControl(value) {
10096
10241
  return { op: "models", agent };
10097
10242
  }
10098
10243
  if (op === "dictate") {
10099
- const id = textField(value, "id");
10244
+ const id = textField2(value, "id");
10100
10245
  const audioBase64 = base64Field(value, "audioBase64");
10101
10246
  if (!id || !audioBase64 || audioBase64.length > MAX_DICTATION_CHARS) return "invalid";
10102
10247
  return { op: "dictate", id, audioBase64 };
@@ -10108,26 +10253,30 @@ function parsePhoneControl(value) {
10108
10253
  return { op, chatId };
10109
10254
  }
10110
10255
  if (op === "git") {
10111
- const action = textField(value, "action");
10256
+ const action = textField2(value, "action");
10112
10257
  if (!PHONE_HEADER_GIT_ACTIONS.includes(action)) return "invalid";
10113
10258
  return { op: "git", chatId, action };
10114
10259
  }
10115
10260
  if (op === "pane") return parsePhonePane(value, chatId);
10116
10261
  if (op === "open-artifact") return parseOpenArtifact(value, chatId);
10117
10262
  if (op === "open-file" || op === "media") {
10118
- const path2 = textField(value, "path");
10263
+ const path2 = textField2(value, "path");
10119
10264
  if (!path2 || path2.length > 2e3) return "invalid";
10120
10265
  if (op === "media") return { op, chatId, path: path2 };
10121
10266
  const startLine = lineField(value, "startLine");
10122
10267
  const endLine = lineField(value, "endLine");
10123
- const view = textField(value, "view") === "diff" ? "diff" : void 0;
10268
+ const view = textField2(value, "view") === "diff" ? "diff" : void 0;
10269
+ const scope = phoneChangeScope(value.scope);
10270
+ const commitSha = phoneCommitSha(value.commitSha);
10124
10271
  return {
10125
10272
  op,
10126
10273
  chatId,
10127
10274
  path: path2,
10128
10275
  ...startLine != null ? { startLine } : {},
10129
10276
  ...endLine != null ? { endLine } : {},
10130
- ...view ? { view } : {}
10277
+ ...view ? { view } : {},
10278
+ ...view && scope ? { scope } : {},
10279
+ ...view && scope === "commits" && commitSha ? { commitSha } : {}
10131
10280
  };
10132
10281
  }
10133
10282
  const draft = parsePhoneDraft(value);
@@ -10141,7 +10290,7 @@ function parsePhoneControl(value) {
10141
10290
  }
10142
10291
  return "invalid";
10143
10292
  }
10144
- function textField(value, key) {
10293
+ function textField2(value, key) {
10145
10294
  const raw = value[key];
10146
10295
  return typeof raw === "string" ? raw.trim() : "";
10147
10296
  }
@@ -10151,7 +10300,7 @@ function lineField(value, key) {
10151
10300
  return raw;
10152
10301
  }
10153
10302
  function agentField(value) {
10154
- const agent = textField(value, "agent");
10303
+ const agent = textField2(value, "agent");
10155
10304
  if (!agent) return void 0;
10156
10305
  return AGENT_KINDS.has(agent) ? agent : void 0;
10157
10306
  }
@@ -10162,7 +10311,7 @@ function base64Field(value, key) {
10162
10311
  }
10163
10312
  function parsePhoneDraft(value) {
10164
10313
  const draft = {};
10165
- if (textField(value, "agent")) {
10314
+ if (textField2(value, "agent")) {
10166
10315
  const agent = agentField(value);
10167
10316
  if (!agent) return "invalid";
10168
10317
  draft.agent = agent;
@@ -10202,7 +10351,7 @@ function parsePhoneDraft(value) {
10202
10351
  const refs = parsePhoneCodeRefs(value.refs);
10203
10352
  if (refs === "invalid") return "invalid";
10204
10353
  if (refs.length) draft.refs = refs;
10205
- const prompt = textField(value, "prompt");
10354
+ const prompt = textField2(value, "prompt");
10206
10355
  if (prompt) draft.prompt = prompt.slice(0, 2e4);
10207
10356
  return draft;
10208
10357
  }
@@ -10213,7 +10362,7 @@ function parsePhoneFiles(value) {
10213
10362
  let total = 0;
10214
10363
  for (const item of value) {
10215
10364
  if (!item || typeof item !== "object") return "invalid";
10216
- const name = (textField(item, "name").replace(/[/\\]/g, "_") || "file").slice(0, 180);
10365
+ const name = (textField2(item, "name").replace(/[/\\]/g, "_") || "file").slice(0, 180);
10217
10366
  const dataBase64 = base64Field(item, "dataBase64");
10218
10367
  if (!dataBase64 || !/^[A-Za-z0-9+/=]+$/.test(dataBase64)) return "invalid";
10219
10368
  if (dataBase64.length > MAX_PHONE_FILE_CHARS) return "invalid";
@@ -10229,10 +10378,10 @@ function parsePhoneLinks(value) {
10229
10378
  const links = [];
10230
10379
  for (const item of value) {
10231
10380
  if (!item || typeof item !== "object") return "invalid";
10232
- const ref = textField(item, "ref").slice(0, 80);
10381
+ const ref = textField2(item, "ref").slice(0, 80);
10233
10382
  if (!ref) return "invalid";
10234
- const title = (textField(item, "title") || ref).slice(0, 200);
10235
- const url = textField(item, "url").slice(0, 500);
10383
+ const title = (textField2(item, "title") || ref).slice(0, 200);
10384
+ const url = textField2(item, "url").slice(0, 500);
10236
10385
  links.push({ ref, title, ...url ? { url } : {} });
10237
10386
  }
10238
10387
  return links;
@@ -10240,7 +10389,7 @@ function parsePhoneLinks(value) {
10240
10389
  function parsePhoneCreate(value) {
10241
10390
  const draft = parsePhoneDraft(value);
10242
10391
  if (draft === "invalid") return "invalid";
10243
- const goal = textField(value, "goal") || draft.prompt || "";
10392
+ const goal = textField2(value, "goal") || draft.prompt || "";
10244
10393
  const where = value.where;
10245
10394
  if (where == null || where === "orchestration") {
10246
10395
  return {
@@ -10251,20 +10400,20 @@ function parsePhoneCreate(value) {
10251
10400
  };
10252
10401
  }
10253
10402
  if (where === "worktree") {
10254
- const chatId = textField(value, "chatId");
10403
+ const chatId = textField2(value, "chatId");
10255
10404
  if (!chatId) return "invalid";
10256
10405
  return { op: "create", where: "worktree", chatId, ...draft };
10257
10406
  }
10258
10407
  if (where === "project") {
10259
- const repoPath = textField(value, "repoPath");
10408
+ const repoPath = textField2(value, "repoPath");
10260
10409
  if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
10261
- const sourceRaw = textField(value, "sourceType");
10410
+ const sourceRaw = textField2(value, "sourceType");
10262
10411
  let sourceType;
10263
10412
  if (sourceRaw === "branch" || sourceRaw === "pr" || sourceRaw === "ticket") sourceType = sourceRaw;
10264
10413
  else if (sourceRaw) return "invalid";
10265
- const sourceRef = textField(value, "sourceRef");
10414
+ const sourceRef = textField2(value, "sourceRef");
10266
10415
  if ((sourceType === "pr" || sourceType === "ticket") && !sourceRef) return "invalid";
10267
- const title = textField(value, "title").slice(0, 200);
10416
+ const title = textField2(value, "title").slice(0, 200);
10268
10417
  const cowboy = value.cowboy === true;
10269
10418
  return {
10270
10419
  op: "create",
@@ -10533,14 +10682,22 @@ function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
10533
10682
  emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10534
10683
  return;
10535
10684
  }
10536
- void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query).then((body) => {
10685
+ void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query, cmd.commitSha).then((body) => {
10537
10686
  emit({
10538
10687
  op: "pane",
10539
10688
  chatId: cmd.chatId,
10540
10689
  pane: cmd.pane,
10541
10690
  rows: body.rows,
10542
10691
  ...body.note ? { note: body.note } : {},
10543
- ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {}
10692
+ ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {},
10693
+ ...cmd.pane === "changes" ? {
10694
+ scope: body.scope,
10695
+ commitSha: body.commitSha ?? null,
10696
+ ...body.base ? { base: body.base } : {},
10697
+ hasLastTurnBase: body.hasLastTurnBase,
10698
+ commits: body.commits ?? [],
10699
+ ...body.scopeStats ? { scopeStats: body.scopeStats } : {}
10700
+ } : {}
10544
10701
  });
10545
10702
  });
10546
10703
  return;
@@ -10909,7 +11066,7 @@ function beginCoordinatorPrompt(body, opts) {
10909
11066
  // src/remote/listen.ts
10910
11067
  function emitOpened(opts, opened) {
10911
11068
  setPhoneOpenChat(opened.chat.id);
10912
- emitControl(opts, { op: "opened", ...opened });
11069
+ emitControl(opts, { op: "opened", ...opened, permission: phonePermissionView(opened.chat.id) });
10913
11070
  }
10914
11071
  function publishOpenedChat(opts, chatId) {
10915
11072
  const opened = openPhoneChat(chatId);
@@ -11137,7 +11294,7 @@ function handlePhoneControl(cmd, opts) {
11137
11294
  return;
11138
11295
  }
11139
11296
  if (cmd.view === "diff") {
11140
- void readPhoneChangePatch(thread.id, relative).then((body) => {
11297
+ void readPhoneChangePatch(thread.id, relative, cmd.scope, cmd.commitSha).then((body) => {
11141
11298
  if (!body) {
11142
11299
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file has no changes to show." });
11143
11300
  return;
@@ -11291,6 +11448,7 @@ function runPhonePrompt(opts, cmd, chatId, generation) {
11291
11448
  }
11292
11449
  function handleRemoteInbound(text5, opts) {
11293
11450
  const body = text5.trim();
11451
+ if (dispatchPhonePermission(body, (reply) => emitControl(opts, reply))) return;
11294
11452
  const control = takePhoneControl(body);
11295
11453
  if (control) {
11296
11454
  if (control === "invalid") {
@@ -11365,7 +11523,7 @@ function startPhoneBoardSync(opts) {
11365
11523
  let timer = null;
11366
11524
  let lastKey = "";
11367
11525
  let stopped = false;
11368
- const push = () => {
11526
+ const push2 = () => {
11369
11527
  timer = null;
11370
11528
  if (stopped) return;
11371
11529
  let sidebar;
@@ -11382,7 +11540,7 @@ function startPhoneBoardSync(opts) {
11382
11540
  const schedule = () => {
11383
11541
  if (stopped) return;
11384
11542
  if (timer) clearTimeout(timer);
11385
- timer = setTimeout(push, debounceMs);
11543
+ timer = setTimeout(push2, debounceMs);
11386
11544
  };
11387
11545
  const handle = (event) => {
11388
11546
  if (isPhoneBoardEvent(event)) schedule();
@@ -11392,13 +11550,13 @@ function startPhoneBoardSync(opts) {
11392
11550
  };
11393
11551
  const unsubscribe = (opts.subscribe ?? subscribePhoneBoard)(schedule);
11394
11552
  const offEvents = (opts.events ?? ((listener) => getOrchestrator().on(listener)))(handle);
11395
- push();
11553
+ push2();
11396
11554
  let loginTimer = null;
11397
11555
  const refreshViewerLogin = opts.refreshViewerLogin;
11398
11556
  if (refreshViewerLogin) {
11399
11557
  const afterLogin = () => {
11400
11558
  void refreshViewerLogin().then(() => {
11401
- if (!stopped) push();
11559
+ if (!stopped) push2();
11402
11560
  });
11403
11561
  };
11404
11562
  afterLogin();
@@ -11635,6 +11793,10 @@ function runRemoteHost(opts) {
11635
11793
  }
11636
11794
  send3({ type: "ask_user", text: outbound.text, questions: outbound.questions });
11637
11795
  };
11796
+ setPhonePermissionPush((chatId, prompt) => {
11797
+ if (getPhoneOpenChat() !== chatId) return;
11798
+ emitControl({ onOutbound: emit }, { op: "permission", chatId, prompt });
11799
+ });
11638
11800
  void loop();
11639
11801
  return {
11640
11802
  requestPairingCode() {
@@ -13254,7 +13416,7 @@ async function continueSourceThread(threadId, prompt) {
13254
13416
  await continueOnReply(threadId, prompt);
13255
13417
  return;
13256
13418
  }
13257
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-VDYZSZWY.js");
13419
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-ZQU62CES.js");
13258
13420
  await getOrchestrator2().send(threadId, prompt);
13259
13421
  } catch {
13260
13422
  }
@@ -13696,6 +13858,7 @@ export {
13696
13858
  allocateTeamName,
13697
13859
  allocateTeamSlug,
13698
13860
  annotateDevAccessRuns,
13861
+ answerToolPermission,
13699
13862
  appDataDir,
13700
13863
  appSettingsPath,
13701
13864
  appendCompactedMessages,