@quantiya/codevibe-core 2.0.89 → 2.0.91

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.
@@ -0,0 +1,117 @@
1
+ /** Random access to one immutable file. The caller owns opening and closing it. */
2
+ export interface FileSource {
3
+ size: number;
4
+ read(position: number, length: number): Promise<Buffer>;
5
+ }
6
+ export type OfficeFormat = 'docx' | 'xlsx' | 'pptx';
7
+ export type FileClass = {
8
+ kind: 'text';
9
+ } | {
10
+ kind: 'office';
11
+ format: OfficeFormat;
12
+ } | {
13
+ kind: 'image';
14
+ format: 'png' | 'jpeg' | 'gif' | 'webp' | 'heic';
15
+ } | {
16
+ kind: 'pdf';
17
+ } | {
18
+ kind: 'other';
19
+ format: 'audio' | 'video' | 'archive' | 'other_binary';
20
+ };
21
+ /** §10.2: per-part inflate output limit (64 MiB). */
22
+ export declare const MAX_OFFICE_PART_BYTES: number;
23
+ /**
24
+ * Per-archive cumulative inflated output limit (256 MiB) across all parts of one
25
+ * Office file version, bounding total memory and inflate work on multi-part archives.
26
+ */
27
+ export declare const MAX_OFFICE_ARCHIVE_INFLATED_BYTES: number;
28
+ /** Initial header scan length (8 KiB) for magic detection and initial UTF-8/NUL check. */
29
+ export declare const HEADER_SCAN_BYTES: number;
30
+ /** Chunk size (64 KiB) for streaming full-file strict UTF-8 and NUL verification. */
31
+ export declare const UTF8_SCAN_CHUNK_BYTES: number;
32
+ /**
33
+ * Compute standard IEEE 802.3 CRC32 of `buf` as an unsigned 32-bit integer (`0..0xffffffff`).
34
+ * Uses a deterministic lookup table so CRC32 verification works across all Node.js >= 22.0.0
35
+ * runtimes without depending on `zlib.crc32` (added in Node 22.2.0).
36
+ */
37
+ export declare function computeCrc32(buf: Buffer): number;
38
+ export interface ZipEntryMeta {
39
+ name: string;
40
+ method: number;
41
+ flags: number;
42
+ crc32: number;
43
+ compressedSize: number;
44
+ uncompressedSize: number;
45
+ localHeaderOffset: number;
46
+ }
47
+ export interface ZipArchiveMeta {
48
+ cdOffset: number;
49
+ entries: Map<string, ZipEntryMeta>;
50
+ }
51
+ export type ZipInspectResult = {
52
+ ok: true;
53
+ archive: ZipArchiveMeta;
54
+ } | {
55
+ ok: false;
56
+ reason: 'zip64' | 'part_too_large' | 'malformed';
57
+ };
58
+ export type ZipEntryReadResult = {
59
+ ok: true;
60
+ bytes: Buffer;
61
+ } | {
62
+ ok: false;
63
+ reason: 'zip64' | 'part_too_large' | 'malformed';
64
+ };
65
+ /**
66
+ * Read up to `length` bytes starting at `position`, looping across short reads.
67
+ * Returns `null` if EOF is reached before `length` bytes or arguments are out of range.
68
+ * Rejections from `source.read` propagate untouched.
69
+ */
70
+ export declare function readExact(source: FileSource, position: number, length: number): Promise<Buffer | null>;
71
+ /**
72
+ * Validate a ZIP central-directory entry name:
73
+ * - non-empty and bounded length
74
+ * - no ASCII or C1 control characters (`\p{Cc}`), format/bidi/BOM characters (`\p{Cf}`),
75
+ * or Unicode line/paragraph separators (`\p{Zl}`, `\p{Zp}`)
76
+ * - no backslashes (`\`) or leading `/`
77
+ * - no empty (`//`), `.`, or `..` path segments
78
+ */
79
+ export declare function isSafeZipPartName(name: string): boolean;
80
+ /**
81
+ * Parse a ZIP archive's End of Central Directory and Central Directory via random access.
82
+ * Refuses ZIP64 archives (`reason: 'zip64'`), oversized central directories (`reason: 'part_too_large'`),
83
+ * and malformed/unsafe/duplicate part names (`reason: 'malformed'`).
84
+ */
85
+ export declare function inspectZipArchive(source: FileSource): Promise<ZipInspectResult>;
86
+ /**
87
+ * Read and inflate one entry from a validated ZIP archive, enforcing the 64 MiB
88
+ * per-part bound, verifying local file header consistency, and checking CRC32 integrity.
89
+ */
90
+ export declare function readZipEntryBytes(source: FileSource, archive: ZipArchiveMeta, entry: ZipEntryMeta): Promise<ZipEntryReadResult>;
91
+ /**
92
+ * Find the index past the closing `>` of an XML declaration (`<!DOCTYPE ...>`,
93
+ * `<!ENTITY ...>`, etc.) starting at `lt` (`xml.startsWith('<!', lt)`),
94
+ * respecting quoted strings (`"..."`, `'...'`), nested comments (`<!-- ... -->`),
95
+ * nested processing instructions (`<? ... ?>`), and internal DTD subset brackets (`[ ... ]`).
96
+ * Returns `-1` if the declaration is unclosed.
97
+ */
98
+ export declare function findXmlDeclarationEnd(xml: string, lt: number): number;
99
+ /**
100
+ * Linear O(N) stripper for XML comments (`<!-- ... -->`), processing instructions (`<? ... ?>`),
101
+ * CDATA sections (`<![CDATA[ ... ]]>`), and DOCTYPE/declarations (`<! ... >`).
102
+ * Used when inspecting structural XML tags so commented-out or DTD-declared elements are ignored.
103
+ */
104
+ export declare function stripXmlCommentsAndDeclarations(xml: string): string;
105
+ /**
106
+ * Detect whether `[Content_Types].xml` names a WordprocessingML (`docx`),
107
+ * SpreadsheetML (`xlsx`), or PresentationML (`pptx`) main part.
108
+ * Strips XML comments and declarations first so commented-out `<Override>` elements are ignored.
109
+ * Returns `'ambiguous'` if multiple distinct Office main content types are present.
110
+ */
111
+ export declare function detectOfficeFormatFromContentTypes(contentTypesXml: string): OfficeFormat | 'ambiguous' | null;
112
+ /** Verify that the archive contains the structural parts required for `format`. */
113
+ export declare function hasRequiredOfficeMainParts(entries: Map<string, ZipEntryMeta>, format: OfficeFormat): boolean;
114
+ /**
115
+ * Classify `source` according to §10.1 (`text`, `office`, `image`, `pdf`, or `other`).
116
+ */
117
+ export declare function classifyFile(source: FileSource): Promise<FileClass>;
@@ -0,0 +1,61 @@
1
+ import { type FileSource, type OfficeFormat } from './binary-classifier';
2
+ export type { FileSource, OfficeFormat } from './binary-classifier';
3
+ export interface OfficePartChange {
4
+ part: string;
5
+ change: 'added' | 'removed' | 'modified';
6
+ mark: 'text changed' | 'no text change — inspect the part';
7
+ }
8
+ export type OfficeExtraction = {
9
+ ok: true;
10
+ format: OfficeFormat;
11
+ beforeText: string | null;
12
+ afterText: string;
13
+ parts: OfficePartChange[];
14
+ } | {
15
+ ok: false;
16
+ fallback: 'other_binary';
17
+ reason: 'zip64' | 'part_too_large' | 'malformed';
18
+ };
19
+ /**
20
+ * Maximum cumulative extracted text characters and element-overhead units (32 MiB)
21
+ * per Office archive version, preventing memory exhaustion or V8 RangeError on
22
+ * high-expansion shared-string or dense-element inputs.
23
+ */
24
+ export declare const MAX_OFFICE_EXTRACTED_TEXT_BYTES: number;
25
+ /**
26
+ * Maximum cumulative decoded XML characters (64 MiB) across all extracted XML parts
27
+ * of one Office archive version before text extraction runs.
28
+ */
29
+ export declare const MAX_OFFICE_DECODED_XML_CHARS: number;
30
+ /**
31
+ * Per-element budget charge (64 bytes) applied to structural XML elements (`<si>`, `<c>`,
32
+ * `<sheet>`, `<definedName>`, `<Relationship>`, `<p:sldId>`, `<w:p>`, `<a:p>`, `<w:comment>`,
33
+ * and inner elements buffered inside `<w:comment>`) so millions of empty tags
34
+ * (`<si/>`, `<c/>`, `<sheet/>`, `<a>`) cannot allocate unbounded arrays.
35
+ */
36
+ export declare const ELEMENT_BUDGET_OVERHEAD = 64;
37
+ /**
38
+ * Per-chunk budget charge (32 bytes) applied alongside character length whenever a
39
+ * text fragment is pushed into an intermediate chunk array or string accumulator,
40
+ * bounding V8 array/string-object overhead on millions of tiny text runs.
41
+ */
42
+ export declare const CHUNK_BUDGET_OVERHEAD = 32;
43
+ declare class TextBudget {
44
+ private readonly limit;
45
+ private used;
46
+ constructor(limit?: number);
47
+ add(chars: number): boolean;
48
+ get exceeded(): boolean;
49
+ }
50
+ /**
51
+ * Decode standard XML named and numeric character entities in a single linear O(N) pass
52
+ * without global regex replacement (`Runtime_RegExpExecMultiple`). Caches `nextSemi` so
53
+ * semicolons are never rescanned across multiple `&` characters, emits slices only when
54
+ * a valid entity is decoded (charging `CHUNK_BUDGET_OVERHEAD` per decoded entity when a
55
+ * budget is supplied), and leaves undecoded `&` sequences in the original contiguous
56
+ * string slice with zero intermediate string allocations. Doubly-escaped sequences
57
+ * (e.g. `&#38;lt;` or `&#x26;#65;`) are decoded once.
58
+ */
59
+ export declare function decodeXmlEntities(raw: string, budget?: TextBudget): string;
60
+ /** `before` is null for a created file. A deletion needs no extraction text beyond the `before/` original. */
61
+ export declare function extractOfficeChange(before: FileSource | null, after: FileSource): Promise<OfficeExtraction>;
@@ -24263,7 +24263,13 @@ var AppSyncClient = class _AppSyncClient {
24263
24263
  this.assertSyncAccount(userId);
24264
24264
  let session2 = await this.getSession(state.sessionId);
24265
24265
  if (!current()) return;
24266
- if (this.assertSyncAccount(userId), !session2 || session2.userId !== userId || !["ACTIVE" /* ACTIVE */, "PAUSED" /* PAUSED */].includes(session2.status))
24266
+ if (this.assertSyncAccount(userId), !session2 || session2.sessionId !== state.sessionId || session2.userId !== userId || state.syncSessionGenerationId !== void 0 && session2.sessionGenerationId !== state.syncSessionGenerationId)
24267
+ throw new SyncEventClientError("SYNC_SESSION_UNAVAILABLE");
24268
+ if (session2.status === "INACTIVE" /* INACTIVE */ && session2.sessionGenerationId) {
24269
+ this.notifyRetiredSession(state.sessionId);
24270
+ return;
24271
+ }
24272
+ if (!["ACTIVE" /* ACTIVE */, "PAUSED" /* PAUSED */].includes(session2.status))
24267
24273
  throw new SyncEventClientError("SYNC_SESSION_UNAVAILABLE");
24268
24274
  let retainedIds = /* @__PURE__ */ new Set();
24269
24275
  state.mobileInputRecoveryHeld = !1, session2.sessionGenerationId && (state.compatibilityInboundIds = retainedIds, state.syncSessionGenerationId = session2.sessionGenerationId), this.createLegacySubscription(state), !(!session2.sessionGenerationId || !current()) && (async () => {
@@ -25285,9 +25291,10 @@ var AppSyncClient = class _AppSyncClient {
25285
25291
  return "stopped";
25286
25292
  }
25287
25293
  try {
25288
- let heartbeatAt = (/* @__PURE__ */ new Date()).toISOString(), refreshed = await this.updateSession({
25294
+ let heartbeatAt = (/* @__PURE__ */ new Date()).toISOString(), replacementAware = !!this.activeSubscriptions.get(sessionId)?.onSessionRetired, refreshed = await this.updateSession({
25289
25295
  sessionId,
25290
- lastHeartbeatAt: heartbeatAt
25296
+ lastHeartbeatAt: heartbeatAt,
25297
+ ...replacementAware ? { status: "ACTIVE" /* ACTIVE */ } : {}
25291
25298
  });
25292
25299
  if (this.heartbeatTokens.get(sessionId) !== token4 || refreshed.status !== "ACTIVE" /* ACTIVE */ && (await this.updateSession({
25293
25300
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.89",
3
+ "version": "2.0.91",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "typecheck": "tsc --noEmit",
19
19
  "emit-types": "tsc -p tsconfig.types.json --emitDeclarationOnly",
20
20
  "build": "rm -rf dist && npm run emit-types && esbuild src/index.ts --bundle --platform=node --target=node18 --minify --packages=external --define:__CODEVIBE_TEST_BUILD__=false --outfile=dist/index.js && esbuild src/orchestration-shell/cli.ts --bundle --platform=node --target=node18 --minify-syntax --packages=external --define:__CODEVIBE_TEST_BUILD__=false --outfile=dist/orchestration-shell/cli.js --banner:js=\"#!/usr/bin/env node\" && esbuild src/orchestration-shell/web/web-extract-worker.ts --bundle --platform=node --target=node18 --minify --packages=external --outfile=dist/orchestration-shell/web-extract-worker.js && npm run verify-no-test-hooks && npm run verify-no-test-types",
21
- "verify-no-test-hooks": "if grep -ERl 'CODEVIBE_TEST_FORCE_QUOTA_FAIL|CODEVIBE_TEST_FORCE_IMPLEMENTOR_AGENT|CODEVIBE_ANCHORED_PUBLICATION_AUTHORITY_ROOT|codevibe-anchored-publications-|cv-publication-authority-vitest-|codevibe\\.test\\.publication-authority-|setPublicationAuthorityRootForTestBuild|__CODEVIBE_TEST_BUILD__|_onBeforeLinkForTesting|_onBeforeSummariesLockForTesting|__codevibe_test_on_before_link|__codevibe_test_on_before_summaries_lock|simulatePostPublicationError' dist; then echo 'ERROR: a test-only hook leaked into a published artifact (prod-unreachability invariant)'; exit 1; fi; echo 'OK — no test-only hooks in published artifacts'",
21
+ "verify-no-test-hooks": "if grep -ERl 'CODEVIBE_TEST_FORCE_QUOTA_FAIL|CODEVIBE_TEST_FORCE_IMPLEMENTOR_AGENT|CODEVIBE_ANCHORED_PUBLICATION_AUTHORITY_ROOT|codevibe-anchored-publications-|cv-publication-authority-vitest-|codevibe\\.test\\.publication-authority-|setPublicationAuthorityRootForTestBuild|__CODEVIBE_TEST_BUILD__|_onBeforeLinkForTesting|_onBeforeSummariesLockForTesting|__codevibe_test_on_before_link|__codevibe_test_on_before_summaries_lock|simulatePostPublicationError' dist; then echo 'ERROR: a test-only hook leaked into a published artifact (prod-unreachability invariant)'; exit 1; fi; echo 'OK \u2014 no test-only hooks in published artifacts'",
22
22
  "verify-no-test-types": "if find dist -type f \\( -path '*/__tests__/*' -o -name '*.test.d.ts' -o -name 'test-publication-authority-setup.d.ts' \\) | grep -q .; then echo 'verify-no-test-types: test declarations present in dist (tsconfig.types.json must exclude test sources)' >&2; exit 1; fi",
23
23
  "clean": "rm -rf dist",
24
24
  "prepublishOnly": "npm run build",