@backtrack-js/browser 0.5.3 → 0.5.5

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 (45) hide show
  1. package/README.md +56 -54
  2. package/bin/cli.cjs +5 -2
  3. package/bin/export-context.cjs +50 -0
  4. package/bin/mcp.cjs +19 -0
  5. package/bin/symbolize.cjs +214 -66
  6. package/dist/capturers/browser.d.ts +18 -0
  7. package/dist/capturers/errors.d.ts +3 -1
  8. package/dist/capturers/network.d.ts +11 -1
  9. package/dist/capturers/rrweb.d.ts +2 -0
  10. package/dist/capturers/sanitizer.d.ts +1 -0
  11. package/dist/core/flight-recorder.d.ts +9 -0
  12. package/dist/dev-server.cjs +3 -6
  13. package/dist/dev-server.js +128 -90
  14. package/dist/incident-reports-9iXHIaBD.js +1242 -0
  15. package/dist/incident-reports-Cjos7N-u.cjs +51 -0
  16. package/dist/index.cjs +53 -38
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +2357 -2079
  19. package/dist/mcp/server.d.ts +5 -0
  20. package/dist/mcp.cjs +2 -0
  21. package/dist/mcp.js +203 -0
  22. package/dist/storage/batch-writer.d.ts +6 -0
  23. package/dist/storage/incident-manager.d.ts +10 -6
  24. package/dist/types/artifact.d.ts +50 -0
  25. package/dist/types/browser-context.d.ts +34 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/options.d.ts +15 -0
  28. package/dist/types/timeline.d.ts +11 -0
  29. package/dist/types/visual-proposal.d.ts +1 -0
  30. package/dist/utils/context-package.d.ts +4 -0
  31. package/dist/utils/incident-query.d.ts +93 -0
  32. package/dist/utils/incident-reports.d.ts +18 -0
  33. package/dist/utils/interactions.d.ts +13 -0
  34. package/dist/utils/markdown.d.ts +3 -0
  35. package/dist/validation/structure.d.ts +11 -0
  36. package/dist/validation/validate.d.ts +3 -1
  37. package/dist/viewer/assets/{index-092olPra.css → index-DXHvcqkw.css} +1 -1
  38. package/dist/viewer/assets/index-_sgOsnDM.js +337 -0
  39. package/dist/viewer/index.html +2 -2
  40. package/dist/widget/element-editor.d.ts +7 -0
  41. package/dist/widget/widget.d.ts +9 -2
  42. package/package.json +113 -99
  43. package/dist/markdown-Ao5Ah9MF.cjs +0 -8
  44. package/dist/markdown-D3C94OlY.js +0 -493
  45. package/dist/viewer/assets/index-BkV3C4kX.js +0 -197
@@ -0,0 +1,18 @@
1
+ import type { BatchWriter } from '../storage/batch-writer';
2
+ /** Diagnostic URLs never include embedded data or caller overrides that expose query values. */
3
+ export declare function diagnosticUrl(raw: string): string;
4
+ export declare class BrowserCapturer {
5
+ private writer;
6
+ private nextSequence;
7
+ private recording;
8
+ private removers;
9
+ private viewportTimer;
10
+ private pendingViewport?;
11
+ private lastViewport;
12
+ constructor(writer: BatchWriter, nextSequence: () => number);
13
+ private listen;
14
+ private marker;
15
+ start(): void;
16
+ flushViewport(): void;
17
+ stop(): void;
18
+ }
@@ -1,14 +1,16 @@
1
+ import type { PrivacyOptions } from '../types/options';
1
2
  import type { BatchWriter } from '../storage/batch-writer';
2
3
  import type { IncidentManager } from '../storage/incident-manager';
3
4
  import type { ErrorContext } from '../types/options';
4
5
  export declare class ErrorCapturer {
6
+ private privacy?;
5
7
  private writer;
6
8
  private incidentManager;
7
9
  private sequenceProvider;
8
10
  private isRecording;
9
11
  private errorHandler?;
10
12
  private rejectionHandler?;
11
- constructor(writer: BatchWriter, incidentManager: IncidentManager, sequenceProvider: () => number);
13
+ constructor(writer: BatchWriter, incidentManager: IncidentManager, sequenceProvider: () => number, privacy?: PrivacyOptions | undefined);
12
14
  start(): void;
13
15
  private handleGlobalError;
14
16
  private handleUnhandledRejection;
@@ -5,6 +5,7 @@ export interface NetworkCapturerConfig {
5
5
  ignoredUrls?: (string | RegExp)[];
6
6
  sanitizeUrlCallback?: (url: URL) => string;
7
7
  capturePayloads?: boolean;
8
+ captureResponseBodies?: boolean;
8
9
  maxPayloadSize?: number;
9
10
  }
10
11
  export declare class NetworkCapturer {
@@ -18,12 +19,21 @@ export declare class NetworkCapturer {
18
19
  private originalXHROpen;
19
20
  private originalXHRSend;
20
21
  private originalXHRSetHeader;
22
+ private wrappedXHROpen;
23
+ private wrappedXHRSend;
24
+ private wrappedXHRSetHeader;
25
+ private payloadControllers;
26
+ private pendingEvents;
21
27
  private xhrMetadata;
22
28
  constructor(writer: BatchWriter, incidentManager: IncidentManager, sequenceProvider: () => number, config?: Partial<NetworkCapturerConfig>);
23
29
  start(): void;
24
30
  private shouldIgnoreUrl;
25
31
  private instrumentFetch;
32
+ private stopFetchCapture?;
33
+ private captureResponseBody;
26
34
  private instrumentXHR;
35
+ private stopXHRCapture?;
27
36
  private recordNetworkEvent;
28
- stop(): void;
37
+ flush(): Promise<void>;
38
+ stop(): Promise<void>;
29
39
  }
@@ -13,5 +13,7 @@ export declare class RrwebCapturer {
13
13
  constructor(writer: BatchWriter, config?: RrwebCapturerConfig);
14
14
  getDroppedEventsCount(): number;
15
15
  start(): void;
16
+ /** Flush pending DOM mutations into a fresh privacy-filtered editing reference. */
17
+ takeFullSnapshot(): void;
16
18
  stop(): void;
17
19
  }
@@ -17,6 +17,7 @@ export declare function sanitizeUrl(rawUrl: string, customCallback?: (url: URL)
17
17
  * Redige padrões reconhecíveis de segredos em texto puro.
18
18
  */
19
19
  export declare function redactSensitiveString(str: string): string;
20
+ export declare function isSensitiveKey(key: string): boolean;
20
21
  /**
21
22
  * Serializador e sanitizador universal à prova de falhas:
22
23
  * - Detecta ciclos circulares
@@ -3,8 +3,10 @@ import type { FlightRecorder, FlightRecorderOptions, CaptureOptions, ErrorContex
3
3
  import type { RecorderHealth } from '../types/health';
4
4
  import type { IncidentSummary } from '../types/incident';
5
5
  import type { FlightRecorderArtifact } from '../types/artifact';
6
+ import type { VisualProposal } from '../types/visual-proposal';
6
7
  export declare class FlightRecorderImpl implements FlightRecorder {
7
8
  private static activeInstance;
9
+ private static initializingPromise;
8
10
  private options;
9
11
  private stateMachine;
10
12
  private db;
@@ -18,6 +20,8 @@ export declare class FlightRecorderImpl implements FlightRecorder {
18
20
  private navigationCapturer;
19
21
  private rrwebCapturer;
20
22
  private performanceCapturer;
23
+ private browserCapturer;
24
+ private deferredVisualIncidents;
21
25
  private sequence;
22
26
  private retentionIntervalTimer;
23
27
  private retentionPromise;
@@ -54,6 +58,10 @@ export declare class FlightRecorderImpl implements FlightRecorder {
54
58
  private clearRuntimeReferences;
55
59
  stop(): Promise<void>;
56
60
  capture(reason?: string, windowSeconds?: number, options?: CaptureOptions): Promise<string>;
61
+ /** Attach QA intent to the original finalized replay without changing its time range. */
62
+ saveVisualProposal(incidentId: string, proposal: VisualProposal): Promise<void>;
63
+ /** Cancellation never deletes a proposal that has already been saved. */
64
+ discardVisualProposal(incidentId: string): Promise<void>;
57
65
  captureException(error: unknown, context?: ErrorContext): Promise<string | undefined> | void;
58
66
  listIncidents(): Promise<IncidentSummary[]>;
59
67
  getArtifact(incidentId: string): Promise<FlightRecorderArtifact>;
@@ -63,6 +71,7 @@ export declare class FlightRecorderImpl implements FlightRecorder {
63
71
  }): Promise<FlightRecorderArtifact>;
64
72
  deleteIncident(incidentId: string): Promise<void>;
65
73
  clear(): Promise<void>;
74
+ private getDroppedEventsCount;
66
75
  getHealth(): RecorderHealth;
67
76
  getSequence(): number;
68
77
  flush(): Promise<void>;
@@ -1,6 +1,3 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("fs"),y=require("path"),I=require("./markdown-Ao5Ah9MF.cjs"),U=/^[a-zA-Z0-9_-]{1,128}$/,W=/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i,_=t=>U.test(t)&&!W.test(t),A=t=>["localhost","127.0.0.1","[::1]"].includes(t.toLowerCase()),Q=t=>t==="127.0.0.1"||t==="::1"||t==="::ffff:127.0.0.1";function V(t,o){if(!Q(t.socket.remoteAddress))return!1;try{const S=t.headers.host;if(!S)return!1;const d=new URL(`http://${S}`);if(d.host!==S.toLowerCase()||!A(d.hostname))return!1;const f=t.headers.origin;if(!f)return!(o!=null&&o.length);const h=new URL(f);return!["http:","https:"].includes(h.protocol)||!A(h.hostname)||h.origin!==f?!1:o!=null&&o.length?o.includes(f):h.host===d.host}catch{return!1}}const Z=(t,o)=>t.triggeredAt-o.triggeredAt||(t.id<o.id?-1:t.id>o.id?1:0);function m(t){return e.existsSync(t)&&e.lstatSync(t).isFile()}function G(t){var L;const o=(t==null?void 0:t.directory)??(t==null?void 0:t.outputDir);if(!t||typeof o!="string"||!y.isAbsolute(o))throw new Error('[Backtrack dev-server] A opção "directory" (ou "outputDir") absoluta é obrigatória.');const S=y.resolve(o),d=y.join(S,"incidents"),f=y.join(S,"latest.md"),h=t.maxBodyBytes??50*1024*1024,F=t.maxIncidents??20,v=t.maxStorageBytes??200*1024*1024;for(const i of[h,F,v])if(!Number.isSafeInteger(i)||i<=0)throw new Error("Backtrack limits must be positive safe integers.");if((L=t.allowedOrigins)!=null&&L.some(i=>{try{const r=new URL(i);return!A(r.hostname)||!["http:","https:"].includes(r.protocol)||r.origin!==i}catch{return!0}}))throw new Error("Backtrack origins must be local HTTP(S) origins.");let P=Promise.resolve();function x(){const i=[];for(const r of e.readdirSync(d)){if(!r.endsWith(".json"))continue;const s=r.slice(0,-5);if(!_(s))continue;const n=y.join(d,r),c=y.join(d,`${s}.md`);try{if(!m(n)||!m(c)||e.statSync(n).size>h)continue;const a=I.validateFlightRecorderArtifact(JSON.parse(e.readFileSync(n,"utf8")));if(!a.success||a.data.incident.id!==s||!e.readFileSync(c,"utf8").startsWith(`# Incidente Backtrack
2
- `))continue;i.push({id:s,jsonPath:n,mdPath:c,triggeredAt:a.data.incident.triggeredAt,size:e.statSync(n).size+e.statSync(c).size})}catch{}}return i.sort(Z)}function D(){const i=x(),r=i[i.length-1];if(!r)throw new Error("No complete incident to publish");const s=`> **Artefato completo:** [\`${r.id}.json\`](incidents/${r.id}.json)
3
-
4
- `+e.readFileSync(r.mdPath,"utf8"),n=`${f}.${Date.now()}.tmp`;try{e.writeFileSync(n,s,{encoding:"utf8",flag:"wx"}),e.renameSync(n,f)}finally{m(n)&&e.unlinkSync(n)}return r.id}function E(i){const r=x();let s=r.length,n=r.reduce((c,a)=>c+a.size,e.statSync(f).size);for(const c of r){if(s<=F&&n<=v)break;c.id!==i&&(e.unlinkSync(c.mdPath),e.unlinkSync(c.jsonPath),s--,n-=c.size)}}return(i,r,s)=>{const n=(l,$)=>{!r.destroyed&&!r.headersSent&&(r.writeHead(l,{"Content-Type":"application/json",...l===405?{Allow:"POST"}:{}}),r.end(JSON.stringify($)))};if((i.url||"").split("?")[0]!=="/__backtrack/incident"){s?s():n(404,{error:"Not found"});return}if(i.method!=="POST"){n(405,{error:"Method Not Allowed"});return}if((i.headers["content-type"]||"").split(";")[0].trim().toLowerCase()!=="application/json"){n(415,{error:"Unsupported Media Type: application/json required"});return}if(!V(i,t.allowedOrigins)){n(403,{error:"Forbidden: invalid origin or non-local access"});return}let c=0,a=!1;const p=[];i.on("error",()=>{a=!0,p.length=0}),i.on("aborted",()=>{a=!0,p.length=0}),i.on("data",l=>{a||(c+=l.length,c>h?(a=!0,p.length=0,r.setHeader("Connection","close"),n(413,{error:"Payload Too Large"})):p.push(l))}),i.on("end",()=>{if(a)return;const l=Buffer.concat(p).toString("utf8");p.length=0;let $;try{$=JSON.parse(l)}catch{n(400,{error:"Invalid JSON"});return}const z=I.validateFlightRecorderArtifact($);if(!z.success){n(400,{error:"Invalid artifact schema"});return}const M=z.data,g=M.incident.id;if(!_(g)){n(400,{error:"Invalid incident ID format"});return}P=P.then(()=>{try{for(const u of[S,d]){if(e.existsSync(u)&&!e.lstatSync(u).isDirectory())throw new Error("Invalid output directory");e.mkdirSync(u,{recursive:!0})}const w=y.join(d,`${g}.json`),k=y.join(d,`${g}.md`),j=e.existsSync(w),O=e.existsSync(k);if(j&&(!m(w)||e.readFileSync(w,"utf8")!==l)||O&&(!m(k)||!j)){n(409,{error:"Conflict: incident ID exists with different content"});return}if(j&&O){const u=D();E(u),n(200,{id:g,status:"already_persisted"});return}const N=I.formatIncidentMarkdown(M),H=`> **Artefato completo:** [\`${g}.json\`](incidents/${g}.json)
5
-
6
- `;if(Buffer.byteLength(l)+2*Buffer.byteLength(N)+Buffer.byteLength(H)>v){n(413,{error:"Incident size exceeds maxStorageBytes limit"});return}const R=`${Date.now()}-${Math.random().toString(36).slice(2)}`,B=`${w}.${R}.tmp`,b=`${k}.${R}.tmp`;let T=!1,C=!1;try{j||e.writeFileSync(B,l,{encoding:"utf8",flag:"wx"}),e.writeFileSync(b,N,{encoding:"utf8",flag:"wx"}),j||(e.renameSync(B,w),T=!0),e.renameSync(b,k),C=!0,D()}catch(u){throw C&&m(k)&&e.unlinkSync(k),T&&m(w)&&e.unlinkSync(w),u}finally{for(const u of[B,b])m(u)&&e.unlinkSync(u)}const J=x();E(J[J.length-1].id),n(201,{id:g,status:"saved"})}catch{n(500,{error:"Failed to persist incident"})}})})}}exports.createBacktrackMiddleware=G;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("fs"),f=require("path"),b=require("./incident-reports-Cjos7N-u.cjs"),rt=/^[a-zA-Z0-9_-]{1,128}$/,it=/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i,q=e=>rt.test(e)&&!it.test(e),N=e=>["localhost","127.0.0.1","[::1]"].includes(e.toLowerCase()),ot=e=>e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1";function ct(e,i){if(!ot(e.socket.remoteAddress))return!1;try{const s=e.headers.host;if(!s)return!1;const l=new URL(`http://${s}`);if(l.host!==s.toLowerCase()||!N(l.hostname))return!1;const S=e.headers.origin;if(!S)return!(i!=null&&i.length);const g=new URL(S);return!["http:","https:"].includes(g.protocol)||!N(g.hostname)||g.origin!==S?!1:i!=null&&i.length?i.includes(S):g.host===l.host}catch{return!1}}const E=`<!-- backtrack:context:1 -->
2
+ `,tt=["incident.md","logs.md","timeline.md","replay.md","agent-instructions.md"];function M(e){if(!t.existsSync(e))return[];if(!t.lstatSync(e).isDirectory())throw new Error("Invalid context directory");return tt.map(i=>{const s=f.join(e,i);if(!h(s)||t.statSync(s).size>1024*1024||!t.readFileSync(s,"utf8").startsWith(E))throw new Error("Invalid context report");return s})}function C(e,i){for(const s of e)h(s)&&t.unlinkSync(s);t.existsSync(i)&&t.lstatSync(i).isDirectory()&&!t.readdirSync(i).length&&t.rmdirSync(i)}const st=(e,i)=>e.triggeredAt-i.triggeredAt||(e.id<i.id?-1:e.id>i.id?1:0);function h(e){return t.existsSync(e)&&t.lstatSync(e).isFile()}function at(e){var U;const i=(e==null?void 0:e.directory)??(e==null?void 0:e.outputDir);if(!e||typeof i!="string"||!f.isAbsolute(i))throw new Error('[Backtrack dev-server] A opção "directory" (ou "outputDir") absoluta é obrigatória.');const s=f.resolve(i),l=f.join(s,"incidents"),S=f.join(s,"latest.md"),g=e.maxBodyBytes??50*1024*1024,T=e.maxIncidents??20,R=e.maxStorageBytes??200*1024*1024;for(const o of[g,T,R])if(!Number.isSafeInteger(o)||o<=0)throw new Error("Backtrack limits must be positive safe integers.");if((U=e.allowedOrigins)!=null&&U.some(o=>{try{const r=new URL(o);return!N(r.hostname)||!["http:","https:"].includes(r.protocol)||r.origin!==o}catch{return!0}}))throw new Error("Backtrack origins must be local HTTP(S) origins.");let _=Promise.resolve();function A(){const o=[];for(const r of t.readdirSync(l)){if(!r.endsWith(".json"))continue;const d=r.slice(0,-5);if(!q(d))continue;const n=f.join(l,r),c=f.join(l,`${d}.md`);try{if(!h(n)||!h(c)||t.statSync(n).size>g)continue;const y=b.validateFlightRecorderArtifact(JSON.parse(t.readFileSync(n,"utf8")));if(!y.success||y.data.incident.id!==d||!t.readFileSync(c,"utf8").startsWith(`# Incidente Backtrack
3
+ `))continue;const m=M(f.join(l,d));o.push({id:d,jsonPath:n,mdPath:c,reportFiles:m,triggeredAt:y.data.incident.triggeredAt,size:t.statSync(n).size+t.statSync(c).size+m.reduce((u,j)=>u+t.statSync(j).size,0)})}catch{}}return o.sort(st)}function J(){const o=A(),r=o[o.length-1];if(!r)throw new Error("No complete incident to publish");const d=JSON.parse(t.readFileSync(r.jsonPath,"utf8")),n=b.formatLatestIncidentMarkdown(d,r.reportFiles.length?"incidents/"+r.id:void 0),c=`${S}.${Date.now()}.tmp`;try{t.writeFileSync(c,n,{encoding:"utf8",flag:"wx"}),t.renameSync(c,S)}finally{h(c)&&t.unlinkSync(c)}return r.id}function H(o){const r=A();let d=r.length,n=r.reduce((c,y)=>c+y.size,t.statSync(S).size);for(const c of r){if(d<=T&&n<=R)break;c.id!==o&&(C(c.reportFiles,f.join(l,c.id)),t.unlinkSync(c.mdPath),t.unlinkSync(c.jsonPath),d--,n-=c.size)}}return(o,r,d)=>{const n=(u,j)=>{!r.destroyed&&!r.headersSent&&(r.writeHead(u,{"Content-Type":"application/json",...u===405?{Allow:"POST"}:{}}),r.end(JSON.stringify(j)))};if((o.url||"").split("?")[0]!=="/__backtrack/incident"){d?d():n(404,{error:"Not found"});return}if(o.method!=="POST"){n(405,{error:"Method Not Allowed"});return}if((o.headers["content-type"]||"").split(";")[0].trim().toLowerCase()!=="application/json"){n(415,{error:"Unsupported Media Type: application/json required"});return}if(!ct(o,e.allowedOrigins)){n(403,{error:"Forbidden: invalid origin or non-local access"});return}let c=0,y=!1;const m=[];o.on("error",()=>{y=!0,m.length=0}),o.on("aborted",()=>{y=!0,m.length=0}),o.on("data",u=>{y||(c+=u.length,c>g?(y=!0,m.length=0,r.setHeader("Connection","close"),n(413,{error:"Payload Too Large"})):m.push(u))}),o.on("end",()=>{if(y)return;const u=Buffer.concat(m).toString("utf8");m.length=0;let j;try{j=JSON.parse(u)}catch{n(400,{error:"Invalid JSON"});return}const W=b.validateFlightRecorderArtifact(j);if(!W.success){n(400,{error:"Invalid artifact schema"});return}const $=W.data,w=$.incident.id;if(!q(w)){n(400,{error:"Invalid incident ID format"});return}_=_.then(()=>{try{for(const a of[s,l]){if(t.existsSync(a)&&!t.lstatSync(a).isDirectory())throw new Error("Invalid output directory");t.mkdirSync(a,{recursive:!0})}const x=f.join(l,`${w}.json`),v=f.join(l,`${w}.md`),p=t.existsSync(x),k=t.existsSync(v);if(p&&(!h(x)||t.readFileSync(x,"utf8")!==u)||k&&(!h(v)||!p)){n(409,{error:"Conflict: incident ID exists with different content"});return}const B=f.join(l,w);let D;try{D=M(B)}catch{n(409,{error:"Conflict: context directory contains unrecognized reports"});return}if(D.length&&(!p||!k)){n(409,{error:"Conflict: incomplete incident with existing context"});return}if(p&&k&&D.length){const a=J();H(a),n(200,{id:w,status:"already_persisted"});return}const L=b.generateIncidentReports($,{maxSizeBytes:32*1024-Buffer.byteLength(E)}),K=L.files["incident.md"],Q={...L.files,"agent-instructions.md":L.agentInstructions},et=b.formatLatestIncidentMarkdown($,"incidents/"+w),nt=Object.values(Q).reduce((a,F)=>a+Buffer.byteLength(E+F),0);if(Buffer.byteLength(u)+Buffer.byteLength(K)+nt+Buffer.byteLength(et)>R){n(413,{error:"Incident size exceeds maxStorageBytes limit"});return}const P=`${Date.now()}-${Math.random().toString(36).slice(2)}`,z=`${x}.${P}.tmp`,O=`${v}.${P}.tmp`,I=`${B}.${P}.tmp`;let V=!1,X=!1,Z=!1;const G=[];try{p||t.writeFileSync(z,u,{encoding:"utf8",flag:"wx"}),k||t.writeFileSync(O,K,{encoding:"utf8",flag:"wx"}),t.mkdirSync(I);for(const a of tt){const F=f.join(I,a);G.push(F),t.writeFileSync(F,E+Q[a],{encoding:"utf8",flag:"wx"})}p||(t.renameSync(z,x),V=!0),k||(t.renameSync(O,v),X=!0),t.renameSync(I,B),Z=!0,J()}catch(a){throw Z&&C(M(B),B),X&&h(v)&&t.unlinkSync(v),V&&h(x)&&t.unlinkSync(x),a}finally{t.existsSync(I)&&C(G,I);for(const a of[z,O])h(a)&&t.unlinkSync(a)}const Y=A();H(Y[Y.length-1].id),n(p&&k?200:201,{id:w,status:p&&k?"already_persisted":"saved"})}catch{n(500,{error:"Failed to persist incident"})}})})}}exports.createBacktrackMiddleware=at;
@@ -1,88 +1,109 @@
1
- import e from "fs";
2
- import y from "path";
3
- import { v as J, f as U } from "./markdown-D3C94OlY.js";
4
- const W = /^[a-zA-Z0-9_-]{1,128}$/, Q = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i, _ = (t) => W.test(t) && !Q.test(t), b = (t) => ["localhost", "127.0.0.1", "[::1]"].includes(t.toLowerCase()), V = (t) => t === "127.0.0.1" || t === "::1" || t === "::ffff:127.0.0.1";
5
- function Z(t, i) {
6
- if (!V(t.socket.remoteAddress)) return !1;
1
+ import t from "fs";
2
+ import f from "path";
3
+ import { v as Y, g as it, f as q } from "./incident-reports-9iXHIaBD.js";
4
+ const ot = /^[a-zA-Z0-9_-]{1,128}$/, st = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i, tt = (e) => ot.test(e) && !st.test(e), N = (e) => ["localhost", "127.0.0.1", "[::1]"].includes(e.toLowerCase()), ct = (e) => e === "127.0.0.1" || e === "::1" || e === "::ffff:127.0.0.1";
5
+ function at(e, i) {
6
+ if (!ct(e.socket.remoteAddress)) return !1;
7
7
  try {
8
- const S = t.headers.host;
9
- if (!S) return !1;
10
- const d = new URL(`http://${S}`);
11
- if (d.host !== S.toLowerCase() || !b(d.hostname)) return !1;
12
- const u = t.headers.origin;
13
- if (!u) return !(i != null && i.length);
14
- const h = new URL(u);
15
- return !["http:", "https:"].includes(h.protocol) || !b(h.hostname) || h.origin !== u ? !1 : i != null && i.length ? i.includes(u) : h.host === d.host;
8
+ const c = e.headers.host;
9
+ if (!c) return !1;
10
+ const l = new URL(`http://${c}`);
11
+ if (l.host !== c.toLowerCase() || !N(l.hostname)) return !1;
12
+ const S = e.headers.origin;
13
+ if (!S) return !(i != null && i.length);
14
+ const p = new URL(S);
15
+ return !["http:", "https:"].includes(p.protocol) || !N(p.hostname) || p.origin !== S ? !1 : i != null && i.length ? i.includes(S) : p.host === l.host;
16
16
  } catch {
17
17
  return !1;
18
18
  }
19
19
  }
20
- const G = (t, i) => t.triggeredAt - i.triggeredAt || (t.id < i.id ? -1 : t.id > i.id ? 1 : 0);
21
- function m(t) {
22
- return e.existsSync(t) && e.lstatSync(t).isFile();
20
+ const F = `<!-- backtrack:context:1 -->
21
+ `, et = ["incident.md", "logs.md", "timeline.md", "replay.md", "agent-instructions.md"];
22
+ function O(e) {
23
+ if (!t.existsSync(e)) return [];
24
+ if (!t.lstatSync(e).isDirectory()) throw new Error("Invalid context directory");
25
+ return et.map((i) => {
26
+ const c = f.join(e, i);
27
+ if (!h(c) || t.statSync(c).size > 1024 * 1024 || !t.readFileSync(c, "utf8").startsWith(F)) throw new Error("Invalid context report");
28
+ return c;
29
+ });
23
30
  }
24
- function q(t) {
25
- var E;
26
- const i = (t == null ? void 0 : t.directory) ?? (t == null ? void 0 : t.outputDir);
27
- if (!t || typeof i != "string" || !y.isAbsolute(i))
31
+ function C(e, i) {
32
+ for (const c of e) h(c) && t.unlinkSync(c);
33
+ t.existsSync(i) && t.lstatSync(i).isDirectory() && !t.readdirSync(i).length && t.rmdirSync(i);
34
+ }
35
+ const lt = (e, i) => e.triggeredAt - i.triggeredAt || (e.id < i.id ? -1 : e.id > i.id ? 1 : 0);
36
+ function h(e) {
37
+ return t.existsSync(e) && t.lstatSync(e).isFile();
38
+ }
39
+ function yt(e) {
40
+ var H;
41
+ const i = (e == null ? void 0 : e.directory) ?? (e == null ? void 0 : e.outputDir);
42
+ if (!e || typeof i != "string" || !f.isAbsolute(i))
28
43
  throw new Error('[Backtrack dev-server] A opção "directory" (ou "outputDir") absoluta é obrigatória.');
29
- const S = y.resolve(i), d = y.join(S, "incidents"), u = y.join(S, "latest.md"), h = t.maxBodyBytes ?? 50 * 1024 * 1024, A = t.maxIncidents ?? 20, $ = t.maxStorageBytes ?? 200 * 1024 * 1024;
30
- for (const o of [h, A, $])
44
+ const c = f.resolve(i), l = f.join(c, "incidents"), S = f.join(c, "latest.md"), p = e.maxBodyBytes ?? 50 * 1024 * 1024, T = e.maxIncidents ?? 20, E = e.maxStorageBytes ?? 200 * 1024 * 1024;
45
+ for (const o of [p, T, E])
31
46
  if (!Number.isSafeInteger(o) || o <= 0) throw new Error("Backtrack limits must be positive safe integers.");
32
- if ((E = t.allowedOrigins) != null && E.some((o) => {
47
+ if ((H = e.allowedOrigins) != null && H.some((o) => {
33
48
  try {
34
49
  const r = new URL(o);
35
- return !b(r.hostname) || !["http:", "https:"].includes(r.protocol) || r.origin !== o;
50
+ return !N(r.hostname) || !["http:", "https:"].includes(r.protocol) || r.origin !== o;
36
51
  } catch {
37
52
  return !0;
38
53
  }
39
54
  })) throw new Error("Backtrack origins must be local HTTP(S) origins.");
40
- let F = Promise.resolve();
41
- function v() {
55
+ let M = Promise.resolve();
56
+ function $() {
42
57
  const o = [];
43
- for (const r of e.readdirSync(d)) {
58
+ for (const r of t.readdirSync(l)) {
44
59
  if (!r.endsWith(".json")) continue;
45
- const c = r.slice(0, -5);
46
- if (!_(c)) continue;
47
- const n = y.join(d, r), s = y.join(d, `${c}.md`);
60
+ const d = r.slice(0, -5);
61
+ if (!tt(d)) continue;
62
+ const n = f.join(l, r), s = f.join(l, `${d}.md`);
48
63
  try {
49
- if (!m(n) || !m(s) || e.statSync(n).size > h) continue;
50
- const a = J(JSON.parse(e.readFileSync(n, "utf8")));
51
- if (!a.success || a.data.incident.id !== c || !e.readFileSync(s, "utf8").startsWith(`# Incidente Backtrack
64
+ if (!h(n) || !h(s) || t.statSync(n).size > p) continue;
65
+ const y = Y(JSON.parse(t.readFileSync(n, "utf8")));
66
+ if (!y.success || y.data.incident.id !== d || !t.readFileSync(s, "utf8").startsWith(`# Incidente Backtrack
52
67
  `)) continue;
53
- o.push({ id: c, jsonPath: n, mdPath: s, triggeredAt: a.data.incident.triggeredAt, size: e.statSync(n).size + e.statSync(s).size });
68
+ const m = O(f.join(l, d));
69
+ o.push({
70
+ id: d,
71
+ jsonPath: n,
72
+ mdPath: s,
73
+ reportFiles: m,
74
+ triggeredAt: y.data.incident.triggeredAt,
75
+ size: t.statSync(n).size + t.statSync(s).size + m.reduce((u, j) => u + t.statSync(j).size, 0)
76
+ });
54
77
  } catch {
55
78
  }
56
79
  }
57
- return o.sort(G);
80
+ return o.sort(lt);
58
81
  }
59
- function P() {
60
- const o = v(), r = o[o.length - 1];
82
+ function _() {
83
+ const o = $(), r = o[o.length - 1];
61
84
  if (!r) throw new Error("No complete incident to publish");
62
- const c = `> **Artefato completo:** [\`${r.id}.json\`](incidents/${r.id}.json)
63
-
64
- ` + e.readFileSync(r.mdPath, "utf8"), n = `${u}.${Date.now()}.tmp`;
85
+ const d = JSON.parse(t.readFileSync(r.jsonPath, "utf8")), n = q(d, r.reportFiles.length ? "incidents/" + r.id : void 0), s = `${S}.${Date.now()}.tmp`;
65
86
  try {
66
- e.writeFileSync(n, c, { encoding: "utf8", flag: "wx" }), e.renameSync(n, u);
87
+ t.writeFileSync(s, n, { encoding: "utf8", flag: "wx" }), t.renameSync(s, S);
67
88
  } finally {
68
- m(n) && e.unlinkSync(n);
89
+ h(s) && t.unlinkSync(s);
69
90
  }
70
91
  return r.id;
71
92
  }
72
- function D(o) {
73
- const r = v();
74
- let c = r.length, n = r.reduce((s, a) => s + a.size, e.statSync(u).size);
93
+ function J(o) {
94
+ const r = $();
95
+ let d = r.length, n = r.reduce((s, y) => s + y.size, t.statSync(S).size);
75
96
  for (const s of r) {
76
- if (c <= A && n <= $) break;
77
- s.id !== o && (e.unlinkSync(s.mdPath), e.unlinkSync(s.jsonPath), c--, n -= s.size);
97
+ if (d <= T && n <= E) break;
98
+ s.id !== o && (C(s.reportFiles, f.join(l, s.id)), t.unlinkSync(s.mdPath), t.unlinkSync(s.jsonPath), d--, n -= s.size);
78
99
  }
79
100
  }
80
- return (o, r, c) => {
81
- const n = (l, x) => {
82
- !r.destroyed && !r.headersSent && (r.writeHead(l, { "Content-Type": "application/json", ...l === 405 ? { Allow: "POST" } : {} }), r.end(JSON.stringify(x)));
101
+ return (o, r, d) => {
102
+ const n = (u, j) => {
103
+ !r.destroyed && !r.headersSent && (r.writeHead(u, { "Content-Type": "application/json", ...u === 405 ? { Allow: "POST" } : {} }), r.end(JSON.stringify(j)));
83
104
  };
84
105
  if ((o.url || "").split("?")[0] !== "/__backtrack/incident") {
85
- c ? c() : n(404, { error: "Not found" });
106
+ d ? d() : n(404, { error: "Not found" });
86
107
  return;
87
108
  }
88
109
  if (o.method !== "POST") {
@@ -93,73 +114,90 @@ function q(t) {
93
114
  n(415, { error: "Unsupported Media Type: application/json required" });
94
115
  return;
95
116
  }
96
- if (!Z(o, t.allowedOrigins)) {
117
+ if (!at(o, e.allowedOrigins)) {
97
118
  n(403, { error: "Forbidden: invalid origin or non-local access" });
98
119
  return;
99
120
  }
100
- let s = 0, a = !1;
101
- const w = [];
121
+ let s = 0, y = !1;
122
+ const m = [];
102
123
  o.on("error", () => {
103
- a = !0, w.length = 0;
124
+ y = !0, m.length = 0;
104
125
  }), o.on("aborted", () => {
105
- a = !0, w.length = 0;
106
- }), o.on("data", (l) => {
107
- a || (s += l.length, s > h ? (a = !0, w.length = 0, r.setHeader("Connection", "close"), n(413, { error: "Payload Too Large" })) : w.push(l));
126
+ y = !0, m.length = 0;
127
+ }), o.on("data", (u) => {
128
+ y || (s += u.length, s > p ? (y = !0, m.length = 0, r.setHeader("Connection", "close"), n(413, { error: "Payload Too Large" })) : m.push(u));
108
129
  }), o.on("end", () => {
109
- if (a) return;
110
- const l = Buffer.concat(w).toString("utf8");
111
- w.length = 0;
112
- let x;
130
+ if (y) return;
131
+ const u = Buffer.concat(m).toString("utf8");
132
+ m.length = 0;
133
+ let j;
113
134
  try {
114
- x = JSON.parse(l);
135
+ j = JSON.parse(u);
115
136
  } catch {
116
137
  n(400, { error: "Invalid JSON" });
117
138
  return;
118
139
  }
119
- const L = J(x);
120
- if (!L.success) {
140
+ const U = Y(j);
141
+ if (!U.success) {
121
142
  n(400, { error: "Invalid artifact schema" });
122
143
  return;
123
144
  }
124
- const z = L.data, g = z.incident.id;
125
- if (!_(g)) {
145
+ const A = U.data, w = A.incident.id;
146
+ if (!tt(w)) {
126
147
  n(400, { error: "Invalid incident ID format" });
127
148
  return;
128
149
  }
129
- F = F.then(() => {
150
+ M = M.then(() => {
130
151
  try {
131
- for (const f of [S, d]) {
132
- if (e.existsSync(f) && !e.lstatSync(f).isDirectory()) throw new Error("Invalid output directory");
133
- e.mkdirSync(f, { recursive: !0 });
152
+ for (const a of [c, l]) {
153
+ if (t.existsSync(a) && !t.lstatSync(a).isDirectory()) throw new Error("Invalid output directory");
154
+ t.mkdirSync(a, { recursive: !0 });
134
155
  }
135
- const p = y.join(d, `${g}.json`), k = y.join(d, `${g}.md`), j = e.existsSync(p), N = e.existsSync(k);
136
- if (j && (!m(p) || e.readFileSync(p, "utf8") !== l) || N && (!m(k) || !j)) {
156
+ const x = f.join(l, `${w}.json`), v = f.join(l, `${w}.md`), g = t.existsSync(x), k = t.existsSync(v);
157
+ if (g && (!h(x) || t.readFileSync(x, "utf8") !== u) || k && (!h(v) || !g)) {
137
158
  n(409, { error: "Conflict: incident ID exists with different content" });
138
159
  return;
139
160
  }
140
- if (j && N) {
141
- const f = P();
142
- D(f), n(200, { id: g, status: "already_persisted" });
161
+ const B = f.join(l, w);
162
+ let D;
163
+ try {
164
+ D = O(B);
165
+ } catch {
166
+ n(409, { error: "Conflict: context directory contains unrecognized reports" });
167
+ return;
168
+ }
169
+ if (D.length && (!g || !k)) {
170
+ n(409, { error: "Conflict: incomplete incident with existing context" });
171
+ return;
172
+ }
173
+ if (g && k && D.length) {
174
+ const a = _();
175
+ J(a), n(200, { id: w, status: "already_persisted" });
143
176
  return;
144
177
  }
145
- const O = U(z), H = `> **Artefato completo:** [\`${g}.json\`](incidents/${g}.json)
146
-
147
- `;
148
- if (Buffer.byteLength(l) + 2 * Buffer.byteLength(O) + Buffer.byteLength(H) > $) {
178
+ const R = it(A, { maxSizeBytes: 32 * 1024 - Buffer.byteLength(F) }), W = R.files["incident.md"], K = { ...R.files, "agent-instructions.md": R.agentInstructions }, nt = q(A, "incidents/" + w), rt = Object.values(K).reduce((a, b) => a + Buffer.byteLength(F + b), 0);
179
+ if (Buffer.byteLength(u) + Buffer.byteLength(W) + rt + Buffer.byteLength(nt) > E) {
149
180
  n(413, { error: "Incident size exceeds maxStorageBytes limit" });
150
181
  return;
151
182
  }
152
- const M = `${Date.now()}-${Math.random().toString(36).slice(2)}`, B = `${p}.${M}.tmp`, I = `${k}.${M}.tmp`;
153
- let R = !1, T = !1;
183
+ const z = `${Date.now()}-${Math.random().toString(36).slice(2)}`, L = `${x}.${z}.tmp`, P = `${v}.${z}.tmp`, I = `${B}.${z}.tmp`;
184
+ let Q = !1, V = !1, X = !1;
185
+ const Z = [];
154
186
  try {
155
- j || e.writeFileSync(B, l, { encoding: "utf8", flag: "wx" }), e.writeFileSync(I, O, { encoding: "utf8", flag: "wx" }), j || (e.renameSync(B, p), R = !0), e.renameSync(I, k), T = !0, P();
156
- } catch (f) {
157
- throw T && m(k) && e.unlinkSync(k), R && m(p) && e.unlinkSync(p), f;
187
+ g || t.writeFileSync(L, u, { encoding: "utf8", flag: "wx" }), k || t.writeFileSync(P, W, { encoding: "utf8", flag: "wx" }), t.mkdirSync(I);
188
+ for (const a of et) {
189
+ const b = f.join(I, a);
190
+ Z.push(b), t.writeFileSync(b, F + K[a], { encoding: "utf8", flag: "wx" });
191
+ }
192
+ g || (t.renameSync(L, x), Q = !0), k || (t.renameSync(P, v), V = !0), t.renameSync(I, B), X = !0, _();
193
+ } catch (a) {
194
+ throw X && C(O(B), B), V && h(v) && t.unlinkSync(v), Q && h(x) && t.unlinkSync(x), a;
158
195
  } finally {
159
- for (const f of [B, I]) m(f) && e.unlinkSync(f);
196
+ t.existsSync(I) && C(Z, I);
197
+ for (const a of [L, P]) h(a) && t.unlinkSync(a);
160
198
  }
161
- const C = v();
162
- D(C[C.length - 1].id), n(201, { id: g, status: "saved" });
199
+ const G = $();
200
+ J(G[G.length - 1].id), n(g && k ? 200 : 201, { id: w, status: g && k ? "already_persisted" : "saved" });
163
201
  } catch {
164
202
  n(500, { error: "Failed to persist incident" });
165
203
  }
@@ -168,5 +206,5 @@ function q(t) {
168
206
  };
169
207
  }
170
208
  export {
171
- q as createBacktrackMiddleware
209
+ yt as createBacktrackMiddleware
172
210
  };