@quantiya/codevibe-claude-plugin 2.0.31 → 2.0.32

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.
@@ -18310,7 +18310,52 @@ ${formatReviewerPolicy(snapshot)}`
18310
18310
  }
18311
18311
 
18312
18312
  // src/planner/local-advisory.ts
18313
- var import_path2 = __toESM(require("path")), MAX_USER_PROMPT_CHARS = 2e3, MAX_README_PREVIEW_CHARS = 900, MAX_RENDERED_PROMPT_CHARS = 18e3, MAX_ADVISORY_SUMMARY_CHARS = 6e3, MAX_BRAINSTORM_PRIOR_TURN_CHARS = 1200, PATH_REDACTION = "[path]", URL_PLACEHOLDER_PREFIX = "__CODEVIBE_URL_", LOCAL_SINGLE_SEGMENT_ROOTS = /* @__PURE__ */ new Set([
18313
+ var import_path2 = __toESM(require("path"));
18314
+
18315
+ // src/planner/url-protect.ts
18316
+ var HTTP_URL_PATTERN_SOURCE = "https?:\\/\\/[^\\s\"'`<>,)}\\]\u3001\u3002\u3003\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\u3014\u3015\u3016\u3017\u3018\u3019\u301A\u301B\u301C\u301D\u301E\u301F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65]+";
18317
+ function httpUrlPattern() {
18318
+ return new RegExp(HTTP_URL_PATTERN_SOURCE, "g");
18319
+ }
18320
+ var URL_PLACEHOLDER_PREFIX = "__CODEVIBE_URL_", URL_PLACEHOLDER_SOURCE = `${URL_PLACEHOLDER_PREFIX}(\\d+)__`, TRAILING_PUNCTUATION = /[.;:!?]+$/;
18321
+ function protectUrls(text2) {
18322
+ let urls = [];
18323
+ return {
18324
+ text: text2.replace(httpUrlPattern(), (url) => {
18325
+ let token = `${URL_PLACEHOLDER_PREFIX}${urls.length}__`;
18326
+ return urls.push(url), token;
18327
+ }),
18328
+ urls
18329
+ };
18330
+ }
18331
+ function restoreUrls(text2, urls) {
18332
+ return text2.replace(new RegExp(URL_PLACEHOLDER_SOURCE, "g"), (token, index) => urls[Number(index)] ?? token);
18333
+ }
18334
+ function isWellFormedHttpUrl(candidate) {
18335
+ if (candidate.includes("[path]")) return !1;
18336
+ try {
18337
+ let url = new URL(candidate);
18338
+ return url.protocol === "http:" || url.protocol === "https:";
18339
+ } catch {
18340
+ return !1;
18341
+ }
18342
+ }
18343
+ function extractHttpUrls(text2) {
18344
+ let seen = /* @__PURE__ */ new Set(), out = [], re = httpUrlPattern(), tail = new RegExp(`^(?:${HTTP_URL_PATTERN_SOURCE.slice(HTTP_URL_PATTERN_SOURCE.indexOf("[^"))})`);
18345
+ for (let m = re.exec(text2); m !== null; m = re.exec(text2)) {
18346
+ let url = m[0], end = m.index + url.length;
18347
+ for (; text2[end] === ")" && url.split("(").length - 1 > url.split(")").length - 1; ) {
18348
+ url += ")", end += 1;
18349
+ let more = tail.exec(text2.slice(end));
18350
+ more && (url += more[0], end += more[0].length);
18351
+ }
18352
+ re.lastIndex = end, url = url.replace(TRAILING_PUNCTUATION, ""), !(url.length === 0 || seen.has(url)) && (seen.add(url), out.push(url));
18353
+ }
18354
+ return out;
18355
+ }
18356
+
18357
+ // src/planner/local-advisory.ts
18358
+ var MAX_USER_PROMPT_CHARS = 2e3, MAX_README_PREVIEW_CHARS = 900, MAX_RENDERED_PROMPT_CHARS = 18e3, MAX_ADVISORY_SUMMARY_CHARS = 6e3, MAX_BRAINSTORM_PRIOR_TURN_CHARS = 1200, PATH_REDACTION = "[path]", LOCAL_SINGLE_SEGMENT_ROOTS = /* @__PURE__ */ new Set([
18314
18359
  "Applications",
18315
18360
  "Library",
18316
18361
  "System",
@@ -18334,19 +18379,6 @@ var import_path2 = __toESM(require("path")), MAX_USER_PROMPT_CHARS = 2e3, MAX_RE
18334
18379
  "workspace",
18335
18380
  "workspaces"
18336
18381
  ]);
18337
- function protectUrls(text2) {
18338
- let urls = [];
18339
- return {
18340
- text: text2.replace(/https?:\/\/[^\s"'`<>,)}\]]+/g, (url) => {
18341
- let token = `${URL_PLACEHOLDER_PREFIX}${urls.length}__`;
18342
- return urls.push(url), token;
18343
- }),
18344
- urls
18345
- };
18346
- }
18347
- function restoreUrls(text2, urls) {
18348
- return text2.replace(new RegExp(`${URL_PLACEHOLDER_PREFIX}(\\d+)__`, "g"), (_token, index) => urls[Number(index)] ?? _token);
18349
- }
18350
18382
  function isPathBoundary(text2, index) {
18351
18383
  return index <= 0 ? !0 : !/[A-Za-z0-9_./~-]/.test(text2[index - 1] ?? "");
18352
18384
  }
@@ -27645,7 +27677,7 @@ var PlannerOutputUnparseableError = class _PlannerOutputUnparseableError extends
27645
27677
  rationale: { type: "string" },
27646
27678
  clarifying_question: { type: "string" },
27647
27679
  advisory_summary: { type: "string" },
27648
- browseUrls: { type: "array", items: { type: "string", pattern: "^https?://.*$" } },
27680
+ browseUrls: { type: "array", items: { type: "string" } },
27649
27681
  browseQuery: { type: "string" },
27650
27682
  action: { type: "string", enum: [...LOCAL_GEMMA_ALLOWED_ACTIONS] }
27651
27683
  },
@@ -27663,7 +27695,7 @@ function truncatePlannerTextPreservingEnds(text2, maxChars) {
27663
27695
  return `${text2.slice(0, headChars)}${omission}${text2.slice(-tailChars)}`;
27664
27696
  }
27665
27697
  function sanitizePlannerText(text2, maxChars) {
27666
- let sanitized = text2.replace(/```[\s\S]*?```/g, "[code block omitted]").replace(/```[\s\S]*$/g, "[code block omitted]").replace(
27698
+ let protectedInput = protectUrls(text2), sanitized = protectedInput.text.replace(/```[\s\S]*?```/g, "[code block omitted]").replace(/```[\s\S]*$/g, "[code block omitted]").replace(
27667
27699
  /^\s*(?:function|class|interface|type|enum|import|export|const|let|var)\b[^\n]{0,240}/gm,
27668
27700
  "[code snippet omitted]"
27669
27701
  ).replace(
@@ -27679,7 +27711,7 @@ function sanitizePlannerText(text2, maxChars) {
27679
27711
  /(^|[\s"'`([{,])(?:\.{1,2}\/)?(?:\.[A-Za-z0-9_.-]+|[A-Za-z0-9_.-]+\.(?:json|ts|tsx|js|jsx|mjs|cjs|env|pem|key|p12|yaml|yml|toml|lock))(?=$|[\s"'`),\]}])/g,
27680
27712
  "$1[path]"
27681
27713
  );
27682
- return truncatePlannerTextPreservingEnds(sanitized, maxChars);
27714
+ return truncatePlannerTextPreservingEnds(restoreUrls(sanitized, protectedInput.urls), maxChars);
27683
27715
  }
27684
27716
  function sanitizeCanonicalConversationContext(text2, maxChars) {
27685
27717
  let sanitized = sanitizePlannerText(text2, Number.MAX_SAFE_INTEGER).trim();
@@ -27845,11 +27877,59 @@ ${keptOptionalLines.join(`
27845
27877
  ${payloadJson}
27846
27878
  ${outputReminder}` : baseRendered;
27847
27879
  }
27848
- function extractJsonObject2(raw) {
27880
+ function firstBalancedJsonObject(raw, from = 0) {
27881
+ let start = raw.indexOf("{", from);
27882
+ if (start < 0) return null;
27883
+ let depth = 0, inString = !1, escaped = !1;
27884
+ for (let i = start; i < raw.length; i++) {
27885
+ let ch = raw[i];
27886
+ if (inString) {
27887
+ escaped ? escaped = !1 : ch === "\\" ? escaped = !0 : ch === '"' && (inString = !1);
27888
+ continue;
27889
+ }
27890
+ if (ch === '"')
27891
+ inString = !0;
27892
+ else if (ch === "{")
27893
+ depth += 1;
27894
+ else if (ch === "}" && (depth -= 1, depth === 0))
27895
+ return { text: raw.slice(start, i + 1), end: i + 1 };
27896
+ }
27897
+ return null;
27898
+ }
27899
+ function assertNoConflictingTrailingDecision(rest, first) {
27900
+ let firstAction = typeof first.action == "string" ? first.action : void 0, conflict = (action) => {
27901
+ throw new PlannerOutputUnparseableError(
27902
+ `local Gemma planner output contained a second decision object with a conflicting action (${String(firstAction)} then ${action})`
27903
+ );
27904
+ }, cursor = 0;
27905
+ for (; ; ) {
27906
+ let next = firstBalancedJsonObject(rest, cursor);
27907
+ if (next === null) break;
27908
+ cursor = next.end;
27909
+ let parsed = parseJsonObjectText(next.text, []);
27910
+ if (parsed === null) continue;
27911
+ let action = parsed.action;
27912
+ typeof action == "string" && action !== firstAction && conflict(action);
27913
+ }
27914
+ let lexical = /(?<!\\)"action"\s*:\s*"([^"\\]*)"/g;
27915
+ for (let m = lexical.exec(rest); m !== null; m = lexical.exec(rest))
27916
+ m[1] !== firstAction && conflict(m[1]);
27917
+ }
27918
+ function parseJsonObjectText(text2, errors) {
27919
+ try {
27920
+ return JSON.parse(text2);
27921
+ } catch (err) {
27922
+ errors.push(err);
27923
+ }
27924
+ try {
27925
+ return JSON.parse(stripTrailingCommas(text2));
27926
+ } catch {
27927
+ return null;
27928
+ }
27929
+ }
27930
+ function legacyJsonObjectSlice(raw) {
27849
27931
  let trimmed = raw.trim(), fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed), body = fenced ? fenced[1].trim() : trimmed, start = body.indexOf("{"), end = body.lastIndexOf("}");
27850
- if (start < 0 || end <= start)
27851
- throw new PlannerOutputUnparseableError("local Gemma planner output contained no JSON object");
27852
- return body.slice(start, end + 1);
27932
+ return start < 0 || end <= start ? null : body.slice(start, end + 1);
27853
27933
  }
27854
27934
  function stripTrailingCommas(jsonStr) {
27855
27935
  let result = "", inString = !1, escaped = !1;
@@ -27874,23 +27954,29 @@ function stripTrailingCommas(jsonStr) {
27874
27954
  }
27875
27955
  return result;
27876
27956
  }
27877
- function repairAndParseLocalGemmaJson(raw, initialErr) {
27878
- let candidate = extractJsonObject2(raw), strippedCommas = stripTrailingCommas(candidate);
27879
- try {
27880
- return JSON.parse(strippedCommas);
27881
- } catch {
27882
- throw new PlannerOutputUnparseableError(
27883
- `local Gemma planner output was not valid JSON: ${initialErr.message}`
27884
- );
27957
+ function parseLocalGemmaJsonObject(raw) {
27958
+ let errors = [], balanced = firstBalancedJsonObject(raw);
27959
+ if (balanced !== null) {
27960
+ let parsed = parseJsonObjectText(balanced.text, errors);
27961
+ if (parsed !== null)
27962
+ return assertNoConflictingTrailingDecision(raw.slice(balanced.end), parsed), parsed;
27885
27963
  }
27964
+ let legacy = legacyJsonObjectSlice(raw);
27965
+ if (legacy !== null && legacy !== balanced?.text) {
27966
+ let parsed = parseJsonObjectText(legacy, errors);
27967
+ if (parsed !== null) return parsed;
27968
+ }
27969
+ throw balanced === null && legacy === null ? new PlannerOutputUnparseableError("local Gemma planner output contained no JSON object") : new PlannerOutputUnparseableError(
27970
+ `local Gemma planner output was not valid JSON: ${errors[0]?.message ?? "unknown parse error"}`
27971
+ );
27972
+ }
27973
+ var RAW_OUTPUT_HEAD_CHARS = 500;
27974
+ function summarizeRawOutputForLog(raw) {
27975
+ let cleaned = raw.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
27976
+ return cleaned.length <= RAW_OUTPUT_HEAD_CHARS ? cleaned : `${cleaned.slice(0, RAW_OUTPUT_HEAD_CHARS)}\u2026 (+${cleaned.length - RAW_OUTPUT_HEAD_CHARS} chars)`;
27886
27977
  }
27887
27978
  function parseLocalGemmaPlannerDecision(raw) {
27888
- let parsed;
27889
- try {
27890
- parsed = JSON.parse(extractJsonObject2(raw));
27891
- } catch (err) {
27892
- parsed = repairAndParseLocalGemmaJson(raw, err);
27893
- }
27979
+ let parsed = parseLocalGemmaJsonObject(raw);
27894
27980
  if (!parsed || typeof parsed != "object")
27895
27981
  throw new PlannerOutputUnparseableError("local Gemma planner output was not a JSON object");
27896
27982
  let obj = parsed;
@@ -27938,7 +28024,11 @@ var LocalGemmaPlannerAdapter = class {
27938
28024
  jsonSchema: LOCAL_GEMMA_DECISION_JSON_SCHEMA,
27939
28025
  numPredict: 1024
27940
28026
  });
27941
- return parseLocalGemmaPlannerDecision(raw);
28027
+ try {
28028
+ return parseLocalGemmaPlannerDecision(raw);
28029
+ } catch (err) {
28030
+ throw err instanceof PlannerOutputUnparseableError && err.rawOutputHead === void 0 && (err.rawOutputHead = summarizeRawOutputForLog(raw)), err;
28031
+ }
27942
28032
  }
27943
28033
  async probe() {
27944
28034
  return this.runner.probe ? this.runner.probe() : {
@@ -60807,6 +60897,13 @@ var LastModeFileSchema = import_zod3.z.object({
60807
60897
  var React19 = __toESM(require("react"));
60808
60898
 
60809
60899
  // src/orchestration-shell/index.ts
60900
+ function resolveBrowseUrls(modelUrls, prompt) {
60901
+ if (!modelUrls || modelUrls.length === 0) return;
60902
+ let promptUrls = extractHttpUrls(prompt);
60903
+ if (promptUrls.length > 0) return promptUrls;
60904
+ let usable = modelUrls.filter(isWellFormedHttpUrl);
60905
+ return usable.length > 0 ? usable : void 0;
60906
+ }
60810
60907
  var OrchestrationShellStartupError = class extends Error {
60811
60908
  constructor(message, cause) {
60812
60909
  super(message), this.name = "OrchestrationShellStartupError", this.cause = cause;
@@ -65248,7 +65345,10 @@ async function handleShellUserInput(deps) {
65248
65345
  if (err instanceof PlannerOutputUnparseableError && isLocalPlannerRuntime(args)) {
65249
65346
  logger.warn(
65250
65347
  "[orchestration-shell] planner output unparseable; request not dispatched",
65251
- { error: errMsg }
65348
+ // `rawOutputHead` (2026-09-11): the bounded, control-free head of what
65349
+ // the model actually returned — the incident line without it needed a
65350
+ // live repro to explain "Expected property name … at position 1".
65351
+ { error: errMsg, rawOutputHead: err.rawOutputHead }
65252
65352
  ), store.dispatch({
65253
65353
  type: "SHELL_ADVISORY",
65254
65354
  source: "shell",
@@ -65411,6 +65511,7 @@ async function handleShellUserInput(deps) {
65411
65511
  return;
65412
65512
  }
65413
65513
  if (decision.action === "browse") {
65514
+ let browseUrls = resolveBrowseUrls(decision.browseUrls, plannerInput.prompt);
65414
65515
  await routeBrowse({
65415
65516
  store,
65416
65517
  localAdvisoryRunner: args.localAdvisoryRunner,
@@ -65418,7 +65519,7 @@ async function handleShellUserInput(deps) {
65418
65519
  // Recent conversation → the model formulates a context-aware search query (resolve
65419
65520
  // acronyms/pronouns) instead of searching the raw command sentence.
65420
65521
  priorTurns: collectRecentConversationTurns(store.getState().conversation, plannerInput.prompt),
65421
- ...decision.browseUrls ? { browseUrls: decision.browseUrls } : {},
65522
+ ...browseUrls && browseUrls.length > 0 ? { browseUrls } : {},
65422
65523
  ...decision.browseQuery ? { browseQuery: decision.browseQuery } : {}
65423
65524
  });
65424
65525
  return;
@@ -2,6 +2,21 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { AppSyncClient } from '../appsync';
3
3
  import { Session } from '../types';
4
4
  import { OrchestrationStore } from './store';
5
+ /**
6
+ * WEB-BROWSING-DESIGN.md rev 6 — the address for a model-chosen fetch.
7
+ *
8
+ * The classifier owns the fetch-vs-search decision: a `browse` decision WITH
9
+ * `browseUrls` is a fetch, one WITHOUT is a search (`browseQuery`). This helper
10
+ * never turns a search into a fetch (Stage-1 r1 F4: a URL that merely appears
11
+ * in "search for alternatives to https://x.com" stays a search). For a fetch,
12
+ * the user's literal URL is the address: the classify prompt is path-redacted
13
+ * before the model sees it, so the model's echo can be mangled
14
+ * (`https://[path]` in the 2026-09-11 incident) or drift. Only when the prompt
15
+ * carries no URL are the model's well-formed URLs used; a fetch decision whose
16
+ * every URL is unusable and whose prompt has none yields no URL (the route then
17
+ * falls back to `browseQuery` or asks for a URL).
18
+ */
19
+ export declare function resolveBrowseUrls(modelUrls: string[] | undefined, prompt: string): string[] | undefined;
5
20
  import { AdvisoryAttachmentJournal } from './advisory-attachment-journal';
6
21
  import type { ImageAttachment } from './types';
7
22
  import { type GateDecisionSubmitDeps, type GroupDecisionSubmitDeps } from './gate-decision-submit';
@@ -31,6 +31,14 @@ export interface LocalGemmaPlannerRunner {
31
31
  * without inventing a successful classification or starting a task.
32
32
  */
33
33
  export declare class PlannerOutputUnparseableError extends Error {
34
+ /**
35
+ * The first {@link RAW_OUTPUT_HEAD_CHARS} characters of the model output that
36
+ * failed to parse (terminal-control bytes stripped), attached by the adapter so
37
+ * the shell's warn line records WHAT the model said. Before 2026-09-11 nothing
38
+ * logged the raw text and the `<channel|>` echo incident needed a live repro
39
+ * to diagnose. Absent when the error was raised without a raw string in hand.
40
+ */
41
+ rawOutputHead?: string;
34
42
  constructor(message: string);
35
43
  }
36
44
  export declare const LOCAL_GEMMA_DECISION_JSON_SCHEMA: {
@@ -49,7 +57,6 @@ export declare const LOCAL_GEMMA_DECISION_JSON_SCHEMA: {
49
57
  type: string;
50
58
  items: {
51
59
  type: string;
52
- pattern: string;
53
60
  };
54
61
  };
55
62
  browseQuery: {
@@ -70,6 +77,10 @@ export declare const LOCAL_GEMMA_DECISION_JSON_SCHEMA: {
70
77
  export declare function renderLocalGemmaPlannerPrompt(input: PlannerInput, options?: {
71
78
  isSubprocessRunner?: boolean;
72
79
  }): string;
80
+ /** Characters of raw model output retained on {@link PlannerOutputUnparseableError.rawOutputHead}. */
81
+ export declare const RAW_OUTPUT_HEAD_CHARS = 500;
82
+ /** Terminal-control-free, bounded head of a raw model output for the warn log. */
83
+ export declare function summarizeRawOutputForLog(raw: string): string;
73
84
  export declare function parseLocalGemmaPlannerDecision(raw: string): PlannerDecision;
74
85
  export declare class LocalGemmaPlannerAdapter implements PlannerAdapter {
75
86
  private readonly runner;
@@ -0,0 +1,20 @@
1
+ /** Source of the shared http(s) URL matcher (no `g` state — build with {@link httpUrlPattern}). */
2
+ export declare const HTTP_URL_PATTERN_SOURCE = "https?:\\/\\/[^\\s\"'`<>,)}\\]\u3001\u3002\u3003\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\u3014\u3015\u3016\u3017\u3018\u3019\u301A\u301B\u301C\u301D\u301E\u301F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65]+";
3
+ /** A fresh global RegExp for {@link HTTP_URL_PATTERN_SOURCE}. */
4
+ export declare function httpUrlPattern(): RegExp;
5
+ /** Replace every http(s) URL with an opaque placeholder; returns the URLs in order. */
6
+ export declare function protectUrls(text: string): {
7
+ text: string;
8
+ urls: string[];
9
+ };
10
+ /** Put the URLs captured by {@link protectUrls} back in place of their placeholders. */
11
+ export declare function restoreUrls(text: string, urls: string[]): string;
12
+ /** `http:`/`https:` and parseable by WHATWG `URL`; never a redacted `[path]` echo. */
13
+ export declare function isWellFormedHttpUrl(candidate: string): boolean;
14
+ /**
15
+ * The literal http(s) URLs a user typed, in order, de-duplicated, with trailing
16
+ * sentence punctuation dropped (`read https://x.com/a.` → `https://x.com/a`) and
17
+ * balanced parentheses kept (`…/wiki/Foo_(bar)` stays whole — F7). Pure text
18
+ * extraction — scheme/port/host validation stays in `guardedFetch`.
19
+ */
20
+ export declare function extractHttpUrls(text: string): string[];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.26",
3
+ "version": "2.0.27",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "2.0.31",
3
+ "version": "2.0.32",
4
4
  "description": "Control Claude Code from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "codevibe": {
@@ -48,7 +48,7 @@
48
48
  "node": ">=22.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@quantiya/codevibe-core": "^2.0.26",
51
+ "@quantiya/codevibe-core": "^2.0.27",
52
52
  "@quantiya/quorum-core": "^1.0.1",
53
53
  "dotenv": "^16.6.1",
54
54
  "express": "^5.1.0",