codeam-cli 2.75.25 → 2.75.26
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.
- package/CHANGELOG.md +7 -0
- package/dist/index.js +726 -120
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.75.25] — 2026-09-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Send every runnable repo script with the preview detection
|
|
12
|
+
- **cli:** Install preview deps in the background at session start; room for monorepo builds
|
|
13
|
+
|
|
7
14
|
## [2.75.23] — 2026-09-25
|
|
8
15
|
|
|
9
16
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -3248,6 +3248,9 @@ var USER_EVENTS = {
|
|
|
3248
3248
|
PREVIEW_STOPPED: "preview_stopped",
|
|
3249
3249
|
PREVIEW_ERROR: "preview_error",
|
|
3250
3250
|
PREVIEW_PROGRESS: "preview_progress",
|
|
3251
|
+
/** The agent points at an element of the running preview (`highlight_element`
|
|
3252
|
+
* MCP tool) — the inspector client draws the agent's mark over it. */
|
|
3253
|
+
PREVIEW_AGENT_HIGHLIGHT: "preview_agent_highlight",
|
|
3251
3254
|
BEADS_STATE_CHANGED: "beads_state_changed",
|
|
3252
3255
|
BEADS_PROVISIONING: "beads_provisioning",
|
|
3253
3256
|
BEADS_TEAM_MEMORY_CHANGED: "beads_team_memory_changed",
|
|
@@ -10509,7 +10512,7 @@ function readAnonId() {
|
|
|
10509
10512
|
}
|
|
10510
10513
|
function superProperties() {
|
|
10511
10514
|
return {
|
|
10512
|
-
cliVersion: true ? "2.75.
|
|
10515
|
+
cliVersion: true ? "2.75.26" : "0.0.0-dev",
|
|
10513
10516
|
nodeVersion: process.version,
|
|
10514
10517
|
platform: process.platform,
|
|
10515
10518
|
arch: process.arch,
|
|
@@ -10751,7 +10754,7 @@ var http = __toESM(require("http"));
|
|
|
10751
10754
|
// package.json
|
|
10752
10755
|
var package_default = {
|
|
10753
10756
|
name: "codeam-cli",
|
|
10754
|
-
version: "2.75.
|
|
10757
|
+
version: "2.75.26",
|
|
10755
10758
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
10756
10759
|
type: "commonjs",
|
|
10757
10760
|
main: "dist/index.js",
|
|
@@ -12081,7 +12084,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
12081
12084
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
12082
12085
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
12083
12086
|
// pair/reconnect). Older backends ignore the extra field.
|
|
12084
|
-
..."2.75.
|
|
12087
|
+
..."2.75.26" ? { ideVersion: "2.75.26" } : {}
|
|
12085
12088
|
};
|
|
12086
12089
|
try {
|
|
12087
12090
|
const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
|
|
@@ -24628,6 +24631,145 @@ function inspectorClientSource(opts) {
|
|
|
24628
24631
|
redrawMarks();
|
|
24629
24632
|
}
|
|
24630
24633
|
|
|
24634
|
+
/**
|
|
24635
|
+
* Las marcas del AGENTE \u2014 la herramienta highlight_element.
|
|
24636
|
+
*
|
|
24637
|
+
* Van aparte de las del usuario a proposito: no se numeran, no se pueden
|
|
24638
|
+
* borrar desde el composer y no dependen de que el modo inspector este
|
|
24639
|
+
* encendido. Son el agente SE\xD1ALANDO algo, y el usuario las ve aunque no
|
|
24640
|
+
* haya tocado nada: un cursor que viaja hasta el elemento, lo centra en
|
|
24641
|
+
* pantalla y deja una caja con su etiqueta.
|
|
24642
|
+
*
|
|
24643
|
+
* Mismo anclaje que las marcas fijadas (coordenadas de documento), asi que
|
|
24644
|
+
* acompa\xF1an al scroll. Un selector que no encuentra nada no pinta nada: el
|
|
24645
|
+
* agente ya sabe por la herramienta que puede fallar.
|
|
24646
|
+
*/
|
|
24647
|
+
var AGENT = '#22d3ee';
|
|
24648
|
+
var agentMarks = [];
|
|
24649
|
+
var agentHost = null;
|
|
24650
|
+
var agentCursor = null;
|
|
24651
|
+
var agentStyle = null;
|
|
24652
|
+
var agentListening = false;
|
|
24653
|
+
|
|
24654
|
+
function ensureAgentStyle() {
|
|
24655
|
+
if (agentStyle && agentStyle.parentNode) return;
|
|
24656
|
+
agentStyle = document.createElement('style');
|
|
24657
|
+
agentStyle.setAttribute('data-codeam', 'agent-style');
|
|
24658
|
+
agentStyle.textContent =
|
|
24659
|
+
'@keyframes codeam-agent-pulse{0%{box-shadow:0 0 0 0 rgba(34,211,238,.55)}' +
|
|
24660
|
+
'70%{box-shadow:0 0 0 10px rgba(34,211,238,0)}100%{box-shadow:0 0 0 0 rgba(34,211,238,0)}}' +
|
|
24661
|
+
'@keyframes codeam-agent-in{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:scale(1)}}';
|
|
24662
|
+
(document.head || document.documentElement).appendChild(agentStyle);
|
|
24663
|
+
}
|
|
24664
|
+
|
|
24665
|
+
function ensureAgentCursor() {
|
|
24666
|
+
if (agentCursor && agentCursor.parentNode) return agentCursor;
|
|
24667
|
+
agentCursor = document.createElement('div');
|
|
24668
|
+
agentCursor.setAttribute('data-codeam', 'agent-cursor');
|
|
24669
|
+
agentCursor.style.cssText = [
|
|
24670
|
+
'position:fixed', 'pointer-events:none', 'z-index:2147483647',
|
|
24671
|
+
'left:' + Math.round(window.innerWidth / 2) + 'px', 'top:' + Math.round(window.innerHeight - 40) + 'px',
|
|
24672
|
+
'transition:left .6s cubic-bezier(.22,1,.36,1),top .6s cubic-bezier(.22,1,.36,1),opacity .3s',
|
|
24673
|
+
'opacity:0'
|
|
24674
|
+
].join(';');
|
|
24675
|
+
agentCursor.innerHTML =
|
|
24676
|
+
'<svg width="22" height="22" viewBox="0 0 24 24" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))">' +
|
|
24677
|
+
'<path d="M4 2l15 8.5-6.6 1.6L9 18.5z" fill="' + AGENT + '" stroke="#000" stroke-width="1.2"/></svg>' +
|
|
24678
|
+
'<div style="margin:2px 0 0 14px;background:' + AGENT + ';color:#000;font:700 10px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;' +
|
|
24679
|
+
'padding:1px 6px;border-radius:8px;white-space:nowrap">Agent</div>';
|
|
24680
|
+
document.body.appendChild(agentCursor);
|
|
24681
|
+
return agentCursor;
|
|
24682
|
+
}
|
|
24683
|
+
|
|
24684
|
+
function redrawAgentMarks() {
|
|
24685
|
+
if (agentMarks.length === 0) {
|
|
24686
|
+
if (agentHost && agentHost.parentNode) agentHost.parentNode.removeChild(agentHost);
|
|
24687
|
+
agentHost = null;
|
|
24688
|
+
return;
|
|
24689
|
+
}
|
|
24690
|
+
if (!agentHost || !agentHost.parentNode) {
|
|
24691
|
+
agentHost = document.createElement('div');
|
|
24692
|
+
agentHost.setAttribute('data-codeam', 'agent-marks');
|
|
24693
|
+
agentHost.style.cssText = 'position:absolute;left:0;top:0;width:0;height:0;z-index:2147483646';
|
|
24694
|
+
document.body.appendChild(agentHost);
|
|
24695
|
+
}
|
|
24696
|
+
agentHost.innerHTML = '';
|
|
24697
|
+
var sx = window.scrollX || window.pageXOffset || 0;
|
|
24698
|
+
var sy = window.scrollY || window.pageYOffset || 0;
|
|
24699
|
+
for (var i = 0; i < agentMarks.length; i++) {
|
|
24700
|
+
var m = agentMarks[i];
|
|
24701
|
+
if (!m.el.isConnected) continue;
|
|
24702
|
+
var r = m.el.getBoundingClientRect();
|
|
24703
|
+
var box = document.createElement('div');
|
|
24704
|
+
box.setAttribute('data-codeam', 'agent-mark');
|
|
24705
|
+
box.style.cssText = [
|
|
24706
|
+
'position:absolute', 'pointer-events:none', 'box-sizing:border-box',
|
|
24707
|
+
'border:2px solid ' + AGENT, 'background:rgba(34,211,238,0.10)', 'border-radius:4px',
|
|
24708
|
+
'left:' + (r.left + sx - 3) + 'px', 'top:' + (r.top + sy - 3) + 'px',
|
|
24709
|
+
'width:' + (r.width + 6) + 'px', 'height:' + (r.height + 6) + 'px',
|
|
24710
|
+
'animation:codeam-agent-in .25s ease-out,codeam-agent-pulse 1.6s ease-out 3'
|
|
24711
|
+
].join(';');
|
|
24712
|
+
if (m.label) {
|
|
24713
|
+
var tag = document.createElement('div');
|
|
24714
|
+
tag.setAttribute('data-codeam', 'agent-mark-label');
|
|
24715
|
+
tag.style.cssText = [
|
|
24716
|
+
'position:absolute', 'left:-2px', (r.top >= 22 ? 'top:-22px' : 'bottom:-22px'),
|
|
24717
|
+
'background:' + AGENT, 'color:#000', 'font:700 11px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace',
|
|
24718
|
+
'padding:0 6px', 'border-radius:3px', 'white-space:nowrap', 'max-width:260px',
|
|
24719
|
+
'overflow:hidden', 'text-overflow:ellipsis'
|
|
24720
|
+
].join(';');
|
|
24721
|
+
tag.textContent = m.label;
|
|
24722
|
+
box.appendChild(tag);
|
|
24723
|
+
}
|
|
24724
|
+
agentHost.appendChild(box);
|
|
24725
|
+
}
|
|
24726
|
+
}
|
|
24727
|
+
|
|
24728
|
+
function listenAgentLayout() {
|
|
24729
|
+
if (agentListening) return;
|
|
24730
|
+
agentListening = true;
|
|
24731
|
+
window.addEventListener('scroll', redrawAgentMarks, true);
|
|
24732
|
+
window.addEventListener('resize', redrawAgentMarks);
|
|
24733
|
+
}
|
|
24734
|
+
|
|
24735
|
+
function agentHighlight(selector, labelText) {
|
|
24736
|
+
var el = null;
|
|
24737
|
+
try { el = document.querySelector(String(selector)); } catch (e) { el = null; }
|
|
24738
|
+
if (!el || !el.getBoundingClientRect) return false;
|
|
24739
|
+
ensureAgentStyle();
|
|
24740
|
+
listenAgentLayout();
|
|
24741
|
+
try { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' }); } catch (e) { /* viejo */ }
|
|
24742
|
+
var cursor = ensureAgentCursor();
|
|
24743
|
+
// Tras el scroll suave: el cursor viaja al centro del elemento y, al
|
|
24744
|
+
// llegar, aparece la caja. El orden es lo que se lee como \xABel agente
|
|
24745
|
+
// lo esta se\xF1alando\xBB y no como un recuadro que salta.
|
|
24746
|
+
setTimeout(function () {
|
|
24747
|
+
var r = el.getBoundingClientRect();
|
|
24748
|
+
cursor.style.opacity = '1';
|
|
24749
|
+
cursor.style.left = Math.round(r.left + Math.min(r.width / 2, 40)) + 'px';
|
|
24750
|
+
cursor.style.top = Math.round(r.top + Math.min(r.height / 2, 24)) + 'px';
|
|
24751
|
+
setTimeout(function () {
|
|
24752
|
+
agentMarks.push({ el: el, label: labelText ? String(labelText).slice(0, 80) : '' });
|
|
24753
|
+
redrawAgentMarks();
|
|
24754
|
+
}, 620);
|
|
24755
|
+
}, 380);
|
|
24756
|
+
return true;
|
|
24757
|
+
}
|
|
24758
|
+
|
|
24759
|
+
function agentClear() {
|
|
24760
|
+
agentMarks = [];
|
|
24761
|
+
redrawAgentMarks();
|
|
24762
|
+
if (agentCursor && agentCursor.parentNode) agentCursor.parentNode.removeChild(agentCursor);
|
|
24763
|
+
agentCursor = null;
|
|
24764
|
+
if (agentStyle && agentStyle.parentNode) agentStyle.parentNode.removeChild(agentStyle);
|
|
24765
|
+
agentStyle = null;
|
|
24766
|
+
if (agentListening) {
|
|
24767
|
+
window.removeEventListener('scroll', redrawAgentMarks, true);
|
|
24768
|
+
window.removeEventListener('resize', redrawAgentMarks);
|
|
24769
|
+
agentListening = false;
|
|
24770
|
+
}
|
|
24771
|
+
}
|
|
24772
|
+
|
|
24631
24773
|
function enable(origin) {
|
|
24632
24774
|
if (enabled) return;
|
|
24633
24775
|
enabled = true;
|
|
@@ -24661,6 +24803,8 @@ function inspectorClientSource(opts) {
|
|
|
24661
24803
|
if (data.type === 'enable') enable(e.origin);
|
|
24662
24804
|
else if (data.type === 'disable') disable();
|
|
24663
24805
|
else if (data.type === 'marks' && data.ids) syncMarks(data.ids);
|
|
24806
|
+
else if (data.type === 'agent-highlight' && data.selector) agentHighlight(data.selector, data.label);
|
|
24807
|
+
else if (data.type === 'agent-clear') agentClear();
|
|
24664
24808
|
});
|
|
24665
24809
|
|
|
24666
24810
|
/**
|
|
@@ -24679,6 +24823,11 @@ function inspectorClientSource(opts) {
|
|
|
24679
24823
|
window.__codeamInspector = {
|
|
24680
24824
|
enable: function () { enable('react-native'); },
|
|
24681
24825
|
disable: disable,
|
|
24826
|
+
// La app movil ya llamaba \`marks\` al borrar un chip y no existia:
|
|
24827
|
+
// la marca se quedaba pintada en la pagina.
|
|
24828
|
+
marks: function (ids) { if (ids && ids.length !== undefined) syncMarks(ids); },
|
|
24829
|
+
highlight: function (selector, text) { return agentHighlight(selector, text); },
|
|
24830
|
+
clear: agentClear,
|
|
24682
24831
|
};
|
|
24683
24832
|
}
|
|
24684
24833
|
} catch (e) {
|
|
@@ -26518,9 +26667,9 @@ async function configureBeads(action, ctx, deps) {
|
|
|
26518
26667
|
return { enabled: false, running: false };
|
|
26519
26668
|
}
|
|
26520
26669
|
const p3 = await deps.probe();
|
|
26521
|
-
const
|
|
26522
|
-
deps.emit({ type: "beads_status", state:
|
|
26523
|
-
return { enabled:
|
|
26670
|
+
const running3 = p3.serverUp && p3.bdAvailable;
|
|
26671
|
+
deps.emit({ type: "beads_status", state: running3 ? "enabled" : "disabled", running: running3, ...p3 });
|
|
26672
|
+
return { enabled: running3, running: running3, ...p3 };
|
|
26524
26673
|
}
|
|
26525
26674
|
if (action === "disable") {
|
|
26526
26675
|
deps.persist({ enabled: false });
|
|
@@ -26537,9 +26686,9 @@ async function configureBeads(action, ctx, deps) {
|
|
|
26537
26686
|
return { enabled: false, ...p2 };
|
|
26538
26687
|
}
|
|
26539
26688
|
await deps.startWatcher().catch(() => void 0);
|
|
26540
|
-
const
|
|
26541
|
-
deps.emit({ type: "beads_status", state: "enabled", running, ...p2 });
|
|
26542
|
-
return { enabled: true, running, ...p2 };
|
|
26689
|
+
const running2 = true;
|
|
26690
|
+
deps.emit({ type: "beads_status", state: "enabled", running: running2, ...p2 });
|
|
26691
|
+
return { enabled: true, running: running2, ...p2 };
|
|
26543
26692
|
}
|
|
26544
26693
|
|
|
26545
26694
|
// src/skills/configure.ts
|
|
@@ -27759,105 +27908,88 @@ async function runDetectOneShot(generate, timeoutMs = PREVIEW_DETECT_TIMEOUT_MS)
|
|
|
27759
27908
|
if (timer) clearTimeout(timer);
|
|
27760
27909
|
}
|
|
27761
27910
|
}
|
|
27762
|
-
|
|
27763
|
-
if (
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
27911
|
+
function originPayload(payload, origin) {
|
|
27912
|
+
if (origin !== "agent") return payload;
|
|
27913
|
+
return { ...payload ?? {}, origin };
|
|
27914
|
+
}
|
|
27915
|
+
async function resolvePreviewDetection(args2) {
|
|
27916
|
+
const { ctx, runtime, pluginAuthToken, origin } = args2;
|
|
27917
|
+
const emit2 = (type, payload) => {
|
|
27769
27918
|
emitPreviewEvent({
|
|
27770
27919
|
sessionId: ctx.sessionId,
|
|
27771
27920
|
pluginId: ctx.pluginId,
|
|
27772
|
-
pluginAuthToken
|
|
27773
|
-
type
|
|
27774
|
-
payload:
|
|
27775
|
-
stage: "detection",
|
|
27776
|
-
message: `Preview detection isn't available on ${ctx.runtime.id} sessions yet \u2014 link a Claude or Codex agent.`
|
|
27777
|
-
}
|
|
27921
|
+
pluginAuthToken,
|
|
27922
|
+
type,
|
|
27923
|
+
payload: originPayload(payload, origin)
|
|
27778
27924
|
});
|
|
27779
|
-
|
|
27925
|
+
};
|
|
27926
|
+
if (typeof runtime.generateOneShot !== "function") {
|
|
27927
|
+
log.info("preview", `runtime ${runtime.id} has no generateOneShot \u2014 emitting unsupported`);
|
|
27928
|
+
emit2(USER_EVENTS.PREVIEW_ERROR, {
|
|
27929
|
+
stage: "detection",
|
|
27930
|
+
message: `Preview detection isn't available on ${runtime.id} sessions yet \u2014 link a Claude or Codex agent.`
|
|
27931
|
+
});
|
|
27932
|
+
return null;
|
|
27780
27933
|
}
|
|
27781
|
-
const
|
|
27782
|
-
const
|
|
27783
|
-
|
|
27784
|
-
|
|
27785
|
-
|
|
27786
|
-
|
|
27787
|
-
|
|
27788
|
-
|
|
27789
|
-
|
|
27790
|
-
|
|
27791
|
-
|
|
27792
|
-
|
|
27793
|
-
|
|
27794
|
-
|
|
27795
|
-
|
|
27796
|
-
|
|
27797
|
-
sessionId: ctx.sessionId,
|
|
27798
|
-
pluginId: ctx.pluginId,
|
|
27799
|
-
pluginAuthToken,
|
|
27800
|
-
type: USER_EVENTS.PREVIEW_DETECTION_PENDING
|
|
27934
|
+
const generateOneShot = runtime.generateOneShot.bind(runtime);
|
|
27935
|
+
const fromFile = await readPreviewConfig(process.cwd());
|
|
27936
|
+
if (fromFile) {
|
|
27937
|
+
log.info("preview", `detect: using .codeam/preview.json (${fromFile.framework})`);
|
|
27938
|
+
return fromFile;
|
|
27939
|
+
}
|
|
27940
|
+
emit2(USER_EVENTS.PREVIEW_DETECTION_PENDING);
|
|
27941
|
+
log.info("preview", "detect: invoking generateOneShot");
|
|
27942
|
+
const startedAt = Date.now();
|
|
27943
|
+
const { raw, timedOut, stderr } = await runDetectOneShot(generateOneShot);
|
|
27944
|
+
const tookMs = Date.now() - startedAt;
|
|
27945
|
+
if (timedOut) {
|
|
27946
|
+
log.info("preview", `detect: timed out after ${tookMs}ms \u2014 emitting preview_error`);
|
|
27947
|
+
emit2(USER_EVENTS.PREVIEW_ERROR, {
|
|
27948
|
+
stage: "detection",
|
|
27949
|
+
message: `Project detection timed out after ${Math.round(PREVIEW_DETECT_TIMEOUT_MS / 1e3)} s \u2014 the agent did not answer. Try again, or add a .codeam/preview.json override.`
|
|
27801
27950
|
});
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
|
|
27807
|
-
|
|
27808
|
-
|
|
27809
|
-
|
|
27810
|
-
pluginId: ctx.pluginId,
|
|
27811
|
-
pluginAuthToken,
|
|
27812
|
-
type: USER_EVENTS.PREVIEW_ERROR,
|
|
27813
|
-
payload: {
|
|
27814
|
-
stage: "detection",
|
|
27815
|
-
message: `Project detection timed out after ${Math.round(PREVIEW_DETECT_TIMEOUT_MS / 1e3)} s \u2014 the agent did not answer. Try again, or add a .codeam/preview.json override.`
|
|
27816
|
-
}
|
|
27817
|
-
});
|
|
27818
|
-
return;
|
|
27819
|
-
}
|
|
27820
|
-
const detection = safeParseDetection(raw);
|
|
27821
|
-
if (!detection) {
|
|
27822
|
-
const failure = describeDetectionFailure(raw);
|
|
27823
|
-
log.info(
|
|
27824
|
-
"preview",
|
|
27825
|
-
`detect: failed after ${tookMs}ms reason=${failure?.reason ?? "unknown"}` + (failure?.missing ? ` missing=${failure.missing.join(",")}` : "") + `
|
|
27951
|
+
return null;
|
|
27952
|
+
}
|
|
27953
|
+
const detection = safeParseDetection(raw);
|
|
27954
|
+
if (!detection) {
|
|
27955
|
+
const failure = describeDetectionFailure(raw);
|
|
27956
|
+
log.info(
|
|
27957
|
+
"preview",
|
|
27958
|
+
`detect: failed after ${tookMs}ms reason=${failure?.reason ?? "unknown"}` + (failure?.missing ? ` missing=${failure.missing.join(",")}` : "") + `
|
|
27826
27959
|
--- salida cruda del agente (acotada) ---
|
|
27827
27960
|
${failure?.rawExcerpt ?? ""}`
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
|
|
27833
|
-
|
|
27834
|
-
payload: {
|
|
27835
|
-
stage: "detection",
|
|
27836
|
-
// El mensaje sale del diagnostico: decir "JSON invalido" cuando el
|
|
27837
|
-
// agente no contesto manda al usuario a mirar un JSON que no existe.
|
|
27838
|
-
message: (failure?.reason === "no_output" ? describeOneShotAgentError(stderr) : null) ?? failure?.message ?? "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
|
|
27839
|
-
}
|
|
27840
|
-
});
|
|
27841
|
-
return;
|
|
27842
|
-
}
|
|
27843
|
-
if (isUnsupportedDetection(detection)) {
|
|
27844
|
-
log.info("preview", "detect: framework=unsupported");
|
|
27845
|
-
emitPreviewEvent({
|
|
27846
|
-
sessionId: ctx.sessionId,
|
|
27847
|
-
pluginId: ctx.pluginId,
|
|
27848
|
-
pluginAuthToken,
|
|
27849
|
-
type: USER_EVENTS.PREVIEW_ERROR,
|
|
27850
|
-
payload: {
|
|
27851
|
-
stage: "unsupported",
|
|
27852
|
-
message: detection.notes ?? "No dev server applies to this project."
|
|
27853
|
-
}
|
|
27854
|
-
});
|
|
27855
|
-
return;
|
|
27856
|
-
}
|
|
27857
|
-
log.info("preview", `detect: ${detection.framework} on :${detection.port} (took ${tookMs}ms)`);
|
|
27858
|
-
void writePreviewConfig(process.cwd(), detection).catch((err) => {
|
|
27859
|
-
log.info("preview", `detect: writePreviewConfig failed (non-fatal): ${String(err)}`);
|
|
27961
|
+
);
|
|
27962
|
+
emit2(USER_EVENTS.PREVIEW_ERROR, {
|
|
27963
|
+
stage: "detection",
|
|
27964
|
+
// El mensaje sale del diagnostico: decir "JSON invalido" cuando el
|
|
27965
|
+
// agente no contesto manda al usuario a mirar un JSON que no existe.
|
|
27966
|
+
message: (failure?.reason === "no_output" ? describeOneShotAgentError(stderr) : null) ?? failure?.message ?? "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
|
|
27860
27967
|
});
|
|
27968
|
+
return null;
|
|
27969
|
+
}
|
|
27970
|
+
if (isUnsupportedDetection(detection)) {
|
|
27971
|
+
log.info("preview", "detect: framework=unsupported");
|
|
27972
|
+
emit2(USER_EVENTS.PREVIEW_ERROR, {
|
|
27973
|
+
stage: "unsupported",
|
|
27974
|
+
message: detection.notes ?? "No dev server applies to this project."
|
|
27975
|
+
});
|
|
27976
|
+
return null;
|
|
27977
|
+
}
|
|
27978
|
+
log.info("preview", `detect: ${detection.framework} on :${detection.port} (took ${tookMs}ms)`);
|
|
27979
|
+
void writePreviewConfig(process.cwd(), detection).catch((err) => {
|
|
27980
|
+
log.info("preview", `detect: writePreviewConfig failed (non-fatal): ${String(err)}`);
|
|
27981
|
+
});
|
|
27982
|
+
return detection;
|
|
27983
|
+
}
|
|
27984
|
+
var requestPreviewDetectH = (ctx) => {
|
|
27985
|
+
if (!ctx.pluginAuthToken) {
|
|
27986
|
+
log.info("preview", "no pluginAuthToken \u2014 skipping detect");
|
|
27987
|
+
return;
|
|
27988
|
+
}
|
|
27989
|
+
const pluginAuthToken = ctx.pluginAuthToken;
|
|
27990
|
+
void (async () => {
|
|
27991
|
+
const detection = await resolvePreviewDetection({ ctx, runtime: ctx.runtime, pluginAuthToken });
|
|
27992
|
+
if (!detection) return;
|
|
27861
27993
|
emitPreviewEvent({
|
|
27862
27994
|
sessionId: ctx.sessionId,
|
|
27863
27995
|
pluginId: ctx.pluginId,
|
|
@@ -27940,19 +28072,21 @@ var previewStartH = (ctx, _cmd, parsed) => {
|
|
|
27940
28072
|
log.info("preview", "start: no detection in payload");
|
|
27941
28073
|
return;
|
|
27942
28074
|
}
|
|
27943
|
-
startPreviewFromDetection(ctx, rawDetection, ctx.pluginAuthToken);
|
|
28075
|
+
void startPreviewFromDetection(ctx, rawDetection, ctx.pluginAuthToken);
|
|
27944
28076
|
};
|
|
27945
|
-
function startPreviewFromDetection(ctx, detection, pluginAuthToken) {
|
|
28077
|
+
function startPreviewFromDetection(ctx, detection, pluginAuthToken, opts = {}) {
|
|
27946
28078
|
const emit2 = (type, payload) => {
|
|
28079
|
+
const tagged = originPayload(payload, opts.origin) ?? {};
|
|
28080
|
+
opts.onEvent?.(type, tagged);
|
|
27947
28081
|
emitPreviewEvent({
|
|
27948
28082
|
sessionId: ctx.sessionId,
|
|
27949
28083
|
pluginId: ctx.pluginId,
|
|
27950
28084
|
pluginAuthToken,
|
|
27951
28085
|
type,
|
|
27952
|
-
payload
|
|
28086
|
+
payload: tagged
|
|
27953
28087
|
});
|
|
27954
28088
|
};
|
|
27955
|
-
runPreviewStart({
|
|
28089
|
+
return runPreviewStart({
|
|
27956
28090
|
sessionId: ctx.sessionId,
|
|
27957
28091
|
detection,
|
|
27958
28092
|
cwd: process.cwd(),
|
|
@@ -27983,7 +28117,7 @@ function maybeAttachBuildHeal(ctx, pluginAuthToken) {
|
|
|
27983
28117
|
if (!activePreviews.has(ctx.sessionId)) return;
|
|
27984
28118
|
await killPreview(ctx.sessionId);
|
|
27985
28119
|
await new Promise((r) => setTimeout(r, 150));
|
|
27986
|
-
startPreviewFromDetection(ctx, preview.detection, pluginAuthToken);
|
|
28120
|
+
void startPreviewFromDetection(ctx, preview.detection, pluginAuthToken);
|
|
27987
28121
|
})();
|
|
27988
28122
|
},
|
|
27989
28123
|
notify: (message) => {
|
|
@@ -28029,7 +28163,7 @@ var previewRestartH = async (ctx, cmd) => {
|
|
|
28029
28163
|
}
|
|
28030
28164
|
await killPreview(ctx.sessionId);
|
|
28031
28165
|
await new Promise((r) => setTimeout(r, 150));
|
|
28032
|
-
startPreviewFromDetection(ctx, preview.detection, ctx.pluginAuthToken);
|
|
28166
|
+
void startPreviewFromDetection(ctx, preview.detection, ctx.pluginAuthToken);
|
|
28033
28167
|
await ctx.relay.sendResult(cmd.id, "completed", { restarted: true });
|
|
28034
28168
|
};
|
|
28035
28169
|
var savePreviewConfigH = (_ctx, _cmd, parsed) => {
|
|
@@ -29339,7 +29473,7 @@ async function resolveAcpAdapterWithRetry(agent, opts = {}) {
|
|
|
29339
29473
|
}
|
|
29340
29474
|
|
|
29341
29475
|
// src/agents/acp/runner.ts
|
|
29342
|
-
var
|
|
29476
|
+
var import_node_crypto11 = require("crypto");
|
|
29343
29477
|
var fs69 = __toESM(require("fs"));
|
|
29344
29478
|
var os53 = __toESM(require("os"));
|
|
29345
29479
|
var path80 = __toESM(require("path"));
|
|
@@ -39345,8 +39479,175 @@ async function mcpRouter() {
|
|
|
39345
39479
|
});
|
|
39346
39480
|
}
|
|
39347
39481
|
|
|
39482
|
+
// src/commands/preview-mcp.ts
|
|
39483
|
+
var PREVIEW_MCP_SERVER_NAME = "codeagent_preview";
|
|
39484
|
+
var PREVIEW_IPC_URL_ENV = "CODEAM_PREVIEW_IPC_URL";
|
|
39485
|
+
var PREVIEW_IPC_TOKEN_ENV = "CODEAM_PREVIEW_IPC_TOKEN";
|
|
39486
|
+
var START_TIMEOUT_MS = 27e4;
|
|
39487
|
+
var SHORT_TIMEOUT_MS = 2e4;
|
|
39488
|
+
var PREVIEW_TOOLS = [
|
|
39489
|
+
{
|
|
39490
|
+
name: "start_preview",
|
|
39491
|
+
description: "Open this project's live Preview in the user's CodeAgent app \u2014 the same thing as their Preview button: it installs dependencies if needed, starts the dev server, publishes it on a public URL and switches the user's screen to it. Use it after you change anything visual (UI, styles, layout, copy) so you and the user can check the result, or whenever the user asks to see the app. Returns the public URL (you can curl it) or the exact error. Reuses a preview that is already running. Usually leave the arguments out: the project is detected automatically. Pass command/args/port only if you know the exact dev command.",
|
|
39492
|
+
inputSchema: {
|
|
39493
|
+
type: "object",
|
|
39494
|
+
properties: {
|
|
39495
|
+
command: { type: "string", description: 'Dev server executable, e.g. "npm". Optional.' },
|
|
39496
|
+
args: { type: "array", items: { type: "string" }, description: 'e.g. ["run","dev"]. Optional.' },
|
|
39497
|
+
port: { type: "number", description: "Port the dev server listens on. Required with command." },
|
|
39498
|
+
framework: { type: "string", description: 'Label shown to the user, e.g. "next". Optional.' }
|
|
39499
|
+
},
|
|
39500
|
+
additionalProperties: false
|
|
39501
|
+
}
|
|
39502
|
+
},
|
|
39503
|
+
{
|
|
39504
|
+
name: "preview_status",
|
|
39505
|
+
description: "Current state of the Preview: running (with its URL), starting, error (with the reason) or idle. Use it when start_preview answered that the preview is still starting.",
|
|
39506
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
39507
|
+
},
|
|
39508
|
+
{
|
|
39509
|
+
name: "highlight_element",
|
|
39510
|
+
description: 'Point at an element in the running Preview: the user sees your cursor move to it and a labelled box drawn around it, scrolled into view. Use it to show the user exactly what you changed or what you are talking about ("this button", "the header spacing"). Takes a CSS selector (prefer ids, data-testid or specific classes you saw in the code). Several calls add several marks; clear_highlights removes them.',
|
|
39511
|
+
inputSchema: {
|
|
39512
|
+
type: "object",
|
|
39513
|
+
properties: {
|
|
39514
|
+
selector: { type: "string", description: 'CSS selector, e.g. "#checkout" or "[data-testid=hero-cta]".' },
|
|
39515
|
+
label: { type: "string", description: 'Short caption on the mark, e.g. "New CTA". Optional.' }
|
|
39516
|
+
},
|
|
39517
|
+
required: ["selector"],
|
|
39518
|
+
additionalProperties: false
|
|
39519
|
+
}
|
|
39520
|
+
},
|
|
39521
|
+
{
|
|
39522
|
+
name: "clear_highlights",
|
|
39523
|
+
description: "Remove every mark you drew in the Preview with highlight_element.",
|
|
39524
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
39525
|
+
},
|
|
39526
|
+
{
|
|
39527
|
+
name: "stop_preview",
|
|
39528
|
+
description: "Stop the Preview (dev server + public URL). Only when the user asks, or to restart it cleanly.",
|
|
39529
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
39530
|
+
}
|
|
39531
|
+
];
|
|
39532
|
+
function previewMcpServer(bridge) {
|
|
39533
|
+
if (!bridge) return null;
|
|
39534
|
+
return {
|
|
39535
|
+
name: PREVIEW_MCP_SERVER_NAME,
|
|
39536
|
+
// Same runtime + entrypoint as this CLI — never trust the agent's PATH.
|
|
39537
|
+
command: process.execPath,
|
|
39538
|
+
args: [process.argv[1], "preview-mcp"],
|
|
39539
|
+
env: [
|
|
39540
|
+
{ name: PREVIEW_IPC_URL_ENV, value: bridge.url },
|
|
39541
|
+
{ name: PREVIEW_IPC_TOKEN_ENV, value: bridge.token }
|
|
39542
|
+
]
|
|
39543
|
+
};
|
|
39544
|
+
}
|
|
39545
|
+
function httpCall(baseUrl, token) {
|
|
39546
|
+
return async (path96, method, body, timeoutMs) => {
|
|
39547
|
+
const ac = new AbortController();
|
|
39548
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
39549
|
+
try {
|
|
39550
|
+
const res = await fetch(`${baseUrl}${path96}`, {
|
|
39551
|
+
method,
|
|
39552
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
39553
|
+
...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {},
|
|
39554
|
+
signal: ac.signal
|
|
39555
|
+
});
|
|
39556
|
+
return await res.json();
|
|
39557
|
+
} finally {
|
|
39558
|
+
clearTimeout(timer);
|
|
39559
|
+
}
|
|
39560
|
+
};
|
|
39561
|
+
}
|
|
39562
|
+
async function runPreviewTool(name, args2, call) {
|
|
39563
|
+
switch (name) {
|
|
39564
|
+
case "start_preview":
|
|
39565
|
+
return describeResult(await call("/preview/start", "POST", args2, START_TIMEOUT_MS));
|
|
39566
|
+
case "preview_status":
|
|
39567
|
+
return describeResult(await call("/preview/status", "GET", void 0, SHORT_TIMEOUT_MS));
|
|
39568
|
+
case "highlight_element":
|
|
39569
|
+
return describeAck(await call("/preview/highlight", "POST", args2, SHORT_TIMEOUT_MS));
|
|
39570
|
+
case "clear_highlights":
|
|
39571
|
+
return describeAck(await call("/preview/highlight", "POST", { clear: true }, SHORT_TIMEOUT_MS));
|
|
39572
|
+
case "stop_preview":
|
|
39573
|
+
return describeAck(await call("/preview/stop", "POST", {}, SHORT_TIMEOUT_MS));
|
|
39574
|
+
default:
|
|
39575
|
+
return { text: `Unknown tool: ${name}`, isError: true };
|
|
39576
|
+
}
|
|
39577
|
+
}
|
|
39578
|
+
function describeResult(raw) {
|
|
39579
|
+
const r = raw && typeof raw === "object" ? raw : {};
|
|
39580
|
+
switch (r.status) {
|
|
39581
|
+
case "running": {
|
|
39582
|
+
const inspector = r.inspector ? " You can point at elements with highlight_element." : " (No element highlighting for this framework.)";
|
|
39583
|
+
return {
|
|
39584
|
+
text: `Preview ${r.reused ? "already running" : "is live"}: ${String(r.url)} (${String(r.framework)}). The user's app is showing it now.${inspector}`,
|
|
39585
|
+
isError: false
|
|
39586
|
+
};
|
|
39587
|
+
}
|
|
39588
|
+
case "starting":
|
|
39589
|
+
return { text: String(r.message ?? "The preview is starting."), isError: false };
|
|
39590
|
+
case "idle":
|
|
39591
|
+
return { text: "No preview is running.", isError: false };
|
|
39592
|
+
case "error":
|
|
39593
|
+
return {
|
|
39594
|
+
text: `Preview failed at ${String(r.stage)}: ${String(r.message)}`,
|
|
39595
|
+
isError: true
|
|
39596
|
+
};
|
|
39597
|
+
default:
|
|
39598
|
+
return { text: `Unexpected answer from the preview bridge: ${JSON.stringify(raw)}`, isError: true };
|
|
39599
|
+
}
|
|
39600
|
+
}
|
|
39601
|
+
function describeAck(raw) {
|
|
39602
|
+
const r = raw && typeof raw === "object" ? raw : {};
|
|
39603
|
+
return { text: String(r.message ?? r.error ?? "done"), isError: r.ok !== true };
|
|
39604
|
+
}
|
|
39605
|
+
async function previewMcp() {
|
|
39606
|
+
const url2 = process.env[PREVIEW_IPC_URL_ENV];
|
|
39607
|
+
const token = process.env[PREVIEW_IPC_TOKEN_ENV];
|
|
39608
|
+
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
39609
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39610
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
39611
|
+
const call = url2 && token ? httpCall(url2, token) : null;
|
|
39612
|
+
const server = new Server({ name: PREVIEW_MCP_SERVER_NAME, version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
39613
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: PREVIEW_TOOLS }));
|
|
39614
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
39615
|
+
if (!call) {
|
|
39616
|
+
return {
|
|
39617
|
+
content: [{ type: "text", text: "Preview is not available in this session." }],
|
|
39618
|
+
isError: true
|
|
39619
|
+
};
|
|
39620
|
+
}
|
|
39621
|
+
try {
|
|
39622
|
+
const { text, isError: isError2 } = await runPreviewTool(
|
|
39623
|
+
req.params.name,
|
|
39624
|
+
req.params.arguments ?? {},
|
|
39625
|
+
call
|
|
39626
|
+
);
|
|
39627
|
+
return { content: [{ type: "text", text }], isError: isError2 };
|
|
39628
|
+
} catch (err) {
|
|
39629
|
+
const aborted = err instanceof Error && err.name === "AbortError";
|
|
39630
|
+
return {
|
|
39631
|
+
content: [
|
|
39632
|
+
{
|
|
39633
|
+
type: "text",
|
|
39634
|
+
text: aborted ? "The preview is taking long \u2014 it keeps going in the background; call preview_status shortly." : `Preview bridge unreachable: ${err instanceof Error ? err.message : String(err)}`
|
|
39635
|
+
}
|
|
39636
|
+
],
|
|
39637
|
+
isError: !aborted
|
|
39638
|
+
};
|
|
39639
|
+
}
|
|
39640
|
+
});
|
|
39641
|
+
await server.connect(new StdioServerTransport());
|
|
39642
|
+
}
|
|
39643
|
+
|
|
39348
39644
|
// src/integrations/provision.ts
|
|
39349
39645
|
function buildMcpServersForStart(ctx) {
|
|
39646
|
+
const servers = buildIntegrationMcpServers(ctx);
|
|
39647
|
+
const preview = previewMcpServer(ctx.preview ?? null);
|
|
39648
|
+
return preview ? [...servers, preview] : servers;
|
|
39649
|
+
}
|
|
39650
|
+
function buildIntegrationMcpServers(ctx) {
|
|
39350
39651
|
const manifest = readIntegrationsManifest();
|
|
39351
39652
|
if (!manifest || manifest.integrations.length === 0) return [];
|
|
39352
39653
|
if (!ctx.pluginAuthToken) {
|
|
@@ -39518,6 +39819,299 @@ function mergeWithLocalMcpServers(integrationServers) {
|
|
|
39518
39819
|
return merged;
|
|
39519
39820
|
}
|
|
39520
39821
|
|
|
39822
|
+
// src/commands/start/agent-preview-bridge.ts
|
|
39823
|
+
var http11 = __toESM(require("http"));
|
|
39824
|
+
var import_node_crypto10 = require("crypto");
|
|
39825
|
+
var START_WAIT_MS = 24e4;
|
|
39826
|
+
var MAX_SELECTOR = 500;
|
|
39827
|
+
var MAX_LABEL = 80;
|
|
39828
|
+
var AgentPreviewBridge = class {
|
|
39829
|
+
server = null;
|
|
39830
|
+
listening = null;
|
|
39831
|
+
token = (0, import_node_crypto10.randomBytes)(24).toString("hex");
|
|
39832
|
+
ctx = null;
|
|
39833
|
+
/** The bring-up in flight, so two tool calls never start two dev servers. */
|
|
39834
|
+
inflight = null;
|
|
39835
|
+
lastError = null;
|
|
39836
|
+
bound = null;
|
|
39837
|
+
/** Start the loopback server once. `null` when it could not bind — the
|
|
39838
|
+
* preview tools are then simply absent, never a failed session. */
|
|
39839
|
+
listen() {
|
|
39840
|
+
if (this.listening) return this.listening;
|
|
39841
|
+
this.listening = new Promise((resolve12) => {
|
|
39842
|
+
const server = http11.createServer((req, res) => {
|
|
39843
|
+
void this.handle(req, res);
|
|
39844
|
+
});
|
|
39845
|
+
server.on("error", (err) => {
|
|
39846
|
+
log.warn("agent-preview", `bridge failed to listen: ${String(err)}`);
|
|
39847
|
+
resolve12(null);
|
|
39848
|
+
});
|
|
39849
|
+
server.listen(0, "127.0.0.1", () => {
|
|
39850
|
+
const addr = server.address();
|
|
39851
|
+
if (!addr || typeof addr === "string") {
|
|
39852
|
+
resolve12(null);
|
|
39853
|
+
return;
|
|
39854
|
+
}
|
|
39855
|
+
server.unref();
|
|
39856
|
+
this.server = server;
|
|
39857
|
+
this.bound = { url: `http://127.0.0.1:${addr.port}`, token: this.token };
|
|
39858
|
+
resolve12(this.bound);
|
|
39859
|
+
});
|
|
39860
|
+
});
|
|
39861
|
+
return this.listening;
|
|
39862
|
+
}
|
|
39863
|
+
/** The bound address once {@link listen} resolved, else null (sync, for
|
|
39864
|
+
* the MCP list rebuilds that happen mid-session). */
|
|
39865
|
+
address() {
|
|
39866
|
+
return this.bound;
|
|
39867
|
+
}
|
|
39868
|
+
/** Bind the live session. Tool calls before this answer "still starting". */
|
|
39869
|
+
attach(ctx) {
|
|
39870
|
+
this.ctx = ctx;
|
|
39871
|
+
}
|
|
39872
|
+
close() {
|
|
39873
|
+
this.server?.close();
|
|
39874
|
+
this.server = null;
|
|
39875
|
+
}
|
|
39876
|
+
// ─── Operations (public for tests) ─────────────────────────────────────
|
|
39877
|
+
async start(override, waitMs = START_WAIT_MS) {
|
|
39878
|
+
const ctx = this.ctx;
|
|
39879
|
+
if (!ctx?.pluginAuthToken) {
|
|
39880
|
+
return {
|
|
39881
|
+
status: "error",
|
|
39882
|
+
stage: "session",
|
|
39883
|
+
message: "The session is still starting (or has no pairing token) \u2014 try again in a few seconds."
|
|
39884
|
+
};
|
|
39885
|
+
}
|
|
39886
|
+
if (!this.inflight) {
|
|
39887
|
+
const run = this.launch(ctx, ctx.pluginAuthToken, override);
|
|
39888
|
+
this.inflight = run;
|
|
39889
|
+
void run.finally(() => {
|
|
39890
|
+
if (this.inflight === run) this.inflight = null;
|
|
39891
|
+
});
|
|
39892
|
+
}
|
|
39893
|
+
const inflight = this.inflight;
|
|
39894
|
+
let timer;
|
|
39895
|
+
const pending = new Promise((resolve12) => {
|
|
39896
|
+
timer = setTimeout(
|
|
39897
|
+
() => resolve12({
|
|
39898
|
+
status: "starting",
|
|
39899
|
+
message: "The preview is still coming up (dependencies or a slow dev server). It keeps going in the background \u2014 call preview_status in a minute."
|
|
39900
|
+
}),
|
|
39901
|
+
waitMs
|
|
39902
|
+
);
|
|
39903
|
+
timer.unref?.();
|
|
39904
|
+
});
|
|
39905
|
+
try {
|
|
39906
|
+
return await Promise.race([inflight, pending]);
|
|
39907
|
+
} finally {
|
|
39908
|
+
if (timer) clearTimeout(timer);
|
|
39909
|
+
}
|
|
39910
|
+
}
|
|
39911
|
+
status() {
|
|
39912
|
+
const ctx = this.ctx;
|
|
39913
|
+
if (!ctx) return { status: "idle" };
|
|
39914
|
+
const active2 = serving(ctx.sessionId);
|
|
39915
|
+
if (active2) return running(active2, true);
|
|
39916
|
+
if (this.inflight) return { status: "starting", message: "The preview is starting." };
|
|
39917
|
+
if (this.lastError) return { status: "error", ...this.lastError };
|
|
39918
|
+
return { status: "idle" };
|
|
39919
|
+
}
|
|
39920
|
+
async highlight(input) {
|
|
39921
|
+
const ctx = this.ctx;
|
|
39922
|
+
if (!ctx?.pluginAuthToken) return { ok: false, message: "The session is still starting." };
|
|
39923
|
+
const active2 = serving(ctx.sessionId);
|
|
39924
|
+
if (!active2) {
|
|
39925
|
+
return { ok: false, message: "No preview is running \u2014 call start_preview first." };
|
|
39926
|
+
}
|
|
39927
|
+
if (!active2.inspector) {
|
|
39928
|
+
return {
|
|
39929
|
+
ok: false,
|
|
39930
|
+
message: `Element highlighting needs the web inspector, which ${active2.framework} previews don't have.`
|
|
39931
|
+
};
|
|
39932
|
+
}
|
|
39933
|
+
let payload;
|
|
39934
|
+
if (input.clear === true) {
|
|
39935
|
+
payload = { clear: true };
|
|
39936
|
+
} else {
|
|
39937
|
+
const selector = typeof input.selector === "string" ? input.selector.trim() : "";
|
|
39938
|
+
if (!selector || selector.length > MAX_SELECTOR) {
|
|
39939
|
+
return { ok: false, message: `selector must be a CSS selector of 1-${MAX_SELECTOR} characters.` };
|
|
39940
|
+
}
|
|
39941
|
+
const label = typeof input.label === "string" ? input.label.trim().slice(0, MAX_LABEL) : "";
|
|
39942
|
+
payload = label ? { selector, label } : { selector };
|
|
39943
|
+
}
|
|
39944
|
+
const res = await postPreviewEvent({
|
|
39945
|
+
sessionId: ctx.sessionId,
|
|
39946
|
+
pluginId: ctx.pluginId,
|
|
39947
|
+
pluginAuthToken: ctx.pluginAuthToken,
|
|
39948
|
+
type: USER_EVENTS.PREVIEW_AGENT_HIGHLIGHT,
|
|
39949
|
+
payload
|
|
39950
|
+
});
|
|
39951
|
+
if (!res.ok) {
|
|
39952
|
+
return { ok: false, message: `Could not reach the app (${res.status}): ${res.message}` };
|
|
39953
|
+
}
|
|
39954
|
+
return {
|
|
39955
|
+
ok: true,
|
|
39956
|
+
message: payload.clear === true ? "Cleared your marks from the preview." : `Marked ${String(payload.selector)} in the user's preview. If the selector matches nothing, no mark appears.`
|
|
39957
|
+
};
|
|
39958
|
+
}
|
|
39959
|
+
async stop() {
|
|
39960
|
+
const ctx = this.ctx;
|
|
39961
|
+
if (!ctx?.pluginAuthToken) return { ok: false, message: "The session is still starting." };
|
|
39962
|
+
if (!activePreviews.has(ctx.sessionId)) return { ok: true, message: "No preview was running." };
|
|
39963
|
+
await killPreview(ctx.sessionId);
|
|
39964
|
+
resetBuildHealState(ctx.sessionId);
|
|
39965
|
+
await postPreviewEvent({
|
|
39966
|
+
sessionId: ctx.sessionId,
|
|
39967
|
+
pluginId: ctx.pluginId,
|
|
39968
|
+
pluginAuthToken: ctx.pluginAuthToken,
|
|
39969
|
+
type: USER_EVENTS.PREVIEW_STOPPED,
|
|
39970
|
+
payload: { reason: "user", origin: "agent" }
|
|
39971
|
+
});
|
|
39972
|
+
return { ok: true, message: "Preview stopped." };
|
|
39973
|
+
}
|
|
39974
|
+
// ─── Internals ─────────────────────────────────────────────────────────
|
|
39975
|
+
async launch(ctx, pluginAuthToken, override) {
|
|
39976
|
+
this.lastError = null;
|
|
39977
|
+
const already = serving(ctx.sessionId);
|
|
39978
|
+
if (already) {
|
|
39979
|
+
await postPreviewEvent({
|
|
39980
|
+
sessionId: ctx.sessionId,
|
|
39981
|
+
pluginId: ctx.pluginId,
|
|
39982
|
+
pluginAuthToken,
|
|
39983
|
+
type: USER_EVENTS.PREVIEW_READY,
|
|
39984
|
+
payload: {
|
|
39985
|
+
url: already.url,
|
|
39986
|
+
framework: already.framework,
|
|
39987
|
+
port: already.detection.port,
|
|
39988
|
+
origin: "agent"
|
|
39989
|
+
}
|
|
39990
|
+
});
|
|
39991
|
+
return running(already, true);
|
|
39992
|
+
}
|
|
39993
|
+
void postPreviewEvent({
|
|
39994
|
+
sessionId: ctx.sessionId,
|
|
39995
|
+
pluginId: ctx.pluginId,
|
|
39996
|
+
pluginAuthToken,
|
|
39997
|
+
type: USER_EVENTS.PREVIEW_DETECTION_PENDING,
|
|
39998
|
+
payload: { origin: "agent" }
|
|
39999
|
+
});
|
|
40000
|
+
const detection = (override && detectionFromOverride(override)) ?? await resolvePreviewDetection({ ctx, runtime: ctx.getRuntime(), pluginAuthToken, origin: "agent" });
|
|
40001
|
+
if (!detection) {
|
|
40002
|
+
const err2 = { stage: "detection", message: "Could not work out how to start this project." };
|
|
40003
|
+
this.lastError = err2;
|
|
40004
|
+
return { status: "error", ...err2 };
|
|
40005
|
+
}
|
|
40006
|
+
let failure = null;
|
|
40007
|
+
await startPreviewFromDetection(ctx, detection, pluginAuthToken, {
|
|
40008
|
+
origin: "agent",
|
|
40009
|
+
onEvent: (type, payload) => {
|
|
40010
|
+
if (type === USER_EVENTS.PREVIEW_ERROR) {
|
|
40011
|
+
failure = {
|
|
40012
|
+
stage: String(payload.stage ?? "spawn"),
|
|
40013
|
+
message: String(payload.message ?? "The preview failed to start.")
|
|
40014
|
+
};
|
|
40015
|
+
}
|
|
40016
|
+
}
|
|
40017
|
+
});
|
|
40018
|
+
const up = serving(ctx.sessionId);
|
|
40019
|
+
if (up) return running(up, false);
|
|
40020
|
+
const err = failure ?? { stage: "spawn", message: "The preview did not come up." };
|
|
40021
|
+
this.lastError = err;
|
|
40022
|
+
return { status: "error", ...err };
|
|
40023
|
+
}
|
|
40024
|
+
async handle(req, res) {
|
|
40025
|
+
const reply = (code, body) => {
|
|
40026
|
+
res.writeHead(code, { "content-type": "application/json" });
|
|
40027
|
+
res.end(JSON.stringify(body));
|
|
40028
|
+
};
|
|
40029
|
+
if (!authorized(req.headers.authorization, this.token)) return reply(401, { error: "unauthorized" });
|
|
40030
|
+
try {
|
|
40031
|
+
const body = req.method === "POST" ? await readJson(req) : {};
|
|
40032
|
+
switch (`${req.method} ${req.url}`) {
|
|
40033
|
+
case "POST /preview/start":
|
|
40034
|
+
return reply(200, await this.start(overrideFrom(body)));
|
|
40035
|
+
case "GET /preview/status":
|
|
40036
|
+
return reply(200, this.status());
|
|
40037
|
+
case "POST /preview/highlight":
|
|
40038
|
+
return reply(200, await this.highlight(body));
|
|
40039
|
+
case "POST /preview/stop":
|
|
40040
|
+
return reply(200, await this.stop());
|
|
40041
|
+
default:
|
|
40042
|
+
return reply(404, { error: "not found" });
|
|
40043
|
+
}
|
|
40044
|
+
} catch (err) {
|
|
40045
|
+
log.warn("agent-preview", `bridge request failed: ${String(err)}`);
|
|
40046
|
+
return reply(500, { error: err instanceof Error ? err.message : String(err) });
|
|
40047
|
+
}
|
|
40048
|
+
}
|
|
40049
|
+
};
|
|
40050
|
+
function serving(sessionId) {
|
|
40051
|
+
const active2 = activePreviews.get(sessionId);
|
|
40052
|
+
if (!active2 || active2.devServer && active2.devServer.exitCode !== null) return null;
|
|
40053
|
+
return active2;
|
|
40054
|
+
}
|
|
40055
|
+
function running(active2, reused) {
|
|
40056
|
+
return {
|
|
40057
|
+
status: "running",
|
|
40058
|
+
url: active2.url,
|
|
40059
|
+
framework: active2.framework,
|
|
40060
|
+
...reused ? { reused: true } : {},
|
|
40061
|
+
inspector: Boolean(active2.inspector)
|
|
40062
|
+
};
|
|
40063
|
+
}
|
|
40064
|
+
function detectionFromOverride(o) {
|
|
40065
|
+
if (!o.command || !Number.isInteger(o.port) || o.port <= 0 || o.port > 65535) return null;
|
|
40066
|
+
return {
|
|
40067
|
+
framework: o.framework?.trim() || "custom",
|
|
40068
|
+
command: o.command,
|
|
40069
|
+
args: Array.isArray(o.args) ? o.args.map(String) : [],
|
|
40070
|
+
port: o.port,
|
|
40071
|
+
// Any Local/ready/listening line; the TCP probe is the real fallback.
|
|
40072
|
+
ready_pattern: o.ready_pattern?.trim() || "(ready|local:|listening|started server|compiled)"
|
|
40073
|
+
};
|
|
40074
|
+
}
|
|
40075
|
+
function overrideFrom(body) {
|
|
40076
|
+
if (typeof body.command !== "string" || typeof body.port !== "number") return void 0;
|
|
40077
|
+
return {
|
|
40078
|
+
command: body.command,
|
|
40079
|
+
args: Array.isArray(body.args) ? body.args.map(String) : void 0,
|
|
40080
|
+
port: body.port,
|
|
40081
|
+
framework: typeof body.framework === "string" ? body.framework : void 0,
|
|
40082
|
+
ready_pattern: typeof body.ready_pattern === "string" ? body.ready_pattern : void 0
|
|
40083
|
+
};
|
|
40084
|
+
}
|
|
40085
|
+
function authorized(header, token) {
|
|
40086
|
+
const presented = Buffer.from((header ?? "").replace(/^Bearer\s+/i, ""));
|
|
40087
|
+
const expected = Buffer.from(token);
|
|
40088
|
+
return presented.length === expected.length && (0, import_node_crypto10.timingSafeEqual)(presented, expected);
|
|
40089
|
+
}
|
|
40090
|
+
function readJson(req) {
|
|
40091
|
+
return new Promise((resolve12, reject) => {
|
|
40092
|
+
let raw = "";
|
|
40093
|
+
req.setEncoding("utf8");
|
|
40094
|
+
req.on("data", (chunk) => {
|
|
40095
|
+
raw += chunk;
|
|
40096
|
+
if (raw.length > 64e3) {
|
|
40097
|
+
reject(new Error("body too large"));
|
|
40098
|
+
req.destroy();
|
|
40099
|
+
}
|
|
40100
|
+
});
|
|
40101
|
+
req.on("end", () => {
|
|
40102
|
+
if (!raw) return resolve12({});
|
|
40103
|
+
try {
|
|
40104
|
+
const parsed = JSON.parse(raw);
|
|
40105
|
+
resolve12(parsed && typeof parsed === "object" ? parsed : {});
|
|
40106
|
+
} catch {
|
|
40107
|
+
reject(new Error("invalid JSON"));
|
|
40108
|
+
}
|
|
40109
|
+
});
|
|
40110
|
+
req.on("error", reject);
|
|
40111
|
+
});
|
|
40112
|
+
}
|
|
40113
|
+
var agentPreviewBridge = new AgentPreviewBridge();
|
|
40114
|
+
|
|
39521
40115
|
// src/integrations/detect-stack.ts
|
|
39522
40116
|
var import_node_fs13 = __toESM(require("fs"));
|
|
39523
40117
|
var import_node_path13 = __toESM(require("path"));
|
|
@@ -40658,7 +41252,8 @@ async function integrationsSyncH(ctx) {
|
|
|
40658
41252
|
sessionId: opts.sessionId,
|
|
40659
41253
|
pluginId: opts.pluginId,
|
|
40660
41254
|
pluginAuthToken: opts.pluginAuthToken,
|
|
40661
|
-
pollSecret: opts.pollSecret
|
|
41255
|
+
pollSecret: opts.pollSecret,
|
|
41256
|
+
preview: agentPreviewBridge.address()
|
|
40662
41257
|
})
|
|
40663
41258
|
);
|
|
40664
41259
|
const applied = await client3.reprovisionMcp(servers);
|
|
@@ -41295,7 +41890,7 @@ var AcpHistory = class {
|
|
|
41295
41890
|
this.summary = trimmed.length > 120 ? trimmed.slice(0, 117) + "\u2026" : trimmed;
|
|
41296
41891
|
}
|
|
41297
41892
|
this.messages.push({
|
|
41298
|
-
id: (0,
|
|
41893
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
41299
41894
|
role: "user",
|
|
41300
41895
|
text,
|
|
41301
41896
|
timestamp: Date.now(),
|
|
@@ -41311,7 +41906,7 @@ var AcpHistory = class {
|
|
|
41311
41906
|
appendAgentReply(text, agentId = this.opts.agent) {
|
|
41312
41907
|
if (text.length === 0) return;
|
|
41313
41908
|
this.messages.push({
|
|
41314
|
-
id: (0,
|
|
41909
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
41315
41910
|
role: "agent",
|
|
41316
41911
|
text,
|
|
41317
41912
|
timestamp: Date.now(),
|
|
@@ -44265,12 +44860,20 @@ async function start(requestedAgent, presetSession) {
|
|
|
44265
44860
|
agent: isHouseProxyEnv(process.env) ? houseRailWireId(process.env) : session.agent
|
|
44266
44861
|
});
|
|
44267
44862
|
}
|
|
44863
|
+
const previewBridge = await agentPreviewBridge.listen();
|
|
44864
|
+
agentPreviewBridge.attach({
|
|
44865
|
+
sessionId: session.id,
|
|
44866
|
+
pluginId,
|
|
44867
|
+
pluginAuthToken: session.pluginAuthToken ?? void 0,
|
|
44868
|
+
getRuntime: () => createInteractiveAgentStrategy(session.agent, createOsStrategy())
|
|
44869
|
+
});
|
|
44268
44870
|
const mcpServers = mergeWithLocalMcpServers(
|
|
44269
44871
|
buildMcpServersForStart({
|
|
44270
44872
|
sessionId: session.id,
|
|
44271
44873
|
pluginId,
|
|
44272
44874
|
pluginAuthToken: session.pluginAuthToken ?? void 0,
|
|
44273
|
-
pollSecret: session.pollSecret
|
|
44875
|
+
pollSecret: session.pollSecret,
|
|
44876
|
+
preview: previewBridge
|
|
44274
44877
|
})
|
|
44275
44878
|
);
|
|
44276
44879
|
provisionSkillsForStart();
|
|
@@ -46853,7 +47456,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
46853
47456
|
var fs73 = __toESM(require("fs"));
|
|
46854
47457
|
var os57 = __toESM(require("os"));
|
|
46855
47458
|
var path87 = __toESM(require("path"));
|
|
46856
|
-
var
|
|
47459
|
+
var import_node_crypto12 = require("crypto");
|
|
46857
47460
|
function sampleCpuTimes() {
|
|
46858
47461
|
let idle = 0;
|
|
46859
47462
|
let total = 0;
|
|
@@ -47005,8 +47608,8 @@ function resolveHostLabel(label) {
|
|
|
47005
47608
|
return resolved.slice(0, 80);
|
|
47006
47609
|
}
|
|
47007
47610
|
async function redeemEnrollToken(token, label) {
|
|
47008
|
-
const controlPollSecret = (0,
|
|
47009
|
-
const pluginSecretHash = (0,
|
|
47611
|
+
const controlPollSecret = (0, import_node_crypto12.randomBytes)(32).toString("base64url");
|
|
47612
|
+
const pluginSecretHash = (0, import_node_crypto12.createHash)("sha256").update(controlPollSecret).digest("hex");
|
|
47010
47613
|
const data = await postJson("/api/self-hosted/redeem", {
|
|
47011
47614
|
token,
|
|
47012
47615
|
label: resolveHostLabel(label),
|
|
@@ -47865,7 +48468,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
47865
48468
|
if (process.env.NODE_ENV === "test") return;
|
|
47866
48469
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
47867
48470
|
if (process.env.CI) return;
|
|
47868
|
-
const current2 = true ? "2.75.
|
|
48471
|
+
const current2 = true ? "2.75.26" : null;
|
|
47869
48472
|
if (!current2) return;
|
|
47870
48473
|
const cache = readCache();
|
|
47871
48474
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -47882,7 +48485,7 @@ function checkForUpdates() {
|
|
|
47882
48485
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
47883
48486
|
if (process.env.CI) return;
|
|
47884
48487
|
if (!process.stdout.isTTY) return;
|
|
47885
|
-
const current2 = true ? "2.75.
|
|
48488
|
+
const current2 = true ? "2.75.26" : null;
|
|
47886
48489
|
if (!current2) return;
|
|
47887
48490
|
const cache = readCache();
|
|
47888
48491
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -47905,7 +48508,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
|
47905
48508
|
var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
|
|
47906
48509
|
var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
|
|
47907
48510
|
function currentCliVersion() {
|
|
47908
|
-
return true ? "2.75.
|
|
48511
|
+
return true ? "2.75.26" : null;
|
|
47909
48512
|
}
|
|
47910
48513
|
async function installedVersion(deps) {
|
|
47911
48514
|
const ls = await deps.run(
|
|
@@ -49621,7 +50224,7 @@ async function invite() {
|
|
|
49621
50224
|
// src/commands/doctor.ts
|
|
49622
50225
|
var import_node_dns = require("dns");
|
|
49623
50226
|
var import_node_util6 = require("util");
|
|
49624
|
-
var
|
|
50227
|
+
var import_node_crypto13 = require("crypto");
|
|
49625
50228
|
var fs79 = __toESM(require("fs"));
|
|
49626
50229
|
var path93 = __toESM(require("path"));
|
|
49627
50230
|
var import_picocolors14 = __toESM(require("picocolors"));
|
|
@@ -49791,9 +50394,9 @@ function checkChokidar() {
|
|
|
49791
50394
|
}
|
|
49792
50395
|
async function doctor(args2 = []) {
|
|
49793
50396
|
const json = args2.includes("--json");
|
|
49794
|
-
const cliVersion = true ? "2.75.
|
|
50397
|
+
const cliVersion = true ? "2.75.26" : "0.0.0-dev";
|
|
49795
50398
|
const apiBase2 = resolveApiBaseUrl();
|
|
49796
|
-
const diagnosticId = (0,
|
|
50399
|
+
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
49797
50400
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
49798
50401
|
const [dns, health] = await Promise.all([
|
|
49799
50402
|
checkDns(apiBase2),
|
|
@@ -50315,7 +50918,7 @@ async function mcpWarm(argv = [], opts = {}) {
|
|
|
50315
50918
|
// src/commands/version.ts
|
|
50316
50919
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
50317
50920
|
function version2() {
|
|
50318
|
-
const v = true ? "2.75.
|
|
50921
|
+
const v = true ? "2.75.26" : "unknown";
|
|
50319
50922
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
50320
50923
|
}
|
|
50321
50924
|
|
|
@@ -50516,6 +51119,9 @@ async function main() {
|
|
|
50516
51119
|
if (command === "mcp-run") {
|
|
50517
51120
|
return mcpRun(args);
|
|
50518
51121
|
}
|
|
51122
|
+
if (command === "preview-mcp") {
|
|
51123
|
+
return previewMcp();
|
|
51124
|
+
}
|
|
50519
51125
|
if (command === "mcp-warm") {
|
|
50520
51126
|
await mcpWarm(args);
|
|
50521
51127
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.75.
|
|
3
|
+
"version": "2.75.26",
|
|
4
4
|
"description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|