@michengai/dsh-codex-ui 1.1.7 → 1.1.8
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 +23 -0
- package/CHANGELOG.zh-CN.md +23 -0
- package/cordis.patch.yml +2 -0
- package/lib/index.mjs +204 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
This changelog records recent releases of DSH Codex UI and its one-click installer. Earlier changes remain available in the [Git history](https://github.com/MichengAI/dsh-codex-ui/commits/main).
|
|
6
6
|
|
|
7
|
+
## 1.1.8 - 2026-09-15
|
|
8
|
+
|
|
9
|
+
- After the first user message, new chats are titled once as emoji, type, and theme, for example `⚡ 优化|批次文字显示`. Dates are not written.
|
|
10
|
+
- A manual rename is pinned by the host and later messages do not overwrite it.
|
|
11
|
+
|
|
12
|
+
## suite-installer-v1.0.10 - 2026-09-14
|
|
13
|
+
|
|
14
|
+
Install or maintain 11 first-party plugins in one step. Included plugins and versions:
|
|
15
|
+
|
|
16
|
+
- `@michengai/dsh-archive-manager@0.1.42`
|
|
17
|
+
- `@michengai/dsh-codex-ui@1.1.7`
|
|
18
|
+
- `@michengai/dsh-skills-manager@0.1.50`
|
|
19
|
+
- `@michengai/dsh-agency-agents@0.1.42`
|
|
20
|
+
- `@michengai/dsh-im-connect@0.1.49`
|
|
21
|
+
- `@michengai/dsh-automation@0.1.42`
|
|
22
|
+
- `@michengai/dsh-btw@0.1.7`
|
|
23
|
+
- `@michengai/dsh-simplify@0.1.4`
|
|
24
|
+
- `@michengai/dsh-pua@0.3.11`
|
|
25
|
+
- `@michengai/dsh-code-review@0.1.0`
|
|
26
|
+
- `@michengai/dsh-codex-pet@0.1.5`
|
|
27
|
+
|
|
28
|
+
The installer uses these exact versions for reproducible installation.
|
|
29
|
+
|
|
7
30
|
## 1.1.7 - 2026-09-14
|
|
8
31
|
|
|
9
32
|
- Restore the billing plugin's native sidebar entry styles by removing Codex UI overrides for its card, icons, text, and expanded/collapsed appearance. Clicking the entry still opens the existing usage statistics settings page.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
|
|
6
6
|
|
|
7
|
+
## 1.1.8 - 2026-09-15
|
|
8
|
+
|
|
9
|
+
- 新会话在第一条用户消息后,按「表情 + 类型|主题」自动命名一次,例如「⚡ 优化|批次文字显示」。不再写入日期。
|
|
10
|
+
- 手动改名后由宿主钉死,后续消息不会再覆盖。
|
|
11
|
+
|
|
12
|
+
## suite-installer-v1.0.10 - 2026-09-14
|
|
13
|
+
|
|
14
|
+
一键维护安装 11 个自研插件。包含以下插件及版本:
|
|
15
|
+
|
|
16
|
+
- `@michengai/dsh-archive-manager@0.1.42`
|
|
17
|
+
- `@michengai/dsh-codex-ui@1.1.7`
|
|
18
|
+
- `@michengai/dsh-skills-manager@0.1.50`
|
|
19
|
+
- `@michengai/dsh-agency-agents@0.1.42`
|
|
20
|
+
- `@michengai/dsh-im-connect@0.1.49`
|
|
21
|
+
- `@michengai/dsh-automation@0.1.42`
|
|
22
|
+
- `@michengai/dsh-btw@0.1.7`
|
|
23
|
+
- `@michengai/dsh-simplify@0.1.4`
|
|
24
|
+
- `@michengai/dsh-pua@0.3.11`
|
|
25
|
+
- `@michengai/dsh-code-review@0.1.0`
|
|
26
|
+
- `@michengai/dsh-codex-pet@0.1.5`
|
|
27
|
+
|
|
28
|
+
安装器使用这些精确版本执行安装,确保可复现。
|
|
29
|
+
|
|
7
30
|
## 1.1.7 - 2026-09-14
|
|
8
31
|
|
|
9
32
|
- 恢复侧栏用量入口的插件原生样式,移除 Codex UI 对卡片、图标、文字和宽窄态外观的覆盖;点击入口仍打开现有用量统计设置页。
|
package/cordis.patch.yml
CHANGED
package/lib/index.mjs
CHANGED
|
@@ -1555,6 +1555,209 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1555
1555
|
movingSessions.delete(sessionId);
|
|
1556
1556
|
}
|
|
1557
1557
|
}
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region src/session-title.ts
|
|
1560
|
+
const SESSION_TITLE_TYPES = [
|
|
1561
|
+
"功能",
|
|
1562
|
+
"设计",
|
|
1563
|
+
"修复",
|
|
1564
|
+
"优化",
|
|
1565
|
+
"发布",
|
|
1566
|
+
"探索",
|
|
1567
|
+
"文档",
|
|
1568
|
+
"研究"
|
|
1569
|
+
];
|
|
1570
|
+
const SESSION_TITLE_EMOJI = {
|
|
1571
|
+
功能: "✨",
|
|
1572
|
+
设计: "🎨",
|
|
1573
|
+
修复: "🐛",
|
|
1574
|
+
优化: "⚡",
|
|
1575
|
+
发布: "🚀",
|
|
1576
|
+
探索: "🔍",
|
|
1577
|
+
文档: "📝",
|
|
1578
|
+
研究: "🔬"
|
|
1579
|
+
};
|
|
1580
|
+
const TITLE_TYPES = new Set(SESSION_TITLE_TYPES);
|
|
1581
|
+
const LEADING_EMOJI = /^(?:\p{Extended_Pictographic}(?:\uFE0F|\u200D\p{Extended_Pictographic})*)+\s*/u;
|
|
1582
|
+
const TITLE_SEPARATORS = /[||│]/g;
|
|
1583
|
+
const GENERIC_THEME_SUFFIXES = [
|
|
1584
|
+
"类型",
|
|
1585
|
+
"分类",
|
|
1586
|
+
"相关",
|
|
1587
|
+
"需求",
|
|
1588
|
+
"问题",
|
|
1589
|
+
"会话",
|
|
1590
|
+
"标题",
|
|
1591
|
+
"主题",
|
|
1592
|
+
"内容"
|
|
1593
|
+
];
|
|
1594
|
+
function stripLeadingNoise(part) {
|
|
1595
|
+
const trimmed = part.trim();
|
|
1596
|
+
if (/^\d{4}$/.test(trimmed)) return "";
|
|
1597
|
+
return trimmed.replace(/^\d{4}\s*/, "").replace(LEADING_EMOJI, "").trim();
|
|
1598
|
+
}
|
|
1599
|
+
function parseTypeAndTheme(raw) {
|
|
1600
|
+
const parts = raw.trim().replace(/^["'`]+|["'`]+$/g, "").trim().replace(TITLE_SEPARATORS, "|").split("|").map(stripLeadingNoise).filter((part) => part !== "");
|
|
1601
|
+
const type = parts[0];
|
|
1602
|
+
const theme = parts[1];
|
|
1603
|
+
if (type === void 0 || theme === void 0 || !TITLE_TYPES.has(type)) return void 0;
|
|
1604
|
+
return {
|
|
1605
|
+
type,
|
|
1606
|
+
theme
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function isRedundantTheme(type, theme) {
|
|
1610
|
+
const normalized = theme.replace(/[\s·.\-_/]/g, "");
|
|
1611
|
+
if (normalized === type || normalized === `新${type}`) return true;
|
|
1612
|
+
if (type !== "" && normalized.length >= type.length * 2 && normalized.length % type.length === 0 && normalized === type.repeat(normalized.length / type.length)) return true;
|
|
1613
|
+
return GENERIC_THEME_SUFFIXES.some((suffix) => normalized === suffix || normalized === `${type}${suffix}`);
|
|
1614
|
+
}
|
|
1615
|
+
function assembleSessionTitle(type, theme) {
|
|
1616
|
+
const parsed = parseTypeAndTheme(`${type}|${theme}`);
|
|
1617
|
+
if (parsed === void 0) return void 0;
|
|
1618
|
+
const title = isRedundantTheme(parsed.type, parsed.theme) ? `${SESSION_TITLE_EMOJI[parsed.type]} ${parsed.type}` : `${SESSION_TITLE_EMOJI[parsed.type]} ${parsed.type}|${parsed.theme}`;
|
|
1619
|
+
if (Buffer.byteLength(title, "utf8") > 80) return void 0;
|
|
1620
|
+
return title;
|
|
1621
|
+
}
|
|
1622
|
+
function shouldSkipAutoTitle(session) {
|
|
1623
|
+
const origin = session.origin ?? session.header?.origin;
|
|
1624
|
+
return session.id.startsWith("dsh-automation-session-") || session.id.startsWith("im:") || origin === "subagent" || session.header?.parentSession !== void 0 && session.header.parentSession !== "";
|
|
1625
|
+
}
|
|
1626
|
+
//#endregion
|
|
1627
|
+
//#region src/session-title-provider.ts
|
|
1628
|
+
const SESSION_TITLE_PROVIDER_ID = "michengai-codex-ui-session-title";
|
|
1629
|
+
const MAX_INPUT_BYTES = 4096;
|
|
1630
|
+
const MAX_OUTPUT_TOKENS = 64;
|
|
1631
|
+
const TIMEOUT_MS = 6e4;
|
|
1632
|
+
const SYSTEM_PROMPT = [
|
|
1633
|
+
"为编程助手会话生成标题。",
|
|
1634
|
+
"只输出一行,恰好两段:类型|主题",
|
|
1635
|
+
"类型必须是其中一个:功能、设计、修复、优化、发布、探索、文档、研究",
|
|
1636
|
+
"主题必须是具体事项,不能重复类型,不要写“功能”“功能类型”这类空主题。不要写日期或表情,不要引号、前缀、解释或 Markdown。",
|
|
1637
|
+
"使用用户消息的语言。"
|
|
1638
|
+
].join("\n");
|
|
1639
|
+
function asTitleService(value) {
|
|
1640
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1641
|
+
return typeof value.register === "function" ? value : void 0;
|
|
1642
|
+
}
|
|
1643
|
+
function asLlm(value) {
|
|
1644
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1645
|
+
return typeof value.stream === "function" ? value : void 0;
|
|
1646
|
+
}
|
|
1647
|
+
function abortableSignal(signal) {
|
|
1648
|
+
const timeout = AbortSignal.timeout(TIMEOUT_MS);
|
|
1649
|
+
return signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Assemble the streamed text the way the host's `BlockAssembler` does: deltas
|
|
1653
|
+
* accumulate per block index and a closing `block-end` replaces them, so the
|
|
1654
|
+
* adapters that send both channels cannot duplicate the title text.
|
|
1655
|
+
*/
|
|
1656
|
+
function collectText(chunks) {
|
|
1657
|
+
return (async () => {
|
|
1658
|
+
const parts = /* @__PURE__ */ new Map();
|
|
1659
|
+
let finish = "stop";
|
|
1660
|
+
const part = (index) => {
|
|
1661
|
+
let existing = parts.get(index);
|
|
1662
|
+
if (existing === void 0) {
|
|
1663
|
+
existing = {
|
|
1664
|
+
text: "",
|
|
1665
|
+
closed: false
|
|
1666
|
+
};
|
|
1667
|
+
parts.set(index, existing);
|
|
1668
|
+
}
|
|
1669
|
+
return existing;
|
|
1670
|
+
};
|
|
1671
|
+
for await (const chunk of chunks) {
|
|
1672
|
+
if (chunk === null || typeof chunk !== "object") continue;
|
|
1673
|
+
const row = chunk;
|
|
1674
|
+
if (row.type === "tool-call-delta" || row.block?.type === "tool-call") throw new Error("codex-ui session title output must be text");
|
|
1675
|
+
if (row.type === "finish") {
|
|
1676
|
+
finish = typeof row.reason?.kind === "string" ? row.reason.kind : "unknown";
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (row.type !== "text-delta" && row.type !== "block-end") continue;
|
|
1680
|
+
if (typeof row.index !== "number" || !Number.isSafeInteger(row.index)) continue;
|
|
1681
|
+
if (row.type === "text-delta") {
|
|
1682
|
+
if (typeof row.text !== "string") continue;
|
|
1683
|
+
const target = part(row.index);
|
|
1684
|
+
if (!target.closed) target.text += row.text;
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
if (row.block?.type !== "text" || typeof row.block.text !== "string") continue;
|
|
1688
|
+
const target = part(row.index);
|
|
1689
|
+
if (target.closed) continue;
|
|
1690
|
+
target.text = row.block.text;
|
|
1691
|
+
target.closed = true;
|
|
1692
|
+
}
|
|
1693
|
+
if (finish !== "stop") throw new Error(`codex-ui session title output finished with "${finish}"`);
|
|
1694
|
+
return [...parts.values()].map((segment) => segment.text).join("").trim().split(/\r?\n/, 1)[0] ?? "";
|
|
1695
|
+
})();
|
|
1696
|
+
}
|
|
1697
|
+
async function generateCodexSessionTitle(llm, request) {
|
|
1698
|
+
request.signal?.throwIfAborted();
|
|
1699
|
+
const session = request.session;
|
|
1700
|
+
if (session === void 0 || shouldSkipAutoTitle(session)) throw new Error("codex-ui session title skipped");
|
|
1701
|
+
const first = request.messages?.[0];
|
|
1702
|
+
const text = first?.text?.trim() ?? "";
|
|
1703
|
+
if (first === void 0 || text === "") throw new Error("codex-ui session title missing message");
|
|
1704
|
+
const route = request.route;
|
|
1705
|
+
if (route === void 0 || route.provider === "" || route.model === "") throw new Error("codex-ui session title missing route");
|
|
1706
|
+
const framed = `根据这条用户消息生成会话标题。只返回“类型|主题”一行,不要第三段,不要重复主题。\n${JSON.stringify([{
|
|
1707
|
+
seq: first.seq,
|
|
1708
|
+
text
|
|
1709
|
+
}])}`;
|
|
1710
|
+
if (Buffer.byteLength(framed, "utf8") > MAX_INPUT_BYTES) throw new Error("codex-ui session title input too large");
|
|
1711
|
+
const signal = abortableSignal(request.signal);
|
|
1712
|
+
const raw = await collectText(llm.stream({
|
|
1713
|
+
provider: route.provider,
|
|
1714
|
+
model: route.model,
|
|
1715
|
+
messages: [{
|
|
1716
|
+
id: crypto.randomUUID(),
|
|
1717
|
+
role: "user",
|
|
1718
|
+
content: [{
|
|
1719
|
+
type: "text",
|
|
1720
|
+
text: framed
|
|
1721
|
+
}],
|
|
1722
|
+
source: {
|
|
1723
|
+
kind: "plugin",
|
|
1724
|
+
plugin: "michengai-codex-ui"
|
|
1725
|
+
}
|
|
1726
|
+
}],
|
|
1727
|
+
system: SYSTEM_PROMPT,
|
|
1728
|
+
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1729
|
+
sessionId: session.id,
|
|
1730
|
+
purpose: "session-title",
|
|
1731
|
+
signal
|
|
1732
|
+
}));
|
|
1733
|
+
signal.throwIfAborted();
|
|
1734
|
+
const parsed = parseTypeAndTheme(raw);
|
|
1735
|
+
if (parsed === void 0) throw new Error("codex-ui session title invalid model output");
|
|
1736
|
+
const title = assembleSessionTitle(parsed.type, parsed.theme);
|
|
1737
|
+
if (title === void 0) throw new Error("codex-ui session title rejected");
|
|
1738
|
+
return {
|
|
1739
|
+
title,
|
|
1740
|
+
messageSeqs: typeof first.seq === "number" ? [first.seq] : [],
|
|
1741
|
+
model: route
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function registerSessionTitleProvider(ctx) {
|
|
1745
|
+
const sessionTitle = asTitleService(ctx.get("sessionTitle"));
|
|
1746
|
+
const llm = asLlm(ctx.get("llm"));
|
|
1747
|
+
if (sessionTitle === void 0 || llm === void 0) return void 0;
|
|
1748
|
+
try {
|
|
1749
|
+
const dispose = sessionTitle.register({
|
|
1750
|
+
id: SESSION_TITLE_PROVIDER_ID,
|
|
1751
|
+
automatic: "first-prompt",
|
|
1752
|
+
generate: (request) => generateCodexSessionTitle(llm, request)
|
|
1753
|
+
});
|
|
1754
|
+
return typeof dispose === "function" ? dispose : void 0;
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
const reason = error instanceof Error ? error.message : typeof error;
|
|
1757
|
+
ctx.logger?.warn(`Codex UI 未能注册会话标题提供方:${reason}`);
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1558
1761
|
/** 校验结构和归属;存储兼容模式保留旧 locale 下合法的大小写变体名称。 */
|
|
1559
1762
|
function parseGroups(value, preserveCaseVariants) {
|
|
1560
1763
|
if (!Array.isArray(value) || value.length > 100) return void 0;
|
|
@@ -1773,6 +1976,7 @@ function sessionMoveAuthenticationError(status, error) {
|
|
|
1773
1976
|
/** 提供不泄露地址、命令和凭证的连接器目录。 */
|
|
1774
1977
|
function apply(ctx) {
|
|
1775
1978
|
apply$1(ctx);
|
|
1979
|
+
ctx.effect(() => registerSessionTitleProvider(ctx) ?? (() => {}), "michengai-codex-ui: session title");
|
|
1776
1980
|
const host = hostServices(ctx);
|
|
1777
1981
|
ctx.effect(() => {
|
|
1778
1982
|
const disposers = USAGE_FRAME_ROUTES.map((path) => host.webServer.register({
|