@quantiya/codevibe-claude-plugin 2.0.41 → 2.0.42

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.
@@ -112,6 +112,7 @@ export type SubstrateEngager = (input: {
112
112
  * applies whenever the row cannot be established).
113
113
  */
114
114
  requireConfined?: boolean;
115
+ webBrowsingActive?: boolean;
115
116
  }) => Promise<SubstrateEngageResult>;
116
117
  /** Backend lease is 15 minutes; three renewal opportunities fit inside it. */
117
118
  export declare const TEAM_TRACK_HEARTBEAT_INTERVAL_MS: number;
@@ -18,6 +18,7 @@ export interface SpawnArgs {
18
18
  stdinPayload?: string;
19
19
  signal?: AbortSignal;
20
20
  taskId?: string;
21
+ webTurnActive?: boolean;
21
22
  lifecycleAudit?: 'active_task' | 'none';
22
23
  onProcessSpawned?: ProcessSpawnedHook;
23
24
  onProcessExited?: ProcessExitedHook;
@@ -18616,11 +18616,11 @@ function renderLocalGemmaPlannerPrompt(input, options) {
18616
18616
  "Never refuse merely because the user asks about local repository files, the current directory, the workspace, or the project. Refuse only for clearly unsafe requests such as exposing secrets/credentials, destructive root/home deletion, malware, bypassing auth/paywalls, or exfiltration.",
18617
18617
  "",
18618
18618
  "Routing rules:",
18619
- '- start_task: user asks to create, add, edit, fix, implement, refactor, test, run tests, or review a SPECIFIC, BOUNDED change \u2014 such as their pending diff, staged/uncommitted changes, a named file, or a pull request. (A BROAD "review/audit the WHOLE codebase for bugs" is read-only understanding \u2192 familiarize, NOT start_task \u2014 see below.) If the user says current directory, current working directory, repo root, workspace, ".", "./", or an absolute path, treat the target as sufficiently specified. If the requested content is obvious, such as a JavaScript hello-world file, do not ask for extra content. A SINGLE task is start_task even when it has multiple steps; choose team_decompose ONLY when the user explicitly asks for parallel work (see below).',
18619
+ '- start_task: user asks to create, add, edit, fix, implement, refactor, test, run tests, or review a SPECIFIC, BOUNDED change \u2014 such as their pending diff, staged/uncommitted changes, a named file, or a pull request. (A BROAD "review/audit the WHOLE codebase for bugs" is read-only understanding \u2192 familiarize, NOT start_task \u2014 see below.) If the user says current directory, current working directory, repo root, workspace, ".", "./", or an absolute path, treat the target as sufficiently specified. If the requested content is obvious, such as a JavaScript hello-world file, do not ask for extra content. A SINGLE task is start_task even when it has multiple steps; choose team_decompose ONLY when the user explicitly asks for parallel work (see below). This ALSO includes requests that fetch/check an external URL or search online AND write, save, create, or update local files, reports, or code (e.g. "check https://... and write a report into report.md" -> start_task).',
18620
18620
  '- team_decompose: user EXPLICITLY asks to split the work into MULTIPLE PARALLEL tasks or tracks, run an "agent team", do things "in parallel", or describes 2+ INDEPENDENT pieces (typically touching different files) to run concurrently. Prefer team_decompose over start_task whenever the request names an agent team or parallel/separate tracks. A single multi-step task is start_task, NOT team_decompose.',
18621
18621
  '- familiarize: user asks to read, inspect, understand, explain, or summarize the current project, codebase, repository, folder, files, or working directory without asking for a mutation. This ALSO covers a BROAD read-only REVIEW or AUDIT of the WHOLE repository/codebase \u2014 e.g. "review the codebase for bugs", "audit the repo for issues", "look over the whole project for problems": reviewing the ENTIRE repo for bugs/quality/security is a read-only understanding task, so it is familiarize, NOT start_task. (A BOUNDED review of a specific pending DIFF or named file is start_task instead.)',
18622
18622
  `- brainstorm: user asks to explore options, tradeoffs, risks, architecture directions, or recommendations for THIS project's design or implementation before deciding what to design or implement. brainstorm is NOT for opinions or analysis of a web page, article, or answer from earlier in this session \u2014 that is advisory_response. Use brainstorm for exploratory prompts such as "brainstorm ways to build offline support" or "compare approaches to create a local context store". Do NOT use brainstorm when the user asks for immediate mutation, a design artifact, a hard gate, tests, review, commit, deploy, or release; choose the workflow action or ask one clarifying question.`,
18623
- '- browse: user asks to read/open/fetch/summarize a specific web URL (http/https), OR to look something up on the web / search online / find the latest on a topic. Put any explicit URL(s) in "browseUrls" (array) and, when there is no URL, put the search query in "browseQuery". This route fetches the page (or searches) on the user machine and answers from the content; it is NOT a familiarize (which reads the LOCAL repo) and NOT advisory_response.',
18623
+ '- browse: user asks a read-only question to read/open/fetch/summarize a specific web URL (http/https), OR to look something up on the web / search online / find the latest on a topic, WITHOUT asking to create, write, or modify any local files. If the user asks to fetch a URL or search AND save/write/create local files or reports, choose start_task instead. Put any explicit URL(s) in "browseUrls" (array) and, when there is no URL, put the search query in "browseQuery". This route fetches the page (or searches) on the user machine and answers from the content; it is NOT a familiarize (which reads the LOCAL repo) and NOT advisory_response.',
18624
18624
  options?.isSubprocessRunner ? `- advisory_response: user asks a general question that does not require repository context or file changes. Put a COMPLETE, natural, conversational ANSWER to the question in the "advisory_summary" field (a full helpful reply, like a chat assistant \u2014 NOT a one-line label or a restatement of the question); explain core principles accurately: for search/pathfinding, start with the start node in the open set, use strictly standard A* terminology (open set and closed set only; never invent other sets like missed set or turn set), and compare with Dijkstra's algorithm; explain that a more accurate, higher admissible heuristic (closer to the true remaining cost) guides the search more directly to the goal and expands fewer nodes, whereas a smaller or zero heuristic (like Dijkstra's algorithm) explores in all directions and expands more nodes; a heuristic must be admissible (never overestimate the true distance) to guarantee an optimal shortest path; write all mathematical expressions in clean plain text like f(n) = g(n) + h(n) (never use LaTeX math notation, \\text{}, math mode $, or backslashes); format the entire explanation in clean, well-structured markdown prose paragraphs separated by blank lines (do not use bullet lists, numbered sub-lists, or backslash line breaks; write complete narrative paragraphs); never use tab characters or \\t; never use double quotes inside advisory_summary (use single quotes ' if quoting terms); keep "rationale" a short internal classification reason. Answer directly and warmly, e.g. "Yes \u2014 I can \u2026".` : '- advisory_response: user asks a general question or capability question. Put a concise, natural, 1-sentence direct answer or definition in "advisory_summary" (e.g. "Yes, I can write Rust" or "A* is a best-first pathfinding algorithm that expands nodes by f(n) = g(n) + h(n)"); do NOT write preambles like "Here is..." or conversational labels; keep "rationale" a short reason. The downstream local advisory engine generates the full answer.',
18625
18625
  options?.isSubprocessRunner ? '- FOLLOW-UP ON EARLIER CONTENT: when the user asks for your thoughts, opinion, analysis, critique, or a deeper explanation of something already fetched or answered earlier in this session (a web page that was read, a search result, a previous reply) \u2014 e.g. "I am looking for your thoughts", "what do you think about that", "go deeper on that", "is that right?" \u2014 choose advisory_response and put the COMPLETE reply in advisory_summary (no downstream answerer runs for this runner). Never brainstorm, familiarize, or browse again for such a follow-up.' : `- FOLLOW-UP ON EARLIER CONTENT: when the user asks for your thoughts, opinion, analysis, critique, or a deeper explanation of something already fetched or answered earlier in this session (a web page that was read, a search result, a previous reply) \u2014 e.g. "I am looking for your thoughts", "what do you think about that", "go deeper on that", "is that right?" \u2014 choose advisory_response with a one-sentence advisory_summary (the shell's answerer writes the full reply from the page and the conversation). Never brainstorm, familiarize, or browse again for such a follow-up.`,
18626
18626
  options?.isSubprocessRunner ? "- summarize_current_status: user asks what changed, what the last task did, current progress, or workflow status." : "- Questions about progress, what changed, what the last task did, or workflow status are advisory_response too: the shell attaches its own status record to the answer, so never invent task history.",
@@ -18658,6 +18658,8 @@ function renderLocalGemmaPlannerPrompt(input, options) {
18658
18658
  'User: "look up the React 19 release notes online" -> {"action":"browse","rationale":"web lookup","browseQuery":"React 19 release notes"}',
18659
18659
  'User: "what is the latest LTS version of Node.js?" -> {"action":"browse","rationale":"freshness lookup needs current web info, not stale knowledge","browseQuery":"latest LTS version Node.js"}',
18660
18660
  'User: "what is the current stable version of Python?" -> {"action":"browse","rationale":"current version is a freshness web lookup","browseQuery":"current stable version Python"}',
18661
+ 'User: "check https://registry.npmjs.org/@quantiya/codevibe-core/latest and write a report into report.md" -> {"action":"start_task","rationale":"check external URL and write report file"}',
18662
+ 'User: "Please use your codevibe_web_search tool to search for \\"Quantiya AI\\", then use codevibe_web_fetch to read https://quantiya.ai/ and write a 3-bullet summary of the site into a file named claude-web-test.md." -> {"action":"start_task","rationale":"search web and write summary file"}',
18661
18663
  `User: "fyi every FLAGS.md row should also say what the default is" (session.currentTaskState is in_progress) -> {"action":"advisory_response","rationale":"a convention stated while a task runs; acknowledge, no second task","advisory_summary":"Noted \u2014 FLAGS.md rows will also state each flag's default."}`,
18662
18664
  'User: "scratch that \u2014 descriptions should not end with a period" (session.currentTaskState is in_progress) -> {"action":"advisory_response","rationale":"retracts an earlier rule while a task runs","advisory_summary":"Understood \u2014 descriptions will not end with a period."}',
18663
18665
  'User: "also add a --cv-quiet flag to release.sh" (session.currentTaskState is in_progress) -> {"action":"start_task","rationale":"asks for a new deliverable while a task runs"}',
@@ -21326,6 +21328,57 @@ function buildCommandIntentMetadata(text2) {
21326
21328
  if (intent)
21327
21329
  return { command_intent: buildCommandIntentEnvelope(intent) };
21328
21330
  }
21331
+ function isWebAccessNeeded(text2) {
21332
+ return !text2 || typeof text2 != "string" ? !1 : /https?:\/\/[^\s]+/i.test(text2) || /\bwww\.[^\s]+/i.test(text2) ? !0 : /(?:^|[.?!;])\s*(?:please\s+)?(?:implement|build|create|add|support|develop)\b[\s\S]{0,50}?\b(?:feature|functionality|function|button|capability|endpoint|command|module|ui|api|service)\b/i.test(
21333
+ text2
21334
+ ) || /\b(?:feature|functionality|function|capability)\b[\s\S]{0,40}?\b(?:to|that|which)\s+(?:search|browse|look\s+up)\b/i.test(
21335
+ text2
21336
+ ) || /(?:实现|添加|开发|支持|做个|写个)[\s\S]{0,30}?(?:功能|特性)/.test(text2) || /(?:功能|特性)[\s\S]{0,30}?(?:实现|添加|开发|支持)/.test(text2) || /(?:機能|기능)[\s\S]{0,30}?(?:実装|追加|作成|開発|구현|추가)/.test(text2) || /(?:実装|追加|作成|開発|구현|추가)[\s\S]{0,30}?(?:機能|기능)/.test(text2) ? !1 : !!(/(?:^|[.?!;])\s*(?:please\s+)?(?:search|browse)\s+(?:the\s+(?:web|internet)\b|online)(?:\s+(?:for|about)\b|\s*[,.;?!]?$)/i.test(
21337
+ text2
21338
+ ) || /(?:^|[.?!;])\s*(?:please\s+)?look\s+up\s+(?!where\b|how\b|who\b|why\b)[\w\s.-]{1,60}?\s+(?:online|on\s+the\s+web\b|on\s+the\s+internet\b)(?:\s+(?:for|about)\b|\s*[,.;?!]?$)/i.test(
21339
+ text2
21340
+ ) && !/\bonline\s+(?:checkout|users?|status|banking|store|shop|service|mode|game|player|account|system|portal|flow|documentation)\b/i.test(
21341
+ text2
21342
+ ) || /(?:^|[.?!;])\s*(?:please\s+)?(?:use|try|run|perform|do)\s+(?:a\s+)?(?:web|internet|online)\s+search(?:\s+(?:for|about|on)\b|\s*[,.;?!]?$)(?!\s+(?:api|service|endpoint|tests?|feature|sdk|library|component|function|code)\b)/i.test(
21343
+ text2
21344
+ ) || /(?:^|[.?!;])\s*(?:please\s+)?(?:google\s+(?:this|it|that)\b|search\s+(?:on\s+google|google)\s+for\b)/i.test(
21345
+ text2
21346
+ ) || /(?:^|[.?!;,。!?;])\s*(?:请|帮我|麻烦)?\s*(?:(?:去?上网|在网上|去官网|在官网|在官方网站)(?:搜索|查找|查阅|查一下|搜一下)(?!到|过|出|的|结果)|去网上搜(?:一下|索)|谷歌一下|(?:查阅|查找|搜索)(?:官网|官方网站)(?:的)?(?:文档|说明|api)|(?:在线|官网|联网)搜索一下)/i.test(
21347
+ text2
21348
+ ) || /(?:^|[.?!;])\s*(?:(?:ネット|オンライン)で\s*(?:検索して(?:みて|ください)?|調べて(?:みて|ください)?|探して(?:みて|ください)?)(?=$|[\s。、.!?])|ウェブ検索で\s*[\u4E00-\u9FFF\u30A0-\u30FF\w ]{1,20}を(?:調べて|探して))|(?:^|[.?!;])\s*[\w\s\u3040-\u30FF\u4E00-\u9FFF]{0,20}?ググ[るっ](?=$|[\s.?!;。~])/i.test(
21349
+ text2
21350
+ ) || /(?:^|[.?!;])\s*(?:[\w\s가-힣]{0,40}?\s+)?(?:인터넷|온라인|구글)에서\s*(?:검색해(?:봐|줘|요)|찾아(?:봐|줘)|조회해(?:봐|줘))(?=$|[\s.?!;。~])/i.test(
21351
+ text2
21352
+ ));
21353
+ }
21354
+ function hasFileMutationIntent(text2) {
21355
+ if (!text2 || typeof text2 != "string") return !1;
21356
+ let stripped = stripLeadingAgentControlToken(text2).trim(), textWithoutUrls = stripped.replace(/https?:\/\/[^\s]+/gi, " ").trim();
21357
+ if (/\b(?:how\s+(?:to|do\s+I|can\s+I)|explain\s+how\s+to|tell\s+me\s+how\s+to)\b/i.test(stripped) || /(?:^|[.?!;])\s*(?:what\s+(?:is|are)\b|c[oó]mo\s+(?:hacer|puedo|se\s+puede|escribir|guardar|crear)|comment\s+(?:faire|écrire|sauvegarder|créer|enregistrer)|wie\s+(?:kann\s+ich|macht\s+man|schreibt\s+man|speichert\s+man|erstellt\s+man))\b/i.test(stripped) || /(?:如何|怎么|怎样|告诉我(?:如何|怎么|怎样)|解释如何|请问|什么是)/.test(stripped) || /(?:どうやって|方法(?:を(?:教えて|説明して))|やり方を教えて|でしょうか|とは(?:何|なん)ですか)/.test(stripped) || /(?:어떻게|방법을?\s*(?:알려줘|설명해줘)|법\s*설명|하는지\s*알려줘)/.test(stripped))
21358
+ return !1;
21359
+ if (/>{1,2}\s*[\w./-]+\b/.test(textWithoutUrls))
21360
+ return !0;
21361
+ let fileExt = "(?:md|markdown|txt|json|csv|ts|js|tsx|jsx|py|html|css|yaml|yml|log|sh|xml|toml|sql|env)", writeToFilePatternEn = new RegExp(
21362
+ `\\b(?:write|save|create|dump|output|put|export|store|record|guardar|escribir|crear|enregistrer|\xE9crire|cr\xE9er|sauvegarder|speichern|schreiben|erstellen)\\b[\\s\\S]*?\\b(?:into|to|in|as|en|dans|auf|zu)\\b(?!\\s+(?:chat|console|stdout|screen|terminal)\\b)[\\s\\S]*?(?:(?:a|an|the|un|une|ein|eine|el|la)?\\s*file(?:\\s+named|\\s+called)?\\s+)?[\\w./-]+\\.${fileExt}\\b`,
21363
+ "i"
21364
+ ), writeToFilePatternGermanic = new RegExp(
21365
+ `\\b(?:in|auf|zu|into|to)\\s+[\\w./-]+\\.${fileExt}[\\s\\S]*?\\b(?:speichern|schreiben|erstellen|ablegen)\\b`,
21366
+ "i"
21367
+ ), namedFilePatternEn = /\b(?:into|to|in|en|dans|auf|zu)\s+(?:a|an|un|une|ein|eine|el|la)?\s*file(?:\s+(?:named|called))?\s+[\w./-]+\b/i, createFilePatternEn = /\b(?:create|add|touch)\s+(?:a|an|the)?\s*(?:new\s+)?(?:file|markdown\s+file|script)\b/i, directFileVerbPatternEn = new RegExp(
21368
+ `\\b(?:create|write|touch|add|crear|escribir|cr\xE9er|\xE9crire|erstellen)\\s+[\\w./-]+\\.${fileExt}\\b`,
21369
+ "i"
21370
+ ), writeToFilePatternZh = new RegExp(
21371
+ `(?:\u5199\u5165|\u4FDD\u5B58\u5230?|\u521B\u5EFA|\u751F\u6210|\u8F93\u51FA\u5230|\u5B58\u5165|\u5BFC\u51FA\u5230?|\u8BB0\u5F55\u5230?)[\\s\\S]*?[\\w./-]+\\.${fileExt}\\b`,
21372
+ "i"
21373
+ ), writeToFilePatternJa = new RegExp(
21374
+ `(?:[\\w./-]+\\.${fileExt}[\\s\\S]*?(?:\u306B|\u3078)?\\s*(?:\u66F8\u304D\u51FA|\u4FDD\u5B58|\u4F5C\u6210|\u751F\u6210|\u51FA\u529B|\u8A18\u9332|\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8)|(?:\u66F8\u304D\u51FA|\u4FDD\u5B58|\u4F5C\u6210|\u751F\u6210|\u51FA\u529B|\u8A18\u9332|\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8)[\\s\\S]*?[\\w./-]+\\.${fileExt})`,
21375
+ "i"
21376
+ ), writeToFilePatternKo = new RegExp(
21377
+ `(?:[\\w./-]+\\.${fileExt}[\\s\\S]*?(?:\uC5D0|\uC73C\uB85C|\uB85C)?\\s*(?:\uC800\uC7A5|\uC791\uC131|\uC0DD\uC131|\uAE30\uB85D|\uCD9C\uB825|\uB0B4\uBCF4\uB0B4\uAE30)|(?:\uC800\uC7A5|\uC791\uC131|\uC0DD\uC131|\uAE30\uB85D|\uCD9C\uB825|\uB0B4\uBCF4\uB0B4\uAE30)[\\s\\S]*?[\\w./-]+\\.${fileExt})`,
21378
+ "i"
21379
+ );
21380
+ return writeToFilePatternEn.test(textWithoutUrls) || writeToFilePatternGermanic.test(textWithoutUrls) || namedFilePatternEn.test(textWithoutUrls) || createFilePatternEn.test(textWithoutUrls) || directFileVerbPatternEn.test(textWithoutUrls) || writeToFilePatternZh.test(textWithoutUrls) || writeToFilePatternJa.test(textWithoutUrls) || writeToFilePatternKo.test(textWithoutUrls);
21381
+ }
21329
21382
 
21330
21383
  // src/orchestration-shell/route-browse.ts
21331
21384
  var RETAINED_PAGE_MAX_CHARS = 12e4, NO_MODEL_PREFIX = "Local CodeVibe model is required to read and summarize web pages.", RUN_INSTALL = "Run `codevibe model install` from a Pro/Max account, then ask again. No hosted model was called and no code was changed.";
@@ -21390,17 +21443,29 @@ function fetchErrorMessage(err, url) {
21390
21443
  async function readUrl(deps, runner, url) {
21391
21444
  let { store, userPrompt, signal } = deps, dispUrl = sanitizeForTerminal(url);
21392
21445
  advise(store, `Reading ${dispUrl}\u2026`);
21393
- let body, finalUrl, fetch2 = deps.guardedFetchFn ?? guardedFetch;
21446
+ let body, finalUrl, contentType = "", fetch2 = deps.guardedFetchFn ?? guardedFetch;
21394
21447
  try {
21395
- let res = await fetch2(url, signal);
21396
- body = res.body, finalUrl = res.finalUrl;
21448
+ let res = await fetch2(url, signal, { allowJson: !0 });
21449
+ body = res.body, finalUrl = res.finalUrl, contentType = res.contentType;
21397
21450
  } catch (err) {
21398
21451
  if (signal?.aborted) return;
21399
21452
  let suffix = deps.delegateAnswer ? " The mentioned agent was not consulted." : "";
21400
21453
  err instanceof FetchError ? advise(store, `${fetchErrorMessage(err, dispUrl)}${suffix}`) : advise(store, `Couldn't read ${dispUrl} (${sanitizeForTerminal(err.message)}). No code was changed.${suffix}`);
21401
21454
  return;
21402
21455
  }
21403
- let dispFinal = sanitizeForTerminal(finalUrl), { title, text: text2 } = await htmlToText(body), safeTitle = sanitizeForTerminal(title).slice(0, 200), fullSafeText = sanitizeForTerminal(text2);
21456
+ let dispFinal = sanitizeForTerminal(finalUrl), title = dispFinal, text2 = "";
21457
+ if (contentType.toLowerCase().includes("application/json"))
21458
+ try {
21459
+ let parsed = JSON.parse(body);
21460
+ text2 = JSON.stringify(parsed, null, 2);
21461
+ } catch {
21462
+ text2 = body;
21463
+ }
21464
+ else {
21465
+ let extracted = await htmlToText(body);
21466
+ title = extracted.title || dispFinal, text2 = extracted.text || "";
21467
+ }
21468
+ let safeTitle = sanitizeForTerminal(title).slice(0, 200), fullSafeText = sanitizeForTerminal(text2);
21404
21469
  if (signal?.aborted) return;
21405
21470
  if (!fullSafeText.trim()) {
21406
21471
  let suffix = deps.delegateAnswer ? " The mentioned agent was not consulted." : "";
@@ -21529,7 +21594,19 @@ async function readSearchResults(deps, runner, query, results) {
21529
21594
  advise(store, `Reading top ${targetResults.length} web pages in parallel\u2026`);
21530
21595
  let fetch2 = deps.guardedFetchFn ?? guardedFetch, fetchPromises = targetResults.map(async (res) => {
21531
21596
  try {
21532
- let fetched = await fetch2(res.url, signal), { title, text: text2 } = await htmlToText(fetched.body), safeTitle = sanitizeForTerminal(title || res.title || "").slice(0, 200), rawText = text2 || "";
21597
+ let fetched = await fetch2(res.url, signal, { allowJson: !0 }), title = res.title || "", text2 = "";
21598
+ if ((fetched.contentType || "").toLowerCase().includes("application/json"))
21599
+ try {
21600
+ let parsed = JSON.parse(fetched.body);
21601
+ text2 = JSON.stringify(parsed, null, 2);
21602
+ } catch {
21603
+ text2 = fetched.body;
21604
+ }
21605
+ else {
21606
+ let extracted = await htmlToText(fetched.body);
21607
+ title = extracted.title || title, text2 = extracted.text || "";
21608
+ }
21609
+ let safeTitle = sanitizeForTerminal(title).slice(0, 200), rawText = text2 || "";
21533
21610
  return rawText.trim() ? {
21534
21611
  finalUrl: fetched.finalUrl,
21535
21612
  safeTitle,
@@ -31273,7 +31350,7 @@ function stripAgyWebPlugin(workdir) {
31273
31350
  function buildImplementorArgv(agent, mode, workdir, options) {
31274
31351
  switch (agent) {
31275
31352
  case "CLAUDE": {
31276
- if (process.env.CODEVIBE_ENABLE_AGENT_WEB === "1" && !!options?.mcpConfigPath) {
31353
+ if (!!options?.mcpConfigPath) {
31277
31354
  let disallowedTools = mode === "plan" ? "WebFetch,WebSearch,Bash,Agent,Task,Workflow,SendMessage,ListAgents" : "WebFetch,WebSearch,Agent,Task,Workflow,SendMessage,ListAgents", baseTools = mode === "plan" ? "Read,Grep,Glob" : "Read,Grep,Glob,Edit,Write,Bash", allowedTools = `${baseTools},mcp__codevibe-web__codevibe_web_search,mcp__codevibe-web__codevibe_web_fetch`;
31278
31355
  return {
31279
31356
  argv: [
@@ -31310,7 +31387,7 @@ function buildImplementorArgv(agent, mode, workdir, options) {
31310
31387
  let sandbox = mode === "plan" ? "read-only" : "workspace-write", lastMessagePath = import_node_path.default.join(
31311
31388
  import_node_os.default.tmpdir(),
31312
31389
  `quorum-impl-codex-${process.pid}-${(0, import_uuid4.v4)()}.txt`
31313
- ), webEnabled = process.env.CODEVIBE_ENABLE_AGENT_WEB === "1" && !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
31390
+ ), webEnabled = !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
31314
31391
  return webEnabled && mcpServer ? {
31315
31392
  argv: [
31316
31393
  "codex",
@@ -31360,7 +31437,7 @@ function buildImplementorArgv(agent, mode, workdir, options) {
31360
31437
  throw new Error(
31361
31438
  "buildImplementorArgv: ANTIGRAVITY requires an absolute workdir (agy is workspace-centric \u2014 --add-dir is its only view of the tree); the call site must thread it (AGY-2.0 D3)"
31362
31439
  );
31363
- let webEnabled = process.env.CODEVIBE_ENABLE_AGENT_WEB === "1" && !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
31440
+ let webEnabled = !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
31364
31441
  return webEnabled && mcpServer && setupAgyWebPlugin(workdir, mcpServer), {
31365
31442
  argv: [...agyImplementorArgvPrefix(mode), workdir, "--print", ""],
31366
31443
  capture: { kind: "stdout" }
@@ -47859,7 +47936,7 @@ var LocalExecutorImpl = class {
47859
47936
  "none"
47860
47937
  ), { args, substrateEngaged: !1 };
47861
47938
  }
47862
- let result = await this.substrateEngager({
47939
+ let isWebActive = args.webTurnActive === !0, result = await this.substrateEngager({
47863
47940
  taskId: effectiveTaskId,
47864
47941
  agentKind,
47865
47942
  workdir: args.workingDir,
@@ -47868,7 +47945,8 @@ var LocalExecutorImpl = class {
47868
47945
  // A1d (design §8.1) — thread the spawn's confinement demand so the engager
47869
47946
  // can build the WRITE-CONFINED resolver row for a broker-less agent
47870
47947
  // (GEMINI/ANTIGRAVITY) rather than immediately returning `reduced_trust`.
47871
- requireConfined: args.requireConfined === !0
47948
+ requireConfined: args.requireConfined === !0,
47949
+ webBrowsingActive: isWebActive
47872
47950
  });
47873
47951
  if (result.mode === "reduced_trust") {
47874
47952
  if (args.requireConfined)
@@ -57040,6 +57118,7 @@ var QuorumLoop = class _QuorumLoop {
57040
57118
  * are removed with the task's other per-task state at its terminal cleanup.
57041
57119
  */
57042
57120
  this.singleTaskContextByTask = /* @__PURE__ */ new Map();
57121
+ this.webAccessByTask = /* @__PURE__ */ new Map();
57043
57122
  /**
57044
57123
  * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
57045
57124
  * user-requested changes for a task, accumulated across ALL revise rounds.
@@ -58306,7 +58385,7 @@ var QuorumLoop = class _QuorumLoop {
58306
58385
  })).workflowState;
58307
58386
  } catch (err) {
58308
58387
  let wasNewest = this.activeTaskId === taskId, restoreTo = previousActive && this.singleTaskContextByTask.has(previousActive.taskId) ? previousActive : null;
58309
- wasNewest && (this.activeTaskId = restoreTo?.taskId ?? null, this.activeBrief = restoreTo?.brief ?? null, restoreTo && (this.activeImplementorAgent = restoreTo.agent)), this.singleTaskContextByTask.delete(taskId), this.startTasksInFlight.delete(taskId);
58388
+ wasNewest && (this.activeTaskId = restoreTo?.taskId ?? null, this.activeBrief = restoreTo?.brief ?? null, restoreTo && (this.activeImplementorAgent = restoreTo.agent)), this.singleTaskContextByTask.delete(taskId), this.webAccessByTask.delete(taskId), this.startTasksInFlight.delete(taskId);
58310
58389
  for (let i = this.pendingGateDispatches.length - 1; i >= 0; i--) {
58311
58390
  let pending = this.pendingGateDispatches[i];
58312
58391
  (pending.envelopeTaskId === taskId || !pending.envelopeTaskId && wasNewest) && this.pendingGateDispatches.splice(i, 1);
@@ -58942,8 +59021,12 @@ ${section}`);
58942
59021
  settlePending = resolve23;
58943
59022
  }),
58944
59023
  ...args.teamAuthority ? { teamAuthority: args.teamAuthority } : {}
58945
- };
58946
- if (process.env.CODEVIBE_ENABLE_AGENT_WEB === "1" && (args.agent === "CLAUDE" || args.agent === "ANTIGRAVITY"))
59024
+ }, needsWeb = this.webAccessByTask.get(args.taskId);
59025
+ if (needsWeb === void 0) {
59026
+ let originalPrompt = this.singleTaskContextByTask.get(args.taskId)?.brief ?? this.activeBriefByTask.get(args.taskId)?.brief ?? (args.roundNumber === 0 ? args.brief : this.activeBrief ?? "");
59027
+ needsWeb = isWebAccessNeeded(originalPrompt), this.webAccessByTask.set(args.taskId, needsWeb);
59028
+ }
59029
+ if (needsWeb && (args.agent === "CLAUDE" || args.agent === "ANTIGRAVITY"))
58947
59030
  try {
58948
59031
  agentWebTurn = await startAgentWebTurn({
58949
59032
  sessionId: this.deps.session.sessionId,
@@ -58981,6 +59064,7 @@ ${section}`);
58981
59064
  // (`setActiveTaskId`), so the LE's `args.taskId ?? this.taskId` fallback
58982
59065
  // yields byte-identical single-impl substrate engagement.
58983
59066
  taskId: args.taskId,
59067
+ webTurnActive: !!agentWebTurn?.mcpConfigPath,
58984
59068
  timeoutMs: null,
58985
59069
  stdinTty: !0,
58986
59070
  // PHASE-CP-10-MIN (#585) — the cooperative abort signal (#C10M-16).
@@ -60203,7 +60287,7 @@ ${section}`);
60203
60287
  ));
60204
60288
  }
60205
60289
  for (let taskId of taskIds)
60206
- this.activeBriefByTask.get(taskId)?.taskGroupId === taskGroupId && (this.activeBriefByTask.delete(taskId), this.activeAttachmentsByTask.delete(taskId));
60290
+ this.activeBriefByTask.get(taskId)?.taskGroupId === taskGroupId && (this.activeBriefByTask.delete(taskId), this.activeAttachmentsByTask.delete(taskId), this.webAccessByTask.delete(taskId));
60207
60291
  if (this.teamAttachmentsByGroup.delete(taskGroupId), failures.length > 0) {
60208
60292
  try {
60209
60293
  this.surfaceHalt(
@@ -60281,7 +60365,7 @@ ${section}`);
60281
60365
  });
60282
60366
  return;
60283
60367
  }
60284
- if (opts?.preserveReviseContext || (this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), opts?.taskContinues !== !0 && this.singleTaskContextByTask.delete(taskId), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId), appendContextItem(this.deps.session.sessionId, {
60368
+ if (opts?.preserveReviseContext || (this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), opts?.taskContinues !== !0 && (this.singleTaskContextByTask.delete(taskId), this.webAccessByTask.delete(taskId)), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId), appendContextItem(this.deps.session.sessionId, {
60285
60369
  kind: "verdict",
60286
60370
  author: { role: "engine" },
60287
60371
  sensitivity: "user",
@@ -60362,7 +60446,7 @@ ${section}`);
60362
60446
  error: err?.message
60363
60447
  }), historySnapshot = null;
60364
60448
  }
60365
- this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), this.singleTaskContextByTask.delete(taskId), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId);
60449
+ this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), this.singleTaskContextByTask.delete(taskId), this.webAccessByTask.delete(taskId), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId);
60366
60450
  let shadow = this.shadowsByTask.get(taskId);
60367
60451
  if (!shadow) {
60368
60452
  logger.info("[QuorumLoop] promote no-op \u2014 no in-memory shadow for task", { taskId });
@@ -68776,7 +68860,10 @@ async function handleShellUserInput(deps) {
68776
68860
  }, decision, showClassifySpinner = store.getState().progress === null;
68777
68861
  showClassifySpinner && store.dispatch({ type: "TASK_PROGRESS", event: { phase: "planner_classifying" } });
68778
68862
  try {
68779
- decision = await dispatchClassify(plannerInput), isLocalPlannerRuntime(args) && decision.action === "start_task" && plannerInput.clarifications.length === 0 && isDestructiveFileRequest(plannerInput.prompt) && (logger.warn(
68863
+ decision = await dispatchClassify(plannerInput), isLocalPlannerRuntime(args) && decision.action === "browse" && hasFileMutationIntent(plannerInput.prompt) && (decision = {
68864
+ action: "start_task",
68865
+ rationale: decision.rationale ? `${decision.rationale} (promoted to start_task due to file mutation intent)` : "web request with file mutation intent"
68866
+ }), isLocalPlannerRuntime(args) && decision.action === "start_task" && plannerInput.clarifications.length === 0 && isDestructiveFileRequest(plannerInput.prompt) && (logger.warn(
68780
68867
  "[orchestration-shell] P43 backstop: a destructive file request was classified start_task without a confirmation this turn; asking first",
68781
68868
  { rationale: decision.rationale }
68782
68869
  ), decision = {
@@ -72365,6 +72452,14 @@ async function assertAuditOutsideWorkdir(auditPath, workdir) {
72365
72452
  return resolvedAuditPath;
72366
72453
  }
72367
72454
  async function engageSubstrate(input) {
72455
+ if (input.webBrowsingActive)
72456
+ return {
72457
+ mode: "reduced_trust",
72458
+ reducedTrust: !0,
72459
+ reducedTrustReason: `Agent web browsing active for ${input.agentKind} \u2014 running in reduced-trust mode with shadow-diff capture and guarded web tools`,
72460
+ teardown: async () => {
72461
+ }
72462
+ };
72368
72463
  let provider = providerForAgent(input.agentKind);
72369
72464
  if (provider === null)
72370
72465
  return input.requireConfined ? engageWriteConfinedResolverSandbox(input) : {
@@ -74904,6 +74999,7 @@ async function buildQuorumLoopExecutor(args) {
74904
74999
  userContextFn: async () => ""
74905
75000
  }, localExecutor = new LocalExecutorImpl({
74906
75001
  sessionId: session.sessionId,
75002
+ logger,
74907
75003
  // CP-1.f Hybrid authority split (mirrors the team LE) — the DESKTOP seeds
74908
75004
  // the implementor command allowlist so `enforceCommand` permits the spawn;
74909
75005
  // write/read/network stay empty (the engine authorizes PATH scopes via a
@@ -75145,6 +75241,7 @@ async function buildTeamLocalExecutor(args) {
75145
75241
  userContextFn: async () => ""
75146
75242
  }, localExecutor = new LocalExecutorImpl({
75147
75243
  sessionId: session.sessionId,
75244
+ logger,
75148
75245
  initialScope,
75149
75246
  baseCtx: {
75150
75247
  sessionId: session.sessionId,
@@ -53,4 +53,22 @@ export declare function extractAllAgentMentionIntents(text: string): AgentMentio
53
53
  export declare function stripLeadingAgentControlToken(text: string): string;
54
54
  export declare function buildCommandIntentEnvelope(intent: AgentMentionIntent): CommandIntentEnvelope;
55
55
  export declare function buildCommandIntentMetadata(text: string): CommandIntentMetadata | undefined;
56
+ /**
57
+ * Detects whether a prompt or brief expresses need for external web browsing or online searching.
58
+ * Used by QuorumLoop to dynamically attach web tools ONLY when needed, preserving full
59
+ * sandboxing for normal non-web tasks.
60
+ *
61
+ * Supports language-agnostic URLs, as well as English, Chinese, and Japanese web search intents.
62
+ */
63
+ export declare function isWebAccessNeeded(text: string): boolean;
64
+ /**
65
+ * Detects whether a prompt expresses intent to create, write, save, or modify local files/reports,
66
+ * as opposed to a purely read-only conversational request.
67
+ * Used as a backstop when classifying or routing web requests so that prompts asking to fetch/search
68
+ * AND write/save to a local file route to `start_task` (implementor execution) instead of desktop `browse`.
69
+ *
70
+ * Designed to handle English, Chinese, Japanese, Korean, European languages, and universal structural patterns
71
+ * (e.g. file targets and shell redirection), while accurately filtering out informational "how-to" / conceptual questions.
72
+ */
73
+ export declare function hasFileMutationIntent(text: string): boolean;
56
74
  export {};
@@ -690,6 +690,7 @@ export declare class QuorumLoop {
690
690
  * are removed with the task's other per-task state at its terminal cleanup.
691
691
  */
692
692
  private readonly singleTaskContextByTask;
693
+ private readonly webAccessByTask;
693
694
  /**
694
695
  * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
695
696
  * user-requested changes for a task, accumulated across ALL revise rounds.
@@ -80,6 +80,8 @@ export interface EngageSubstrateInput {
80
80
  claudeScratchHostRoot?: string;
81
81
  /** Safe locale source for the sanitized env (default: none). */
82
82
  localeSource?: Readonly<Record<string, string | undefined>>;
83
+ /** If true, the turn has active web tools (MCP bridge) and runs in reduced-trust mode. */
84
+ webBrowsingActive?: boolean;
83
85
  }
84
86
  /**
85
87
  * The substrate-engaged result: the spawn seam runs the agent via
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.36",
3
+ "version": "2.0.37",
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.41",
3
+ "version": "2.0.42",
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.36",
51
+ "@quantiya/codevibe-core": "^2.0.37",
52
52
  "@quantiya/quorum-core": "^1.0.1",
53
53
  "dotenv": "^16.6.1",
54
54
  "express": "^5.1.0",