ue-mcp 1.3.2 → 1.3.4

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 (36) hide show
  1. package/dist/dialog-guard.d.ts +287 -0
  2. package/dist/dialog-guard.js +700 -0
  3. package/dist/dialog-guard.js.map +1 -0
  4. package/dist/editor-control.d.ts +77 -3
  5. package/dist/editor-control.js +163 -108
  6. package/dist/editor-control.js.map +1 -1
  7. package/dist/epic-cache.d.ts +12 -0
  8. package/dist/epic-cache.js +13 -0
  9. package/dist/epic-cache.js.map +1 -1
  10. package/dist/flow/guarded-bridge.d.ts +44 -10
  11. package/dist/flow/guarded-bridge.js +88 -20
  12. package/dist/flow/guarded-bridge.js.map +1 -1
  13. package/dist/flow/http-server.js +47 -4
  14. package/dist/flow/http-server.js.map +1 -1
  15. package/dist/flow/task-factory.js +41 -0
  16. package/dist/flow/task-factory.js.map +1 -1
  17. package/dist/flow/task-guards.js +17 -1
  18. package/dist/flow/task-guards.js.map +1 -1
  19. package/dist/index.js +201 -16
  20. package/dist/index.js.map +1 -1
  21. package/dist/locking.js +15 -0
  22. package/dist/locking.js.map +1 -1
  23. package/dist/offline.js +11 -2
  24. package/dist/offline.js.map +1 -1
  25. package/dist/session.js +8 -1
  26. package/dist/session.js.map +1 -1
  27. package/dist/tool-counts.json +2 -2
  28. package/dist/tools/editor.js +27 -5
  29. package/dist/tools/editor.js.map +1 -1
  30. package/package.json +2 -2
  31. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +1 -1
  32. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +1 -1
  33. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +1 -1
  34. package/dist/dialog-gate.d.ts +0 -48
  35. package/dist/dialog-gate.js +0 -93
  36. package/dist/dialog-gate.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-guard.js","sourceRoot":"","sources":["../src/dialog-guard.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { type ProjectContext } from "./project.js";
2
2
  import { type EngineState } from "./engine-observer.js";
3
3
  import { type DialogMode } from "./user-state.js";
4
+ import { DialogGuard } from "./dialog-guard.js";
4
5
  import type { ElicitFn, ProgressFn } from "./types.js";
5
6
  /**
6
7
  * Is anything answering on this port? Exported so a session can be reported as
@@ -31,7 +32,19 @@ export interface ReadyResult {
31
32
  * waits for the snapshot to say `ready` and reports the whole startup as a
32
33
  * progress bar rather than handing control back early.
33
34
  */
34
- export declare function waitForEditorReadyExternal(projectPath: string, projectDir: string, maxWaitSeconds?: number, launchedAtMs?: number): Promise<ReadyResult>;
35
+ export declare function waitForEditorReady(projectPath: string | null | undefined, projectDir: string | undefined, maxWaitSeconds: number, opts?: {
36
+ showProgress?: boolean;
37
+ onProgress?: ProgressFn;
38
+ launchedAtMs?: number;
39
+ /**
40
+ * Whether a dialog seen during startup is reported with the call that
41
+ * presses each button. False under defer, whose whole promise is that the
42
+ * report is for recognition and carries nothing that actuates. Defaults to
43
+ * true: a direct start_editor has no mode to consult, and the report is the
44
+ * same under every mode there.
45
+ */
46
+ pressCalls?: boolean;
47
+ }): Promise<ReadyResult>;
35
48
  /**
36
49
  * Decide the next progress update, or null when there is nothing new to send.
37
50
  *
@@ -98,7 +111,7 @@ export declare function startEditor(project: ProjectContext, timeoutSeconds?: nu
98
111
  */
99
112
  dialogPolicy?: string;
100
113
  /**
101
- * Arm the bridge's parameter echo for this editor. The live tier's leak
114
+ * Arm the bridge's parameter echo for this editor. The live tests' leak
102
115
  * assertions, which prove a routing key never reaches an editor, can only
103
116
  * run when the editor was LAUNCHED with it: it is read at startup, so
104
117
  * turning it on over the socket afterwards is too late, exactly like the
@@ -218,6 +231,17 @@ export declare function resolveDialogMode(opts: {
218
231
  canElicit: boolean;
219
232
  env?: NodeJS.ProcessEnv;
220
233
  }): ResolvedDialogMode;
234
+ /**
235
+ * What the caller is told to do about a dialog nothing pressed, per mode.
236
+ *
237
+ * Every branch here ends with the dialog still up and no button pressed. The
238
+ * modes differ in who is being asked to deal with it, which is the whole point
239
+ * of having them, so each says that plainly rather than leaving the reader to
240
+ * infer it from a mode name.
241
+ */
242
+ export declare function dialogModeGuidance(resolved: ResolvedDialogMode, canElicit: boolean, opts?: {
243
+ quitSent?: boolean;
244
+ }): string;
221
245
  /**
222
246
  * What happened to the button the user picked.
223
247
  *
@@ -296,7 +320,23 @@ export interface StopEditorResult {
296
320
  */
297
321
  alreadyStopped?: boolean;
298
322
  /** Why the stop refused, for a caller that would rather branch than parse. */
299
- refusedReason?: "unsaved-work" | "blocking-dialog" | "unknown-dirty-state";
323
+ refusedReason?: "unsaved-work" | "blocking-dialog" | "unknown-dirty-state" | "unknown-dialog-state";
324
+ /**
325
+ * The guard's refusal fields, so a caller gets the SAME shape here as from
326
+ * every other route. This used to assemble its own object carrying only
327
+ * `dialogBlocking`, leaving the rest undefined depending on which route did
328
+ * the refusing. Built by DialogGuard.describeRefusal.
329
+ */
330
+ dialogBlocking?: true;
331
+ refusedMethod?: string;
332
+ dialogTitle?: string;
333
+ dialogMessage?: string;
334
+ buttons?: string[];
335
+ choices?: Array<{
336
+ buttonLabel: string;
337
+ respondWith: string;
338
+ }>;
339
+ error?: string;
300
340
  /** Every package that was dirty when the stop was asked for. */
301
341
  dirtyPackages?: string[];
302
342
  /** The dialog holding the editor, reported whole so a person can answer it. */
@@ -321,6 +361,16 @@ export interface StopEditorResult {
321
361
  * editor(stop_editor) and a caller cannot see which dialog handling mode
322
362
  * applied if the fields are dropped on the way out.
323
363
  */
364
+ /**
365
+ * The stop half's account of a dialog, forwarded onto the restart's result.
366
+ *
367
+ * Both the record of WHAT HAPPENED (mode, source, the dialog, any press) and
368
+ * the guard's refusal fields travel. Copying only the former dropped
369
+ * dialogBlocking, refusedMethod, dialogTitle, dialogMessage and error, so a
370
+ * restart refused by a dialog came back unrecognisable to isDialogRefusal and
371
+ * to every client branching on that flag.
372
+ */
373
+ export declare function restartDialogAccount(stopResult: Partial<StopEditorResult>): Partial<RestartEditorResult>;
324
374
  export interface RestartEditorResult {
325
375
  success: boolean;
326
376
  message: string;
@@ -332,6 +382,22 @@ export interface RestartEditorResult {
332
382
  dialogPressUnconfirmed?: string;
333
383
  dialogMode?: DialogMode;
334
384
  dialogModeSource?: string;
385
+ /**
386
+ * The guard's refusal fields, carried through from the stop half. Copying
387
+ * only the account of what happened dropped these, so a restart refused by a
388
+ * dialog was unrecognisable to isDialogRefusal.
389
+ */
390
+ dialogBlocking?: true;
391
+ refusedReason?: "unsaved-work" | "blocking-dialog" | "unknown-dirty-state" | "unknown-dialog-state";
392
+ refusedMethod?: string;
393
+ dialogTitle?: string;
394
+ dialogMessage?: string;
395
+ buttons?: string[];
396
+ choices?: Array<{
397
+ buttonLabel: string;
398
+ respondWith: string;
399
+ }>;
400
+ error?: string;
335
401
  }
336
402
  /**
337
403
  * Stop the editor by asking it to quit ITSELF through the bridge. ue-mcp NEVER
@@ -370,6 +436,13 @@ export interface RestartEditorResult {
370
436
  export declare function stopEditor(projectDir?: string, opts?: {
371
437
  /** Ask the connected client's user, when it advertised elicitation. */
372
438
  elicit?: ElicitFn;
439
+ /**
440
+ * The editor's own guard. Passed so this route shares the ONE guard per
441
+ * editor rather than building a second with its own ask-once state, which
442
+ * put a second form in front of the person for a dialog the first had
443
+ * already asked about.
444
+ */
445
+ guard?: DialogGuard;
373
446
  /**
374
447
  * How long each poll of the confirm wait sleeps, in milliseconds.
375
448
  *
@@ -384,6 +457,7 @@ export declare function restartEditor(project: ProjectContext, bridge?: {
384
457
  connect: (timeoutMs?: number) => Promise<void>;
385
458
  }, opts?: {
386
459
  elicit?: ElicitFn;
460
+ guard?: DialogGuard;
387
461
  }): Promise<RestartEditorResult>;
388
462
  export interface BuildResult {
389
463
  success: boolean;
@@ -10,6 +10,7 @@ import { editorOwnsProject, findEditorByPid, findInteractiveEditors, readEngineS
10
10
  import { findLiveInstanceRecord, isPidAlive, lockfileIsFromThisLaunch, resolveBridgeTarget } from "./editor-target.js";
11
11
  import { startProgress } from "./ui/progress.js";
12
12
  import { getDialogMode, getUserStatePath } from "./user-state.js";
13
+ import { DialogGuard } from "./dialog-guard.js";
13
14
  // Process control is cross-platform: the editor binary path and the running-
14
15
  // process probe differ per OS, and stopping goes through the bridge (#790).
15
16
  const IS_WINDOWS = process.platform === "win32";
@@ -176,10 +177,7 @@ const WINDOW_PROBE_INTERVAL_MS = 60_000;
176
177
  * waits for the snapshot to say `ready` and reports the whole startup as a
177
178
  * progress bar rather than handing control back early.
178
179
  */
179
- export async function waitForEditorReadyExternal(projectPath, projectDir, maxWaitSeconds = 300, launchedAtMs) {
180
- return waitForEditorReady(projectPath, projectDir, maxWaitSeconds, { launchedAtMs });
181
- }
182
- async function waitForEditorReady(projectPath, projectDir, maxWaitSeconds, opts = {}) {
180
+ export async function waitForEditorReady(projectPath, projectDir, maxWaitSeconds, opts = {}) {
183
181
  const startTime = Date.now();
184
182
  const maxWaitMs = maxWaitSeconds * 1000;
185
183
  const timeline = [];
@@ -291,7 +289,11 @@ async function waitForEditorReady(projectPath, projectDir, maxWaitSeconds, opts
291
289
  message: snapshot.modal.message ?? "",
292
290
  buttons,
293
291
  choices: buttons.map((label) => ({ buttonLabel: label, respondWith: respondCallFor(label) })),
294
- }, { pressCalls: opts.pressCalls !== false }),
292
+ },
293
+ // Forwarded, not defaulted: describeBlockingDialog applies the
294
+ // default, and a second copy of it here is a second place for the
295
+ // rule to drift away from the guard's.
296
+ { pressCalls: opts.pressCalls }),
295
297
  state: await readEngineState(projectPath ?? null),
296
298
  });
297
299
  }
@@ -683,14 +685,14 @@ function respondCallFor(label) {
683
685
  ? `editor(action='respond_to_dialog', buttonLabel="${label}")`
684
686
  : `editor(action='respond_to_dialog', buttonLabel='${label}')`;
685
687
  }
686
- /** The dialog blocking this editor, or null when nothing is. */
688
+ const UNREADABLE = { dialog: null, readable: false };
687
689
  async function readBlockingDialog(port, host) {
688
690
  const reply = await callBridgeOnce(port, "list_dialogs", {}, host);
689
691
  if (!accepted(reply) || !reply.result)
690
- return null;
692
+ return UNREADABLE;
691
693
  const dialogs = reply.result.dialogs;
692
694
  if (!Array.isArray(dialogs) || dialogs.length === 0)
693
- return null;
695
+ return { dialog: null, readable: true };
694
696
  const first = dialogs[0];
695
697
  const buttons = Array.isArray(first.buttons)
696
698
  ? first.buttons.filter((b) => typeof b === "string" && b !== "")
@@ -704,10 +706,13 @@ async function readBlockingDialog(port, host) {
704
706
  };
705
707
  });
706
708
  return {
707
- title: typeof first.title === "string" ? first.title : "",
708
- message: typeof first.message === "string" ? first.message : "",
709
- buttons,
710
- choices,
709
+ readable: true,
710
+ dialog: {
711
+ title: typeof first.title === "string" ? first.title : "",
712
+ message: typeof first.message === "string" ? first.message : "",
713
+ buttons,
714
+ choices,
715
+ },
711
716
  };
712
717
  }
713
718
  /**
@@ -830,7 +835,13 @@ export function resolveDialogMode(opts) {
830
835
  * of having them, so each says that plainly rather than leaving the reader to
831
836
  * infer it from a mode name.
832
837
  */
833
- function dialogModeGuidance(resolved, canElicit, opts = {}) {
838
+ export function dialogModeGuidance(resolved, canElicit, opts = {}) {
839
+ // What the mode MEANS is the guard's answer, not a second reading of it
840
+ // here. This branched on the configured mode and re-derived the "interactive
841
+ // with nobody to ask behaves as defer" rule inline, which is one more place
842
+ // for it to drift away from the rule the refusal is actually built from.
843
+ const effective = DialogGuard.effectiveMode(resolved.mode, canElicit);
844
+ const downgraded = effective !== resolved.mode;
834
845
  const applied = `Dialog handling mode: ${resolved.mode} (${resolved.source}).`;
835
846
  // Whether a quit went out is a fact about the call, and this text is reused
836
847
  // on both sides of it: before the quit, where nothing was asked to close, and
@@ -838,89 +849,56 @@ function dialogModeGuidance(resolved, canElicit, opts = {}) {
838
849
  const quitState = opts.quitSent
839
850
  ? "The quit went out before this dialog appeared and the editor has not closed."
840
851
  : "The editor was not asked to quit.";
841
- if (resolved.mode === "auto") {
852
+ const again = "then call editor(action='stop_editor') again.";
853
+ if (effective === "auto") {
842
854
  return (`${applied} Nothing was pressed for you: in auto mode the whole dialog is handed back and the ` +
843
- "choice is yours. Press the button you choose with the call listed beside it, then call " +
844
- "editor(action='stop_editor') again.");
855
+ `choice is yours. Press the button you choose with the call listed beside it, ${again}`);
856
+ }
857
+ if (effective === "interactive") {
858
+ return `${applied} ${quitState} Answer the dialog, ${again}`;
845
859
  }
846
- if (resolved.mode === "interactive" && !canElicit) {
860
+ if (downgraded) {
847
861
  return (`${applied} Interactive mode shows this dialog to the user in an MCP elicitation form, and this ` +
848
862
  "client did not advertise that capability, so no form could be shown and nothing was pressed. " +
849
863
  "Answer the dialog in the Unreal Editor window, or set UE_MCP_DIALOG_MODE=auto to answer it from " +
850
- "here with editor(action='respond_to_dialog'), then call editor(action='stop_editor') again.");
851
- }
852
- if (resolved.mode === "interactive") {
853
- return `${applied} ${quitState} Answer the dialog, then call editor(action='stop_editor') again.`;
864
+ `here with editor(action='respond_to_dialog'), ${again}`);
854
865
  }
855
866
  return (`${applied} Nothing was pressed and nothing was asked of you: in defer mode a blocking dialog is ` +
856
- "left alone. Go to the Unreal Editor window and answer the dialog quoted above yourself, then call " +
857
- "editor(action='stop_editor') again.");
867
+ `left alone. Go to the Unreal Editor window and answer the dialog quoted above yourself, ${again}`);
858
868
  }
859
869
  /**
860
- * Put the dialog to the person, through MCP elicitation, and press whichever
861
- * button they choose.
870
+ * Put the dialog to the person and press what they choose.
862
871
  *
863
- * This is the one path that ends with a button being pressed without a separate
864
- * respond_to_dialog call, and it presses only what the user picked from the
865
- * dialog's own labels, in a prompt carrying its full text. Declining leaves the
866
- * dialog exactly where it is.
872
+ * The decision, the form and the press all belong to DialogGuard: this only
873
+ * supplies the transport, because a quit may already be in flight and the
874
+ * session's own bridge is not usable then. Keeping a second copy of the
875
+ * interactive logic here is what let the two disagree.
867
876
  *
868
- * Returns null ONLY when nothing was sent, or when the editor answered saying
869
- * it pressed nothing. A send whose outcome is unknown comes back as a press
870
- * with confirmed: false, because that is what it is.
877
+ * Returns the button pressed, or null when nothing was.
871
878
  */
872
- async function askUserToAnswerDialog(port, host, dialog, elicit) {
873
- if (dialog.choices.length === 0)
874
- return null;
875
- const LEAVE_OPEN = "Leave the dialog open";
876
- let answer;
877
- try {
878
- answer = await elicit({
879
- message: [
880
- "Unreal Editor is blocked on a modal dialog and is waiting for an answer.",
881
- "",
882
- `Title: ${dialog.title}`,
883
- dialog.message === "" ? "(no message text)" : dialog.message,
884
- "",
885
- "Choose the button to press. Nothing is pressed unless you choose it.",
886
- ].join("\n"),
887
- requestedSchema: {
888
- type: "object",
889
- properties: {
890
- button: {
891
- type: "string",
892
- title: "Button",
893
- description: "The dialog's own buttons, in the order it lays them out.",
894
- enum: [...dialog.buttons, LEAVE_OPEN],
895
- },
896
- },
897
- required: ["button"],
898
- },
899
- });
900
- }
901
- catch {
902
- // No elicitation UI, or the prompt failed. The dialog stays up and the
903
- // report is what the caller gets.
904
- return null;
905
- }
906
- if (answer.action !== "accept")
907
- return null;
908
- const chosen = answer.content?.button;
909
- if (typeof chosen !== "string" || chosen === LEAVE_OPEN)
910
- return null;
911
- if (!dialog.buttons.includes(chosen))
912
- return null;
913
- const pressed = await callBridgeOnce(port, "respond_to_dialog", { buttonLabel: chosen }, host);
914
- if (accepted(pressed))
915
- return { button: chosen, confirmed: true };
916
- // The bridge answered and said it did not press: an unregistered method on an
917
- // older plugin build, or a handler that refused. Nothing happened to the
918
- // dialog, and "nothing was sent" is the honest report.
919
- if (pressed.answered)
920
- return null;
921
- // It did not answer at all. The frame was already on the wire, so whether the
922
- // button was pressed is genuinely unknown and must not be reported either way.
923
- return { button: chosen, confirmed: false };
879
+ async function askUserToAnswerDialog(port, host, dialog, elicit, mode, canElicit, sessionGuard) {
880
+ // The editor's own guard when the caller had one, so its ask-once record and
881
+ // its knowledge of what is on screen are shared rather than duplicated. The
882
+ // fallback exists for callers with no session in hand.
883
+ const guard = sessionGuard ?? new DialogGuard({
884
+ // The resolved mode, not a hardcoded one. Hardcoding it here meant this
885
+ // route ignored what the machine asked for and elicited under auto and
886
+ // defer too.
887
+ mode: () => mode,
888
+ probe: () => callBridgeOnce(port, "list_dialogs", {}, host),
889
+ press: (buttonLabel) => callBridgeOnce(port, "respond_to_dialog", { buttonLabel }, host),
890
+ elicit: () => elicit,
891
+ });
892
+ const decision = await guard.decideFor("editor.stop_editor", {
893
+ title: dialog.title,
894
+ message: dialog.message,
895
+ buttons: dialog.buttons,
896
+ choices: (dialog.choices ?? []).filter((c) => typeof c.respondWith === "string"),
897
+ }, { canElicit });
898
+ // An unconfirmed press refuses (the guard cannot prove the way is clear) but
899
+ // still has to be reported, so read the outcome rather than the decision.
900
+ void decision;
901
+ return guard.lastPressed;
924
902
  }
925
903
  /**
926
904
  * What is holding the editor open, in the words of the thing holding it.
@@ -1054,6 +1032,42 @@ export async function resolveOwnedEditor(projectDir, projectPath) {
1054
1032
  "for this project republishes it.",
1055
1033
  };
1056
1034
  }
1035
+ /**
1036
+ * What a restart returns. It is the start's result plus the stop half's account
1037
+ * of any dialog met on the way, because the restart's stop is a full
1038
+ * editor(stop_editor) and a caller cannot see which dialog handling mode
1039
+ * applied if the fields are dropped on the way out.
1040
+ */
1041
+ /**
1042
+ * The stop half's account of a dialog, forwarded onto the restart's result.
1043
+ *
1044
+ * Both the record of WHAT HAPPENED (mode, source, the dialog, any press) and
1045
+ * the guard's refusal fields travel. Copying only the former dropped
1046
+ * dialogBlocking, refusedMethod, dialogTitle, dialogMessage and error, so a
1047
+ * restart refused by a dialog came back unrecognisable to isDialogRefusal and
1048
+ * to every client branching on that flag.
1049
+ */
1050
+ export function restartDialogAccount(stopResult) {
1051
+ return {
1052
+ ...(stopResult.dialogBlocking ? { dialogBlocking: stopResult.dialogBlocking } : {}),
1053
+ ...(stopResult.refusedMethod ? { refusedMethod: stopResult.refusedMethod } : {}),
1054
+ ...(stopResult.dialogTitle ? { dialogTitle: stopResult.dialogTitle } : {}),
1055
+ ...(stopResult.dialogMessage !== undefined ? { dialogMessage: stopResult.dialogMessage } : {}),
1056
+ ...(stopResult.buttons ? { buttons: stopResult.buttons } : {}),
1057
+ ...(stopResult.choices ? { choices: stopResult.choices } : {}),
1058
+ ...(stopResult.error ? { error: stopResult.error } : {}),
1059
+ ...(stopResult.refusedReason ? { refusedReason: stopResult.refusedReason } : {}),
1060
+ ...(stopResult.dialogMode ? { dialogMode: stopResult.dialogMode } : {}),
1061
+ ...(stopResult.dialogModeSource ? { dialogModeSource: stopResult.dialogModeSource } : {}),
1062
+ ...(stopResult.blockingDialog ? { blockingDialog: stopResult.blockingDialog } : {}),
1063
+ ...(stopResult.dialogAnsweredByUser
1064
+ ? { dialogAnsweredByUser: stopResult.dialogAnsweredByUser }
1065
+ : {}),
1066
+ ...(stopResult.dialogPressUnconfirmed
1067
+ ? { dialogPressUnconfirmed: stopResult.dialogPressUnconfirmed }
1068
+ : {}),
1069
+ };
1070
+ }
1057
1071
  /**
1058
1072
  * What this tool will and will not do to a process that is not closing.
1059
1073
  *
@@ -1155,7 +1169,13 @@ export async function stopEditor(projectDir, opts = {}) {
1155
1169
  * every button label in order - and nothing that actuates it.
1156
1170
  */
1157
1171
  const dialogMode = resolveDialogMode({ projectDir, canElicit });
1158
- const pressCalls = dialogMode.mode !== "defer";
1172
+ // The guard owns this rule. Computing it here as well meant the same
1173
+ // decision existed twice and the two renderings disagreed about what defer
1174
+ // strips.
1175
+ // canElicit matters here too: interactive with nobody to ask handed the
1176
+ // press calls over anyway, which is the leak defer exists to close, on the
1177
+ // mode that promises a person chooses.
1178
+ const pressCalls = DialogGuard.handsOverPressCalls(dialogMode.mode, canElicit);
1159
1179
  const ownership = await resolveOwnedEditor(projectDir, projectPath);
1160
1180
  if (!ownership.owned) {
1161
1181
  // No editor was closed, so the verdict is false on every branch here. What
@@ -1201,7 +1221,22 @@ export async function stopEditor(projectDir, opts = {}) {
1201
1221
  // while the game thread is parked inside the modal loop, which is exactly the
1202
1222
  // state where sending a quit would time out and teach nobody anything. Ask
1203
1223
  // before sending, and report the whole question rather than acting on it.
1204
- let dialog = await readBlockingDialog(port, host);
1224
+ const firstRead = await readBlockingDialog(port, host);
1225
+ if (!firstRead.readable) {
1226
+ // The same rule readDirtyPackages already follows: a question that cannot
1227
+ // be answered is not an answer of "clean". Sending a quit here is how an
1228
+ // editor sitting on a modal got one anyway, under every mode including
1229
+ // defer, because a slow reply was reported as no dialog at all.
1230
+ return {
1231
+ success: false,
1232
+ refusedReason: "unknown-dialog-state",
1233
+ message: "The editor's bridge did not answer when asked whether a dialog is blocking it, so whether "
1234
+ + "one is on screen cannot be established and stopping could dismiss it. Nothing was asked "
1235
+ + "to quit. Read it with editor(action='list_dialogs'), or close the editor yourself and "
1236
+ + "answer whatever it asks.",
1237
+ };
1238
+ }
1239
+ let dialog = firstRead.dialog;
1205
1240
  let answeredByUser;
1206
1241
  /** A press that went out with no answer. Not an answer, and not silence. */
1207
1242
  let unconfirmedPress;
@@ -1226,11 +1261,11 @@ export async function stopEditor(projectDir, opts = {}) {
1226
1261
  }
1227
1262
  : {};
1228
1263
  if (dialog) {
1229
- // Interactive is the ONLY mode that reaches a button, and only through the
1230
- // user's own pick in the elicitation form. auto and defer both leave the
1231
- // dialog exactly where it is.
1232
- if (dialogMode.mode === "interactive" && canElicit && opts.elicit) {
1233
- const press = await askUserToAnswerDialog(port, host, dialog, opts.elicit);
1264
+ // Always ask the guard. It applies the mode, so branching on the mode here
1265
+ // as well was a second copy of that decision and the two could disagree.
1266
+ // auto and defer press nothing; interactive puts it to the person.
1267
+ if (opts.elicit) {
1268
+ const press = await askUserToAnswerDialog(port, host, dialog, opts.elicit, dialogMode.mode, canElicit, opts.guard);
1234
1269
  if (press !== null) {
1235
1270
  if (press.confirmed)
1236
1271
  answeredByUser = press.button;
@@ -1239,11 +1274,26 @@ export async function stopEditor(projectDir, opts = {}) {
1239
1274
  // Their choice may have raised the next one, and on the unconfirmed
1240
1275
  // path this read is also the only evidence available about whether the
1241
1276
  // press landed. Look again rather than assuming the editor is clear.
1242
- dialog = await readBlockingDialog(port, host);
1277
+ // Unreadable here keeps the dialog rather than dropping it: this read
1278
+ // is the only evidence about whether the press landed, and treating
1279
+ // silence as success is what would report a clear editor.
1280
+ const after = await readBlockingDialog(port, host);
1281
+ dialog = after.readable ? after.dialog : dialog;
1243
1282
  }
1244
1283
  }
1245
1284
  if (dialog) {
1246
1285
  return {
1286
+ // The guard's own refusal fields, so this route hands a caller exactly
1287
+ // what every other route does: dialogBlocking, refusedMethod,
1288
+ // dialogTitle, dialogMessage, buttons, choices and error. Assembling
1289
+ // its own object left most of those undefined depending on which route
1290
+ // did the refusing.
1291
+ ...DialogGuard.describeRefusal("editor.stop_editor", {
1292
+ title: dialog.title,
1293
+ message: dialog.message,
1294
+ buttons: dialog.buttons,
1295
+ choices: (dialog.choices ?? []).filter((c) => typeof c.respondWith === "string"),
1296
+ }, dialogMode.mode, canElicit),
1247
1297
  success: false,
1248
1298
  refusedReason: "blocking-dialog",
1249
1299
  blockingDialog: pressCalls ? dialog : forRecognitionOnly(dialog),
@@ -1333,14 +1383,15 @@ export async function stopEditor(projectDir, opts = {}) {
1333
1383
  // It was clean and unblocked when the quit went out, so whatever is holding
1334
1384
  // it started afterwards. Say what that is, in full.
1335
1385
  let blockedState = await readEngineState(projectPath, { probeWindows: false });
1336
- let late = await readBlockingDialog(port, host);
1386
+ const lateRead = await readBlockingDialog(port, host);
1387
+ let late = lateRead.dialog;
1337
1388
  if (late)
1338
1389
  dialogSeen = true;
1339
1390
  // The same mode governs a dialog that came up behind the quit. Interactive
1340
1391
  // puts it to the user exactly as it would have before the quit; auto and
1341
1392
  // defer leave it alone, so the loop below never runs for them.
1342
- if (late && dialogMode.mode === "interactive" && canElicit && opts.elicit) {
1343
- const press = await askUserToAnswerDialog(port, host, late, opts.elicit);
1393
+ if (late && opts.elicit) {
1394
+ const press = await askUserToAnswerDialog(port, host, late, opts.elicit, dialogMode.mode, canElicit, opts.guard);
1344
1395
  if (press !== null) {
1345
1396
  if (press.confirmed)
1346
1397
  answeredByUser = press.button;
@@ -1364,7 +1415,8 @@ export async function stopEditor(projectDir, opts = {}) {
1364
1415
  };
1365
1416
  }
1366
1417
  }
1367
- late = await readBlockingDialog(port, host);
1418
+ const lateAfter = await readBlockingDialog(port, host);
1419
+ late = lateAfter.readable ? lateAfter.dialog : late;
1368
1420
  // The snapshot above was taken BEFORE the elicitation, so it still holds
1369
1421
  // the modal the user has since answered. Rendering it after the press is
1370
1422
  // how one message came to quote a dialog that no longer exists, hand back
@@ -1409,6 +1461,7 @@ export async function stopEditor(projectDir, opts = {}) {
1409
1461
  ...(late
1410
1462
  ? {
1411
1463
  blockingDialog: pressCalls ? late : forRecognitionOnly(late),
1464
+ dialogBlocking: true,
1412
1465
  refusedReason: "blocking-dialog",
1413
1466
  }
1414
1467
  : {}),
@@ -1428,11 +1481,19 @@ export async function restartEditor(project, bridge, opts = {}) {
1428
1481
  // result: the stop only reports one when it MET a dialog, and the start half
1429
1482
  // has to keep defer's no-actuation promise whether or not the stop ran into
1430
1483
  // anything.
1484
+ const restartCanElicit = clientAdvertisesElicitation(opts.elicit);
1431
1485
  const dialogMode = resolveDialogMode({
1432
1486
  projectDir: project.projectDir ?? null,
1433
- canElicit: clientAdvertisesElicitation(opts.elicit),
1487
+ canElicit: restartCanElicit,
1488
+ });
1489
+ // Forward the editor's OWN guard. Without it the stop half fell back to
1490
+ // constructing a second one, with its own ask-once record, so a restart
1491
+ // asked the person again about a dialog the session guard had already put
1492
+ // to them, and pressed a second real button for it.
1493
+ const stopResult = await stopEditor(project.projectDir ?? undefined, {
1494
+ elicit: opts.elicit,
1495
+ guard: opts.guard,
1434
1496
  });
1435
- const stopResult = await stopEditor(project.projectDir ?? undefined, { elicit: opts.elicit });
1436
1497
  // Whether the stop mattered is a question about THIS project's editor: a
1437
1498
  // failed stop with nothing of ours left running just means it was already
1438
1499
  // down, and another project's editor being up says nothing either way.
@@ -1440,22 +1501,16 @@ export async function restartEditor(project, bridge, opts = {}) {
1440
1501
  // account of it rather than flattening it into a message string. The doc
1441
1502
  // promises dialogMode on every result that met a dialog, and a return type
1442
1503
  // that cannot hold it would make that promise unkeepable here.
1443
- const dialogAccount = {
1444
- ...(stopResult.dialogMode ? { dialogMode: stopResult.dialogMode } : {}),
1445
- ...(stopResult.dialogModeSource ? { dialogModeSource: stopResult.dialogModeSource } : {}),
1446
- ...(stopResult.blockingDialog ? { blockingDialog: stopResult.blockingDialog } : {}),
1447
- ...(stopResult.dialogAnsweredByUser ? { dialogAnsweredByUser: stopResult.dialogAnsweredByUser } : {}),
1448
- ...(stopResult.dialogPressUnconfirmed
1449
- ? { dialogPressUnconfirmed: stopResult.dialogPressUnconfirmed }
1450
- : {}),
1451
- };
1504
+ const dialogAccount = restartDialogAccount(stopResult);
1452
1505
  if (!stopResult.success && (await findInteractiveEditors(project.projectPath)).length > 0) {
1453
1506
  return { success: false, message: `Failed to stop editor: ${stopResult.message}`, ...dialogAccount };
1454
1507
  }
1455
1508
  // Wait for process to fully terminate and release locks
1456
1509
  await new Promise((resolve) => setTimeout(resolve, 3000));
1457
1510
  const startResult = await startEditor(project, undefined, undefined, {
1458
- pressCalls: dialogMode.mode !== "defer",
1511
+ // The guard's rule, not a second copy of it. This one also ignored
1512
+ // canElicit, so interactive with nobody to ask handed the buttons over.
1513
+ pressCalls: DialogGuard.handsOverPressCalls(dialogMode.mode, restartCanElicit),
1459
1514
  });
1460
1515
  if (!startResult.success) {
1461
1516
  return { ...startResult, ...dialogAccount };