@quantiya/codevibe-core 2.0.88 → 2.0.90

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 () => {
@@ -25289,11 +25295,23 @@ var AppSyncClient = class _AppSyncClient {
25289
25295
  sessionId,
25290
25296
  lastHeartbeatAt: heartbeatAt
25291
25297
  });
25292
- return this.heartbeatTokens.get(sessionId) !== token4 || refreshed.status !== "ACTIVE" /* ACTIVE */ && (await this.updateSession({
25298
+ if (this.heartbeatTokens.get(sessionId) !== token4 || refreshed.status !== "ACTIVE" /* ACTIVE */ && (await this.updateSession({
25293
25299
  sessionId,
25294
25300
  lastHeartbeatAt: heartbeatAt,
25295
25301
  status: "ACTIVE" /* ACTIVE */
25296
- }), this.heartbeatTokens.get(sessionId) !== token4) ? "stopped" : (logger.debug("[AppSyncClient] Heartbeat sent", { sessionId }), "confirmed");
25302
+ }), this.heartbeatTokens.get(sessionId) !== token4))
25303
+ return "stopped";
25304
+ if (this.syncDisplayListeners.size > 0 && this.authenticated && this.currentUserId) {
25305
+ let userId = this.currentUserId;
25306
+ if (Array.from(this.syncDisplayListeners).some(
25307
+ (entry5) => entry5.userId === userId
25308
+ ) && !this.companionSyncStatusSnapshot(userId)) {
25309
+ let refreshAbort = new AbortController(), refreshDeadline = setTimeout(() => refreshAbort.abort(), 4500);
25310
+ refreshDeadline.unref?.(), this.refreshCompanionSyncDisplayOnMaintenance(userId, refreshAbort.signal).catch(() => {
25311
+ }).finally(() => clearTimeout(refreshDeadline));
25312
+ }
25313
+ }
25314
+ return logger.debug("[AppSyncClient] Heartbeat sent", { sessionId }), "confirmed";
25297
25315
  } catch (error) {
25298
25316
  return logger.warn("[AppSyncClient] Heartbeat failed", { sessionId, error }), isRetiredSessionError(error) ? "retired" : "failed";
25299
25317
  }
@@ -28797,22 +28815,43 @@ var LOCAL_GEMMA_ALLOWED_OUTPUT_KEYS = /* @__PURE__ */ new Set([
28797
28815
  // would throw "included unsupported keys" on every browse decision.
28798
28816
  "browseUrls",
28799
28817
  "browseQuery"
28800
- ]), LOCAL_GEMMA_NULLABLE_OUTPUT_KEYS = ["clarifying_question", "advisory_summary", "browseUrls", "browseQuery"];
28801
- function buildLocalGemmaDecisionJsonSchema() {
28818
+ ]), LOCAL_GEMMA_NULLABLE_OUTPUT_KEYS = ["clarifying_question", "advisory_summary", "browseUrls", "browseQuery"], LOCAL_GEMMA_DECISION_REQUIRED_KEYS = ["rationale", "clarifying_question", "advisory_summary", "browseUrls", "browseQuery", "action"], LOCAL_GEMMA_FIELD_CARRYING_ACTIONS = /* @__PURE__ */ new Set(["browse", "ask_user", "advisory_response"]);
28819
+ function decisionBranch(actions, fields) {
28802
28820
  return {
28803
28821
  type: "object",
28804
28822
  properties: {
28805
28823
  rationale: { type: "string" },
28806
- clarifying_question: { type: ["string", "null"] },
28807
- advisory_summary: { type: ["string", "null"] },
28808
- browseUrls: { type: ["array", "null"], items: { type: "string" } },
28809
- browseQuery: { type: ["string", "null"] },
28810
- action: { type: "string", enum: [...localGemmaAllowedActions()] }
28824
+ clarifying_question: fields.clarifying_question,
28825
+ advisory_summary: fields.advisory_summary,
28826
+ browseUrls: fields.browseUrls,
28827
+ browseQuery: fields.browseQuery,
28828
+ action: { type: "string", enum: actions }
28811
28829
  },
28812
- required: ["rationale", "clarifying_question", "advisory_summary", "browseUrls", "browseQuery", "action"],
28830
+ required: [...LOCAL_GEMMA_DECISION_REQUIRED_KEYS],
28813
28831
  additionalProperties: !1
28814
28832
  };
28815
28833
  }
28834
+ function buildLocalGemmaDecisionJsonSchema() {
28835
+ let nul = { type: "null" }, otherActions = [...localGemmaAllowedActions()].filter((a) => !LOCAL_GEMMA_FIELD_CARRYING_ACTIONS.has(a));
28836
+ return {
28837
+ anyOf: [
28838
+ decisionBranch(["browse"], {
28839
+ clarifying_question: nul,
28840
+ advisory_summary: nul,
28841
+ browseUrls: { type: ["array", "null"], items: { type: "string" } },
28842
+ browseQuery: { type: ["string", "null"] }
28843
+ }),
28844
+ decisionBranch(["ask_user"], { clarifying_question: { type: "string" }, advisory_summary: nul, browseUrls: nul, browseQuery: nul }),
28845
+ decisionBranch(["advisory_response"], {
28846
+ clarifying_question: nul,
28847
+ advisory_summary: { type: ["string", "null"] },
28848
+ browseUrls: nul,
28849
+ browseQuery: nul
28850
+ }),
28851
+ decisionBranch(otherActions, { clarifying_question: nul, advisory_summary: nul, browseUrls: nul, browseQuery: nul })
28852
+ ]
28853
+ };
28854
+ }
28816
28855
  var LOCAL_GEMMA_DECISION_JSON_SCHEMA = buildLocalGemmaDecisionJsonSchema();
28817
28856
  var LOCAL_GEMMA_MAX_USER_PROMPT_CHARS = 4e3, LOCAL_GEMMA_MAX_CLARIFICATIONS = 4, LOCAL_GEMMA_MAX_CLARIFICATION_QUESTION_CHARS = 300, LOCAL_GEMMA_MAX_CLARIFICATION_ANSWER_CHARS = 900, LOCAL_GEMMA_MAX_CANONICAL_CONTEXT_CHARS = 6e3, LOCAL_GEMMA_MAX_RENDERED_PROMPT_CHARS = 24e3;
28818
28857
  function truncatePlannerTextPreservingEnds(text2, maxChars) {
@@ -29170,7 +29209,7 @@ function parseLocalGemmaPlannerDecision(raw) {
29170
29209
  throw validationError("Gemma planner browse output must not include advisory_summary or clarifying_question", obj, canDegrade);
29171
29210
  } else {
29172
29211
  let hasRealBrowseUrls = Array.isArray(obj.browseUrls) && obj.browseUrls.some(isRealHttpUrl), hasRealBrowseQuery = typeof obj.browseQuery == "string" && obj.browseQuery.trim().length > 0, userFacingPlannerText = typeof obj.advisory_summary == "string" && obj.advisory_summary.trim().length > 0 || typeof obj.clarifying_question == "string" && obj.clarifying_question.trim().length > 0, taskStartingAction = obj.action === "start_task" || obj.action === "team_decompose";
29173
- if ((hasRealBrowseUrls || hasRealBrowseQuery) && (userFacingPlannerText || taskStartingAction || obj.action === "refuse" || obj.action === "ask_user"))
29212
+ if ((hasRealBrowseUrls || hasRealBrowseQuery) && (userFacingPlannerText || taskStartingAction || obj.action === "advisory_response" && hasRealBrowseQuery || obj.action === "refuse" || obj.action === "ask_user"))
29174
29213
  throw new PlannerOutputUnparseableError(
29175
29214
  "Gemma planner non-browse output must not combine browse fields with user-facing or task-starting planner output",
29176
29215
  { kind: "validation" }
@@ -77317,7 +77356,7 @@ function OrchestrationApp(props) {
77317
77356
  setLiveRuntimeKind(kind), setLiveRuntimeLabel(props.plannerLabel ?? "");
77318
77357
  });
77319
77358
  }, [props.plannerPipeline, props.plannerLabel]);
77320
- let [syncDisplay, setSyncDisplay] = React22.useState(null), syncDisplayRef = React22.useRef(syncDisplay), syncOwner = state.session.userId;
77359
+ let [syncDisplay, setSyncDisplay] = React22.useState(null), [syncBootstrapSettledFor, setSyncBootstrapSettledFor] = React22.useState(null), syncDisplayRef = React22.useRef(syncDisplay), syncOwner = state.session.userId;
77321
77360
  React22.useEffect(() => {
77322
77361
  let source2 = props.companionSyncStateSource;
77323
77362
  if (!source2) return;
@@ -77325,16 +77364,19 @@ function OrchestrationApp(props) {
77325
77364
  if (!active) return;
77326
77365
  clearTimeout(expiry);
77327
77366
  let display = { userId: syncOwner, state: next };
77328
- syncDisplayRef.current = display, setSyncDisplay(display), next && (expiry = setTimeout(() => update(null), Math.max(0, next.validUntilEpochMs - Date.now())), expiry.unref?.());
77367
+ syncDisplayRef.current = display, setSyncDisplay(display), next && (setSyncBootstrapSettledFor(syncOwner), expiry = setTimeout(() => update(null), Math.max(0, next.validUntilEpochMs - Date.now())), expiry.unref?.());
77329
77368
  };
77330
- update(null);
77369
+ setSyncBootstrapSettledFor(null), update(null);
77331
77370
  try {
77332
77371
  unsubscribe = source2.subscribeCompanionSyncState(syncOwner, update);
77333
77372
  } catch {
77373
+ active && setSyncBootstrapSettledFor(syncOwner);
77334
77374
  }
77335
77375
  let deadline = setTimeout(() => abort.abort(), 4500);
77336
77376
  return deadline.unref?.(), Promise.resolve().then(() => source2.getCompanionSyncState(abort.signal)).catch(() => {
77337
- }).finally(() => clearTimeout(deadline)), () => {
77377
+ }).finally(() => {
77378
+ clearTimeout(deadline), active && setSyncBootstrapSettledFor(syncOwner);
77379
+ }), () => {
77338
77380
  active = !1, clearTimeout(expiry), clearTimeout(deadline), abort.abort(), unsubscribe?.();
77339
77381
  };
77340
77382
  }, [props.companionSyncStateSource, syncOwner]);
@@ -77613,7 +77655,7 @@ ${formatImplementorPolicy(detected, updated, props.tier)}`
77613
77655
  tier: props.tier,
77614
77656
  plannerRuntimeKind: liveRuntimeKind,
77615
77657
  plannerLabel: liveRuntimeLabel,
77616
- companionSyncBadge: props.companionSyncStateSource ? companionSyncBadge(syncDisplay?.userId === syncOwner ? syncDisplay.state : null) : null
77658
+ companionSyncBadge: props.companionSyncStateSource && (syncDisplay?.userId === syncOwner && syncDisplay.state || syncBootstrapSettledFor === syncOwner) ? companionSyncBadge(syncDisplay?.userId === syncOwner ? syncDisplay.state : null) : null
77617
77659
  }),
77618
77660
  // CP-12 W3 §3.5 — minimal multi-track status node, mounted ONLY when a team
77619
77661
  // is active. Single-track / Companion sessions leave `state.team`
@@ -83897,7 +83939,7 @@ var INTENT_CLASSIFIER_JSON_SCHEMA = {
83897
83939
  rationale: { type: "string" }
83898
83940
  },
83899
83941
  required: ["action", "is_code_or_disk_mutation", "confidence", "requires_file_read", "rationale"]
83900
- }, DEFAULT_INTENT_TIMEOUT_MS = 5e3, INTENT_MAX_OUTPUT_TOKENS = 256;
83942
+ }, DEFAULT_INTENT_TIMEOUT_MS = 3e4, INTENT_MAX_OUTPUT_TOKENS = 256;
83901
83943
  function buildIntentClassifierPrompt(prompt, clarifications) {
83902
83944
  return [
83903
83945
  "You are a strict code/workspace intent classifier for CodeVibe.",
@@ -86714,7 +86756,7 @@ async function runOrchestrationShellSession(args) {
86714
86756
  { teardownDeadlineExpired: !0 }
86715
86757
  ) : void 0,
86716
86758
  start: () => {
86717
- shellSubmissionsFenced = !0, shellSyncRecovery.stop(), fenceHostedSessionIngress(), taskOrdinalsAtEarlyRelease = store.getState().taskOrdinals.size, earlySessionWorkRelease = releaseSessionWorkBeforeRetirement();
86759
+ shellSubmissionsFenced = !0, activeBrowseAbortController?.abort(), shellSyncRecovery.stop(), fenceHostedSessionIngress(), taskOrdinalsAtEarlyRelease = store.getState().taskOrdinals.size, earlySessionWorkRelease = releaseSessionWorkBeforeRetirement();
86718
86760
  let splitLocalDurableRetirement = typeof args.localExecutor?.disposeDurableSessionResources == "function", workspaceShutdown = startWorkspaceShutdownOperations({
86719
86761
  ...args.plannerAdapter || args.localAdvisoryRunner ? {
86720
86762
  clearPlannerSession: () => {
@@ -91342,6 +91384,13 @@ async function handleShellUserInput(deps) {
91342
91384
  } finally {
91343
91385
  stopClassifyRelabel?.();
91344
91386
  }
91387
+ if (browseSignal?.aborted) {
91388
+ logger.info(
91389
+ "[orchestration-shell] Planner classify turn aborted via browseSignal; returning silently",
91390
+ { prompt: plannerInput.prompt }
91391
+ ), clearPendingClarificationIfPresent(store);
91392
+ return;
91393
+ }
91345
91394
  logger.info("[orchestration-shell] Planner classified input", {
91346
91395
  prompt: plannerInput.prompt,
91347
91396
  action: decision.action,
@@ -91354,8 +91403,16 @@ async function handleShellUserInput(deps) {
91354
91403
  if ((decision.action === "start_task" || decision.action === "browse") && args.localAdvisoryRunner && process.env.CODEVIBE_INTENT_CLASSIFIER !== "regex") {
91355
91404
  let gemmaIntent = await classifyPromptIntentWithGemma(
91356
91405
  { prompt: plannerInput.prompt, clarifications: plannerInput.clarifications },
91357
- args.localAdvisoryRunner
91406
+ args.localAdvisoryRunner,
91407
+ ...browseSignal ? [{ signal: browseSignal }] : []
91358
91408
  );
91409
+ if (browseSignal?.aborted) {
91410
+ logger.info(
91411
+ "[orchestration-shell] Gemma intent classifier aborted via browseSignal; returning silently",
91412
+ { prompt: plannerInput.prompt, rationale: gemmaIntent.rationale }
91413
+ ), clearPendingClarificationIfPresent(store);
91414
+ return;
91415
+ }
91359
91416
  if (gemmaIntent.source === "model")
91360
91417
  gemmaEvaluatedIntent = !0, decision.action === "browse" ? gemmaIntent.is_code_or_disk_mutation && (logger.info(
91361
91418
  "[orchestration-shell] Gemma intent classifier: promoting browse to start_task with code/disk mutation",
@@ -91372,6 +91429,12 @@ async function handleShellUserInput(deps) {
91372
91429
  ), decision = {
91373
91430
  action: "familiarize",
91374
91431
  rationale: decision.rationale ? `${decision.rationale} (demoted to familiarize via Gemma intent classifier)` : gemmaIntent.rationale
91432
+ }) : gemmaIntent.action === "browse" && !gemmaIntent.requires_file_read ? (logger.info(
91433
+ "[orchestration-shell] Gemma intent classifier: demoting start_task to browse",
91434
+ { prompt: plannerInput.prompt, rationale: gemmaIntent.rationale }
91435
+ ), decision = {
91436
+ action: "browse",
91437
+ rationale: decision.rationale ? `${decision.rationale} (demoted to browse via Gemma intent classifier)` : gemmaIntent.rationale
91375
91438
  }) : (logger.info(
91376
91439
  "[orchestration-shell] Gemma intent classifier: demoting start_task to advisory_response",
91377
91440
  { prompt: plannerInput.prompt, rationale: gemmaIntent.rationale }
@@ -20,6 +20,7 @@ export interface GemmaIntentPort {
20
20
  }>;
21
21
  }
22
22
  export declare const INTENT_CLASSIFIER_JSON_SCHEMA: Record<string, unknown>;
23
+ export declare const DEFAULT_INTENT_TIMEOUT_MS = 30000;
23
24
  export declare function buildIntentClassifierPrompt(prompt: string, clarifications?: {
24
25
  question: string;
25
26
  answer: string;
@@ -67,35 +67,44 @@ export declare class PlannerOutputUnparseableError extends Error {
67
67
  });
68
68
  }
69
69
  export declare function localGemmaAllowedActions(): ReadonlySet<PlannerDecision['action']>;
70
- interface LocalGemmaDecisionJsonSchema {
70
+ type NullField = {
71
+ type: 'null';
72
+ };
73
+ type StringField = {
74
+ type: 'string';
75
+ };
76
+ type NullableStringField = {
77
+ type: ['string', 'null'];
78
+ };
79
+ type NullableUrlsField = {
80
+ type: ['array', 'null'];
81
+ items: {
82
+ type: 'string';
83
+ };
84
+ };
85
+ /** One per-action branch of the planner decision schema (planner parity D-15). */
86
+ export interface LocalGemmaDecisionBranchSchema {
71
87
  type: 'object';
72
88
  properties: {
73
- rationale: {
74
- type: 'string';
75
- };
76
- clarifying_question: {
77
- type: ['string', 'null'];
78
- };
79
- advisory_summary: {
80
- type: ['string', 'null'];
81
- };
82
- browseUrls: {
83
- type: ['array', 'null'];
84
- items: {
85
- type: 'string';
86
- };
87
- };
88
- browseQuery: {
89
- type: ['string', 'null'];
90
- };
89
+ rationale: StringField;
90
+ clarifying_question: StringField | NullableStringField | NullField;
91
+ advisory_summary: NullableStringField | NullField;
92
+ browseUrls: NullableUrlsField | NullField;
93
+ browseQuery: NullableStringField | NullField;
91
94
  action: {
92
95
  type: 'string';
93
96
  enum: PlannerDecision['action'][];
94
97
  };
95
98
  };
96
99
  required: string[];
97
- additionalProperties: boolean;
100
+ additionalProperties: false;
101
+ }
102
+ /** The planner decision schema: exactly one branch per action family (planner parity D-15). */
103
+ export interface LocalGemmaDecisionJsonSchema {
104
+ anyOf: LocalGemmaDecisionBranchSchema[];
98
105
  }
106
+ /** Every action the decision schema can emit, in branch order (each exactly once). */
107
+ export declare function localGemmaDecisionSchemaActions(schema?: LocalGemmaDecisionJsonSchema): PlannerDecision['action'][];
99
108
  /**
100
109
  * The ONE planner decision schema (both runtimes). Its canonical SHA-256 is allow-listed by
101
110
  * planner-proxy (`lambda/planner-proxy/schemas.ts`); a change here needs the backend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.88",
3
+ "version": "2.0.90",
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",