@agent-native/core 0.168.6 → 0.168.9

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 (87) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/mutate-dashboard.ts +8 -1
  3. package/corpus/templates/calendar/actions/delete-event.ts +34 -18
  4. package/corpus/templates/calendar/actions/delete-events.ts +13 -1
  5. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
  6. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
  7. package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
  8. package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
  9. package/corpus/templates/clips/actions/list-meetings.ts +1 -0
  10. package/corpus/templates/clips/actions/list-recordings.ts +14 -0
  11. package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
  12. package/corpus/templates/clips/app/components/library/recording-card.tsx +2 -2
  13. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +20 -3
  14. package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
  15. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +12 -5
  16. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
  17. package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
  18. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
  19. package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
  20. package/corpus/templates/factory/AGENTS.md +12 -22
  21. package/corpus/templates/factory/README.md +11 -8
  22. package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
  23. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
  24. package/corpus/templates/factory/actions/create-factory.ts +211 -0
  25. package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
  26. package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
  27. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
  28. package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
  29. package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
  30. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
  31. package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
  32. package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
  33. package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
  34. package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
  35. package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
  36. package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
  37. package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
  38. package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
  39. package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
  40. package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
  41. package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
  42. package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
  43. package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
  44. package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
  45. package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
  46. package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
  47. package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
  48. package/corpus/templates/factory/actions/view-screen.ts +22 -30
  49. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
  50. package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
  51. package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
  52. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  53. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
  54. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
  55. package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
  56. package/corpus/templates/factory/app/routes/factory.tsx +78 -62
  57. package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
  58. package/corpus/templates/factory/server/db/schema.ts +18 -3
  59. package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
  60. package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
  61. package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
  62. package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
  63. package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
  64. package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
  65. package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
  66. package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
  67. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
  68. package/corpus/templates/factory/server/triage/audit.ts +4 -0
  69. package/corpus/templates/factory/server/triage/ids.ts +4 -0
  70. package/dist/cli/native-dependencies.js +4 -1
  71. package/dist/client/AgentPanel.d.ts +5 -1
  72. package/dist/client/AgentPanel.js +9 -4
  73. package/dist/client/agent-chat/index.d.ts +1 -1
  74. package/dist/client/agent-chat/index.js +1 -1
  75. package/dist/client/chat-first/apps-rail.js +6 -6
  76. package/dist/client/chat-first/primary-nav.js +14 -6
  77. package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
  78. package/dist/client/integrations/IntegrationsPanel.js +29 -21
  79. package/dist/client/integrations/index.d.ts +1 -1
  80. package/dist/client/integrations/index.js +1 -1
  81. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  82. package/dist/collab/routes.d.ts +1 -1
  83. package/dist/notifications/routes.d.ts +1 -1
  84. package/dist/progress/routes.d.ts +1 -1
  85. package/dist/resources/handlers.d.ts +3 -3
  86. package/dist/server/ssr-handler.js +19 -1
  87. package/package.json +1 -1
@@ -294,6 +294,86 @@ const migrations = [
294
294
  ALTER TABLE factory_config ADD COLUMN builder_slack_user_id TEXT;
295
295
  `,
296
296
  },
297
+ {
298
+ version: 20,
299
+ name: "factory-runtime-factory-id-columns",
300
+ sql: `
301
+ ALTER TABLE factory_config ADD COLUMN factory_id TEXT;
302
+ ALTER TABLE factory_items ADD COLUMN factory_id TEXT;
303
+ ALTER TABLE factory_rules ADD COLUMN factory_id TEXT;
304
+ ALTER TABLE factory_decisions ADD COLUMN factory_id TEXT;
305
+ ALTER TABLE factory_runs ADD COLUMN factory_id TEXT;
306
+ ALTER TABLE factory_feedback ADD COLUMN factory_id TEXT;
307
+ ALTER TABLE factory_audit_events ADD COLUMN factory_id TEXT;
308
+ `,
309
+ },
310
+ {
311
+ version: 21,
312
+ name: "factory-runtime-factory-id-backfill",
313
+ sql: {},
314
+ run: async () => {
315
+ const { getDbExec } = await import("@agent-native/core/db");
316
+ const exec = getDbExec();
317
+ const defaultFactoryId = "product-feedback";
318
+ const tables = [
319
+ "factory_config",
320
+ "factory_items",
321
+ "factory_rules",
322
+ "factory_decisions",
323
+ "factory_runs",
324
+ "factory_feedback",
325
+ "factory_audit_events",
326
+ ] as const;
327
+ for (const table of tables) {
328
+ await exec.execute({
329
+ sql: `UPDATE ${table} SET factory_id = ? WHERE factory_id IS NULL OR factory_id = ''`,
330
+ args: [defaultFactoryId],
331
+ });
332
+ }
333
+ const configRows = await exec.execute({
334
+ sql: `SELECT id, org_id FROM factory_config WHERE factory_id = ?`,
335
+ args: [defaultFactoryId],
336
+ });
337
+ for (const row of configRows.rows ?? []) {
338
+ const orgId = String(row.org_id ?? "");
339
+ const id = String(row.id ?? "");
340
+ if (!orgId || id.includes(":")) continue;
341
+ const nextId = `${orgId}:${defaultFactoryId}`;
342
+ await exec.execute({
343
+ sql: `UPDATE factory_config SET id = ? WHERE id = ? AND org_id = ?`,
344
+ args: [nextId, id, orgId],
345
+ });
346
+ }
347
+ },
348
+ },
349
+ {
350
+ version: 22,
351
+ name: "factory-items-org-factory-dedupe-index",
352
+ sql: `
353
+ CREATE UNIQUE INDEX IF NOT EXISTS factory_items_org_factory_dedupe_idx
354
+ ON factory_items (org_id, factory_id, dedupe_key);
355
+ CREATE INDEX IF NOT EXISTS factory_items_org_factory_status_idx
356
+ ON factory_items (org_id, factory_id, status, updated_at);
357
+ CREATE INDEX IF NOT EXISTS factory_audit_events_org_factory_created_idx
358
+ ON factory_audit_events (org_id, factory_id, created_at);
359
+ `,
360
+ },
361
+ {
362
+ version: 23,
363
+ name: "factory-items-drop-org-dedupe-index",
364
+ sql: `
365
+ DROP INDEX IF EXISTS factory_items_org_dedupe_idx;
366
+ `,
367
+ },
368
+ {
369
+ version: 24,
370
+ name: "factory-config-org-slack-channel-unique",
371
+ sql: `
372
+ CREATE UNIQUE INDEX IF NOT EXISTS factory_config_org_slack_channel_idx
373
+ ON factory_config (org_id, slack_channel_id)
374
+ WHERE slack_channel_id IS NOT NULL AND slack_channel_id != '';
375
+ `,
376
+ },
297
377
  ];
298
378
 
299
379
  export const runFactoryMigrations = runMigrations(migrations, {
@@ -3,6 +3,7 @@ import { notify } from "@agent-native/core/notifications";
3
3
  import { resolveOrgIdForEmail } from "@agent-native/core/org";
4
4
  import {
5
5
  organizationResourceOwner,
6
+ resourceDeleteByPath,
6
7
  resourceGetByPath,
7
8
  resourcePut,
8
9
  resourcePutIfCurrent,
@@ -12,10 +13,21 @@ import {
12
13
  defineNitroPlugin,
13
14
  runWithRequestContext,
14
15
  } from "@agent-native/core/server";
16
+ import { listAutomationDefinitions } from "@agent-native/core/triggers";
15
17
  import { and, eq, isNull, lt, ne, or } from "drizzle-orm";
16
18
 
17
19
  import { getDb } from "../db/index.js";
18
20
  import { triageConfig } from "../db/schema.js";
21
+ import { repairFactoryAutomationsFromConfig } from "../lib/factory-automation-repair.js";
22
+ import {
23
+ DEFAULT_FACTORY_ID,
24
+ factoryAutomationJobPath,
25
+ factoryConfigRowId,
26
+ patchAutomationResource,
27
+ readAutomationFactoryId,
28
+ readFactoryIdFromAutomationPath,
29
+ readTriageConfigRow,
30
+ } from "../lib/factory-scope.js";
19
31
  import {
20
32
  repairSlackFeedbackPrompt,
21
33
  SLACK_HANDOFF_INSTRUCTION,
@@ -57,31 +69,23 @@ async function notifyFactoryAutomationFailure(
57
69
  if (
58
70
  (event.status !== "error" && event.status !== "interrupted") ||
59
71
  !event.orgId ||
60
- !event.path.startsWith("jobs/factory-")
72
+ (!event.path.startsWith("jobs/factory-") &&
73
+ !event.path.startsWith("jobs/factories/"))
61
74
  ) {
62
75
  return;
63
76
  }
64
77
 
65
78
  const db = getDb();
66
- const config = (
67
- await db
68
- .select({
69
- ownerEmail: triageConfig.ownerEmail,
70
- alertsEnabled: triageConfig.automationFailureAlertsEnabled,
71
- alertEmail: triageConfig.automationFailureAlertEmail,
72
- })
73
- .from(triageConfig)
74
- .where(
75
- and(
76
- eq(triageConfig.id, event.orgId),
77
- eq(triageConfig.orgId, event.orgId),
78
- ),
79
- )
80
- .limit(1)
81
- )[0];
82
- if (!config || config.alertsEnabled !== 1) return;
83
-
84
- const recipient = (config.alertEmail || config.ownerEmail || "")
79
+ const factoryId =
80
+ readFactoryIdFromAutomationPath(event.path) ?? DEFAULT_FACTORY_ID;
81
+ const config = await readTriageConfigRow(db, event.orgId, factoryId);
82
+ if (!config || config.automationFailureAlertsEnabled !== 1) return;
83
+
84
+ const recipient = (
85
+ config.automationFailureAlertEmail ||
86
+ config.ownerEmail ||
87
+ ""
88
+ )
85
89
  .trim()
86
90
  .toLowerCase();
87
91
  if (!recipient) return;
@@ -92,6 +96,7 @@ async function notifyFactoryAutomationFailure(
92
96
  const alertKey = `${event.automation}\n${error}`.slice(0, 700);
93
97
  const now = new Date();
94
98
  const cutoff = new Date(now.getTime() - FAILURE_ALERT_COOLDOWN_MS);
99
+ const configRowId = factoryConfigRowId(event.orgId, factoryId);
95
100
  const claimed = await db
96
101
  .update(triageConfig)
97
102
  .set({
@@ -100,7 +105,7 @@ async function notifyFactoryAutomationFailure(
100
105
  })
101
106
  .where(
102
107
  and(
103
- eq(triageConfig.id, event.orgId),
108
+ eq(triageConfig.id, configRowId),
104
109
  eq(triageConfig.orgId, event.orgId),
105
110
  eq(triageConfig.automationFailureAlertsEnabled, 1),
106
111
  or(
@@ -116,7 +121,7 @@ async function notifyFactoryAutomationFailure(
116
121
 
117
122
  const url = factoryPublicUrl();
118
123
  const debugTarget = url
119
- ? `${url}/factory?tab=automations`
124
+ ? `${url}/factory?factoryId=${encodeURIComponent(factoryId)}&tab=automations`
120
125
  : "Factory > Automations";
121
126
  const details = [
122
127
  `Automation: ${event.automation}`,
@@ -440,10 +445,31 @@ function frontmatterField(content: string, key: string): string | undefined {
440
445
  return value.replace(/^(\"|')|((\"|')$)/g, "");
441
446
  }
442
447
 
448
+ function automationFactoryScopeInstruction(factoryId: string): string {
449
+ return `This automation runs for factory \`${factoryId}\`. Pass \`factoryId: "${factoryId}"\` on every Factory triage, poll, and config action in this run.`;
450
+ }
451
+
452
+ function repairAutomationFactoryScopeInstruction(
453
+ content: string,
454
+ factoryId: string,
455
+ ): string {
456
+ if (content.includes(`Pass \`factoryId: "${factoryId}"\``)) {
457
+ return content;
458
+ }
459
+ const end = content.indexOf("\n---", 4);
460
+ if (end === -1) {
461
+ return `${automationFactoryScopeInstruction(factoryId)}\n\n${content.trim()}\n`;
462
+ }
463
+ const insertAt = end + 4;
464
+ return `${content.slice(0, insertAt)}\n\n${automationFactoryScopeInstruction(factoryId)}\n${content.slice(insertAt)}`;
465
+ }
466
+
443
467
  function automationContent(
444
468
  ownerEmail: string,
445
469
  orgId: string,
470
+ factoryId: string,
446
471
  seed: AutomationSeed,
472
+ enabled = true,
447
473
  ): string {
448
474
  const body = syncManagedReviewSkillAlignment(
449
475
  seed.body.trim(),
@@ -451,17 +477,20 @@ function automationContent(
451
477
  );
452
478
  return `---
453
479
  schedule: "${seed.schedule}"
454
- ${seed.timezone ? `timezone: ${seed.timezone}\n` : ""}enabled: true
480
+ ${seed.timezone ? `timezone: ${seed.timezone}\n` : ""}enabled: ${enabled ? "true" : "false"}
455
481
  triggerType: schedule
456
482
  domain: factory
457
483
  appId: factory
458
484
  orgId: ${orgId}
485
+ factoryId: ${factoryId}
459
486
  createdBy: ${ownerEmail}
460
487
  runAs: creator
461
488
  model: ${seed.model}
462
489
  maxIterations: ${seed.maxIterations}
463
490
  maxRunInputTokens: ${seed.maxRunInputTokens}
464
491
  ---
492
+ ${automationFactoryScopeInstruction(factoryId)}
493
+
465
494
  ${body.trim()}
466
495
  `;
467
496
  }
@@ -480,20 +509,26 @@ async function disableLegacyObserver(): Promise<void> {
480
509
  }
481
510
  }
482
511
 
483
- async function ensureOrganizationAutomations(
512
+ export async function ensureFactoryAutomations(
484
513
  ownerEmail: string,
485
514
  orgId: string,
515
+ factoryId: string,
516
+ options?: { enabled?: boolean; enabledNames?: ReadonlySet<string> },
486
517
  ): Promise<void> {
487
518
  const owner = organizationResourceOwner(orgId);
519
+ const defaultEnabled = options?.enabled ?? false;
488
520
  await Promise.all(
489
521
  AUTOMATION_SEEDS.map(async (seed) => {
490
- const path = `jobs/${seed.name}.md`;
522
+ const enabled = options?.enabledNames
523
+ ? options.enabledNames.has(seed.name)
524
+ : defaultEnabled;
525
+ const path = factoryAutomationJobPath(factoryId, seed.name);
491
526
  const existing = await resourceGetByPath(owner, path);
492
527
  if (!existing) {
493
528
  await resourcePut(
494
529
  owner,
495
530
  path,
496
- automationContent(ownerEmail, orgId, seed),
531
+ automationContent(ownerEmail, orgId, factoryId, seed, enabled),
497
532
  "text/markdown",
498
533
  );
499
534
  return;
@@ -507,6 +542,7 @@ async function ensureOrganizationAutomations(
507
542
  repaired = setFrontmatterField(repaired, "domain", "factory");
508
543
  repaired = setFrontmatterField(repaired, "appId", "factory");
509
544
  repaired = setFrontmatterField(repaired, "orgId", orgId);
545
+ repaired = setFrontmatterField(repaired, "factoryId", factoryId);
510
546
  repaired = setFrontmatterField(repaired, "createdBy", ownerEmail);
511
547
  repaired = setFrontmatterField(repaired, "runAs", "creator");
512
548
  if (!frontmatterField(repaired, "model")) {
@@ -552,6 +588,7 @@ async function ensureOrganizationAutomations(
552
588
  if (seed.name === "factory-slack-feedback") {
553
589
  repaired = repairSlackFeedbackPrompt(repaired);
554
590
  }
591
+ repaired = repairAutomationFactoryScopeInstruction(repaired, factoryId);
555
592
  if (repaired === existing.content) return;
556
593
 
557
594
  const updated = await resourcePutIfCurrent({
@@ -572,33 +609,95 @@ async function ensureOrganizationAutomations(
572
609
  );
573
610
  }
574
611
 
612
+ export async function syncFactoryAutomationEnabledStates(
613
+ ownerEmail: string,
614
+ orgId: string,
615
+ factoryId: string,
616
+ enabledNames: readonly string[],
617
+ ): Promise<void> {
618
+ const enabledSet = new Set(enabledNames);
619
+ const definitions = await listAutomationDefinitions(
620
+ { userEmail: ownerEmail, orgId, appId: "factory" },
621
+ "organization",
622
+ );
623
+ await Promise.all(
624
+ definitions
625
+ .filter(
626
+ (definition) =>
627
+ readAutomationFactoryId(
628
+ definition.meta,
629
+ definition.resource.content,
630
+ ) === factoryId,
631
+ )
632
+ .map(async (definition) => {
633
+ const resource = await resourceGetByPath(
634
+ definition.resource.owner,
635
+ definition.resource.path,
636
+ );
637
+ if (!resource) return;
638
+ const leafName =
639
+ definition.resource.path.match(/([^/]+)\.md$/)?.[1] ??
640
+ definition.name;
641
+ const enabled = enabledSet.has(leafName);
642
+ const content = patchAutomationResource(resource.content, { enabled });
643
+ if (content === resource.content) return;
644
+ const updated = await resourcePutIfCurrent({
645
+ owner: definition.resource.owner,
646
+ path: definition.resource.path,
647
+ content,
648
+ mimeType: "text/markdown",
649
+ expectedId: resource.id,
650
+ expectedUpdatedAt: resource.updatedAt,
651
+ expectedContent: resource.content,
652
+ });
653
+ if (!updated) {
654
+ console.warn(
655
+ `[factory-scheduler-job] skipped enabled-state sync for ${definition.resource.path}: the resource changed concurrently`,
656
+ );
657
+ }
658
+ }),
659
+ );
660
+ }
661
+
662
+ export async function removeFactoryAutomationResources(
663
+ orgId: string,
664
+ factoryId: string,
665
+ ): Promise<void> {
666
+ const owner = organizationResourceOwner(orgId);
667
+ await Promise.all(
668
+ AUTOMATION_SEEDS.map(async (seed) => {
669
+ await resourceDeleteByPath(
670
+ owner,
671
+ factoryAutomationJobPath(factoryId, seed.name),
672
+ );
673
+ }),
674
+ );
675
+ }
676
+
575
677
  async function ensureDefaultTriageConfig(
576
678
  ownerEmail: string,
577
679
  orgId: string,
578
680
  ): Promise<void> {
579
681
  const db = getDb();
580
- const existing = (
581
- await db
582
- .select({
583
- id: triageConfig.id,
584
- })
585
- .from(triageConfig)
586
- .where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
587
- .limit(1)
588
- )[0];
589
- const repository = defaultRepository();
590
- // Existing rows are operator-owned. An empty repository plus disabled
591
- // polling can be an intentional choice, so do not infer bootstrap state.
592
- if (existing) return;
682
+ const factoryId = DEFAULT_FACTORY_ID;
683
+ const existing = await readTriageConfigRow(db, orgId, factoryId);
684
+ if (existing) {
685
+ await repairFactoryAutomationsFromConfig(ownerEmail, orgId, factoryId);
686
+ return;
687
+ }
593
688
  const now = new Date().toISOString();
689
+ const repository = defaultRepository();
690
+ const pollingEnabled = 1;
691
+ const githubPollingEnabled = defaultGithubPollingEnabled() ? 1 : 0;
594
692
  await db.insert(triageConfig).values({
595
- id: orgId,
693
+ id: factoryConfigRowId(orgId, factoryId),
694
+ factoryId,
596
695
  slackWorkspace: "primary",
597
696
  slackChannelId: DEFAULT_SLACK_CHANNEL_ID,
598
697
  slackChannelName: DEFAULT_SLACK_CHANNEL_NAME,
599
698
  builderSlackUserId: null,
600
- pollingEnabled: 1,
601
- githubPollingEnabled: defaultGithubPollingEnabled(),
699
+ pollingEnabled,
700
+ githubPollingEnabled,
602
701
  sentryPollingEnabled: 0,
603
702
  lastSlackTs: null,
604
703
  slackHistoryCursor: null,
@@ -612,6 +711,7 @@ async function ensureDefaultTriageConfig(
612
711
  ownerEmail,
613
712
  orgId,
614
713
  });
714
+ await repairFactoryAutomationsFromConfig(ownerEmail, orgId, factoryId);
615
715
  }
616
716
 
617
717
  async function ensureSchedulerJobs(): Promise<void> {
@@ -636,7 +736,6 @@ async function ensureSchedulerJobs(): Promise<void> {
636
736
  orgId = existingConfig.orgId?.trim() || existingConfig.id;
637
737
  }
638
738
  await ensureDefaultTriageConfig(ownerEmail, orgId);
639
- await ensureOrganizationAutomations(ownerEmail, orgId);
640
739
  await disableLegacyObserver();
641
740
  }
642
741
 
@@ -21,6 +21,7 @@ export interface FactoryAuditInput {
21
21
  action: string;
22
22
  kind: FactoryAuditKind;
23
23
  status?: FactoryAuditStatus;
24
+ factoryId?: string | null;
24
25
  itemId?: string | null;
25
26
  source?: string | null;
26
27
  sourceUrl?: string | null;
@@ -55,8 +56,10 @@ export async function recordFactoryAudit(
55
56
  context: ActionRunContext | undefined,
56
57
  identity: { userEmail: string; orgId: string },
57
58
  input: FactoryAuditInput,
59
+ factoryId?: string | null,
58
60
  ): Promise<void> {
59
61
  if (context?.caller !== "automation" || !context.runId) return;
62
+ const resolvedFactoryId = factoryId ?? input.factoryId ?? null;
60
63
  await getDb()
61
64
  .insert(factoryAuditEvents)
62
65
  .values({
@@ -64,6 +67,7 @@ export async function recordFactoryAudit(
64
67
  automationRunId: context.runId,
65
68
  automationThreadId: context.threadId ?? null,
66
69
  automationName: context.automation?.triggerName ?? null,
70
+ factoryId: resolvedFactoryId,
67
71
  itemId: input.itemId ?? null,
68
72
  source: input.source ?? null,
69
73
  sourceUrl: input.sourceUrl ?? null,
@@ -13,9 +13,13 @@ export function itemDedupeKey(
13
13
  headSha?: string;
14
14
  },
15
15
  organizationId = "",
16
+ factoryId = "",
16
17
  ): string {
18
+ const factorySegment =
19
+ factoryId && factoryId !== "product-feedback" ? factoryId : "";
17
20
  return stableId(
18
21
  organizationId,
22
+ factorySegment,
19
23
  input.source,
20
24
  input.externalId,
21
25
  input.repository ?? "",
@@ -58,7 +58,10 @@ function probeBetterSqlite3(packageDir) {
58
58
  }
59
59
  }
60
60
  export function checkNativeDependencies(fromDirectory = corePackageDirectory()) {
61
- const packageDir = resolvePackageDirectory(BETTER_SQLITE3, fromDirectory);
61
+ const packageDir = resolvePackageDirectory(BETTER_SQLITE3, fromDirectory) ??
62
+ (path.resolve(fromDirectory) === corePackageDirectory()
63
+ ? null
64
+ : resolvePackageDirectory(BETTER_SQLITE3, corePackageDirectory()));
62
65
  if (!packageDir) {
63
66
  return { status: "absent", packageName: BETTER_SQLITE3 };
64
67
  }
@@ -21,6 +21,8 @@
21
21
  * <AgentChatSurface mode="page" className="h-screen" />
22
22
  */
23
23
  import React from "react";
24
+ /** Start loading the desktop chat surface before a sidebar is opened. */
25
+ export declare function preloadAgentChatSurface(): Promise<void>;
24
26
  import type { AgentChatSurfaceKind } from "./agent-chat-adapter.js";
25
27
  import type { AssistantChatProps } from "./AssistantChat.js";
26
28
  import type { MultiTabAssistantChatHeaderProps, MultiTabAssistantChatProps } from "./MultiTabAssistantChat.js";
@@ -223,6 +225,8 @@ export interface AgentSidebarProps {
223
225
  chatViewTransitionHandoff?: boolean;
224
226
  /** Namespace for persisted chat state. Use the same key as AgentChatHome. */
225
227
  storageKey?: string;
228
+ /** Restore the previously active chat thread on mount. Default: true. */
229
+ restoreActiveThread?: boolean;
226
230
  /** Namespace for the persisted open/closed preference. Defaults to storageKey. */
227
231
  openStorageKey?: string;
228
232
  /** API base URL used by the chat surface. */
@@ -262,7 +266,7 @@ export interface AgentSidebarProps {
262
266
  * Wraps app content with a toggleable agent sidebar.
263
267
  * Use AgentToggleButton in your header to open/close it.
264
268
  */
265
- export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
269
+ export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, restoreActiveThread, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
266
270
  /**
267
271
  * Focus the agent chat composer input.
268
272
  * Opens the sidebar if closed, then focuses the text input.
@@ -36,9 +36,14 @@ import { ShareButton } from "./sharing/ShareButton.js";
36
36
  // assistant-ui + zod block schemas) so it is NOT in the static import closure of
37
37
  // every page. The header/tab chrome renders immediately; chat streams in once the
38
38
  // chunk lands (~650-700 KB gzip saved from the critical path).
39
- const MultiTabAssistantChatLazy = lazy(() => import("./MultiTabAssistantChat.js").then((m) => ({
39
+ const loadMultiTabAssistantChat = () => import("./MultiTabAssistantChat.js").then((m) => ({
40
40
  default: m.MultiTabAssistantChat,
41
- })));
41
+ }));
42
+ const MultiTabAssistantChatLazy = lazy(loadMultiTabAssistantChat);
43
+ /** Start loading the desktop chat surface before a sidebar is opened. */
44
+ export function preloadAgentChatSurface() {
45
+ return loadMultiTabAssistantChat().then(() => undefined);
46
+ }
42
47
  import { useQuery, useQueryClient } from "@tanstack/react-query";
43
48
  import { Link, useLocation, useNavigate } from "react-router";
44
49
  import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
@@ -1418,7 +1423,7 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
1418
1423
  * Wraps app content with a toggleable agent sidebar.
1419
1424
  * Use AgentToggleButton in your header to open/close it.
1420
1425
  */
1421
- export function AgentSidebar({ children, enabled = true, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1426
+ export function AgentSidebar({ children, enabled = true, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, restoreActiveThread = true, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1422
1427
  const staticHostedHarnessEnabled = isHostedHarnessConfigured(injectedAgentNativeConfig().harness);
1423
1428
  const hostedHarnessQuery = useActionQuery("get-hosted-harness-config", undefined, { enabled: staticHostedHarnessEnabled });
1424
1429
  const hostedHarnessStatus = hostedHarnessQuery.data;
@@ -2062,7 +2067,7 @@ export function AgentSidebar({ children, enabled = true, emptyStateText = "How c
2062
2067
  ? "desktop"
2063
2068
  : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-per-app-chat": isPerAppChatSidebar ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
2064
2069
  ? getAgentChatViewTransitionStyle(panelStyle)
2065
- : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: effectiveAvailableAgents, availableModels: availableModels, modelListLoading: modelListLoading, selectedAgent: effectiveSelectedAgent, onAgentChange: effectiveOnAgentChange, hostedHarness: hostedHarnessEnabled, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, desktopIdentityUnauthenticated: desktopIdentityUnauthenticated, desktopIdentityAuthenticated: desktopIdentityAuthenticated, showTabBar: effectiveShowTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, isolateHistoryByScope: isolateHistoryByScope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
2070
+ : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: effectiveAvailableAgents, availableModels: availableModels, modelListLoading: modelListLoading, selectedAgent: effectiveSelectedAgent, onAgentChange: effectiveOnAgentChange, hostedHarness: hostedHarnessEnabled, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, desktopIdentityUnauthenticated: desktopIdentityUnauthenticated, desktopIdentityAuthenticated: desktopIdentityAuthenticated, showTabBar: effectiveShowTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, restoreActiveThread: restoreActiveThread, scope: scope, isolateHistoryByScope: isolateHistoryByScope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
2066
2071
  const drawerPlaceholder = wideDrawerEnabled && !presentationMode && panelOpen ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
2067
2072
  return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-native-hosted-chat": isPerAppChatHosted ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
2068
2073
  !isPerAppChatHosted &&
@@ -32,7 +32,7 @@ export { GuidedQuestionFlow, useGuidedQuestionFlow, askUserQuestion, formatGuide
32
32
  export { useChatThreads, type ChatThreadScope, type ChatThreadSnapshot, type ChatThreadSummary, type ChatThreadData, type ChatThreadShareLink, type ChatThreadShareState, type UseChatThreadsOptions, } from "../use-chat-threads.js";
33
33
  export { ChatHistoryList, type ChatHistoryItem, type ChatHistorySection, type ChatHistoryListProps, } from "../chat/ChatHistoryList.js";
34
34
  export { AgentChatHome, type AgentChatHomeProps } from "../AgentChatHome.js";
35
- export { AgentChatSurface, AgentPanel, AgentSidebar, AgentToggleButton, focusAgentChat, type AgentChatSurfaceMode, type AgentChatSurfaceProps, type AgentPanelProps, type AgentSidebarProps, } from "../AgentPanel.js";
35
+ export { AgentChatSurface, AgentPanel, AgentSidebar, AgentToggleButton, focusAgentChat, preloadAgentChatSurface, type AgentChatSurfaceMode, type AgentChatSurfaceProps, type AgentPanelProps, type AgentSidebarProps, } from "../AgentPanel.js";
36
36
  export { AgentTabsPage, ConnectionsTab, type AgentPageExtraTabContext, type AgentPageExtraTabFactory, type AgentTabsPageProps, } from "../agent-page/AgentTabsPage.js";
37
37
  export type { AgentPageScope, AgentPageTabProps } from "../agent-page/types.js";
38
38
  export { AGENT_CHAT_HOME_HANDOFF_TTL_MS, AGENT_CHAT_VIEW_TRANSITION_CLASS, AGENT_CHAT_VIEW_TRANSITION_NAME, consumeAgentChatHomeHandoff, getAgentChatViewTransitionStyle, isAgentChatHomeHandoffActive, markAgentChatHomeHandoff, navigateWithAgentChatViewTransition, startAgentChatViewTransition, supportsAgentChatViewTransition, type AgentChatHomeHandoffOptions, type AgentChatViewTransition, type AgentChatViewTransitionOptions, } from "../chat-view-transition.js";
@@ -32,7 +32,7 @@ export { GuidedQuestionFlow, useGuidedQuestionFlow, askUserQuestion, formatGuide
32
32
  export { useChatThreads, } from "../use-chat-threads.js";
33
33
  export { ChatHistoryList, } from "../chat/ChatHistoryList.js";
34
34
  export { AgentChatHome } from "../AgentChatHome.js";
35
- export { AgentChatSurface, AgentPanel, AgentSidebar, AgentToggleButton, focusAgentChat, } from "../AgentPanel.js";
35
+ export { AgentChatSurface, AgentPanel, AgentSidebar, AgentToggleButton, focusAgentChat, preloadAgentChatSurface, } from "../AgentPanel.js";
36
36
  export { AgentTabsPage, ConnectionsTab, } from "../agent-page/AgentTabsPage.js";
37
37
  export { AGENT_CHAT_HOME_HANDOFF_TTL_MS, AGENT_CHAT_VIEW_TRANSITION_CLASS, AGENT_CHAT_VIEW_TRANSITION_NAME, consumeAgentChatHomeHandoff, getAgentChatViewTransitionStyle, isAgentChatHomeHandoffActive, markAgentChatHomeHandoff, navigateWithAgentChatViewTransition, startAgentChatViewTransition, supportsAgentChatViewTransition, } from "../chat-view-transition.js";
38
38
  export { useAgentChatHomeHandoff, useAgentChatHomeHandoffLinks, } from "../use-agent-chat-home-handoff.js";
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, Skeleton, Tooltip, TooltipContent, TooltipTrigger, } from "@agent-native/toolkit/ui";
2
+ import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, Skeleton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@agent-native/toolkit/ui";
3
3
  import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, IconTrash, } from "@tabler/icons-react";
4
4
  import { memo, useEffect, useMemo, useState } from "react";
5
5
  import { CHAT_FIRST_DEFAULT_APP_IDS, orderChatFirstAppIds, readChatFirstAppLayout, writeChatFirstAppLayout, } from "../chat-first.js";
@@ -133,11 +133,11 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, default
133
133
  persistLayout({ ...layout, orderedIds: nextOrder });
134
134
  }
135
135
  if (collapsed) {
136
- return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [loading && apps.length === 0
137
- ? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
138
- : visibleApps.map((app) => (_jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
139
- ? "text-sidebar-foreground"
140
- : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: _jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
136
+ return (_jsx(TooltipProvider, { children: _jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [loading && apps.length === 0
137
+ ? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
138
+ : visibleApps.map((app) => (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
139
+ ? "text-sidebar-foreground"
140
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), children: _jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }) }) }), _jsx(TooltipContent, { side: "right", children: app.name })] }, app.id))), onOpenAllApps ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) }) }), _jsx(TooltipContent, { side: "right", children: copy("allApps") })] })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }) }));
141
141
  }
142
142
  return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, defaultAppIds: defaultAppIds, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
143
143
  });
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@agent-native/toolkit/ui";
2
3
  import { IconClock, IconPlus, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
3
4
  import { defaultChatFirstCopy } from "./copy.js";
4
5
  export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, collapsed = false, stickyNewChat = false, copy = defaultChatFirstCopy, }) {
@@ -10,15 +11,22 @@ export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOp
10
11
  : tab === "integrations"
11
12
  ? copy("integrations")
12
13
  : copy("scheduled");
13
- const renderTab = (tab, content, onSelect, className) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, "aria-label": collapsed || tab === "new-chat" ? tabLabel(tab) : undefined, title: collapsed ? tabLabel(tab) : undefined, className: [tabClassName(tab), className].filter(Boolean).join(" "), onClick: onSelect, children: content }));
14
+ const renderTab = (tab, content, onSelect, className) => {
15
+ const label = tabLabel(tab);
16
+ const control = (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, "aria-label": collapsed || tab === "new-chat" ? label : undefined, className: [tabClassName(tab), className].filter(Boolean).join(" "), onClick: onSelect, children: content }));
17
+ return collapsed ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: control }), _jsx(TooltipContent, { side: "right", children: label })] })) : (control);
18
+ };
14
19
  const newChatTab = onNewChat
15
20
  ? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("newChat") })] }), onNewChat, "code-agents-primary-new-chat")
16
21
  : null;
17
22
  const integrationsTab = renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("integrations") })] }), onOpenIntegrations);
18
23
  const scheduledTab = renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("scheduled") })] }), onOpenScheduled);
19
- const searchAction = onSearch ? (_jsxs("button", { type: "button", "aria-label": collapsed ? copy("search") : undefined, title: collapsed ? copy("search") : undefined, className: `mt-px flex h-8 w-full items-center gap-2 rounded-md text-[13px] font-medium text-sidebar-foreground/80 transition-[background-color,color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${collapsed ? "justify-center px-0" : "px-2"}`, onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("search") })] })) : null;
20
- if (stickyNewChat) {
21
- return (_jsxs(_Fragment, { children: [newChatTab ? (_jsx("div", { className: "code-agents-primary-new-chat-shell", children: newChatTab })) : null, _jsxs("div", { className: "code-agents-nav-list", "aria-label": "Agent navigation", children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [integrationsTab, scheduledTab] }), searchAction] })] }));
22
- }
23
- return (_jsxs("div", { children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [newChatTab, integrationsTab, scheduledTab] }), searchAction] }));
24
+ const searchAction = onSearch
25
+ ? (() => {
26
+ const label = copy("search");
27
+ const control = (_jsxs("button", { type: "button", "aria-label": collapsed ? label : undefined, className: `mt-px flex h-8 w-full items-center gap-2 rounded-md text-[13px] font-medium text-sidebar-foreground/80 transition-[background-color,color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${collapsed ? "justify-center px-0" : "px-2"}`, onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: label })] }));
28
+ return collapsed ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: control }), _jsx(TooltipContent, { side: "right", children: label })] })) : (control);
29
+ })()
30
+ : null;
31
+ return (_jsx(TooltipProvider, { delayDuration: 0, children: stickyNewChat ? (_jsxs(_Fragment, { children: [newChatTab ? (_jsx("div", { className: "code-agents-primary-new-chat-shell", children: newChatTab })) : null, _jsxs("div", { className: "code-agents-nav-list", "aria-label": "Agent navigation", children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [integrationsTab, scheduledTab] }), searchAction] })] })) : (_jsxs("div", { children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [newChatTab, integrationsTab, scheduledTab] }), searchAction] })) }));
24
32
  }