Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
395eb26
feat(fleet-panel): scaffold WebviewViewProvider shell + status bar in…
jeonghun-jj-lee Aug 12, 2026
6f0bccb
feat(fleet-panel): SVG topology graph with clickable nodes (#353)
jeonghun-jj-lee Aug 12, 2026
b1316f6
feat(fleet-panel): Fleet Profiles CRUD (#356)
jeonghun-jj-lee Aug 12, 2026
39cdf80
feat(fleet-panel): setup wizard + fleet lifecycle (#354)
jeonghun-jj-lee Aug 12, 2026
57b8c2e
feat(fleet-panel): session launch from profile + aggregate stats (#357)
jeonghun-jj-lee Aug 12, 2026
88d9ca2
feat(fleet-panel): remote host settings (DB path, port, binary, logs)…
jeonghun-jj-lee Aug 12, 2026
b678b3f
feat(fleet-panel): sessions dropdown enrichment — bridge + action han…
jeonghun-jj-lee Aug 12, 2026
1f61a77
fix(fleet-panel): move Create Fleet button under badge, hide topology…
jeonghun-jj-lee Aug 13, 2026
d794708
fix(fleet-panel): force Create Fleet button onto its own line below b…
jeonghun-jj-lee Aug 13, 2026
a8b4b8b
fix(fleet-panel): add yellow accent glow to Create Fleet button
jeonghun-jj-lee Aug 13, 2026
c75d5ef
fix(fleet-panel): increase spacing between sections (profiles/stats)
jeonghun-jj-lee Aug 13, 2026
f58110f
fix(fleet-panel): more gap between Create Fleet button and Profiles s…
jeonghun-jj-lee Aug 13, 2026
41d9d7a
fix(fleet-panel): more gap between STANDALONE badge and Create Fleet …
jeonghun-jj-lee Aug 13, 2026
2419efa
feat(fleet-wizard): add Development clone + Release binary modes
jeonghun-jj-lee Aug 13, 2026
56e12e2
feat(fleet-wizard): session database merge (local → server)
jeonghun-jj-lee Aug 13, 2026
a09ad3d
fix(fleet-wizard): auto-detect session DB path via XDG_DATA_HOME
jeonghun-jj-lee Aug 13, 2026
d08a271
feat(fleet): version compatibility negotiation (Option B)
jeonghun-jj-lee Aug 13, 2026
3ca7ce1
feat(fleet-sync): direct-to-host push + bidirectional sync
jeonghun-jj-lee Aug 13, 2026
3839e66
feat(fleet-sync): conflict detection + amicode chat resolution
jeonghun-jj-lee Aug 13, 2026
d0fd3be
feat(fleet-sync): launch conflict resolution in a visible Amicode chat
jeonghun-jj-lee Aug 13, 2026
ba42ea0
feat(fleet-wizard): detect local repo paths from the running build
jeonghun-jj-lee Aug 13, 2026
d3d9d5f
refactor(fleet-sync): use detectLocalRepos() instead of hardcoded paths
jeonghun-jj-lee Aug 13, 2026
49dcfef
refactor(fleet-wizard): provisionDevClone uses detected paths + direc…
jeonghun-jj-lee Aug 13, 2026
efe8cec
feat(fleet-wizard): wire detectLocalRepos into createFleet command
jeonghun-jj-lee Aug 13, 2026
5fbeb76
fix(fleet-wizard): prompt window reload after fleet creation
jeonghun-jj-lee Aug 13, 2026
11b6419
fix(fleet-wizard): increase git push timeout to 10 minutes
jeonghun-jj-lee Aug 13, 2026
98d8de7
feat(fleet): add fleet skill for agentic setup (#363)
jeonghun-jj-lee Aug 13, 2026
2ecaaa6
refactor(fleet): replace wizard commands with chat-launching stubs (#…
jeonghun-jj-lee Aug 13, 2026
de85246
test(fleet): remove fleet_wizard.test.ts (#363)
jeonghun-jj-lee Aug 13, 2026
4a915be
fix(fleet): use existing chat tab for fleet prompts instead of new ta…
jeonghun-jj-lee Aug 13, 2026
8f91e24
fix(fleet): open chat + poll for readyUrl when server isn't up yet (#…
jeonghun-jj-lee Aug 13, 2026
13f127a
fix(chat): add draft-message to the relay allowlist (#363)
jeonghun-jj-lee Aug 13, 2026
095091d
fix(fleet): navigate iframe to /new-session?prompt= instead of dead p…
jeonghun-jj-lee Aug 13, 2026
1565cef
fix(fleet): preserve auth/theme params when navigating iframe (#363)
jeonghun-jj-lee Aug 13, 2026
f9848a5
fix(fleet): client-side SPA navigation via history.pushState (#363)
jeonghun-jj-lee Aug 13, 2026
972e7f2
fix(fleet): use tabs.newDraft for proper new-session creation (#363)
jeonghun-jj-lee Aug 13, 2026
89b12d8
fix(fleet): add frontmatter to fleet skill for discovery (#363)
jeonghun-jj-lee Aug 13, 2026
8d88df6
fix(fleet): prefix prompts with /fleet to auto-invoke the skill (#363)
jeonghun-jj-lee Aug 13, 2026
ff5090e
fix(fleet): remove double-post from navigateToPath (#363)
jeonghun-jj-lee Aug 13, 2026
5034c1d
feat(fleet): pass autoSend=1 to auto-submit the fleet command (#363)
jeonghun-jj-lee Aug 13, 2026
88f41ae
feat(fleet): rename skill to create-a-fleet, use global signal for au…
jeonghun-jj-lee Aug 13, 2026
1e08d51
feat(fleet): unified fleet-issue notifications + /fleet-troubleshooti…
jeonghun-jj-lee Aug 13, 2026
13501a4
fix(fleet): correct session DB path in rebuild script
jeonghun-jj-lee Aug 13, 2026
841862c
fleet skill: move session DB copy before build, use local_rebuild_ami…
jeonghun-jj-lee Aug 13, 2026
b7f4521
fleet: stop touching opencodePort during enrollment/unenrollment
jeonghun-jj-lee Aug 13, 2026
6aed83f
fix(fleet skill): include options: [] for text-type question tool calls
jeonghun-jj-lee Aug 13, 2026
8e73f4a
fix(fleet): single status bar item + session-safe create ordering
jeonghun-jj-lee Aug 13, 2026
1d3bca0
fix: surface fleet-troubleshooting skill as public
jeonghun-jj-lee Aug 13, 2026
5bb9315
docs(context): Go Standalone preserves canonical coordinates for Reco…
jeonghun-jj-lee Aug 13, 2026
a696da2
docs(fleet): ADR 0006 — Fleet Panel & Profile UI + CONTEXT.md terms
jeonghun-jj-lee Aug 12, 2026
50f306a
docs(adr): renumber fleet panel ADR to 0007 (0006 taken by inspectors…
jeonghun-jj-lee Aug 12, 2026
a77bd73
docs(adr-0007): add one-fleet-at-a-time constraint + reconfigure support
jeonghun-jj-lee Aug 12, 2026
00f9642
docs(adr-0007): add remote host settings (DB path, port, binary) to f…
jeonghun-jj-lee Aug 12, 2026
1ccf5c2
fix(fleet skill): add PATH to tunnel plist + preserve canonical on re…
jeonghun-jj-lee Aug 13, 2026
a4779d3
fix(fleet): show 'Reconnect Fleet' when standalone with existing cano…
jeonghun-jj-lee Aug 14, 2026
3ea9c27
fix(fleet): goStandalone preserves canonical coordinates for reconnect
jeonghun-jj-lee Aug 14, 2026
17b4ee7
feat(fleet): seamless reconnect + go-standalone without window reload
jeonghun-jj-lee Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The user's machines acting as one logical studio: exactly one Canonical Server p
_Avoid_: mesh, cluster

**Go Standalone**:
The user-invoked mode switch from `client` to `standalone` — the machine leaves the fleet and serves itself permanently. Not an escape hatch: a first-class choice. Sessions made locally stay local. Re-enrollment in a fleet is a separate flow (Enroll, deferred).
The user-invoked mode switch from `client` to `standalone` — the machine leaves the fleet and serves itself. Not an escape hatch: a first-class choice. Sessions made locally stay local. The `canonical` coordinates are preserved in `fleet.json` so the panel can offer "Reconnect Fleet" (one-click rejoin) rather than forcing a full re-setup. Only "Dismantle Fleet" removes the coordinates entirely.
_Avoid_: local fallback, offline mode, degraded mode

**Fleet token**:
Expand Down
13 changes: 13 additions & 0 deletions packages/extension/esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ const targets = [
minify: false,
logLevel: "info",
},
// Fleet Panel webview bundle (activity bar sidebar — #350)
{
entryPoints: ["src/fleet_webview.ts"],
bundle: true,
platform: "browser",
target: "es2022",
format: "iife",
outfile: "dist/fleet_webview.js",
sourcemap: true,
minify: false,
logLevel: "info",
loader: { ".svg": "text" },
},
];

if (watch) {
Expand Down
236 changes: 236 additions & 0 deletions packages/extension/media/ui/components/fleet_profiles_view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Fleet Profiles view component — renders the profiles list in the Fleet Panel
// webview. Each row shows name + model/variant + play button + overflow menu.
// Inline form for create/edit.
//
// Part of #356 (Fleet Panel: Fleet Profiles CRUD).

import { defineStyle } from "../style";
import { button } from "../atoms/button";
import type { FleetWebviewMessage } from "../../../src/fleet_panel";

defineStyle(
"fleet-profiles",
`
.profiles-list { display: flex; flex-direction: column; gap: var(--space-xs); }
.profile-row { display: flex; align-items: center; gap: var(--space-sm);
padding: var(--space-xs) var(--space-sm);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); background: var(--bg-box); }
.profile-row .pr-name { font-weight: 600; flex: 1; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; }
.profile-row .pr-model { color: var(--color-dim); font-size: var(--text-small);
flex: 0 0 auto; max-width: 120px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; }
.profile-row .pr-play { flex: 0 0 auto; cursor: pointer; background: none;
border: none; color: var(--color-ok); font-size: 14px;
padding: 2px 4px; }
.profile-row .pr-play:hover { opacity: 0.8; }
.profile-row .pr-menu { flex: 0 0 auto; cursor: pointer; background: none;
border: none; color: var(--color-dim); font-size: 14px;
padding: 2px 4px; position: relative; }
.profile-row .pr-menu:hover { color: var(--vscode-foreground); }
.profile-menu { position: absolute; right: 0; top: 100%; background: var(--bg-box);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); z-index: 100; min-width: 100px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
.profile-menu-item { padding: var(--space-xs) var(--space-sm); cursor: pointer;
font-size: var(--text-small); white-space: nowrap; }
.profile-menu-item:hover { background: color-mix(in srgb, var(--vscode-foreground) 10%, transparent); }
.profile-form { display: flex; flex-direction: column; gap: var(--space-xs);
padding: var(--space-sm); border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); background: var(--bg-box); }
.profile-form input, .profile-form select { font-family: var(--text-font);
font-size: var(--text-small); padding: var(--space-xs) var(--space-sm);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); background: var(--vscode-input-background, transparent);
color: var(--vscode-foreground); }
.profile-form label { font-size: var(--text-small); color: var(--color-dim); }
.profile-form .form-row { display: flex; flex-direction: column; gap: 2px; }
.profile-form .form-actions { display: flex; gap: var(--space-xs); margin-top: var(--space-xs); }
`,
);

export interface ProfileSummary {
slug: string;
name: string;
model: string;
variant: string;
}

export interface ProfilesView {
el: HTMLElement;
update(profiles: ProfileSummary[]): void;
}

export function createProfilesView(post: (msg: FleetWebviewMessage) => void): ProfilesView {
const el = document.createElement("div");
const list = document.createElement("div");
list.className = "profiles-list";
el.appendChild(list);

// "+" button at the top
const addBtn = button("+", () => showForm(null));
addBtn.el.title = "Create new profile";
el.insertBefore(addBtn.el, list);

let formEl: HTMLElement | null = null;
let currentMenu: HTMLElement | null = null;

function dismissMenu(): void {
if (currentMenu) {
currentMenu.remove();
currentMenu = null;
}
}

document.addEventListener("click", () => dismissMenu());

function showForm(editSlug: string | null, prefill?: ProfileSummary): void {
hideForm();
formEl = document.createElement("div");
formEl.className = "profile-form";

const fields = [
{ key: "name", label: "Name", value: prefill?.name ?? "" },
{ key: "model", label: "Model", value: prefill?.model ?? "anthropic.claude-opus-4-6-v1" },
{ key: "variant", label: "Variant", value: prefill?.variant ?? "" },
{ key: "base", label: "Base", value: "pulse-designer" },
{ key: "task_type", label: "Task Type", value: "interactive" },
{ key: "skills", label: "Skills (comma-separated)", value: "transmon, atoms, bosonic" },
];

const inputs: Record<string, HTMLInputElement> = {};
for (const f of fields) {
const row = document.createElement("div");
row.className = "form-row";
const label = document.createElement("label");
label.textContent = f.label;
const input = document.createElement("input");
input.type = "text";
input.value = f.value;
input.placeholder = f.label;
inputs[f.key] = input;
row.appendChild(label);
row.appendChild(input);
formEl.appendChild(row);
}

const actions = document.createElement("div");
actions.className = "form-actions";

const saveBtn = button(editSlug ? "Save" : "Create", () => {
const payload: Record<string, unknown> = {
name: inputs.name.value,
model: inputs.model.value,
variant: inputs.variant.value,
base: inputs.base.value,
task_type: inputs.task_type.value,
skills: inputs.skills.value.split(",").map((s) => s.trim()).filter(Boolean),
};
if (editSlug) {
post({ type: "action", action: "editProfile", payload: { slug: editSlug, ...payload } });
} else {
post({ type: "action", action: "createProfile", payload });
}
hideForm();
});
actions.appendChild(saveBtn.el);

const cancelBtn = button("Cancel", () => hideForm());
actions.appendChild(cancelBtn.el);
formEl.appendChild(actions);

el.insertBefore(formEl, list);
}

function hideForm(): void {
if (formEl) {
formEl.remove();
formEl = null;
}
}

function update(profiles: ProfileSummary[]): void {
list.innerHTML = "";
if (profiles.length === 0) {
const hint = document.createElement("div");
hint.style.color = "var(--color-dim)";
hint.style.fontStyle = "italic";
hint.textContent = "No profiles yet";
list.appendChild(hint);
return;
}

for (const p of profiles) {
const row = document.createElement("div");
row.className = "profile-row";

const name = document.createElement("span");
name.className = "pr-name";
name.textContent = p.name;
row.appendChild(name);

const model = document.createElement("span");
model.className = "pr-model";
model.textContent = p.variant ? `${p.model} (${p.variant})` : p.model;
row.appendChild(model);

// Play button
const play = document.createElement("button");
play.className = "pr-play";
play.textContent = "\u25B6";
play.title = "Launch session from this profile";
play.addEventListener("click", (e) => {
e.stopPropagation();
post({ type: "action", action: "launch", payload: { slug: p.slug } });
});
row.appendChild(play);

// Overflow menu button
const menuBtn = document.createElement("button");
menuBtn.className = "pr-menu";
menuBtn.textContent = "\u22EE";
menuBtn.addEventListener("click", (e) => {
e.stopPropagation();
dismissMenu();
const menu = document.createElement("div");
menu.className = "profile-menu";

const editItem = document.createElement("div");
editItem.className = "profile-menu-item";
editItem.textContent = "Edit";
editItem.addEventListener("click", () => {
dismissMenu();
showForm(p.slug, p);
});
menu.appendChild(editItem);

const dupItem = document.createElement("div");
dupItem.className = "profile-menu-item";
dupItem.textContent = "Duplicate";
dupItem.addEventListener("click", () => {
dismissMenu();
post({ type: "action", action: "duplicateProfile", payload: { slug: p.slug } });
});
menu.appendChild(dupItem);

const delItem = document.createElement("div");
delItem.className = "profile-menu-item";
delItem.textContent = "Delete";
delItem.addEventListener("click", () => {
dismissMenu();
post({ type: "action", action: "deleteProfile", payload: { slug: p.slug } });
});
menu.appendChild(delItem);

currentMenu = menu;
menuBtn.appendChild(menu);
});
row.appendChild(menuBtn);

list.appendChild(row);
}
}

return { el, update };
}
Loading
Loading