@bonginkan/maria 4.4.0 → 4.4.1

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.
@@ -9506,7 +9506,7 @@ app.get("/api/status", (req, res) => {
9506
9506
  app.get("/", (req, res) => {
9507
9507
  res.json({
9508
9508
  name: "MARIA CODE API",
9509
- version: "4.4.0",
9509
+ version: "4.4.1",
9510
9510
  status: "running",
9511
9511
  environment: process.env.NODE_ENV || "development",
9512
9512
  endpoints: {
@@ -10043,12 +10043,18 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
10043
10043
  }
10044
10044
  const requestedProvider = typeof reqProvider === "string" ? reqProvider.toLowerCase() : void 0;
10045
10045
  const requestedModel = typeof model === "string" ? String(model).trim().toLowerCase() : void 0;
10046
+ const providerFromModel2 = (() => {
10047
+ if (!requestedModel) return void 0;
10048
+ if (requestedModel.startsWith("sora")) return "openai";
10049
+ if (requestedModel.startsWith("veo") || requestedModel.startsWith("gemini")) return "google";
10050
+ return void 0;
10051
+ })();
10046
10052
  const openaiKey = process.env.OPENAI_API_KEY;
10047
- const preferOpenAI = requestedProvider === "openai" || requestedModel?.startsWith("sora") || isProOrAbove && !!openaiKey;
10048
- if (preferOpenAI && openaiKey) {
10053
+ const goOpenAI = providerFromModel2 === "openai" || requestedProvider === "openai" || !providerFromModel2 && !requestedProvider && (isProOrAbove && !!openaiKey);
10054
+ if (goOpenAI && openaiKey) {
10049
10055
  const OpenAI2 = (await import('openai')).default;
10050
10056
  const client = new OpenAI2({ apiKey: openaiKey });
10051
- const soraModel = requestedModel && requestedModel.length > 0 ? requestedModel : "sora-2";
10057
+ const soraModel = requestedModel && requestedModel.startsWith("sora") ? requestedModel : "sora-2";
10052
10058
  const secondsStr = (() => {
10053
10059
  const d = Number(duration) || 8;
10054
10060
  if (d <= 4) return "4";
@@ -10740,6 +10746,136 @@ async function getFirestoreSafe() {
10740
10746
  return null;
10741
10747
  }
10742
10748
  }
10749
+ app.post("/api/v1/snapshots", rateLimitMiddleware, async (req, res) => {
10750
+ try {
10751
+ const auth = req.headers.authorization;
10752
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10753
+ const idToken = auth.substring("Bearer ".length).trim();
10754
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10755
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10756
+ const uid = decoded?.uid || decoded?.sub;
10757
+ const { projectId, taskId, summary, decisions, artifacts, refs, resumePrompt } = req.body || {};
10758
+ const pid = typeof projectId === "string" && projectId.trim() ? String(projectId).trim() : "default";
10759
+ const tid = typeof taskId === "string" && taskId.trim() ? String(taskId).trim() : "";
10760
+ const sum = typeof summary === "string" && summary.trim() ? String(summary).trim() : "";
10761
+ try {
10762
+ console.log("[Snapshots][POST] incoming", { uid: String(uid).slice(0, 8) + "\u2026", projectId: pid, taskId: tid, hasSummary: !!sum });
10763
+ } catch {
10764
+ }
10765
+ if (!tid || !sum) return res.status(400).json({ error: "bad_request", message: "taskId and summary are required" });
10766
+ const db = await getFirestoreSafe();
10767
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10768
+ const tsId = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
10769
+ const userRef = db.collection("users").doc(uid);
10770
+ const projRef = userRef.collection("projects").doc(pid);
10771
+ const taskRef = projRef.collection("tasks").doc(tid);
10772
+ const ref = taskRef.collection("snapshots").doc(tsId);
10773
+ const nowISO = (/* @__PURE__ */ new Date()).toISOString();
10774
+ try {
10775
+ await Promise.all([
10776
+ userRef.set({ updatedAt: nowISO }, { merge: true }),
10777
+ projRef.set({ projectId: pid, updatedAt: nowISO }, { merge: true }),
10778
+ taskRef.set({ taskId: tid, updatedAt: nowISO }, { merge: true })
10779
+ ]);
10780
+ } catch (e2) {
10781
+ try {
10782
+ console.warn("[Snapshots][POST] parent set warn:", e2?.message || String(e2));
10783
+ } catch {
10784
+ }
10785
+ }
10786
+ await ref.set({
10787
+ snapshotVersion: 1,
10788
+ uid,
10789
+ projectId: pid,
10790
+ taskId: tid,
10791
+ timestamp: nowISO,
10792
+ summary: sum,
10793
+ decisions: Array.isArray(decisions) ? decisions.slice(0, 50).map(String) : [],
10794
+ artifacts: Array.isArray(artifacts) ? artifacts.slice(0, 200).map(String) : [],
10795
+ links: Array.isArray(refs) ? refs.slice(0, 200).map((r2) => ({ type: "fs", ref: String(r2) })) : [],
10796
+ resumePrompt: typeof resumePrompt === "string" ? String(resumePrompt) : ""
10797
+ });
10798
+ try {
10799
+ console.log("[Snapshots][POST] saved", { path: `users/${uid}/projects/${pid}/tasks/${tid}/snapshots/${tsId}` });
10800
+ } catch {
10801
+ }
10802
+ return res.json({ success: true });
10803
+ } catch (e2) {
10804
+ try {
10805
+ console.error("[Snapshots][POST] error:", e2?.message || String(e2));
10806
+ } catch {
10807
+ }
10808
+ return res.status(500).json({ error: "internal_error" });
10809
+ }
10810
+ });
10811
+ app.get("/api/v1/get-snapshots", rateLimitMiddleware, async (req, res) => {
10812
+ try {
10813
+ const auth = req.headers.authorization;
10814
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10815
+ const idToken = auth.substring("Bearer ".length).trim();
10816
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10817
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10818
+ const uid = decoded?.uid || decoded?.sub;
10819
+ const projectId = typeof req.query.projectId === "string" && req.query.projectId.trim() ? String(req.query.projectId).trim() : "default";
10820
+ const taskId = typeof req.query.taskId === "string" && req.query.taskId.trim() ? String(req.query.taskId).trim() : void 0;
10821
+ const dateISO = typeof req.query.date === "string" && req.query.date.trim() ? String(req.query.date).trim() : void 0;
10822
+ const limit = Number.isFinite(Number(req.query.limit)) ? Math.max(1, Math.min(50, Number(req.query.limit))) : 5;
10823
+ const db = await getFirestoreSafe();
10824
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10825
+ let docs = [];
10826
+ if (taskId) {
10827
+ const q = db.collection("users").doc(uid).collection("projects").doc(projectId).collection("tasks").doc(taskId).collection("snapshots").orderBy("timestamp", "desc").limit(limit);
10828
+ const ss = await q.get();
10829
+ docs = ss.docs.map((d) => d.data());
10830
+ } else if (dateISO) {
10831
+ const start = new Date(dateISO).toISOString();
10832
+ const end = new Date(new Date(dateISO).getTime() + 24 * 60 * 60 * 1e3).toISOString();
10833
+ try {
10834
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).where("timestamp", ">=", start).where("timestamp", "<", end).orderBy("timestamp", "desc").limit(limit).get();
10835
+ docs = ss.docs.map((d) => d.data());
10836
+ } catch (e2) {
10837
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10838
+ const tasksSnap = await projRef.collection("tasks").get();
10839
+ const collected = [];
10840
+ for (const t2 of tasksSnap.docs) {
10841
+ try {
10842
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(50).get();
10843
+ for (const s2 of snaps.docs) {
10844
+ const data = s2.data();
10845
+ const ts = String(data?.timestamp || "");
10846
+ if (ts >= start && ts < end) collected.push(data);
10847
+ }
10848
+ } catch {
10849
+ }
10850
+ }
10851
+ collected.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10852
+ docs = collected.slice(0, limit);
10853
+ }
10854
+ } else {
10855
+ try {
10856
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).orderBy("timestamp", "desc").limit(limit).get();
10857
+ docs = ss.docs.map((d) => d.data());
10858
+ } catch (e2) {
10859
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10860
+ const tasksSnap = await projRef.collection("tasks").get();
10861
+ const perTaskTop = [];
10862
+ for (const t2 of tasksSnap.docs) {
10863
+ try {
10864
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(Math.max(1, Math.ceil(limit / Math.max(1, tasksSnap.size)))).get();
10865
+ for (const s2 of snaps.docs) perTaskTop.push(s2.data());
10866
+ } catch {
10867
+ }
10868
+ }
10869
+ perTaskTop.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10870
+ docs = perTaskTop.slice(0, limit);
10871
+ }
10872
+ }
10873
+ return res.json({ success: true, data: { snapshots: docs } });
10874
+ } catch (e2) {
10875
+ console.error("[Snapshots] GET error", e2);
10876
+ return res.status(500).json({ error: "internal_error" });
10877
+ }
10878
+ });
10743
10879
  app.get("/api/v1/usage", rateLimitMiddleware, async (req, res) => {
10744
10880
  try {
10745
10881
  const auth = req.headers.authorization;
@@ -9506,7 +9506,7 @@ app.get("/api/status", (req, res) => {
9506
9506
  app.get("/", (req, res) => {
9507
9507
  res.json({
9508
9508
  name: "MARIA CODE API",
9509
- version: "4.4.0",
9509
+ version: "4.4.1",
9510
9510
  status: "running",
9511
9511
  environment: process.env.NODE_ENV || "development",
9512
9512
  endpoints: {
@@ -10043,12 +10043,18 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
10043
10043
  }
10044
10044
  const requestedProvider = typeof reqProvider === "string" ? reqProvider.toLowerCase() : void 0;
10045
10045
  const requestedModel = typeof model === "string" ? String(model).trim().toLowerCase() : void 0;
10046
+ const providerFromModel2 = (() => {
10047
+ if (!requestedModel) return void 0;
10048
+ if (requestedModel.startsWith("sora")) return "openai";
10049
+ if (requestedModel.startsWith("veo") || requestedModel.startsWith("gemini")) return "google";
10050
+ return void 0;
10051
+ })();
10046
10052
  const openaiKey = process.env.OPENAI_API_KEY;
10047
- const preferOpenAI = requestedProvider === "openai" || requestedModel?.startsWith("sora") || isProOrAbove && !!openaiKey;
10048
- if (preferOpenAI && openaiKey) {
10053
+ const goOpenAI = providerFromModel2 === "openai" || requestedProvider === "openai" || !providerFromModel2 && !requestedProvider && (isProOrAbove && !!openaiKey);
10054
+ if (goOpenAI && openaiKey) {
10049
10055
  const OpenAI2 = (await import('openai')).default;
10050
10056
  const client = new OpenAI2({ apiKey: openaiKey });
10051
- const soraModel = requestedModel && requestedModel.length > 0 ? requestedModel : "sora-2";
10057
+ const soraModel = requestedModel && requestedModel.startsWith("sora") ? requestedModel : "sora-2";
10052
10058
  const secondsStr = (() => {
10053
10059
  const d = Number(duration) || 8;
10054
10060
  if (d <= 4) return "4";
@@ -10740,6 +10746,136 @@ async function getFirestoreSafe() {
10740
10746
  return null;
10741
10747
  }
10742
10748
  }
10749
+ app.post("/api/v1/snapshots", rateLimitMiddleware, async (req, res) => {
10750
+ try {
10751
+ const auth = req.headers.authorization;
10752
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10753
+ const idToken = auth.substring("Bearer ".length).trim();
10754
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10755
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10756
+ const uid = decoded?.uid || decoded?.sub;
10757
+ const { projectId, taskId, summary, decisions, artifacts, refs, resumePrompt } = req.body || {};
10758
+ const pid = typeof projectId === "string" && projectId.trim() ? String(projectId).trim() : "default";
10759
+ const tid = typeof taskId === "string" && taskId.trim() ? String(taskId).trim() : "";
10760
+ const sum = typeof summary === "string" && summary.trim() ? String(summary).trim() : "";
10761
+ try {
10762
+ console.log("[Snapshots][POST] incoming", { uid: String(uid).slice(0, 8) + "\u2026", projectId: pid, taskId: tid, hasSummary: !!sum });
10763
+ } catch {
10764
+ }
10765
+ if (!tid || !sum) return res.status(400).json({ error: "bad_request", message: "taskId and summary are required" });
10766
+ const db = await getFirestoreSafe();
10767
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10768
+ const tsId = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
10769
+ const userRef = db.collection("users").doc(uid);
10770
+ const projRef = userRef.collection("projects").doc(pid);
10771
+ const taskRef = projRef.collection("tasks").doc(tid);
10772
+ const ref = taskRef.collection("snapshots").doc(tsId);
10773
+ const nowISO = (/* @__PURE__ */ new Date()).toISOString();
10774
+ try {
10775
+ await Promise.all([
10776
+ userRef.set({ updatedAt: nowISO }, { merge: true }),
10777
+ projRef.set({ projectId: pid, updatedAt: nowISO }, { merge: true }),
10778
+ taskRef.set({ taskId: tid, updatedAt: nowISO }, { merge: true })
10779
+ ]);
10780
+ } catch (e2) {
10781
+ try {
10782
+ console.warn("[Snapshots][POST] parent set warn:", e2?.message || String(e2));
10783
+ } catch {
10784
+ }
10785
+ }
10786
+ await ref.set({
10787
+ snapshotVersion: 1,
10788
+ uid,
10789
+ projectId: pid,
10790
+ taskId: tid,
10791
+ timestamp: nowISO,
10792
+ summary: sum,
10793
+ decisions: Array.isArray(decisions) ? decisions.slice(0, 50).map(String) : [],
10794
+ artifacts: Array.isArray(artifacts) ? artifacts.slice(0, 200).map(String) : [],
10795
+ links: Array.isArray(refs) ? refs.slice(0, 200).map((r2) => ({ type: "fs", ref: String(r2) })) : [],
10796
+ resumePrompt: typeof resumePrompt === "string" ? String(resumePrompt) : ""
10797
+ });
10798
+ try {
10799
+ console.log("[Snapshots][POST] saved", { path: `users/${uid}/projects/${pid}/tasks/${tid}/snapshots/${tsId}` });
10800
+ } catch {
10801
+ }
10802
+ return res.json({ success: true });
10803
+ } catch (e2) {
10804
+ try {
10805
+ console.error("[Snapshots][POST] error:", e2?.message || String(e2));
10806
+ } catch {
10807
+ }
10808
+ return res.status(500).json({ error: "internal_error" });
10809
+ }
10810
+ });
10811
+ app.get("/api/v1/get-snapshots", rateLimitMiddleware, async (req, res) => {
10812
+ try {
10813
+ const auth = req.headers.authorization;
10814
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10815
+ const idToken = auth.substring("Bearer ".length).trim();
10816
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10817
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10818
+ const uid = decoded?.uid || decoded?.sub;
10819
+ const projectId = typeof req.query.projectId === "string" && req.query.projectId.trim() ? String(req.query.projectId).trim() : "default";
10820
+ const taskId = typeof req.query.taskId === "string" && req.query.taskId.trim() ? String(req.query.taskId).trim() : void 0;
10821
+ const dateISO = typeof req.query.date === "string" && req.query.date.trim() ? String(req.query.date).trim() : void 0;
10822
+ const limit = Number.isFinite(Number(req.query.limit)) ? Math.max(1, Math.min(50, Number(req.query.limit))) : 5;
10823
+ const db = await getFirestoreSafe();
10824
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10825
+ let docs = [];
10826
+ if (taskId) {
10827
+ const q = db.collection("users").doc(uid).collection("projects").doc(projectId).collection("tasks").doc(taskId).collection("snapshots").orderBy("timestamp", "desc").limit(limit);
10828
+ const ss = await q.get();
10829
+ docs = ss.docs.map((d) => d.data());
10830
+ } else if (dateISO) {
10831
+ const start = new Date(dateISO).toISOString();
10832
+ const end = new Date(new Date(dateISO).getTime() + 24 * 60 * 60 * 1e3).toISOString();
10833
+ try {
10834
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).where("timestamp", ">=", start).where("timestamp", "<", end).orderBy("timestamp", "desc").limit(limit).get();
10835
+ docs = ss.docs.map((d) => d.data());
10836
+ } catch (e2) {
10837
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10838
+ const tasksSnap = await projRef.collection("tasks").get();
10839
+ const collected = [];
10840
+ for (const t2 of tasksSnap.docs) {
10841
+ try {
10842
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(50).get();
10843
+ for (const s2 of snaps.docs) {
10844
+ const data = s2.data();
10845
+ const ts = String(data?.timestamp || "");
10846
+ if (ts >= start && ts < end) collected.push(data);
10847
+ }
10848
+ } catch {
10849
+ }
10850
+ }
10851
+ collected.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10852
+ docs = collected.slice(0, limit);
10853
+ }
10854
+ } else {
10855
+ try {
10856
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).orderBy("timestamp", "desc").limit(limit).get();
10857
+ docs = ss.docs.map((d) => d.data());
10858
+ } catch (e2) {
10859
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10860
+ const tasksSnap = await projRef.collection("tasks").get();
10861
+ const perTaskTop = [];
10862
+ for (const t2 of tasksSnap.docs) {
10863
+ try {
10864
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(Math.max(1, Math.ceil(limit / Math.max(1, tasksSnap.size)))).get();
10865
+ for (const s2 of snaps.docs) perTaskTop.push(s2.data());
10866
+ } catch {
10867
+ }
10868
+ }
10869
+ perTaskTop.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10870
+ docs = perTaskTop.slice(0, limit);
10871
+ }
10872
+ }
10873
+ return res.json({ success: true, data: { snapshots: docs } });
10874
+ } catch (e2) {
10875
+ console.error("[Snapshots] GET error", e2);
10876
+ return res.status(500).json({ error: "internal_error" });
10877
+ }
10878
+ });
10743
10879
  app.get("/api/v1/usage", rateLimitMiddleware, async (req, res) => {
10744
10880
  try {
10745
10881
  const auth = req.headers.authorization;
@@ -9506,7 +9506,7 @@ app.get("/api/status", (req, res) => {
9506
9506
  app.get("/", (req, res) => {
9507
9507
  res.json({
9508
9508
  name: "MARIA CODE API",
9509
- version: "4.4.0",
9509
+ version: "4.4.1",
9510
9510
  status: "running",
9511
9511
  environment: process.env.NODE_ENV || "development",
9512
9512
  endpoints: {
@@ -10043,12 +10043,18 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
10043
10043
  }
10044
10044
  const requestedProvider = typeof reqProvider === "string" ? reqProvider.toLowerCase() : void 0;
10045
10045
  const requestedModel = typeof model === "string" ? String(model).trim().toLowerCase() : void 0;
10046
+ const providerFromModel2 = (() => {
10047
+ if (!requestedModel) return void 0;
10048
+ if (requestedModel.startsWith("sora")) return "openai";
10049
+ if (requestedModel.startsWith("veo") || requestedModel.startsWith("gemini")) return "google";
10050
+ return void 0;
10051
+ })();
10046
10052
  const openaiKey = process.env.OPENAI_API_KEY;
10047
- const preferOpenAI = requestedProvider === "openai" || requestedModel?.startsWith("sora") || isProOrAbove && !!openaiKey;
10048
- if (preferOpenAI && openaiKey) {
10053
+ const goOpenAI = providerFromModel2 === "openai" || requestedProvider === "openai" || !providerFromModel2 && !requestedProvider && (isProOrAbove && !!openaiKey);
10054
+ if (goOpenAI && openaiKey) {
10049
10055
  const OpenAI2 = (await import('openai')).default;
10050
10056
  const client = new OpenAI2({ apiKey: openaiKey });
10051
- const soraModel = requestedModel && requestedModel.length > 0 ? requestedModel : "sora-2";
10057
+ const soraModel = requestedModel && requestedModel.startsWith("sora") ? requestedModel : "sora-2";
10052
10058
  const secondsStr = (() => {
10053
10059
  const d = Number(duration) || 8;
10054
10060
  if (d <= 4) return "4";
@@ -10740,6 +10746,136 @@ async function getFirestoreSafe() {
10740
10746
  return null;
10741
10747
  }
10742
10748
  }
10749
+ app.post("/api/v1/snapshots", rateLimitMiddleware, async (req, res) => {
10750
+ try {
10751
+ const auth = req.headers.authorization;
10752
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10753
+ const idToken = auth.substring("Bearer ".length).trim();
10754
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10755
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10756
+ const uid = decoded?.uid || decoded?.sub;
10757
+ const { projectId, taskId, summary, decisions, artifacts, refs, resumePrompt } = req.body || {};
10758
+ const pid = typeof projectId === "string" && projectId.trim() ? String(projectId).trim() : "default";
10759
+ const tid = typeof taskId === "string" && taskId.trim() ? String(taskId).trim() : "";
10760
+ const sum = typeof summary === "string" && summary.trim() ? String(summary).trim() : "";
10761
+ try {
10762
+ console.log("[Snapshots][POST] incoming", { uid: String(uid).slice(0, 8) + "\u2026", projectId: pid, taskId: tid, hasSummary: !!sum });
10763
+ } catch {
10764
+ }
10765
+ if (!tid || !sum) return res.status(400).json({ error: "bad_request", message: "taskId and summary are required" });
10766
+ const db = await getFirestoreSafe();
10767
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10768
+ const tsId = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
10769
+ const userRef = db.collection("users").doc(uid);
10770
+ const projRef = userRef.collection("projects").doc(pid);
10771
+ const taskRef = projRef.collection("tasks").doc(tid);
10772
+ const ref = taskRef.collection("snapshots").doc(tsId);
10773
+ const nowISO = (/* @__PURE__ */ new Date()).toISOString();
10774
+ try {
10775
+ await Promise.all([
10776
+ userRef.set({ updatedAt: nowISO }, { merge: true }),
10777
+ projRef.set({ projectId: pid, updatedAt: nowISO }, { merge: true }),
10778
+ taskRef.set({ taskId: tid, updatedAt: nowISO }, { merge: true })
10779
+ ]);
10780
+ } catch (e2) {
10781
+ try {
10782
+ console.warn("[Snapshots][POST] parent set warn:", e2?.message || String(e2));
10783
+ } catch {
10784
+ }
10785
+ }
10786
+ await ref.set({
10787
+ snapshotVersion: 1,
10788
+ uid,
10789
+ projectId: pid,
10790
+ taskId: tid,
10791
+ timestamp: nowISO,
10792
+ summary: sum,
10793
+ decisions: Array.isArray(decisions) ? decisions.slice(0, 50).map(String) : [],
10794
+ artifacts: Array.isArray(artifacts) ? artifacts.slice(0, 200).map(String) : [],
10795
+ links: Array.isArray(refs) ? refs.slice(0, 200).map((r2) => ({ type: "fs", ref: String(r2) })) : [],
10796
+ resumePrompt: typeof resumePrompt === "string" ? String(resumePrompt) : ""
10797
+ });
10798
+ try {
10799
+ console.log("[Snapshots][POST] saved", { path: `users/${uid}/projects/${pid}/tasks/${tid}/snapshots/${tsId}` });
10800
+ } catch {
10801
+ }
10802
+ return res.json({ success: true });
10803
+ } catch (e2) {
10804
+ try {
10805
+ console.error("[Snapshots][POST] error:", e2?.message || String(e2));
10806
+ } catch {
10807
+ }
10808
+ return res.status(500).json({ error: "internal_error" });
10809
+ }
10810
+ });
10811
+ app.get("/api/v1/get-snapshots", rateLimitMiddleware, async (req, res) => {
10812
+ try {
10813
+ const auth = req.headers.authorization;
10814
+ if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
10815
+ const idToken = auth.substring("Bearer ".length).trim();
10816
+ const decoded = await decodeFirebaseToken(idToken).catch(() => null);
10817
+ if (!decoded) return res.status(401).json({ error: "unauthorized" });
10818
+ const uid = decoded?.uid || decoded?.sub;
10819
+ const projectId = typeof req.query.projectId === "string" && req.query.projectId.trim() ? String(req.query.projectId).trim() : "default";
10820
+ const taskId = typeof req.query.taskId === "string" && req.query.taskId.trim() ? String(req.query.taskId).trim() : void 0;
10821
+ const dateISO = typeof req.query.date === "string" && req.query.date.trim() ? String(req.query.date).trim() : void 0;
10822
+ const limit = Number.isFinite(Number(req.query.limit)) ? Math.max(1, Math.min(50, Number(req.query.limit))) : 5;
10823
+ const db = await getFirestoreSafe();
10824
+ if (!db) return res.status(503).json({ error: "unavailable", message: "database is not configured" });
10825
+ let docs = [];
10826
+ if (taskId) {
10827
+ const q = db.collection("users").doc(uid).collection("projects").doc(projectId).collection("tasks").doc(taskId).collection("snapshots").orderBy("timestamp", "desc").limit(limit);
10828
+ const ss = await q.get();
10829
+ docs = ss.docs.map((d) => d.data());
10830
+ } else if (dateISO) {
10831
+ const start = new Date(dateISO).toISOString();
10832
+ const end = new Date(new Date(dateISO).getTime() + 24 * 60 * 60 * 1e3).toISOString();
10833
+ try {
10834
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).where("timestamp", ">=", start).where("timestamp", "<", end).orderBy("timestamp", "desc").limit(limit).get();
10835
+ docs = ss.docs.map((d) => d.data());
10836
+ } catch (e2) {
10837
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10838
+ const tasksSnap = await projRef.collection("tasks").get();
10839
+ const collected = [];
10840
+ for (const t2 of tasksSnap.docs) {
10841
+ try {
10842
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(50).get();
10843
+ for (const s2 of snaps.docs) {
10844
+ const data = s2.data();
10845
+ const ts = String(data?.timestamp || "");
10846
+ if (ts >= start && ts < end) collected.push(data);
10847
+ }
10848
+ } catch {
10849
+ }
10850
+ }
10851
+ collected.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10852
+ docs = collected.slice(0, limit);
10853
+ }
10854
+ } else {
10855
+ try {
10856
+ const ss = await db.collectionGroup("snapshots").where("uid", "==", uid).where("projectId", "==", projectId).orderBy("timestamp", "desc").limit(limit).get();
10857
+ docs = ss.docs.map((d) => d.data());
10858
+ } catch (e2) {
10859
+ const projRef = db.collection("users").doc(uid).collection("projects").doc(projectId);
10860
+ const tasksSnap = await projRef.collection("tasks").get();
10861
+ const perTaskTop = [];
10862
+ for (const t2 of tasksSnap.docs) {
10863
+ try {
10864
+ const snaps = await t2.ref.collection("snapshots").orderBy("timestamp", "desc").limit(Math.max(1, Math.ceil(limit / Math.max(1, tasksSnap.size)))).get();
10865
+ for (const s2 of snaps.docs) perTaskTop.push(s2.data());
10866
+ } catch {
10867
+ }
10868
+ }
10869
+ perTaskTop.sort((a, b) => String(b?.timestamp || "").localeCompare(String(a?.timestamp || "")));
10870
+ docs = perTaskTop.slice(0, limit);
10871
+ }
10872
+ }
10873
+ return res.json({ success: true, data: { snapshots: docs } });
10874
+ } catch (e2) {
10875
+ console.error("[Snapshots] GET error", e2);
10876
+ return res.status(500).json({ error: "internal_error" });
10877
+ }
10878
+ });
10743
10879
  app.get("/api/v1/usage", rateLimitMiddleware, async (req, res) => {
10744
10880
  try {
10745
10881
  const auth = req.headers.authorization;