stagent 0.9.5 → 0.10.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 (277) hide show
  1. package/README.md +5 -42
  2. package/dist/cli.js +42 -18
  3. package/docs/.coverage-gaps.json +13 -55
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/provider-runtimes.md +4 -0
  6. package/docs/features/schedules.md +32 -4
  7. package/docs/features/settings.md +28 -5
  8. package/docs/features/tables.md +9 -2
  9. package/docs/features/workflows.md +10 -4
  10. package/docs/journeys/developer.md +15 -1
  11. package/docs/journeys/personal-use.md +21 -4
  12. package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
  13. package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
  14. package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
  15. package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
  16. package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
  17. package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
  18. package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
  19. package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
  20. package/package.json +3 -2
  21. package/src/__tests__/instrumentation-smoke.test.ts +15 -0
  22. package/src/app/analytics/page.tsx +1 -21
  23. package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
  24. package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
  25. package/src/app/api/instance/config/route.ts +41 -0
  26. package/src/app/api/instance/init/route.ts +34 -0
  27. package/src/app/api/instance/upgrade/check/route.ts +26 -0
  28. package/src/app/api/instance/upgrade/route.ts +96 -0
  29. package/src/app/api/instance/upgrade/status/route.ts +35 -0
  30. package/src/app/api/memory/route.ts +0 -11
  31. package/src/app/api/notifications/route.ts +4 -2
  32. package/src/app/api/projects/[id]/route.ts +5 -155
  33. package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
  34. package/src/app/api/schedules/[id]/execute/route.ts +111 -0
  35. package/src/app/api/schedules/[id]/route.ts +9 -1
  36. package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
  37. package/src/app/api/schedules/route.ts +3 -12
  38. package/src/app/api/settings/openai/login/route.ts +22 -0
  39. package/src/app/api/settings/openai/logout/route.ts +7 -0
  40. package/src/app/api/settings/openai/route.ts +21 -1
  41. package/src/app/api/settings/providers/route.ts +35 -8
  42. package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
  43. package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
  44. package/src/app/api/tables/[id]/enrich/route.ts +147 -0
  45. package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
  46. package/src/app/api/tasks/[id]/execute/route.ts +0 -21
  47. package/src/app/api/workflows/[id]/resume/route.ts +59 -0
  48. package/src/app/api/workflows/[id]/status/route.ts +22 -8
  49. package/src/app/api/workspace/context/route.ts +2 -0
  50. package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
  51. package/src/app/chat/page.tsx +11 -0
  52. package/src/app/inbox/page.tsx +12 -5
  53. package/src/app/layout.tsx +42 -21
  54. package/src/app/page.tsx +0 -2
  55. package/src/app/settings/page.tsx +6 -9
  56. package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
  57. package/src/components/chat/chat-command-popover.tsx +2 -2
  58. package/src/components/chat/chat-input.tsx +2 -3
  59. package/src/components/chat/chat-session-provider.tsx +720 -0
  60. package/src/components/chat/chat-shell.tsx +92 -401
  61. package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
  62. package/src/components/instance/instance-section.tsx +382 -0
  63. package/src/components/instance/upgrade-badge.tsx +219 -0
  64. package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
  65. package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
  66. package/src/components/notifications/batch-proposal-review.tsx +20 -5
  67. package/src/components/notifications/inbox-list.tsx +11 -2
  68. package/src/components/notifications/notification-item.tsx +56 -2
  69. package/src/components/notifications/pending-approval-host.tsx +56 -37
  70. package/src/components/schedules/schedule-create-sheet.tsx +19 -1
  71. package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
  72. package/src/components/schedules/schedule-form.tsx +31 -0
  73. package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
  74. package/src/components/settings/auth-method-selector.tsx +19 -4
  75. package/src/components/settings/auth-status-badge.tsx +28 -3
  76. package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
  77. package/src/components/settings/openai-runtime-section.tsx +7 -1
  78. package/src/components/settings/providers-runtimes-section.tsx +138 -19
  79. package/src/components/shared/app-sidebar.tsx +4 -3
  80. package/src/components/shared/command-palette.tsx +4 -5
  81. package/src/components/shared/theme-toggle.tsx +5 -24
  82. package/src/components/shared/workspace-indicator.tsx +61 -2
  83. package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
  84. package/src/components/tables/table-create-sheet.tsx +4 -0
  85. package/src/components/tables/table-enrichment-runs.tsx +103 -0
  86. package/src/components/tables/table-enrichment-sheet.tsx +538 -0
  87. package/src/components/tables/table-spreadsheet.tsx +29 -5
  88. package/src/components/tables/table-toolbar.tsx +10 -1
  89. package/src/components/tasks/kanban-board.tsx +1 -0
  90. package/src/components/tasks/kanban-column.tsx +53 -14
  91. package/src/components/tasks/task-bento-grid.tsx +19 -0
  92. package/src/components/tasks/task-card.tsx +26 -3
  93. package/src/components/tasks/task-chip-bar.tsx +24 -0
  94. package/src/components/tasks/task-result-renderer.tsx +1 -1
  95. package/src/components/workflows/delay-step-body.tsx +109 -0
  96. package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
  97. package/src/components/workflows/loop-status-view.tsx +1 -1
  98. package/src/components/workflows/shared/step-result.tsx +78 -0
  99. package/src/components/workflows/shared/workflow-header.tsx +141 -0
  100. package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
  101. package/src/components/workflows/swarm-dashboard.tsx +2 -15
  102. package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
  103. package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
  104. package/src/components/workflows/workflow-form-view.tsx +133 -16
  105. package/src/components/workflows/workflow-status-view.tsx +30 -740
  106. package/src/instrumentation-node.ts +94 -0
  107. package/src/instrumentation.ts +4 -48
  108. package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
  109. package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
  110. package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
  111. package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
  112. package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
  113. package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
  114. package/src/lib/agents/claude-agent.ts +155 -18
  115. package/src/lib/agents/execution-manager.ts +0 -35
  116. package/src/lib/agents/learned-context.ts +0 -12
  117. package/src/lib/agents/learning-session.ts +18 -5
  118. package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
  119. package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
  120. package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
  121. package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
  122. package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
  123. package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
  124. package/src/lib/agents/runtime/openai-codex.ts +29 -60
  125. package/src/lib/agents/runtime/types.ts +8 -0
  126. package/src/lib/book/chapter-mapping.ts +11 -0
  127. package/src/lib/book/content.ts +10 -0
  128. package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
  129. package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
  130. package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
  131. package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
  132. package/src/lib/chat/active-streams.ts +27 -0
  133. package/src/lib/chat/codex-engine.ts +16 -17
  134. package/src/lib/chat/context-builder.ts +5 -3
  135. package/src/lib/chat/engine.ts +50 -3
  136. package/src/lib/chat/reconcile.ts +117 -0
  137. package/src/lib/chat/stagent-tools.ts +1 -0
  138. package/src/lib/chat/stream-telemetry.ts +132 -0
  139. package/src/lib/chat/suggested-prompts.ts +28 -1
  140. package/src/lib/chat/system-prompt.ts +26 -1
  141. package/src/lib/chat/tool-catalog.ts +2 -1
  142. package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
  143. package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
  144. package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
  145. package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
  146. package/src/lib/chat/tools/document-tools.ts +29 -13
  147. package/src/lib/chat/tools/helpers.ts +39 -0
  148. package/src/lib/chat/tools/notification-tools.ts +9 -5
  149. package/src/lib/chat/tools/project-tools.ts +33 -0
  150. package/src/lib/chat/tools/schedule-tools.ts +44 -11
  151. package/src/lib/chat/tools/table-tools.ts +71 -0
  152. package/src/lib/chat/tools/task-tools.ts +84 -20
  153. package/src/lib/chat/tools/workflow-tools.ts +234 -32
  154. package/src/lib/constants/settings.ts +8 -18
  155. package/src/lib/data/__tests__/clear.test.ts +56 -2
  156. package/src/lib/data/clear.ts +20 -15
  157. package/src/lib/data/delete-project.ts +171 -0
  158. package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
  159. package/src/lib/db/bootstrap.ts +45 -16
  160. package/src/lib/db/index.ts +5 -0
  161. package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
  162. package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
  163. package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
  164. package/src/lib/db/migrations/0026_drop_license.sql +3 -0
  165. package/src/lib/db/migrations/meta/_journal.json +21 -0
  166. package/src/lib/db/schema.ts +68 -23
  167. package/src/lib/environment/workspace-context.ts +13 -1
  168. package/src/lib/import/dedup.ts +4 -54
  169. package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
  170. package/src/lib/instance/__tests__/detect.test.ts +115 -0
  171. package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
  172. package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
  173. package/src/lib/instance/__tests__/settings.test.ts +83 -0
  174. package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
  175. package/src/lib/instance/bootstrap.ts +270 -0
  176. package/src/lib/instance/detect.ts +49 -0
  177. package/src/lib/instance/fingerprint.ts +78 -0
  178. package/src/lib/instance/git-ops.ts +95 -0
  179. package/src/lib/instance/settings.ts +61 -0
  180. package/src/lib/instance/types.ts +77 -0
  181. package/src/lib/instance/upgrade-poller.ts +153 -0
  182. package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
  183. package/src/lib/notifications/visibility.ts +33 -0
  184. package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
  185. package/src/lib/schedules/__tests__/config.test.ts +62 -0
  186. package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
  187. package/src/lib/schedules/__tests__/integration.test.ts +82 -0
  188. package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
  189. package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
  190. package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
  191. package/src/lib/schedules/collision-check.ts +105 -0
  192. package/src/lib/schedules/config.ts +53 -0
  193. package/src/lib/schedules/scheduler.ts +232 -13
  194. package/src/lib/schedules/slot-claim.ts +105 -0
  195. package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
  196. package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
  197. package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
  198. package/src/lib/settings/openai-auth.ts +105 -10
  199. package/src/lib/settings/openai-login-manager.ts +260 -0
  200. package/src/lib/settings/runtime-setup.ts +14 -4
  201. package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
  202. package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
  203. package/src/lib/tables/enrichment-planner.ts +454 -0
  204. package/src/lib/tables/enrichment.ts +328 -0
  205. package/src/lib/tables/query-builder.ts +5 -2
  206. package/src/lib/tables/trigger-evaluator.ts +3 -2
  207. package/src/lib/theme.ts +71 -0
  208. package/src/lib/usage/ledger.ts +2 -18
  209. package/src/lib/util/__tests__/similarity.test.ts +106 -0
  210. package/src/lib/util/similarity.ts +77 -0
  211. package/src/lib/utils/format-timestamp.ts +24 -0
  212. package/src/lib/utils/stagent-paths.ts +12 -0
  213. package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
  214. package/src/lib/validators/__tests__/settings.test.ts +10 -0
  215. package/src/lib/validators/blueprint.ts +70 -9
  216. package/src/lib/validators/profile.ts +2 -2
  217. package/src/lib/validators/settings.ts +3 -1
  218. package/src/lib/workflows/__tests__/delay.test.ts +196 -0
  219. package/src/lib/workflows/__tests__/engine.test.ts +8 -0
  220. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  221. package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
  222. package/src/lib/workflows/blueprints/instantiator.ts +22 -1
  223. package/src/lib/workflows/blueprints/types.ts +10 -2
  224. package/src/lib/workflows/delay.ts +106 -0
  225. package/src/lib/workflows/engine.ts +207 -4
  226. package/src/lib/workflows/loop-executor.ts +349 -24
  227. package/src/lib/workflows/post-action.ts +91 -0
  228. package/src/lib/workflows/types.ts +166 -1
  229. package/src/app/api/license/checkout/route.ts +0 -28
  230. package/src/app/api/license/portal/route.ts +0 -26
  231. package/src/app/api/license/route.ts +0 -89
  232. package/src/app/api/license/usage/route.ts +0 -63
  233. package/src/app/api/marketplace/browse/route.ts +0 -15
  234. package/src/app/api/marketplace/import/route.ts +0 -28
  235. package/src/app/api/marketplace/publish/route.ts +0 -40
  236. package/src/app/api/onboarding/email/route.ts +0 -53
  237. package/src/app/api/settings/telemetry/route.ts +0 -14
  238. package/src/app/api/sync/export/route.ts +0 -54
  239. package/src/app/api/sync/restore/route.ts +0 -37
  240. package/src/app/api/sync/sessions/route.ts +0 -24
  241. package/src/app/auth/callback/route.ts +0 -73
  242. package/src/app/marketplace/page.tsx +0 -19
  243. package/src/components/analytics/analytics-gate-card.tsx +0 -101
  244. package/src/components/marketplace/blueprint-card.tsx +0 -61
  245. package/src/components/marketplace/marketplace-browser.tsx +0 -131
  246. package/src/components/onboarding/email-capture-card.tsx +0 -104
  247. package/src/components/settings/activation-form.tsx +0 -95
  248. package/src/components/settings/cloud-account-section.tsx +0 -147
  249. package/src/components/settings/cloud-sync-section.tsx +0 -155
  250. package/src/components/settings/subscription-section.tsx +0 -410
  251. package/src/components/settings/telemetry-section.tsx +0 -80
  252. package/src/components/shared/premium-gate-overlay.tsx +0 -50
  253. package/src/components/shared/schedule-gate-dialog.tsx +0 -64
  254. package/src/components/shared/upgrade-banner.tsx +0 -112
  255. package/src/hooks/use-supabase-auth.ts +0 -79
  256. package/src/lib/billing/email.ts +0 -54
  257. package/src/lib/billing/products.ts +0 -80
  258. package/src/lib/billing/stripe.ts +0 -101
  259. package/src/lib/cloud/supabase-browser.ts +0 -32
  260. package/src/lib/cloud/supabase-client.ts +0 -56
  261. package/src/lib/license/__tests__/features.test.ts +0 -56
  262. package/src/lib/license/__tests__/key-format.test.ts +0 -88
  263. package/src/lib/license/__tests__/manager.test.ts +0 -64
  264. package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
  265. package/src/lib/license/cloud-validation.ts +0 -60
  266. package/src/lib/license/features.ts +0 -44
  267. package/src/lib/license/key-format.ts +0 -101
  268. package/src/lib/license/limit-check.ts +0 -111
  269. package/src/lib/license/limit-queries.ts +0 -51
  270. package/src/lib/license/manager.ts +0 -345
  271. package/src/lib/license/notifications.ts +0 -59
  272. package/src/lib/license/tier-limits.ts +0 -71
  273. package/src/lib/marketplace/marketplace-client.ts +0 -107
  274. package/src/lib/sync/cloud-sync.ts +0 -235
  275. package/src/lib/telemetry/conversion-events.ts +0 -71
  276. package/src/lib/telemetry/queue.ts +0 -122
  277. package/src/lib/validators/license.ts +0 -33
@@ -1,235 +0,0 @@
1
- /**
2
- * Cloud Sync — encrypted SQLite backup and restore via Supabase Storage.
3
- *
4
- * Encryption: AES-256-GCM with HKDF-derived keys.
5
- * Envelope: [4B version][32B salt][12B IV][N bytes ciphertext][16B auth tag]
6
- *
7
- * V1: Full-database export (no incremental). Manual backup/restore only.
8
- */
9
-
10
- import { createHash, createCipheriv, createDecipheriv, randomBytes } from "crypto";
11
- import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
12
- import { join } from "path";
13
- import { tmpdir } from "os";
14
- import { getStagentDataDir } from "@/lib/utils/stagent-paths";
15
- import { getSupabaseClient, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
16
- import { sqlite } from "@/lib/db";
17
-
18
- const SYNC_VERSION = Buffer.from([0, 0, 0, 1]); // Version 1
19
- const BUCKET_NAME = "stagent-sync";
20
-
21
- export interface SyncResult {
22
- success: boolean;
23
- blobPath?: string;
24
- sizeBytes?: number;
25
- error?: string;
26
- }
27
-
28
- /**
29
- * Derive an AES-256 key from userId using HKDF-like construction.
30
- * Uses SHA-256 HMAC with a fixed info string and random salt.
31
- */
32
- function deriveKey(userId: string, salt: Buffer): Buffer {
33
- const hmac = createHash("sha256");
34
- hmac.update(salt);
35
- hmac.update(userId);
36
- hmac.update("stagent-sync-v1");
37
- return Buffer.from(hmac.digest());
38
- }
39
-
40
- /**
41
- * Encrypt data using AES-256-GCM.
42
- * Returns envelope: [4B version][32B salt][12B IV][ciphertext + 16B auth tag]
43
- */
44
- function encrypt(data: Buffer, userId: string): Buffer {
45
- const salt = randomBytes(32);
46
- const iv = randomBytes(12);
47
- const key = deriveKey(userId, salt);
48
-
49
- const cipher = createCipheriv("aes-256-gcm", key, iv);
50
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
51
- const authTag = cipher.getAuthTag();
52
-
53
- return Buffer.concat([SYNC_VERSION, salt, iv, encrypted, authTag]);
54
- }
55
-
56
- /**
57
- * Decrypt an envelope back to raw data.
58
- */
59
- function decrypt(envelope: Buffer, userId: string): Buffer {
60
- // Parse envelope
61
- const version = envelope.subarray(0, 4);
62
- if (!version.equals(SYNC_VERSION)) {
63
- throw new Error(`Unsupported sync version: ${version.toString("hex")}`);
64
- }
65
-
66
- const salt = envelope.subarray(4, 36);
67
- const iv = envelope.subarray(36, 48);
68
- const authTag = envelope.subarray(envelope.length - 16);
69
- const ciphertext = envelope.subarray(48, envelope.length - 16);
70
-
71
- const key = deriveKey(userId, salt);
72
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
73
- decipher.setAuthTag(authTag);
74
-
75
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
76
- }
77
-
78
- /**
79
- * Export the SQLite database, encrypt it, and upload to Supabase Storage.
80
- */
81
- export async function exportAndUpload(
82
- userId: string,
83
- deviceId: string,
84
- accessToken?: string
85
- ): Promise<SyncResult> {
86
- let supabase = getSupabaseClient();
87
- if (!supabase) return { success: false, error: "Cloud not configured" };
88
-
89
- // If an access token is provided, create an authenticated client for Storage RLS
90
- if (accessToken) {
91
- const { createClient } = await import("@supabase/supabase-js");
92
- supabase = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
93
- global: { headers: { Authorization: `Bearer ${accessToken}` } },
94
- auth: { persistSession: false },
95
- });
96
- }
97
-
98
- try {
99
- // 1. Create a consistent backup using better-sqlite3 .backup()
100
- const tempPath = join(tmpdir(), `stagent-export-${Date.now()}.db`);
101
- await sqlite.backup(tempPath);
102
-
103
- // 2. Read and encrypt
104
- const dbBuffer = readFileSync(tempPath);
105
- const encrypted = encrypt(dbBuffer, userId);
106
-
107
- // 3. Upload to Supabase Storage
108
- const blobPath = `${userId}/${deviceId}/${Date.now()}.enc`;
109
- const { error: uploadError } = await supabase.storage
110
- .from(BUCKET_NAME)
111
- .upload(blobPath, encrypted, {
112
- contentType: "application/octet-stream",
113
- upsert: false,
114
- });
115
-
116
- if (uploadError) {
117
- return { success: false, error: uploadError.message };
118
- }
119
-
120
- // 4. Record sync session
121
- await supabase.from("sync_sessions").insert({
122
- user_id: userId,
123
- device_name: deviceId,
124
- device_id: deviceId,
125
- blob_path: blobPath,
126
- blob_size_bytes: encrypted.length,
127
- sync_type: "backup",
128
- status: "completed",
129
- });
130
-
131
- // Clean up temp file
132
- try { require("fs").unlinkSync(tempPath); } catch { /* ignore */ }
133
-
134
- return { success: true, blobPath, sizeBytes: encrypted.length };
135
- } catch (err) {
136
- return { success: false, error: err instanceof Error ? err.message : "Export failed" };
137
- }
138
- }
139
-
140
- /**
141
- * Download the latest snapshot and restore it.
142
- * Always creates a safety backup before restoring.
143
- */
144
- export async function downloadAndRestore(userId: string): Promise<SyncResult> {
145
- const supabase = getSupabaseClient();
146
- if (!supabase) return { success: false, error: "Cloud not configured" };
147
-
148
- try {
149
- // 1. Find the latest snapshot
150
- const { data: sessions, error: listError } = await supabase
151
- .from("sync_sessions")
152
- .select("blob_path, blob_size_bytes")
153
- .eq("user_id", userId)
154
- .eq("sync_type", "backup")
155
- .eq("status", "completed")
156
- .order("created_at", { ascending: false })
157
- .limit(1);
158
-
159
- if (listError || !sessions?.length) {
160
- return { success: false, error: "No backup found" };
161
- }
162
-
163
- const { blob_path } = sessions[0];
164
-
165
- // 2. Download encrypted snapshot
166
- const { data: blob, error: downloadError } = await supabase.storage
167
- .from(BUCKET_NAME)
168
- .download(blob_path);
169
-
170
- if (downloadError || !blob) {
171
- return { success: false, error: downloadError?.message ?? "Download failed" };
172
- }
173
-
174
- const encrypted = Buffer.from(await blob.arrayBuffer());
175
-
176
- // 3. Decrypt
177
- const decrypted = decrypt(encrypted, userId);
178
-
179
- // 4. Create safety backup of current DB
180
- const dataDir = getStagentDataDir();
181
- const safetyPath = join(dataDir, `stagent-safety-${Date.now()}.db`);
182
- const dbPath = join(dataDir, "stagent.db");
183
- if (existsSync(dbPath)) {
184
- copyFileSync(dbPath, safetyPath);
185
- }
186
-
187
- // 5. Write restored DB to temp location and validate
188
- const tempRestore = join(tmpdir(), `stagent-restore-${Date.now()}.db`);
189
- writeFileSync(tempRestore, decrypted);
190
-
191
- // Basic validation: check it's a valid SQLite file
192
- const header = decrypted.subarray(0, 16).toString("ascii");
193
- if (!header.startsWith("SQLite format 3")) {
194
- return { success: false, error: "Decrypted data is not a valid SQLite database" };
195
- }
196
-
197
- // 6. Replace current DB (requires app restart)
198
- copyFileSync(tempRestore, dbPath);
199
-
200
- // Clean up
201
- try { require("fs").unlinkSync(tempRestore); } catch { /* ignore */ }
202
-
203
- // Record restore session
204
- await supabase.from("sync_sessions").insert({
205
- user_id: userId,
206
- device_name: "restore",
207
- device_id: "restore",
208
- blob_path,
209
- blob_size_bytes: decrypted.length,
210
- sync_type: "restore",
211
- status: "completed",
212
- });
213
-
214
- return { success: true, sizeBytes: decrypted.length };
215
- } catch (err) {
216
- return { success: false, error: err instanceof Error ? err.message : "Restore failed" };
217
- }
218
- }
219
-
220
- /**
221
- * List recent sync sessions for the user.
222
- */
223
- export async function listSyncSessions(userId: string) {
224
- const supabase = getSupabaseClient();
225
- if (!supabase) return [];
226
-
227
- const { data } = await supabase
228
- .from("sync_sessions")
229
- .select("*")
230
- .eq("user_id", userId)
231
- .order("created_at", { ascending: false })
232
- .limit(20);
233
-
234
- return data ?? [];
235
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * Conversion funnel tracking — lightweight, anonymous event tracking
3
- * for Community→Premium conversion optimization.
4
- *
5
- * Events: banner_impression, banner_click, checkout_started,
6
- * checkout_completed, limit_hit
7
- *
8
- * Fire-and-forget — never blocks user actions. No PII.
9
- * No-op if Supabase is not configured.
10
- */
11
-
12
- import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
13
- import { getSettingSync, setSetting } from "@/lib/settings/helpers";
14
-
15
- const SESSION_KEY = "conversion.sessionId";
16
-
17
- export type ConversionEventType =
18
- | "banner_impression"
19
- | "banner_click"
20
- | "checkout_started"
21
- | "checkout_completed"
22
- | "limit_hit";
23
-
24
- /**
25
- * Get or create an anonymous session ID for conversion tracking.
26
- * Stored in settings, not tied to any user identity.
27
- */
28
- function getSessionId(): string {
29
- let id = getSettingSync(SESSION_KEY);
30
- if (!id) {
31
- id = crypto.randomUUID();
32
- setSetting(SESSION_KEY, id).catch(() => {});
33
- }
34
- return id;
35
- }
36
-
37
- /**
38
- * Track a conversion funnel event. Fire-and-forget.
39
- *
40
- * @param eventType - The event type
41
- * @param source - Where the event originated (e.g., "memory_banner", "schedule_gate")
42
- * @param metadata - Optional additional context
43
- */
44
- export function trackConversionEvent(
45
- eventType: ConversionEventType,
46
- source?: string,
47
- metadata?: Record<string, unknown>
48
- ): void {
49
- if (!isCloudConfigured()) return;
50
-
51
- const sessionId = getSessionId();
52
- const supabaseUrl = getSupabaseUrl();
53
- const anonKey = getSupabaseAnonKey();
54
-
55
- // Fire-and-forget — don't await, don't block
56
- fetch(`${supabaseUrl}/functions/v1/conversion-ingest`, {
57
- method: "POST",
58
- headers: {
59
- "Content-Type": "application/json",
60
- Authorization: `Bearer ${anonKey}`,
61
- },
62
- body: JSON.stringify({
63
- eventType,
64
- sessionId,
65
- source: source ?? null,
66
- metadata: metadata ?? null,
67
- }),
68
- }).catch(() => {
69
- // Silently ignore — conversion tracking is non-critical
70
- });
71
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Telemetry batch queue — opt-in anonymized usage data.
3
- *
4
- * Events are queued in the settings table as a JSON array,
5
- * flushed every 5 minutes to the Supabase telemetry-ingest Edge Function.
6
- * Capped at 200 events to prevent unbounded growth.
7
- *
8
- * EXPLICITLY ABSENT from events: taskId, projectId, taskTitle,
9
- * description, result, userId, email (no PII).
10
- */
11
-
12
- import { getSettingSync, setSetting } from "@/lib/settings/helpers";
13
- import { SETTINGS_KEYS } from "@/lib/constants/settings";
14
- import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
15
-
16
- const MAX_BATCH_SIZE = 200;
17
- const FLUSH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
18
-
19
- export interface TelemetryEvent {
20
- runtimeId: string;
21
- providerId: string;
22
- modelId: string;
23
- profileDomain?: string;
24
- workflowPattern?: string;
25
- activityType: string;
26
- outcomeStatus?: string;
27
- tokenCount?: number;
28
- costMicros?: number;
29
- durationMs?: number;
30
- stepCount?: number;
31
- }
32
-
33
- /**
34
- * Check if telemetry is opt-in enabled.
35
- */
36
- export function isTelemetryEnabled(): boolean {
37
- return getSettingSync(SETTINGS_KEYS.TELEMETRY_ENABLED) === "true";
38
- }
39
-
40
- /**
41
- * Get or create the anonymous runtime ID.
42
- */
43
- export function getRuntimeId(): string {
44
- let id = getSettingSync(SETTINGS_KEYS.TELEMETRY_RUNTIME_ID);
45
- if (!id) {
46
- id = crypto.randomUUID();
47
- setSetting(SETTINGS_KEYS.TELEMETRY_RUNTIME_ID, id).catch(() => {});
48
- }
49
- return id;
50
- }
51
-
52
- /**
53
- * Queue a telemetry event for batch flush.
54
- * No-op if telemetry is disabled.
55
- */
56
- export function queueTelemetryEvent(event: TelemetryEvent): void {
57
- if (!isTelemetryEnabled()) return;
58
-
59
- const batch = loadBatch();
60
- if (batch.length >= MAX_BATCH_SIZE) {
61
- // Drop oldest events when at capacity
62
- batch.shift();
63
- }
64
- batch.push(event);
65
- saveBatch(batch);
66
- }
67
-
68
- /**
69
- * Flush the batch to the cloud telemetry endpoint.
70
- * Called on an interval from instrumentation.ts.
71
- */
72
- export async function flushTelemetryBatch(): Promise<void> {
73
- if (!isTelemetryEnabled() || !isCloudConfigured()) return;
74
-
75
- const batch = loadBatch();
76
- if (batch.length === 0) return;
77
-
78
- const supabaseUrl = getSupabaseUrl();
79
- const anonKey = getSupabaseAnonKey();
80
-
81
- try {
82
- const res = await fetch(`${supabaseUrl}/functions/v1/telemetry-ingest`, {
83
- method: "POST",
84
- headers: {
85
- "Content-Type": "application/json",
86
- Authorization: `Bearer ${anonKey}`,
87
- },
88
- body: JSON.stringify({ events: batch }),
89
- });
90
-
91
- if (res.ok) {
92
- // Clear the batch on successful flush
93
- saveBatch([]);
94
- }
95
- } catch {
96
- // Network failure — retain batch for next flush
97
- }
98
- }
99
-
100
- /**
101
- * Start the periodic flush timer. Call from instrumentation.ts.
102
- */
103
- export function startTelemetryFlush(): void {
104
- // Flush once on startup
105
- flushTelemetryBatch().catch(() => {});
106
- // Then every 5 minutes
107
- setInterval(() => flushTelemetryBatch().catch(() => {}), FLUSH_INTERVAL_MS);
108
- }
109
-
110
- function loadBatch(): TelemetryEvent[] {
111
- try {
112
- const raw = getSettingSync(SETTINGS_KEYS.TELEMETRY_BATCH);
113
- if (!raw) return [];
114
- return JSON.parse(raw) as TelemetryEvent[];
115
- } catch {
116
- return [];
117
- }
118
- }
119
-
120
- function saveBatch(batch: TelemetryEvent[]): void {
121
- setSetting(SETTINGS_KEYS.TELEMETRY_BATCH, JSON.stringify(batch)).catch(() => {});
122
- }
@@ -1,33 +0,0 @@
1
- import { z } from "zod";
2
- import { TIERS } from "@/lib/license/tier-limits";
3
-
4
- export const activateLicenseSchema = z.object({
5
- key: z
6
- .string()
7
- .min(1, "License key is required")
8
- .regex(
9
- /^STAG-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/,
10
- "Invalid license key format (expected STAG-XXXX-XXXX-XXXX-XXXX)"
11
- )
12
- .optional(),
13
- email: z.string().email("Invalid email address").optional(),
14
- tier: z.enum(TIERS).optional(),
15
- token: z.string().optional(),
16
- });
17
-
18
- export type ActivateLicenseInput = z.infer<typeof activateLicenseSchema>;
19
-
20
- export const licenseStatusSchema = z.object({
21
- tier: z.enum(TIERS),
22
- status: z.enum(["active", "inactive", "grace"]),
23
- email: z.string().nullable(),
24
- activatedAt: z.string().nullable(),
25
- expiresAt: z.string().nullable(),
26
- lastValidatedAt: z.string().nullable(),
27
- gracePeriodExpiresAt: z.string().nullable(),
28
- isPremium: z.boolean(),
29
- features: z.record(z.string(), z.boolean()),
30
- limits: z.record(z.string(), z.number()),
31
- });
32
-
33
- export type LicenseStatusResponse = z.infer<typeof licenseStatusSchema>;