@burdenoff/fe-libs 2026.1028.3 → 2026.1028.5

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 (37) hide show
  1. package/dist/shared/assistant/appControls/grammar.d.ts +2 -0
  2. package/dist/shared/assistant/appControls/grammar.js +101 -93
  3. package/dist/shared/assistant/appControls/registry.d.ts +2 -0
  4. package/dist/shared/assistant/appControls/registry.js +20 -12
  5. package/dist/shared/assistant/commandCompletions.d.ts +9 -0
  6. package/dist/shared/assistant/commandCompletions.js +161 -0
  7. package/dist/shared/assistant/index.d.ts +3 -0
  8. package/dist/shared/assistant/localUiCommands.d.ts +16 -0
  9. package/dist/shared/assistant/localUiCommands.js +192 -105
  10. package/dist/shared/assistant/localeCommands/engine.d.ts +6 -0
  11. package/dist/shared/assistant/localeCommands/engine.js +212 -116
  12. package/dist/shared/assistant/turnTimeline.d.ts +18 -6
  13. package/dist/shared/assistant/turnTimeline.js +57 -39
  14. package/dist/shared/assistant/turnTimingReport.d.ts +10 -1
  15. package/dist/shared/assistant/turnTimingReport.js +19 -6
  16. package/dist/shared/assistant/ui/AssistantChatArea.js +1491 -1406
  17. package/dist/shared/assistant/ui/AssistantSpeechProvider.js +239 -237
  18. package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +8 -0
  19. package/dist/shared/assistant/ui/composer/AssistantComposer.js +142 -131
  20. package/dist/shared/assistant/ui/draftCommandCheck.d.ts +7 -0
  21. package/dist/shared/assistant/ui/draftCommandCheck.js +17 -0
  22. package/dist/shared/assistant/ui/useVoiceSession.js +218 -202
  23. package/dist/shared/assistant/uiActions/backStack.d.ts +1 -0
  24. package/dist/shared/assistant/uiActions/backStack.js +13 -5
  25. package/dist/shared/assistant/uiActions/deixis.d.ts +2 -0
  26. package/dist/shared/assistant/uiActions/deixis.js +12 -1
  27. package/dist/shared/assistant/uiActions/draftPathname.d.ts +13 -0
  28. package/dist/shared/assistant/uiActions/draftPathname.js +31 -0
  29. package/dist/shared/assistant/uiActions/recordNames.js +33 -30
  30. package/dist/shared/assistant/voice/handsfree/handsFreeController.d.ts +3 -0
  31. package/dist/shared/assistant/voice/handsfree/handsFreeController.js +5 -1
  32. package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.d.ts +1 -0
  33. package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.js +1 -0
  34. package/dist/shared/assistant/voice/useVoiceInput.d.ts +3 -1
  35. package/dist/shared/assistant/voice/useVoiceInput.js +49 -43
  36. package/dist/shared-assistant.js +329 -326
  37. package/package.json +1 -1
@@ -101,3 +101,5 @@ export declare function queryAsSaid(original: string, canonicalQuery: string): s
101
101
  * transcribed — queries keep the person's own case and punctuation.
102
102
  */
103
103
  export declare function matchAppControl(utterance: string, original: string, snapshot: ControlSnapshot): AppControlMatch | null;
104
+ /** Shared grammar patterns, gated by what the page actually registered. */
105
+ export declare function appControlPrefixPatterns(snapshot: ControlSnapshot): readonly RegExp[];
@@ -16,37 +16,37 @@ function c(e, t) {
16
16
  for (let e of i.matchAll(r)) a = e[0];
17
17
  return a ?? t;
18
18
  }
19
- var l = 60, u = /^(?:log ?out|sign ?out|(?:log|sign) (?:me )?out(?: of (?:the |my |this )?(?:app|application|account|session))?)$/, d = "(?:workspace|workspaces|context|contexts|organi[sz]ation|organi[sz]ations|org|project)", f = "(?:switcher|selector|picker|menu|dropdown|list)", p = /^(?:another|other|a different|different|a new|new|a|the|another one|other one)$/, m = RegExp(`^${i} ${r}(?:notification|notifications) (?:panel|bell|centre|center|menu|tray|drawer|list)$`), h = RegExp(`^${i} ${r}(?:notification )?bell$`), ee = RegExp(`^${i} ${r}notifications?$`), g = RegExp(`^${i} ${r}(?:help (?:menu|panel|dialog|dialogue|centre|center)|keyboard shortcuts|keyboard help|shortcuts)$`), _ = RegExp(`^${i} ${r}help$`), v = RegExp(`^(?:open|show|start|use|pull up|bring up) ${r}(?:global |app )search(?: (?:bar|box|dialog|dialogue|panel))?$|^(?:open|show|start|use|pull up|bring up) ${r}search (?:bar|box|dialog|dialogue|panel)$`), y = /^(?:open|start|use|pull up|bring up) (?:(?:the|my) )?search$/, b = [
19
+ var l = 60, u = /^(?:log ?out|sign ?out|(?:log|sign) (?:me )?out(?: of (?:the |my |this )?(?:app|application|account|session))?)$/, d = "(?:workspace|workspaces|context|contexts|organi[sz]ation|organi[sz]ations|org|project)", ee = "(?:switcher|selector|picker|menu|dropdown|list)", te = /^(?:another|other|a different|different|a new|new|a|the|another one|other one)$/, f = RegExp(`^(?:switch|change|move|go|jump|hop)(?: over)? to ${r}(.+?) ${d}$`), p = RegExp(`^(?:switch|change|move) ${r}${d} (?:to|over to) ${r}(.+)$`), m = RegExp(`^${i} ${r}${d} ${ee}$`), h = RegExp(`^(?:switch|change) ${r}${d}$`), g = RegExp(`^${i} ${r}(?:notification|notifications) (?:panel|bell|centre|center|menu|tray|drawer|list)$`), _ = RegExp(`^${i} ${r}(?:notification )?bell$`), v = RegExp(`^${i} ${r}notifications?$`), y = RegExp(`^${i} ${r}(?:help (?:menu|panel|dialog|dialogue|centre|center)|keyboard shortcuts|keyboard help|shortcuts)$`), b = RegExp(`^${i} ${r}help$`), x = RegExp(`^(?:open|show|start|use|pull up|bring up) ${r}(?:global |app )search(?: (?:bar|box|dialog|dialogue|panel))?$|^(?:open|show|start|use|pull up|bring up) ${r}search (?:bar|box|dialog|dialogue|panel)$`), S = /^(?:open|start|use|pull up|bring up) (?:(?:the|my) )?search$/, C = [
20
20
  /^(?:open|start|use|pull up|bring up) (?:(?:the|my) )?(?:global |app )?search (?:for|with) (.+)$/,
21
21
  /^(?:global search|search everything|search everywhere|search globally|search the app|search all|search across the app) (?:for )?(.+)$/,
22
22
  /^search (?:the app|everything|everywhere|globally|all|across the app) for (.+)$/
23
- ], x = "(?:side ?bar|side menu|side navigation|side nav|navigation (?:bar|menu|panel|drawer)|nav (?:bar|menu|panel|drawer)|left (?:menu|sidebar|panel))", S = RegExp(`^(?:collapse|close|hide|minimi[sz]e|shrink|fold) ${r}${x}$`), C = RegExp(`^(?:expand|open|show|reveal|unhide|bring up|widen|unfold) ${r}${x}$`), w = "(?: (?:group|section|menu|folder|submenu|sub menu|list|dropdown))?", T = RegExp(`^(expand|unfold|open up|open|show) ${r}(.+?)${w}$`), E = RegExp(`^(collapse|fold up|close|hide) ${r}(.+?)${w}$`), D = [
23
+ ], w = "(?:side ?bar|side menu|side navigation|side nav|navigation (?:bar|menu|panel|drawer)|nav (?:bar|menu|panel|drawer)|left (?:menu|sidebar|panel))", T = RegExp(`^(?:collapse|close|hide|minimi[sz]e|shrink|fold) ${r}${w}$`), E = RegExp(`^(?:expand|open|show|reveal|unhide|bring up|widen|unfold) ${r}${w}$`), D = "(?: (?:group|section|menu|folder|submenu|sub menu|list|dropdown))?", O = RegExp(`^(expand|unfold|open up|open|show) ${r}(.+?)${D}$`), k = RegExp(`^(collapse|fold up|close|hide) ${r}(.+?)${D}$`), A = [
24
24
  RegExp(`^(?:change|switch|set|update|turn|put|make)(?: over)? ${r}(?:(?:app|ui|user interface|interface|screen|display|site|system|menu|application) )?language (?:to|into|as|over to) (.+)$`),
25
25
  RegExp(`^(?:switch|change|set|put|turn) ${r}(?:app|ui|user interface|interface|screen|display|site|application|menus?) (?:language )?(?:to|into) (.+)$`),
26
26
  RegExp(`^(?:show|display|use|run) ${r}(?:app|ui|user interface|interface|screen|display|site|application)(?: menus?)? in (.+)$`)
27
- ], O = RegExp(`^(?:switch|change)(?: over)? to ${r}(.+)$`), k = RegExp(`^${i} ${r}(?:(?:assistant|ai|conversation|conversations|chat) history|history of (?:my )?(?:conversations|chats)|(?:past|previous|old|earlier) conversations)$`), A = RegExp(`^${i} ${r}history$`), j = RegExp(`^${i} ${r}(?:voice|spoken|speech) transcript$`), M = RegExp(`^${i} ${r}transcript$`), N = RegExp(`^${i} ${r}(?:ai|assistant) preview(?: panel| pane| surface)?$`), P = RegExp(`^${i} ${r}preview(?: panel| pane)?$`), F = RegExp(`^(?:close|hide|dismiss) ${r}(?:ai|assistant) preview(?: panel| pane| surface)?$`), te = RegExp(`^(?:close|hide) ${r}preview(?: panel| pane)?$`), I = /^(?:(?:start|begin|open) (?:a |an |another )?(?:new |fresh |blank )?conversation|new conversation|(?:start|begin) (?:a )?(?:new|fresh) conversation with you)$/, L = [RegExp(`^(?:go to|open|show|show me|switch to|select|view|change to|move to|take me to|bring up) ${r}(.+) tab$`), /^(?:switch|change|move) tabs? to (.+)$/], R = "(?:filter|narrow|limit)(?: down)?", z = [RegExp(`^${R}(?: ${r}(?:page|list|table|results|items|rows|view))? (?:by|to|for|on) ${r}(.+)$`)], B = [/^(?:show|display|list|keep) only (.+)$/, /^only (?:show|display|list|keep) (.+)$/], V = [
27
+ ], j = RegExp(`^(?:switch|change)(?: over)? to ${r}(.+)$`), M = RegExp(`^${i} ${r}(?:(?:assistant|ai|conversation|conversations|chat) history|history of (?:my )?(?:conversations|chats)|(?:past|previous|old|earlier) conversations)$`), N = RegExp(`^${i} ${r}history$`), P = RegExp(`^${i} ${r}(?:voice|spoken|speech) transcript$`), F = RegExp(`^${i} ${r}transcript$`), ne = RegExp(`^${i} ${r}(?:ai|assistant) preview(?: panel| pane| surface)?$`), I = RegExp(`^${i} ${r}preview(?: panel| pane)?$`), L = RegExp(`^(?:close|hide|dismiss) ${r}(?:ai|assistant) preview(?: panel| pane| surface)?$`), R = RegExp(`^(?:close|hide) ${r}preview(?: panel| pane)?$`), z = /^(?:(?:start|begin|open) (?:a |an |another )?(?:new |fresh |blank )?conversation|new conversation|(?:start|begin) (?:a )?(?:new|fresh) conversation with you)$/, B = [RegExp(`^(?:go to|open|show|show me|switch to|select|view|change to|move to|take me to|bring up) ${r}(.+) tab$`), /^(?:switch|change|move) tabs? to (.+)$/], V = "(?:filter|narrow|limit)(?: down)?", H = [RegExp(`^${V}(?: ${r}(?:page|list|table|results|items|rows|view))? (?:by|to|for|on) ${r}(.+)$`)], U = [/^(?:show|display|list|keep) only (.+)$/, /^only (?:show|display|list|keep) (.+)$/], W = [
28
28
  RegExp(`^(?:set|change|switch|put|make) ${r}(.+?) filter (?:to|as|on) (.+)$`),
29
- RegExp(`^${R} ${r}(.+?) (?:by|to|for|on) (.+)$`),
29
+ RegExp(`^${V} ${r}(.+?) (?:by|to|for|on) (.+)$`),
30
30
  RegExp(`^(?:use|apply) ${r}(.+?) filter (?:of|with|for|set to) (.+)$`)
31
- ], H = RegExp(`^(?:clear|remove|reset|drop|turn off) ${r}(?:(?:all|every) )?(?:(.+?) )?filters?$`), U = [
31
+ ], G = RegExp(`^(?:clear|remove|reset|drop|turn off) ${r}(?:(?:all|every) )?(?:(.+?) )?filters?$`), K = [
32
32
  [/(?: in)? (?:ascending|asc)(?: order)?$/, "asc"],
33
33
  [/(?: in)? (?:descending|desc)(?: order)?$/, "desc"],
34
34
  [/ (?:a to z|low to high|lowest first|smallest first|oldest first)$/, "asc"],
35
35
  [/ (?:z to a|high to low|highest first|largest first|biggest first|newest first|latest first)$/, "desc"]
36
- ], W = RegExp(`^(?:sort|order|arrange|rank)(?: ${r}(?:page|list|table|results|items|rows|view|it|them))? by (.+)$`), G = /^(?:sort|order)(?: it| them)? (ascending|descending|asc|desc)$/, K = [
36
+ ], q = RegExp(`^(?:sort|order|arrange|rank)(?: ${r}(?:page|list|table|results|items|rows|view|it|them))? by (.+)$`), re = K.map(([e]) => new RegExp(q.source.slice(0, -1) + e.source)), J = /^(?:sort|order)(?: it| them)? (ascending|descending|asc|desc)$/, Y = [
37
37
  /^(?:search|find|look)(?: (?:this page|the page|the list|the table|this list|this table|here|these|them))? for (.+)$/,
38
38
  /^(?:find|search for) (.+?) (?:on|in) (?:this|the) (?:page|list|table)$/,
39
39
  /^search (?:this page|the page|the list|the table|here) (?:for )?(.+)$/
40
- ], q = /^(?:clear|reset|empty|remove|stop) (?:the |my |this )?(?:page )?search(?:ing)?$/, J = [RegExp(`^(?:open|show|start|bring up|pull up) ${r}(.+?) (?:form|dialog|dialogue|modal)$`)], Y = RegExp(`^(?:open|show|start|bring up|pull up) ${r}(.+)$`), X = /\b(?:and then|then|and also|also|after that|afterwards|and open|and show|and go|and sort|and filter)\b/;
41
- function Z(e, t) {
40
+ ], X = /^(?:clear|reset|empty|remove|stop) (?:the |my |this )?(?:page )?search(?:ing)?$/, Z = [RegExp(`^(?:open|show|start|bring up|pull up) ${r}(.+?) (?:form|dialog|dialogue|modal)$`)], Q = RegExp(`^(?:open|show|start|bring up|pull up) ${r}(.+)$`), ie = /\b(?:and then|then|and also|also|after that|afterwards|and open|and show|and go|and sort|and filter)\b/;
41
+ function $(e, t) {
42
42
  for (let n of e) {
43
43
  let e = n.exec(t);
44
44
  if (e) return e;
45
45
  }
46
46
  return null;
47
47
  }
48
- function Q(e) {
49
- for (let [t, n] of U) {
48
+ function ae(e) {
49
+ for (let [t, n] of K) {
50
50
  let r = t.exec(e);
51
51
  if (r) return {
52
52
  field: e.slice(0, r.index).trim(),
@@ -58,110 +58,110 @@ function Q(e) {
58
58
  direction: null
59
59
  };
60
60
  }
61
- function $(e) {
61
+ function oe(e) {
62
62
  let t = e.replace(/\b(?:please|pls)$/, "").trim();
63
63
  return !t || t.length > l ? null : t;
64
64
  }
65
- function ne(s, l, x) {
66
- let w = x.registered;
67
- if (X.test(s)) return null;
68
- if (w["account.logout"] && u.test(s)) return a({ control: "account.logout" });
69
- if (w["header.workspace"] || w["header.context"]) {
70
- let e = RegExp(`^(?:switch|change|move|go|jump|hop)(?: over)? to ${r}(.+?) ${d}$`).exec(s) ?? RegExp(`^(?:switch|change|move) ${r}${d} (?:to|over to) ${r}(.+)$`).exec(s);
71
- if (e && w["header.workspace"]) {
65
+ function se(r, i, s) {
66
+ let l = s.registered;
67
+ if (ie.test(r)) return null;
68
+ if (l["account.logout"] && u.test(r)) return a({ control: "account.logout" });
69
+ if (l["header.workspace"] || l["header.context"]) {
70
+ let e = f.exec(r) ?? p.exec(r);
71
+ if (e && l["header.workspace"]) {
72
72
  let t = (e[1] ?? "").trim();
73
- if (t && !p.test(t)) return a({
73
+ if (t && !te.test(t)) return a({
74
74
  control: "header.workspace",
75
75
  target: t
76
76
  });
77
- if (w["header.context"]) return a({ control: "header.context" });
77
+ if (l["header.context"]) return a({ control: "header.context" });
78
78
  }
79
- if (w["header.context"] && (RegExp(`^${i} ${r}${d} ${f}$`).test(s) || RegExp(`^(?:switch|change) ${r}${d}$`).test(s))) return a({ control: "header.context" });
79
+ if (l["header.context"] && (m.test(r) || h.test(r))) return a({ control: "header.context" });
80
80
  }
81
- if (w["header.notifications"]) {
82
- if (m.test(s) || h.test(s)) return a({ control: "header.notifications" });
83
- if (ee.test(s)) return o({ control: "header.notifications" });
81
+ if (l["header.notifications"]) {
82
+ if (g.test(r) || _.test(r)) return a({ control: "header.notifications" });
83
+ if (v.test(r)) return o({ control: "header.notifications" });
84
84
  }
85
- if (w["header.help"]) {
86
- if (g.test(s)) return a({ control: "header.help" });
87
- if (_.test(s)) return o({ control: "header.help" });
85
+ if (l["header.help"]) {
86
+ if (y.test(r)) return a({ control: "header.help" });
87
+ if (b.test(r)) return o({ control: "header.help" });
88
88
  }
89
- if (w["header.search"]) {
90
- for (let e of b) {
91
- let t = e.exec(s), n = t ? $(t[1] ?? "") : null;
89
+ if (l["header.search"]) {
90
+ for (let e of C) {
91
+ let t = e.exec(r), n = t ? oe(t[1] ?? "") : null;
92
92
  if (n) return a({
93
93
  control: "header.search",
94
- query: c(l, n)
94
+ query: c(i, n)
95
95
  });
96
96
  }
97
- if (v.test(s)) return a({
97
+ if (x.test(r)) return a({
98
98
  control: "header.search",
99
99
  query: null
100
100
  });
101
- if (y.test(s)) return o({
101
+ if (S.test(r)) return o({
102
102
  control: "header.search",
103
103
  query: null
104
104
  });
105
105
  }
106
- if (w.sidebar) {
107
- if (S.test(s)) return a({
106
+ if (l.sidebar) {
107
+ if (T.test(r)) return a({
108
108
  control: "sidebar",
109
109
  collapsed: !0
110
110
  });
111
- if (C.test(s)) return a({
111
+ if (E.test(r)) return a({
112
112
  control: "sidebar",
113
113
  collapsed: !1
114
114
  });
115
- let e = T.exec(s), t = e ? null : E.exec(s), r = e ?? t;
116
- if (r && x.sidebarEntries.length > 0) {
117
- let t = (r[2] ?? "").trim(), i = r[1] ?? "";
118
- if (t && n(t, x.sidebarEntries).kind !== "none") return {
115
+ let e = O.exec(r), t = e ? null : k.exec(r), i = e ?? t;
116
+ if (i && s.sidebarEntries.length > 0) {
117
+ let t = (i[2] ?? "").trim(), r = i[1] ?? "";
118
+ if (t && n(t, s.sidebarEntries).kind !== "none") return {
119
119
  request: {
120
120
  control: "sidebar.entry",
121
121
  name: t,
122
122
  expanded: e !== null
123
123
  },
124
- weak: !/^(?:expand|unfold|open up|collapse|fold up)$/.test(i)
124
+ weak: !/^(?:expand|unfold|open up|collapse|fold up)$/.test(r)
125
125
  };
126
126
  }
127
127
  }
128
- if (w.language) {
129
- for (let e of D) {
130
- let n = e.exec(s);
128
+ if (l.language) {
129
+ for (let e of A) {
130
+ let n = e.exec(r);
131
131
  if (!n) continue;
132
- let r = t(n[1] ?? "");
133
- if (r) return a({
132
+ let i = t(n[1] ?? "");
133
+ if (i) return a({
134
134
  control: "language",
135
- code: r
135
+ code: i
136
136
  });
137
137
  }
138
- let e = O.exec(s), n = e ? t(e[1] ?? "") : null;
138
+ let e = j.exec(r), n = e ? t(e[1] ?? "") : null;
139
139
  if (n) return o({
140
140
  control: "language",
141
141
  code: n
142
142
  });
143
143
  }
144
- if (w["assistant.history"]) {
145
- if (k.test(s)) return a({ control: "assistant.history" });
146
- if (A.test(s)) return o({ control: "assistant.history" });
147
- }
148
- if (w["assistant.transcript"]) {
149
- if (j.test(s)) return a({ control: "assistant.transcript" });
150
- if (M.test(s)) return o({ control: "assistant.transcript" });
151
- }
152
- if (w["assistant.preview"]) {
153
- if (N.test(s)) return a({ control: "assistant.preview" });
154
- if (P.test(s)) return o({ control: "assistant.preview" });
155
- if (F.test(s)) return a({ control: "assistant.preview.close" });
156
- if (te.test(s)) return o({ control: "assistant.preview.close" });
157
- }
158
- if (w["assistant.conversation.new"] && I.test(s)) return a({ control: "assistant.conversation.new" });
159
- let R = x.page;
160
- if (!R) return null;
161
- if (R.tabs.length > 0) {
162
- let e = Z(L, s), t = e ? (e[1] ?? "").trim() : "";
144
+ if (l["assistant.history"]) {
145
+ if (M.test(r)) return a({ control: "assistant.history" });
146
+ if (N.test(r)) return o({ control: "assistant.history" });
147
+ }
148
+ if (l["assistant.transcript"]) {
149
+ if (P.test(r)) return a({ control: "assistant.transcript" });
150
+ if (F.test(r)) return o({ control: "assistant.transcript" });
151
+ }
152
+ if (l["assistant.preview"]) {
153
+ if (ne.test(r)) return a({ control: "assistant.preview" });
154
+ if (I.test(r)) return o({ control: "assistant.preview" });
155
+ if (L.test(r)) return a({ control: "assistant.preview.close" });
156
+ if (R.test(r)) return o({ control: "assistant.preview.close" });
157
+ }
158
+ if (l["assistant.conversation.new"] && z.test(r)) return a({ control: "assistant.conversation.new" });
159
+ let d = s.page;
160
+ if (!d) return null;
161
+ if (d.tabs.length > 0) {
162
+ let e = $(B, r), t = e ? (e[1] ?? "").trim() : "";
163
163
  if (t) {
164
- let e = n(t, R.tabs).kind !== "none";
164
+ let e = n(t, d.tabs).kind !== "none";
165
165
  return {
166
166
  request: {
167
167
  control: "page.tab",
@@ -171,30 +171,30 @@ function ne(s, l, x) {
171
171
  };
172
172
  }
173
173
  }
174
- if (R.filters.length > 0) {
175
- let e = H.exec(s);
174
+ if (d.filters.length > 0) {
175
+ let e = G.exec(r);
176
176
  if (e) return a({
177
177
  control: "page.filter.clear",
178
178
  filter: (e[1] ?? "").trim() || null
179
179
  });
180
- let t = Z(V, s);
180
+ let t = $(W, r);
181
181
  if (t) {
182
182
  let e = (t[1] ?? "").trim(), r = (t[2] ?? "").trim();
183
- if (e && r && n(e, R.filters).kind !== "none") return a({
183
+ if (e && r && n(e, d.filters).kind !== "none") return a({
184
184
  control: "page.filter",
185
185
  filter: e,
186
186
  option: r
187
187
  });
188
188
  }
189
- let r = Z(z, s);
190
- if (r && (r[1] ?? "").trim()) return a({
189
+ let i = $(H, r);
190
+ if (i && (i[1] ?? "").trim()) return a({
191
191
  control: "page.filter",
192
192
  filter: null,
193
- option: (r[1] ?? "").trim()
193
+ option: (i[1] ?? "").trim()
194
194
  });
195
- let i = Z(B, s);
196
- if (i) {
197
- let e = (i[1] ?? "").trim(), t = R.filters.some((t) => n(e, t.options).kind !== "none");
195
+ let o = $(U, r);
196
+ if (o) {
197
+ let e = (o[1] ?? "").trim(), t = d.filters.some((t) => n(e, t.options).kind !== "none");
198
198
  if (e && t) return a({
199
199
  control: "page.filter",
200
200
  filter: null,
@@ -202,16 +202,16 @@ function ne(s, l, x) {
202
202
  });
203
203
  }
204
204
  }
205
- if (R.sorts.length > 0) {
206
- let e = G.exec(s);
205
+ if (d.sorts.length > 0) {
206
+ let e = J.exec(r);
207
207
  if (e) return a({
208
208
  control: "page.sort",
209
209
  field: null,
210
210
  direction: /^asc/.test(e[1] ?? "") ? "asc" : "desc"
211
211
  });
212
- let t = W.exec(s);
212
+ let t = q.exec(r);
213
213
  if (t) {
214
- let { field: e, direction: n } = Q((t[1] ?? "").trim());
214
+ let { field: e, direction: n } = ae((t[1] ?? "").trim());
215
215
  if (e) return a({
216
216
  control: "page.sort",
217
217
  field: e,
@@ -219,27 +219,35 @@ function ne(s, l, x) {
219
219
  });
220
220
  }
221
221
  }
222
- if (R.searchable) {
223
- if (q.test(s)) return a({ control: "page.search.clear" });
224
- let e = Z(K, s), t = e ? $(e[1] ?? "") : null;
222
+ if (d.searchable) {
223
+ if (X.test(r)) return a({ control: "page.search.clear" });
224
+ let e = $(Y, r), t = e ? oe(e[1] ?? "") : null;
225
225
  if (t) return a({
226
226
  control: "page.search",
227
- query: c(l, t)
227
+ query: c(i, t)
228
228
  });
229
229
  }
230
- if (R.openers.length > 0) {
231
- let t = Z(J, s), r = t ? (t[1] ?? "").trim() : "";
232
- if (r && n(r, R.openers).kind !== "none") return a({
230
+ if (d.openers.length > 0) {
231
+ let t = $(Z, r), i = t ? (t[1] ?? "").trim() : "";
232
+ if (i && n(i, d.openers).kind !== "none") return a({
233
233
  control: "page.open",
234
- name: r
234
+ name: i
235
235
  });
236
- let i = Y.exec(s), c = i ? (i[1] ?? "").trim() : "";
237
- if (c && R.openers.filter((t) => e(t.label) === c).length > 0) return o({
236
+ let s = Q.exec(r), c = s ? (s[1] ?? "").trim() : "";
237
+ if (c && d.openers.filter((t) => e(t.label) === c).length > 0) return o({
238
238
  control: "page.open",
239
239
  name: c
240
240
  });
241
241
  }
242
242
  return null;
243
243
  }
244
+ function ce(e) {
245
+ let t = e.registered, n = [], r = (e, ...t) => {
246
+ e && n.push(...t);
247
+ };
248
+ r(t["account.logout"], u), r(t["header.workspace"], f, p), r(t["header.context"], m, h), r(t["header.notifications"], g, _, v), r(t["header.help"], y, b), r(t["header.search"], x, S, ...C), r(t.sidebar, T, E, O, k), r(t.language, ...A, j), r(t["assistant.history"], M, N), r(t["assistant.transcript"], P, F), r(t["assistant.preview"], ne, I, L, R), r(t["assistant.conversation.new"], z);
249
+ let i = e.page;
250
+ return i && (r(i.tabs.length > 0, ...B), r(i.filters.length > 0, ...H, ...U, ...W, G), r(i.sorts.length > 0, q, ...re, J), r(i.searchable, X, ...Y), r(i.openers.length > 0, ...Z, Q)), n;
251
+ }
244
252
  //#endregion
245
- export { ne as matchAppControl, c as queryAsSaid };
253
+ export { ce as appControlPrefixPatterns, se as matchAppControl, c as queryAsSaid };
@@ -1,4 +1,6 @@
1
1
  import { ControlKey, ControlMap } from './types';
2
+ /** Wake retained draft checks when registered controls mount, change or unmount. */
3
+ export declare function subscribeAssistantControls(listener: () => void): () => void;
2
4
  /** Register `impl` for `key`. Returns the cleanup that removes exactly this entry. */
3
5
  export declare function registerAssistantControl<K extends ControlKey>(key: K, impl: ControlMap[K]): () => void;
4
6
  /** The handler in force for `key` (the newest registration), or null. */
@@ -13,26 +13,34 @@ var e = {
13
13
  "assistant.preview": [],
14
14
  "assistant.conversation.new": [],
15
15
  page: []
16
+ }, t = /* @__PURE__ */ new Set(), n = () => {
17
+ for (let e of t) e();
16
18
  };
17
- function t(t, n) {
18
- let r = Symbol(t), i = e[t];
19
- return i.push({
20
- token: r,
21
- impl: n
22
- }), () => {
23
- let e = i.findIndex((e) => e.token === r);
24
- e >= 0 && i.splice(e, 1);
19
+ function r(e) {
20
+ return t.add(e), () => {
21
+ t.delete(e);
25
22
  };
26
23
  }
27
- function n(t) {
24
+ function i(t, r) {
25
+ let i = Symbol(t), a = e[t];
26
+ return a.push({
27
+ token: i,
28
+ impl: r
29
+ }), n(), () => {
30
+ let e = a.findIndex((e) => e.token === i);
31
+ e >= 0 && (a.splice(e, 1), n());
32
+ };
33
+ }
34
+ function a(t) {
28
35
  let n = e[t];
29
36
  return n.length > 0 ? n[n.length - 1]?.impl ?? null : null;
30
37
  }
31
- function r(t) {
38
+ function o(t) {
32
39
  return e[t].length > 0;
33
40
  }
34
- function i() {
41
+ function s() {
35
42
  for (let t of Object.keys(e)) e[t].length = 0;
43
+ n();
36
44
  }
37
45
  //#endregion
38
- export { i as clearAssistantControls, n as getAssistantControl, r as hasAssistantControl, t as registerAssistantControl };
46
+ export { s as clearAssistantControls, a as getAssistantControl, o as hasAssistantControl, i as registerAssistantControl, r as subscribeAssistantControls };
@@ -0,0 +1,9 @@
1
+ export interface CompletionOptions {
2
+ maxLength: number;
3
+ /** Exact live names for an open capture; an absent capture is free text. */
4
+ slots?: Readonly<Record<string, readonly string[]>>;
5
+ /** Also try live labels for legacy, unnamed open captures. */
6
+ names?: readonly string[];
7
+ }
8
+ /** Is there a continuation of prefix that the real matcher accepts? */
9
+ export declare function hasCommandCompletion(pattern: RegExp, prefix: string, options: CompletionOptions, accepts: (completion: string) => boolean): boolean;
@@ -0,0 +1,161 @@
1
+ //#region src/shared/assistant/commandCompletions.ts
2
+ var e = /* @__PURE__ */ new WeakMap(), t = " abcdefghijklmnopqrstuvwxyz0123456789";
3
+ function n(n) {
4
+ let r = e.get(n);
5
+ if (r) return r;
6
+ let i = n.source, a = 0, o = () => {
7
+ let e = [], n = [];
8
+ for (; a < i.length && i[a] !== ")";) {
9
+ let r = i[a++];
10
+ if (r === "|") {
11
+ e.push({
12
+ kind: "seq",
13
+ nodes: n
14
+ }), n = [];
15
+ continue;
16
+ }
17
+ let s;
18
+ if (r === "^" || r === "$") continue;
19
+ if (r === "(") {
20
+ let e;
21
+ if (i.startsWith("?:", a)) a += 2;
22
+ else if (i.startsWith("?<", a)) {
23
+ let t = i.indexOf(">", a);
24
+ if (t < 0) throw Error("Invalid command capture");
25
+ e = i.slice(a + 2, t), a = t + 1;
26
+ } else if (i[a] === "?") throw Error("Unsupported command assertion");
27
+ if (s = o(), i[a++] !== ")") throw Error("Unclosed command group");
28
+ e && (s = {
29
+ kind: "slot",
30
+ name: e,
31
+ node: s
32
+ });
33
+ } else if (r === ".") s = {
34
+ kind: "free",
35
+ min: 1,
36
+ max: 1
37
+ };
38
+ else if (r === "[") {
39
+ let e = a - 1;
40
+ for (; a < i.length && i[a] !== "]";) i[a] === "\\" && (a += 1), a += 1;
41
+ let n = RegExp(`^${i.slice(e, ++a)}$`, "u");
42
+ s = {
43
+ kind: "choice",
44
+ nodes: [...t].filter((e) => n.test(e)).map((e) => ({
45
+ kind: "text",
46
+ text: e
47
+ }))
48
+ };
49
+ } else if (r === "\\") {
50
+ let e = i[a++];
51
+ if (e === "b") continue;
52
+ if (e === "s") s = {
53
+ kind: "text",
54
+ text: " "
55
+ };
56
+ else if (e === "S") s = {
57
+ kind: "free",
58
+ min: 1,
59
+ max: 1
60
+ };
61
+ else if (e === "d") s = {
62
+ kind: "choice",
63
+ nodes: [..."0123456789"].map((e) => ({
64
+ kind: "text",
65
+ text: e
66
+ }))
67
+ };
68
+ else if (e && !/[a-zA-Z0-9]/.test(e)) s = {
69
+ kind: "text",
70
+ text: e
71
+ };
72
+ else throw Error("Unsupported command escape");
73
+ } else s = {
74
+ kind: "text",
75
+ text: r ?? ""
76
+ };
77
+ let c = 1, l = 1, u = i[a];
78
+ if (u === "?" || u === "*" || u === "+") a += 1, c = +(u === "+"), l = u === "?" ? 1 : Infinity;
79
+ else if (u === "{") {
80
+ let e = /^\{(\d+)(?:,(\d*))?\}/.exec(i.slice(a));
81
+ if (!e) throw Error("Unsupported command repetition");
82
+ a += e[0].length, c = Number(e[1]), l = e[2] === void 0 ? c : e[2] === "" ? Infinity : Number(e[2]);
83
+ }
84
+ i[a] === "?" && (c !== 1 || l !== 1) && (a += 1), (c !== 1 || l !== 1) && (s = s.kind === "free" ? {
85
+ kind: "free",
86
+ min: c,
87
+ max: l
88
+ } : {
89
+ kind: "repeat",
90
+ node: s,
91
+ min: c,
92
+ max: l
93
+ }), n.push(s);
94
+ }
95
+ return e.push({
96
+ kind: "seq",
97
+ nodes: n
98
+ }), e.length === 1 ? e[0] : {
99
+ kind: "choice",
100
+ nodes: e
101
+ };
102
+ }, s = o();
103
+ if (a !== i.length) throw Error("Unexpected command pattern suffix");
104
+ return e.set(n, s), s;
105
+ }
106
+ function r(e, t, r, i) {
107
+ if (t.length > r.maxLength) return !1;
108
+ let a = 0, o = (e) => e.length <= r.maxLength && (t.startsWith(e) || e.startsWith(t));
109
+ function* s(e, n) {
110
+ if (++a > 2e4) throw Error("Command completion budget");
111
+ if (e.kind === "text") {
112
+ let t = n + e.text;
113
+ o(t) && (yield t);
114
+ } else if (e.kind === "choice") for (let t of e.nodes) yield* s(t, n);
115
+ else if (e.kind === "seq") {
116
+ let { nodes: t } = e;
117
+ function* r(e, n) {
118
+ let i = t[e];
119
+ if (!i) {
120
+ yield n;
121
+ return;
122
+ }
123
+ for (let t of s(i, n)) yield* r(e + 1, t);
124
+ }
125
+ yield* r(0, n);
126
+ } else if (e.kind === "slot" && r.slots?.[e.name] !== void 0) for (let t of r.slots[e.name] ?? []) yield* s({
127
+ kind: "text",
128
+ text: t
129
+ }, n);
130
+ else if (e.kind === "slot") yield* s(e.node, n);
131
+ else if (e.kind === "repeat") {
132
+ let { min: t, max: i, node: a } = e;
133
+ function* o(e, n) {
134
+ if (e >= t && (yield n), !(e >= Math.min(i, r.maxLength + 1))) for (let t of s(a, n)) t !== n && (yield* o(e + 1, t));
135
+ }
136
+ yield* o(0, n);
137
+ } else {
138
+ let i = t.slice(Math.min(n.length, t.length)), a = new Set([
139
+ i,
140
+ i + "item",
141
+ "item",
142
+ "x",
143
+ ...r.names ?? []
144
+ ]), s = i.replace(/ /g, "");
145
+ for (let e of r.names ?? []) {
146
+ let t = e.replace(/ /g, "");
147
+ t.startsWith(s) && a.add(i + t.slice(s.length));
148
+ }
149
+ for (let e = i.length; e >= 0; --e) a.add(i.slice(0, e));
150
+ for (let t of a) t.length < e.min || t.length > e.max || o(n + t) && (yield n + t);
151
+ }
152
+ }
153
+ try {
154
+ for (let r of s(n(e), "")) if (r.startsWith(t) && e.test(r) && i(r)) return !0;
155
+ return !1;
156
+ } catch {
157
+ return !0;
158
+ }
159
+ }
160
+ //#endregion
161
+ export { r as hasCommandCompletion };
@@ -253,6 +253,7 @@ export * from './turnTimingReport';
253
253
  export * from './turnTimingReporter';
254
254
  export * from './prewarm';
255
255
  export * from './localUiCommands';
256
+ export * from './commandCompletions';
256
257
  export * from './localeCommands/types';
257
258
  export * from './localeCommands/fold';
258
259
  export * from './localeCommands/frames';
@@ -274,6 +275,7 @@ export * from './localeCommands/tables/en';
274
275
  export * from './localUiCommandCopy';
275
276
  export * from './uiActions/navigationOutcome';
276
277
  export * from './uiActions/backStack';
278
+ export * from './uiActions/draftPathname';
277
279
  export * from './uiActions/navigateAction';
278
280
  export * from './uiActions/assistantHref';
279
281
  export * from './uiActions/inAppLinks';
@@ -320,6 +322,7 @@ export * from './useAssistantHeartbeat';
320
322
  export * from './useAssistantKeyboardShortcuts';
321
323
  export * from './ui/AssistantApiCallsConsentDialog';
322
324
  export * from './ui/AssistantChatArea';
325
+ export * from './ui/draftCommandCheck';
323
326
  export * from './ui/AIAssistantPanel';
324
327
  export * from './ui/assistantOverlayState';
325
328
  export * from './ui/useAssistantLauncherLift';
@@ -212,6 +212,17 @@ export interface LocalUiCommandContext {
212
212
  * Absent: no app-control grammar runs, and the matcher is what it was.
213
213
  */
214
214
  controls?: ControlSnapshot;
215
+ /**
216
+ * Live host integrations for speculative drafts. Omitted by grammar-only
217
+ * callers; Send still checks the actual provider before running a command.
218
+ */
219
+ executors?: {
220
+ theme: boolean;
221
+ navigation: boolean;
222
+ conversationDelete?: boolean;
223
+ /** Build the current plan only; never run or confirm it during speculation. */
224
+ appControl?: (request: AppControlRequest) => boolean;
225
+ };
215
226
  }
216
227
  /**
217
228
  * The command this whole utterance is, or null to send it to the model.
@@ -222,6 +233,11 @@ export interface LocalUiCommandContext {
222
233
  * only for what the manifest has switched on.
223
234
  */
224
235
  export declare function matchLocalUiCommand(text: string, destinations: readonly LocalDestination[], options?: LocalUiCommandOptions, context?: LocalUiCommandContext): LocalUiCommand | null;
236
+ /**
237
+ * Pure speculative check for typed drafts. A complete command, or a prefix
238
+ * with a completion accepted by the SAME matcher, must not acquire a sandbox.
239
+ */
240
+ export declare function couldBeLocalUiCommand(text: string, destinations: readonly LocalDestination[], options?: LocalUiCommandOptions, context?: LocalUiCommandContext): boolean;
225
241
  /**
226
242
  * ★ BOFF-8060 / BOFF-8867: could this sentence be "take me there" or a pick —
227
243
  * in English or in an active language? The panel builds the offered-navigation