wp-studio 1.7.9 → 1.7.10-beta1

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/cli/{_events-ByiJRMQ1.mjs → _events-MbsmeZ64.mjs} +5 -5
  2. package/dist/cli/{certificate-manager-Bp1E0km4.mjs → certificate-manager-SVYcCL_i.mjs} +6 -1
  3. package/dist/cli/{delete-D1lAYFtg.mjs → delete-Br0zEYcv.mjs} +8 -4
  4. package/dist/cli/{helpers-Bh-WikQr.mjs → helpers-DE340RS-.mjs} +4 -153
  5. package/dist/cli/{index-CyYXE85k.mjs → index-4lan3TI_.mjs} +24 -4
  6. package/dist/cli/{index-DBCpWm8k.mjs → index-B-JMTSj2.mjs} +525 -256
  7. package/dist/cli/{index-BiCiEz-r.mjs → index-upH2G_fw.mjs} +510 -365
  8. package/dist/cli/{list-COLca0rr.mjs → list-CqctqyGC.mjs} +4 -3
  9. package/dist/cli/{login-OvZJPTV5.mjs → login-IwZhKWzT.mjs} +3 -3
  10. package/dist/cli/{logout-BaG-QFSN.mjs → logout-BJ7AzNzE.mjs} +3 -3
  11. package/dist/cli/main.mjs +2 -2
  12. package/dist/cli/paths-CqXGLB7R.mjs +195 -0
  13. package/dist/cli/plugin/.claude-plugin/plugin.json +5 -0
  14. package/dist/cli/plugin/skills/need-for-speed/SKILL.md +55 -0
  15. package/dist/cli/plugin/skills/site-spec/SKILL.md +35 -0
  16. package/dist/cli/plugin/skills/taxonomist/SKILL.md +270 -0
  17. package/dist/cli/plugin/skills/taxonomist/scripts/apply-changes.php +223 -0
  18. package/dist/cli/plugin/skills/taxonomist/scripts/backup.php +112 -0
  19. package/dist/cli/plugin/skills/taxonomist/scripts/export-posts.php +119 -0
  20. package/dist/cli/plugin/skills/taxonomist/scripts/restore.php +233 -0
  21. package/dist/cli/process-manager-daemon.mjs +11 -4
  22. package/dist/cli/{process-manager-ipc-heiF195f.mjs → process-manager-ipc-BisO0qtU.mjs} +1 -1
  23. package/dist/cli/proxy-daemon.mjs +1 -1
  24. package/dist/cli/prune-pm-logs-COryxqeo.mjs +41 -0
  25. package/dist/cli/resume-CNesCmkg.mjs +113 -0
  26. package/dist/cli/{rewrite-wp-cli-post-content-DH3hRTU5.mjs → rewrite-wp-cli-post-content-2zlfFnKT.mjs} +1 -1
  27. package/dist/cli/{set-DY9OcXFg.mjs → set-C4J6ru7I.mjs} +3 -3
  28. package/dist/cli/{set-BX9MWFxi.mjs → set-T8A1lBPU.mjs} +4 -4
  29. package/dist/cli/{status-CgY39wpU.mjs → status-CPcjT8jc.mjs} +2 -2
  30. package/dist/cli/{well-known-paths-CG_o9mSO.mjs → well-known-paths-BYA1Bw5o.mjs} +1 -1
  31. package/dist/cli/wordpress-server-child.mjs +2 -2
  32. package/dist/cli/{wp-DeUSBbLc.mjs → wp-A9VDe8QE.mjs} +2 -2
  33. package/dist/cli/wp-files/latest/available-site-translations.json +1 -1
  34. package/dist/cli/wp-files/skills/STUDIO.md +1 -1
  35. package/dist/cli/wp-files/skills/studio-cli/SKILL.md +1 -1
  36. package/package.json +9 -10
  37. package/patches/@mariozechner+pi-tui+0.54.0.patch +12 -0
  38. package/scripts/postinstall-npm.mjs +1 -0
  39. package/dist/cli/paths-BPK_RySX.mjs +0 -31
  40. package/dist/cli/resume-DshNzC7q.mjs +0 -62
@@ -1,19 +1,48 @@
1
- import { S as STUDIO_SITES_ROOT, c as createRemoteSiteTools, a as createStudioTools, r as readAuthToken, b as getSnapshotsFromConfig, i as isSnapshotExpired, d as captureCommandOutput, e as runCommand$3, f as runCommand$4, h as getSitesRunningStatus, j as getWpComSites, k as isSiteRunning, o as openBrowser, l as getSiteUrl, m as closeSharedBrowser } from "./index-BiCiEz-r.mjs";
1
+ import { b as getAiSessionsDirectoryForDate, c as buildAiSessionFileName, l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-CqXGLB7R.mjs";
2
+ import { S as STUDIO_SITES_ROOT, c as createRemoteSiteTools, a as createStudioTools, r as readAuthToken, b as getSnapshotsFromConfig, i as isSnapshotExpired, d as captureCommandOutput, e as runCommand$3, f as runCommand$4, h as chalk, j as getSitesRunningStatus, k as getWpComSites, l as isSiteRunning, o as openBrowser, m as getSiteUrl, n as closeSharedBrowser } from "./index-upH2G_fw.mjs";
2
3
  import { __, sprintf, _n } from "@wordpress/i18n";
3
4
  import fs__default from "fs";
4
5
  import path__default from "path";
5
6
  import { query } from "@anthropic-ai/claude-agent-sdk";
6
7
  import os from "os";
8
+ import { l as lockCliConfig, r as readCliConfig, s as saveCliConfig, u as unlockCliConfig, g as updateCliConfigWithPartial } from "./certificate-manager-SVYcCL_i.mjs";
7
9
  import { password } from "@inquirer/prompts";
8
- import { r as readCliConfig, g as updateCliConfigWithPartial } from "./certificate-manager-Bp1E0km4.mjs";
9
- import { L as LoggerError, d as Logger, s as setProgressCallback } from "./well-known-paths-CG_o9mSO.mjs";
10
+ import { L as LoggerError, d as Logger, s as setProgressCallback } from "./well-known-paths-BYA1Bw5o.mjs";
10
11
  import crypto from "crypto";
11
12
  import fs from "fs/promises";
12
- import { g as getAiSessionsDirectoryForDate, b as buildAiSessionFileName } from "./paths-BPK_RySX.mjs";
13
- import { runCommand as runCommand$1 } from "./login-OvZJPTV5.mjs";
14
- import { runCommand as runCommand$2 } from "./logout-BaG-QFSN.mjs";
13
+ import { runCommand as runCommand$1 } from "./login-IwZhKWzT.mjs";
14
+ import { runCommand as runCommand$2 } from "./logout-BJ7AzNzE.mjs";
15
15
  import { ProcessTerminal, TUI, Container, Loader, CombinedAutocompleteProvider, isKeyRelease, matchesKey, Text, SelectList, Input, Markdown, Editor, visibleWidth, CURSOR_MARKER, truncateToWidth } from "@mariozechner/pi-tui";
16
- import chalk from "chalk";
16
+ const AI_MODELS = {
17
+ "claude-sonnet-4-6": "Sonnet 4.6",
18
+ "claude-opus-4-6": "Opus 4.6",
19
+ "claude-opus-4-7": "Opus 4.7"
20
+ };
21
+ const DEFAULT_MODEL = "claude-sonnet-4-6";
22
+ function isAiModelId(value) {
23
+ return Object.prototype.hasOwnProperty.call(AI_MODELS, value);
24
+ }
25
+ async function readApprovedPermissions() {
26
+ const config = await readCliConfig();
27
+ return config.approvedPermissions ?? [];
28
+ }
29
+ async function addApprovedPermission(entry) {
30
+ try {
31
+ await lockCliConfig();
32
+ const config = await readCliConfig();
33
+ const existing = config.approvedPermissions ?? [];
34
+ const alreadyPresent = existing.some(
35
+ (item) => item.toolName === entry.toolName && item.approvalPath === entry.approvalPath
36
+ );
37
+ if (alreadyPresent) {
38
+ return;
39
+ }
40
+ config.approvedPermissions = [...existing, entry];
41
+ await saveCliConfig(config);
42
+ } finally {
43
+ await unlockCliConfig();
44
+ }
45
+ }
17
46
  const ALLOWED_TOOLS = [
18
47
  "mcp__studio__*",
19
48
  "Read",
@@ -25,21 +54,10 @@ const ALLOWED_TOOLS = [
25
54
  "NotebookRead",
26
55
  "AskUserQuestion"
27
56
  ];
28
- const ALLOWED_TOOLS_REMOTE = [
29
- "mcp__studio__*",
30
- "Read",
31
- "Glob",
32
- "Grep",
33
- "WebFetch",
34
- "WebSearch",
35
- "TodoRead",
36
- "NotebookRead",
37
- "AskUserQuestion"
38
- ];
39
57
  const PATH_GATED_TOOLS = ["Write", "Edit", "Bash", "NotebookEdit"];
40
58
  const PATH_INPUT_KEYS = ["path", "file_path", "filePath"];
41
59
  const APPROVE_ONCE_LABEL = "Allow once";
42
- const APPROVE_SESSION_LABEL = "Allow for this session";
60
+ const APPROVE_ALWAYS_LABEL = "Allow always";
43
61
  const DENY_LABEL = "Deny";
44
62
  const STUDIO_ROOT = path__default.resolve(STUDIO_SITES_ROOT);
45
63
  const TMP_ROOT = path__default.resolve(os.tmpdir());
@@ -111,6 +129,19 @@ function createPathApprovalSession() {
111
129
  }
112
130
  };
113
131
  }
132
+ const defaultApprovalSession = createPathApprovalSession();
133
+ let primePromise = null;
134
+ function primeDefaultApprovalSession() {
135
+ if (!primePromise) {
136
+ primePromise = (async () => {
137
+ const entries = await readApprovedPermissions();
138
+ for (const { toolName, approvalPath } of entries) {
139
+ defaultApprovalSession.rememberApprovedPath(toolName, approvalPath);
140
+ }
141
+ })();
142
+ }
143
+ return primePromise;
144
+ }
114
145
  function getPathGatedPermissionRequest({
115
146
  toolName,
116
147
  input,
@@ -147,8 +178,8 @@ async function askForPathGatedToolApproval({
147
178
  description: `Run ${toolName} outside trusted directories for this step.`
148
179
  },
149
180
  {
150
- label: APPROVE_SESSION_LABEL,
151
- description: `Allow this kind of ${toolName} action for the rest of this session.`
181
+ label: APPROVE_ALWAYS_LABEL,
182
+ description: `Remember this choice and stop asking for ${toolName} on this path.`
152
183
  },
153
184
  {
154
185
  label: DENY_LABEL,
@@ -160,8 +191,8 @@ async function askForPathGatedToolApproval({
160
191
  if (answers[question] === APPROVE_ONCE_LABEL) {
161
192
  return "allow_once";
162
193
  }
163
- if (answers[question] === APPROVE_SESSION_LABEL) {
164
- return "allow_session";
194
+ if (answers[question] === APPROVE_ALWAYS_LABEL) {
195
+ return "allow_always";
165
196
  }
166
197
  return "deny";
167
198
  }
@@ -170,8 +201,11 @@ async function promptForApproval({
170
201
  input,
171
202
  metadata,
172
203
  onAskUser,
173
- pathApprovalSession: pathApprovalSession2
204
+ pathApprovalSession = defaultApprovalSession
174
205
  }) {
206
+ if (pathApprovalSession === defaultApprovalSession) {
207
+ await primeDefaultApprovalSession();
208
+ }
175
209
  const permissionRequest = getPathGatedPermissionRequest({
176
210
  toolName,
177
211
  input,
@@ -179,7 +213,7 @@ async function promptForApproval({
179
213
  suggestions: metadata?.suggestions
180
214
  });
181
215
  if (permissionRequest) {
182
- if (!pathApprovalSession2.hasApprovedPath(toolName, permissionRequest.approvalPath)) {
216
+ if (!pathApprovalSession.hasApprovedPath(toolName, permissionRequest.approvalPath)) {
183
217
  const approvalDecision = await askForPathGatedToolApproval({
184
218
  toolName,
185
219
  outsidePath: permissionRequest.approvalPath,
@@ -191,8 +225,12 @@ async function promptForApproval({
191
225
  message: ACCESS_DENIED_MESSAGE
192
226
  };
193
227
  }
194
- if (approvalDecision === "allow_session") {
195
- pathApprovalSession2.rememberApprovedPath(toolName, permissionRequest.approvalPath);
228
+ if (approvalDecision === "allow_always") {
229
+ pathApprovalSession.rememberApprovedPath(toolName, permissionRequest.approvalPath);
230
+ await addApprovedPermission({
231
+ toolName,
232
+ approvalPath: permissionRequest.approvalPath
233
+ });
196
234
  }
197
235
  }
198
236
  return {
@@ -411,14 +449,12 @@ Interpret creatively and make unexpected choices that feel genuinely designed fo
411
449
  **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
412
450
 
413
451
  Remember: You are capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.`;
414
- const AI_MODELS = {
415
- "claude-sonnet-4-6": "Sonnet 4.6",
416
- "claude-opus-4-6": "Opus 4.6"
417
- };
418
- const DEFAULT_MODEL = "claude-sonnet-4-6";
419
- const pathApprovalSession = createPathApprovalSession();
452
+ const SDK_INTERRUPT_CLEANUP_ERRORS = [
453
+ "Query closed",
454
+ "ProcessTransport is not ready for writing"
455
+ ];
420
456
  process.on("unhandledRejection", (reason) => {
421
- if (reason instanceof Error && reason.message.includes("Query closed")) {
457
+ if (reason instanceof Error && SDK_INTERRUPT_CLEANUP_ERRORS.some((msg) => reason.message.includes(msg))) {
422
458
  return;
423
459
  }
424
460
  throw reason;
@@ -440,7 +476,7 @@ function startAiAgent(config) {
440
476
  const mcpServers = {
441
477
  studio: isRemoteSite ? createRemoteSiteTools(wpcomAccessToken, activeSite.wpcomSiteId) : createStudioTools()
442
478
  };
443
- const allowedTools = isRemoteSite ? [...ALLOWED_TOOLS_REMOTE] : [...ALLOWED_TOOLS];
479
+ const allowedTools = [...ALLOWED_TOOLS];
444
480
  const systemPromptOptions = isRemoteSite ? {
445
481
  remoteSite: {
446
482
  name: activeSite.name,
@@ -485,13 +521,7 @@ function startAiAgent(config) {
485
521
  updatedInput: { ...input, answers }
486
522
  };
487
523
  }
488
- return promptForApproval({
489
- toolName,
490
- input,
491
- metadata,
492
- onAskUser,
493
- pathApprovalSession
494
- });
524
+ return promptForApproval({ toolName, input, metadata, onAskUser });
495
525
  },
496
526
  plugins: [{ type: "local", path: path__default.resolve(import.meta.dirname, "plugin") }],
497
527
  model,
@@ -536,6 +566,12 @@ async function hasValidWpcomAuth() {
536
566
  const token = await readAuthToken();
537
567
  return token !== null;
538
568
  }
569
+ function readInlineWpcomToken() {
570
+ return process.env.STUDIO_WPCOM_TOKEN?.trim() || null;
571
+ }
572
+ function hasInlineWpcomAuth() {
573
+ return readInlineWpcomToken() !== null;
574
+ }
539
575
  function createBaseEnvironment() {
540
576
  const env = { ...process.env };
541
577
  delete env.ANTHROPIC_API_KEY;
@@ -543,6 +579,9 @@ function createBaseEnvironment() {
543
579
  delete env.ANTHROPIC_BASE_URL;
544
580
  delete env.ANTHROPIC_CUSTOM_HEADERS;
545
581
  delete env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS;
582
+ if (!env.CLAUDE_CODE_MAX_RETRIES) {
583
+ env.CLAUDE_CODE_MAX_RETRIES = "1";
584
+ }
546
585
  return env;
547
586
  }
548
587
  const AI_PROVIDER_DEFINITIONS = {
@@ -550,21 +589,22 @@ const AI_PROVIDER_DEFINITIONS = {
550
589
  id: "wpcom",
551
590
  autoFallbackWhenUnavailable: true,
552
591
  isVisible: async () => true,
553
- isReady: hasValidWpcomAuth,
592
+ isReady: async () => hasInlineWpcomAuth() || await hasValidWpcomAuth(),
554
593
  prepare: async () => {
555
- if (await hasValidWpcomAuth()) {
594
+ if (hasInlineWpcomAuth() || await hasValidWpcomAuth()) {
556
595
  return;
557
596
  }
558
597
  throw new LoggerError(__("WordPress.com login required. Use /login to authenticate."));
559
598
  },
560
599
  resolveEnv: async () => {
561
- const token = await readAuthToken();
562
- if (!token) {
600
+ const inlineToken = readInlineWpcomToken();
601
+ const accessToken = inlineToken ?? (await readAuthToken())?.accessToken;
602
+ if (!accessToken) {
563
603
  throw new LoggerError(__("WordPress.com login required. Use /login to authenticate."));
564
604
  }
565
605
  const env = createBaseEnvironment();
566
606
  env.ANTHROPIC_BASE_URL = getWpcomAiGatewayBaseUrl();
567
- env.ANTHROPIC_AUTH_TOKEN = token.accessToken;
607
+ env.ANTHROPIC_AUTH_TOKEN = accessToken;
568
608
  env.ANTHROPIC_CUSTOM_HEADERS = buildAnthropicCustomHeaders({
569
609
  "X-WPCOM-AI-Feature": WPCOM_AI_FEATURE_HEADER
570
610
  });
@@ -633,6 +673,9 @@ async function resolveUnavailableAiProvider(provider) {
633
673
  return getPreferredReadyProvider(provider);
634
674
  }
635
675
  async function resolveInitialAiProvider() {
676
+ if (hasInlineWpcomAuth()) {
677
+ return "wpcom";
678
+ }
636
679
  const { aiProvider: savedProvider } = await readCliConfig();
637
680
  if (savedProvider) {
638
681
  const definition = getAiProviderDefinition(savedProvider);
@@ -659,6 +702,10 @@ async function resolveAiEnvironment(provider) {
659
702
  return getAiProviderDefinition(provider).resolveEnv();
660
703
  }
661
704
  function emitEvent(event) {
705
+ if (typeof process.send === "function") {
706
+ process.send(event);
707
+ return;
708
+ }
662
709
  process.stdout.write(JSON.stringify(event) + "\n");
663
710
  }
664
711
  class JsonAdapter {
@@ -669,10 +716,25 @@ class JsonAdapter {
669
716
  this.onSiteSelected = null;
670
717
  this.onInterrupt = null;
671
718
  this.onBeforeExit = null;
719
+ this.permissionResponse = null;
720
+ this.ipcMessageListener = null;
672
721
  }
673
722
  start() {
723
+ if (typeof process.send !== "function") {
724
+ return;
725
+ }
726
+ this.ipcMessageListener = (message) => {
727
+ if (message && typeof message === "object" && message.type === "interrupt") {
728
+ this.onInterrupt?.();
729
+ }
730
+ };
731
+ process.on("message", this.ipcMessageListener);
674
732
  }
675
733
  stop() {
734
+ if (this.ipcMessageListener) {
735
+ process.off("message", this.ipcMessageListener);
736
+ this.ipcMessageListener = null;
737
+ }
676
738
  }
677
739
  showWelcome() {
678
740
  }
@@ -696,7 +758,7 @@ class JsonAdapter {
696
758
  setStatusMessage() {
697
759
  }
698
760
  setLoaderMessage(message, _update) {
699
- emitEvent({ type: "progress", timestamp: (/* @__PURE__ */ new Date()).toISOString(), message });
761
+ this.showProgress(message);
700
762
  }
701
763
  beginAgentTurn() {
702
764
  emitEvent({ type: "turn.started", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -720,7 +782,7 @@ class JsonAdapter {
720
782
  return {
721
783
  type: "result",
722
784
  sessionId: message.session_id,
723
- success: message.subtype === "success"
785
+ success: !message.is_error
724
786
  };
725
787
  }
726
788
  return void 0;
@@ -738,6 +800,11 @@ class JsonAdapter {
738
800
  throw new Error("waitForInput is not available in JSON mode");
739
801
  }
740
802
  async askUser(questions) {
803
+ if (this.permissionResponse) {
804
+ const response = this.permissionResponse;
805
+ this.permissionResponse = null;
806
+ return response;
807
+ }
741
808
  emitEvent({
742
809
  type: "question.asked",
743
810
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -746,6 +813,18 @@ class JsonAdapter {
746
813
  options: q.options
747
814
  }))
748
815
  });
816
+ if (typeof process.send === "function") {
817
+ return new Promise((resolve) => {
818
+ const onMessage = (message) => {
819
+ if (message && typeof message === "object" && message.type === "answer") {
820
+ const answers = message.answers;
821
+ process.off("message", onMessage);
822
+ resolve(answers ?? {});
823
+ }
824
+ };
825
+ process.on("message", onMessage);
826
+ });
827
+ }
749
828
  this.emitTurnCompleted("paused");
750
829
  await this.onBeforeExit?.();
751
830
  process.exitCode = 0;
@@ -759,9 +838,6 @@ class JsonAdapter {
759
838
  function isAiProviderId(value) {
760
839
  return Object.prototype.hasOwnProperty.call(AI_PROVIDERS, value);
761
840
  }
762
- function isAiModelId(value) {
763
- return Object.prototype.hasOwnProperty.call(AI_MODELS, value);
764
- }
765
841
  function resolveResumeSessionContext(resumeSession) {
766
842
  if (!resumeSession) {
767
843
  return {};
@@ -779,6 +855,11 @@ function resolveResumeSessionContext(resumeSession) {
779
855
  context.sessionId = sessionId;
780
856
  }
781
857
  }
858
+ if (!context.model && event.type === "session.model_selected") {
859
+ if (isAiModelId(event.model)) {
860
+ context.model = event.model;
861
+ }
862
+ }
782
863
  if (event.type === "session.context") {
783
864
  if (!context.provider && isAiProviderId(event.provider)) {
784
865
  context.provider = event.provider;
@@ -857,6 +938,15 @@ class AiSessionRecorder {
857
938
  wpcomSiteId: site.wpcomSiteId
858
939
  });
859
940
  }
941
+ async recordEnvironmentSelected(payload) {
942
+ await this.appendEvent({
943
+ type: "environment.selected",
944
+ timestamp: toIsoTimestamp(),
945
+ environment: payload.environment,
946
+ url: payload.url,
947
+ wpcomSiteId: payload.wpcomSiteId
948
+ });
949
+ }
860
950
  async recordUserMessage(options) {
861
951
  await this.appendEvent({
862
952
  type: "user.message",
@@ -911,17 +1001,21 @@ class AiSessionRecorder {
911
1001
  });
912
1002
  }
913
1003
  }
914
- function replaySessionHistory(ui, events) {
915
- ui.prepareForReplay();
916
- let isTurnOpen = false;
917
- let lastClearedIndex = -1;
1004
+ function filterEventsAfterLastClear(events) {
918
1005
  for (let i = events.length - 1; i >= 0; i--) {
919
1006
  if (events[i].type === "session.cleared") {
920
- lastClearedIndex = i;
921
- break;
1007
+ return events.slice(i + 1);
922
1008
  }
923
1009
  }
924
- const eventsToReplay = lastClearedIndex >= 0 ? events.slice(lastClearedIndex + 1) : events;
1010
+ return events;
1011
+ }
1012
+ function isVisibleUserMessage(event) {
1013
+ return event.source === "prompt";
1014
+ }
1015
+ function replaySessionHistory(ui, events) {
1016
+ ui.prepareForReplay();
1017
+ let isTurnOpen = false;
1018
+ const eventsToReplay = filterEventsAfterLastClear(events);
925
1019
  try {
926
1020
  for (const event of eventsToReplay) {
927
1021
  ui.setReplayTimestamp(event.timestamp);
@@ -932,14 +1026,34 @@ function replaySessionHistory(ui, events) {
932
1026
  path: event.sitePath,
933
1027
  running: false,
934
1028
  remote: event.remote === true,
935
- url: typeof event.url === "string" ? event.url : void 0
1029
+ url: typeof event.url === "string" ? event.url : void 0,
1030
+ wpcomSiteId: typeof event.wpcomSiteId === "number" ? event.wpcomSiteId : void 0
1031
+ },
1032
+ { announce: true, emitEvent: false }
1033
+ );
1034
+ continue;
1035
+ }
1036
+ if (event.type === "environment.selected") {
1037
+ const current = ui.activeSite;
1038
+ if (!current) {
1039
+ continue;
1040
+ }
1041
+ const isLive = event.environment === "live";
1042
+ ui.setActiveSite(
1043
+ {
1044
+ name: current.name,
1045
+ path: current.path,
1046
+ running: current.running,
1047
+ remote: isLive,
1048
+ url: isLive ? event.url : void 0,
1049
+ wpcomSiteId: isLive ? event.wpcomSiteId : void 0
936
1050
  },
937
1051
  { announce: true, emitEvent: false }
938
1052
  );
939
1053
  continue;
940
1054
  }
941
1055
  if (event.type === "user.message") {
942
- if (event.source === "ask_user") {
1056
+ if (!isVisibleUserMessage(event)) {
943
1057
  continue;
944
1058
  }
945
1059
  if (isTurnOpen) {
@@ -1216,6 +1330,137 @@ const AI_CHAT_SLASH_COMMANDS = [
1216
1330
  { name: "taxonomist", description: __("Optimize category taxonomy with AI") },
1217
1331
  { name: "need-for-speed", description: __("Run a performance audit on a site") }
1218
1332
  ];
1333
+ const THINKING_MESSAGES = [
1334
+ "Thinking…",
1335
+ "Iterating…",
1336
+ "Interpolating…",
1337
+ "Philosophising…",
1338
+ "Cogitating…",
1339
+ "Poetizing…",
1340
+ "Sketching…",
1341
+ "Scribbling…",
1342
+ "Drafting…",
1343
+ "Harmonizing…",
1344
+ "Rehearsing…",
1345
+ "Combabulating…",
1346
+ "Conjectureing…",
1347
+ "Tinkering…",
1348
+ "Polishing…",
1349
+ "Concocting…",
1350
+ "Wizarding…",
1351
+ "Enchanting…",
1352
+ "Transmuting…",
1353
+ "Summoning…",
1354
+ "Gutenberging…",
1355
+ "Hooking…",
1356
+ "Filtering…",
1357
+ "Looping…",
1358
+ "Codexing…",
1359
+ "Annotating…",
1360
+ "Ruminating…",
1361
+ "Paragraphing…",
1362
+ "Typesetting…",
1363
+ "Soloing…",
1364
+ "Compiling…",
1365
+ "Abstracting…",
1366
+ "Meandering…",
1367
+ "Daydreaming…",
1368
+ "Riffing…",
1369
+ "Wandering…",
1370
+ "Introspecting…",
1371
+ "Experiencing…",
1372
+ "Reflecting…",
1373
+ "Adventuring…",
1374
+ "Levitating…",
1375
+ "Glueing…",
1376
+ "Soaring…",
1377
+ "Gliding…",
1378
+ "Paragliding…",
1379
+ "Excavating…",
1380
+ "Planting…",
1381
+ "Stargazing…",
1382
+ "Scribing…"
1383
+ ];
1384
+ function randomThinkingMessage() {
1385
+ return THINKING_MESSAGES[Math.floor(Math.random() * THINKING_MESSAGES.length)];
1386
+ }
1387
+ function getToolDisplayName(name) {
1388
+ const displayNames = {
1389
+ mcp__studio__site_create: __("Create site"),
1390
+ mcp__studio__site_list: __("List sites"),
1391
+ mcp__studio__site_info: __("Get site info"),
1392
+ mcp__studio__site_start: __("Start site"),
1393
+ mcp__studio__site_stop: __("Stop site"),
1394
+ mcp__studio__site_delete: __("Delete site"),
1395
+ mcp__studio__preview_create: __("Create preview"),
1396
+ mcp__studio__preview_list: __("List previews"),
1397
+ mcp__studio__preview_update: __("Update preview"),
1398
+ mcp__studio__preview_delete: __("Delete preview"),
1399
+ mcp__studio__wp_cli: __("Run WP-CLI"),
1400
+ mcp__studio__validate_blocks: __("Validate blocks"),
1401
+ mcp__studio__take_screenshot: __("Take screenshot"),
1402
+ Read: __("Read"),
1403
+ Write: __("Write"),
1404
+ Edit: __("Edit"),
1405
+ Bash: __("Run"),
1406
+ Glob: __("Search"),
1407
+ Grep: __("Search"),
1408
+ Skill: __("Load skill"),
1409
+ Task: __("Run task"),
1410
+ TodoWrite: __("Update todo list")
1411
+ };
1412
+ return displayNames[name] ?? name;
1413
+ }
1414
+ const BASH_DETAIL_MAX_LENGTH = 60;
1415
+ function getToolDetail(name, input) {
1416
+ if (!input) {
1417
+ return "";
1418
+ }
1419
+ switch (name) {
1420
+ case "mcp__studio__site_create":
1421
+ return typeof input.name === "string" ? input.name : "";
1422
+ case "mcp__studio__site_info":
1423
+ case "mcp__studio__site_start":
1424
+ case "mcp__studio__site_stop":
1425
+ case "mcp__studio__site_delete":
1426
+ case "mcp__studio__preview_create":
1427
+ case "mcp__studio__preview_list":
1428
+ return typeof input.nameOrPath === "string" ? input.nameOrPath : "";
1429
+ case "mcp__studio__preview_update":
1430
+ case "mcp__studio__preview_delete":
1431
+ return typeof input.host === "string" ? input.host : "";
1432
+ case "mcp__studio__wp_cli":
1433
+ return typeof input.command === "string" ? `wp ${input.command}` : "";
1434
+ case "mcp__studio__validate_blocks":
1435
+ if (typeof input.filePath === "string") {
1436
+ return input.filePath.split("/").slice(-2).join("/");
1437
+ }
1438
+ return __("inline content");
1439
+ case "mcp__studio__take_screenshot":
1440
+ return typeof input.url === "string" ? input.url : "";
1441
+ case "Read":
1442
+ case "Write":
1443
+ case "Edit": {
1444
+ const filePath = input.file_path ?? input.path;
1445
+ if (typeof filePath === "string") {
1446
+ return filePath.split("/").slice(-2).join("/");
1447
+ }
1448
+ return "";
1449
+ }
1450
+ case "Bash":
1451
+ if (typeof input.command !== "string") {
1452
+ return "";
1453
+ }
1454
+ return input.command.length > BASH_DETAIL_MAX_LENGTH ? input.command.slice(0, BASH_DETAIL_MAX_LENGTH - 3) + "…" : input.command;
1455
+ case "Skill":
1456
+ return typeof input.skill === "string" ? input.skill : "";
1457
+ case "Grep":
1458
+ case "Glob":
1459
+ return typeof input.pattern === "string" ? input.pattern : "";
1460
+ default:
1461
+ return "";
1462
+ }
1463
+ }
1219
1464
  function formatTodoSnapshotLine(todo) {
1220
1465
  switch (todo.status) {
1221
1466
  case "completed":
@@ -1341,6 +1586,13 @@ const DEFAULT_COLLAPSE_THRESHOLD_LINES = 5;
1341
1586
  function formatToolOutputLines(lines) {
1342
1587
  return lines.map((line, index2) => `${index2 === 0 ? " " + chalk.dim("⎿ ") : " "}${line}`).join("\n");
1343
1588
  }
1589
+ function formatQueuedPrompt(text) {
1590
+ const lines = text.split("\n");
1591
+ return lines.map((line, i) => {
1592
+ const body = i === 0 ? "↳ " + line + " " : " " + line + " ";
1593
+ return " " + chalk.bgHex("#e8eef5").hex("#5a6b7d")(body);
1594
+ }).join("\n");
1595
+ }
1344
1596
  class PromptEditor {
1345
1597
  constructor(tui, theme, options) {
1346
1598
  this._focused = false;
@@ -1466,83 +1718,13 @@ const editorTheme = {
1466
1718
  noMatch: (text) => chalk.dim(text)
1467
1719
  }
1468
1720
  };
1469
- const toolDisplayNames = {
1470
- mcp__studio__site_create: __("Create site"),
1471
- mcp__studio__site_list: __("List sites"),
1472
- mcp__studio__site_info: __("Get site info"),
1473
- mcp__studio__site_start: __("Start site"),
1474
- mcp__studio__site_stop: __("Stop site"),
1475
- mcp__studio__site_delete: __("Delete site"),
1476
- mcp__studio__preview_create: __("Create preview"),
1477
- mcp__studio__preview_list: __("List previews"),
1478
- mcp__studio__preview_update: __("Update preview"),
1479
- mcp__studio__preview_delete: __("Delete preview"),
1480
- mcp__studio__wp_cli: __("Run WP-CLI"),
1481
- mcp__studio__validate_blocks: __("Validate blocks"),
1482
- mcp__studio__take_screenshot: __("Take screenshot"),
1483
- Read: __("Read"),
1484
- Write: __("Write"),
1485
- Edit: __("Edit"),
1486
- Bash: __("Run"),
1487
- Glob: __("Search"),
1488
- Grep: __("Search"),
1489
- Skill: __("Load skill"),
1490
- Task: __("Run task"),
1491
- TodoWrite: __("Update todo list")
1492
- };
1493
- function getToolDetail(name, input) {
1494
- switch (name) {
1495
- case "mcp__studio__site_create":
1496
- return typeof input.name === "string" ? input.name : "";
1497
- case "mcp__studio__site_info":
1498
- case "mcp__studio__site_start":
1499
- case "mcp__studio__site_stop":
1500
- case "mcp__studio__site_delete":
1501
- case "mcp__studio__preview_create":
1502
- case "mcp__studio__preview_list":
1503
- return typeof input.nameOrPath === "string" ? input.nameOrPath : "";
1504
- case "mcp__studio__preview_update":
1505
- case "mcp__studio__preview_delete":
1506
- return typeof input.host === "string" ? input.host : "";
1507
- case "mcp__studio__wp_cli":
1508
- return typeof input.command === "string" ? `wp ${input.command}` : "";
1509
- case "mcp__studio__validate_blocks":
1510
- if (typeof input.filePath === "string") {
1511
- return input.filePath.split("/").slice(-2).join("/");
1512
- }
1513
- return __("inline content");
1514
- case "mcp__studio__take_screenshot":
1515
- return typeof input.url === "string" ? input.url : "";
1516
- case "Read":
1517
- case "Write":
1518
- case "Edit": {
1519
- const filePath = input.file_path ?? input.path;
1520
- if (typeof filePath === "string") {
1521
- const parts = filePath.split("/");
1522
- return parts.slice(-2).join("/");
1523
- }
1524
- return "";
1525
- }
1526
- case "Bash":
1527
- return typeof input.command === "string" ? input.command.length > 60 ? input.command.slice(0, 57) + "…" : input.command : "";
1528
- case "Skill":
1529
- return typeof input.skill === "string" ? input.skill : "";
1530
- case "Grep":
1531
- case "Glob":
1532
- return typeof input.pattern === "string" ? input.pattern : "";
1533
- default:
1534
- return "";
1535
- }
1536
- }
1537
1721
  function formatToolName(name, input) {
1538
- const displayName = toolDisplayNames[name] ?? name;
1539
- if (input) {
1540
- const detail = getToolDetail(name, input);
1541
- if (detail) {
1542
- return chalk.bold(displayName) + " " + chalk.dim("(" + detail + ")");
1543
- }
1722
+ const displayName = chalk.bold(getToolDisplayName(name));
1723
+ const detail = getToolDetail(name, input);
1724
+ if (detail) {
1725
+ return displayName + " " + chalk.dim("(" + detail + ")");
1544
1726
  }
1545
- return chalk.bold(displayName);
1727
+ return displayName;
1546
1728
  }
1547
1729
  function isTodoWriteInput(input) {
1548
1730
  if (!input || typeof input !== "object" || !Array.isArray(input.todos)) {
@@ -1602,6 +1784,7 @@ function normalizeToolUseResult(result) {
1602
1784
  }
1603
1785
  class AiChatUI {
1604
1786
  constructor() {
1787
+ this.queuedPrompts = [];
1605
1788
  this.currentResponseText = "";
1606
1789
  this.currentMarkdown = null;
1607
1790
  this.submitResolve = null;
@@ -1631,58 +1814,6 @@ class AiChatUI {
1631
1814
  this.pendingToolCalls = /* @__PURE__ */ new Map();
1632
1815
  this.currentModel = DEFAULT_MODEL;
1633
1816
  this.currentProvider = DEFAULT_AI_PROVIDER;
1634
- this.thinkingMessages = [
1635
- "Thinking…",
1636
- "Iterating…",
1637
- "Interpolating…",
1638
- "Philosophising…",
1639
- "Cogitating…",
1640
- "Poetizing…",
1641
- "Sketching…",
1642
- "Scribbling…",
1643
- "Drafting…",
1644
- "Harmonizing…",
1645
- "Rehearsing…",
1646
- "Combabulating…",
1647
- "Conjectureing…",
1648
- "Tinkering…",
1649
- "Polishing…",
1650
- "Concocting…",
1651
- "Wizarding…",
1652
- "Enchanting…",
1653
- "Transmuting…",
1654
- "Summoning…",
1655
- "Gutenberging…",
1656
- "Hooking…",
1657
- "Filtering…",
1658
- "Looping…",
1659
- "Codexing…",
1660
- "Annotating…",
1661
- "Ruminating…",
1662
- "Paragraphing…",
1663
- "Typesetting…",
1664
- "Soloing…",
1665
- "Compiling…",
1666
- "Abstracting…",
1667
- "Meandering…",
1668
- "Daydreaming…",
1669
- "Riffing…",
1670
- "Wandering…",
1671
- "Introspecting…",
1672
- "Experiencing…",
1673
- "Reflecting…",
1674
- "Adventuring…",
1675
- "Levitating…",
1676
- "Glueing…",
1677
- "Soaring…",
1678
- "Gliding…",
1679
- "Paragliding…",
1680
- "Excavating…",
1681
- "Planting…",
1682
- "Stargazing…",
1683
- "Scribing…",
1684
- "Levitating…"
1685
- ];
1686
1817
  this.optionPickerContainer = null;
1687
1818
  this.optionPickerSelectList = null;
1688
1819
  this.optionPickerVisible = false;
@@ -1708,6 +1839,8 @@ class AiChatUI {
1708
1839
  this.tui = new TUI(terminal, true);
1709
1840
  this.messages = new Container();
1710
1841
  this.tui.addChild(this.messages);
1842
+ this.queuedContainer = new Container();
1843
+ this.tui.addChild(this.queuedContainer);
1711
1844
  this.loader = new Loader(
1712
1845
  this.tui,
1713
1846
  (str) => chalk.yellow(str),
@@ -1744,10 +1877,19 @@ class AiChatUI {
1744
1877
  );
1745
1878
  this.editor.onSubmit = (text) => {
1746
1879
  const trimmed = text.trim();
1747
- if (trimmed && this.submitResolve) {
1880
+ if (!trimmed) {
1881
+ return;
1882
+ }
1883
+ if (this.submitResolve) {
1748
1884
  const resolve = this.submitResolve;
1749
1885
  this.submitResolve = null;
1750
1886
  resolve(trimmed);
1887
+ return;
1888
+ }
1889
+ if (this._inAgentTurn) {
1890
+ this.queuedPrompts.push(trimmed);
1891
+ this.editor.setText("");
1892
+ this.renderQueuedContainer();
1751
1893
  }
1752
1894
  };
1753
1895
  this.tui.addInputListener((data) => {
@@ -1829,6 +1971,11 @@ class AiChatUI {
1829
1971
  this.renderSitePicker();
1830
1972
  return { consume: true };
1831
1973
  }
1974
+ if (matchesKey(data, "backspace") && this.editorVisible && this.queuedPrompts.length > 0 && this.editor.getText() === "") {
1975
+ this.queuedPrompts.pop();
1976
+ this.renderQueuedContainer();
1977
+ return { consume: true };
1978
+ }
1832
1979
  if (matchesKey(data, "escape") && this.interruptCallback) {
1833
1980
  this.wasInterrupted = true;
1834
1981
  this.interruptCallback();
@@ -1840,9 +1987,6 @@ class AiChatUI {
1840
1987
  return void 0;
1841
1988
  });
1842
1989
  }
1843
- randomThinkingMessage() {
1844
- return this.thinkingMessages[Math.floor(Math.random() * this.thinkingMessages.length)];
1845
- }
1846
1990
  static {
1847
1991
  this.OTHER_VALUE = "__other__";
1848
1992
  }
@@ -1897,6 +2041,10 @@ class AiChatUI {
1897
2041
  this.currentMarkdown = null;
1898
2042
  this.currentResponseText = "";
1899
2043
  this.messages.clear();
2044
+ if (this.queuedPrompts.length > 0) {
2045
+ this.queuedPrompts = [];
2046
+ this.renderQueuedContainer();
2047
+ }
1900
2048
  this.tui.requestRender();
1901
2049
  }
1902
2050
  showAgentQuestion(question, _options) {
@@ -2300,12 +2448,12 @@ ${chalk.dim(message)}
2300
2448
  this.tui.start();
2301
2449
  }
2302
2450
  showWelcome() {
2303
- const version = "1.7.9";
2451
+ const version = "1.7.10-beta1";
2304
2452
  const cwd = process.cwd();
2305
2453
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2306
2454
  const displayCwd = home && cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd;
2307
2455
  const b = chalk.blue;
2308
- const logo = [
2456
+ const logoLines = [
2309
2457
  " ▄█▛▀▀▀▀█▙▖",
2310
2458
  " ▗▟█ ▗██▄",
2311
2459
  "▄███▛ ▝▜██ ▝███▙",
@@ -2314,21 +2462,37 @@ ${chalk.dim(message)}
2314
2462
  "▀▙▖ ▜█▄▟ ▝█▙▄▌ ▄▛",
2315
2463
  " ▝▜▄▝██▌ ▀██▗▟▀",
2316
2464
  " ▀██▙▄▄▄█▛▘"
2317
- ].map((s) => b(s));
2465
+ ];
2466
+ const logo = logoLines.map((s) => b(s));
2467
+ const logoWidth = Math.max(...logoLines.map((s) => s.length));
2468
+ const gap = 4;
2469
+ const leading = 1;
2470
+ const termWidth = process.stdout.columns ?? 80;
2471
+ const availableInfoWidth = Math.max(0, termWidth - leading - logoWidth - gap);
2472
+ const baseInfo = `${AI_MODELS[this.currentModel]} · ${AI_PROVIDERS[this.currentProvider]}`;
2473
+ const sep = " · ";
2474
+ let secondLine;
2475
+ if (baseInfo.length + sep.length + displayCwd.length <= availableInfoWidth) {
2476
+ secondLine = `${baseInfo}${sep}${displayCwd}`;
2477
+ } else {
2478
+ const pathBudget = availableInfoWidth - baseInfo.length - sep.length;
2479
+ if (pathBudget >= 4) {
2480
+ secondLine = `${baseInfo}${sep}…${displayCwd.slice(-(pathBudget - 1))}`;
2481
+ } else {
2482
+ secondLine = baseInfo;
2483
+ }
2484
+ }
2318
2485
  const info = [
2319
2486
  chalk.bold("WordPress Studio") + chalk.dim(` v${version}`),
2320
- chalk.dim(
2321
- `${AI_MODELS[this.currentModel]} · ${AI_PROVIDERS[this.currentProvider]} · ${displayCwd}`
2322
- ),
2487
+ chalk.dim(secondLine),
2323
2488
  "",
2324
2489
  chalk.dim.italic(__("Code is Poetry"))
2325
2490
  ];
2326
- const gap = 4;
2327
2491
  const infoStartRow = Math.max(0, Math.floor((logo.length - info.length) / 2));
2328
2492
  const lines = logo.map((logoLine, i) => {
2329
2493
  const infoIndex = i - infoStartRow;
2330
2494
  const infoText = infoIndex >= 0 && infoIndex < info.length ? info[infoIndex] : "";
2331
- return " " + logoLine + " ".repeat(gap) + infoText;
2495
+ return " ".repeat(leading) + logoLine + " ".repeat(gap) + infoText;
2332
2496
  });
2333
2497
  this.messages.addChild(new Text("\n" + lines.join("\n") + "\n", 0, 0));
2334
2498
  this.tui.requestRender();
@@ -2344,6 +2508,11 @@ ${chalk.dim(message)}
2344
2508
  this.editor.setText("");
2345
2509
  this.hideLoader();
2346
2510
  this.showEditor();
2511
+ if (this.queuedPrompts.length > 0) {
2512
+ const next = this.queuedPrompts.shift();
2513
+ this.renderQueuedContainer();
2514
+ return Promise.resolve(next);
2515
+ }
2347
2516
  return new Promise((resolve) => {
2348
2517
  this.submitResolve = resolve;
2349
2518
  });
@@ -2359,6 +2528,14 @@ ${chalk.dim(message)}
2359
2528
  this.messages.addChild(new Text("\n" + formatted, 0, 0));
2360
2529
  this.tui.requestRender();
2361
2530
  }
2531
+ renderQueuedContainer() {
2532
+ this.queuedContainer.clear();
2533
+ for (const prompt of this.queuedPrompts) {
2534
+ this.queuedContainer.addChild(new Text("\n" + formatQueuedPrompt(prompt), 0, 0));
2535
+ }
2536
+ this.updateHints();
2537
+ this.tui.requestRender();
2538
+ }
2362
2539
  setLoaderMessage(message, update) {
2363
2540
  if (!message) {
2364
2541
  return;
@@ -2378,7 +2555,9 @@ ${chalk.dim(message)}
2378
2555
  if (wasEditorVisible) {
2379
2556
  this.tui.removeChild(this.editor);
2380
2557
  }
2558
+ this.tui.removeChild(this.queuedContainer);
2381
2559
  this.tui.addChild(this.loader);
2560
+ this.tui.addChild(this.queuedContainer);
2382
2561
  if (wasEditorVisible) {
2383
2562
  this.tui.addChild(this.editor);
2384
2563
  }
@@ -2409,6 +2588,9 @@ ${chalk.dim(message)}
2409
2588
  this.activeExpandablePreview.isExpanded ? __("ctrl+o collapse") : __("ctrl+o expand")
2410
2589
  );
2411
2590
  }
2591
+ if (this.queuedPrompts.length > 0) {
2592
+ hints.push(__("backspace to unqueue"));
2593
+ }
2412
2594
  hints.push(__("esc to interrupt"));
2413
2595
  this.editor.hints = hints;
2414
2596
  }
@@ -2436,7 +2618,7 @@ ${chalk.dim(message)}
2436
2618
  this.editor.setText("");
2437
2619
  this._inAgentTurn = true;
2438
2620
  this.updateHints();
2439
- this.showLoader(this.randomThinkingMessage());
2621
+ this.showLoader(randomThinkingMessage());
2440
2622
  this.currentResponseText = "";
2441
2623
  this.hasShownResponseMarker = false;
2442
2624
  this.wasInterrupted = false;
@@ -2710,7 +2892,7 @@ ${chalk.dim(message)}
2710
2892
  }
2711
2893
  }
2712
2894
  showToolUse(toolLabel) {
2713
- this.showLoader(this.randomThinkingMessage());
2895
+ this.showLoader(randomThinkingMessage());
2714
2896
  this.stopToolDotBlink();
2715
2897
  this.lastProgressText = null;
2716
2898
  this.toolDotLabel = toolLabel;
@@ -2921,7 +3103,7 @@ ${chalk.dim(message)}
2921
3103
  answers[q.question] = answer;
2922
3104
  }
2923
3105
  }
2924
- this.showLoader(this.randomThinkingMessage());
3106
+ this.showLoader(randomThinkingMessage());
2925
3107
  return answers;
2926
3108
  }
2927
3109
  /**
@@ -2982,7 +3164,7 @@ ${chalk.dim(message)}
2982
3164
  }
2983
3165
  }
2984
3166
  if (!this.replayMode && !this.loaderVisible) {
2985
- this.showLoader(this.randomThinkingMessage());
3167
+ this.showLoader(randomThinkingMessage());
2986
3168
  }
2987
3169
  return void 0;
2988
3170
  }
@@ -3006,29 +3188,15 @@ ${chalk.dim(message)}
3006
3188
  }
3007
3189
  case "result": {
3008
3190
  this.hideLoader();
3009
- if (message.subtype === "success") {
3010
- const thinkingSec = Math.round((this.nowMs() - this.turnStartTime) / 1e3);
3011
- if (!this.hasShownResponseMarker) {
3012
- this.messages.addChild(
3013
- new Text("\n " + chalk.blue("⏺") + " " + __("Done"), 0, 0)
3014
- );
3015
- }
3016
- this.showInfo(
3017
- sprintf(
3018
- /* translators: 1: seconds spent thinking, 2: number of turns */
3019
- _n(
3020
- "Thought for %1$ds · %2$d turn",
3021
- "Thought for %1$ds · %2$d turns",
3022
- message.num_turns
3023
- ),
3024
- thinkingSec,
3025
- message.num_turns
3026
- )
3027
- );
3028
- return { type: "result", sessionId: message.session_id, success: true };
3191
+ if (message.subtype === "error_max_turns") {
3192
+ return {
3193
+ type: "max_turns",
3194
+ sessionId: message.session_id,
3195
+ numTurns: message.num_turns
3196
+ };
3029
3197
  }
3030
3198
  if (this.wasInterrupted) {
3031
- const thinkingSec = Math.round((this.nowMs() - this.turnStartTime) / 1e3);
3199
+ const thinkingSec2 = Math.round((this.nowMs() - this.turnStartTime) / 1e3);
3032
3200
  this.messages.addChild(
3033
3201
  new Text(
3034
3202
  "\n " + chalk.yellow("⏺") + " " + chalk.yellow(__("Interrupted")),
@@ -3040,37 +3208,59 @@ ${chalk.dim(message)}
3040
3208
  sprintf(
3041
3209
  /* translators: %d: number of seconds */
3042
3210
  __("Ran for %ds before interruption"),
3043
- thinkingSec
3211
+ thinkingSec2
3044
3212
  )
3045
3213
  );
3046
- return { type: "result", sessionId: message.session_id, success: false };
3047
- }
3048
- const parts = [];
3049
- if ("errors" in message && message.errors?.length) {
3050
- parts.push(...message.errors);
3051
- }
3052
- if (message.subtype === "error_max_turns") {
3053
3214
  return {
3054
- type: "max_turns",
3215
+ type: "result",
3055
3216
  sessionId: message.session_id,
3056
- numTurns: message.num_turns
3217
+ success: false,
3218
+ interrupted: true
3057
3219
  };
3058
- } else if (message.subtype) {
3059
- parts.push(`(${message.subtype})`);
3060
3220
  }
3061
- if ("permission_denials" in message && message.permission_denials?.length) {
3062
- for (const denial of message.permission_denials) {
3063
- parts.push(
3064
- sprintf(
3065
- /* translators: %s: tool name */
3066
- __("Permission denied: %s"),
3067
- denial.tool_name
3068
- )
3069
- );
3221
+ if (message.is_error) {
3222
+ const parts = [];
3223
+ if ("errors" in message && message.errors?.length) {
3224
+ parts.push(...message.errors);
3225
+ }
3226
+ if ("result" in message && typeof message.result === "string" && message.result) {
3227
+ parts.push(message.result);
3228
+ } else if (message.subtype && message.subtype !== "success") {
3229
+ parts.push(`(${message.subtype})`);
3230
+ }
3231
+ if ("permission_denials" in message && message.permission_denials?.length) {
3232
+ for (const denial of message.permission_denials) {
3233
+ parts.push(
3234
+ sprintf(
3235
+ /* translators: %s: tool name */
3236
+ __("Permission denied: %s"),
3237
+ denial.tool_name
3238
+ )
3239
+ );
3240
+ }
3070
3241
  }
3242
+ this.showError(parts.length > 0 ? parts.join("\n") : __("Unknown error"));
3243
+ return { type: "result", sessionId: message.session_id, success: false };
3244
+ }
3245
+ const thinkingSec = Math.round((this.nowMs() - this.turnStartTime) / 1e3);
3246
+ if (!this.hasShownResponseMarker) {
3247
+ this.messages.addChild(
3248
+ new Text("\n " + chalk.blue("⏺") + " " + __("Done"), 0, 0)
3249
+ );
3071
3250
  }
3072
- this.showError(parts.length > 0 ? parts.join("\n") : __("Unknown error"));
3073
- return { type: "result", sessionId: message.session_id, success: false };
3251
+ this.showInfo(
3252
+ sprintf(
3253
+ /* translators: 1: seconds spent thinking, 2: number of turns */
3254
+ _n(
3255
+ "Thought for %1$ds · %2$d turn",
3256
+ "Thought for %1$ds · %2$d turns",
3257
+ message.num_turns
3258
+ ),
3259
+ thinkingSec,
3260
+ message.num_turns
3261
+ )
3262
+ );
3263
+ return { type: "result", sessionId: message.session_id, success: true };
3074
3264
  }
3075
3265
  case "system": {
3076
3266
  if (message.subtype === "status" && message.status === "compacting") {
@@ -3103,6 +3293,16 @@ async function runCommand(options) {
3103
3293
  let currentModel = resumeContext.model ?? DEFAULT_MODEL;
3104
3294
  ui.currentProvider = currentProvider;
3105
3295
  ui.currentModel = currentModel;
3296
+ if (options.activeSite) {
3297
+ ui.activeSite = {
3298
+ name: options.activeSite.name,
3299
+ path: options.activeSite.path,
3300
+ running: options.activeSite.running ?? false,
3301
+ remote: options.activeSite.remote,
3302
+ url: options.activeSite.url,
3303
+ wpcomSiteId: options.activeSite.wpcomSiteId
3304
+ };
3305
+ }
3106
3306
  ui.start();
3107
3307
  ui.showWelcome();
3108
3308
  if (options.showLegacyCommandNotice && !isJsonMode) {
@@ -3110,7 +3310,7 @@ async function runCommand(options) {
3110
3310
  }
3111
3311
  let sessionRecorder;
3112
3312
  let didDisableSessionPersistence = options.noSessionPersistence === true;
3113
- let sessionId = resumeContext.sessionId;
3313
+ let sessionId = options.resumeSessionId ?? resumeContext.sessionId;
3114
3314
  let persistQueue = Promise.resolve();
3115
3315
  if (options.noSessionPersistence) {
3116
3316
  ui.showInfo(__("Session persistence disabled (--no-session-persistence)."));
@@ -3129,6 +3329,25 @@ async function runCommand(options) {
3129
3329
  filePath: options.resumeSession.summary.filePath,
3130
3330
  linkedAgentSessionIds: options.resumeSession.summary.linkedAgentSessionIds
3131
3331
  });
3332
+ } else if (options.resumeSessionId) {
3333
+ const sessions = await listAiSessions(getAiSessionsRootDirectory());
3334
+ const existing = sessions.find(
3335
+ (s) => s.linkedAgentSessionIds.includes(options.resumeSessionId)
3336
+ );
3337
+ if (!existing) {
3338
+ throw new Error(
3339
+ sprintf(
3340
+ /* translators: %s: agent session ID */
3341
+ __("No AI session found for resume ID: %s"),
3342
+ options.resumeSessionId
3343
+ )
3344
+ );
3345
+ }
3346
+ sessionRecorder = await AiSessionRecorder.open({
3347
+ sessionId: existing.id,
3348
+ filePath: existing.filePath,
3349
+ linkedAgentSessionIds: existing.linkedAgentSessionIds
3350
+ });
3132
3351
  } else {
3133
3352
  sessionRecorder = await AiSessionRecorder.create();
3134
3353
  }
@@ -3182,9 +3401,9 @@ async function runCommand(options) {
3182
3401
  })
3183
3402
  );
3184
3403
  }
3185
- setProgressCallback((message, update) => {
3404
+ setProgressCallback((message) => {
3186
3405
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3187
- ui.setLoaderMessage(message, update);
3406
+ ui.setLoaderMessage(message);
3188
3407
  void persist((recorder) => recorder.recordToolProgress(message, timestamp));
3189
3408
  });
3190
3409
  ui.onSiteSelected = (site) => {
@@ -3366,7 +3585,9 @@ async function runCommand(options) {
3366
3585
  }
3367
3586
  return answers;
3368
3587
  }
3369
- async function runAgentTurn(prompt) {
3588
+ const MAX_RETRY_ATTEMPTS = 4;
3589
+ async function runAgentTurn(prompt, retryAttempt = 0) {
3590
+ await maybeAutoSwitchProvider();
3370
3591
  const env = await resolveAiEnvironment(currentProvider);
3371
3592
  ui.beginAgentTurn();
3372
3593
  let enrichedPrompt = prompt;
@@ -3421,6 +3642,8 @@ ${prompt}`;
3421
3642
  numTurns: result.numTurns
3422
3643
  };
3423
3644
  turnStatus = "max_turns";
3645
+ } else if (result.interrupted) {
3646
+ turnStatus = "interrupted";
3424
3647
  } else {
3425
3648
  turnStatus = result.success ? "success" : "error";
3426
3649
  }
@@ -3428,7 +3651,10 @@ ${prompt}`;
3428
3651
  }
3429
3652
  } catch (error) {
3430
3653
  turnStatus = "error";
3431
- throw error;
3654
+ if (isJsonMode) {
3655
+ throw error;
3656
+ }
3657
+ ui.showError(getErrorMessage(error));
3432
3658
  } finally {
3433
3659
  await persist((recorder) => recorder.recordTurnClosed(turnStatus));
3434
3660
  ui.endAgentTurn();
@@ -3456,6 +3682,32 @@ ${prompt}`;
3456
3682
  return runAgentTurn("Continue from where you left off.");
3457
3683
  }
3458
3684
  }
3685
+ if (turnStatus === "error" && !isJsonMode) {
3686
+ if (retryAttempt >= MAX_RETRY_ATTEMPTS) {
3687
+ ui.showInfo(
3688
+ __("The server has not recovered after multiple attempts. Please try again later.")
3689
+ );
3690
+ } else {
3691
+ const answer = await ui.askUser([
3692
+ {
3693
+ question: __("There was a hiccup on the server. Do you want to continue?"),
3694
+ options: [
3695
+ { label: "Yes", description: __("Continue from where you left off") },
3696
+ {
3697
+ label: "No",
3698
+ description: __("Stop so I can give different instructions")
3699
+ }
3700
+ ]
3701
+ }
3702
+ ]);
3703
+ const choice = Object.values(answer)[0]?.toLowerCase();
3704
+ if (choice === "yes") {
3705
+ ui.showInfo(__("Retrying…"));
3706
+ const retryPrompt = sessionId ? "Continue from where you left off." : prompt;
3707
+ return runAgentTurn(retryPrompt, retryAttempt + 1);
3708
+ }
3709
+ }
3710
+ }
3459
3711
  return {
3460
3712
  status: turnStatus,
3461
3713
  usage: maxTurnsResult
@@ -3535,7 +3787,6 @@ ${prompt}`;
3535
3787
  break;
3536
3788
  }
3537
3789
  } else {
3538
- await maybeAutoSwitchProvider();
3539
3790
  ui.addUserMessage(prompt);
3540
3791
  try {
3541
3792
  await runAgentTurn(`Run the /${cmd.name} skill using the Skill tool.`);
@@ -3545,7 +3796,6 @@ ${prompt}`;
3545
3796
  }
3546
3797
  continue;
3547
3798
  }
3548
- await maybeAutoSwitchProvider();
3549
3799
  ui.addUserMessage(prompt);
3550
3800
  try {
3551
3801
  await runAgentTurn(prompt);
@@ -3569,6 +3819,10 @@ const registerCommand = (yargs) => {
3569
3819
  description: __("Initial message to send to the AI agent")
3570
3820
  }).option("path", {
3571
3821
  hidden: true
3822
+ }).option("site-name", {
3823
+ type: "string",
3824
+ hidden: true,
3825
+ description: __("Name of the active WordPress site")
3572
3826
  }).option("json", {
3573
3827
  type: "boolean",
3574
3828
  default: false,
@@ -3576,6 +3830,14 @@ const registerCommand = (yargs) => {
3576
3830
  }).option("auto-approve", {
3577
3831
  type: "boolean",
3578
3832
  description: __("Auto-approve all tool calls (defaults to true in --json mode)")
3833
+ }).option("resume-session", {
3834
+ type: "string",
3835
+ hidden: true,
3836
+ description: __("SDK session ID to resume (for JSON mode multi-turn)")
3837
+ }).option("permission-response", {
3838
+ type: "string",
3839
+ hidden: true,
3840
+ description: __("JSON-encoded permission response for a paused session")
3579
3841
  }).option("session-persistence", {
3580
3842
  type: "boolean",
3581
3843
  default: true,
@@ -3592,12 +3854,18 @@ const registerCommand = (yargs) => {
3592
3854
  const typedArgv = argv;
3593
3855
  const noSessionPersistence = typedArgv.sessionPersistence === false;
3594
3856
  const adapter = typedArgv.json ? new JsonAdapter() : new AiChatUI();
3857
+ if (adapter instanceof JsonAdapter && typedArgv.permissionResponse) {
3858
+ adapter.permissionResponse = JSON.parse(typedArgv.permissionResponse);
3859
+ }
3860
+ const sitePath = typeof argv.path === "string" ? argv.path : void 0;
3595
3861
  await runCommand({
3596
3862
  adapter,
3597
3863
  initialMessage: typedArgv.message,
3864
+ resumeSessionId: typedArgv.resumeSession,
3598
3865
  noSessionPersistence,
3599
3866
  autoApprove: typedArgv.autoApprove,
3600
- showLegacyCommandNotice: argv._[0] === "ai"
3867
+ showLegacyCommandNotice: argv._[0] === "ai",
3868
+ activeSite: sitePath && typedArgv.siteName ? { name: typedArgv.siteName, path: sitePath } : void 0
3601
3869
  });
3602
3870
  } catch (error) {
3603
3871
  if (error instanceof LoggerError) {
@@ -3617,6 +3885,7 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
3617
3885
  }, Symbol.toStringTag, { value: "Module" }));
3618
3886
  export {
3619
3887
  AiChatUI as A,
3888
+ JsonAdapter as J,
3620
3889
  index as i,
3621
3890
  runCommand as r
3622
3891
  };