@viberaven/cli 1.1.13 → 1.1.14

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.
Files changed (3) hide show
  1. package/dist/cli.js +1236 -188
  2. package/dist/cli.js.map +3 -3
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -545,12 +545,12 @@ function createAppWindowOpenCommands(target, platform = process.platform, env =
545
545
  }
546
546
  async function openWithSystemDefault(target) {
547
547
  const { command, args, shell } = createOpenCommand(target);
548
- await new Promise((resolve6, reject) => {
548
+ await new Promise((resolve7, reject) => {
549
549
  const child = (0, import_node_child_process.spawn)(command, args, { stdio: "ignore", shell });
550
550
  child.on("error", reject);
551
551
  child.on("exit", (code) => {
552
552
  if (code === 0) {
553
- resolve6();
553
+ resolve7();
554
554
  } else {
555
555
  reject(new Error(`Could not open browser (exit ${code ?? "unknown"}). Open manually: ${target}`));
556
556
  }
@@ -561,7 +561,7 @@ async function spawnDetached(command) {
561
561
  if (/^[A-Za-z]:[\\/]/.test(command.command) && !(0, import_node_fs2.existsSync)(command.command)) {
562
562
  throw new Error(`Browser not found: ${command.command}`);
563
563
  }
564
- await new Promise((resolve6, reject) => {
564
+ await new Promise((resolve7, reject) => {
565
565
  const child = (0, import_node_child_process.spawn)(command.command, command.args, {
566
566
  detached: true,
567
567
  stdio: "ignore",
@@ -569,7 +569,7 @@ async function spawnDetached(command) {
569
569
  });
570
570
  child.once("spawn", () => {
571
571
  child.unref();
572
- resolve6();
572
+ resolve7();
573
573
  });
574
574
  child.once("error", reject);
575
575
  });
@@ -638,7 +638,7 @@ var init_commands = __esm({
638
638
 
639
639
  // src/auth.ts
640
640
  function sleep(ms) {
641
- return new Promise((resolve6) => setTimeout(resolve6, ms));
641
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
642
642
  }
643
643
  async function runDeviceLogin(apiBaseUrl) {
644
644
  const signIn = await startManagedSignIn(apiBaseUrl);
@@ -10442,12 +10442,12 @@ async function copyToClipboard(text) {
10442
10442
  }
10443
10443
  }
10444
10444
  function pipeToCommand(command, text, extraArgs = []) {
10445
- return new Promise((resolve6, reject) => {
10445
+ return new Promise((resolve7, reject) => {
10446
10446
  const child = (0, import_node_child_process2.spawn)(command, extraArgs, { stdio: ["pipe", "ignore", "ignore"] });
10447
10447
  child.on("error", reject);
10448
10448
  child.on("close", (code) => {
10449
10449
  if (code === 0) {
10450
- resolve6();
10450
+ resolve7();
10451
10451
  } else {
10452
10452
  reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
10453
10453
  }
@@ -10925,10 +10925,10 @@ function sleepForRunnerPoll(ms, signal) {
10925
10925
  if (signal?.aborted) {
10926
10926
  return Promise.reject(createRunnerWatchAbortError());
10927
10927
  }
10928
- return new Promise((resolve6, reject) => {
10928
+ return new Promise((resolve7, reject) => {
10929
10929
  const timer = setTimeout(() => {
10930
10930
  cleanup();
10931
- resolve6();
10931
+ resolve7();
10932
10932
  }, ms);
10933
10933
  const onAbort = () => {
10934
10934
  cleanup();
@@ -11500,9 +11500,9 @@ function formatRepoMatch(repoMatch) {
11500
11500
  }
11501
11501
  }
11502
11502
  async function runCommand(command, args, cwd) {
11503
- return new Promise((resolve6) => {
11503
+ return new Promise((resolve7) => {
11504
11504
  (0, import_node_child_process3.execFile)(command, args, { cwd, windowsHide: true }, (error, stdout, stderr) => {
11505
- resolve6({
11505
+ resolve7({
11506
11506
  ok: !error,
11507
11507
  stdout: String(stdout ?? ""),
11508
11508
  stderr: String(stderr ?? "")
@@ -12306,7 +12306,7 @@ var VERSION;
12306
12306
  var init_version = __esm({
12307
12307
  "src/version.ts"() {
12308
12308
  "use strict";
12309
- VERSION = "1.1.13";
12309
+ VERSION = "1.1.14";
12310
12310
  }
12311
12311
  });
12312
12312
 
@@ -16921,6 +16921,56 @@ function emptyLocalUiState(cwd) {
16921
16921
  ...state.project,
16922
16922
  name: "Project"
16923
16923
  },
16924
+ gate: {
16925
+ ...state.gate,
16926
+ status: "unknown",
16927
+ label: "Waiting for project evidence"
16928
+ },
16929
+ providers: state.providers.map((provider2) => ({
16930
+ ...provider2,
16931
+ state: "not_detected",
16932
+ statusText: "Add when needed",
16933
+ connectLabel: "Add provider",
16934
+ launchPath: provider2.launchPath.map((item4) => ({
16935
+ ...item4,
16936
+ state: "not_checked",
16937
+ shortReason: "Waiting for local evidence"
16938
+ })),
16939
+ selectedItemId: provider2.launchPath[0]?.id,
16940
+ nextFix: provider2.nextFix ? {
16941
+ ...provider2.nextFix,
16942
+ title: `Add ${provider2.name} when your app needs it`,
16943
+ whyItMatters: `${provider2.area} is available as a production slot. VibeRaven will use repo evidence before asking for live proof.`,
16944
+ whatToChange: `Drag ${provider2.name} into chat or open the slot when this project uses it.`,
16945
+ verifyInstruction: "Connect a coding CLI, add project context, then ask VibeRaven for the next safe step."
16946
+ } : provider2.nextFix
16947
+ })),
16948
+ releases: [
16949
+ {
16950
+ id: "v1.3.0",
16951
+ label: "v1.3.0",
16952
+ meta: "Local fixture",
16953
+ branch: "main",
16954
+ tone: "current",
16955
+ summary: "Client interaction fixture. The local server replaces this with real project release context."
16956
+ },
16957
+ {
16958
+ id: "v1.2.2",
16959
+ label: "v1.2.2",
16960
+ meta: "Previous fixture",
16961
+ branch: "main",
16962
+ tone: "prod",
16963
+ summary: "Previous release fixture for UI interaction tests."
16964
+ },
16965
+ {
16966
+ id: "workspace",
16967
+ label: "Workspace",
16968
+ meta: "Not released",
16969
+ branch: "local",
16970
+ tone: "planned",
16971
+ summary: "No git release was detected yet."
16972
+ }
16973
+ ],
16924
16974
  empty: true
16925
16975
  };
16926
16976
  }
@@ -17014,12 +17064,16 @@ const demo = {
17014
17064
  reasoningMenuOpen: false,
17015
17065
  contextMenuOpen: false,
17016
17066
  pickerChatIndex: 0,
17017
- chatContexts: [{ providerId: null, releaseId: null }],
17018
- activeRecentChatId: 'rollout',
17067
+ chatContexts: [{ providerId: null, releaseId: null, cliId: 'codex', modelId: 'gpt-5.5', reasoning: 'High', context: 'Production' }],
17068
+ activeRecentChatId: null,
17019
17069
  chatTaskKind: null,
17020
17070
  chatTaskPhase: 'idle',
17021
17071
  chatTaskPrompt: '',
17072
+ chatTaskResponse: '',
17073
+ chatTaskError: '',
17022
17074
  chatTaskProviderId: null,
17075
+ chatTaskReleaseId: null,
17076
+ chatTaskChatIndex: 0,
17023
17077
  droppedProviderId: null,
17024
17078
  dragKind: null,
17025
17079
  dragProviderId: null,
@@ -17040,6 +17094,7 @@ const providerAssets = {
17040
17094
  sentry: '/report/assets/provider-sentry.png',
17041
17095
  posthog: '/report/assets/provider-posthog.png',
17042
17096
  clerk: '/report/assets/provider-clerk.png',
17097
+ authjs: '/report/assets/provider-clerk.png',
17043
17098
  resend: '/report/assets/provider-resend.png',
17044
17099
  upstash: '/report/assets/provider-upstash.png',
17045
17100
  };
@@ -17052,6 +17107,7 @@ const providerMarkAssets = {
17052
17107
  sentry: '/report/assets/provider-sentry-mark.svg',
17053
17108
  posthog: '/report/assets/provider-posthog-mark.svg',
17054
17109
  clerk: '/report/assets/provider-clerk-mark.svg',
17110
+ authjs: '/report/assets/provider-clerk-mark.svg',
17055
17111
  resend: '/report/assets/provider-resend-mark.svg',
17056
17112
  upstash: '/report/assets/provider-upstash-mark.svg',
17057
17113
  };
@@ -17071,6 +17127,7 @@ const providerDashboardUrls = {
17071
17127
  sentry: 'https://sentry.io',
17072
17128
  posthog: 'https://app.posthog.com',
17073
17129
  clerk: 'https://dashboard.clerk.com',
17130
+ authjs: 'https://authjs.dev',
17074
17131
  resend: 'https://resend.com/emails',
17075
17132
  upstash: 'https://console.upstash.com',
17076
17133
  };
@@ -17092,25 +17149,61 @@ const githubStats = {
17092
17149
  let lastRenderedModal = demo.activeModal;
17093
17150
  let chatTaskTimer = null;
17094
17151
 
17095
- const providers = [
17096
- { id: 'supabase', name: 'Supabase', area: 'Database', status: 'Connected', tone: 'green', detail: 'RLS evidence found. Migrations and storage checks are ready.', region: 'us-east-1', lastSync: '2m ago', checks: '8/8' },
17097
- { id: 'vercel', name: 'Vercel', area: 'Cloud / Hosting', status: 'Connected', tone: 'green', detail: 'Production build target, env review, and preview flow are linked.', region: 'iad1', lastSync: '5m ago', checks: '6/6' },
17098
- { id: 'stripe', name: 'Stripe', area: 'Payments', status: 'Connected', tone: 'green', detail: 'Billing mode is detected. Webhook proof is ready for final verification.', region: 'global', lastSync: '8m ago', checks: '5/5' },
17099
- { id: 'github', name: 'GitHub', area: 'Version Control', status: 'Connected', tone: 'green', detail: 'Main branch is protected and release evidence is available.', region: 'main', lastSync: '1m ago', checks: '7/7' },
17100
- { id: 'sentry', name: 'Sentry', area: 'Monitoring', status: 'Connected', tone: 'green', detail: 'Monitoring provider is connected. Error capture proof is ready.', region: 'global', lastSync: '4m ago', checks: '4/4' },
17101
- { id: 'posthog', name: 'PostHog', area: 'Analytics', status: 'Connected', tone: 'green', detail: 'Launch events and product signals are detected.', region: 'eu', lastSync: '6m ago', checks: '4/4' },
17102
- { id: 'clerk', name: 'Clerk', area: 'Auth', status: 'Connected', tone: 'green', detail: 'Auth provider proof is available. Redirect and session checks are ready.', region: 'global', lastSync: '7m ago', checks: '6/6' },
17103
- { id: 'resend', name: 'Resend', area: 'Email', status: 'Connected', tone: 'green', detail: 'Sender identity and delivery proof are connected.', region: 'global', lastSync: '9m ago', checks: '3/3' },
17104
- { id: 'upstash', name: 'Upstash', area: 'Queue / Cache', status: 'Connected', tone: 'green', detail: 'Quota and rate-limit provider is connected.', region: 'global', lastSync: '10m ago', checks: '3/3' },
17105
- ];
17152
+ function providerTone(state, status) {
17153
+ const text = String(status || '').toLowerCase();
17154
+ if (state === 'live_verified' || text.includes('connected') || text.includes('verified')) return 'green';
17155
+ if (state === 'needs_repo_fix' || state === 'blocked' || text.includes('fix') || text.includes('blocked')) return 'red';
17156
+ if (state === 'repo_evidence_found' || text.includes('detected')) return 'green';
17157
+ if (state === 'requires_user_action' || state === 'connect_live') return 'orange';
17158
+ return 'neutral';
17159
+ }
17106
17160
 
17107
- const releases = [
17108
- { id: 'v1.3.0', label: 'v1.3.0', meta: '8m ago', branch: 'main', tone: 'current' },
17109
- { id: 'v1.2.2', label: 'v1.2.2', meta: '2h ago', branch: 'main', tone: 'prod' },
17110
- { id: 'v1.2.1', label: 'v1.2.1', meta: '1d ago', branch: 'main', tone: 'prod' },
17111
- { id: 'v1.1.0', label: 'v1.1.0', meta: '3d ago', branch: 'main', tone: 'main' },
17112
- { id: 'v1.0.3', label: 'v1.0.3', meta: '1w ago', branch: 'release/v1.0', tone: 'planned' },
17113
- ];
17161
+ function providerChecks(provider) {
17162
+ const items = Array.isArray(provider.launchPath) ? provider.launchPath : [];
17163
+ if (!items.length) return provider.state === 'not_detected' ? '0/0' : '1/1';
17164
+ const ready = items.filter((item) => item.state === 'ready').length;
17165
+ return ready + '/' + items.length;
17166
+ }
17167
+
17168
+ function providerDetail(provider) {
17169
+ if (provider.nextFix && provider.nextFix.whyItMatters) return provider.nextFix.whyItMatters;
17170
+ if (provider.cockpit && provider.cockpit.proof && provider.cockpit.proof.summary) return provider.cockpit.proof.summary;
17171
+ if (provider.state === 'not_detected') return provider.area + ' is available as a production slot. Add it when your app uses ' + provider.name + '.';
17172
+ return provider.area + ' evidence was detected in this project. Drag this slot into chat for a scoped production check.';
17173
+ }
17174
+
17175
+ function normalizeProvider(provider) {
17176
+ return {
17177
+ id: provider.id,
17178
+ name: provider.name,
17179
+ area: provider.area,
17180
+ status: provider.statusText || (provider.state === 'not_detected' ? 'Add when needed' : 'Detected in repo'),
17181
+ tone: providerTone(provider.state, provider.statusText),
17182
+ detail: providerDetail(provider),
17183
+ region: provider.state === 'not_detected' ? 'not linked' : 'local repo',
17184
+ lastSync: currentState.project && currentState.project.scannedAt ? 'scan artifact' : 'startup',
17185
+ checks: providerChecks(provider),
17186
+ state: provider.state || 'not_detected',
17187
+ };
17188
+ }
17189
+
17190
+ const providers = Array.isArray(currentState.providers) && currentState.providers.length
17191
+ ? currentState.providers.map(normalizeProvider)
17192
+ : [];
17193
+
17194
+ const releases = Array.isArray(currentState.releases) && currentState.releases.length
17195
+ ? currentState.releases.map((release, index) => ({
17196
+ id: release.id || release.label || 'workspace',
17197
+ label: release.label || release.id || 'Workspace',
17198
+ meta: release.meta || (index === 0 ? 'current workspace' : 'local history'),
17199
+ branch: release.branch || 'local',
17200
+ tone: release.tone || (index === 0 ? 'current' : 'prod'),
17201
+ summary: release.summary || '',
17202
+ }))
17203
+ : [{ id: 'workspace', label: 'Workspace', meta: 'not released', branch: 'local', tone: 'current', summary: 'No release detected yet.' }];
17204
+
17205
+ demo.selectedProviderId = currentState.selectedProviderId || (providers[0] && providers[0].id) || 'supabase';
17206
+ demo.selectedReleaseId = (releases[0] && releases[0].id) || 'workspace';
17114
17207
 
17115
17208
  let cliAgents = [
17116
17209
  { id: 'codex', label: 'Codex CLI', caption: 'Connect OpenAI agent', tone: 'green' },
@@ -17145,26 +17238,47 @@ const stackSlots = {
17145
17238
  sentry: 'Monitoring',
17146
17239
  posthog: 'Analytics',
17147
17240
  clerk: 'Auth',
17241
+ authjs: 'Auth',
17148
17242
  resend: 'Email',
17149
17243
  upstash: 'Cache',
17150
17244
  };
17151
17245
 
17152
- const providerBoardOrder = ['supabase', 'clerk', 'vercel', 'stripe', 'sentry', 'resend', 'posthog', 'upstash', 'github'];
17246
+ const providerBoardOrder = ['supabase', 'clerk', 'authjs', 'vercel', 'stripe', 'sentry', 'resend', 'posthog', 'upstash', 'github'];
17247
+
17248
+ function visibleBoardProviders() {
17249
+ const authProviders = providers.filter((provider) => provider.id === 'clerk' || provider.id === 'authjs');
17250
+ if (authProviders.length <= 1) return providers;
17251
+ const detectedAuth = authProviders.find((provider) => provider.state !== 'not_detected');
17252
+ const visibleAuth = detectedAuth || authProviders.find((provider) => provider.id === 'clerk') || authProviders[0];
17253
+ return providers.filter((provider) => provider.id !== 'clerk' && provider.id !== 'authjs').concat(visibleAuth);
17254
+ }
17153
17255
 
17154
17256
  function orderedProviders() {
17155
- return providerBoardOrder
17156
- .map((id) => providers.find((provider) => provider.id === id))
17257
+ const visible = visibleBoardProviders();
17258
+ const ordered = providerBoardOrder
17259
+ .map((id) => visible.find((provider) => provider.id === id))
17157
17260
  .filter(Boolean);
17261
+ return ordered.concat(visible.filter((provider) => !providerBoardOrder.includes(provider.id)));
17158
17262
  }
17159
17263
 
17160
- const recentChats = [
17161
- { id: 'rollout', title: 'Production rollout blockers', caption: 'Analyzing blockers and proposing fixes', time: '2m ago' },
17162
- { id: 'stripe-webhook', title: 'Stripe webhook issue', caption: 'Investigating payment failures', time: '1h ago' },
17163
- { id: 'auth-timeout', title: 'Auth timeout errors', caption: 'Debugging session expirations', time: '3h ago' },
17164
- { id: 'database-spikes', title: 'Database connection spikes', caption: 'Reviewing connection pool alerts', time: 'Yesterday' },
17165
- { id: 'lambda-cold-start', title: 'Lambda cold start latency', caption: 'Optimizing startup performance', time: '2d ago' },
17166
- { id: 'email-delivery', title: 'Email delivery failures', caption: 'SES bounce rate analysis', time: '3d ago' },
17167
- ];
17264
+ let recentChats = [];
17265
+
17266
+ function rememberRecentChat(prompt, provider, release) {
17267
+ const text = String(prompt || '').trim();
17268
+ if (!text) return;
17269
+ const title = text.length > 38 ? text.slice(0, 35).trim() + '...' : text;
17270
+ const context = provider ? (stackSlots[provider.id] || provider.area) + ' / ' + provider.name : release ? 'Release / ' + release.label : 'Production';
17271
+ const entry = {
17272
+ id: 'chat-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7),
17273
+ title,
17274
+ caption: context,
17275
+ time: 'Just now',
17276
+ providerId: provider?.id || null,
17277
+ releaseId: release?.id || null
17278
+ };
17279
+ recentChats = [entry].concat(recentChats).slice(0, 6);
17280
+ demo.activeRecentChatId = entry.id;
17281
+ }
17168
17282
 
17169
17283
  function escapeHtml(value) {
17170
17284
  return String(value)
@@ -17330,9 +17444,30 @@ function selectedProvider() {
17330
17444
  return providers.find((item) => item.id === demo.selectedProviderId) || providers[0];
17331
17445
  }
17332
17446
 
17447
+ function defaultChatContext(seed) {
17448
+ const source = seed || {};
17449
+ const cliId = source.cliId || demo.selectedCliId || 'codex';
17450
+ const models = cliModels[cliId] || cliModels.codex;
17451
+ return {
17452
+ providerId: source.providerId || null,
17453
+ releaseId: source.releaseId || null,
17454
+ cliId,
17455
+ modelId: source.modelId || demo.selectedModelId || normalizeModelId(models[0]),
17456
+ reasoning: source.reasoning || demo.selectedReasoning || 'High',
17457
+ context: source.context || demo.selectedContext || 'Production',
17458
+ draft: source.draft || '',
17459
+ };
17460
+ }
17461
+
17333
17462
  function chatContext(index) {
17334
- if (!demo.chatContexts[index]) demo.chatContexts[index] = { providerId: null, releaseId: null };
17335
- return demo.chatContexts[index];
17463
+ const safeIndex = Math.max(0, Number(index) || 0);
17464
+ while (!demo.chatContexts[safeIndex]) {
17465
+ demo.chatContexts.push(defaultChatContext(demo.chatContexts[demo.chatContexts.length - 1]));
17466
+ }
17467
+ const context = demo.chatContexts[safeIndex];
17468
+ const hydrated = defaultChatContext(context);
17469
+ Object.assign(context, hydrated);
17470
+ return context;
17336
17471
  }
17337
17472
 
17338
17473
  function syncPrimaryContext() {
@@ -17398,25 +17533,43 @@ function taskMeta(kind) {
17398
17533
  return base[kind] || base.analyze;
17399
17534
  }
17400
17535
 
17401
- function runChatTask(kind) {
17536
+ function runChatTask(kind, promptOverride) {
17402
17537
  const meta = taskMeta(kind);
17403
17538
  if (chatTaskTimer) window.clearTimeout(chatTaskTimer);
17539
+ const chatIndex = Math.max(0, Math.min(demo.pickerChatIndex || 0, demo.activeChatCount - 1));
17540
+ const chat = chatContext(chatIndex);
17541
+ const attachedProviderId = chat.providerId || null;
17542
+ const attachedReleaseId = chat.releaseId || null;
17543
+ const providerId = attachedProviderId || demo.selectedProviderId;
17544
+ const releaseId = attachedReleaseId || demo.selectedReleaseId;
17545
+ const provider = providers.find((item) => item.id === providerId) || selectedProvider();
17546
+ const release = releases.find((item) => item.id === releaseId);
17547
+ const prompt = String(promptOverride || '').trim() || meta.prompt;
17404
17548
  demo.activeStudioTab = 'chat';
17405
- demo.activeRecentChatId = demo.activeRecentChatId || 'rollout';
17406
17549
  demo.activeChatCount = Math.max(1, demo.activeChatCount);
17407
17550
  demo.activeModal = null;
17408
17551
  demo.chatTaskKind = kind;
17409
17552
  demo.chatTaskPhase = 'working';
17410
- demo.chatTaskPrompt = meta.prompt;
17411
- demo.chatTaskProviderId = demo.selectedProviderId;
17553
+ demo.chatTaskPrompt = prompt;
17554
+ demo.chatTaskResponse = '';
17555
+ demo.chatTaskError = '';
17556
+ demo.chatTaskProviderId = attachedProviderId || (kind === 'provider' ? provider?.id : null);
17557
+ demo.chatTaskReleaseId = attachedReleaseId || null;
17558
+ demo.chatTaskChatIndex = chatIndex;
17559
+ rememberRecentChat(prompt, provider, release);
17560
+ chat.providerId = null;
17561
+ chat.releaseId = null;
17562
+ if (chatIndex === 0) syncPrimaryContext();
17412
17563
  demo.agentFixing = kind !== 'diff' && kind !== 'explain';
17413
17564
  demo.verified = kind === 'verify' ? false : demo.verified;
17414
17565
  demo.activeStepId = kind === 'verify' ? 'verify' : kind === 'provider' ? 'provider' : 'agent';
17415
17566
  demo.notice = meta.notice;
17416
17567
  render();
17417
- chatTaskTimer = window.setTimeout(() => {
17568
+ const finish = (message, isError) => {
17418
17569
  demo.chatTaskPhase = 'complete';
17419
17570
  demo.agentFixing = false;
17571
+ demo.chatTaskResponse = isError ? '' : (message || '');
17572
+ demo.chatTaskError = isError ? (message || 'The connected CLI did not return a usable response.') : '';
17420
17573
  if (kind === 'verify') {
17421
17574
  demo.verified = true;
17422
17575
  demo.activeStepId = 'clear';
@@ -17429,7 +17582,55 @@ function runChatTask(kind) {
17429
17582
  demo.notice = taskMeta(kind).title + ' finished in chat.';
17430
17583
  }
17431
17584
  render();
17432
- }, kind === 'verify' ? 1500 : 1250);
17585
+ };
17586
+ if (chat.cliId === 'terminal') {
17587
+ finish('Terminal chat execution is blocked. Use the Terminal tab for explicit commands.', true);
17588
+ return;
17589
+ }
17590
+ if (typeof fetch !== 'function') {
17591
+ chatTaskTimer = window.setTimeout(() => finish('', false), kind === 'verify' ? 1500 : 1250);
17592
+ return;
17593
+ }
17594
+ fetch('/api/agent-chat', {
17595
+ method: 'POST',
17596
+ headers: { 'content-type': 'application/json' },
17597
+ body: JSON.stringify({
17598
+ cliId: chat.cliId,
17599
+ modelId: selectedModelLabel(chatIndex),
17600
+ reasoning: chat.reasoning,
17601
+ context: chat.context,
17602
+ prompt,
17603
+ provider: { area: provider.area, name: provider.name },
17604
+ release: release ? { label: release.label } : undefined
17605
+ })
17606
+ })
17607
+ .then(async (response) => {
17608
+ if (!response.ok) throw new Error(await response.text());
17609
+ const payload = await response.json();
17610
+ const run = payload && payload.agentRun ? payload.agentRun : {};
17611
+ const output = String(run.output || run.stderr || '').trim();
17612
+ const failed = Boolean(run.timedOut || run.exitCode === null || Number(run.exitCode) !== 0);
17613
+ finish(output || 'The selected local CLI did not return output. Check that it is installed, signed in, and available on PATH.', failed || !output);
17614
+ })
17615
+ .catch((error) => {
17616
+ finish(error && error.message ? error.message : 'The connected CLI could not be reached.', true);
17617
+ });
17618
+ }
17619
+
17620
+ function sendChatInput(chatIndex) {
17621
+ const safeIndex = Math.max(0, Math.min(Number(chatIndex) || 0, demo.activeChatCount - 1));
17622
+ const input = app.querySelector('[data-chat-input="' + String(safeIndex) + '"]');
17623
+ const chat = chatContext(safeIndex);
17624
+ const value = String(chat.draft || (input && 'value' in input ? input.value : '') || '').trim();
17625
+ demo.pickerChatIndex = safeIndex;
17626
+ if (!value) {
17627
+ demo.notice = 'Type a mission first, or use a VibeRaven quick action.';
17628
+ render();
17629
+ return;
17630
+ }
17631
+ chat.draft = '';
17632
+ if (input && 'value' in input) input.value = '';
17633
+ runChatTask(chat.providerId ? 'provider' : 'analyze', value);
17433
17634
  }
17434
17635
 
17435
17636
  function resetLaunchPreview() {
@@ -17453,13 +17654,17 @@ function resetLaunchPreview() {
17453
17654
  demo.reasoningMenuOpen = false;
17454
17655
  demo.contextMenuOpen = false;
17455
17656
  demo.pickerChatIndex = 0;
17456
- demo.chatContexts = [{ providerId: null, releaseId: null }];
17657
+ demo.chatContexts = [defaultChatContext({ cliId: 'codex', modelId: 'gpt-5.5', reasoning: 'High', context: 'Production' })];
17457
17658
  demo.droppedProviderId = null;
17458
17659
  demo.droppedReleaseId = null;
17459
17660
  demo.chatTaskKind = null;
17460
17661
  demo.chatTaskPhase = 'idle';
17461
17662
  demo.chatTaskPrompt = '';
17663
+ demo.chatTaskResponse = '';
17664
+ demo.chatTaskError = '';
17462
17665
  demo.chatTaskProviderId = null;
17666
+ demo.chatTaskReleaseId = null;
17667
+ demo.chatTaskChatIndex = 0;
17463
17668
  demo.selectedEnvironment = 'us-east-1';
17464
17669
  demo.selectedBranch = 'main';
17465
17670
  demo.topbarMenuOpen = null;
@@ -17533,7 +17738,7 @@ function RecentChatRail() {
17533
17738
  '<span><strong>' + escapeHtml(chat.title) + '</strong><em>' + escapeHtml(chat.caption) + '</em></span>' +
17534
17739
  '<small>' + escapeHtml(chat.time) + '</small>' +
17535
17740
  '</button>'
17536
- ).join('') + '</div>' +
17741
+ ).join('') + (recentChats.length ? '' : '<p class="vr-recent-empty">No saved chats yet. Split a mission when you need another lane.</p>') + '</div>' +
17537
17742
  '<button class="vr-view-all-chats" type="button" data-action="view-all-chats">View all chats <b></b></button>' +
17538
17743
  '</aside>';
17539
17744
  }
@@ -17551,7 +17756,7 @@ function ProviderCard(provider) {
17551
17756
  function ProviderDetailPanel() {
17552
17757
  const provider = providers.find((item) => item.id === demo.selectedProviderId) || providers[0];
17553
17758
  return '<aside class="vr-provider-detail-panel">' +
17554
- '<div class="vr-detail-head"><span class="vr-provider-token is-large"><img src="' + (providerMarkAssets[provider.id] || providerAssets[provider.id]) + '" alt="" /></span><div><span>' + escapeHtml(provider.area) + '</span><strong>' + escapeHtml(provider.name) + '</strong></div>' + StatusBadge(provider.status, 'green') + '</div>' +
17759
+ '<div class="vr-detail-head"><span class="vr-provider-token is-large"><img src="' + (providerMarkAssets[provider.id] || providerAssets[provider.id]) + '" alt="" /></span><div><span>' + escapeHtml(provider.area) + '</span><strong>' + escapeHtml(provider.name) + '</strong></div>' + StatusBadge(provider.status, provider.tone) + '</div>' +
17555
17760
  '<p>' + escapeHtml(provider.detail) + '</p>' +
17556
17761
  '<div class="vr-provider-stats"><span><b>Checks</b><em>' + escapeHtml(provider.checks) + '</em></span><span><b>Region</b><em>' + escapeHtml(provider.region) + '</em></span><span><b>Last sync</b><em>' + escapeHtml(provider.lastSync) + '</em></span></div>' +
17557
17762
  '<a class="vr-provider-dashboard" href="' + escapeHtml(providerDashboardUrls[provider.id] || docsUrl) + '" target="_blank" rel="noreferrer">Open dashboard ' + Icon('external') + '</a>' +
@@ -17560,8 +17765,10 @@ function ProviderDetailPanel() {
17560
17765
 
17561
17766
  function ProviderGrid() {
17562
17767
  const boardProviders = orderedProviders();
17768
+ const activeCount = boardProviders.filter((provider) => provider.state !== 'not_detected').length;
17769
+ const summary = activeCount > 0 ? activeCount + ' production slot' + (activeCount === 1 ? '' : 's') + ' detected locally.' : 'No production providers detected yet.';
17563
17770
  return '<section class="vr-providers-panel" aria-label="Connected providers">' +
17564
- '<div class="vr-panel-head"><div><h2>Provider Control Board</h2><p><b>All systems operational</b> &middot; 9 production slots connected.</p></div><button class="vr-add-provider-inline" type="button" data-action="add-provider">' + Icon('plus') + 'Add Provider</button></div>' +
17771
+ '<div class="vr-panel-head"><div><h2>Provider Control Board</h2><p><b>Local project evidence</b> &middot; ' + escapeHtml(summary) + '</p></div><button class="vr-add-provider-inline" type="button" data-action="add-provider">' + Icon('plus') + 'Add Provider</button></div>' +
17565
17772
  (demo.providerPickerOpen ? ProviderPicker() : '') +
17566
17773
  '<div class="vr-provider-composition"><div class="vr-provider-grid" aria-label="Production provider slots">' + boardProviders.map(ProviderCard).join('') + '</div>' + ProviderDetailPanel() + '</div>' +
17567
17774
  '</section>';
@@ -17594,10 +17801,10 @@ function VersionTimeline() {
17594
17801
  '<div class="vr-panel-head"><div><h2>Versions & Releases</h2><p>Current release, rollback context, and changelog actions.</p></div></div>' +
17595
17802
  '<div class="vr-release-composition">' +
17596
17803
  '<button class="vr-release-card vr-release-current ' + (currentRelease.id === demo.selectedReleaseId ? 'is-selected' : '') + '" draggable="true" data-release="' + escapeHtml(currentRelease.id) + '" data-tone="' + escapeHtml(currentRelease.tone) + '" type="button">' +
17597
- '<small>Current Release</small><strong>' + escapeHtml(currentRelease.label) + '</strong><em>Deployed 2h ago by VibeRaven Agent</em><span>All checks passed</span>' +
17804
+ '<small>Current Release</small><strong>' + escapeHtml(currentRelease.label) + '</strong><em>' + escapeHtml(currentRelease.meta) + ' &middot; ' + escapeHtml(currentRelease.branch) + '</em><span>' + escapeHtml(currentRelease.summary || 'Local release context') + '</span>' +
17598
17805
  '</button>' +
17599
17806
  '<section class="vr-release-list"><header><strong>Recent Versions</strong><button type="button" data-action="all-releases">View all</button></header>' +
17600
- previousReleases.map((release) => '<button class="vr-release-row ' + (release.id === demo.selectedReleaseId ? 'is-selected' : '') + '" draggable="true" data-release="' + escapeHtml(release.id) + '" data-tone="' + escapeHtml(release.tone) + '" type="button"><span><strong>' + escapeHtml(release.label) + '</strong><em>All checks passed</em></span><small>' + escapeHtml(release.meta) + '</small></button>').join('') +
17807
+ (previousReleases.length > 0 ? previousReleases.map((release) => '<button class="vr-release-row ' + (release.id === demo.selectedReleaseId ? 'is-selected' : '') + '" draggable="true" data-release="' + escapeHtml(release.id) + '" data-tone="' + escapeHtml(release.tone) + '" type="button"><span><strong>' + escapeHtml(release.label) + '</strong><em>' + escapeHtml(release.summary || release.branch) + '</em></span><small>' + escapeHtml(release.meta) + '</small></button>').join('') : '<div class="vr-release-empty">No previous releases detected.</div>') +
17601
17808
  '</section>' +
17602
17809
  '</div>' +
17603
17810
  '<div class="vr-release-actions"><button type="button" data-action="compare">' + Icon('branch') + 'Compare Releases</button><button type="button" data-action="changelog">' + Icon('report') + 'View Changelog</button><button type="button" data-action="release">' + Icon('refresh') + 'Rollback</button></div>' +
@@ -17821,17 +18028,19 @@ function StudioTabButton(id, label) {
17821
18028
  return '<button class="' + (demo.activeStudioTab === id ? 'is-active' : '') + '" type="button" data-studio-tab="' + escapeHtml(id) + '">' + escapeHtml(label) + '</button>';
17822
18029
  }
17823
18030
 
17824
- function CliAgentButton(agent) {
18031
+ function CliAgentButton(agent, index) {
18032
+ const chat = chatContext(index);
17825
18033
  const connected = agent.connected !== false;
17826
- const label = demo.selectedCliId === agent.id && connected ? 'Active' : connected ? 'Use' : 'Connect';
17827
- return '<button class="' + (demo.selectedCliId === agent.id ? 'is-selected' : '') + '" type="button" data-cli-agent="' + escapeHtml(agent.id) + '" data-tone="' + escapeHtml(agent.tone) + '" data-connected="' + (connected ? 'true' : 'false') + '">' +
18034
+ const label = chat.cliId === agent.id && connected ? 'Active' : connected ? 'Use' : 'Connect';
18035
+ return '<button class="' + (chat.cliId === agent.id ? 'is-selected' : '') + '" type="button" data-cli-agent="' + escapeHtml(agent.id) + '" data-chat-index="' + String(index) + '" data-tone="' + escapeHtml(agent.tone) + '" data-connected="' + (connected ? 'true' : 'false') + '">' +
17828
18036
  '<span></span><strong>' + escapeHtml(agent.label) + '</strong><b>' + label + '</b>' +
17829
18037
  '</button>';
17830
18038
  }
17831
18039
 
17832
- function CliConnectPanel() {
18040
+ function CliConnectPanel(index) {
18041
+ const codingAgents = cliAgents.filter((agent) => agent.id !== 'terminal');
17833
18042
  return '<section class="vr-cli-connect" aria-label="Connect coding CLI">' +
17834
- '<div class="vr-cli-strip">' + cliAgents.map(CliAgentButton).join('') + '</div>' +
18043
+ '<div class="vr-cli-strip">' + codingAgents.map((agent) => CliAgentButton(agent, index)).join('') + '</div>' +
17835
18044
  '</section>';
17836
18045
  }
17837
18046
 
@@ -17839,23 +18048,24 @@ function normalizeModelId(label) {
17839
18048
  return String(label).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
17840
18049
  }
17841
18050
 
17842
- function allModelOptions(cliId) {
17843
- const selectedCli = cliId || demo.selectedCliId;
17844
- const connectedAgents = cliAgents.filter((item) => item.connected !== false);
18051
+ function allModelOptions(index) {
18052
+ const chat = chatContext(index);
18053
+ const selectedCli = chat.cliId || demo.selectedCliId;
18054
+ const connectedAgents = cliAgents.filter((item) => item.id !== 'terminal' && item.connected !== false);
17845
18055
  const agent = connectedAgents.find((item) => item.id === selectedCli) || connectedAgents[0] || cliAgents[0];
17846
18056
  const modelCliId = agent?.id || selectedCli;
17847
18057
  return (cliModels[modelCliId] || cliModels[selectedCli] || cliModels.codex).map((label) => ({
17848
18058
  cliId: modelCliId,
17849
18059
  label,
17850
18060
  id: normalizeModelId(label),
17851
- agentLabel: (agent?.label || 'CLI').replace(' CLI', '').replace(' Code', ''),
17852
18061
  }));
17853
18062
  }
17854
18063
 
17855
- function selectedModelLabel() {
17856
- const option = allModelOptions().find((item) => item.id === demo.selectedModelId);
18064
+ function selectedModelLabel(index) {
18065
+ const chat = chatContext(index || 0);
18066
+ const option = allModelOptions(index || 0).find((item) => item.id === chat.modelId);
17857
18067
  if (option) return option.label;
17858
- const models = cliModels[demo.selectedCliId] || cliModels.codex;
18068
+ const models = cliModels[chat.cliId] || cliModels.codex;
17859
18069
  return models[0];
17860
18070
  }
17861
18071
 
@@ -17889,6 +18099,13 @@ async function hydrateCliAgents() {
17889
18099
  if (!models.map(normalizeModelId).includes(demo.selectedModelId)) {
17890
18100
  demo.selectedModelId = normalizeModelId(models[0]);
17891
18101
  }
18102
+ demo.chatContexts.forEach((context, index) => {
18103
+ const chat = chatContext(index);
18104
+ const connected = connectedAgents.some((agent) => agent.id === chat.cliId);
18105
+ if (!connected || !cliModels[chat.cliId]) chat.cliId = selected;
18106
+ const chatModels = cliModels[chat.cliId] || models;
18107
+ if (!chatModels.map(normalizeModelId).includes(chat.modelId)) chat.modelId = normalizeModelId(chatModels[0]);
18108
+ });
17892
18109
  demo.cliConnected = cliAgents.some((agent) => agent.id === demo.selectedCliId && agent.connected !== false);
17893
18110
  render();
17894
18111
  } catch {
@@ -17897,25 +18114,22 @@ async function hydrateCliAgents() {
17897
18114
  }
17898
18115
 
17899
18116
  function ModelPicker(index) {
17900
- const models = allModelOptions();
17901
- const selectedModel = selectedModelLabel();
18117
+ const chat = chatContext(index);
18118
+ const models = allModelOptions(index);
18119
+ const selectedModel = selectedModelLabel(index);
17902
18120
  const scoped = demo.pickerChatIndex === index;
17903
- const connectedAgents = cliAgents.filter((agent) => agent.connected !== false);
17904
- const cliSourceRow = '<div class="vr-cli-source-row"><span>Connected CLI</span>' + connectedAgents.map((agent) =>
17905
- '<button class="' + (demo.selectedCliId === agent.id ? 'is-selected' : '') + '" type="button" data-cli-agent="' + escapeHtml(agent.id) + '" data-tone="' + escapeHtml(agent.tone) + '" data-connected="' + (agent.connected === false ? 'false' : 'true') + '">' + escapeHtml(agent.label.replace(' Code', '').replace(' CLI', '')) + '</button>'
17906
- ).join('') + (connectedAgents.length === 0 ? '<em>No local CLI detected</em>' : '') + '</div>';
17907
18121
  return '<div class="vr-model-picker">' +
17908
- '<button type="button" data-action="toggle-reasoning" data-chat-index="' + String(index) + '"><span>Reasoning</span><strong>' + escapeHtml(demo.selectedReasoning) + '</strong><i></i></button>' +
18122
+ '<button type="button" data-action="toggle-reasoning" data-chat-index="' + String(index) + '"><span>Reasoning</span><strong>' + escapeHtml(chat.reasoning) + '</strong><i></i></button>' +
17909
18123
  '<button type="button" data-action="toggle-models" data-chat-index="' + String(index) + '"><span>Model</span><strong>' + escapeHtml(selectedModel) + '</strong><i></i></button>' +
17910
- '<button type="button" data-action="toggle-context" data-chat-index="' + String(index) + '"><span>Context</span><strong>' + escapeHtml(demo.selectedContext) + '</strong><i></i></button>' +
18124
+ '<button type="button" data-action="toggle-context" data-chat-index="' + String(index) + '"><span>Context</span><strong>' + escapeHtml(chat.context) + '</strong><i></i></button>' +
17911
18125
  (scoped && demo.reasoningMenuOpen ? '<div class="vr-model-menu is-reasoning" role="menu"><p>Reasoning</p>' + reasoningLevels.map((level) =>
17912
- '<button type="button" data-reasoning="' + escapeHtml(level) + '">' + escapeHtml(level) + (demo.selectedReasoning === level ? '<b></b>' : '') + '</button>'
18126
+ '<button type="button" data-reasoning="' + escapeHtml(level) + '" data-chat-index="' + String(index) + '">' + escapeHtml(level) + (chat.reasoning === level ? '<b></b>' : '') + '</button>'
17913
18127
  ).join('') + '</div>' : '') +
17914
- (scoped && demo.modelMenuOpen ? '<div class="vr-model-menu is-models" role="menu"><p>Model</p>' + cliSourceRow + models.map((model) =>
17915
- '<button type="button" data-model="' + escapeHtml(model.id) + '" data-model-cli="' + escapeHtml(model.cliId) + '"><span><strong>' + escapeHtml(model.label) + '</strong><em>' + escapeHtml(model.agentLabel) + '</em></span>' + (selectedModel === model.label ? '<b></b>' : '') + '</button>'
18128
+ (scoped && demo.modelMenuOpen ? '<div class="vr-model-menu is-models" role="menu"><p>Model</p>' + models.map((model) =>
18129
+ '<button type="button" data-model="' + escapeHtml(model.id) + '" data-chat-index="' + String(index) + '"><span><strong>' + escapeHtml(model.label) + '</strong></span>' + (selectedModel === model.label ? '<b></b>' : '') + '</button>'
17916
18130
  ).join('') + '</div>' : '') +
17917
18131
  (scoped && demo.contextMenuOpen ? '<div class="vr-model-menu is-context" role="menu"><p>Context</p>' + contextLevels.map((level) =>
17918
- '<button type="button" data-context="' + escapeHtml(level) + '">' + escapeHtml(level) + (demo.selectedContext === level ? '<b></b>' : '') + '</button>'
18132
+ '<button type="button" data-context="' + escapeHtml(level) + '" data-chat-index="' + String(index) + '">' + escapeHtml(level) + (chat.context === level ? '<b></b>' : '') + '</button>'
17919
18133
  ).join('') + '</div>' : '') +
17920
18134
  '</div>';
17921
18135
  }
@@ -17951,25 +18165,87 @@ function ChatTaskAnimation() {
17951
18165
  if (!demo.chatTaskKind) return '';
17952
18166
  const meta = taskMeta(demo.chatTaskKind);
17953
18167
  const complete = demo.chatTaskPhase === 'complete';
17954
- const statusText = complete ? (demo.chatTaskKind === 'verify' ? 'Verified' : 'Done') : (demo.chatTaskKind === 'verify' ? 'Verifying...' : demo.chatTaskKind === 'diff' ? 'Reviewing...' : demo.chatTaskKind === 'explain' ? 'Explaining...' : 'Effecting...');
17955
- const thoughtText = complete ? 'finished' : 'thinking';
18168
+ if (complete) return ChatAgentResponse(meta, demo.chatTaskError || demo.chatTaskResponse, Boolean(demo.chatTaskError));
18169
+ const statusText = demo.chatTaskKind === 'verify' ? 'Verifying...' : demo.chatTaskKind === 'diff' ? 'Reviewing...' : demo.chatTaskKind === 'explain' ? 'Explaining...' : 'Effecting...';
18170
+ const thoughtText = 'thinking';
17956
18171
  return '<section class="vr-chat-agent-task ' + (complete ? 'is-complete' : 'is-working') + '" data-task="' + escapeHtml(demo.chatTaskKind) + '">' +
17957
18172
  '<header><span class="vr-task-glyph" aria-hidden="true"></span><div><strong>' + escapeHtml(statusText) + '</strong><em>(' + escapeHtml(thoughtText) + ' for launch)</em></div><b>' + escapeHtml(meta.title) + '</b></header>' +
17958
18173
  '<div class="vr-task-rail" aria-hidden="true"><span></span></div>' +
17959
- '<p>' + (complete ? (demo.chatTaskKind === 'verify' ? 'All checks are green. Review the diff before release.' : 'Ready for the next safe action.') : escapeHtml(meta.line)) + '</p>' +
18174
+ '<p>' + escapeHtml(meta.line) + '</p>' +
18175
+ '</section>';
18176
+ }
18177
+
18178
+ function formatAgentOutput(value) {
18179
+ let clean = String(value || '').replace(/\\r\\n/g, '\\n').trim();
18180
+ clean = clean
18181
+ .split('\\n')
18182
+ .filter((line) => !/^\\d{4}-\\d{2}-\\d{2}T.*\\s(WARN|ERROR)\\s/.test(line))
18183
+ .filter((line) => !/^(OpenAI Codex v|workdir:|model:|provider:|approval:|sandbox:|reasoning effort:|reasoning summaries:|session id:|--------$|user$)/.test(line.trim()))
18184
+ .join('\\n')
18185
+ .trim();
18186
+ if (/IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist/i.test(clean)) {
18187
+ return 'Gemini CLI is installed, but this account/client is not eligible for the current Gemini Code Assist CLI. Connect a supported Gemini CLI account, then retry this chat.';
18188
+ }
18189
+ if (/not running in a trusted directory|--skip-trust|GEMINI_CLI_TRUST_WORKSPACE/i.test(clean)) {
18190
+ return 'Gemini CLI needs this project trusted for headless chat. VibeRaven now passes the trust flag for Gemini runs; retry after your Gemini account is eligible.';
18191
+ }
18192
+ if (/command not found|not recognized as an internal or external command|ENOENT/i.test(clean)) {
18193
+ return 'The selected CLI is not available on PATH. Install it or sign in, then reconnect it from the chat header.';
18194
+ }
18195
+ if (/authentication|not logged in|login required|sign in/i.test(clean)) {
18196
+ return 'The selected CLI needs sign-in before VibeRaven can use it. Sign in with the CLI, then retry this mission.';
18197
+ }
18198
+ const missionIndex = clean.indexOf('User mission:');
18199
+ if (missionIndex >= 0) {
18200
+ const afterMission = clean.slice(missionIndex + 'User mission:'.length).trim();
18201
+ if (afterMission && afterMission.length < clean.length) clean = afterMission;
18202
+ }
18203
+ if (!clean) return 'Ready for the next safe action.';
18204
+ return clean.length > 2200 ? clean.slice(0, 2200) + '\\n\\n...output trimmed in Studio preview.' : clean;
18205
+ }
18206
+
18207
+ function ChatAgentResponse(meta, responseText, isError) {
18208
+ const provider = demo.chatTaskProviderId ? providers.find((item) => item.id === demo.chatTaskProviderId) : null;
18209
+ const release = demo.chatTaskReleaseId ? releases.find((item) => item.id === demo.chatTaskReleaseId) : null;
18210
+ const contextLine = provider || release
18211
+ ? '<div class="vr-agent-context-line">' +
18212
+ (provider ? '<span><img src="' + (providerMarkAssets[provider.id] || providerAssets[provider.id]) + '" alt="" /><b>' + escapeHtml(provider.area) + '</b><em>' + escapeHtml(provider.name) + '</em></span>' : '') +
18213
+ (release ? '<span><i>' + Icon('repo') + '</i><b>Version</b><em>' + escapeHtml(release.label) + '</em></span>' : '') +
18214
+ '</div>'
18215
+ : '';
18216
+ return '<section class="vr-chat-message is-agent vr-chat-agent-response ' + (isError ? 'is-error' : 'is-ready') + '">' +
18217
+ '<div><span><img src="' + mascotUrl() + '" alt="" /></span><p><strong>VibeRaven Agent</strong><em>Just now</em></p></div>' +
18218
+ '<div class="vr-agent-response-meta"><span class="vr-agent-thinking-label">' + escapeHtml(isError ? 'Needs attention' : meta.title) + '</span></div>' +
18219
+ contextLine +
18220
+ '<p class="vr-agent-answer-text">' + escapeHtml(formatAgentOutput(responseText)) + '</p>' +
18221
+ '<div class="vr-chat-response-actions">' +
18222
+ '<button type="button" data-action="show-diff">' + Icon('code') + '<span>Open Diff</span></button>' +
18223
+ '<button type="button" data-action="verify-now">' + Icon('shield') + '<span>Run Verify</span></button>' +
18224
+ '<button type="button" data-action="add-context">' + Icon('plus') + '<span>Add Context</span></button>' +
18225
+ '</div>' +
17960
18226
  '</section>';
17961
18227
  }
17962
18228
 
17963
- function ChatQuickActions() {
18229
+ function SentContextPills() {
18230
+ const provider = demo.chatTaskProviderId ? providers.find((item) => item.id === demo.chatTaskProviderId) : null;
18231
+ const release = demo.chatTaskReleaseId ? releases.find((item) => item.id === demo.chatTaskReleaseId) : null;
18232
+ if (!provider && !release) return '';
18233
+ return '<div class="vr-sent-context">' +
18234
+ (provider ? '<span><img src="' + (providerMarkAssets[provider.id] || providerAssets[provider.id]) + '" alt="" /><b>' + escapeHtml(provider.area) + '</b><em>' + escapeHtml(provider.name) + '</em></span>' : '') +
18235
+ (release ? '<span><i>' + Icon('repo') + '</i><b>Version</b><em>' + escapeHtml(release.label) + '</em></span>' : '') +
18236
+ '</div>';
18237
+ }
18238
+
18239
+ function ChatQuickActions(index) {
17964
18240
  return '<div class="vr-chat-inline-actions">' +
17965
- '<button type="button" data-action="plan">' + Icon('report') + 'Plan</button>' +
17966
- '<button type="button" data-action="start-agent-fix">' + Icon('agent') + 'Agent Fix</button>' +
17967
- '<button type="button" data-action="verify-now">' + Icon('shield') + 'Run Verify</button>' +
17968
- '<button type="button" data-action="provider-proof">' + Icon('cube') + 'Provider Proof</button>' +
17969
- '<button type="button" data-action="show-diff">' + Icon('code') + 'Open Diff</button>' +
17970
- '<button type="button" data-action="explain-changes">' + Icon('prompt') + 'Explain Changes</button>' +
17971
- '<button type="button" data-action="add-context">' + Icon('plus') + 'Add Context</button>' +
17972
- '<button type="button" data-action="release">' + Icon('lock') + 'Release</button>' +
18241
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="plan">' + Icon('report') + 'Plan</button>' +
18242
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="start-agent-fix">' + Icon('agent') + 'Agent Fix</button>' +
18243
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="verify-now">' + Icon('shield') + 'Run Verify</button>' +
18244
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="provider-proof">' + Icon('cube') + 'Provider Proof</button>' +
18245
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="show-diff">' + Icon('code') + 'Open Diff</button>' +
18246
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="explain-changes">' + Icon('prompt') + 'Explain Changes</button>' +
18247
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="add-context">' + Icon('plus') + 'Add Context</button>' +
18248
+ '<button type="button" data-chat-index="' + String(index) + '" data-action="release">' + Icon('lock') + 'Release</button>' +
17973
18249
  '</div>';
17974
18250
  }
17975
18251
 
@@ -17995,24 +18271,31 @@ function ContextDropZone(index, showAttached) {
17995
18271
 
17996
18272
  function StudioChatLane(index) {
17997
18273
  const provider = providers.find((item) => item.id === demo.selectedProviderId) || providers[0];
17998
- const agent = cliAgents.find((item) => item.id === demo.selectedCliId) || cliAgents[0];
17999
- const model = selectedModelLabel();
18274
+ const chat = chatContext(index);
18275
+ const agent = cliAgents.find((item) => item.id === chat.cliId) || cliAgents[0];
18276
+ const model = selectedModelLabel(index);
18000
18277
  const secondary = index > 0;
18001
- const fresh = !secondary && !demo.activeRecentChatId;
18002
- const primaryTask = !secondary && demo.chatTaskKind;
18003
- const sendAction = demo.agentFixing ? 'stop-agent' : 'start-agent-fix';
18278
+ const fresh = !secondary && !demo.activeRecentChatId && !demo.chatTaskKind && !chat.providerId && !chat.releaseId;
18279
+ const laneHasTask = demo.chatTaskKind && Number(demo.chatTaskChatIndex || 0) === index;
18280
+ const introBlock = fresh
18281
+ ? '<section class="vr-chat-empty-state"><strong>New mission chat</strong><span>Connect a CLI model, add provider context, then ask VibeRaven for the next safe launch step.</span></section>'
18282
+ : secondary
18283
+ ? '<p class="vr-chat-copy">This chat is ready for a focused release or provider follow-up.</p>' + ChatPlanCard({ id: provider.id, name: provider.name, area: 'Release' })
18284
+ : (laneHasTask ? '' : ChatMissionSummary(provider));
18285
+ const sendAction = demo.agentFixing ? 'stop-agent' : 'send-chat-message';
18004
18286
  const sendIcon = demo.agentFixing ? 'stop' : 'arrowUp';
18005
18287
  const sendLabel = demo.agentFixing ? 'Stop connected agent' : 'Send mission to connected agent';
18006
18288
  return '<article class="vr-chat-lane ' + (secondary ? 'is-secondary' : 'is-primary') + '" data-chat-index="' + String(index) + '">' +
18007
18289
  (secondary ? '<button class="vr-chat-close" type="button" data-action="close-chat" data-chat-index="' + String(index) + '" aria-label="Close chat">' + Icon('x') + '</button>' : '') +
18290
+ CliConnectPanel(index) +
18008
18291
  '<div class="vr-chat-transcript">' +
18009
18292
  '<section class="vr-chat-message is-agent"><div><span><img src="' + mascotUrl() + '" alt="" /></span><p><strong>VibeRaven Agent</strong><em>' + (fresh ? 'Ready' : secondary ? '1m ago' : '5m ago') + '</em></p></div><p>' + (fresh ? 'Start a production mission. Drag a provider or version into this chat, or ask what blocks launch.' : demo.agentFixing ? 'Applying the production fix and watching the evidence.' : demo.verified ? 'Verification is green. Review the diff, then prepare the release.' : 'I analyzed the production rollout and found 3 blockers preventing a safe deploy.') + '</p></section>' +
18010
- (fresh ? '<section class="vr-chat-empty-state"><strong>New mission chat</strong><span>Connect a CLI model, add provider context, then ask VibeRaven for the next safe launch step.</span></section>' : secondary ? '<p class="vr-chat-copy">This chat is ready for a focused release or provider follow-up.</p>' + ChatPlanCard({ id: provider.id, name: provider.name, area: 'Release' }) : ChatMissionSummary(provider)) +
18011
- (primaryTask ? '<section class="vr-chat-message is-user"><div><p><strong>You</strong><em>Just now</em></p></div><p>' + escapeHtml(demo.chatTaskPrompt) + '</p></section>' + ChatTaskAnimation() : '') +
18293
+ introBlock +
18294
+ (laneHasTask ? '<section class="vr-chat-message is-user"><p>' + escapeHtml(demo.chatTaskPrompt) + '</p>' + SentContextPills() + '</section>' + ChatTaskAnimation() : '') +
18012
18295
  '</div>' +
18013
18296
  '<footer class="vr-chat-command-bar">' +
18014
- ChatQuickActions() +
18015
- '<div class="vr-chat-composer" data-chat-drop="true" data-chat-index="' + String(index) + '"><span>Ask VibeRaven anything about this deployment...</span>' + ContextDropZone(index, true) + ModelPicker(index) + '<button class="vr-chat-send ' + (demo.agentFixing ? 'is-stop' : '') + '" type="button" data-action="' + sendAction + '" aria-label="' + sendLabel + '">' + Icon(sendIcon) + '</button></div>' +
18297
+ ChatQuickActions(index) +
18298
+ '<div class="vr-chat-composer" data-chat-drop="true" data-chat-index="' + String(index) + '"><textarea class="vr-chat-input" data-chat-input="' + String(index) + '" rows="2" placeholder="Ask VibeRaven anything about this deployment...">' + escapeHtml(chat.draft || '') + '</textarea>' + ContextDropZone(index, true) + ModelPicker(index) + '<button class="vr-chat-send ' + (demo.agentFixing ? 'is-stop' : '') + '" type="button" data-action="' + sendAction + '" data-chat-index="' + String(index) + '" aria-label="' + sendLabel + '">' + Icon(sendIcon) + '</button></div>' +
18016
18299
  '</footer>' +
18017
18300
  '</article>';
18018
18301
  }
@@ -18020,16 +18303,17 @@ function StudioChatLane(index) {
18020
18303
  function StudioChatView() {
18021
18304
  const lanes = Array.from({ length: demo.activeChatCount }, (_, index) => StudioChatLane(index)).join('');
18022
18305
  return '<section class="vr-studio-chat ' + (demo.activeChatCount > 1 ? 'is-split' : '') + (demo.dropActive ? ' is-drop-preview' : '') + '" data-chat-count="' + String(demo.activeChatCount) + '" aria-label="Production mission chat workspace">' +
18023
- CliConnectPanel() +
18024
18306
  '<div class="vr-chat-lanes" data-chat-drop="true">' + lanes + '</div>' +
18025
18307
  '</section>';
18026
18308
  }
18027
18309
 
18028
18310
  function StudioTerminalView() {
18029
- const agent = cliAgents.find((item) => item.id === demo.selectedCliId) || cliAgents[0];
18311
+ const chat = chatContext(demo.pickerChatIndex || 0);
18312
+ const agent = cliAgents.find((item) => item.id === chat.cliId) || cliAgents[0];
18030
18313
  return '<section class="vr-studio-terminal" aria-label="Connected agent terminal">' +
18031
18314
  '<header><strong>' + escapeHtml(agent.label) + '</strong><span>' + (demo.cliConnected ? 'connected to ' + escapeHtml(projectName()) : 'ready to connect') + '</span></header>' +
18032
18315
  '<pre><code>$ npx -y viberaven studio\\nVibeRaven Studio connected to ' + escapeHtml(projectName()) + '\\nagent: ' + escapeHtml(agent.label) + '\\nmission: fix selected launch gap only\\n\\n' + (demo.agentFixing ? '> applying scoped fix...\\n> updating provider evidence\\n> running verification checks' : demo.verified ? '> verification passed\\n> launch flow clear\\n> review git diff before release' : '> waiting for Apply Fix') + '</code></pre>' +
18316
+ '<div class="vr-terminal-compose"><textarea data-terminal-input rows="2" placeholder="Ask the connected CLI to inspect something safely...">' + escapeHtml(chat.draft || '') + '</textarea><button type="button" data-action="terminal-send">' + Icon('arrowUp') + 'Send to chat</button></div>' +
18033
18317
  '<footer><button type="button" data-action="show-diff">Show Diff</button><button type="button" data-action="verify-now">Run Verify</button><button type="button" data-action="stop-agent">Stop Agent</button></footer>' +
18034
18318
  '</section>';
18035
18319
  }
@@ -18038,7 +18322,7 @@ function StudioDiffView() {
18038
18322
  return '<section class="vr-studio-diff" aria-label="Live git diff preview">' +
18039
18323
  '<header><strong>Live Diff</strong><span>' + (demo.verified ? '+16 -2 verified' : demo.agentFixing ? '+7 -1 running' : 'No changes accepted yet') + '</span></header>' +
18040
18324
  '<pre><code><b>src/lib/monitoring.ts</b>\\n<span class="add">+ export function captureLaunchError(error) {</span>\\n<span class="add">+ if (process.env.SENTRY_DSN) Sentry.captureException(error)</span>\\n<span class="add">+ }</span>\\n\\n<b>.env.example</b>\\n<span class="add">+ SENTRY_DSN=</span>\\n<span class="del">- # monitoring optional</span></code></pre>' +
18041
- '<footer><button type="button" data-action="prompt">Edit Mission</button><button type="button" data-action="verify-now">Accept & Verify</button></footer>' +
18325
+ '<footer><button type="button" data-action="diff-to-chat">Explain in Chat</button><button type="button" data-action="verify-now">Accept & Verify</button></footer>' +
18042
18326
  '</section>';
18043
18327
  }
18044
18328
 
@@ -18047,7 +18331,7 @@ function StudioDock() {
18047
18331
  return '<section class="vr-studio-dock ' + (demo.agentFixing ? 'is-running' : '') + (demo.verified ? ' is-verified' : '') + '" aria-label="VibeRaven Studio agent workspace">' +
18048
18332
  '<div class="vr-studio-head">' +
18049
18333
  '<div><h2>VibeRaven Chat</h2></div>' +
18050
- '<div class="vr-studio-head-actions"><button class="vr-studio-add-chat" type="button" data-action="new-chat" aria-label="Start a new chat">' + Icon('plus') + 'New chat</button><div class="vr-studio-tabs">' + StudioTabButton('chat', 'Chat') + StudioTabButton('terminal', 'Terminal') + StudioTabButton('diff', 'Diff') + '</div></div>' +
18334
+ '<div class="vr-studio-head-actions"><button class="vr-studio-add-chat" type="button" data-action="split-chat" aria-label="Open a side-by-side chat">' + Icon('plus') + 'Split chat</button><div class="vr-studio-tabs">' + StudioTabButton('chat', 'Chat') + StudioTabButton('terminal', 'Terminal') + StudioTabButton('diff', 'Diff') + '</div></div>' +
18051
18335
  '</div>' +
18052
18336
  '<div class="vr-studio-body">' + view + '</div>' +
18053
18337
  '</section>';
@@ -18138,10 +18422,14 @@ async function loadGithubStats() {
18138
18422
  app.addEventListener('click', async (event) => {
18139
18423
  const recentChatButton = event.target.closest('[data-chat-id]');
18140
18424
  if (recentChatButton) {
18141
- demo.activeRecentChatId = recentChatButton.getAttribute('data-chat-id');
18425
+ const chatId = recentChatButton.getAttribute('data-chat-id');
18426
+ const recent = recentChats.find((chat) => chat.id === chatId);
18427
+ demo.activeRecentChatId = chatId;
18142
18428
  demo.activeChatCount = 1;
18429
+ demo.chatContexts = [defaultChatContext({ ...demo.chatContexts[0], providerId: recent?.providerId || null, releaseId: recent?.releaseId || null })];
18430
+ demo.pickerChatIndex = 0;
18143
18431
  demo.activeStudioTab = 'chat';
18144
- demo.notice = 'Recent production chat loaded into the mission workspace.';
18432
+ demo.notice = recent ? recent.title + ' loaded with saved context.' : 'Recent production chat loaded into the mission workspace.';
18145
18433
  render();
18146
18434
  return;
18147
18435
  }
@@ -18157,6 +18445,8 @@ app.addEventListener('click', async (event) => {
18157
18445
 
18158
18446
  const cliButton = event.target.closest('[data-cli-agent]');
18159
18447
  if (cliButton) {
18448
+ const chatIndex = Number(cliButton.getAttribute('data-chat-index') || String(demo.pickerChatIndex || 0));
18449
+ const chat = chatContext(chatIndex);
18160
18450
  if (cliButton.getAttribute('data-connected') === 'false') {
18161
18451
  demo.modelMenuOpen = false;
18162
18452
  demo.reasoningMenuOpen = false;
@@ -18165,12 +18455,15 @@ app.addEventListener('click', async (event) => {
18165
18455
  render();
18166
18456
  return;
18167
18457
  }
18168
- demo.selectedCliId = cliButton.getAttribute('data-cli-agent');
18458
+ chat.cliId = cliButton.getAttribute('data-cli-agent');
18459
+ demo.selectedCliId = chat.cliId;
18169
18460
  demo.cliConnected = true;
18170
- demo.activeStudioTab = demo.selectedCliId === 'terminal' ? 'terminal' : 'chat';
18461
+ demo.activeStudioTab = chat.cliId === 'terminal' ? 'terminal' : 'chat';
18171
18462
  demo.activeNavId = 'agents';
18172
- const models = cliModels[demo.selectedCliId] || cliModels.codex;
18173
- demo.selectedModelId = normalizeModelId(models[0]);
18463
+ const models = cliModels[chat.cliId] || cliModels.codex;
18464
+ chat.modelId = normalizeModelId(models[0]);
18465
+ demo.selectedModelId = chat.modelId;
18466
+ demo.pickerChatIndex = chatIndex;
18174
18467
  demo.modelMenuOpen = false;
18175
18468
  demo.reasoningMenuOpen = false;
18176
18469
  demo.contextMenuOpen = false;
@@ -18181,38 +18474,50 @@ app.addEventListener('click', async (event) => {
18181
18474
 
18182
18475
  const modelButton = event.target.closest('[data-model]');
18183
18476
  if (modelButton) {
18184
- demo.selectedModelId = modelButton.getAttribute('data-model');
18185
- demo.selectedCliId = modelButton.getAttribute('data-model-cli') || demo.selectedCliId;
18477
+ const chatIndex = Number(modelButton.getAttribute('data-chat-index') || String(demo.pickerChatIndex || 0));
18478
+ const chat = chatContext(chatIndex);
18479
+ chat.modelId = modelButton.getAttribute('data-model');
18480
+ demo.selectedModelId = chat.modelId;
18481
+ demo.selectedCliId = chat.cliId;
18186
18482
  demo.cliConnected = true;
18187
18483
  demo.modelMenuOpen = false;
18188
18484
  demo.reasoningMenuOpen = false;
18189
18485
  demo.contextMenuOpen = false;
18190
18486
  demo.activeStudioTab = 'chat';
18191
- demo.notice = selectedModelLabel() + ' selected for the connected agent.';
18487
+ demo.pickerChatIndex = chatIndex;
18488
+ demo.notice = selectedModelLabel(chatIndex) + ' selected for this chat.';
18192
18489
  render();
18193
18490
  return;
18194
18491
  }
18195
18492
 
18196
18493
  const reasoningButton = event.target.closest('[data-reasoning]');
18197
18494
  if (reasoningButton) {
18198
- demo.selectedReasoning = reasoningButton.getAttribute('data-reasoning');
18495
+ const chatIndex = Number(reasoningButton.getAttribute('data-chat-index') || String(demo.pickerChatIndex || 0));
18496
+ const chat = chatContext(chatIndex);
18497
+ chat.reasoning = reasoningButton.getAttribute('data-reasoning');
18498
+ demo.selectedReasoning = chat.reasoning;
18199
18499
  demo.reasoningMenuOpen = false;
18200
18500
  demo.modelMenuOpen = false;
18201
18501
  demo.contextMenuOpen = false;
18202
18502
  demo.activeStudioTab = 'chat';
18203
- demo.notice = demo.selectedReasoning + ' reasoning selected for production fix planning.';
18503
+ demo.pickerChatIndex = chatIndex;
18504
+ demo.notice = chat.reasoning + ' reasoning selected for this chat.';
18204
18505
  render();
18205
18506
  return;
18206
18507
  }
18207
18508
 
18208
18509
  const contextButton = event.target.closest('[data-context]');
18209
18510
  if (contextButton) {
18210
- demo.selectedContext = contextButton.getAttribute('data-context');
18511
+ const chatIndex = Number(contextButton.getAttribute('data-chat-index') || String(demo.pickerChatIndex || 0));
18512
+ const chat = chatContext(chatIndex);
18513
+ chat.context = contextButton.getAttribute('data-context');
18514
+ demo.selectedContext = chat.context;
18211
18515
  demo.contextMenuOpen = false;
18212
18516
  demo.modelMenuOpen = false;
18213
18517
  demo.reasoningMenuOpen = false;
18214
18518
  demo.activeStudioTab = 'chat';
18215
- demo.notice = demo.selectedContext + ' context selected for this VibeRaven mission.';
18519
+ demo.pickerChatIndex = chatIndex;
18520
+ demo.notice = chat.context + ' context selected for this chat.';
18216
18521
  render();
18217
18522
  return;
18218
18523
  }
@@ -18334,6 +18639,7 @@ app.addEventListener('click', async (event) => {
18334
18639
  const actionButton = event.target.closest('[data-action]');
18335
18640
  if (!actionButton) return;
18336
18641
  const action = actionButton.getAttribute('data-action');
18642
+ const actionChatIndex = Math.max(0, Math.min(Number(actionButton.getAttribute('data-chat-index') || actionButton.closest('.vr-chat-lane')?.getAttribute('data-chat-index') || demo.pickerChatIndex || 0) || 0, demo.activeChatCount - 1));
18337
18643
  if (action === 'close-modal') {
18338
18644
  demo.activeModal = null;
18339
18645
  render();
@@ -18410,18 +18716,22 @@ app.addEventListener('click', async (event) => {
18410
18716
  return;
18411
18717
  }
18412
18718
  if (action === 'chat-agent-fix' || action === 'start-agent-fix') {
18719
+ demo.pickerChatIndex = actionChatIndex;
18413
18720
  runChatTask('fix');
18414
18721
  return;
18415
18722
  }
18416
18723
  if (action === 'verify-now') {
18724
+ demo.pickerChatIndex = actionChatIndex;
18417
18725
  runChatTask('verify');
18418
18726
  return;
18419
18727
  }
18420
18728
  if (action === 'recheck') {
18729
+ demo.pickerChatIndex = actionChatIndex;
18421
18730
  runChatTask('verify');
18422
18731
  return;
18423
18732
  }
18424
18733
  if (action === 'show-diff') {
18734
+ demo.pickerChatIndex = actionChatIndex;
18425
18735
  runChatTask('diff');
18426
18736
  return;
18427
18737
  }
@@ -18434,13 +18744,41 @@ app.addEventListener('click', async (event) => {
18434
18744
  render();
18435
18745
  return;
18436
18746
  }
18747
+ if (action === 'send-chat-message') {
18748
+ const chatIndex = Number(actionButton.getAttribute('data-chat-index') || '0');
18749
+ sendChatInput(chatIndex);
18750
+ return;
18751
+ }
18752
+ if (action === 'terminal-send') {
18753
+ const chatIndex = Math.max(0, Math.min(demo.pickerChatIndex || 0, demo.activeChatCount - 1));
18754
+ const terminalInput = app.querySelector('[data-terminal-input]');
18755
+ const value = String((terminalInput && 'value' in terminalInput ? terminalInput.value : '') || '').trim();
18756
+ if (!value) {
18757
+ demo.notice = 'Type a terminal-safe mission first, then send it to chat.';
18758
+ render();
18759
+ return;
18760
+ }
18761
+ chatContext(chatIndex).draft = value;
18762
+ demo.activeStudioTab = 'chat';
18763
+ demo.notice = 'Terminal note moved into chat. Press send when ready.';
18764
+ render();
18765
+ return;
18766
+ }
18767
+ if (action === 'diff-to-chat') {
18768
+ const chatIndex = Math.max(0, Math.min(demo.pickerChatIndex || 0, demo.activeChatCount - 1));
18769
+ chatContext(chatIndex).draft = 'Explain this diff, identify production risk, and suggest the next safe verification step.';
18770
+ demo.activeStudioTab = 'chat';
18771
+ demo.notice = 'Diff question moved into chat.';
18772
+ render();
18773
+ return;
18774
+ }
18437
18775
  if (action === 'add-chat') {
18438
18776
  demo.activeStudioTab = 'chat';
18439
18777
  demo.activeChatCount = 1;
18440
18778
  demo.activeRecentChatId = null;
18441
18779
  demo.droppedProviderId = null;
18442
18780
  demo.droppedReleaseId = null;
18443
- demo.chatContexts = [{ providerId: null, releaseId: null }];
18781
+ demo.chatContexts = [defaultChatContext({ providerId: null, releaseId: null })];
18444
18782
  demo.pickerChatIndex = 0;
18445
18783
  demo.modelMenuOpen = false;
18446
18784
  demo.reasoningMenuOpen = false;
@@ -18450,6 +18788,33 @@ app.addEventListener('click', async (event) => {
18450
18788
  return;
18451
18789
  }
18452
18790
  if (action === 'new-chat') {
18791
+ demo.activeStudioTab = 'chat';
18792
+ demo.activeChatCount = 1;
18793
+ demo.activeRecentChatId = null;
18794
+ demo.chatContexts = [defaultChatContext({ providerId: null, releaseId: null })];
18795
+ demo.pickerChatIndex = 0;
18796
+ demo.dropActive = false;
18797
+ demo.dragKind = null;
18798
+ demo.dragProviderId = null;
18799
+ demo.dragReleaseId = null;
18800
+ demo.dragChatId = null;
18801
+ demo.agentFixing = false;
18802
+ demo.verified = false;
18803
+ demo.chatTaskKind = null;
18804
+ demo.chatTaskPhase = 'idle';
18805
+ demo.chatTaskPrompt = '';
18806
+ demo.chatTaskResponse = '';
18807
+ demo.chatTaskError = '';
18808
+ demo.chatTaskProviderId = null;
18809
+ demo.chatTaskReleaseId = null;
18810
+ demo.chatTaskChatIndex = 0;
18811
+ if (chatTaskTimer) window.clearTimeout(chatTaskTimer);
18812
+ setDragUi(null);
18813
+ demo.notice = 'New mission chat opened.';
18814
+ render();
18815
+ return;
18816
+ }
18817
+ if (action === 'split-chat') {
18453
18818
  demo.activeStudioTab = 'chat';
18454
18819
  demo.activeChatCount = Math.min(4, demo.activeChatCount + 1);
18455
18820
  demo.activeRecentChatId = null;
@@ -18465,10 +18830,14 @@ app.addEventListener('click', async (event) => {
18465
18830
  demo.chatTaskKind = null;
18466
18831
  demo.chatTaskPhase = 'idle';
18467
18832
  demo.chatTaskPrompt = '';
18833
+ demo.chatTaskResponse = '';
18834
+ demo.chatTaskError = '';
18468
18835
  demo.chatTaskProviderId = null;
18836
+ demo.chatTaskReleaseId = null;
18837
+ demo.chatTaskChatIndex = 0;
18469
18838
  if (chatTaskTimer) window.clearTimeout(chatTaskTimer);
18470
18839
  setDragUi(null);
18471
- demo.notice = demo.activeChatCount > 1 ? 'New split mission chat opened. Drag a provider or version into that lane.' : 'New mission chat opened.';
18840
+ demo.notice = 'New side-by-side mission chat opened. Drag a provider or version into that lane.';
18472
18841
  render();
18473
18842
  return;
18474
18843
  }
@@ -18488,6 +18857,7 @@ app.addEventListener('click', async (event) => {
18488
18857
  return;
18489
18858
  }
18490
18859
  if (action === 'guide') {
18860
+ demo.pickerChatIndex = actionChatIndex;
18491
18861
  runChatTask('analyze');
18492
18862
  return;
18493
18863
  }
@@ -18510,10 +18880,12 @@ app.addEventListener('click', async (event) => {
18510
18880
  return;
18511
18881
  }
18512
18882
  if (action === 'provider-proof') {
18883
+ demo.pickerChatIndex = actionChatIndex;
18513
18884
  runChatTask('provider');
18514
18885
  return;
18515
18886
  }
18516
18887
  if (action === 'plan') {
18888
+ demo.pickerChatIndex = actionChatIndex;
18517
18889
  runChatTask('plan');
18518
18890
  return;
18519
18891
  }
@@ -18566,6 +18938,24 @@ app.addEventListener('pointerdown', (event) => {
18566
18938
  }
18567
18939
  });
18568
18940
 
18941
+ app.addEventListener('input', (event) => {
18942
+ const input = event.target && event.target.closest ? event.target.closest('.vr-chat-input') : null;
18943
+ if (input) {
18944
+ const chatIndex = Number(input.getAttribute('data-chat-input') || '0');
18945
+ chatContext(chatIndex).draft = input.value || '';
18946
+ return;
18947
+ }
18948
+ const terminalInput = event.target && event.target.closest ? event.target.closest('[data-terminal-input]') : null;
18949
+ if (terminalInput) chatContext(demo.pickerChatIndex || 0).draft = terminalInput.value || '';
18950
+ });
18951
+
18952
+ app.addEventListener('keydown', (event) => {
18953
+ const input = event.target && event.target.closest ? event.target.closest('.vr-chat-input') : null;
18954
+ if (!input || event.key !== 'Enter' || event.shiftKey) return;
18955
+ event.preventDefault();
18956
+ sendChatInput(input.getAttribute('data-chat-input') || '0');
18957
+ });
18958
+
18569
18959
  app.addEventListener('dragstart', (event) => {
18570
18960
  const providerButton = event.target.closest('[data-provider]');
18571
18961
  const releaseButton = event.target.closest('[data-release]');
@@ -18589,7 +18979,7 @@ app.addEventListener('dragstart', (event) => {
18589
18979
  provider ? '<img src="' + escapeHtml(providerMarkAssets[provider.id] || providerAssets[provider.id]) + '" alt="" />' : ''
18590
18980
  );
18591
18981
  providerButton.classList.add('is-being-dragged');
18592
- demo.notice = 'Dragging provider context. Drop it into chat or on New Chat.';
18982
+ demo.notice = 'Dragging provider context. Drop it into a chat composer or Split chat.';
18593
18983
  setDragUi('provider');
18594
18984
  return;
18595
18985
  }
@@ -18627,7 +19017,7 @@ app.addEventListener('dragstart', (event) => {
18627
19017
  });
18628
19018
 
18629
19019
  app.addEventListener('dragover', (event) => {
18630
- const dropTarget = event.target.closest('[data-chat-drop], [data-action="new-chat"]');
19020
+ const dropTarget = event.target.closest('[data-chat-drop], [data-action="split-chat"]');
18631
19021
  if (!dropTarget) return;
18632
19022
  event.preventDefault();
18633
19023
  if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
@@ -18636,21 +19026,23 @@ app.addEventListener('dragover', (event) => {
18636
19026
  });
18637
19027
 
18638
19028
  app.addEventListener('dragleave', (event) => {
18639
- const dropTarget = event.target.closest('[data-chat-drop], [data-action="new-chat"]');
19029
+ const dropTarget = event.target.closest('[data-chat-drop], [data-action="split-chat"]');
18640
19030
  if (!dropTarget) return;
18641
19031
  dropTarget.classList.remove('is-drop-active');
18642
19032
  demo.dropActive = false;
18643
19033
  });
18644
19034
 
18645
19035
  app.addEventListener('drop', (event) => {
18646
- const newChatTarget = event.target.closest('[data-action="new-chat"]');
19036
+ const newChatTarget = event.target.closest('[data-action="split-chat"]');
18647
19037
  const dropTarget = event.target.closest('[data-chat-drop]');
18648
19038
  if (!newChatTarget && !dropTarget) return;
18649
19039
  event.preventDefault();
18650
19040
  (newChatTarget || dropTarget).classList.remove('is-drop-active');
18651
19041
  const targetLane = event.target.closest('.vr-chat-lane');
19042
+ const chatId = event.dataTransfer?.getData('text/plain').replace(/^chat:/, '') || (demo.dragKind === 'chat' ? demo.dragChatId : '');
19043
+ const isRecentChatDrop = Boolean(chatId && recentChats.some((chat) => chat.id === chatId));
18652
19044
  let newChatIndex = null;
18653
- if (newChatTarget) {
19045
+ if (newChatTarget || isRecentChatDrop) {
18654
19046
  demo.activeChatCount = Math.min(4, demo.activeChatCount + 1);
18655
19047
  newChatIndex = demo.activeChatCount - 1;
18656
19048
  chatContext(newChatIndex);
@@ -18660,13 +19052,12 @@ app.addEventListener('drop', (event) => {
18660
19052
  const context = chatContext(chatIndex);
18661
19053
  const providerId = event.dataTransfer?.getData('application/x-viberaven-provider') || (demo.dragKind === 'provider' ? demo.dragProviderId : '');
18662
19054
  const releaseId = event.dataTransfer?.getData('application/x-viberaven-release') || (demo.dragKind === 'release' ? demo.dragReleaseId : '');
18663
- const chatId = event.dataTransfer?.getData('text/plain').replace(/^chat:/, '') || (demo.dragKind === 'chat' ? demo.dragChatId : '');
18664
- if (chatId && recentChats.some((chat) => chat.id === chatId)) {
19055
+ if (isRecentChatDrop) {
19056
+ const recent = recentChats.find((chat) => chat.id === chatId);
18665
19057
  demo.activeRecentChatId = chatId;
18666
- demo.activeChatCount = 1;
18667
- demo.chatContexts = [{ providerId: null, releaseId: null }];
18668
- chatContext(0);
18669
- demo.notice = 'Recent production chat opened as the active mission.';
19058
+ context.providerId = recent?.providerId || null;
19059
+ context.releaseId = recent?.releaseId || null;
19060
+ demo.notice = recent ? recent.title + ' opened beside the active mission.' : 'Recent production chat opened beside the active mission.';
18670
19061
  }
18671
19062
  if (providerId) {
18672
19063
  demo.selectedProviderId = providerId;
@@ -19933,6 +20324,16 @@ body:has(.vr-app.is-dragging) {
19933
20324
  overflow: auto;
19934
20325
  scrollbar-width: thin;
19935
20326
  }
20327
+ .vr-recent-empty {
20328
+ margin: 0;
20329
+ border: 1px dashed rgba(148, 163, 184, 0.18);
20330
+ border-radius: 10px;
20331
+ background: rgba(255, 255, 255, 0.025);
20332
+ color: #94a3b8;
20333
+ padding: 12px;
20334
+ font-size: 12px;
20335
+ line-height: 1.45;
20336
+ }
19936
20337
  .vr-recent-chat {
19937
20338
  min-width: 0;
19938
20339
  min-height: 60px;
@@ -20304,7 +20705,7 @@ body:has(.vr-app.is-dragging) {
20304
20705
  overflow: hidden;
20305
20706
  }
20306
20707
  .vr-studio-chat {
20307
- grid-template-rows: auto minmax(0, 1fr);
20708
+ grid-template-rows: minmax(0, 1fr);
20308
20709
  gap: 10px;
20309
20710
  padding: 12px;
20310
20711
  }
@@ -20321,6 +20722,9 @@ body:has(.vr-app.is-dragging) {
20321
20722
  .vr-studio-diff {
20322
20723
  grid-template-rows: auto minmax(0, 1fr) auto;
20323
20724
  }
20725
+ .vr-studio-terminal {
20726
+ grid-template-rows: auto minmax(0, 1fr) auto auto;
20727
+ }
20324
20728
  .vr-studio-terminal header,
20325
20729
  .vr-studio-diff header,
20326
20730
  .vr-studio-terminal footer,
@@ -20346,6 +20750,47 @@ body:has(.vr-app.is-dragging) {
20346
20750
  color: #bfdbfe;
20347
20751
  font: 13px/1.55 "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
20348
20752
  }
20753
+ .vr-terminal-compose {
20754
+ display: grid;
20755
+ grid-template-columns: minmax(0, 1fr) auto;
20756
+ align-items: end;
20757
+ gap: 10px;
20758
+ border-top: 1px solid rgba(96, 122, 177, 0.18);
20759
+ padding: 10px 12px;
20760
+ }
20761
+ .vr-terminal-compose textarea {
20762
+ min-height: 44px;
20763
+ resize: none;
20764
+ border: 1px solid rgba(148, 163, 184, 0.16);
20765
+ border-radius: 10px;
20766
+ background: rgba(255, 255, 255, 0.035);
20767
+ color: #e5e7eb;
20768
+ padding: 9px 10px;
20769
+ font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
20770
+ outline: none;
20771
+ }
20772
+ .vr-terminal-compose textarea:focus {
20773
+ border-color: rgba(168, 85, 247, 0.45);
20774
+ }
20775
+ .vr-terminal-compose button {
20776
+ min-height: 38px;
20777
+ display: inline-flex;
20778
+ align-items: center;
20779
+ gap: 7px;
20780
+ border: 1px solid rgba(168, 85, 247, 0.45);
20781
+ border-radius: 10px;
20782
+ background: rgba(124, 58, 237, 0.2);
20783
+ color: #fff;
20784
+ padding: 0 12px;
20785
+ font-weight: 800;
20786
+ }
20787
+ .vr-terminal-compose svg {
20788
+ width: 15px;
20789
+ height: 15px;
20790
+ fill: none;
20791
+ stroke: currentColor;
20792
+ stroke-width: 2;
20793
+ }
20349
20794
  .vr-studio-diff .add { color: #86efac; }
20350
20795
  .vr-studio-diff .del { color: #fca5a5; }
20351
20796
 
@@ -20448,7 +20893,7 @@ body:has(.vr-app.is-dragging) {
20448
20893
  min-height: 100%;
20449
20894
  min-height: 0;
20450
20895
  display: grid;
20451
- grid-template-rows: minmax(0, 1fr) auto;
20896
+ grid-template-rows: auto minmax(0, 1fr) auto;
20452
20897
  border: 0;
20453
20898
  border-radius: 0;
20454
20899
  background: transparent;
@@ -20515,6 +20960,9 @@ body:has(.vr-app.is-dragging) {
20515
20960
  .vr-studio-chat.is-split .vr-chat-command-bar > .vr-chat-composer {
20516
20961
  grid-template-columns: minmax(0, 1fr) 38px;
20517
20962
  }
20963
+ .vr-studio-chat.is-split .vr-chat-input {
20964
+ grid-column: 1;
20965
+ }
20518
20966
  .vr-studio-chat.is-split .vr-model-picker {
20519
20967
  grid-column: 1 / -1;
20520
20968
  justify-self: start;
@@ -20532,7 +20980,7 @@ body:has(.vr-app.is-dragging) {
20532
20980
  max-width: none;
20533
20981
  }
20534
20982
  .vr-studio-chat:not(.is-split) .vr-chat-transcript {
20535
- padding: 18px 18px 18px;
20983
+ padding: 18px 18px 10px;
20536
20984
  }
20537
20985
  .vr-studio-chat:not(.is-split) .vr-chat-message,
20538
20986
  .vr-studio-chat:not(.is-split) .vr-chat-mission-summary {
@@ -20542,11 +20990,11 @@ body:has(.vr-app.is-dragging) {
20542
20990
  max-width: 720px;
20543
20991
  }
20544
20992
  .vr-studio-chat:not(.is-split) .vr-chat-message.is-user {
20545
- max-width: 390px;
20993
+ max-width: min(620px, 74%);
20546
20994
  justify-self: end;
20547
20995
  }
20548
20996
  .vr-studio-chat:not(.is-split) .vr-chat-message.is-user > p {
20549
- max-width: 340px;
20997
+ max-width: 560px;
20550
20998
  }
20551
20999
  .vr-studio-chat:not(.is-split) .vr-chat-message p {
20552
21000
  font-size: 13.5px;
@@ -20581,19 +21029,16 @@ body:has(.vr-app.is-dragging) {
20581
21029
  }
20582
21030
  .vr-studio-chat:not(.is-split) .vr-chat-command-bar {
20583
21031
  padding: 0;
21032
+ align-self: end;
20584
21033
  }
20585
21034
  .vr-studio-chat:not(.is-split) .vr-chat-command-bar > .vr-chat-composer {
20586
- min-height: 74px;
21035
+ min-height: 92px;
20587
21036
  border-color: rgba(226, 232, 240, 0.16);
20588
21037
  border-radius: 14px;
20589
21038
  background: #202124;
20590
21039
  color: #f4f4f5;
20591
21040
  padding: 10px 14px;
20592
21041
  }
20593
- .vr-studio-chat:not(.is-split) .vr-chat-command-bar > .vr-chat-composer > span {
20594
- color: #9ca3af;
20595
- font-size: 13px;
20596
- }
20597
21042
  .vr-studio-chat:not(.is-split) .vr-provider-drop-zone > span {
20598
21043
  border-color: rgba(226, 232, 240, 0.14);
20599
21044
  background: rgba(255, 255, 255, 0.04);
@@ -20606,10 +21051,10 @@ body:has(.vr-app.is-dragging) {
20606
21051
  padding-top: 18px;
20607
21052
  }
20608
21053
  .vr-studio-chat:not(.is-split) .vr-chat-message.is-user {
20609
- position: absolute;
20610
- top: 28px;
20611
- right: 22px;
20612
- z-index: 2;
21054
+ position: relative;
21055
+ top: auto;
21056
+ right: auto;
21057
+ z-index: auto;
20613
21058
  }
20614
21059
  .vr-studio-chat:not(.is-split) .vr-chat-message.is-agent,
20615
21060
  .vr-studio-chat:not(.is-split) .vr-chat-mission-summary {
@@ -20705,7 +21150,7 @@ body:has(.vr-app.is-dragging) {
20705
21150
  min-height: 0;
20706
21151
  display: grid;
20707
21152
  align-content: start;
20708
- gap: 10px;
21153
+ gap: 14px;
20709
21154
  overflow: auto;
20710
21155
  scrollbar-width: thin;
20711
21156
  padding: 20px 24px 12px;
@@ -20719,37 +21164,78 @@ body:has(.vr-app.is-dragging) {
20719
21164
  max-width: 560px;
20720
21165
  justify-self: end;
20721
21166
  justify-items: end;
21167
+ margin-top: 4px;
20722
21168
  }
20723
21169
  .vr-chat-message.is-user > p {
20724
21170
  width: fit-content;
20725
21171
  border: 1px solid rgba(148, 163, 184, 0.14);
20726
- border-radius: 9px;
20727
- background: rgba(31, 34, 41, 0.72);
20728
- padding: 10px 12px;
21172
+ border-radius: 13px 13px 4px 13px;
21173
+ background: #f8fafc;
21174
+ color: #111827;
21175
+ padding: 9px 12px;
20729
21176
  text-align: left;
20730
21177
  }
20731
21178
  .vr-chat-message.is-user > p {
20732
21179
  padding-left: 12px;
20733
21180
  }
20734
- .vr-chat-message.is-user > div > span {
21181
+ .vr-sent-context {
21182
+ max-width: 560px;
21183
+ display: flex;
21184
+ flex-wrap: wrap;
21185
+ justify-content: flex-end;
21186
+ gap: 7px;
21187
+ }
21188
+ .vr-sent-context span {
21189
+ min-height: 30px;
21190
+ max-width: min(360px, 100%);
21191
+ display: inline-flex;
21192
+ align-items: center;
21193
+ gap: 8px;
21194
+ border: 1px solid rgba(148, 163, 184, 0.16);
21195
+ border-radius: 10px;
21196
+ background: rgba(255, 255, 255, 0.055);
21197
+ color: #cbd5e1;
21198
+ padding: 4px 9px;
21199
+ font-size: 12px;
21200
+ }
21201
+ .vr-sent-context img,
21202
+ .vr-sent-context i {
21203
+ width: 22px;
21204
+ height: 22px;
21205
+ flex: 0 0 auto;
21206
+ display: grid;
20735
21207
  place-items: center;
20736
- background: rgba(226, 232, 240, 0.88);
21208
+ overflow: hidden;
21209
+ border: 1px solid rgba(148, 163, 184, 0.16);
21210
+ border-radius: 8px;
21211
+ background: rgba(15, 23, 42, 0.7);
21212
+ object-fit: contain;
21213
+ font-style: normal;
20737
21214
  }
20738
- .vr-chat-message.is-user > div > span::before {
20739
- content: "";
20740
- width: 13px;
20741
- height: 13px;
20742
- border-radius: 999px;
20743
- background: #111827;
21215
+ .vr-sent-context b {
21216
+ border: 0;
21217
+ background: transparent;
21218
+ color: #94a3b8;
21219
+ padding: 0;
21220
+ font-size: 10px;
21221
+ font-weight: 720;
21222
+ text-transform: uppercase;
21223
+ letter-spacing: 0;
21224
+ }
21225
+ .vr-sent-context em {
21226
+ overflow: hidden;
21227
+ text-overflow: ellipsis;
21228
+ white-space: nowrap;
21229
+ margin: 0;
21230
+ color: #f8fafc;
21231
+ font-style: normal;
21232
+ font-weight: 760;
20744
21233
  }
20745
21234
  .vr-chat-message > div {
20746
21235
  display: flex;
20747
21236
  align-items: center;
20748
21237
  gap: 8px;
20749
21238
  }
20750
- .vr-chat-message.is-user > div {
20751
- justify-self: end;
20752
- }
20753
21239
  .vr-chat-message > div > span {
20754
21240
  width: 28px;
20755
21241
  height: 28px;
@@ -20797,6 +21283,61 @@ body:has(.vr-app.is-dragging) {
20797
21283
  color: #aeb9ca;
20798
21284
  font-style: normal;
20799
21285
  }
21286
+ .vr-chat-message > .vr-sent-context,
21287
+ .vr-chat-message > .vr-agent-response-meta,
21288
+ .vr-chat-message > .vr-agent-context-line {
21289
+ width: auto;
21290
+ height: auto;
21291
+ display: flex;
21292
+ flex-wrap: wrap;
21293
+ align-items: center;
21294
+ overflow: visible;
21295
+ }
21296
+ .vr-chat-message > .vr-sent-context > span,
21297
+ .vr-chat-message > .vr-agent-response-meta > span,
21298
+ .vr-chat-message > .vr-agent-context-line > span {
21299
+ width: auto;
21300
+ height: auto;
21301
+ min-width: 0;
21302
+ display: inline-flex;
21303
+ align-items: center;
21304
+ justify-content: flex-start;
21305
+ place-items: initial;
21306
+ overflow: hidden;
21307
+ border-radius: 10px;
21308
+ }
21309
+ .vr-chat-message > .vr-sent-context img,
21310
+ .vr-chat-message > .vr-sent-context i,
21311
+ .vr-chat-message > .vr-agent-response-meta img,
21312
+ .vr-chat-message > .vr-agent-response-meta i,
21313
+ .vr-chat-message > .vr-agent-context-line img,
21314
+ .vr-chat-message > .vr-agent-context-line i {
21315
+ transform: none;
21316
+ }
21317
+ .vr-chat-message > .vr-sent-context b,
21318
+ .vr-chat-message > .vr-agent-response-meta b,
21319
+ .vr-chat-message > .vr-agent-context-line b {
21320
+ border: 0;
21321
+ background: transparent;
21322
+ color: #94a3b8;
21323
+ padding: 0;
21324
+ font-size: 10px;
21325
+ font-weight: 720;
21326
+ text-transform: uppercase;
21327
+ }
21328
+ .vr-chat-message > .vr-sent-context em,
21329
+ .vr-chat-message > .vr-agent-response-meta em,
21330
+ .vr-chat-message > .vr-agent-context-line em {
21331
+ min-width: 0;
21332
+ overflow: hidden;
21333
+ text-overflow: ellipsis;
21334
+ white-space: nowrap;
21335
+ margin: 0;
21336
+ color: #f8fafc;
21337
+ font-size: 12px;
21338
+ font-style: normal;
21339
+ font-weight: 720;
21340
+ }
20800
21341
  .vr-chat-copy {
20801
21342
  max-width: 720px;
20802
21343
  margin: 0;
@@ -21174,6 +21715,134 @@ body:has(.vr-app.is-dragging) {
21174
21715
  font-size: 12px;
21175
21716
  line-height: 1.35;
21176
21717
  }
21718
+ .vr-chat-agent-response {
21719
+ max-width: min(920px, 82%);
21720
+ display: grid;
21721
+ gap: 7px;
21722
+ animation: vrTaskEnter 220ms cubic-bezier(0.2, 0, 0, 1) both;
21723
+ }
21724
+ .vr-agent-response-meta {
21725
+ margin-left: 48px;
21726
+ max-width: min(640px, calc(100% - 48px));
21727
+ display: flex;
21728
+ flex-wrap: wrap;
21729
+ align-items: center;
21730
+ gap: 8px;
21731
+ }
21732
+ .vr-agent-thinking-label {
21733
+ min-height: 24px;
21734
+ display: inline-flex;
21735
+ align-items: center;
21736
+ gap: 7px;
21737
+ border: 1px solid rgba(245, 158, 11, 0.16);
21738
+ border-radius: 999px;
21739
+ background: rgba(245, 158, 11, 0.055);
21740
+ color: #f6b35c;
21741
+ padding: 3px 9px;
21742
+ font-size: 11px;
21743
+ font-weight: 680;
21744
+ }
21745
+ .vr-agent-thinking-label::before {
21746
+ content: "";
21747
+ width: 7px;
21748
+ height: 7px;
21749
+ flex: 0 0 auto;
21750
+ border-radius: 999px;
21751
+ background: #f97316;
21752
+ box-shadow: 0 0 12px rgba(249, 115, 22, 0.4);
21753
+ }
21754
+ .vr-agent-context-line {
21755
+ margin-left: 48px;
21756
+ max-width: min(640px, calc(100% - 48px));
21757
+ display: flex;
21758
+ flex-wrap: wrap;
21759
+ gap: 8px;
21760
+ }
21761
+ .vr-agent-context-line span {
21762
+ min-width: 0;
21763
+ min-height: 32px;
21764
+ display: inline-flex;
21765
+ align-items: center;
21766
+ gap: 8px;
21767
+ border: 1px solid rgba(148, 163, 184, 0.16);
21768
+ border-radius: 10px;
21769
+ background: rgba(255, 255, 255, 0.045);
21770
+ padding: 4px 10px 4px 5px;
21771
+ }
21772
+ .vr-agent-context-line img,
21773
+ .vr-agent-context-line i {
21774
+ width: 24px;
21775
+ height: 24px;
21776
+ flex: 0 0 auto;
21777
+ display: grid;
21778
+ place-items: center;
21779
+ overflow: hidden;
21780
+ border-radius: 8px;
21781
+ background: rgba(15, 23, 42, 0.72);
21782
+ object-fit: contain;
21783
+ transform: none;
21784
+ font-style: normal;
21785
+ }
21786
+ .vr-agent-context-line b {
21787
+ margin: 0;
21788
+ color: #94a3b8;
21789
+ font-size: 10px;
21790
+ font-weight: 720;
21791
+ text-transform: uppercase;
21792
+ letter-spacing: 0;
21793
+ }
21794
+ .vr-agent-context-line em {
21795
+ min-width: 0;
21796
+ overflow: hidden;
21797
+ text-overflow: ellipsis;
21798
+ white-space: nowrap;
21799
+ margin: 0;
21800
+ color: #f8fafc;
21801
+ font-size: 12px;
21802
+ font-style: normal;
21803
+ font-weight: 720;
21804
+ }
21805
+ .vr-agent-answer-text {
21806
+ max-width: min(860px, calc(100% - 48px));
21807
+ overflow: visible;
21808
+ white-space: pre-wrap;
21809
+ color: #e5e7eb;
21810
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
21811
+ font-size: 13.5px;
21812
+ line-height: 1.58;
21813
+ }
21814
+ .vr-chat-response-actions {
21815
+ padding-left: 48px;
21816
+ display: flex;
21817
+ flex-wrap: wrap;
21818
+ gap: 8px;
21819
+ }
21820
+ .vr-chat-response-actions button {
21821
+ min-height: 30px;
21822
+ display: inline-flex;
21823
+ align-items: center;
21824
+ gap: 7px;
21825
+ border: 1px solid rgba(148, 163, 184, 0.16);
21826
+ border-radius: 8px;
21827
+ background: rgba(255, 255, 255, 0.035);
21828
+ color: #f8fafc;
21829
+ padding: 0 10px;
21830
+ font-size: 12px;
21831
+ font-weight: 740;
21832
+ }
21833
+ .vr-chat-response-actions button:hover {
21834
+ border-color: rgba(168, 85, 247, 0.34);
21835
+ background: rgba(168, 85, 247, 0.12);
21836
+ }
21837
+ .vr-chat-response-actions svg {
21838
+ width: 14px;
21839
+ height: 14px;
21840
+ fill: none;
21841
+ stroke: currentColor;
21842
+ stroke-width: 1.9;
21843
+ stroke-linecap: round;
21844
+ stroke-linejoin: round;
21845
+ }
21177
21846
  .vr-chat-card header {
21178
21847
  display: flex;
21179
21848
  align-items: center;
@@ -21375,6 +22044,31 @@ body:has(.vr-app.is-dragging) {
21375
22044
  color: #f8fafc;
21376
22045
  padding: 12px 14px;
21377
22046
  }
22047
+ .vr-chat-input {
22048
+ grid-column: 1 / 3;
22049
+ grid-row: 1;
22050
+ align-self: stretch;
22051
+ width: 100%;
22052
+ min-width: 0;
22053
+ min-height: 38px;
22054
+ border: 0;
22055
+ outline: 0;
22056
+ resize: none;
22057
+ background: transparent;
22058
+ color: #f8fafc;
22059
+ padding: 0;
22060
+ font: inherit;
22061
+ font-size: 13.5px;
22062
+ font-weight: 520;
22063
+ line-height: 1.45;
22064
+ caret-color: #f8fafc;
22065
+ }
22066
+ .vr-chat-input::placeholder {
22067
+ color: #a1a8b3;
22068
+ }
22069
+ .vr-chat-input:focus {
22070
+ outline: 0;
22071
+ }
21378
22072
  .vr-chat-command-bar > .vr-chat-composer > span {
21379
22073
  align-self: start;
21380
22074
  overflow: hidden;
@@ -24475,6 +25169,11 @@ body:has(.vr-app.is-dragging) {
24475
25169
  min-width: 0;
24476
25170
  min-height: 92px;
24477
25171
  }
25172
+ .vr-chat-input {
25173
+ grid-column: 1;
25174
+ min-width: 0;
25175
+ overflow-wrap: anywhere;
25176
+ }
24478
25177
  .vr-chat-command-bar > .vr-chat-composer > span {
24479
25178
  min-width: 0;
24480
25179
  overflow-wrap: anywhere;
@@ -24546,19 +25245,173 @@ async function defaultScanRunner(flags, positional) {
24546
25245
  const cli = await Promise.resolve().then(() => (init_cli(), cli_exports));
24547
25246
  return cli.runScanCommand(flags, positional);
24548
25247
  }
24549
- async function readPackageMetadata(cwd) {
25248
+ async function readPackageJson2(cwd) {
24550
25249
  try {
24551
25250
  const raw = await (0, import_promises24.readFile)((0, import_node_path31.join)(cwd, "package.json"), "utf8");
24552
- const parsed = JSON.parse(raw);
24553
- if (!isRecord7(parsed)) return {};
24554
- return {
24555
- name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : void 0,
24556
- version: typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0
24557
- };
25251
+ const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
25252
+ return isRecord7(parsed) ? parsed : {};
24558
25253
  } catch {
24559
25254
  return {};
24560
25255
  }
24561
25256
  }
25257
+ function dependencyNames(packageJson) {
25258
+ const names = /* @__PURE__ */ new Set();
25259
+ for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
25260
+ const value = packageJson[key];
25261
+ if (!isRecord7(value)) continue;
25262
+ Object.keys(value).forEach((name) => names.add(name.toLowerCase()));
25263
+ }
25264
+ return names;
25265
+ }
25266
+ async function listProjectFiles(cwd, maxFiles = 1800) {
25267
+ const skipped = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "coverage", ".turbo", ".viberaven"]);
25268
+ const files = [];
25269
+ async function walk(dir, prefix = "") {
25270
+ if (files.length >= maxFiles) return;
25271
+ let entries;
25272
+ try {
25273
+ entries = await (0, import_promises24.readdir)(dir, { withFileTypes: true });
25274
+ } catch {
25275
+ return;
25276
+ }
25277
+ for (const entry of entries) {
25278
+ if (files.length >= maxFiles) return;
25279
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
25280
+ if (entry.isDirectory()) {
25281
+ if (!skipped.has(entry.name)) await walk((0, import_node_path31.join)(dir, entry.name), rel);
25282
+ continue;
25283
+ }
25284
+ if (entry.isFile()) files.push(rel.toLowerCase());
25285
+ }
25286
+ }
25287
+ await walk(cwd);
25288
+ return files;
25289
+ }
25290
+ function hasDependency(deps, needles) {
25291
+ return needles.some((needle) => deps.has(needle) || Array.from(deps).some((dep) => dep.includes(needle)));
25292
+ }
25293
+ function hasFile(files, needles) {
25294
+ return needles.some((needle) => files.some((file) => file.includes(needle)));
25295
+ }
25296
+ function providerDetected(providerId, deps, files) {
25297
+ switch (providerId) {
25298
+ case "supabase":
25299
+ return hasDependency(deps, ["@supabase/supabase-js", "supabase"]) || hasFile(files, ["supabase/", "supabase\\", "supabase.ts"]);
25300
+ case "vercel":
25301
+ return hasFile(files, ["vercel.json", ".vercel/project.json"]) || hasDependency(deps, ["@vercel/"]);
25302
+ case "stripe":
25303
+ return hasDependency(deps, ["stripe", "@stripe/stripe-js"]) || hasFile(files, ["stripe", "webhook"]);
25304
+ case "github":
25305
+ return hasFile(files, [".github/workflows", ".github/dependabot.yml"]) || files.includes("readme.md");
25306
+ case "sentry":
25307
+ return hasDependency(deps, ["@sentry/"]) || hasFile(files, ["sentry.", "instrumentation.ts", "instrumentation.js"]);
25308
+ case "posthog":
25309
+ return hasDependency(deps, ["posthog", "posthog-js"]) || hasFile(files, ["posthog"]);
25310
+ case "clerk":
25311
+ return hasDependency(deps, ["@clerk/"]) || hasFile(files, ["clerk", "middleware.ts", "middleware.js"]);
25312
+ case "authjs":
25313
+ return hasDependency(deps, ["next-auth", "@auth/"]) || hasFile(files, ["auth.ts", "auth.js", "nextauth"]);
25314
+ case "resend":
25315
+ return hasDependency(deps, ["resend"]) || hasFile(files, ["resend", "email", "mail"]);
25316
+ case "upstash":
25317
+ return hasDependency(deps, ["@upstash/", "upstash"]) || hasFile(files, ["upstash", "ratelimit", "rate-limit", "redis"]);
25318
+ default:
25319
+ return false;
25320
+ }
25321
+ }
25322
+ function collapseAuthProviders(providers) {
25323
+ const authProviders = providers.filter((provider2) => provider2.id === "clerk" || provider2.id === "authjs");
25324
+ if (authProviders.length <= 1) return providers;
25325
+ const detectedAuth = authProviders.find((provider2) => provider2.state !== "not_detected");
25326
+ const visibleAuth = detectedAuth ?? authProviders.find((provider2) => provider2.id === "clerk") ?? authProviders[0];
25327
+ return providers.filter((provider2) => provider2.id !== "clerk" && provider2.id !== "authjs").concat(visibleAuth);
25328
+ }
25329
+ function providerDetail(provider2, detected) {
25330
+ if (detected) {
25331
+ return `${provider2.area} evidence was found in this project. Drag this slot into chat to ask the connected CLI for the next production step.`;
25332
+ }
25333
+ return `${provider2.area} is available as a production slot. Add it when this project depends on ${provider2.name}.`;
25334
+ }
25335
+ function applyProviderDetection(state, deps, files) {
25336
+ const providers = collapseAuthProviders(state.providers.map((provider2) => {
25337
+ const detected = providerDetected(provider2.id, deps, files);
25338
+ const providerState2 = detected ? provider2.state === "not_detected" ? "repo_evidence_found" : provider2.state : "not_detected";
25339
+ return {
25340
+ ...provider2,
25341
+ state: providerState2,
25342
+ statusText: detected ? "Detected in repo" : "Add when needed",
25343
+ connectLabel: detected ? "Open slot" : "Add provider",
25344
+ launchPath: provider2.launchPath.map((item4, index) => ({
25345
+ ...item4,
25346
+ state: detected && item4.source === "repo" && index < 2 ? "ready" : "not_checked",
25347
+ shortReason: detected ? "Repo evidence detected" : "No local evidence yet"
25348
+ })),
25349
+ selectedItemId: provider2.launchPath[0]?.id,
25350
+ nextFix: provider2.nextFix ? {
25351
+ ...provider2.nextFix,
25352
+ title: detected ? `Review ${provider2.name} production proof` : `Add ${provider2.name} when needed`,
25353
+ whyItMatters: providerDetail(provider2, detected),
25354
+ whatToChange: detected ? `Ask VibeRaven Chat to inspect ${provider2.name} evidence and identify missing production proof.` : `Use Add Provider when this project starts using ${provider2.name}.`,
25355
+ verifyInstruction: "Use the connected CLI chat to inspect local evidence before claiming live provider proof."
25356
+ } : provider2.nextFix
25357
+ };
25358
+ }));
25359
+ const firstDetected = providers.find((provider2) => provider2.state !== "not_detected");
25360
+ return {
25361
+ ...state,
25362
+ providers,
25363
+ selectedProviderId: firstDetected?.id ?? providers[0]?.id ?? state.selectedProviderId
25364
+ };
25365
+ }
25366
+ function runGit(cwd, args, timeoutMs = 1500) {
25367
+ return new Promise((resolveGit) => {
25368
+ const child = (0, import_node_child_process4.spawn)("git", args, {
25369
+ cwd,
25370
+ windowsHide: true,
25371
+ stdio: ["ignore", "pipe", "ignore"]
25372
+ });
25373
+ let stdout = "";
25374
+ const timer = setTimeout(() => child.kill(), timeoutMs);
25375
+ child.stdout?.on("data", (chunk) => {
25376
+ stdout += chunk.toString("utf8");
25377
+ });
25378
+ child.once("error", () => {
25379
+ clearTimeout(timer);
25380
+ resolveGit("");
25381
+ });
25382
+ child.once("close", () => {
25383
+ clearTimeout(timer);
25384
+ resolveGit(stdout.trim());
25385
+ });
25386
+ });
25387
+ }
25388
+ async function detectReleases(cwd, version) {
25389
+ const branch = await runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "local";
25390
+ const tagsRaw = await runGit(cwd, ["tag", "--sort=-creatordate"]);
25391
+ const tags = tagsRaw.split(/\r?\n/).map((tag) => tag.trim()).filter(Boolean).slice(0, 5);
25392
+ if (tags.length > 0) {
25393
+ return tags.map((tag, index) => ({
25394
+ id: tag,
25395
+ label: tag,
25396
+ meta: index === 0 ? "latest tag" : "git tag",
25397
+ branch,
25398
+ tone: index === 0 ? "current" : "prod",
25399
+ summary: index === 0 ? "Latest git tag detected locally." : "Git tag detected locally."
25400
+ }));
25401
+ }
25402
+ const shortSha = await runGit(cwd, ["rev-parse", "--short", "HEAD"]) || "";
25403
+ const current = version ? `v${version}` : shortSha ? `commit ${shortSha}` : "Workspace";
25404
+ return [
25405
+ {
25406
+ id: current.toLowerCase().replace(/[^a-z0-9._-]+/g, "-"),
25407
+ label: current,
25408
+ meta: shortSha ? "current commit" : "not released",
25409
+ branch,
25410
+ tone: "current",
25411
+ summary: shortSha ? "Current git commit detected locally." : "No git release was detected yet."
25412
+ }
25413
+ ];
25414
+ }
24562
25415
  function cliModelDefaults(id) {
24563
25416
  switch (id) {
24564
25417
  case "claude":
@@ -24622,14 +25475,23 @@ function isPlaceholderProjectName(value) {
24622
25475
  return normalized === "" || normalized === "project" || normalized === "npx-open-source-check" || normalized.startsWith("viberaven-local-ui");
24623
25476
  }
24624
25477
  async function enrichProjectMetadata(state, cwd) {
24625
- const metadata = await readPackageMetadata(cwd);
24626
- if (!metadata.name && !metadata.version) return state;
25478
+ const packageJson = await readPackageJson2(cwd);
25479
+ const metadata = {
25480
+ name: typeof packageJson.name === "string" && packageJson.name.trim() ? packageJson.name.trim() : void 0,
25481
+ version: typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version.trim() : void 0
25482
+ };
25483
+ const files = await listProjectFiles(cwd);
25484
+ const deps = dependencyNames(packageJson);
25485
+ const detectedState = state.empty ? applyProviderDetection(state, deps, files) : state;
25486
+ const releases = await detectReleases(cwd, metadata.version);
24627
25487
  return {
24628
- ...state,
25488
+ ...detectedState,
25489
+ releases,
25490
+ selectedProviderId: detectedState.providers.find((provider2) => provider2.id === detectedState.selectedProviderId) ? detectedState.selectedProviderId : detectedState.providers[0]?.id ?? detectedState.selectedProviderId,
24629
25491
  project: {
24630
- ...state.project,
24631
- name: metadata.name && !isPlaceholderProjectName(metadata.name) ? metadata.name : state.project.name,
24632
- version: metadata.version ?? state.project.version
25492
+ ...detectedState.project,
25493
+ name: metadata.name && !isPlaceholderProjectName(metadata.name) ? metadata.name : detectedState.project.name,
25494
+ version: metadata.version ?? detectedState.project.version
24633
25495
  }
24634
25496
  };
24635
25497
  }
@@ -24689,6 +25551,172 @@ function localActionIntent(value) {
24689
25551
  if (!isRecord7(value) || typeof value.intent !== "string") return null;
24690
25552
  return ["audit_vercel_supabase", "cleanup_plan", "preview_rehearsal", "heal_plan", "heal_prompt", "heal_apply"].includes(value.intent) ? value.intent : null;
24691
25553
  }
25554
+ function stringField(value, max = 2e3) {
25555
+ if (typeof value !== "string") return void 0;
25556
+ const trimmed = value.trim();
25557
+ return trimmed ? trimmed.slice(0, max) : void 0;
25558
+ }
25559
+ function parseAgentChatRequest(value) {
25560
+ if (!isRecord7(value)) return { error: "Invalid agent chat request" };
25561
+ const rawCliId = stringField(value.cliId, 40);
25562
+ if (rawCliId === "terminal") return { error: "Terminal chat execution is not supported. Use an explicit terminal command surface." };
25563
+ if (rawCliId !== "codex" && rawCliId !== "claude" && rawCliId !== "gemini") return { error: "Unsupported coding CLI" };
25564
+ const prompt = stringField(value.prompt, 12e3);
25565
+ if (!prompt) return { error: "Agent chat prompt is required" };
25566
+ const provider2 = isRecord7(value.provider) ? { area: stringField(value.provider.area, 120), name: stringField(value.provider.name, 120) } : void 0;
25567
+ const release = isRecord7(value.release) ? { label: stringField(value.release.label, 120) } : void 0;
25568
+ return {
25569
+ cliId: rawCliId,
25570
+ modelId: stringField(value.modelId, 120),
25571
+ reasoning: stringField(value.reasoning, 80),
25572
+ context: stringField(value.context, 80),
25573
+ prompt,
25574
+ provider: provider2,
25575
+ release
25576
+ };
25577
+ }
25578
+ function buildAgentChatPrompt(request, cwd) {
25579
+ const lines = [
25580
+ "You are VibeRaven Studio, a production-readiness coding agent for an open-source local runner.",
25581
+ `Project folder: ${cwd}`,
25582
+ request.modelId ? `Selected model label: ${request.modelId}` : void 0,
25583
+ request.reasoning ? `Reasoning: ${request.reasoning}` : void 0,
25584
+ request.context ? `Context: ${request.context}` : void 0,
25585
+ request.provider?.name ? `Provider context: ${request.provider.area ?? "Provider"} / ${request.provider.name}` : void 0,
25586
+ request.release?.label ? `Release context: ${request.release.label}` : void 0,
25587
+ "",
25588
+ "Safety rules:",
25589
+ "- Stay scoped to this project folder.",
25590
+ "- Explain risky or destructive changes before making them.",
25591
+ "- Do not claim provider dashboard setup is complete from repo edits alone.",
25592
+ "- Keep the answer focused on production readiness.",
25593
+ "- Return only the final user-facing answer. Do not echo this prompt, session metadata, logs, or command transcripts.",
25594
+ "- Prefer short sections with concrete next actions and verification buttons can handle follow-up actions.",
25595
+ "",
25596
+ "User mission:",
25597
+ request.prompt
25598
+ ];
25599
+ return lines.filter((line) => typeof line === "string").join("\n");
25600
+ }
25601
+ function commandForAgentChat(request, prompt, cwd, outputPath) {
25602
+ const command = process.platform === "win32" ? `${request.cliId}.cmd` : request.cliId;
25603
+ const promptArg = prompt.replace(/\s*\r?\n\s*/g, " ");
25604
+ if (request.cliId === "gemini") return { command, args: ["-p", promptArg, "--output-format", "text", "--skip-trust"] };
25605
+ if (request.cliId === "claude") return { command, args: ["-p", promptArg] };
25606
+ const codexArgs = ["--cd", cwd, "--sandbox", "workspace-write", "--ask-for-approval", "never", "exec", "--color", "never"];
25607
+ if (outputPath) codexArgs.push("--output-last-message", outputPath);
25608
+ codexArgs.push("-");
25609
+ return {
25610
+ command,
25611
+ args: codexArgs,
25612
+ stdin: prompt
25613
+ };
25614
+ }
25615
+ function quoteWindowsCmdArg(value) {
25616
+ if (/^[A-Za-z0-9._/@:=+-]+$/.test(value)) return value;
25617
+ return `"${value.replace(/%/g, "%%").replace(/"/g, '""')}"`;
25618
+ }
25619
+ async function resolveWindowsNodeShim(command, cwd) {
25620
+ if (process.platform !== "win32" || !command.endsWith(".cmd")) return void 0;
25621
+ const shimPath = await new Promise((resolvePath) => {
25622
+ const child = (0, import_node_child_process4.spawn)("where.exe", [command], {
25623
+ cwd,
25624
+ windowsHide: true,
25625
+ stdio: ["ignore", "pipe", "ignore"]
25626
+ });
25627
+ let stdout = "";
25628
+ child.stdout?.on("data", (chunk) => {
25629
+ stdout += chunk.toString("utf8");
25630
+ });
25631
+ child.once("error", () => resolvePath(void 0));
25632
+ child.once("close", (code) => {
25633
+ if (code !== 0) {
25634
+ resolvePath(void 0);
25635
+ return;
25636
+ }
25637
+ resolvePath(stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean));
25638
+ });
25639
+ });
25640
+ if (!shimPath) return void 0;
25641
+ try {
25642
+ const content = await (0, import_promises24.readFile)(shimPath, "utf8");
25643
+ const match = content.match(/"%dp0%\\([^"]+\.js)"/i);
25644
+ if (!match?.[1]) return void 0;
25645
+ return { command: process.execPath, args: [(0, import_node_path31.join)((0, import_node_path31.dirname)(shimPath), match[1])] };
25646
+ } catch {
25647
+ return void 0;
25648
+ }
25649
+ }
25650
+ async function runAgentCommand(command, args, cwd, stdin) {
25651
+ const nodeShim = await resolveWindowsNodeShim(command, cwd);
25652
+ const useCmdShim = process.platform === "win32" && command.endsWith(".cmd") && !nodeShim;
25653
+ const spawnCommand = nodeShim?.command ?? (useCmdShim ? process.env.ComSpec || "cmd.exe" : command);
25654
+ const spawnArgs = nodeShim ? [...nodeShim.args, ...args] : useCmdShim ? ["/d", "/s", "/c", [command, ...args.map(quoteWindowsCmdArg)].join(" ")] : args;
25655
+ return new Promise((resolveRun) => {
25656
+ const child = (0, import_node_child_process4.spawn)(spawnCommand, spawnArgs, {
25657
+ cwd,
25658
+ windowsHide: true,
25659
+ stdio: [stdin ? "pipe" : "ignore", "pipe", "pipe"]
25660
+ });
25661
+ let stdout = "";
25662
+ let stderr = "";
25663
+ let timedOut = false;
25664
+ const append = (current, chunk) => {
25665
+ const next = current + chunk.toString("utf8");
25666
+ return next.length > 48e3 ? next.slice(0, 48e3) : next;
25667
+ };
25668
+ const timer = setTimeout(() => {
25669
+ timedOut = true;
25670
+ child.kill();
25671
+ }, 12e4);
25672
+ child.stdout?.on("data", (chunk) => {
25673
+ stdout = append(stdout, chunk);
25674
+ });
25675
+ child.stderr?.on("data", (chunk) => {
25676
+ stderr = append(stderr, chunk);
25677
+ });
25678
+ if (stdin && child.stdin) {
25679
+ child.stdin.end(stdin);
25680
+ }
25681
+ child.once("error", (error) => {
25682
+ clearTimeout(timer);
25683
+ resolveRun({
25684
+ command,
25685
+ args,
25686
+ exitCode: null,
25687
+ output: "",
25688
+ stderr: error.message,
25689
+ timedOut
25690
+ });
25691
+ });
25692
+ child.once("close", (code) => {
25693
+ clearTimeout(timer);
25694
+ resolveRun({
25695
+ command,
25696
+ args,
25697
+ exitCode: code,
25698
+ output: stdout.trim(),
25699
+ stderr: stderr.trim(),
25700
+ timedOut
25701
+ });
25702
+ });
25703
+ });
25704
+ }
25705
+ async function runAgentChat(cwd, request) {
25706
+ const prompt = buildAgentChatPrompt(request, cwd);
25707
+ const outputPath = request.cliId === "codex" ? (0, import_node_path31.join)(cwd, ".viberaven", `studio-agent-${Date.now()}-${Math.random().toString(36).slice(2)}.md`) : void 0;
25708
+ if (outputPath) await (0, import_promises24.mkdir)((0, import_node_path31.dirname)(outputPath), { recursive: true });
25709
+ const { command, args, stdin } = commandForAgentChat(request, prompt, cwd, outputPath);
25710
+ const result = await runAgentCommand(command, args, cwd, stdin);
25711
+ if (outputPath) {
25712
+ try {
25713
+ const finalMessage = (await (0, import_promises24.readFile)(outputPath, "utf8")).trim();
25714
+ if (finalMessage) return { ...result, output: finalMessage };
25715
+ } catch {
25716
+ }
25717
+ }
25718
+ return result;
25719
+ }
24692
25720
  function firstGapId(state) {
24693
25721
  if (state.empty) return void 0;
24694
25722
  return state.providers.find((provider2) => provider2.nextFix?.id)?.nextFix?.id;
@@ -24984,6 +26012,23 @@ async function handleRequest(request, response, options) {
24984
26012
  sendJson(response, await detectLocalCliAgents(options.cwd));
24985
26013
  return;
24986
26014
  }
26015
+ if (request.method === "POST" && url.pathname === "/api/agent-chat") {
26016
+ let body;
26017
+ try {
26018
+ body = await readJsonBody(request);
26019
+ } catch {
26020
+ sendText(response, 400, "Invalid JSON");
26021
+ return;
26022
+ }
26023
+ const parsed = parseAgentChatRequest(body);
26024
+ if ("error" in parsed) {
26025
+ sendText(response, 400, parsed.error);
26026
+ return;
26027
+ }
26028
+ const agentRun = await runAgentChat(options.cwd, parsed);
26029
+ sendJson(response, { agentRun });
26030
+ return;
26031
+ }
24987
26032
  if (request.method === "POST" && url.pathname === "/api/shutdown") {
24988
26033
  sendJson(response, { ok: true });
24989
26034
  setTimeout(() => process.kill(process.pid, "SIGINT"), 0);
@@ -25486,6 +26531,9 @@ function resolveDefaultEntrypointMode(options) {
25486
26531
  if (options.env.VIBERAVEN_AGENT === "1") return "agent-scan";
25487
26532
  return "local-ui";
25488
26533
  }
26534
+ function resolvePositionalCwd(positional) {
26535
+ return positional[0] ? (0, import_node_path33.resolve)(process.cwd(), positional[0]) : process.cwd();
26536
+ }
25489
26537
  function formatScanJsonStdout(artifact) {
25490
26538
  return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
25491
26539
  }
@@ -25526,7 +26574,7 @@ async function cmdStatus(flags, positional) {
25526
26574
  console.log("Not signed in. Run: viberaven login");
25527
26575
  return 1;
25528
26576
  }
25529
- const startDir = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
26577
+ const startDir = resolvePositionalCwd(positional);
25530
26578
  let artifact;
25531
26579
  try {
25532
26580
  artifact = await loadLastArtifact(startDir);
@@ -25680,7 +26728,7 @@ async function cmdWatch(flags) {
25680
26728
  }
25681
26729
  }
25682
26730
  async function runScanCommand(flags, positional, options) {
25683
- const workspacePath = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
26731
+ const workspacePath = positional[0] ? resolvePositionalCwd(positional) : await resolveWorkspaceRoot(process.cwd());
25684
26732
  const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
25685
26733
  let accessToken;
25686
26734
  try {
@@ -25764,7 +26812,7 @@ async function runScanCommand(flags, positional, options) {
25764
26812
  return { exitCode: 0, artifacts: paths };
25765
26813
  }
25766
26814
  async function cmdReport(flags, positional) {
25767
- const startDir = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
26815
+ const startDir = resolvePositionalCwd(positional);
25768
26816
  try {
25769
26817
  const paths = await refreshReportFromDisk(startDir);
25770
26818
  console.log(`Report refreshed: ${paths.reportPath}`);
@@ -25786,7 +26834,7 @@ async function cmdReport(flags, positional) {
25786
26834
  }
25787
26835
  }
25788
26836
  async function cmdPrompt(flags, positional) {
25789
- const startDir = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
26837
+ const startDir = resolvePositionalCwd(positional);
25790
26838
  let artifact;
25791
26839
  try {
25792
26840
  artifact = await loadLastArtifact(startDir);
@@ -25883,7 +26931,7 @@ async function main() {
25883
26931
  const wantsJsonl = hasFlag(flags, "jsonl");
25884
26932
  const wantsStrict = hasFlag(flags, "strict");
25885
26933
  if (flags.condense) {
25886
- const cwd = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
26934
+ const cwd = resolvePositionalCwd(positional);
25887
26935
  const result = await runCondenseCommand({ cwd });
25888
26936
  console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
25889
26937
  return 0;
@@ -25903,7 +26951,7 @@ async function main() {
25903
26951
  }
25904
26952
  if (!command && flags.verify === true && typeof flags.action === "string") {
25905
26953
  return runVerifyActionCommand({
25906
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd(),
26954
+ cwd: resolvePositionalCwd(positional),
25907
26955
  actionId: flags.action
25908
26956
  });
25909
26957
  }
@@ -25940,7 +26988,7 @@ async function main() {
25940
26988
  env: process.env
25941
26989
  });
25942
26990
  if (mode === "local-ui") {
25943
- const cwd = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
26991
+ const cwd = resolvePositionalCwd(positional);
25944
26992
  const handle = await startLocalUiServer({ cwd });
25945
26993
  console.log(`VibeRaven local UI: ${handle.url}`);
25946
26994
  await waitForServerShutdown();
@@ -25960,7 +27008,7 @@ async function main() {
25960
27008
  await runInteractiveSession();
25961
27009
  return 0;
25962
27010
  case "ui": {
25963
- const cwd = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
27011
+ const cwd = resolvePositionalCwd(positional);
25964
27012
  const handle = await startLocalUiServer({ cwd });
25965
27013
  console.log(`VibeRaven local UI: ${handle.url}`);
25966
27014
  await waitForServerShutdown();
@@ -25977,19 +27025,19 @@ async function main() {
25977
27025
  return cmdStatus(flags, positional);
25978
27026
  case "actions":
25979
27027
  return runActionsCommand({
25980
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd(),
27028
+ cwd: resolvePositionalCwd(positional),
25981
27029
  json: Boolean(flags.json)
25982
27030
  });
25983
27031
  case "preview":
25984
27032
  return runPreviewCommand({
25985
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd(),
27033
+ cwd: resolvePositionalCwd(positional),
25986
27034
  agentMode: flags["agent-mode"] === true,
25987
27035
  json: Boolean(flags.json)
25988
27036
  });
25989
27037
  case "next":
25990
27038
  return runNextCommand({
25991
27039
  json: Boolean(flags.json),
25992
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd()
27040
+ cwd: resolvePositionalCwd(positional)
25993
27041
  });
25994
27042
  case "guide": {
25995
27043
  const provider2 = positional[0];
@@ -26027,7 +27075,7 @@ async function main() {
26027
27075
  case "provider-verify":
26028
27076
  return cmdProviderVerify(flags, positional);
26029
27077
  case "init": {
26030
- const cwd = positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd();
27078
+ const cwd = resolvePositionalCwd(positional);
26031
27079
  const agents = typeof flags.agents === "string" ? flags.agents : void 0;
26032
27080
  return runInitCommand({
26033
27081
  cwd,
@@ -26041,7 +27089,7 @@ async function main() {
26041
27089
  return 1;
26042
27090
  }
26043
27091
  return runDoctorAgentsCommand({
26044
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd()
27092
+ cwd: resolvePositionalCwd(positional)
26045
27093
  });
26046
27094
  case "validate-npm-package":
26047
27095
  return runValidateNpmPackageCommand({
@@ -26054,7 +27102,7 @@ async function main() {
26054
27102
  return 1;
26055
27103
  }
26056
27104
  return runAuditCommand({
26057
- cwd: positional[0] ? (0, import_node_path33.join)(process.cwd(), positional[0]) : process.cwd(),
27105
+ cwd: resolvePositionalCwd(positional),
26058
27106
  json: Boolean(flags.json)
26059
27107
  });
26060
27108
  default: