@calliopelabs/ui 0.14.0 → 0.18.0

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 (127) hide show
  1. package/README.md +235 -1
  2. package/dist/agent/AgentChat.d.ts +8 -1
  3. package/dist/agent/AgentChat.js +43 -2
  4. package/dist/agent/WorkflowRenderers.d.ts +61 -0
  5. package/dist/agent/WorkflowRenderers.js +102 -0
  6. package/dist/agent/controller.d.ts +19 -0
  7. package/dist/agent/controller.js +33 -0
  8. package/dist/agent/copilotkit/CopilotKitAgentProvider.js +2 -2
  9. package/dist/agent/index.d.ts +4 -2
  10. package/dist/agent/index.js +3 -1
  11. package/dist/agent/mock.d.ts +26 -4
  12. package/dist/agent/mock.js +68 -37
  13. package/dist/agent/renderers.d.ts +49 -0
  14. package/dist/agent/renderers.js +27 -0
  15. package/dist/appearance/brands.d.ts +17 -0
  16. package/dist/appearance/brands.js +14 -0
  17. package/dist/chat/MessageBubble.js +1 -1
  18. package/dist/chat/MessageList.d.ts +9 -2
  19. package/dist/chat/MessageList.js +11 -8
  20. package/dist/css/appearance.js +1 -1
  21. package/dist/css/calliope.css +5 -0
  22. package/dist/css/tokens.css +5 -0
  23. package/dist/index.d.ts +9 -1
  24. package/dist/index.js +8 -1
  25. package/dist/primitives/Badge.d.ts +14 -0
  26. package/dist/primitives/Badge.js +25 -0
  27. package/dist/primitives/ContextMenu.d.ts +18 -0
  28. package/dist/primitives/ContextMenu.js +34 -0
  29. package/dist/primitives/Menubar.d.ts +21 -0
  30. package/dist/primitives/Menubar.js +43 -0
  31. package/dist/primitives/ScrollArea.d.ts +16 -0
  32. package/dist/primitives/ScrollArea.js +11 -0
  33. package/dist/primitives/Toast.d.ts +37 -0
  34. package/dist/primitives/Toast.js +43 -0
  35. package/dist/primitives/Tooltip.d.ts +16 -0
  36. package/dist/primitives/Tooltip.js +26 -0
  37. package/dist/provider/messages.d.ts +217 -0
  38. package/dist/provider/messages.js +238 -0
  39. package/dist/shell/RailPanel.js +1 -1
  40. package/dist/theme/css-layer.js +2 -1
  41. package/dist/theme/tokens.generated.d.ts +3 -0
  42. package/dist/theme/tokens.generated.js +3 -0
  43. package/dist/workbench/ActivityBar.d.ts +37 -0
  44. package/dist/workbench/ActivityBar.js +57 -0
  45. package/dist/workbench/BottomPanel.d.ts +30 -0
  46. package/dist/workbench/BottomPanel.js +20 -0
  47. package/dist/workbench/CodeEditor.d.ts +48 -0
  48. package/dist/workbench/CodeEditor.js +153 -0
  49. package/dist/workbench/CommandPalette.d.ts +49 -0
  50. package/dist/workbench/CommandPalette.js +114 -0
  51. package/dist/workbench/DataChart.d.ts +45 -0
  52. package/dist/workbench/DataChart.js +146 -0
  53. package/dist/workbench/DataGrid.d.ts +89 -0
  54. package/dist/workbench/DataGrid.js +590 -0
  55. package/dist/workbench/DataProfile.d.ts +17 -0
  56. package/dist/workbench/DataProfile.js +51 -0
  57. package/dist/workbench/EditorTabs.d.ts +62 -0
  58. package/dist/workbench/EditorTabs.js +115 -0
  59. package/dist/workbench/FileBrowser.d.ts +62 -0
  60. package/dist/workbench/FileBrowser.js +117 -0
  61. package/dist/workbench/FileGrid.d.ts +13 -0
  62. package/dist/workbench/FileGrid.js +114 -0
  63. package/dist/workbench/FileIcon.d.ts +10 -0
  64. package/dist/workbench/FileIcon.js +24 -0
  65. package/dist/workbench/FileList.d.ts +35 -0
  66. package/dist/workbench/FileList.js +160 -0
  67. package/dist/workbench/FilePreview.d.ts +66 -0
  68. package/dist/workbench/FilePreview.js +108 -0
  69. package/dist/workbench/FileTree.d.ts +37 -0
  70. package/dist/workbench/FileTree.js +218 -0
  71. package/dist/workbench/GridCell.d.ts +19 -0
  72. package/dist/workbench/GridCell.js +50 -0
  73. package/dist/workbench/GridDetails.d.ts +31 -0
  74. package/dist/workbench/GridDetails.js +78 -0
  75. package/dist/workbench/MenuBar.d.ts +49 -0
  76. package/dist/workbench/MenuBar.js +25 -0
  77. package/dist/workbench/PaneGroup.d.ts +50 -0
  78. package/dist/workbench/PaneGroup.js +146 -0
  79. package/dist/workbench/PendingChangesBar.d.ts +20 -0
  80. package/dist/workbench/PendingChangesBar.js +19 -0
  81. package/dist/workbench/Shortcuts.d.ts +50 -0
  82. package/dist/workbench/Shortcuts.js +109 -0
  83. package/dist/workbench/SqlEditor.d.ts +21 -0
  84. package/dist/workbench/SqlEditor.js +79 -0
  85. package/dist/workbench/StatusBar.d.ts +28 -0
  86. package/dist/workbench/StatusBar.js +26 -0
  87. package/dist/workbench/TruncationBanner.d.ts +11 -0
  88. package/dist/workbench/TruncationBanner.js +12 -0
  89. package/dist/workbench/UploadDropzone.d.ts +52 -0
  90. package/dist/workbench/UploadDropzone.js +115 -0
  91. package/dist/workbench/WorkbenchShell.d.ts +36 -0
  92. package/dist/workbench/WorkbenchShell.js +20 -0
  93. package/dist/workbench/dnd.d.ts +17 -0
  94. package/dist/workbench/dnd.js +77 -0
  95. package/dist/workbench/download.d.ts +11 -0
  96. package/dist/workbench/download.js +28 -0
  97. package/dist/workbench/editor-theme.d.ts +42 -0
  98. package/dist/workbench/editor-theme.js +113 -0
  99. package/dist/workbench/grid-format.d.ts +59 -0
  100. package/dist/workbench/grid-format.js +169 -0
  101. package/dist/workbench/grid-model.d.ts +75 -0
  102. package/dist/workbench/grid-model.js +259 -0
  103. package/dist/workbench/grid-selection.d.ts +69 -0
  104. package/dist/workbench/grid-selection.js +183 -0
  105. package/dist/workbench/grid-stats.d.ts +58 -0
  106. package/dist/workbench/grid-stats.js +185 -0
  107. package/dist/workbench/index.d.ts +44 -0
  108. package/dist/workbench/index.js +47 -0
  109. package/dist/workbench/keys.d.ts +23 -0
  110. package/dist/workbench/keys.js +105 -0
  111. package/dist/workbench/model.d.ts +72 -0
  112. package/dist/workbench/model.js +214 -0
  113. package/dist/workbench/palette.d.ts +17 -0
  114. package/dist/workbench/palette.js +45 -0
  115. package/dist/workbench/pending.d.ts +92 -0
  116. package/dist/workbench/pending.js +207 -0
  117. package/dist/workbench/selection.d.ts +16 -0
  118. package/dist/workbench/selection.js +46 -0
  119. package/dist/workbench/sql-completion.d.ts +107 -0
  120. package/dist/workbench/sql-completion.js +475 -0
  121. package/dist/workbench/typeahead.d.ts +15 -0
  122. package/dist/workbench/typeahead.js +22 -0
  123. package/dist/workflow/WorkflowRuntimeControls.d.ts +21 -0
  124. package/dist/workflow/WorkflowRuntimeControls.js +54 -0
  125. package/dist/workflow/runtime-controls.d.ts +29 -0
  126. package/dist/workflow/runtime-controls.js +10 -0
  127. package/package.json +17 -2
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Schema-aware SQL completion, without Monaco: what to suggest at a cursor,
3
+ * given the text and a host-provided schema. `SqlEditor` turns the result
4
+ * into Monaco completion items; anything else (a plain textarea, a test) can
5
+ * use it as it is. Pure.
6
+ */
7
+ export const SQL_KEYWORDS = [
8
+ "SELECT",
9
+ "FROM",
10
+ "WHERE",
11
+ "AND",
12
+ "OR",
13
+ "NOT",
14
+ "IN",
15
+ "BETWEEN",
16
+ "LIKE",
17
+ "IS",
18
+ "NULL",
19
+ "AS",
20
+ "ON",
21
+ "JOIN",
22
+ "INNER",
23
+ "LEFT",
24
+ "RIGHT",
25
+ "FULL",
26
+ "OUTER",
27
+ "CROSS",
28
+ "GROUP BY",
29
+ "ORDER BY",
30
+ "HAVING",
31
+ "LIMIT",
32
+ "OFFSET",
33
+ "UNION",
34
+ "ALL",
35
+ "DISTINCT",
36
+ "INSERT INTO",
37
+ "VALUES",
38
+ "UPDATE",
39
+ "SET",
40
+ "DELETE",
41
+ "CREATE TABLE",
42
+ "ALTER TABLE",
43
+ "DROP TABLE",
44
+ "CREATE INDEX",
45
+ "DROP INDEX",
46
+ "TRUNCATE",
47
+ "EXISTS",
48
+ "CASE",
49
+ "WHEN",
50
+ "THEN",
51
+ "ELSE",
52
+ "END",
53
+ "CAST",
54
+ "COALESCE",
55
+ "NULLIF",
56
+ "WITH",
57
+ "RECURSIVE",
58
+ "ASC",
59
+ "DESC",
60
+ "TRUE",
61
+ "FALSE",
62
+ "DEFAULT",
63
+ "PRIMARY KEY",
64
+ "FOREIGN KEY",
65
+ "REFERENCES",
66
+ "CONSTRAINT",
67
+ "UNIQUE",
68
+ "CHECK",
69
+ "INDEX",
70
+ "USE",
71
+ "FETCH FIRST",
72
+ "ROWS ONLY",
73
+ "PERCENT",
74
+ "TOP",
75
+ "WINDOW",
76
+ ];
77
+ export const SQL_FUNCTIONS = [
78
+ "COUNT",
79
+ "SUM",
80
+ "AVG",
81
+ "MIN",
82
+ "MAX",
83
+ "ROUND",
84
+ "UPPER",
85
+ "LOWER",
86
+ "TRIM",
87
+ "LENGTH",
88
+ "SUBSTRING",
89
+ "REPLACE",
90
+ "CONCAT",
91
+ "NOW",
92
+ "CURRENT_TIMESTAMP",
93
+ "CURRENT_DATE",
94
+ "DATE",
95
+ "EXTRACT",
96
+ "COALESCE",
97
+ "NULLIF",
98
+ "CAST",
99
+ "CONVERT",
100
+ "ROW_NUMBER",
101
+ "RANK",
102
+ "DENSE_RANK",
103
+ "LAG",
104
+ "LEAD",
105
+ "FIRST_VALUE",
106
+ "LAST_VALUE",
107
+ "NTILE",
108
+ "PERCENT_RANK",
109
+ "CUME_DIST",
110
+ "NTH_VALUE",
111
+ "ARRAY_AGG",
112
+ "STRING_AGG",
113
+ "JSON_AGG",
114
+ "JSONB_AGG",
115
+ "ABS",
116
+ "CEIL",
117
+ "FLOOR",
118
+ "MOD",
119
+ "POWER",
120
+ "SQRT",
121
+ "LOG",
122
+ "EXP",
123
+ "LEFT",
124
+ "RIGHT",
125
+ "LPAD",
126
+ "RPAD",
127
+ "LTRIM",
128
+ "RTRIM",
129
+ "REVERSE",
130
+ "DATE_TRUNC",
131
+ "DATE_PART",
132
+ "AGE",
133
+ "INTERVAL",
134
+ "TO_CHAR",
135
+ "TO_DATE",
136
+ "TO_NUMBER",
137
+ "TO_TIMESTAMP",
138
+ "GREATEST",
139
+ "LEAST",
140
+ "GENERATE_SERIES",
141
+ ];
142
+ const WINDOW_SNIPPETS = [
143
+ {
144
+ label: "ROW_NUMBER() OVER ...",
145
+ insertText: "ROW_NUMBER() OVER (PARTITION BY ${1:column} ORDER BY ${2:column})",
146
+ detail: "Window: row number",
147
+ },
148
+ {
149
+ label: "RANK() OVER ...",
150
+ insertText: "RANK() OVER (PARTITION BY ${1:column} ORDER BY ${2:column})",
151
+ detail: "Window: rank",
152
+ },
153
+ {
154
+ label: "DENSE_RANK() OVER ...",
155
+ insertText: "DENSE_RANK() OVER (PARTITION BY ${1:column} ORDER BY ${2:column})",
156
+ detail: "Window: dense rank",
157
+ },
158
+ {
159
+ label: "LAG() OVER ...",
160
+ insertText: "LAG(${1:column}, ${2:1}) OVER (ORDER BY ${3:column})",
161
+ detail: "Window: previous row value",
162
+ },
163
+ {
164
+ label: "LEAD() OVER ...",
165
+ insertText: "LEAD(${1:column}, ${2:1}) OVER (ORDER BY ${3:column})",
166
+ detail: "Window: next row value",
167
+ },
168
+ {
169
+ label: "SUM() OVER ...",
170
+ insertText: "SUM(${1:column}) OVER (PARTITION BY ${2:column} ORDER BY ${3:column})",
171
+ detail: "Window: running sum",
172
+ },
173
+ {
174
+ label: "AVG() OVER ...",
175
+ insertText: "AVG(${1:column}) OVER (PARTITION BY ${2:column} ORDER BY ${3:column})",
176
+ detail: "Window: running average",
177
+ },
178
+ {
179
+ label: "COUNT() OVER ...",
180
+ insertText: "COUNT(${1:*}) OVER (PARTITION BY ${2:column})",
181
+ detail: "Window: count per partition",
182
+ },
183
+ {
184
+ label: "NTILE() OVER ...",
185
+ insertText: "NTILE(${1:4}) OVER (ORDER BY ${2:column})",
186
+ detail: "Window: distribute into N buckets",
187
+ },
188
+ ];
189
+ /** Case-insensitive substring match; an empty input matches everything. */
190
+ export function sqlMatch(input, candidate) {
191
+ return !input || candidate.toLowerCase().includes(input.toLowerCase());
192
+ }
193
+ /** What the cursor is in, from the text before it: after FROM, after SELECT, after `alias.`. */
194
+ export function sqlCursorContext(textBeforeCursor) {
195
+ const trimmed = textBeforeCursor.trimEnd();
196
+ const dot = /([A-Za-z_][\w$]*)\.([\w$]*)$/.exec(trimmed);
197
+ if (dot)
198
+ return { context: "dot", prefix: dot[1] ?? "", word: dot[2] ?? "" };
199
+ const upper = trimmed.toUpperCase().replace(/\s+/g, " ");
200
+ if (/\bUSE$/.test(upper))
201
+ return { context: "database" };
202
+ if (/(?:FROM|JOIN|INTO|UPDATE|TABLE)$/.test(upper) ||
203
+ /(?:FROM|JOIN|INTO|UPDATE|TABLE) [\w, ]*,$/.test(upper)) {
204
+ return { context: "table" };
205
+ }
206
+ if (/(?:SELECT|WHERE|AND|OR|ON|SET|HAVING|BY)$/.test(upper) ||
207
+ /(?:SELECT|WHERE|AND|OR|ON|SET|HAVING|BY) [\w.]+ ?,$/.test(upper)) {
208
+ return { context: "column" };
209
+ }
210
+ return { context: "general" };
211
+ }
212
+ /**
213
+ * The statement around `offset` in a buffer of several. Semicolons inside
214
+ * string literals and line comments do not split, so aliases resolve against
215
+ * the statement being written, never an earlier one.
216
+ */
217
+ export function statementAt(text, offset) {
218
+ let start = 0;
219
+ let inSingle = false;
220
+ let inDouble = false;
221
+ for (let i = 0; i < text.length; i++) {
222
+ const ch = text[i];
223
+ if (ch === "'" && !inDouble) {
224
+ if (inSingle && text[i + 1] === "'") {
225
+ i++;
226
+ continue;
227
+ }
228
+ inSingle = !inSingle;
229
+ }
230
+ else if (ch === '"' && !inSingle) {
231
+ inDouble = !inDouble;
232
+ }
233
+ else if (ch === "-" && text[i + 1] === "-" && !inSingle && !inDouble) {
234
+ const nl = text.indexOf("\n", i);
235
+ if (nl === -1)
236
+ break;
237
+ i = nl;
238
+ }
239
+ else if (ch === ";" && !inSingle && !inDouble) {
240
+ if (i >= offset)
241
+ return text.slice(start, i);
242
+ start = i + 1;
243
+ }
244
+ }
245
+ return text.slice(start);
246
+ }
247
+ const NON_ALIAS = new Set([
248
+ ...SQL_KEYWORDS.map((k) => k.split(" ")[0] ?? k),
249
+ "GROUP",
250
+ "ORDER",
251
+ "NATURAL",
252
+ "LATERAL",
253
+ "USING",
254
+ "EXCEPT",
255
+ "INTERSECT",
256
+ "RETURNING",
257
+ "TABLESAMPLE",
258
+ "PARTITION",
259
+ "STRAIGHT_JOIN",
260
+ "FETCH",
261
+ "FOR",
262
+ ]);
263
+ const IDENT = String.raw `(?:"[^"]+"|\`[^\`]+\`|[A-Za-z_][\w$]*)`;
264
+ const TABLE_REF = new RegExp(String.raw `\b(?:FROM|JOIN|UPDATE|INTO)\s+(${IDENT}(?:\s*\.\s*${IDENT})*)(?:\s+(?:AS\s+)?(${IDENT}))?`, "gi");
265
+ const unquote = (ident) => ident.replace(/^["`]|["`]$/g, "").toLowerCase();
266
+ /** The tables a statement's FROM, JOIN, UPDATE and INTO name, with their aliases. */
267
+ export function extractTableRefs(statement) {
268
+ const refs = [];
269
+ for (const match of statement.matchAll(TABLE_REF)) {
270
+ const parts = (match[1] ?? "").split(".").map((p) => unquote(p.trim()));
271
+ const table = parts[parts.length - 1] ?? "";
272
+ const schema = parts.length > 1 ? parts[parts.length - 2] : undefined;
273
+ const raw = match[2];
274
+ const alias = raw && !NON_ALIAS.has(unquote(raw).toUpperCase()) ? unquote(raw) : undefined;
275
+ refs.push({ table, schema, alias });
276
+ }
277
+ return refs;
278
+ }
279
+ /**
280
+ * What `prefix.` refers to: an alias in this statement first, then a table
281
+ * the statement names, a known table, a known schema, a known database. An
282
+ * unrecognised prefix is unknown, and nothing should be requested for it.
283
+ */
284
+ export function resolveSqlDot(prefix, statement, schema) {
285
+ const needle = unquote(prefix);
286
+ if (!needle)
287
+ return { kind: "unknown" };
288
+ const refs = extractTableRefs(statement);
289
+ const aliased = refs.find((r) => r.alias === needle);
290
+ if (aliased)
291
+ return { kind: "columns", table: aliased.table };
292
+ const named = refs.find((r) => !r.alias && r.table === needle);
293
+ if (named)
294
+ return { kind: "columns", table: named.table };
295
+ const known = schema.tables.find((t) => t.name.toLowerCase() === needle);
296
+ if (known)
297
+ return { kind: "columns", table: known.name };
298
+ const sch = schema.schemas?.find((s) => s.toLowerCase() === needle);
299
+ if (sch)
300
+ return { kind: "schema", schema: sch };
301
+ const db = schema.databases?.find((d) => d.toLowerCase() === needle);
302
+ if (db)
303
+ return { kind: "database", database: db };
304
+ return { kind: "unknown" };
305
+ }
306
+ /** Column names that more than one table has, which completion qualifies. */
307
+ export function ambiguousColumns(tables) {
308
+ const seen = new Map();
309
+ for (const t of tables) {
310
+ for (const c of t.columns ?? []) {
311
+ const key = c.name.toLowerCase();
312
+ seen.set(key, (seen.get(key) ?? 0) + 1);
313
+ }
314
+ }
315
+ return new Set([...seen].filter(([, n]) => n > 1).map(([k]) => k));
316
+ }
317
+ /** True when a statement changes the schema, so a host's cached schema is stale. */
318
+ export function isDdlStatement(sql) {
319
+ return /^\s*(CREATE|ALTER|DROP|TRUNCATE|RENAME|COMMENT\s+ON|REFRESH\s+MATERIALIZED)\b/i.test(sql);
320
+ }
321
+ const columnDetail = (c, table) => [c.type, c.nullable ? "nullable" : undefined, table].filter(Boolean).join(" · ");
322
+ /**
323
+ * The completions at `offset` in `text`, from the schema the host gave.
324
+ * Never waits for anything: what the schema lacks comes back as `requests`.
325
+ */
326
+ export function sqlCompletions(text, offset, schema) {
327
+ const before = text.slice(0, offset);
328
+ const word = /[\w$]*$/.exec(before)?.[0] ?? "";
329
+ const ctx = sqlCursorContext(before);
330
+ const items = [];
331
+ const requests = [];
332
+ const databases = schema.databases ?? [];
333
+ const tables = (list, sort, detail) => {
334
+ for (const t of list) {
335
+ if (sqlMatch(word, t.name)) {
336
+ items.push({
337
+ label: t.name,
338
+ kind: "table",
339
+ insertText: t.name,
340
+ detail: detail(t),
341
+ sortText: sort + t.name,
342
+ });
343
+ }
344
+ }
345
+ };
346
+ const dbs = (sort) => {
347
+ for (const d of databases) {
348
+ if (sqlMatch(word, d))
349
+ items.push({
350
+ label: d,
351
+ kind: "database",
352
+ insertText: d,
353
+ detail: "Database",
354
+ sortText: sort + d,
355
+ });
356
+ }
357
+ };
358
+ const windows = () => {
359
+ for (const s of WINDOW_SNIPPETS) {
360
+ if (sqlMatch(word, s.label)) {
361
+ items.push({
362
+ label: s.label,
363
+ kind: "snippet",
364
+ insertText: s.insertText,
365
+ snippet: true,
366
+ detail: s.detail,
367
+ sortText: "1w" + s.label,
368
+ });
369
+ }
370
+ }
371
+ };
372
+ const tableDetail = (t) => (t.schema ? `Table · ${t.schema}` : "Table");
373
+ switch (ctx.context) {
374
+ case "database":
375
+ dbs("0");
376
+ break;
377
+ case "table":
378
+ tables(schema.tables, "0", tableDetail);
379
+ dbs("1");
380
+ break;
381
+ case "dot": {
382
+ // The half-typed `prefix.word` is not a table reference of the statement.
383
+ const start = before.lastIndexOf(`${ctx.prefix}.${ctx.word}`);
384
+ const rest = start >= 0 ? text.slice(0, start) + text.slice(offset) : text;
385
+ const target = resolveSqlDot(ctx.prefix, statementAt(rest, Math.max(start, 0)), schema);
386
+ if (target.kind === "columns") {
387
+ const table = schema.tables.find((t) => t.name.toLowerCase() === target.table.toLowerCase());
388
+ if (table?.columns) {
389
+ for (const c of table.columns) {
390
+ if (sqlMatch(word, c.name)) {
391
+ items.push({
392
+ label: c.name,
393
+ kind: "column",
394
+ insertText: c.name,
395
+ detail: columnDetail(c, table.name),
396
+ sortText: "0" + c.name,
397
+ });
398
+ }
399
+ }
400
+ }
401
+ else if (table) {
402
+ requests.push({ kind: "columns", table: table.name, schema: table.schema });
403
+ }
404
+ }
405
+ else if (target.kind === "schema") {
406
+ const inSchema = schema.tables.filter((t) => !t.schema || t.schema.toLowerCase() === target.schema.toLowerCase());
407
+ tables(inSchema, "0", () => `Table · ${target.schema}`);
408
+ }
409
+ else if (target.kind === "database") {
410
+ const other = schema.otherDatabases?.[target.database];
411
+ if (other)
412
+ tables(other, "0", () => `Table · ${target.database}`);
413
+ else
414
+ requests.push({ kind: "database", database: target.database });
415
+ }
416
+ break;
417
+ }
418
+ case "column": {
419
+ const ambiguous = ambiguousColumns(schema.tables);
420
+ const seen = new Set();
421
+ for (const t of schema.tables) {
422
+ for (const c of t.columns ?? []) {
423
+ if (!sqlMatch(word, c.name))
424
+ continue;
425
+ const key = c.name.toLowerCase();
426
+ const ambig = ambiguous.has(key);
427
+ if (ambig) {
428
+ items.push({
429
+ label: `${t.name}.${c.name}`,
430
+ kind: "column",
431
+ insertText: `${t.name}.${c.name}`,
432
+ detail: columnDetail(c, t.name),
433
+ sortText: "0a" + c.name,
434
+ });
435
+ }
436
+ if (!seen.has(key)) {
437
+ seen.add(key);
438
+ items.push({
439
+ label: c.name,
440
+ kind: "column",
441
+ insertText: c.name,
442
+ detail: columnDetail(c, t.name),
443
+ sortText: (ambig ? "0b" : "0") + c.name,
444
+ });
445
+ }
446
+ }
447
+ }
448
+ tables(schema.tables, "1", () => "Table");
449
+ windows();
450
+ break;
451
+ }
452
+ default: {
453
+ for (const k of SQL_KEYWORDS) {
454
+ if (sqlMatch(word, k))
455
+ items.push({ label: k, kind: "keyword", insertText: k, sortText: "2" + k });
456
+ }
457
+ for (const f of SQL_FUNCTIONS) {
458
+ if (sqlMatch(word, f)) {
459
+ items.push({
460
+ label: f,
461
+ kind: "function",
462
+ insertText: `${f}(\${1})`,
463
+ snippet: true,
464
+ detail: "Function",
465
+ sortText: "1" + f,
466
+ });
467
+ }
468
+ }
469
+ windows();
470
+ tables(schema.tables, "0", tableDetail);
471
+ dbs("3");
472
+ }
473
+ }
474
+ return { items, requests };
475
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Type-to-find for trees, lists and grids: printable keys typed within half
3
+ * a second build a prefix; the next name after the current one that starts
4
+ * with it (case-insensitive) is the answer. Returns that index, or null when
5
+ * the key is not a typeahead key or nothing matches.
6
+ */
7
+ export declare function typeahead(state: {
8
+ text: string;
9
+ at: number;
10
+ }, event: {
11
+ key: string;
12
+ ctrlKey: boolean;
13
+ metaKey: boolean;
14
+ altKey: boolean;
15
+ }, names: readonly string[], current: number): number | null;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Type-to-find for trees, lists and grids: printable keys typed within half
3
+ * a second build a prefix; the next name after the current one that starts
4
+ * with it (case-insensitive) is the answer. Returns that index, or null when
5
+ * the key is not a typeahead key or nothing matches.
6
+ */
7
+ export function typeahead(state, event, names, current) {
8
+ if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey || event.key === " ")
9
+ return null;
10
+ const now = Date.now();
11
+ state.text = now - state.at > 500 ? event.key : state.text + event.key;
12
+ state.at = now;
13
+ const prefix = state.text.toLowerCase();
14
+ // A repeated single letter cycles; a longer prefix may stay on the current name.
15
+ const start = state.text.length === 1 ? current + 1 : current;
16
+ for (let i = 0; i < names.length; i++) {
17
+ const index = (start + i) % names.length;
18
+ if (names[index]?.toLowerCase().startsWith(prefix))
19
+ return index;
20
+ }
21
+ return null;
22
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import { type WorkflowRuntimeState, type WorkflowRuntimeBinding, type WorkflowPauseReceipt, type WorkflowRuntimeIntent } from "./runtime-controls.js";
3
+ export interface WorkflowRuntimeControlsProps {
4
+ state: WorkflowRuntimeState;
5
+ /** Current execution phase, independent of the last control's delivery state. */
6
+ phase: "running" | "paused" | null;
7
+ binding: WorkflowRuntimeBinding | null;
8
+ /** Verified by the host against the current runtime. A scheduling pause supplies no receipt. */
9
+ pausedReceipt?: WorkflowPauseReceipt | null;
10
+ draft?: string;
11
+ onDraftChange?: (value: string) => void;
12
+ /** The host must retain request identity before delivery. No network calls occur here. */
13
+ onIntent?: (intent: WorkflowRuntimeIntent) => void;
14
+ onRefresh?: () => void;
15
+ canControl?: boolean;
16
+ loading?: boolean;
17
+ reason?: string;
18
+ className?: string;
19
+ }
20
+ /** Controlled original-agent controls. Unknown delivery cannot emit a new operation. */
21
+ export declare function WorkflowRuntimeControls({ state, phase, binding, pausedReceipt, draft, onDraftChange, onIntent, onRefresh, canControl, loading, reason, className, }: WorkflowRuntimeControlsProps): React.JSX.Element;
@@ -0,0 +1,54 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Button } from "../primitives/Button.js";
6
+ import { Field } from "../primitives/Field.js";
7
+ import { Textarea } from "../primitives/Textarea.js";
8
+ import { Skeleton } from "../primitives/Skeleton.js";
9
+ import { StatusBadge } from "../status/StatusBadge.js";
10
+ import { useUI } from "../provider/UIProvider.js";
11
+ import { validWorkflowSteering, } from "./runtime-controls.js";
12
+ const tone = {
13
+ running: "info",
14
+ paused: "warn",
15
+ "pause-requested": "info",
16
+ "resume-requested": "info",
17
+ "steering-queued": "info",
18
+ "steering-applied": "ok",
19
+ refused: "error",
20
+ "delivery-unknown": "warn",
21
+ unavailable: "muted",
22
+ finished: "muted",
23
+ };
24
+ /** Controlled original-agent controls. Unknown delivery cannot emit a new operation. */
25
+ export function WorkflowRuntimeControls({ state, phase, binding, pausedReceipt, draft = "", onDraftChange, onIntent, onRefresh, canControl = false, loading = false, reason, className, }) {
26
+ const t = useUI().messages.workflowRuntime;
27
+ const id = React.useId();
28
+ const running = phase === "running" &&
29
+ (state === "running" || state === "steering-applied" || state === "refused");
30
+ const editable = !loading && canControl && !!binding && !!onIntent;
31
+ const canResume = editable &&
32
+ phase === "paused" &&
33
+ (state === "paused" || state === "refused") &&
34
+ !!pausedReceipt &&
35
+ pausedReceipt.kind === "paused" &&
36
+ pausedReceipt.sessionId === binding?.sessionId &&
37
+ pausedReceipt.revision === binding?.revision &&
38
+ pausedReceipt.checksum === binding?.checksum;
39
+ const valid = validWorkflowSteering(draft);
40
+ function send(operation) {
41
+ if (!editable || !binding)
42
+ return;
43
+ if (operation === "resume" && canResume && pausedReceipt)
44
+ onIntent?.({ operation, binding: { ...binding }, pausedReceipt: { ...pausedReceipt } });
45
+ else if (operation === "pause" && running)
46
+ onIntent?.({ operation, binding: { ...binding } });
47
+ else if (operation === "steer" && running && valid)
48
+ onIntent?.({ operation, binding: { ...binding }, text: draft });
49
+ }
50
+ return (_jsxs("section", { "aria-labelledby": `${id}-title`, "aria-busy": loading, "data-slot": "workflow-runtime-controls", className: cn("space-y-4 rounded-lg border bg-card p-4 text-card-foreground", className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h3", { id: `${id}-title`, className: "font-medium break-words", children: t.title }), !loading && (_jsx(StatusBadge, { tone: tone[state], pulse: ["pause-requested", "resume-requested", "steering-queued"].includes(state), children: t.state(state) }))] }), _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: loading ? t.loading : t.description(state) }), reason && _jsx("p", { className: "text-sm break-words text-muted-foreground", children: reason }), loading ? (_jsx(Skeleton, { className: "h-20 w-full" })) : (_jsxs(_Fragment, { children: [!binding && _jsx("p", { className: "text-sm text-muted-foreground", children: t.empty }), !canControl && _jsx("p", { className: "text-sm text-muted-foreground", children: t.readOnly }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "button", variant: "outline", disabled: !editable || !running, onClick: () => send("pause"), children: t.pause }), _jsx(Button, { type: "button", variant: "outline", disabled: !canResume, onClick: () => send("resume"), children: t.resume }), onRefresh && (_jsx(Button, { type: "button", variant: "ghost", onClick: onRefresh, children: t.refresh }))] }), _jsxs("form", { className: "space-y-3", onSubmit: (event) => {
51
+ event.preventDefault();
52
+ send("steer");
53
+ }, children: [_jsx(Field, { label: t.steering, hint: t.steeringHint, error: draft.length > 0 && !valid ? t.invalidSteering : undefined, children: _jsx(Textarea, { value: draft, readOnly: !editable || !running || !onDraftChange, onChange: (event) => onDraftChange?.(event.target.value) }) }), _jsx(Button, { type: "submit", disabled: !editable || !running || !valid || !onDraftChange, children: t.send })] })] }))] }));
54
+ }
@@ -0,0 +1,29 @@
1
+ /** Confirmed host projection; submitting intent never changes these states locally. */
2
+ export type WorkflowRuntimeState = "running" | "paused" | "pause-requested" | "resume-requested" | "steering-queued" | "steering-applied" | "refused" | "delivery-unknown" | "unavailable" | "finished";
3
+ export interface WorkflowRuntimeBinding {
4
+ taskId: string;
5
+ invocationId: string;
6
+ sessionId: string;
7
+ revision: string;
8
+ checksum: string;
9
+ }
10
+ export interface WorkflowPauseReceipt {
11
+ sessionId: string;
12
+ requestId: string;
13
+ revision: string;
14
+ checksum: string;
15
+ kind: "paused";
16
+ }
17
+ export type WorkflowRuntimeIntent = {
18
+ operation: "pause";
19
+ binding: WorkflowRuntimeBinding;
20
+ } | {
21
+ operation: "resume";
22
+ binding: WorkflowRuntimeBinding;
23
+ pausedReceipt: WorkflowPauseReceipt;
24
+ } | {
25
+ operation: "steer";
26
+ binding: WorkflowRuntimeBinding;
27
+ text: string;
28
+ };
29
+ export declare function validWorkflowSteering(text: string): boolean;
@@ -0,0 +1,10 @@
1
+ export function validWorkflowSteering(text) {
2
+ const characters = Array.from(text);
3
+ return (text.trim().length > 0 &&
4
+ characters.length <= 4000 &&
5
+ !text.includes("\0") &&
6
+ characters.every((c) => {
7
+ const code = c.codePointAt(0);
8
+ return code < 0xd800 || code > 0xdfff;
9
+ }));
10
+ }