@omercnet/paseo-omp 4.0.0-next.294.1 → 4.0.0-next.312.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "4.0.0-next.294.1",
3
+ "version": "4.0.0-next.312.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -1,2 +1,2 @@
1
1
  /** Release Please keeps this bundle-safe constant aligned with package.json. */
2
- export const PASEO_OMP_PACKAGE_VERSION = "4.0.0-next.294.1"; // x-release-please-version
2
+ export const PASEO_OMP_PACKAGE_VERSION = "4.0.0-next.312.1"; // x-release-please-version
@@ -155,7 +155,7 @@ export const OMP_RPC_COMMAND_POLICIES = {
155
155
  predict_word_feedback: "unsupported",
156
156
  promote_queued_message: "unsupported",
157
157
  remove_queued_message: "unsupported",
158
- set_ask_dialog: "unsupported",
158
+ set_ask_dialog: "implemented",
159
159
  set_cache_warming: "unsupported",
160
160
  set_slow_mode: "unsupported",
161
161
  steer_subagent: "unsupported",
@@ -524,6 +524,10 @@ const OMP_PROVIDER_NAME = NAME.refine((provider) => !provider.includes("/"));
524
524
  const TEXT = boundedString(MAX_TEXT_LENGTH);
525
525
  const RAW_DISPLAY_TEXT = boundedString(MAX_IMAGE_DATA_LENGTH);
526
526
  const DISPLAY_TRUNCATION_MARKER = "<truncated>";
527
+ /** OMP's `ask` tool has no upstream caps; these bound one dialog like the select schema does. */
528
+ export const MAX_ASK_QUESTIONS = 32;
529
+ export const MAX_ASK_OPTIONS = 128;
530
+ const MAX_ASK_QUESTION_ID_BYTES = 4_096;
527
531
 
528
532
  function boundRawDisplayContent(value: unknown): unknown {
529
533
  if (typeof value === "string") {
@@ -634,6 +638,7 @@ export const OMP_RPC_DIAGNOSTIC_COMMANDS = [
634
638
  "get_messages",
635
639
  "abort",
636
640
  "set_host_tools",
641
+ "set_ask_dialog",
637
642
  ] as const;
638
643
  export type OmpRpcDiagnosticCommand = (typeof OMP_RPC_DIAGNOSTIC_COMMANDS)[number];
639
644
  type JsonBoundViolation = {
@@ -1695,6 +1700,44 @@ const OmpExtensionUiRequestSchema = z.discriminatedUnion("method", [
1695
1700
  timeout: z.number().nonnegative().finite().optional(),
1696
1701
  })
1697
1702
  .strict(),
1703
+ z
1704
+ .object({
1705
+ ...ExtensionUiBase,
1706
+ method: z.literal("ask"),
1707
+ questions: z
1708
+ .array(
1709
+ z
1710
+ .object({
1711
+ id: boundedString(MAX_ASK_QUESTION_ID_BYTES, 1),
1712
+ question: boundedString(64 * 1024),
1713
+ header: boundedString(4_096).optional(),
1714
+ options: z
1715
+ .array(
1716
+ z
1717
+ .object({
1718
+ label: boundedString(4_096),
1719
+ description: boundedString(16_384).optional(),
1720
+ preview: boundedString(64 * 1024).optional(),
1721
+ })
1722
+ .strict(),
1723
+ )
1724
+ .max(MAX_ASK_OPTIONS),
1725
+ multi: z.boolean().optional(),
1726
+ recommended: z.number().int().nonnegative().optional(),
1727
+ })
1728
+ .strict(),
1729
+ )
1730
+ .min(1)
1731
+ .max(MAX_ASK_QUESTIONS),
1732
+ timeout: z.number().nonnegative().finite().optional(),
1733
+ })
1734
+ .strict()
1735
+ .superRefine((request, context) => {
1736
+ const ids = new Set(request.questions.map((question) => question.id));
1737
+ if (ids.size !== request.questions.length) {
1738
+ context.addIssue({ code: "custom", message: "invalid extension UI request" });
1739
+ }
1740
+ }),
1698
1741
  z.object({ ...ExtensionUiBase, method: z.literal("cancel"), targetId: IDENTIFIER }).strict(),
1699
1742
  z
1700
1743
  .object({
@@ -1980,7 +2023,37 @@ export interface OmpPersistedSessionMessages {
1980
2023
 
1981
2024
  export type OmpAvailableCommand = z.infer<typeof OmpAvailableCommandSchema>;
1982
2025
  export type OmpImage = { type: "image"; data: string; mimeType: string };
1983
- export type OmpExtensionUiResponse =
1984
- | { type: "extension_ui_response"; id: string; value: string }
1985
- | { type: "extension_ui_response"; id: string; confirmed: boolean }
1986
- | { type: "extension_ui_response"; id: string; cancelled: true; timedOut?: boolean };
2026
+ export const OmpExtensionUiResponseSchema = z.union([
2027
+ z.object({ type: z.literal("extension_ui_response"), id: IDENTIFIER, value: TEXT }).strict(),
2028
+ z
2029
+ .object({ type: z.literal("extension_ui_response"), id: IDENTIFIER, confirmed: z.boolean() })
2030
+ .strict(),
2031
+ z
2032
+ .object({
2033
+ type: z.literal("extension_ui_response"),
2034
+ id: IDENTIFIER,
2035
+ cancelled: z.literal(true),
2036
+ timedOut: z.boolean().optional(),
2037
+ })
2038
+ .strict(),
2039
+ z
2040
+ .object({
2041
+ type: z.literal("extension_ui_response"),
2042
+ id: IDENTIFIER,
2043
+ answers: z
2044
+ .array(
2045
+ z
2046
+ .object({
2047
+ id: boundedString(MAX_ASK_QUESTION_ID_BYTES, 1),
2048
+ selectedOptions: z.array(boundedString(4_096)).max(MAX_ASK_OPTIONS),
2049
+ customInput: TEXT.optional(),
2050
+ })
2051
+ .strict(),
2052
+ )
2053
+ .min(1)
2054
+ .max(MAX_ASK_QUESTIONS),
2055
+ })
2056
+ .strict(),
2057
+ ]);
2058
+ export type OmpExtensionUiResponse = z.infer<typeof OmpExtensionUiResponseSchema>;
2059
+ export const OmpAskDialogEnabledSchema = z.object({ enabled: z.boolean() }).strict();
@@ -10,6 +10,7 @@ import {
10
10
  MAX_REPLAY_MESSAGES,
11
11
  NAME,
12
12
  OMP_MESSAGE_PAGE_LIMIT,
13
+ OmpAskDialogEnabledSchema,
13
14
  type OmpAvailableCommand,
14
15
  OmpAvailableCommandsResultSchema,
15
16
  OmpBranchMessagesResultSchema,
@@ -18,6 +19,7 @@ import {
18
19
  type OmpCompactionResult,
19
20
  OmpCompactionResultSchema,
20
21
  type OmpExtensionUiResponse,
22
+ OmpExtensionUiResponseSchema,
21
23
  type OmpHostToolDefinition,
22
24
  OmpHostToolDefinitionSchema,
23
25
  type OmpHostToolResult,
@@ -546,7 +548,10 @@ class OmpRpcSession implements OmpRuntimeSession {
546
548
  }
547
549
 
548
550
  respondToExtensionUi(response: OmpExtensionUiResponse): Promise<void> {
549
- return this.process.sendFrame(response);
551
+ // Callers chain `.catch` without a surrounding try, so a bad frame must reject, not throw.
552
+ const parsed = OmpExtensionUiResponseSchema.safeParse(response);
553
+ if (!parsed.success) return Promise.reject(parsed.error);
554
+ return this.process.sendFrame(parsed.data);
550
555
  }
551
556
  respondToToolApproval(response: OmpToolApprovalResponse): Promise<void> {
552
557
  return this.process.sendFrame(OmpToolApprovalResponseSchema.parse(response));
@@ -652,6 +657,7 @@ export class OmpRpcRuntime implements OmpRuntime {
652
657
  }),
653
658
  );
654
659
  options.signal?.throwIfAborted();
660
+ enableAskDialog(process);
655
661
  return new OmpRpcSession(
656
662
  process,
657
663
  removeAbortListener,
@@ -671,3 +677,17 @@ export class OmpRpcRuntime implements OmpRuntime {
671
677
  }
672
678
  }
673
679
  }
680
+
681
+ /**
682
+ * Opts into OMP's single-request `ask` dialog so every question reaches Paseo as one card that can
683
+ * be revisited before submitting. OMP applies the flag in command order, so it is set before any
684
+ * later prompt can ask; startup does not wait for the acknowledgement. OMP releases without
685
+ * `set_ask_dialog` reject it and keep the per-question select/editor fallback, which the
686
+ * permission bridge still handles.
687
+ */
688
+ function enableAskDialog(process: OmpRpcProcess): void {
689
+ process
690
+ .request({ type: "set_ask_dialog", enabled: true })
691
+ .then((data) => OmpAskDialogEnabledSchema.parse(data))
692
+ .catch(() => undefined);
693
+ }
@@ -25,14 +25,23 @@ import type { OmpTimelineProjector, OmpTimelineScheduler } from "./timeline-proj
25
25
  type SessionPermissionInput = Extract<ProviderInput, { type: "session.permission" }>;
26
26
  type OmpQuestionRequest = Extract<
27
27
  Extract<OmpRpcEvent, { type: "extension_ui_request" }>,
28
- { method: "select" | "confirm" | "input" | "editor" }
28
+ { method: "select" | "confirm" | "input" | "editor" | "ask" }
29
29
  >;
30
+ type OmpAskRequest = Extract<OmpQuestionRequest, { method: "ask" }>;
30
31
  type Emit = (event: ProviderEvent) => void;
31
32
 
32
33
  const MAX_PENDING_PERMISSIONS = 32;
33
34
  const MAX_PENDING_PERMISSION_BYTES = 2 * 1024 * 1024;
34
35
  const MAX_RESOLVED_TOOL_APPROVAL_IDS = 1_024;
35
- const OMP_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
36
+ /**
37
+ * Select rows that hand off to a follow-up free-text request: `ask_user` extensions follow with
38
+ * `input`, OMP's own `ask` fallback (hosts without `set_ask_dialog`) follows with `editor`.
39
+ */
40
+ const OMP_FREEFORM_SENTINELS: readonly string[] = [
41
+ "✏️ Type custom response...",
42
+ "Other (type your own)",
43
+ ];
44
+ const OMP_ASK_RECOMMENDED_SUFFIX = " (Recommended)";
36
45
  const MAX_FREEFORM_RESPONSE_BYTES = 64 * 1024;
37
46
 
38
47
  type PendingPermission = {
@@ -50,6 +59,14 @@ type PendingPermission = {
50
59
  turnId?: string;
51
60
  request: OmpQuestionRequest;
52
61
  freeformSentinel?: string;
62
+ askQuestions?: readonly PendingAskQuestion[];
63
+ };
64
+ /** One `ask` question as rendered on the Paseo card, with display labels mapped back to OMP. */
65
+ type PendingAskQuestion = {
66
+ id: string;
67
+ header: string;
68
+ multi: boolean;
69
+ nativeLabels: ReadonlyMap<string, string>;
53
70
  };
54
71
  type PendingToolPermission = {
55
72
  nativeId: string;
@@ -75,6 +92,96 @@ function permissionFingerprint(request: OmpQuestionRequest): string {
75
92
  return createHash("sha256").update(JSON.stringify(request)).digest("base64url");
76
93
  }
77
94
 
95
+ type CardQuestion = {
96
+ header: string;
97
+ question: string;
98
+ options: Array<{ label: string; value?: string; description?: string; preview?: string }>;
99
+ multiSelect: boolean;
100
+ allowOther?: boolean;
101
+ placeholder?: string;
102
+ prefill?: string;
103
+ };
104
+ type AskAnswer = { id: string; selectedOptions: string[]; customInput?: string };
105
+
106
+ function uniqueLabel(base: string, used: Set<string>): string {
107
+ let label = base;
108
+ let suffix = 2;
109
+ while (used.has(label)) {
110
+ label = `${base} (${suffix})`;
111
+ suffix += 1;
112
+ }
113
+ used.add(label);
114
+ return label;
115
+ }
116
+
117
+ /**
118
+ * Paseo's question card submits a multi-select answer as one string: checked option labels in
119
+ * click order, then any typed answer, joined with ", ". Exact labels are consumed from the front,
120
+ * longest first so a label containing ", " wins over its prefix; the rest is the typed answer.
121
+ */
122
+ function splitMultiSelectAnswer(
123
+ answer: string,
124
+ labels: readonly string[],
125
+ ): { selected: string[]; custom: string } {
126
+ const candidates = [...labels].sort((left, right) => right.length - left.length);
127
+ const selected: string[] = [];
128
+ let remaining = answer;
129
+ while (remaining.length > 0) {
130
+ const label = candidates.find(
131
+ (candidate) =>
132
+ !selected.includes(candidate) &&
133
+ (remaining === candidate || remaining.startsWith(`${candidate}, `)),
134
+ );
135
+ if (label === undefined) break;
136
+ selected.push(label);
137
+ remaining = remaining === label ? "" : remaining.slice(label.length + 2);
138
+ }
139
+ return { selected, custom: remaining };
140
+ }
141
+
142
+ /**
143
+ * Converts one card answer to OMP's shape. OMP rejects unknown or repeated labels and, for
144
+ * single-select questions, a selection combined with typed text, so neither is ever produced.
145
+ */
146
+ function askAnswer(question: PendingAskQuestion, answer: unknown): AskAnswer {
147
+ const parts =
148
+ answer === undefined || answer === null
149
+ ? []
150
+ : typeof answer === "string"
151
+ ? [answer]
152
+ : Array.isArray(answer) && answer.every((part) => typeof part === "string")
153
+ ? answer
154
+ : undefined;
155
+ if (!parts) throw new OmpPublicError("OMP question response is invalid");
156
+ const labels = [...question.nativeLabels.keys()];
157
+ const selectedOptions: string[] = [];
158
+ const custom: string[] = [];
159
+ const select = (label: string) => {
160
+ const native = question.nativeLabels.get(label);
161
+ if (native !== undefined && !selectedOptions.includes(native)) selectedOptions.push(native);
162
+ };
163
+ for (const part of parts) {
164
+ if (question.nativeLabels.has(part)) {
165
+ select(part);
166
+ } else if (question.multi) {
167
+ const split = splitMultiSelectAnswer(part, labels);
168
+ for (const label of split.selected) select(label);
169
+ if (split.custom.trim()) custom.push(split.custom);
170
+ } else if (part.trim()) {
171
+ custom.push(part);
172
+ }
173
+ }
174
+ const customInput = custom.join(", ").trim();
175
+ if (
176
+ customInput.includes("\0") ||
177
+ utf8Bytes(customInput) > MAX_FREEFORM_RESPONSE_BYTES ||
178
+ (!question.multi && selectedOptions.length + (customInput ? 1 : 0) > 1)
179
+ ) {
180
+ throw new OmpPublicError("OMP question response is invalid");
181
+ }
182
+ return { id: question.id, selectedOptions, ...(customInput ? { customInput } : {}) };
183
+ }
184
+
78
185
  interface PermissionContext {
79
186
  runtime: OmpRuntimeSession;
80
187
  generation: number;
@@ -279,7 +386,7 @@ export class OmpSessionPermissions {
279
386
  }
280
387
 
281
388
  submitPendingFreeformSelection(
282
- request: Extract<OmpQuestionRequest, { method: "input" }>,
389
+ request: Extract<OmpQuestionRequest, { method: "input" | "editor" }>,
283
390
  ): boolean {
284
391
  const pending = this.pendingFreeformSelection;
285
392
  if (!pending) return false;
@@ -505,10 +612,17 @@ export class OmpSessionPermissions {
505
612
  if (!existingId) this.permissionSequence += 1;
506
613
  const id =
507
614
  existingId ?? `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
508
- const header = this.dataFilter.text(request.title ?? "OMP question", 4_096);
615
+ const header = this.dataFilter.text(
616
+ request.method === "ask"
617
+ ? request.questions.length === 1
618
+ ? "OMP question"
619
+ : `OMP questions (${request.questions.length})`
620
+ : (request.title ?? "OMP question"),
621
+ 4_096,
622
+ );
509
623
  const freeformSentinel =
510
- request.method === "select" && request.options.includes(OMP_ASK_USER_FREEFORM_SENTINEL)
511
- ? OMP_ASK_USER_FREEFORM_SENTINEL
624
+ request.method === "select"
625
+ ? request.options.find((option) => OMP_FREEFORM_SENTINELS.includes(option))
512
626
  : undefined;
513
627
  const optionValues = new Map<string, string>();
514
628
  const displayValues = new Map<string, string>();
@@ -523,14 +637,7 @@ export class OmpSessionPermissions {
523
637
  const options =
524
638
  request.method === "select"
525
639
  ? selectableOptions.map(({ nativeValue, index }) => {
526
- const baseLabel = this.dataFilter.text(nativeValue, 4_096);
527
- let label = baseLabel;
528
- let suffix = 2;
529
- while (usedOptionLabels.has(label)) {
530
- label = `${baseLabel} (${suffix})`;
531
- suffix += 1;
532
- }
533
- usedOptionLabels.add(label);
640
+ const label = uniqueLabel(this.dataFilter.text(nativeValue, 4_096), usedOptionLabels);
534
641
  const value = `${id}:option:${index}`;
535
642
  optionValues.set(value, nativeValue);
536
643
  displayValues.set(label, nativeValue);
@@ -550,6 +657,28 @@ export class OmpSessionPermissions {
550
657
  : undefined;
551
658
  const questionOptions =
552
659
  request.method === "confirm" ? [{ label: "Yes" }, { label: "No" }] : (options ?? []);
660
+ const ask = request.method === "ask" ? this.askCard(request) : undefined;
661
+ const cardQuestions: CardQuestion[] =
662
+ request.method === "ask"
663
+ ? (ask?.questions ?? [])
664
+ : [
665
+ {
666
+ header,
667
+ question: this.dataFilter.text(
668
+ request.method === "confirm" ? request.message : request.title,
669
+ 64 * 1024,
670
+ ),
671
+ options: questionOptions,
672
+ multiSelect: false,
673
+ ...(freeformSentinel ? { allowOther: true } : {}),
674
+ ...(request.method === "input" && request.placeholder
675
+ ? { placeholder: this.dataFilter.text(request.placeholder, 4_096) }
676
+ : {}),
677
+ ...((request.method === "input" || request.method === "editor") && request.prefill
678
+ ? { prefill: this.dataFilter.text(request.prefill) }
679
+ : {}),
680
+ },
681
+ ];
553
682
  const actions =
554
683
  request.method === "select"
555
684
  ? [
@@ -591,7 +720,7 @@ export class OmpSessionPermissions {
591
720
  runtime: this.runtime,
592
721
  request,
593
722
  retainedBytes: boundedJsonBytes(
594
- { request, header, options: questionOptions, actions },
723
+ { request, header, questions: cardQuestions, actions },
595
724
  MAX_PENDING_PERMISSION_BYTES,
596
725
  512,
597
726
  MAX_PENDING_PERMISSION_BYTES,
@@ -600,6 +729,7 @@ export class OmpSessionPermissions {
600
729
  ...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
601
730
  ...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
602
731
  ...(freeformSentinel ? { freeformSentinel } : {}),
732
+ ...(ask ? { askQuestions: ask.pending } : {}),
603
733
  };
604
734
  let retainedPermissionBytes = pending.retainedBytes;
605
735
  for (const [permissionId, retained] of this.pendingPermissions) {
@@ -647,26 +777,7 @@ export class OmpSessionPermissions {
647
777
  ...(request.method === "confirm"
648
778
  ? { description: this.dataFilter.text(request.message, 64 * 1024) }
649
779
  : {}),
650
- input: {
651
- questions: [
652
- {
653
- header,
654
- question: this.dataFilter.text(
655
- request.method === "confirm" ? request.message : request.title,
656
- 64 * 1024,
657
- ),
658
- options: questionOptions,
659
- multiSelect: false,
660
- ...(freeformSentinel ? { allowOther: true } : {}),
661
- ...(request.method === "input" && request.placeholder
662
- ? { placeholder: this.dataFilter.text(request.placeholder, 4_096) }
663
- : {}),
664
- ...((request.method === "input" || request.method === "editor") && request.prefill
665
- ? { prefill: this.dataFilter.text(request.prefill) }
666
- : {}),
667
- },
668
- ],
669
- },
780
+ input: { questions: cardQuestions },
670
781
  actions,
671
782
  },
672
783
  });
@@ -688,6 +799,76 @@ export class OmpSessionPermissions {
688
799
  .catch(() => this.handleRuntimeFailure());
689
800
  }
690
801
 
802
+ /**
803
+ * Renders every `ask` question on one card. Headers key the card's answers and option labels
804
+ * come back verbatim, so both are made unique here and mapped back to OMP's labels on submit.
805
+ */
806
+ private askCard(request: OmpAskRequest): {
807
+ questions: CardQuestion[];
808
+ pending: PendingAskQuestion[];
809
+ } {
810
+ const usedHeaders = new Set<string>();
811
+ const questions: CardQuestion[] = [];
812
+ const pending: PendingAskQuestion[] = [];
813
+ for (const [index, question] of request.questions.entries()) {
814
+ const header = uniqueLabel(
815
+ this.dataFilter.text(question.header?.trim() || `Question ${index + 1}`, 4_096),
816
+ usedHeaders,
817
+ );
818
+ const usedLabels = new Set<string>();
819
+ const nativeLabels = new Map<string, string>();
820
+ const options = question.options.map((option, optionIndex) => {
821
+ const label = uniqueLabel(
822
+ `${this.dataFilter.text(option.label, 4_096)}${
823
+ optionIndex === question.recommended ? OMP_ASK_RECOMMENDED_SUFFIX : ""
824
+ }`,
825
+ usedLabels,
826
+ );
827
+ nativeLabels.set(label, option.label);
828
+ return {
829
+ label,
830
+ ...(option.description
831
+ ? { description: this.dataFilter.text(option.description, 16_384) }
832
+ : {}),
833
+ ...(option.preview ? { preview: this.dataFilter.text(option.preview, 64 * 1024) } : {}),
834
+ };
835
+ });
836
+ const multi = question.multi === true;
837
+ questions.push({
838
+ header,
839
+ question: this.dataFilter.text(question.question, 64 * 1024),
840
+ options,
841
+ multiSelect: multi,
842
+ allowOther: true,
843
+ });
844
+ pending.push({ id: question.id, header, multi, nativeLabels });
845
+ }
846
+ return { questions, pending };
847
+ }
848
+
849
+ /** Maps card answers to OMP's per-question `selectedOptions`/`customInput` in question order. */
850
+ private askResponse(
851
+ pending: PendingPermission,
852
+ response: ProviderPermissionResponse,
853
+ ): OmpExtensionUiResponse {
854
+ const answers = response.behavior === "allow" ? response.updatedInput?.answers : undefined;
855
+ if (
856
+ !pending.askQuestions ||
857
+ !answers ||
858
+ typeof answers !== "object" ||
859
+ Array.isArray(answers)
860
+ ) {
861
+ throw new OmpPublicError("OMP question response requires answers");
862
+ }
863
+ return {
864
+ type: "extension_ui_response",
865
+ id: pending.nativeId,
866
+ answers: pending.askQuestions.map((question) =>
867
+ askAnswer(question, (answers as Record<string, unknown>)[question.header]),
868
+ ),
869
+ };
870
+ }
871
+
691
872
  private freeformSelection(
692
873
  pending: PendingPermission,
693
874
  response: ProviderPermissionResponse,
@@ -754,6 +935,7 @@ export class OmpSessionPermissions {
754
935
  if (response.behavior === "deny") {
755
936
  return { type: "extension_ui_response", id: nativeId, cancelled: true };
756
937
  }
938
+ if (request.method === "ask") return this.askResponse(pending, response);
757
939
  const selectedValue = response.selectedActionId
758
940
  ? pending.optionValues.get(response.selectedActionId)
759
941
  : undefined;
@@ -2280,13 +2280,17 @@ export class OmpProviderSession implements OmpOpenedSession {
2280
2280
  this.permissions.resolvePermissionByNativeId(event.targetId ?? "");
2281
2281
  return;
2282
2282
  }
2283
- if (event.method === "input" && this.permissions.submitPendingFreeformSelection(event))
2283
+ if (
2284
+ (event.method === "input" || event.method === "editor") &&
2285
+ this.permissions.submitPendingFreeformSelection(event)
2286
+ )
2284
2287
  return;
2285
2288
  if (
2286
2289
  event.method === "select" ||
2287
2290
  event.method === "confirm" ||
2288
2291
  event.method === "input" ||
2289
- event.method === "editor"
2292
+ event.method === "editor" ||
2293
+ event.method === "ask"
2290
2294
  ) {
2291
2295
  if (!this.capabilities.includes("permission")) {
2292
2296
  this.handleRuntimeFailure();
@@ -2459,7 +2463,9 @@ export class OmpProviderSession implements OmpOpenedSession {
2459
2463
  if (event.type === "tool_execution_start") turn.activeToolCallIds.add(event.toolCallId);
2460
2464
  if (event.type === "tool_execution_end") {
2461
2465
  turn.activeToolCallIds.delete(event.toolCallId);
2462
- if (event.toolName === "ask_user") this.permissions.clearPendingFreeformSelection();
2466
+ if (event.toolName === "ask_user" || event.toolName === "ask") {
2467
+ this.permissions.clearPendingFreeformSelection();
2468
+ }
2463
2469
  }
2464
2470
  if (event.type === "tool_execution_start" || event.type === "tool_execution_end") {
2465
2471
  try {