Merge pull request #2868 from Hmbown/codex/v090-vscode-git-meta

feat(vscode): show thread git metadata
This commit is contained in:
Hunter Bown
2026-06-06 10:51:43 -07:00
committed by GitHub
8 changed files with 63 additions and 6 deletions
+5
View File
@@ -225,6 +225,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
cross-tab events, and corruption-tolerant persisted state, while leaving the
broader collaboration UI wiring to follow-up work (#2864). Thanks
@ljm3790865 for the tab-core implementation and #2753 direction.
- The VS Code Agent View now renders the runtime thread summary's Git `head`
and dirty-worktree flag alongside branch metadata, keeping branch switches
visible without adding retry/undo/restore mutation endpoints yet (#2580,
#2862). Thanks @AiurArtanis and @nasus9527 for the IDE/agent-view requests
and @gaord for the runtime metadata direction.
### Changed
+5
View File
@@ -225,6 +225,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
cross-tab events, and corruption-tolerant persisted state, while leaving the
broader collaboration UI wiring to follow-up work (#2864). Thanks
@ljm3790865 for the tab-core implementation and #2753 direction.
- The VS Code Agent View now renders the runtime thread summary's Git `head`
and dirty-worktree flag alongside branch metadata, keeping branch switches
visible without adding retry/undo/restore mutation endpoints yet (#2580,
#2862). Thanks @AiurArtanis and @nasus9527 for the IDE/agent-view requests
and @gaord for the runtime metadata direction.
### Changed
+5
View File
@@ -200,6 +200,8 @@ function readThreadSummaries(value) {
mode: readString(record.mode) ?? "agent",
workspace: readString(record.workspace),
branch: readString(record.branch),
head: readString(record.head),
dirty: readBoolean(record.dirty),
archived: record.archived === true,
updatedAt: readString(record.updated_at) ?? "",
latestTurnStatus: readString(record.latest_turn_status),
@@ -231,6 +233,9 @@ function readString(value) {
function readNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function readBoolean(value) {
return value === true;
}
function clampRefreshInterval(value) {
if (!Number.isFinite(value)) {
return 15;
File diff suppressed because one or more lines are too long
+18 -2
View File
@@ -148,15 +148,31 @@ function renderSnapshot(snapshot) {
function renderThread(thread) {
const status = thread.latestTurnStatus ? ` · ${thread.latestTurnStatus}` : "";
const archived = thread.archived ? " · archived" : "";
const branch = thread.branch ? ` · branch ${thread.branch}` : "";
const git = renderGitMetadata(thread);
const workspace = thread.workspace ? ` · ${thread.workspace}` : "";
const updated = thread.updatedAt ? ` · ${formatTimestamp(thread.updatedAt)}` : "";
return `<div class="thread">
<div class="thread-title">${escapeHtml(thread.title)}</div>
<div class="thread-preview">${escapeHtml(thread.preview || "No recent message.")}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${branch}${archived}${updated}${workspace}`)}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${git}${archived}${updated}${workspace}`)}</div>
</div>`;
}
function renderGitMetadata(thread) {
if (!thread.branch && !thread.head && !thread.dirty) {
return "";
}
const parts = [];
if (thread.branch) {
parts.push(`branch ${thread.branch}`);
}
if (thread.head) {
parts.push(`@ ${thread.head}`);
}
if (thread.dirty) {
parts.push("dirty");
}
return ` · ${parts.join(" ")}`;
}
function labelFor(kind) {
switch (kind) {
case "connected":
File diff suppressed because one or more lines are too long
+8
View File
@@ -18,6 +18,8 @@ export interface ThreadSummary {
mode: string;
workspace?: string;
branch?: string;
head?: string;
dirty: boolean;
archived: boolean;
updatedAt: string;
latestTurnStatus?: string;
@@ -228,6 +230,8 @@ function readThreadSummaries(value: unknown): ThreadSummary[] {
mode: readString(record.mode) ?? "agent",
workspace: readString(record.workspace),
branch: readString(record.branch),
head: readString(record.head),
dirty: readBoolean(record.dirty),
archived: record.archived === true,
updatedAt: readString(record.updated_at) ?? "",
latestTurnStatus: readString(record.latest_turn_status),
@@ -265,6 +269,10 @@ function readNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function readBoolean(value: unknown): boolean {
return value === true;
}
function clampRefreshInterval(value: number): number {
if (!Number.isFinite(value)) {
return 15;
+20 -2
View File
@@ -120,16 +120,34 @@ function renderSnapshot(snapshot: SnapshotEntry): string {
function renderThread(thread: ThreadSummary): string {
const status = thread.latestTurnStatus ? ` · ${thread.latestTurnStatus}` : "";
const archived = thread.archived ? " · archived" : "";
const branch = thread.branch ? ` · branch ${thread.branch}` : "";
const git = renderGitMetadata(thread);
const workspace = thread.workspace ? ` · ${thread.workspace}` : "";
const updated = thread.updatedAt ? ` · ${formatTimestamp(thread.updatedAt)}` : "";
return `<div class="thread">
<div class="thread-title">${escapeHtml(thread.title)}</div>
<div class="thread-preview">${escapeHtml(thread.preview || "No recent message.")}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${branch}${archived}${updated}${workspace}`)}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${git}${archived}${updated}${workspace}`)}</div>
</div>`;
}
function renderGitMetadata(thread: ThreadSummary): string {
if (!thread.branch && !thread.head && !thread.dirty) {
return "";
}
const parts: string[] = [];
if (thread.branch) {
parts.push(`branch ${thread.branch}`);
}
if (thread.head) {
parts.push(`@ ${thread.head}`);
}
if (thread.dirty) {
parts.push("dirty");
}
return ` · ${parts.join(" ")}`;
}
function labelFor(kind: RuntimeState["kind"]): string {
switch (kind) {
case "connected":