[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
import assert from "node:assert/strict";
import test from "node:test";
import type {
AIPanelView,
AISession,
} from "../../infrastructure/ai/types.ts";
import {
applyDraftEntrySelection,
applyHistorySessionSelection,
normalizePanelView,
panelViewsEqual,
resolveDisplayedPanelView,
resolveDisplayedSession,
shouldForceDraftViewSync,
} from "./aiPanelViewState.ts";
function createSession(id: string): AISession {
return {
id,
title: `Session ${id}`,
messages: [],
createdAt: 1,
updatedAt: 1,
agentId: "catty",
scope: {
type: "terminal",
targetId: "terminal-1",
},
};
}
test("panelViewsEqual treats draft views as equal even when refs differ", () => {
assert.equal(
panelViewsEqual({ mode: "draft" }, { mode: "draft" }),
true,
);
});
test("panelViewsEqual distinguishes session targets", () => {
assert.equal(
panelViewsEqual(
{ mode: "session", sessionId: "session-1" },
{ mode: "session", sessionId: "session-2" },
),
false,
);
});
test("draft view never falls back to most recent history", () => {
const panelView: AIPanelView = { mode: "draft" };
const sessions = [createSession("session-2"), createSession("session-1")];
assert.equal(resolveDisplayedSession(panelView, sessions), null);
});
test("session view returns the selected session", () => {
const selectedSession = createSession("session-2");
const panelView: AIPanelView = { mode: "session", sessionId: selectedSession.id };
const sessions = [createSession("session-1"), selectedSession];
assert.equal(resolveDisplayedSession(panelView, sessions), selectedSession);
});
test("missing session target resolves to null instead of history fallback", () => {
const panelView: AIPanelView = { mode: "session", sessionId: "missing-session" };
const sessions = [createSession("session-2"), createSession("session-1")];
assert.equal(resolveDisplayedSession(panelView, sessions), null);
});
test("missing session target normalizes back to draft view", () => {
const panelView: AIPanelView = { mode: "session", sessionId: "missing-session" };
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(normalizePanelView(panelView, sessions), { mode: "draft" });
});
test("shouldForceDraftViewSync keeps explicit session while it is still in scoped history", () => {
const explicit: AIPanelView = { mode: "session", sessionId: "just-created" };
const normalized: AIPanelView = { mode: "draft" };
// Predicate mirrors scoped historySessions (not the global store).
const scopedHistory = new Set(["just-created"]);
assert.equal(
shouldForceDraftViewSync(explicit, normalized, (id) => scopedHistory.has(id)),
false,
);
});
test("shouldForceDraftViewSync forces draft when explicit session is gone", () => {
const explicit: AIPanelView = { mode: "session", sessionId: "deleted" };
const normalized: AIPanelView = { mode: "draft" };
assert.equal(
shouldForceDraftViewSync(explicit, normalized, () => false),
true,
);
});
test("shouldForceDraftViewSync demotes when session is only in global store, not scoped history", () => {
const explicit: AIPanelView = { mode: "session", sessionId: "other-scope" };
const normalized: AIPanelView = { mode: "draft" };
// Global store still has it; scoped history (what normalize uses) does not.
const globalStore = new Set(["other-scope"]);
const scopedHistory = new Set<string>();
assert.equal(
shouldForceDraftViewSync(explicit, normalized, (id) => scopedHistory.has(id)),
true,
);
assert.equal(
shouldForceDraftViewSync(explicit, normalized, (id) => globalStore.has(id)),
false,
);
});
test("shouldForceDraftViewSync is a no-op when views already match", () => {
const view: AIPanelView = { mode: "session", sessionId: "session-1" };
assert.equal(
shouldForceDraftViewSync(view, view, () => true),
false,
);
});
test("missing explicit panel view resumes the most recent matching history when no draft exists", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, false, sessions, undefined, "workspace"),
{ mode: "session", sessionId: "session-2" },
);
});
test("missing explicit panel view restores the persisted active session instead of the newest", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, false, sessions, "session-1", "workspace"),
{ mode: "session", sessionId: "session-1" },
);
});
test("persisted session id that no longer exists in history falls back to newest", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, false, sessions, "deleted-session", "workspace"),
{ mode: "session", sessionId: "session-2" },
);
});
test("null persisted session id falls back to newest history entry", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, false, sessions, null, "workspace"),
{ mode: "session", sessionId: "session-2" },
);
});
test("terminal scope without explicit view always starts from draft even when history exists", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, false, sessions, "session-1", "terminal"),
{ mode: "draft" },
);
});
test("missing explicit panel view prefers the draft when unsent input exists without an active chat", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, true, sessions, null, "workspace"),
{ mode: "draft" },
);
});
test("workspace unsent draft keeps the persisted active chat after merge seed", () => {
// Merge often seeds activeSessionIdMap without writing panelView. Typing a
// follow-up must not demote to draft or send will createSession().
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(undefined, true, sessions, "session-1", "workspace"),
{ mode: "session", sessionId: "session-1" },
);
});
test("explicit new-chat draft still wins over a stale persisted id", () => {
const sessions = [createSession("session-2"), createSession("session-1")];
assert.deepEqual(
resolveDisplayedPanelView(
{ mode: "draft" },
true,
sessions,
"session-1",
"workspace",
),
{ mode: "draft" },
);
});
test("draft state is used when there is no implicit history to resume", () => {
assert.deepEqual(
resolveDisplayedPanelView(undefined, true, [], null, "workspace"),
{ mode: "draft" },
);
});
test("history selection switches to the chosen session without touching draft state", () => {
const calls: string[] = [];
applyHistorySessionSelection("session-2", {
showSessionView: (sessionId) => {
calls.push(`view:${sessionId}`);
},
setActiveSessionId: (sessionId) => {
calls.push(`active:${sessionId}`);
},
closeHistory: () => {
calls.push("close-history");
},
});
assert.deepEqual(calls, [
"view:session-2",
"active:session-2",
"close-history",
]);
});
test("draft entry ensures a draft exists before switching the panel to draft mode", () => {
const calls: string[] = [];
applyDraftEntrySelection({
ensureDraft: () => {
calls.push("ensure-draft");
},
showDraftView: () => {
calls.push("show-draft");
},
});
assert.deepEqual(calls, [
"ensure-draft",
"show-draft",
]);
});
test("draft entry can preserve the current session view while ensuring draft state", () => {
const calls: string[] = [];
applyDraftEntrySelection({
ensureDraft: () => {
calls.push("ensure-draft");
},
showDraftView: () => {
calls.push("show-draft");
},
preserveSessionView: true,
});
assert.deepEqual(calls, [
"ensure-draft",
]);
});