@quantiya/codevibe-core 2.0.47 → 2.0.49

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.
@@ -5,7 +5,7 @@ import { type ModeSelectionDeps } from './mode-selection';
5
5
  import { type RunOrchestrationShellArgs } from './index';
6
6
  import { type ShellEventEmit, type ShellEventEmitResult } from './emit-shell-event';
7
7
  import { type ShadowEnv } from '../local-executor/workspace-shadow';
8
- import { BackendPlannerClient, PlannerCacheLayer, PlannerHealthMachine, type LocalGemmaAdvisoryRunner, type LocalGemmaPlannerRunner } from '../planner';
8
+ import { BackendPlannerClient, PlannerCacheLayer, PlannerHealthMachine, HostedGemmaAdvisoryRunner, type AdvisoryRunner, type LocalGemmaAdvisoryRunner, type LocalGemmaPlannerRunner } from '../planner';
9
9
  import type { PlannerAdapter, PlannerInput, PlannerDecision, PlannerProbeResult } from '../planner/adapter';
10
10
  import { keychainManager } from '../keychain';
11
11
  export { keychainManager };
@@ -270,6 +270,7 @@ export interface ProductionPlannerStackResult {
270
270
  adapter: BackendPlannerClient;
271
271
  cache: PlannerCacheLayer;
272
272
  health: PlannerHealthMachine;
273
+ hostedAdvisoryRunner: HostedGemmaAdvisoryRunner;
273
274
  }
274
275
  export declare function buildProductionPlannerStack(appsyncClient: AppSyncClient, emitter: (args: ShellEventEmit) => Promise<unknown>): ProductionPlannerStackResult;
275
276
  export interface ResolvePlannerAdapterArgs {
@@ -284,7 +285,7 @@ export interface ResolvedPlannerResult {
284
285
  adapter: PlannerAdapter;
285
286
  runtimeKind: NonNullable<RunOrchestrationShellArgs['plannerRuntimeKind']>;
286
287
  runtimeLabel: string;
287
- advisoryRunner: LocalGemmaAdvisoryRunner | null;
288
+ advisoryRunner: AdvisoryRunner | null;
288
289
  classifyRunner: LocalGemmaPlannerRunner | null;
289
290
  cache: PlannerCacheLayer | null;
290
291
  health: PlannerHealthMachine | null;
@@ -295,14 +296,15 @@ export interface ResolvedPlannerResult {
295
296
  *
296
297
  * Precedence & Invariants:
297
298
  * 1. Forced hosted mode (`CODEVIBE_FORCE_HOSTED_PLANNER=1` or `CODEVIBE_PLANNER_PROVIDER=hosted`):
298
- * - Allowed for PRO and MAX tiers. Returns the production hosted Bedrock planner stack.
299
+ * - Allowed for PRO and MAX tiers. Returns the production hosted Bedrock planner stack and hosted advisory runner.
299
300
  * - Disallowed for FREE tier (preserves $0 marginal cost invariant). Falls back to local adapter.
300
301
  * 2. Local-first preference:
301
- * - When local Gemma is enabled and operational (`!localPlanner.reason`), returns the local adapter
302
- * (`local_gemma_qat`).
302
+ * - When local Gemma is enabled and operational (`!localPlanner.reason`), returns the composite adapter
303
+ * and composite advisory runner for PRO/MAX (local-first with seamless hosted Bedrock fallback).
304
+ * Returns local adapter/runner for FREE tier.
303
305
  * 3. Unavailable fallback:
304
306
  * - When local Gemma is unavailable (`localPlanner.reason` is truthy):
305
- * - PRO / MAX: Falls back to the hosted Bedrock planner stack (`hosted`).
307
+ * - PRO / MAX: Falls back to the hosted Bedrock planner stack (`hosted`) and hosted advisory runner.
306
308
  * - FREE: Retains the local unavailable adapter (`local_unavailable`), strictly preventing
307
309
  * cloud inference costs.
308
310
  */
@@ -3380,6 +3380,21 @@ var queries = {
3380
3380
  }
3381
3381
  `
3382
3382
  ),
3383
+ // Hosted Gemma advisory text generation (browse synthesis, brainstorm, etc.).
3384
+ // Lambda decrypts input prompt via session key, dispatches to Bedrock Gemma,
3385
+ // encrypts response under session key, returns.
3386
+ generatePlannerAdvisory: (
3387
+ /* GraphQL */
3388
+ `
3389
+ mutation GeneratePlannerAdvisory($input: GeneratePlannerAdvisoryInput!) {
3390
+ generatePlannerAdvisory(input: $input) {
3391
+ advisoryText
3392
+ serverLatencyMs
3393
+ providerUsed
3394
+ }
3395
+ }
3396
+ `
3397
+ ),
3383
3398
  // CP-8 min Stage 1 R1 closure (HIGH-1) — applyUserDecision SDK call.
3384
3399
  // Implementation deviation from #C8M-6 literal wording: codevibe-core
3385
3400
  // does NOT take a runtime dep on `@quantiya/quorum-core` (per the
@@ -5660,6 +5675,31 @@ var AppSyncClient = class _AppSyncClient {
5660
5675
  async pingPlanner(input) {
5661
5676
  return (await this.graphqlRequest(mutations.pingPlanner, { input })).data.pingPlanner;
5662
5677
  }
5678
+ /**
5679
+ * Hosted Gemma advisory text generation (browse synthesis, brainstorm, etc.).
5680
+ *
5681
+ * Implements `PlannerAppSyncTransport.generatePlannerAdvisory`.
5682
+ * Input `prompt` is base64 AES-GCM ciphertext under the session key.
5683
+ * Output `advisoryText` is base64 AES-GCM ciphertext under the session key.
5684
+ */
5685
+ async generatePlannerAdvisory(input) {
5686
+ if (!input || typeof input != "object")
5687
+ throw new Error("generatePlannerAdvisory: input object is required");
5688
+ if (!input.sessionId || typeof input.sessionId != "string")
5689
+ throw new Error("generatePlannerAdvisory: sessionId is required");
5690
+ if (!input.prompt || typeof input.prompt != "string")
5691
+ throw new Error("generatePlannerAdvisory: prompt ciphertext is required");
5692
+ if (input.prompt.length > 2e5)
5693
+ throw new Error(`generatePlannerAdvisory: prompt ciphertext exceeds 200,000 chars ceiling (${input.prompt.length})`);
5694
+ if (input.maxTokens !== void 0 && (input.maxTokens < 1 || input.maxTokens > 1536))
5695
+ throw new Error(`generatePlannerAdvisory: maxTokens must be between 1 and 1536 (got ${input.maxTokens})`);
5696
+ if (input.temperature !== void 0 && (input.temperature < 0 || input.temperature > 1))
5697
+ throw new Error(`generatePlannerAdvisory: temperature must be between 0 and 1 (got ${input.temperature})`);
5698
+ let response = await this.graphqlRequest(mutations.generatePlannerAdvisory, { input });
5699
+ if (!response?.data?.generatePlannerAdvisory)
5700
+ throw new Error("generatePlannerAdvisory: empty response returned from AppSync");
5701
+ return response.data.generatePlannerAdvisory;
5702
+ }
5663
5703
  // MARK: - CP-8 min applyUserDecision (Stage 1 R1 HIGH-1 closure)
5664
5704
  /**
5665
5705
  * CP-8 min Stage 1 R1 HIGH-1 closure (Hendry-approved 2026-05-19).
@@ -63041,30 +63081,30 @@ ${summary}`);
63041
63081
  numPredict: BROWSE_RETRY_NUM_PREDICT
63042
63082
  });
63043
63083
  if (signal?.aborted) return;
63044
- let retrySummary = sanitizeForTerminal(parseLocalGemmaBrowseSummary(retryRaw)).trim();
63084
+ let trustLine2 = runner?.providerKind === "hosted" ? "No code was changed." : "No hosted model was called and no code was changed.", retrySummary = sanitizeForTerminal(parseLocalGemmaBrowseSummary(retryRaw)).trim();
63045
63085
  if (retrySummary.length > 0) {
63046
63086
  if (signal?.aborted) return;
63047
63087
  advise(
63048
63088
  store,
63049
63089
  headerAdvised ? `${retrySummary}
63050
63090
 
63051
- No hosted model was called and no code was changed.` : `${header}
63091
+ ${trustLine2}` : `${header}
63052
63092
 
63053
63093
  ${retrySummary}
63054
63094
 
63055
- No hosted model was called and no code was changed.`
63095
+ ${trustLine2}`
63056
63096
  );
63057
63097
  return;
63058
63098
  }
63059
63099
  } catch (retryErr) {
63060
63100
  if (signal?.aborted) return;
63061
- logger.warn("[orchestration-shell] local browse advisory retry failed", {
63101
+ logger.warn("[orchestration-shell] browse advisory retry failed", {
63062
63102
  error: retryErr.message,
63063
63103
  runtimeLabel: runner.runtimeLabel
63064
63104
  });
63065
63105
  }
63066
63106
  if (signal?.aborted) return;
63067
- let extract = buildBrowseExtract(safeText);
63107
+ let isHosted = runner?.providerKind === "hosted", modelNoun = isHosted ? "The model" : "The local model", deadEndNoun = isHosted ? "the model" : "the local model", trustLine = isHosted ? "No code was changed." : "No hosted model was called and no code was changed.", extract = buildBrowseExtract(safeText);
63068
63108
  if (extract) {
63069
63109
  if (signal?.aborted) return;
63070
63110
  let extractPrefix = headerAdvised ? "" : `${header}
@@ -63072,18 +63112,18 @@ No hosted model was called and no code was changed.`
63072
63112
  `;
63073
63113
  advise(
63074
63114
  store,
63075
- `${extractPrefix}The local model couldn't summarize this page, so here is the extracted page text (an extract, not a summary):
63115
+ `${extractPrefix}${modelNoun} couldn't summarize this page, so here is the extracted page text (an extract, not a summary):
63076
63116
 
63077
63117
  ${extract}
63078
63118
 
63079
- ${BROWSE_AGENT_OFFER} No hosted model was called and no code was changed.`
63119
+ ${BROWSE_AGENT_OFFER} ${trustLine}`
63080
63120
  );
63081
63121
  return;
63082
63122
  }
63083
63123
  if (signal?.aborted) return;
63084
63124
  advise(
63085
63125
  store,
63086
- `Read ${dispFinal} but the local model couldn't summarize it. ${BROWSE_AGENT_OFFER} No hosted model was called and no code was changed.`
63126
+ `Read ${dispFinal} but ${deadEndNoun} couldn't summarize it. ${BROWSE_AGENT_OFFER} ${trustLine}`
63087
63127
  );
63088
63128
  }
63089
63129
  }
@@ -63225,30 +63265,30 @@ ${summary}`);
63225
63265
  if (signal?.aborted) return;
63226
63266
  let retryRaw = await runner.generateAdvisory(retryPrompt, { responseFormat: "text", numCtx: 16384, think: !1 });
63227
63267
  if (signal?.aborted) return;
63228
- let retrySummary = sanitizeForTerminal(parseLocalGemmaBrowseSummary(retryRaw)).trim();
63268
+ let trustLine2 = runner?.providerKind === "hosted" ? "No code was changed." : "No hosted model was called and no code was changed.", retrySummary = sanitizeForTerminal(parseLocalGemmaBrowseSummary(retryRaw)).trim();
63229
63269
  if (retrySummary.length > 0) {
63230
63270
  if (signal?.aborted) return;
63231
63271
  advise(
63232
63272
  store,
63233
63273
  headerAdvised ? `${retrySummary}
63234
63274
 
63235
- No hosted model was called and no code was changed.` : `${header}
63275
+ ${trustLine2}` : `${header}
63236
63276
 
63237
63277
  ${retrySummary}
63238
63278
 
63239
- No hosted model was called and no code was changed.`
63279
+ ${trustLine2}`
63240
63280
  );
63241
63281
  return;
63242
63282
  }
63243
63283
  } catch (retryErr) {
63244
63284
  if (signal?.aborted) return;
63245
- logger.warn("[orchestration-shell] local multi-browse retry failed", {
63285
+ logger.warn("[orchestration-shell] multi-browse retry failed", {
63246
63286
  error: retryErr.message,
63247
63287
  runtimeLabel: runner.runtimeLabel
63248
63288
  });
63249
63289
  }
63250
63290
  if (signal?.aborted) return;
63251
- let extracts = sources.map((s) => {
63291
+ let isHosted = runner?.providerKind === "hosted", modelNoun = isHosted ? "The model" : "The local model", deadEndNoun = isHosted ? "the model" : "the local model", trustLine = isHosted ? "No code was changed." : "No hosted model was called and no code was changed.", extracts = sources.map((s) => {
63252
63292
  let ext = buildBrowseExtract(s.content);
63253
63293
  return `[${s.id}] ${s.title}
63254
63294
  ${ext || "(No extract available)"}`;
@@ -63262,11 +63302,11 @@ ${ext || "(No extract available)"}`;
63262
63302
  `;
63263
63303
  advise(
63264
63304
  store,
63265
- `${extractPrefix}The local model couldn't summarize these pages, so here are the extracted highlights:
63305
+ `${extractPrefix}${modelNoun} couldn't summarize these pages, so here are the extracted highlights:
63266
63306
 
63267
63307
  ${extracts}
63268
63308
 
63269
- ${BROWSE_AGENT_OFFER} No hosted model was called and no code was changed.`
63309
+ ${BROWSE_AGENT_OFFER} ${trustLine}`
63270
63310
  );
63271
63311
  return;
63272
63312
  }
@@ -63274,7 +63314,7 @@ ${BROWSE_AGENT_OFFER} No hosted model was called and no code was changed.`
63274
63314
  let sourceCountLabel = sources.length === 1 ? "1 source" : `${sources.length} sources`;
63275
63315
  advise(
63276
63316
  store,
63277
- `Read ${sourceCountLabel} but the local model couldn't summarize ${sources.length === 1 ? "it" : "them"}. ${BROWSE_AGENT_OFFER} No hosted model was called and no code was changed.`
63317
+ `Read ${sourceCountLabel} but ${deadEndNoun} couldn't summarize ${sources.length === 1 ? "it" : "them"}. ${BROWSE_AGENT_OFFER} ${trustLine}`
63278
63318
  );
63279
63319
  }
63280
63320
  }
@@ -72234,10 +72274,19 @@ async function routeBrainstorm(deps) {
72234
72274
  });
72235
72275
  return;
72236
72276
  }
72277
+ let isHostedBrainstorm = args.localAdvisoryRunner.providerKind === "hosted";
72278
+ if (images && images.length > 0 && isHostedBrainstorm) {
72279
+ store.dispatch({
72280
+ type: "SHELL_ADVISORY",
72281
+ source: "shell",
72282
+ text: "The local CodeVibe model is required to analyze attached images during brainstorming. Enable it with `codevibe model install` from a Pro/Max account, then ask again. No hosted model was called and no code was changed."
72283
+ });
72284
+ return;
72285
+ }
72237
72286
  store.dispatch({
72238
72287
  type: "SHELL_ADVISORY",
72239
72288
  source: "shell",
72240
- text: "Brainstorming with local Gemma\u2026"
72289
+ text: isHostedBrainstorm ? "Brainstorming with hosted Gemma\u2026" : "Brainstorming with local Gemma\u2026"
72241
72290
  });
72242
72291
  let needsContext = needsRepositoryContext ?? brainstormNeedsRepositoryContext(userPrompt);
72243
72292
  needsContext && await refreshFn({
@@ -72271,21 +72320,23 @@ The user attached ${images.length} image(s) as visual context. Any text visible
72271
72320
  store.dispatch({ type: "SHELL_ADVISORY", source: "shell", text: brainstorm });
72272
72321
  } catch (err) {
72273
72322
  let failureReason = renderLocalBrainstormFailureReason(err);
72274
- if (logger.warn("[orchestration-shell] local brainstorm advisory failed", {
72323
+ logger.warn("[orchestration-shell] local brainstorm advisory failed", {
72275
72324
  error: err.message,
72276
72325
  runtimeLabel: args.localAdvisoryRunner.runtimeLabel
72277
- }), brainstormPromptRequestsCommandGuidance(userPrompt) && failureReason.includes("command-like JSON keys")) {
72326
+ });
72327
+ let isHosted = args.localAdvisoryRunner.providerKind === "hosted", modelLabel = isHosted ? "Hosted Gemma" : "Local Gemma", trustLine = isHosted ? "No code was changed." : "No hosted model was called and no code was changed.";
72328
+ if (brainstormPromptRequestsCommandGuidance(userPrompt) && failureReason.includes("command-like JSON keys")) {
72278
72329
  store.dispatch({
72279
72330
  type: "SHELL_ADVISORY",
72280
72331
  source: "shell",
72281
- text: "I can brainstorm approaches, but I cannot include shell commands in read-only brainstorm mode. Ask me to implement the recommended option when you are ready, or ask for a command-free design/checklist. No hosted model was called and no code was changed."
72332
+ text: `I can brainstorm approaches, but I cannot include shell commands in read-only brainstorm mode. Ask me to implement the recommended option when you are ready, or ask for a command-free design/checklist. ${trustLine}`
72282
72333
  });
72283
72334
  return;
72284
72335
  }
72285
72336
  store.dispatch({
72286
72337
  type: "SHELL_ADVISORY",
72287
72338
  source: "shell",
72288
- text: `Local Gemma could not produce the brainstorm. Reason: ${failureReason}. No hosted model was called and no code was changed. Try again with a narrower question or run \`codevibe model health-check\` to verify the local model.`
72339
+ text: `${modelLabel} could not produce the brainstorm. Reason: ${failureReason}. ${trustLine} Try again with a narrower question or run \`codevibe model health-check\` to verify the model.`
72289
72340
  });
72290
72341
  }
72291
72342
  } finally {
@@ -72311,10 +72362,19 @@ async function routeAdvisory(deps) {
72311
72362
  });
72312
72363
  return;
72313
72364
  }
72365
+ let isHostedAdvisory = args.localAdvisoryRunner.providerKind === "hosted";
72366
+ if (hasImages && isHostedAdvisory) {
72367
+ store.dispatch({
72368
+ type: "SHELL_ADVISORY",
72369
+ source: "shell",
72370
+ text: "The local CodeVibe model is required to analyze an attached image. Enable it with `codevibe model install` from a Pro/Max account, then ask again. No hosted model was called and no code was changed."
72371
+ });
72372
+ return;
72373
+ }
72314
72374
  store.dispatch({
72315
72375
  type: "SHELL_ADVISORY",
72316
72376
  source: "shell",
72317
- text: hasImages ? "Analyzing the attached image(s) with local Gemma\u2026" : "Answering with local Gemma\u2026"
72377
+ text: hasImages ? "Analyzing the attached image(s) with local Gemma\u2026" : isHostedAdvisory ? "Answering with hosted Gemma\u2026" : "Answering with local Gemma\u2026"
72318
72378
  });
72319
72379
  let MAX_ADVISORY_CLARIFICATIONS = 4, MAX_ADVISORY_CLARIFICATION_QUESTION_CHARS = 400, MAX_ADVISORY_CLARIFICATION_ANSWER_CHARS = 1e3, MAX_ADVISORY_CANONICAL_CONTEXT_CHARS = 6e3, MAX_ADVISORY_USER_PROMPT_CHARS = 4e3, MAX_ADVISORY_PROMPT_CHARS = 42e3, MAX_ADVISORY_PAGE_CHARS = 24e3, MAX_ADVISORY_STATUS_CHARS = 1500, MAX_ADVISORY_PAGE_TOKENS = 6e3, MAX_ADVISORY_PROMPT_TOKENS = 13500, statusText = "";
72320
72380
  try {
@@ -72448,21 +72508,21 @@ async function routeAdvisory(deps) {
72448
72508
  });
72449
72509
  return;
72450
72510
  }
72451
- let fallback = deps.fallbackSummary?.trim();
72511
+ let fallback = deps.fallbackSummary?.trim(), effectiveIsHosted = args.localAdvisoryRunner.providerKind === "hosted", modelNoun = effectiveIsHosted ? "The model" : "The local model", modelLabel = effectiveIsHosted ? "Hosted Gemma" : "Local Gemma", trustLine = effectiveIsHosted ? "No code was changed." : "No hosted model was called and no code was changed.", generationScope = effectiveIsHosted ? "hosted" : "local";
72452
72512
  if (fallback) {
72453
72513
  store.dispatch({
72454
72514
  type: "SHELL_ADVISORY",
72455
72515
  source: "shell",
72456
72516
  text: sanitizeForTerminal(`${fallback}
72457
72517
 
72458
- (Note: detailed local generation returned an empty answer; showing concise answer.)`)
72518
+ (Note: detailed ${generationScope} generation returned an empty answer; showing concise answer.)`)
72459
72519
  });
72460
72520
  return;
72461
72521
  }
72462
72522
  store.dispatch({
72463
72523
  type: "SHELL_ADVISORY",
72464
72524
  source: "shell",
72465
- text: hasImages ? "The local model returned no answer for the attached image(s). No code was changed." : "The local model returned an empty answer. Try rephrasing with a bit more detail. No code was changed."
72525
+ text: hasImages ? `${modelNoun} returned no answer for the attached image(s). ${trustLine}` : `${modelNoun} returned an empty answer. Try rephrasing with a bit more detail. ${trustLine}`
72466
72526
  });
72467
72527
  } catch (err) {
72468
72528
  logger.warn("[orchestration-shell] local advisory failed", {
@@ -72470,7 +72530,7 @@ async function routeAdvisory(deps) {
72470
72530
  hasImages,
72471
72531
  runtimeLabel: args.localAdvisoryRunner.runtimeLabel
72472
72532
  });
72473
- let fallback = deps.fallbackSummary?.trim();
72533
+ let fallback = deps.fallbackSummary?.trim(), effectiveIsHosted = args.localAdvisoryRunner.providerKind === "hosted", modelLabel = effectiveIsHosted ? "Hosted Gemma" : "Local Gemma", trustLine = effectiveIsHosted ? "No code was changed." : "No hosted model was called and no code was changed.", generationScope = effectiveIsHosted ? "hosted" : "local";
72474
72534
  if (fallback) {
72475
72535
  let reason = renderLocalBrainstormFailureReason(err);
72476
72536
  store.dispatch({
@@ -72478,14 +72538,14 @@ async function routeAdvisory(deps) {
72478
72538
  source: "shell",
72479
72539
  text: sanitizeForTerminal(`${fallback}
72480
72540
 
72481
- (Note: detailed local generation was unavailable: ${reason}. Showing concise answer.)`)
72541
+ (Note: detailed ${generationScope} generation was unavailable: ${reason}. Showing concise answer.)`)
72482
72542
  });
72483
72543
  return;
72484
72544
  }
72485
72545
  store.dispatch({
72486
72546
  type: "SHELL_ADVISORY",
72487
72547
  source: "shell",
72488
- text: hasImages ? `Local Gemma could not analyze the attached image(s). Reason: ${renderLocalBrainstormFailureReason(err)}. No hosted model was called and no code was changed.` : `Local Gemma could not answer. Reason: ${renderLocalBrainstormFailureReason(err)}. No hosted model was called and no code was changed.`
72548
+ text: hasImages ? `${modelLabel} could not analyze the attached image(s). Reason: ${renderLocalBrainstormFailureReason(err)}. ${trustLine}` : `${modelLabel} could not answer. Reason: ${renderLocalBrainstormFailureReason(err)}. ${trustLine}`
72489
72549
  });
72490
72550
  }
72491
72551
  }
@@ -75731,6 +75791,109 @@ Local planner error. Falling back to hosted Gemma planner on AWS.
75731
75791
  }
75732
75792
  };
75733
75793
 
75794
+ // src/planner/hosted-advisory.ts
75795
+ init_logger2();
75796
+ var HostedGemmaAdvisoryRunner = class {
75797
+ constructor(deps) {
75798
+ this.deps = deps;
75799
+ this.runtimeLabel = "Hosted Gemma (AWS Bedrock)";
75800
+ this.providerKind = "hosted";
75801
+ }
75802
+ async generateAdvisory(promptText, options) {
75803
+ if (options?.images && options.images.length > 0)
75804
+ throw new Error(
75805
+ "HostedGemmaAdvisoryRunner: image attachments are not supported by hosted Bedrock Gemma. A local vision model is required."
75806
+ );
75807
+ let sessionId = this.deps.activeSessionIdHolder.value;
75808
+ if (!sessionId)
75809
+ throw new Error("Hosted Gemma advisory generation failed: no active session ID");
75810
+ let sessionKey = await this.deps.sessionKeyResolver.getSessionKey(sessionId);
75811
+ if (!sessionKey)
75812
+ throw new Error(`Hosted Gemma advisory generation failed: session key unresolved for session ${sessionId}`);
75813
+ let encryptedPrompt = this.deps.crypto.encryptString(promptText, sessionKey), maxTokens = options?.numPredict, temperature = options?.temperature, start = Date.now();
75814
+ try {
75815
+ let response = await this.deps.transport.generatePlannerAdvisory({
75816
+ sessionId,
75817
+ prompt: encryptedPrompt,
75818
+ ...maxTokens !== void 0 ? { maxTokens } : {},
75819
+ ...temperature !== void 0 ? { temperature } : {}
75820
+ });
75821
+ return this.deps.crypto.decryptString(response.advisoryText, sessionKey);
75822
+ } catch (err) {
75823
+ let elapsedMs = Date.now() - start;
75824
+ throw logger.warn("[hosted-advisory] generatePlannerAdvisory failed", {
75825
+ sessionId,
75826
+ elapsedMs,
75827
+ error: err?.message
75828
+ }), err;
75829
+ }
75830
+ }
75831
+ async warm() {
75832
+ return !0;
75833
+ }
75834
+ async unload() {
75835
+ return !0;
75836
+ }
75837
+ };
75838
+
75839
+ // src/planner/composite-advisory.ts
75840
+ init_logger2();
75841
+ var CompositeAdvisoryRunner = class {
75842
+ constructor(opts) {
75843
+ this.opts = opts;
75844
+ this.localHealthy = !!opts.localRunner;
75845
+ }
75846
+ get runtimeLabel() {
75847
+ return this.opts.tier === "FREE" || this.localHealthy ? this.opts.localRunner?.runtimeLabel ?? "Local Gemma" : this.opts.hostedRunner.runtimeLabel;
75848
+ }
75849
+ get providerKind() {
75850
+ return this.opts.tier === "FREE" || this.localHealthy ? "local" : "hosted";
75851
+ }
75852
+ async generateAdvisory(promptText, options) {
75853
+ if (this.opts.tier === "FREE") {
75854
+ if (!this.opts.localRunner)
75855
+ throw new Error(
75856
+ "LocalModelUnavailable: local Gemma is not installed or enabled. Free tier operates in local-only mode."
75857
+ );
75858
+ return this.opts.localRunner.generateAdvisory(promptText, options);
75859
+ }
75860
+ if (options?.images && options.images.length > 0) {
75861
+ if (!this.localHealthy || !this.opts.localRunner)
75862
+ throw new Error(
75863
+ "LocalModelUnavailable: local CodeVibe vision model is required to analyze attached images."
75864
+ );
75865
+ return this.opts.localRunner.generateAdvisory(promptText, options);
75866
+ }
75867
+ if (this.localHealthy && this.opts.localRunner)
75868
+ try {
75869
+ return await this.opts.localRunner.generateAdvisory(promptText, options);
75870
+ } catch (err) {
75871
+ logger.warn("[composite-advisory] local advisory failed; falling back to hosted Bedrock Gemma", {
75872
+ error: err?.message
75873
+ }), this.localHealthy = !1, this.opts.onFallbackToHosted?.(err);
75874
+ }
75875
+ return this.opts.hostedRunner.generateAdvisory(promptText, options);
75876
+ }
75877
+ async warm() {
75878
+ if (this.opts.localRunner?.warm)
75879
+ try {
75880
+ return await this.opts.localRunner.warm();
75881
+ } catch {
75882
+ return !1;
75883
+ }
75884
+ return !0;
75885
+ }
75886
+ async unload() {
75887
+ if (this.opts.localRunner?.unload)
75888
+ try {
75889
+ return await this.opts.localRunner.unload();
75890
+ } catch {
75891
+ return !1;
75892
+ }
75893
+ return !0;
75894
+ }
75895
+ };
75896
+
75734
75897
  // src/session/session-rekey.ts
75735
75898
  init_logger2();
75736
75899
 
@@ -81250,7 +81413,8 @@ ${err.message}
81250
81413
  function buildProductionPlannerStack(appsyncClient, emitter) {
81251
81414
  let transport = {
81252
81415
  classifyPlannerPrompt: (input) => appsyncClient.classifyPlannerPrompt(input),
81253
- pingPlanner: (input) => appsyncClient.pingPlanner(input)
81416
+ pingPlanner: (input) => appsyncClient.pingPlanner(input),
81417
+ generatePlannerAdvisory: (input) => appsyncClient.generatePlannerAdvisory(input)
81254
81418
  }, cache = new PlannerCacheLayer(), adaptedEmitter = async (e) => {
81255
81419
  await emitter({
81256
81420
  sessionId: e.sessionId,
@@ -81291,9 +81455,18 @@ function buildProductionPlannerStack(appsyncClient, emitter) {
81291
81455
  sessionKeyResolver,
81292
81456
  adaptedEmitter
81293
81457
  ), realSetActiveSession = adapter.setActiveSession.bind(adapter);
81294
- return adapter.setActiveSession = (sessionId) => {
81458
+ adapter.setActiveSession = (sessionId) => {
81295
81459
  activeSessionIdHolder.value = sessionId, realSetActiveSession(sessionId);
81296
- }, { adapter, cache, health };
81460
+ };
81461
+ let hostedAdvisoryRunner = new HostedGemmaAdvisoryRunner({
81462
+ transport: {
81463
+ generatePlannerAdvisory: (input) => appsyncClient.generatePlannerAdvisory(input)
81464
+ },
81465
+ crypto: crypto33,
81466
+ sessionKeyResolver,
81467
+ activeSessionIdHolder
81468
+ });
81469
+ return { adapter, cache, health, hostedAdvisoryRunner };
81297
81470
  }
81298
81471
  function resolvePlannerAdapter(args) {
81299
81472
  let env = args.env ?? process.env, forceHosted = env.CODEVIBE_FORCE_HOSTED_PLANNER === "1" || env.CODEVIBE_PLANNER_PROVIDER === "hosted", tier = args.tier, isPaidTier = tier === "PRO" || tier === "MAX";
@@ -81304,7 +81477,7 @@ function resolvePlannerAdapter(args) {
81304
81477
  adapter: hostedStack.adapter,
81305
81478
  runtimeKind: "hosted",
81306
81479
  runtimeLabel: "Hosted Gemma (AWS Bedrock)",
81307
- advisoryRunner: args.localPlanner.advisoryRunner,
81480
+ advisoryRunner: hostedStack.hostedAdvisoryRunner,
81308
81481
  classifyRunner: args.localPlanner.classifyRunner,
81309
81482
  cache: hostedStack.cache,
81310
81483
  health: hostedStack.health,
@@ -81336,6 +81509,10 @@ function resolvePlannerAdapter(args) {
81336
81509
  onNotice: (msg) => {
81337
81510
  process.stdout.write(msg);
81338
81511
  }
81512
+ }), compositeAdvisoryRunner = new CompositeAdvisoryRunner({
81513
+ localRunner: args.localPlanner.advisoryRunner,
81514
+ hostedRunner: hostedStack.hostedAdvisoryRunner,
81515
+ tier
81339
81516
  });
81340
81517
  return {
81341
81518
  adapter: compositeAdapter,
@@ -81345,7 +81522,7 @@ function resolvePlannerAdapter(args) {
81345
81522
  get runtimeLabel() {
81346
81523
  return compositeAdapter.runtimeLabel;
81347
81524
  },
81348
- advisoryRunner: args.localPlanner.advisoryRunner,
81525
+ advisoryRunner: compositeAdvisoryRunner,
81349
81526
  classifyRunner: args.localPlanner.classifyRunner,
81350
81527
  cache: hostedStack.cache,
81351
81528
  health: hostedStack.health,
@@ -81378,7 +81555,7 @@ function resolvePlannerAdapter(args) {
81378
81555
  adapter: hostedStack.adapter,
81379
81556
  runtimeKind: "hosted",
81380
81557
  runtimeLabel: "Hosted Gemma (AWS Bedrock)",
81381
- advisoryRunner: null,
81558
+ advisoryRunner: hostedStack.hostedAdvisoryRunner,
81382
81559
  classifyRunner: args.localPlanner.classifyRunner,
81383
81560
  cache: hostedStack.cache,
81384
81561
  health: hostedStack.health,
@@ -26,6 +26,16 @@ export interface PlannerAppSyncTransport {
26
26
  ok: boolean;
27
27
  ms: number;
28
28
  }>;
29
+ generatePlannerAdvisory?(input: {
30
+ sessionId: string;
31
+ prompt: string;
32
+ maxTokens?: number;
33
+ temperature?: number;
34
+ }): Promise<{
35
+ advisoryText: string;
36
+ serverLatencyMs: number;
37
+ providerUsed: string;
38
+ }>;
29
39
  }
30
40
  /**
31
41
  * Crypto bridge — `encryptString` + `encryptJson` + `decryptJson`
@@ -0,0 +1,25 @@
1
+ import type { Tier } from '../orchestration-shell/types';
2
+ import type { AdvisoryOptions, AdvisoryRunner } from './local-advisory';
3
+ export interface CompositeAdvisoryRunnerOptions {
4
+ localRunner?: AdvisoryRunner | null;
5
+ hostedRunner: AdvisoryRunner;
6
+ tier: Tier;
7
+ onFallbackToHosted?: (error: Error) => void;
8
+ }
9
+ /**
10
+ * CompositeAdvisoryRunner coordinates local and hosted advisory execution:
11
+ * - Free Tier Invariant: strictly isolates Free tier from cloud inference ($0 marginal cost).
12
+ * - Local-First Preference: routes natural language turns / browse to local Gemma when healthy.
13
+ * - Dynamic Failover: if local Gemma fails (Ollama down, crash, OOM), seamlessly falls back
14
+ * to hosted Bedrock Gemma for Pro/Max users without interrupting the session.
15
+ */
16
+ export declare class CompositeAdvisoryRunner implements AdvisoryRunner {
17
+ private readonly opts;
18
+ private localHealthy;
19
+ constructor(opts: CompositeAdvisoryRunnerOptions);
20
+ get runtimeLabel(): string;
21
+ get providerKind(): 'local' | 'hosted';
22
+ generateAdvisory(promptText: string, options?: AdvisoryOptions): Promise<string>;
23
+ warm(): Promise<boolean>;
24
+ unload(): Promise<boolean>;
25
+ }
@@ -0,0 +1,37 @@
1
+ import type { AdvisoryOptions, AdvisoryRunner } from './local-advisory';
2
+ import type { PlannerCryptoBridge, SessionKeyResolver } from './client';
3
+ export interface HostedAdvisoryTransport {
4
+ generatePlannerAdvisory(input: {
5
+ sessionId: string;
6
+ prompt: string;
7
+ maxTokens?: number;
8
+ temperature?: number;
9
+ }): Promise<{
10
+ advisoryText: string;
11
+ serverLatencyMs: number;
12
+ providerUsed: string;
13
+ }>;
14
+ }
15
+ export interface HostedGemmaAdvisoryRunnerDeps {
16
+ transport: HostedAdvisoryTransport;
17
+ crypto: PlannerCryptoBridge;
18
+ sessionKeyResolver: SessionKeyResolver;
19
+ activeSessionIdHolder: {
20
+ value: string | null;
21
+ };
22
+ }
23
+ /**
24
+ * HostedGemmaAdvisoryRunner executes advisory text generation (browse synthesis,
25
+ * brainstorm, etc.) against Google Gemma 3 12B on AWS Bedrock via AppSync planner proxy.
26
+ *
27
+ * Wire payload is encrypted end-to-end with AES-256-GCM under the session key.
28
+ */
29
+ export declare class HostedGemmaAdvisoryRunner implements AdvisoryRunner {
30
+ private readonly deps;
31
+ readonly runtimeLabel = "Hosted Gemma (AWS Bedrock)";
32
+ readonly providerKind: "hosted";
33
+ constructor(deps: HostedGemmaAdvisoryRunnerDeps);
34
+ generateAdvisory(promptText: string, options?: AdvisoryOptions): Promise<string>;
35
+ warm(): Promise<boolean>;
36
+ unload(): Promise<boolean>;
37
+ }
@@ -4,5 +4,7 @@ export { PlannerCacheLayer } from './cache';
4
4
  export { PlannerHealthMachine } from './health-state';
5
5
  export { BackendPlannerClient, PlannerBudgetExceededError, PlannerTierGateRejectedError, type PlannerAppSyncTransport, type PlannerCryptoBridge, type SessionKeyResolver, type ShellEventEmit, type EmitShellEventFn, } from './client';
6
6
  export { LocalGemmaPlannerAdapter, parseLocalGemmaPlannerDecision, PlannerOutputUnparseableError, renderLocalGemmaPlannerPrompt, type LocalGemmaPlannerRunner, } from './local-gemma';
7
- export { parseLocalGemmaAdvisorySummary, renderLocalGemmaFamiliarizePrompt, renderLocalGemmaMultiBrowsePrompt, renderLocalGemmaBrowsePrompt, MAX_BROWSE_CONTENT_CHARS, MAX_MULTI_BROWSE_TOTAL_CONTENT_CHARS, MAX_MULTI_BROWSE_RENDERED_PROMPT_CHARS, type LocalGemmaAdvisoryRunner, } from './local-advisory';
7
+ export { parseLocalGemmaAdvisorySummary, renderLocalGemmaFamiliarizePrompt, renderLocalGemmaMultiBrowsePrompt, renderLocalGemmaBrowsePrompt, MAX_BROWSE_CONTENT_CHARS, MAX_MULTI_BROWSE_TOTAL_CONTENT_CHARS, MAX_MULTI_BROWSE_RENDERED_PROMPT_CHARS, type AdvisoryRunner, type AdvisoryOptions, type AdvisoryResponseFormat, type LocalGemmaAdvisoryRunner, type LocalGemmaAdvisoryOptions, type LocalGemmaAdvisoryResponseFormat, } from './local-advisory';
8
8
  export { CompositePlannerAdapter, type CompositePlannerAdapterOptions, } from './composite-adapter';
9
+ export { HostedGemmaAdvisoryRunner, type HostedGemmaAdvisoryRunnerDeps, type HostedAdvisoryTransport, } from './hosted-advisory';
10
+ export { CompositeAdvisoryRunner, type CompositeAdvisoryRunnerOptions, } from './composite-advisory';
@@ -1,6 +1,6 @@
1
1
  import type { StructuralSummary } from '../structural-summary/types';
2
- export interface LocalGemmaAdvisoryRunner {
3
- generateAdvisory(promptText: string, options?: LocalGemmaAdvisoryOptions): Promise<string>;
2
+ export interface AdvisoryRunner {
3
+ generateAdvisory(promptText: string, options?: AdvisoryOptions): Promise<string>;
4
4
  /**
5
5
  * Dogfood #618 — optional fire-and-forget model pre-warm (load + keep_alive).
6
6
  * Never throws; resolves false on failure. Called once at shell startup.
@@ -13,10 +13,11 @@ export interface LocalGemmaAdvisoryRunner {
13
13
  */
14
14
  unload?(): Promise<boolean>;
15
15
  runtimeLabel: string;
16
+ readonly providerKind?: 'local' | 'hosted';
16
17
  }
17
- export type LocalGemmaAdvisoryResponseFormat = 'json' | 'text';
18
- export interface LocalGemmaAdvisoryOptions {
19
- responseFormat?: LocalGemmaAdvisoryResponseFormat;
18
+ export type AdvisoryResponseFormat = 'json' | 'text';
19
+ export interface AdvisoryOptions {
20
+ responseFormat?: AdvisoryResponseFormat;
20
21
  numPredict?: number;
21
22
  /**
22
23
  * RAW base64 images (NO `data:` prefix) for the MULTIMODAL local answerer
@@ -38,7 +39,15 @@ export interface LocalGemmaAdvisoryOptions {
38
39
  * Omitted unless set; browse callers pass false (R8-F2, R9-F4).
39
40
  */
40
41
  think?: boolean;
42
+ /**
43
+ * Temperature hint for advisory generation.
44
+ */
45
+ temperature?: number;
41
46
  }
47
+ /** Backward-compatibility aliases for local runner consumers */
48
+ export type LocalGemmaAdvisoryRunner = AdvisoryRunner;
49
+ export type LocalGemmaAdvisoryResponseFormat = AdvisoryResponseFormat;
50
+ export type LocalGemmaAdvisoryOptions = AdvisoryOptions;
42
51
  export declare const MAX_RENDERED_PROMPT_CHARS = 30000;
43
52
  export declare const MAX_RENDERED_PROMPT_TOKENS = 7500;
44
53
  export declare function redactAbsoluteLocalPaths(text: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.47",
3
+ "version": "2.0.49",
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",