@pillar-ai/sdk 0.1.18 → 0.1.21

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.
@@ -29,5 +29,5 @@
29
29
  *
30
30
  * @module actions
31
31
  */
32
- export type { ActionType, ActionDataSchema, ActionDefinition, ActionDefinitions, ActionManifest, ActionManifestEntry, ClientInfo, Platform, SyncActionDefinition, SyncActionDefinitions, ActionTypeDataMap, NavigateActionData, TriggerActionData, InlineUIData, ExternalLinkData, CopyTextData, ActionDataType, ActionNames, TypedTaskHandler, TypedOnTask, TypedPillarMethods, } from './types';
32
+ export type { ActionType, ActionDataSchema, ActionDefinition, ActionDefinitions, ActionManifest, ActionManifestEntry, ClientInfo, Platform, SyncActionDefinition, SyncActionDefinitions, ActionTypeDataMap, NavigateActionData, TriggerActionData, InlineUIData, ExternalLinkData, CopyTextData, ActionDataType, ActionNames, TypedTaskHandler, TypedOnTask, TypedPillarMethods, ActionResult, ActionSchema, } from './types';
33
33
  export { setClientInfo, getClientInfo, getHandler, getActionDefinition, hasAction, getActionNames, getManifest, clearRegistry, getActionCount, } from './registry';
@@ -439,3 +439,92 @@ export interface TypedOnTask<TActions extends SyncActionDefinitions | ActionDefi
439
439
  export interface TypedPillarMethods<TActions extends SyncActionDefinitions | ActionDefinitions> {
440
440
  onTask: TypedOnTask<TActions>;
441
441
  }
442
+ /**
443
+ * Result returned from an action's execute function.
444
+ *
445
+ * Follows the MCP tool result format. Plain objects are also accepted
446
+ * by the SDK and normalized to this shape automatically.
447
+ */
448
+ export interface ActionResult {
449
+ content: Array<{
450
+ type: 'text';
451
+ text: string;
452
+ } | {
453
+ type: 'image';
454
+ data: string;
455
+ mimeType: string;
456
+ }>;
457
+ isError?: boolean;
458
+ }
459
+ /**
460
+ * Unified action definition that co-locates metadata and handler.
461
+ *
462
+ * Use with `pillar.defineAction()` or the `usePillarAction()` React hook.
463
+ * The CLI scanner (`npx pillar-sync --scan ./src`) discovers these
464
+ * definitions automatically — no barrel file needed.
465
+ *
466
+ * @template TInput - Type of the input object passed to `execute`
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * pillar.defineAction({
471
+ * name: 'add_to_cart',
472
+ * description: 'Add a product to the shopping cart',
473
+ * inputSchema: {
474
+ * type: 'object',
475
+ * properties: {
476
+ * productId: { type: 'string', description: 'Product ID' },
477
+ * quantity: { type: 'number', description: 'Quantity to add' },
478
+ * },
479
+ * required: ['productId', 'quantity'],
480
+ * },
481
+ * execute: async ({ productId, quantity }) => {
482
+ * await cartApi.add(productId, quantity);
483
+ * return { content: [{ type: 'text', text: 'Added to cart' }] };
484
+ * },
485
+ * });
486
+ * ```
487
+ */
488
+ export interface ActionSchema<TInput = Record<string, unknown>> {
489
+ /** Unique action name (e.g., 'add_to_cart') */
490
+ name: string;
491
+ /** Human-readable description for AI matching */
492
+ description: string;
493
+ /**
494
+ * Type of action - determines how the SDK handles it and organizes it in the UI.
495
+ */
496
+ type?: ActionType;
497
+ /**
498
+ * JSON Schema describing the input parameters.
499
+ * The AI extracts structured data from the conversation to populate these.
500
+ */
501
+ inputSchema?: {
502
+ type: 'object';
503
+ properties: Record<string, unknown>;
504
+ required?: string[];
505
+ };
506
+ /**
507
+ * Example user queries that should trigger this action.
508
+ * Used for semantic matching alongside the description.
509
+ */
510
+ examples?: string[];
511
+ /**
512
+ * Whether to auto-execute without user confirmation.
513
+ * @default false
514
+ */
515
+ autoRun?: boolean;
516
+ /**
517
+ * Whether the action completes immediately after execution.
518
+ * @default true
519
+ */
520
+ autoComplete?: boolean;
521
+ /**
522
+ * Handler function executed when the AI invokes this action.
523
+ *
524
+ * Can return:
525
+ * - An `ActionResult` with MCP-style content blocks
526
+ * - A plain object (SDK normalizes it for the agent)
527
+ * - `void` if the action has no return value
528
+ */
529
+ execute: (input: TInput) => Promise<ActionResult | unknown | void> | ActionResult | unknown | void;
530
+ }
package/dist/cli/sync.js CHANGED
@@ -4,7 +4,6 @@
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import { execSync } from "child_process";
7
- import { pathToFileURL } from "url";
8
7
  var DEFAULT_API_URL = "https://help-api.trypillar.com";
9
8
  var LOCAL_API_URL = "http://localhost:8003";
10
9
  function parseArgs(args) {
@@ -28,12 +27,13 @@ function printUsage() {
28
27
  console.log(`
29
28
  Pillar Action Sync CLI
30
29
 
30
+ Scans for usePillarAction/defineAction calls and syncs to the Pillar backend.
31
+
31
32
  Usage:
32
- npx pillar-sync --actions <path> [--local]
33
+ npx pillar-sync --scan <dir> [--local]
33
34
 
34
35
  Arguments:
35
- --actions <path> Path to your actions definition file (required)
36
- Supports .ts, .js, .mjs files
36
+ --scan <dir> Directory to scan for usePillarAction/defineAction calls
37
37
  --local Use localhost:8003 as the API URL (for local development)
38
38
  --help Show this help message
39
39
 
@@ -46,157 +46,13 @@ Environment Variables:
46
46
  GIT_SHA Git commit SHA for traceability
47
47
 
48
48
  Examples:
49
- # Production
50
- PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts
49
+ # Scan and sync actions
50
+ PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --scan ./src
51
51
 
52
52
  # Local development
53
- PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts --local
53
+ PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --scan ./src --local
54
54
  `);
55
55
  }
56
- async function loadActions(actionsPath) {
57
- const absolutePath = path.resolve(process.cwd(), actionsPath);
58
- if (!fs.existsSync(absolutePath)) {
59
- throw new Error(`Actions file not found: ${absolutePath}`);
60
- }
61
- const isTypeScript = absolutePath.endsWith(".ts") || absolutePath.endsWith(".tsx");
62
- if (isTypeScript) {
63
- try {
64
- const tempDir = path.dirname(absolutePath);
65
- const tempFile = path.join(tempDir, `.pillar-sync-temp-${Date.now()}.mjs`);
66
- const importPath = absolutePath.replace(/\\/g, "/");
67
- const extractScript = `import * as module from '${importPath}';
68
-
69
- // Resolve actions - tsx may wrap all exports in module.default
70
- function resolveActions(mod) {
71
- // Direct named export
72
- if (mod.actions && typeof mod.actions === 'object' && !mod.actions.default) {
73
- return mod.actions;
74
- }
75
- // Default export is the actions object directly
76
- if (mod.default && typeof mod.default === 'object') {
77
- // Check if default is a module namespace (has nested default or actions)
78
- if (mod.default.default && typeof mod.default.default === 'object') {
79
- return mod.default.default;
80
- }
81
- if (mod.default.actions && typeof mod.default.actions === 'object') {
82
- return mod.default.actions;
83
- }
84
- // default is the actions object itself
85
- return mod.default;
86
- }
87
- return null;
88
- }
89
-
90
- const actions = resolveActions(module);
91
- const agentGuidance = module.agentGuidance || module.default?.agentGuidance;
92
- console.log(JSON.stringify({ actions, agentGuidance }));`;
93
- fs.writeFileSync(tempFile, extractScript, "utf-8");
94
- try {
95
- const result = execSync(`npx tsx "${tempFile}"`, {
96
- encoding: "utf-8",
97
- cwd: process.cwd(),
98
- stdio: ["pipe", "pipe", "pipe"]
99
- });
100
- const parsed = JSON.parse(result.trim());
101
- const actions = parsed.actions;
102
- const agentGuidance = parsed.agentGuidance;
103
- if (!actions || typeof actions !== "object") {
104
- throw new Error(
105
- 'Actions file must export an actions object as default or named export "actions"'
106
- );
107
- }
108
- return { actions, agentGuidance };
109
- } finally {
110
- if (fs.existsSync(tempFile)) {
111
- fs.unlinkSync(tempFile);
112
- }
113
- }
114
- } catch (error) {
115
- if (error instanceof Error && error.message.includes("tsx")) {
116
- console.error("[pillar-sync] TypeScript files require tsx.");
117
- console.error("[pillar-sync] Make sure tsx is installed: npm install -D tsx");
118
- console.error("[pillar-sync] Then run: npx pillar-sync --actions ./actions.ts");
119
- }
120
- throw error;
121
- }
122
- }
123
- const fileUrl = pathToFileURL(absolutePath).href;
124
- try {
125
- let resolveActions2 = function(mod) {
126
- if (mod.actions && typeof mod.actions === "object" && !mod.actions.default) {
127
- return mod.actions;
128
- }
129
- if (mod.default && typeof mod.default === "object") {
130
- const defaultExport = mod.default;
131
- if (defaultExport.default && typeof defaultExport.default === "object") {
132
- return defaultExport.default;
133
- }
134
- if (defaultExport.actions && typeof defaultExport.actions === "object") {
135
- return defaultExport.actions;
136
- }
137
- return defaultExport;
138
- }
139
- return null;
140
- };
141
- var resolveActions = resolveActions2;
142
- const module = await import(fileUrl);
143
- const actions = resolveActions2(module);
144
- const agentGuidance = module.agentGuidance || module.default?.agentGuidance;
145
- if (!actions || typeof actions !== "object") {
146
- throw new Error(
147
- 'Actions file must export an actions object as default or named export "actions"'
148
- );
149
- }
150
- return { actions, agentGuidance };
151
- } catch (error) {
152
- throw error;
153
- }
154
- }
155
- function buildManifest(actions, platform, version, gitSha, agentGuidance) {
156
- const entries = [];
157
- for (const [name, definition] of Object.entries(actions)) {
158
- const entry = {
159
- name,
160
- description: definition.description,
161
- type: definition.type
162
- };
163
- if (definition.examples?.length)
164
- entry.examples = definition.examples;
165
- if (definition.path)
166
- entry.path = definition.path;
167
- if (definition.externalUrl)
168
- entry.external_url = definition.externalUrl;
169
- if (definition.autoRun)
170
- entry.auto_run = definition.autoRun;
171
- if (definition.autoComplete)
172
- entry.auto_complete = definition.autoComplete;
173
- if (definition.returns !== void 0) {
174
- entry.returns_data = definition.returns;
175
- } else if (definition.type === "query") {
176
- entry.returns_data = true;
177
- }
178
- if (definition.dataSchema)
179
- entry.data_schema = definition.dataSchema;
180
- if (definition.defaultData)
181
- entry.default_data = definition.defaultData;
182
- if (definition.requiredContext)
183
- entry.required_context = definition.requiredContext;
184
- if (definition.parameterExamples?.length)
185
- entry.parameter_examples = definition.parameterExamples;
186
- entries.push(entry);
187
- }
188
- const manifest = {
189
- platform,
190
- version,
191
- gitSha,
192
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
193
- actions: entries
194
- };
195
- if (agentGuidance) {
196
- manifest.agentGuidance = agentGuidance;
197
- }
198
- return manifest;
199
- }
200
56
  async function pollStatus(statusUrl, secret, maxWaitSeconds = 300) {
201
57
  const startTime = Date.now();
202
58
  let lastProgress = { processed: 0, total: 0 };
@@ -262,15 +118,226 @@ function getGitSha() {
262
118
  return void 0;
263
119
  }
264
120
  }
121
+ function globFiles(dir, extensions) {
122
+ const results = [];
123
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
124
+ for (const entry of entries) {
125
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".next") {
126
+ continue;
127
+ }
128
+ const fullPath = path.join(dir, entry.name);
129
+ if (entry.isDirectory()) {
130
+ results.push(...globFiles(fullPath, extensions));
131
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
132
+ results.push(fullPath);
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ function evaluateNode(node, ts) {
138
+ const n = node;
139
+ if (ts.isStringLiteral(n)) {
140
+ return n.text;
141
+ }
142
+ if (ts.isNoSubstitutionTemplateLiteral(n)) {
143
+ return n.text;
144
+ }
145
+ if (ts.isNumericLiteral(n)) {
146
+ return Number(n.text);
147
+ }
148
+ if (n.kind === ts.SyntaxKind.TrueKeyword)
149
+ return true;
150
+ if (n.kind === ts.SyntaxKind.FalseKeyword)
151
+ return false;
152
+ if (n.kind === ts.SyntaxKind.NullKeyword)
153
+ return null;
154
+ if (ts.isPrefixUnaryExpression(n)) {
155
+ const expr = n;
156
+ if (expr.operator === ts.SyntaxKind.MinusToken) {
157
+ const operand = evaluateNode(expr.operand, ts);
158
+ if (typeof operand === "number")
159
+ return -operand;
160
+ }
161
+ }
162
+ if (ts.isArrayLiteralExpression(n)) {
163
+ const arr = n;
164
+ const result = [];
165
+ for (const elem of arr.elements) {
166
+ const val = evaluateNode(elem, ts);
167
+ if (val === void 0)
168
+ return void 0;
169
+ result.push(val);
170
+ }
171
+ return result;
172
+ }
173
+ if (ts.isObjectLiteralExpression(n)) {
174
+ const obj = n;
175
+ const result = {};
176
+ for (const prop of obj.properties) {
177
+ if (ts.isPropertyAssignment(prop)) {
178
+ const key = prop.name ? ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : void 0 : void 0;
179
+ if (!key)
180
+ continue;
181
+ const val = evaluateNode(prop.initializer, ts);
182
+ if (val !== void 0) {
183
+ result[key] = val;
184
+ }
185
+ }
186
+ if (ts.isShorthandPropertyAssignment(prop)) {
187
+ continue;
188
+ }
189
+ }
190
+ return result;
191
+ }
192
+ if (ts.isAsExpression(n)) {
193
+ return evaluateNode(n.expression, ts);
194
+ }
195
+ if (ts.isParenthesizedExpression(n)) {
196
+ return evaluateNode(n.expression, ts);
197
+ }
198
+ if (ts.isBinaryExpression(n)) {
199
+ const bin = n;
200
+ if (bin.operatorToken.kind === ts.SyntaxKind.PlusToken) {
201
+ const left = evaluateNode(bin.left, ts);
202
+ const right = evaluateNode(bin.right, ts);
203
+ if (typeof left === "string" && typeof right === "string") {
204
+ return left + right;
205
+ }
206
+ }
207
+ }
208
+ return void 0;
209
+ }
210
+ async function scanActions(scanDir) {
211
+ const absoluteDir = path.resolve(process.cwd(), scanDir);
212
+ if (!fs.existsSync(absoluteDir)) {
213
+ throw new Error(`Scan directory not found: ${absoluteDir}`);
214
+ }
215
+ let ts;
216
+ try {
217
+ ts = await import("typescript");
218
+ } catch {
219
+ console.error("[pillar-sync] TypeScript is required for --scan mode.");
220
+ console.error("[pillar-sync] Install it: npm install -D typescript");
221
+ process.exit(1);
222
+ }
223
+ const files = globFiles(absoluteDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
224
+ console.log(`[pillar-sync] Scanning ${files.length} files in ${scanDir}`);
225
+ const PATTERNS = ["defineAction", "usePillarAction"];
226
+ const candidateFiles = files.filter((file) => {
227
+ const content = fs.readFileSync(file, "utf-8");
228
+ return PATTERNS.some((p) => content.includes(p));
229
+ });
230
+ console.log(`[pillar-sync] Found ${candidateFiles.length} files with action definitions`);
231
+ const actions = [];
232
+ for (const filePath of candidateFiles) {
233
+ let visit2 = function(node) {
234
+ if (ts.isCallExpression(node)) {
235
+ const callee = node.expression;
236
+ let isTargetCall = false;
237
+ if (ts.isIdentifier(callee)) {
238
+ isTargetCall = PATTERNS.includes(callee.text);
239
+ } else if (ts.isPropertyAccessExpression(callee)) {
240
+ isTargetCall = callee.name.text === "defineAction";
241
+ }
242
+ if (isTargetCall && node.arguments.length > 0) {
243
+ const arg = node.arguments[0];
244
+ const lineNumber = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
245
+ const relativePath = path.relative(process.cwd(), filePath);
246
+ const processActionObject = (obj, line) => {
247
+ if (obj && typeof obj.name === "string" && typeof obj.description === "string") {
248
+ actions.push({
249
+ name: obj.name,
250
+ description: obj.description,
251
+ type: obj.type,
252
+ inputSchema: obj.inputSchema,
253
+ examples: obj.examples,
254
+ autoRun: obj.autoRun,
255
+ autoComplete: obj.autoComplete,
256
+ sourceFile: relativePath,
257
+ line
258
+ });
259
+ console.log(`[pillar-sync] ${obj.name} (${relativePath}:${line})`);
260
+ } else if (obj) {
261
+ console.warn(
262
+ `[pillar-sync] \u26A0 Skipping action at ${relativePath}:${line} \u2014 missing name or description`
263
+ );
264
+ }
265
+ };
266
+ if (ts.isObjectLiteralExpression(arg)) {
267
+ const obj = evaluateNode(arg, ts);
268
+ processActionObject(obj, lineNumber);
269
+ } else if (ts.isArrayLiteralExpression(arg)) {
270
+ for (const element of arg.elements) {
271
+ if (ts.isObjectLiteralExpression(element)) {
272
+ const elementLine = sourceFile.getLineAndCharacterOfPosition(element.getStart()).line + 1;
273
+ const obj = evaluateNode(element, ts);
274
+ processActionObject(obj, elementLine);
275
+ } else {
276
+ const elementLine = sourceFile.getLineAndCharacterOfPosition(element.getStart()).line + 1;
277
+ console.warn(
278
+ `[pillar-sync] \u26A0 Skipping action at ${relativePath}:${elementLine} \u2014 array element is not an inline object literal`
279
+ );
280
+ }
281
+ }
282
+ } else {
283
+ console.warn(
284
+ `[pillar-sync] \u26A0 Skipping action at ${relativePath}:${lineNumber} \u2014 argument is not an inline object literal or array (variable reference can't be resolved statically)`
285
+ );
286
+ }
287
+ }
288
+ }
289
+ ts.forEachChild(node, visit2);
290
+ };
291
+ var visit = visit2;
292
+ const content = fs.readFileSync(filePath, "utf-8");
293
+ const sourceFile = ts.createSourceFile(
294
+ filePath,
295
+ content,
296
+ ts.ScriptTarget.Latest,
297
+ true,
298
+ // setParentNodes
299
+ filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : filePath.endsWith(".jsx") ? ts.ScriptKind.JSX : /\.m?js$/.test(filePath) ? ts.ScriptKind.JS : ts.ScriptKind.TS
300
+ );
301
+ visit2(sourceFile);
302
+ }
303
+ return actions;
304
+ }
305
+ function buildManifestFromScan(actions, platform, version, gitSha) {
306
+ const entries = [];
307
+ for (const action of actions) {
308
+ const entry = {
309
+ name: action.name,
310
+ description: action.description,
311
+ type: action.type || "trigger_action"
312
+ };
313
+ if (action.examples?.length)
314
+ entry.examples = action.examples;
315
+ if (action.autoRun)
316
+ entry.auto_run = action.autoRun;
317
+ if (action.autoComplete !== void 0)
318
+ entry.auto_complete = action.autoComplete;
319
+ entry.returns_data = true;
320
+ if (action.inputSchema)
321
+ entry.data_schema = action.inputSchema;
322
+ entries.push(entry);
323
+ }
324
+ return {
325
+ platform,
326
+ version,
327
+ gitSha,
328
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
329
+ actions: entries
330
+ };
331
+ }
265
332
  async function main() {
266
333
  const args = parseArgs(process.argv.slice(2));
267
334
  if (args.help) {
268
335
  printUsage();
269
336
  process.exit(0);
270
337
  }
271
- const actionsPath = args.actions;
272
- if (!actionsPath) {
273
- console.error("[pillar-sync] Missing required --actions argument");
338
+ const scanDir = args.scan;
339
+ if (!scanDir) {
340
+ console.error("[pillar-sync] Missing required argument: --scan <dir>");
274
341
  console.error("");
275
342
  printUsage();
276
343
  process.exit(1);
@@ -292,31 +359,27 @@ async function main() {
292
359
  console.error("Get these from the Pillar admin: Actions \u2192 Configure Sync");
293
360
  process.exit(1);
294
361
  }
295
- console.log(`[pillar-sync] Loading actions from: ${actionsPath}`);
296
- let loadedModule;
362
+ const platform = process.env.PILLAR_PLATFORM || "web";
363
+ const version = process.env.PILLAR_VERSION || getPackageVersion();
364
+ const gitSha = process.env.GIT_SHA || getGitSha();
365
+ console.log(`[pillar-sync] Scanning for actions in: ${scanDir}`);
366
+ let scannedActions;
297
367
  try {
298
- loadedModule = await loadActions(actionsPath);
368
+ scannedActions = await scanActions(scanDir);
299
369
  } catch (error) {
300
- console.error(`[pillar-sync] Failed to load actions:`, error);
370
+ console.error(`[pillar-sync] Failed to scan actions:`, error);
301
371
  process.exit(1);
302
372
  }
303
- const { actions, agentGuidance } = loadedModule;
304
- const actionCount = Object.keys(actions).length;
373
+ const actionCount = scannedActions.length;
305
374
  console.log(`[pillar-sync] Found ${actionCount} actions`);
306
- if (agentGuidance) {
307
- console.log(`[pillar-sync] Found agent guidance (${agentGuidance.length} chars)`);
308
- }
309
375
  if (actionCount === 0) {
310
376
  console.warn("[pillar-sync] No actions found. Nothing to sync.");
311
377
  process.exit(0);
312
378
  }
313
- const platform = process.env.PILLAR_PLATFORM || "web";
314
- const version = process.env.PILLAR_VERSION || getPackageVersion();
315
- const gitSha = process.env.GIT_SHA || getGitSha();
379
+ const manifest = buildManifestFromScan(scannedActions, platform, version, gitSha);
316
380
  console.log(`[pillar-sync] Platform: ${platform}`);
317
381
  console.log(`[pillar-sync] Version: ${version}`);
318
382
  console.log(`[pillar-sync] Git SHA: ${gitSha || "not available"}`);
319
- const manifest = buildManifest(actions, platform, version, gitSha, agentGuidance);
320
383
  if (process.env.PILLAR_DEBUG) {
321
384
  const manifestPath = path.join(process.cwd(), "actions-manifest.json");
322
385
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
@@ -329,9 +392,6 @@ async function main() {
329
392
  git_sha: gitSha,
330
393
  actions: manifest.actions
331
394
  };
332
- if (agentGuidance) {
333
- requestBody.agent_guidance = agentGuidance;
334
- }
335
395
  const syncUrl = `${apiUrl}/api/admin/configs/${slug}/actions/sync/?async=true`;
336
396
  console.log(`[pillar-sync] POST ${syncUrl}`);
337
397
  try {
@@ -43,11 +43,16 @@ export declare class EdgeTrigger {
43
43
  */
44
44
  private getEdgePosition;
45
45
  /**
46
- * Detect the current theme from the document
46
+ * Detect the current theme from the document (for auto mode)
47
47
  * Checks for .dark class (next-themes) or data-theme attribute
48
48
  * Returns explicit 'light' or 'dark' to match app theme (not system preference)
49
49
  */
50
- private detectTheme;
50
+ private detectThemeFromDOM;
51
+ /**
52
+ * Apply theme mode - respects explicit config, only auto-detects when mode is 'auto'
53
+ * Matches Panel behavior for consistency.
54
+ */
55
+ private applyThemeMode;
51
56
  /**
52
57
  * Initialize the edge trigger
53
58
  */
@@ -3,30 +3,35 @@
3
3
  * Manages the "Page being piloted by Agent" banner, rendering it outside Shadow DOM
4
4
  * so it appears above all other content on the page.
5
5
  */
6
+ import type { ThemeMode } from '../../core/config';
6
7
  export declare class PagePilotManager {
7
8
  private container;
8
9
  private stylesInjected;
9
10
  private unsubscribe;
10
11
  private themeObserver;
11
12
  private primaryColor;
13
+ private themeMode;
12
14
  /**
13
- * Detect the current theme from the document
15
+ * Detect the current theme from the document (for auto mode)
14
16
  * Checks for .dark class (next-themes) or data-theme attribute
15
17
  */
16
18
  private detectThemeFromDOM;
17
19
  /**
18
20
  * Apply theme mode to container element
21
+ * Respects explicit config, only auto-detects when mode is 'auto'
19
22
  */
20
- private applyTheme;
23
+ private applyThemeMode;
21
24
  /**
22
25
  * Set up observer to watch for theme changes on documentElement
26
+ * Only needed for auto mode
23
27
  */
24
28
  private setupThemeObserver;
25
29
  /**
26
30
  * Initialize the page pilot manager
27
31
  * @param primaryColor - Optional primary color from theme config to override the default
32
+ * @param themeMode - Theme mode from config ('light', 'dark', or 'auto')
28
33
  */
29
- init(primaryColor?: string): void;
34
+ init(primaryColor?: string, themeMode?: ThemeMode): void;
30
35
  /**
31
36
  * Update the primary color used by the banner
32
37
  */
@@ -15,9 +15,16 @@ export declare class Panel {
15
15
  private backdrop;
16
16
  private panelElement;
17
17
  private renderRoot;
18
+ private resizeHandle;
18
19
  private unsubscribe;
19
20
  private isManualMount;
20
21
  private themeObserver;
22
+ private _isResizing;
23
+ private _resizeStartX;
24
+ private _resizeStartWidth;
25
+ private _resizeRafId;
26
+ private _boundHandleResizeMove;
27
+ private _boundHandleResizeEnd;
21
28
  constructor(config: ResolvedConfig, api: APIClient, events: EventEmitter, rootContainer?: HTMLElement | null);
22
29
  /**
23
30
  * Detect the current theme from the document
@@ -53,6 +60,20 @@ export declare class Panel {
53
60
  * Destroy the panel
54
61
  */
55
62
  destroy(): void;
63
+ /** Internal safety clamp -- not user-configurable */
64
+ private static readonly MIN_PANEL_WIDTH;
65
+ /**
66
+ * Handle resize start from mouse or touch event on the drag handle
67
+ */
68
+ private handleResizeStart;
69
+ /**
70
+ * Handle resize move - throttled via requestAnimationFrame
71
+ */
72
+ private handleResizeMove;
73
+ /**
74
+ * Handle resize end - save final width and clean up
75
+ */
76
+ private handleResizeEnd;
56
77
  private subscribeToState;
57
78
  /**
58
79
  * Toggle backdrop visibility using inline styles.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ErrorRow Component
3
+ *
4
+ * A subtle, red-tinted progress-row-style error indicator with a retry button.
5
+ * Shown at the bottom of the chat thread when an MCP request fails.
6
+ */
7
+ import type { ChatError } from '../../store/chat';
8
+ export interface ErrorRowProps {
9
+ error: ChatError;
10
+ onRetry: () => void;
11
+ }
12
+ export declare function ErrorRow({ error, onRetry }: ErrorRowProps): import("preact").JSX.Element;
@@ -1,3 +1,4 @@
1
1
  export { ProgressRow, type ProgressRowProps } from './ProgressRow';
2
2
  export { ProgressGroup, type ProgressGroupProps } from './ProgressGroup';
3
3
  export { ProgressStack, type ProgressStackProps } from './ProgressStack';
4
+ export { ErrorRow, type ErrorRowProps } from './ErrorRow';