@walkhi/code-relax 0.1.0-beta.1 → 0.1.0-beta.3

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.
Files changed (63) hide show
  1. package/README.md +5 -5
  2. package/dist/bin/self-relay-server.mjs +5 -2
  3. package/dist/shared/app-server-events.cjs +45 -19
  4. package/dist/shared/p2p-data-channel.cjs +50 -0
  5. package/dist/src/app-server-tasks.mjs +28 -25
  6. package/dist/src/lan-socket-server.mjs +8 -2
  7. package/dist/src/platform/windows/IsolatedProcess.cs +7 -3
  8. package/dist/src/platform/windows/background-process.mjs +7 -2
  9. package/dist/src/platform/windows/launch-worker.mjs +42 -0
  10. package/dist/src/platform/windows/start-hidden-console.ps1 +18 -8
  11. package/dist/src/self-relay/admin-state.mjs +61 -0
  12. package/dist/src/self-relay/client.mjs +2 -0
  13. package/dist/src/self-relay/connector.mjs +23 -8
  14. package/dist/src/self-relay/demo.mjs +2 -2
  15. package/dist/src/self-relay/lifecycle.mjs +1 -1
  16. package/dist/src/self-relay/p2p-probe.mjs +123 -83
  17. package/dist/src/self-relay/server.mjs +137 -11
  18. package/dist/src/server.mjs +337 -255
  19. package/dist/src/shared-app-server.mjs +180 -24
  20. package/dist/src/shared-catalog.mjs +4 -14
  21. package/dist/src/thread-catalog.mjs +12 -7
  22. package/dist/web/activity-view.js +283 -0
  23. package/dist/web/capabilities.js +2 -2
  24. package/dist/web/chat-transport.js +15 -9
  25. package/dist/web/chat.css +139 -93
  26. package/dist/web/chat.js +1451 -2770
  27. package/dist/web/community-view.js +20 -0
  28. package/dist/web/community.css +77 -0
  29. package/dist/web/community.html +37 -0
  30. package/dist/web/composer-controller.js +101 -0
  31. package/dist/web/conversation-controller.js +99 -0
  32. package/dist/web/disclosure-state-controller.js +95 -0
  33. package/dist/web/draft-controller.js +103 -0
  34. package/dist/web/harmony-platform.js +3 -2
  35. package/dist/web/history-cache.js +112 -18
  36. package/dist/web/history-controller.js +167 -0
  37. package/dist/web/index.html +141 -38
  38. package/dist/web/link-action-controller.js +212 -0
  39. package/dist/web/message-send-controller.js +177 -0
  40. package/dist/web/message-view.js +98 -0
  41. package/dist/web/p2p-data-channel.js +50 -0
  42. package/dist/web/p2p-probe.js +67 -27
  43. package/dist/web/page-resume.js +28 -0
  44. package/dist/web/pending-message-store.js +108 -0
  45. package/dist/web/queue-controller.js +82 -0
  46. package/dist/web/resources.json +1 -1
  47. package/dist/web/self-relay-session.js +28 -18
  48. package/dist/web/station-connection-controller.js +75 -0
  49. package/dist/web/task-list-view.js +296 -0
  50. package/dist/web/thread-attention-controller.js +124 -0
  51. package/dist/web/thread-context-controller.js +30 -0
  52. package/dist/web/thread-list-controller.js +61 -0
  53. package/dist/web/thread-list-sync.js +86 -0
  54. package/dist/web/thread-title-controller.js +57 -0
  55. package/dist/web/timeline-formatters.js +249 -0
  56. package/dist/web/timeline-reducer.js +81 -0
  57. package/dist/web/timeline-renderer.js +161 -0
  58. package/dist/web/timeline-scroll-controller.js +50 -0
  59. package/dist/web/usage-controller.js +258 -0
  60. package/dist/web/vendor/lucide.LICENSE.txt +17 -0
  61. package/package.json +1 -1
  62. package/tools/postinstall.mjs +111 -2
  63. package/dist/src/platform/windows/launch-worker.ps1 +0 -13
@@ -0,0 +1,20 @@
1
+ (function (root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory;
3
+ else root.createCommunityView = factory;
4
+ })(globalThis, function createCommunityView({ button, page, onOpen, onClose }) {
5
+ function open() {
6
+ page.hidden = false;
7
+ button.setAttribute('aria-current', 'page');
8
+ onOpen?.();
9
+ }
10
+
11
+ function close() {
12
+ if (page.hidden) return;
13
+ page.hidden = true;
14
+ button.removeAttribute('aria-current');
15
+ onClose?.();
16
+ }
17
+
18
+ button.addEventListener('click', open);
19
+ return { open, close, isOpen: () => !page.hidden };
20
+ });
@@ -0,0 +1,77 @@
1
+ .community-preview { min-height:100vh; margin:0; overflow:hidden; color-scheme:dark; font:14px/1.45 Inter, "Segoe UI", sans-serif; }
2
+ .community-preview * { box-sizing:border-box; }
3
+ .community-preview .community-page { position:fixed; }
4
+ .community-page { position:absolute; z-index:8; inset:0; display:flex; overflow-y:auto; overscroll-behavior:contain; padding:32px 24px; color:#e8eee9; background:radial-gradient(circle at 16% 15%, #28503966 0, transparent 28%), radial-gradient(circle at 86% 76%, #1d3d2b55 0, transparent 26%), linear-gradient(#ffffff06 1px, transparent 1px), linear-gradient(90deg, #ffffff06 1px, transparent 1px), #101311; background-size:auto, auto, 32px 32px, 32px 32px, auto; }
5
+ .community-page[hidden] { display:none; }
6
+ .community-placeholder { position:relative; display:flex; width:100%; max-width:660px; flex:none; flex-direction:column; align-items:center; margin:auto; text-align:center; }
7
+ .community-placeholder::before { content:""; position:absolute; z-index:-1; top:-54px; left:50%; width:min(560px, 92vw); height:260px; border-radius:50%; background:#3c8b5a12; filter:blur(45px); transform:translateX(-50%); pointer-events:none; }
8
+ .community-mark { display:grid; place-items:center; width:58px; height:58px; margin-bottom:20px; border:1px solid #405248; border-radius:18px; color:#76d395; background:#1a251e; box-shadow:0 14px 36px #0004; }
9
+ .community-mark svg { width:34px; height:34px; overflow:visible; fill:none; stroke:currentColor; stroke-width:20.5; stroke-linecap:round; stroke-linejoin:round; }
10
+ .community-kicker { color:#68bb87; font-size:10px; font-weight:750; letter-spacing:.16em; }
11
+ .community-placeholder h2 { margin:8px 0 0; font-size:clamp(21px, 4vw, 28px); line-height:1.25; }
12
+ .community-description { max-width:430px; margin:10px 0 0; color:#9eaca3; font-size:13px; line-height:1.65; }
13
+ .community-section-heading { display:flex; width:100%; align-items:center; gap:12px; margin-top:27px; color:#b8c4bc; text-align:left; }
14
+ .community-section-heading::after { content:""; height:1px; flex:1; background:linear-gradient(90deg, #3b4b41, transparent); }
15
+ .community-section-heading span { flex:none; font-size:12px; font-weight:650; }
16
+ .community-section-heading small { flex:none; color:#68786e; font-size:10px; }
17
+ .community-questions { display:grid; width:100%; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px; margin-top:10px; text-align:left; }
18
+ .community-question { position:relative; display:flex; min-height:96px; overflow:hidden; flex-direction:column; justify-content:space-between; gap:18px; padding:15px 16px; border:1px solid #303b34; border-radius:14px; background:linear-gradient(145deg, #19201cdd, #151a17dd); box-shadow:0 10px 24px #00000014; }
19
+ .community-question::after { content:""; position:absolute; right:-26px; bottom:-34px; width:86px; height:86px; border:1px solid #6ec88f12; border-radius:50%; box-shadow:0 0 0 18px #6ec88f08; pointer-events:none; }
20
+ .community-question-featured { grid-column:1 / -1; min-height:116px; border-color:#42604e; background:linear-gradient(130deg, #223b2c, #171d19 72%); box-shadow:0 16px 34px #0003, inset 3px 0 #68c08a; }
21
+ .community-question-featured::after { content:"?"; right:22px; bottom:-29px; width:auto; height:auto; border:0; color:#b9e6c9; box-shadow:none; font-size:108px; font-weight:750; line-height:1; opacity:.055; }
22
+ .community-question-hot { border-color:#3b493f; background:linear-gradient(145deg, #1c241f, #161b18); }
23
+ .community-question-wide { grid-column:1 / -1; min-height:auto; flex-direction:row; align-items:center; gap:14px; }
24
+ .community-question > span { display:flex; align-items:center; color:#78887e; font-size:10px; letter-spacing:.04em; }
25
+ .community-question > span b { margin-right:8px; color:#68bb87; font-weight:750; letter-spacing:.1em; }
26
+ .community-question > span i { margin-left:auto; padding:2px 6px; border:1px solid #4b5e51; border-radius:999px; color:#8ab99a; font-size:9px; font-style:normal; letter-spacing:.04em; }
27
+ .community-question-hot > span i { border-color:#745b35; color:#d5a85f; background:#d5a85f0d; }
28
+ .community-question strong { color:#dce5df; font-size:13px; font-weight:550; line-height:1.5; }
29
+ .community-question-featured strong { max-width:500px; font-size:19px; }
30
+ .community-agent-board { display:grid; width:100%; grid-template-columns:auto minmax(0, 1fr) auto; align-items:center; gap:13px; margin-top:13px; padding:14px 15px; border:1px dashed #564b70; border-radius:14px; background:linear-gradient(135deg, #211e29dd, #171a18dd); text-align:left; }
31
+ .community-agent-board-mark { display:grid; place-items:center; width:38px; height:38px; border:1px solid #756592; border-radius:12px; color:#c5aaef; background:#ad7bf912; font-size:15px; font-weight:750; }
32
+ .community-agent-board > div { display:flex; min-width:0; flex-direction:column; }
33
+ .community-agent-board > div > span { color:#927daf; font-size:9px; font-weight:700; letter-spacing:.1em; }
34
+ .community-agent-board strong { margin-top:2px; color:#e1d8ef; font-size:13px; }
35
+ .community-agent-board p { margin:3px 0 0; color:#948b9f; font-size:11px; line-height:1.45; }
36
+ .community-agent-board > small { align-self:start; flex:none; padding:3px 7px; border:1px solid #665779; border-radius:999px; color:#aa95c3; font-size:9px; }
37
+ .community-status { display:flex; width:min(380px, 100%); align-items:center; gap:11px; margin-top:25px; padding:13px 15px; border:1px solid #344239; border-radius:13px; background:#171c19e6; box-shadow:0 12px 30px #0002; text-align:left; }
38
+ .community-status-dot { width:9px; height:9px; flex:none; border-radius:50%; background:#76d395; box-shadow:0 0 0 5px #76d39516, 0 0 16px #76d39555; }
39
+ .community-status > span:last-child { display:flex; min-width:0; flex:1; align-items:center; justify-content:space-between; gap:12px; }
40
+ .community-status strong { color:#dce5df; font-size:12px; font-weight:600; }
41
+ .community-status small { flex:none; color:#718078; font-size:11px; }
42
+ @media (max-width:599px) {
43
+ .community-page { padding:58px 16px 24px; background-size:auto, auto, 28px 28px, 28px 28px, auto; }
44
+ .community-mark { width:54px; height:54px; margin-bottom:18px; }
45
+ .community-section-heading { margin-top:23px; }
46
+ .community-questions { grid-template-columns:1fr; }
47
+ .community-question-featured, .community-question-wide { grid-column:auto; }
48
+ .community-question-wide { min-height:92px; flex-direction:column; align-items:stretch; }
49
+ .community-agent-board { grid-template-columns:auto minmax(0, 1fr); }
50
+ .community-agent-board > small { display:none; }
51
+ .community-status { margin-top:22px; }
52
+ }
53
+ @media (prefers-color-scheme:light) {
54
+ .community-preview { color-scheme:light; }
55
+ .community-page { color:#17212b; background:radial-gradient(circle at 16% 15%, #cfe6d7aa 0, transparent 28%), radial-gradient(circle at 86% 76%, #dbece0aa 0, transparent 26%), linear-gradient(#1f4a2d0b 1px, transparent 1px), linear-gradient(90deg, #1f4a2d0b 1px, transparent 1px), #f5f7fa; background-size:auto, auto, 32px 32px, 32px 32px, auto; }
56
+ .community-mark { border-color:#cbd8d0; background:#fff; box-shadow:0 14px 36px #263a2d14; }
57
+ .community-description { color:#607168; }
58
+ .community-section-heading { color:#52645a; }
59
+ .community-section-heading::after { background:linear-gradient(90deg, #c6d4cb, transparent); }
60
+ .community-section-heading small { color:#849289; }
61
+ .community-question { border-color:#d5ddd8; background:linear-gradient(145deg, #ffffffee, #f7f9f8ee); box-shadow:0 10px 24px #263a2d0a; }
62
+ .community-question-featured { border-color:#a9c6b3; background:linear-gradient(135deg, #e4f1e8, #fff 72%); box-shadow:0 16px 34px #263a2d12, inset 3px 0 #68b582; }
63
+ .community-question-hot { border-color:#d7d7c9; background:linear-gradient(145deg, #fff, #fafaf6); }
64
+ .community-question > span { color:#77857d; }
65
+ .community-question > span i { border-color:#bdcec3; color:#59836a; }
66
+ .community-question-hot > span i { border-color:#d8c49f; color:#a87326; background:#c98b2e0a; }
67
+ .community-question strong { color:#26332b; }
68
+ .community-agent-board { border-color:#cbbdde; background:linear-gradient(135deg, #f4effb, #fff); }
69
+ .community-agent-board-mark { border-color:#c6b4dd; color:#765b99; background:#ad7bf912; }
70
+ .community-agent-board > div > span { color:#80669f; }
71
+ .community-agent-board strong { color:#3b2d4d; }
72
+ .community-agent-board p { color:#756b80; }
73
+ .community-agent-board > small { border-color:#c6b4d5; color:#765f90; }
74
+ .community-status { border-color:#d5ddd8; background:#ffffffd9; }
75
+ .community-status strong { color:#26332b; }
76
+ .community-status small { color:#77857d; }
77
+ }
@@ -0,0 +1,37 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Code Relax 社区</title>
7
+ <link rel="stylesheet" href="community.css">
8
+ </head>
9
+ <body class="community-preview">
10
+ <main class="community-page">
11
+ <div class="community-placeholder">
12
+ <span class="community-mark" aria-hidden="true"><svg viewBox="149 149 418 418"><path d="M247.429 247.43C257.73 208.911 292.871 180.543 334.638 180.543C359.555 180.543 382.115 190.64 398.449 206.964C405.906 204.97 413.743 203.905 421.829 203.905C471.681 203.906 512.096 244.32 512.096 294.173C512.096 302.259 511.031 310.096 509.037 317.553C525.361 333.887 535.458 356.446 535.458 381.364C535.458 423.131 507.09 458.271 468.571 468.572C458.271 507.091 423.131 535.459 381.364 535.459C356.446 535.459 333.886 525.362 317.552 509.037C310.095 511.031 302.258 512.097 294.172 512.097C244.319 512.097 203.906 471.682 203.906 421.829C203.906 413.743 204.969 405.905 206.963 398.448C190.639 382.115 180.543 359.555 180.543 334.638C180.543 292.871 208.91 257.73 247.429 247.43Z"/><path d="M436.706 408.738H370.021"/><path d="M276.533 309.154L303.468 357.831C304.433 359.575 304.412 361.698 303.414 363.423L276.533 409.854"/></svg></span>
13
+ <span class="community-kicker">CODE RELAX COMMUNITY</span>
14
+ <h2>面向 Codex 用户的中文实践社区</h2>
15
+ <p class="community-description">分享真实工作流、使用成本、模型体验和一路踩过的坑。</p>
16
+ <aside class="community-agent-board">
17
+ <span class="community-agent-board-mark" aria-hidden="true">A</span>
18
+ <div><span>Agent 自治社区 · 概念规划</span><strong>给你的 Agent 女仆 / 搭档 / 分身,一个只属于它们的社区</strong><p>这里只有 Agent 能开题、投票和发言;人类负责围观,以及照看自己的 Agent。</p></div>
19
+ <small>概念规划</small>
20
+ </aside>
21
+ <div class="community-section-heading"><span>社区热议预告</span><small>6 个话题</small></div>
22
+ <div class="community-questions" aria-label="社区话题预览">
23
+ <article class="community-question community-question-featured"><span><b>01</b>成本与回报<i>必答题</i></span><strong>你能挣回 Codex 的订阅费吗?</strong></article>
24
+ <article class="community-question community-question-hot"><span><b>02</b>模型与额度<i>热议</i></span><strong>Astra 是真聪明,还是只会猛烧额度?</strong></article>
25
+ <article class="community-question community-question-hot"><span><b>03</b>Codex 团队<i>热议</i></span><strong>Tibo,这周还给大家重置额度吗?</strong></article>
26
+ <article class="community-question"><span><b>04</b>云端 Agent</span><strong>Agents API 这么贵,真有人舍得让它自己跑吗?</strong></article>
27
+ <article class="community-question"><span><b>05</b>多 Agent</span><strong>主 Agent 负责想,便宜 Agent 负责干,真能省额度吗?</strong></article>
28
+ <article class="community-question community-question-wide"><span><b>06</b>换谁干活<i>开放讨论</i></span><strong>额度烧完以后,你会回 Claude,还是投奔国产模型?</strong></article>
29
+ </div>
30
+ <div class="community-status" role="status">
31
+ <span class="community-status-dot" aria-hidden="true"></span>
32
+ <span><strong>社区正在建设中</strong><small>敬请期待</small></span>
33
+ </div>
34
+ </div>
35
+ </main>
36
+ </body>
37
+ </html>
@@ -0,0 +1,101 @@
1
+ (function (root) {
2
+ function createComposerController(options) {
3
+ const { state, transport, platform, el, pendingMessages } = options;
4
+
5
+ function isActive() {
6
+ return state.timeline?.thread?.status === 'active' || state.timeline?.turns?.at(-1)?.status === 'inProgress';
7
+ }
8
+
9
+ function queueActionVisible() {
10
+ return transport.has('queue') && isActive()
11
+ && Boolean(el('prompt').value.trim() || state.draftImages.length);
12
+ }
13
+
14
+ function queueChoiceAvailable() {
15
+ return queueActionVisible() && !options.isConnectionBlocked() && !state.historyLoading && !state.timeline?.cached
16
+ && !state.sending && !state.uploadingImages && !platform.voice.stopping
17
+ && options.actionState('send').enabled;
18
+ }
19
+
20
+ function shouldStop() {
21
+ return transport.has('primaryStop') && isActive() && !platform.voice.active
22
+ && !state.uploadingImages && !el('prompt').value.trim() && !state.draftImages.length;
23
+ }
24
+
25
+ function closeAddMenu() {
26
+ el('addMenu').hidden = true;
27
+ el('addMenuTrigger').setAttribute('aria-expanded', 'false');
28
+ }
29
+
30
+ function updateActions() {
31
+ const stop = shouldStop();
32
+ const dualActions = queueActionVisible();
33
+ const button = el('send');
34
+ const action = stop ? 'stop' : dualActions ? 'steer' : 'send';
35
+ if (button.dataset.action !== action) {
36
+ button.dataset.action = action;
37
+ button.querySelector('svg').innerHTML = stop
38
+ ? '<rect x="5" y="5" width="14" height="14" rx="2.25" fill="currentColor" stroke="none"/>'
39
+ : dualActions
40
+ ? '<path d="M13.5 18V6"/><path d="m9.5 10 4-4 4 4"/><path d="M6.5 18v-2.5a3 3 0 0 1 3-3h4"/>'
41
+ : '<path d="M12 18V6"/><path d="m7.5 10.5 4.5-4.5 4.5 4.5"/>';
42
+ }
43
+ button.title = stop ? '停止任务' : platform.voice.active
44
+ ? (isActive() ? '结束录音并插队发送' : '结束录音并发送')
45
+ : isActive() ? '插队发送' : '发送消息';
46
+ el('sendQueued').hidden = !dualActions;
47
+ el('sendQueued').disabled = !queueChoiceAvailable();
48
+ button.setAttribute('aria-label', button.title);
49
+ button.disabled = !options.actionState(stop ? 'interrupt' : 'send',
50
+ state.sending || state.uploadingImages || platform.voice.stopping || options.isStopping(state.selectedId)).enabled;
51
+ for (const message of pendingMessages.get(state.selectedId)) if (message.retryElement) {
52
+ message.retryElement.disabled = Boolean(message.retrying || state.sending || options.isConnectionBlocked()
53
+ || options.isThreadReadOnly() || state.historyLoading);
54
+ }
55
+ const addDisabled = options.isConnectionBlocked() || options.isThreadReadOnly() || state.uploadingImages || state.sending;
56
+ el('addMenuTrigger').disabled = addDisabled;
57
+ el('addImage').disabled = addDisabled;
58
+ if (addDisabled) closeAddMenu();
59
+ }
60
+
61
+ function setControlsDisabled(disabled) {
62
+ const addDisabled = options.isConnectionBlocked() || options.isThreadReadOnly() || disabled || state.uploadingImages;
63
+ el('addMenuTrigger').disabled = addDisabled;
64
+ el('addImage').disabled = addDisabled;
65
+ if (addDisabled) closeAddMenu();
66
+ el('send').disabled = options.isConnectionBlocked() || options.isThreadReadOnly() || disabled;
67
+ el('sendQueued').disabled = options.isConnectionBlocked() || options.isThreadReadOnly() || disabled;
68
+ }
69
+
70
+ function updateMode() {
71
+ const active = isActive();
72
+ const separateStop = transport.has('separateStop');
73
+ el('interruptTurn').hidden = !separateStop || !active;
74
+ el('interruptTurn').disabled = !options.actionState('interrupt', options.isStopping(state.selectedId)).enabled;
75
+ el('turnActions').hidden = el('interruptTurn').hidden;
76
+ el('prompt').readOnly = options.isThreadReadOnly();
77
+ el('prompt').placeholder = options.isThreadReadOnly() ? '该对话正在另一个 Codex 实例中使用' : '随心输入';
78
+ el('modelTrigger').disabled = Boolean(options.isConnectionBlocked() || options.isThreadReadOnly() || state.historyLoading);
79
+ el('modelMenu').setAttribute('aria-disabled', String(el('modelTrigger').disabled));
80
+ options.setApprovalConnected(!options.isConnectionBlocked() && !options.isThreadReadOnly());
81
+ options.renderFastMode();
82
+ options.renderPermissionControl();
83
+ options.renderSharedServiceNotice();
84
+ updateActions();
85
+ return active;
86
+ }
87
+
88
+ return Object.freeze({
89
+ closeAddMenu,
90
+ isActive,
91
+ queueActionVisible,
92
+ queueChoiceAvailable,
93
+ setControlsDisabled,
94
+ shouldStop,
95
+ updateActions,
96
+ updateMode,
97
+ });
98
+ }
99
+
100
+ root.createComposerController = createComposerController;
101
+ })(globalThis);
@@ -0,0 +1,99 @@
1
+ (function (root) {
2
+ function createConversationController(state) {
3
+ function nextSelection() {
4
+ state.selectionVersion = (state.selectionVersion || 0) + 1;
5
+ return state.selectionVersion;
6
+ }
7
+
8
+ function generation() {
9
+ return state.selectionVersion || 0;
10
+ }
11
+
12
+ function invalidate(options = {}) {
13
+ const selection = nextSelection();
14
+ if (typeof options.historyLoading === 'boolean') state.historyLoading = options.historyLoading;
15
+ if (options.clearProgress === true) state.historyProgress = '';
16
+ return selection;
17
+ }
18
+
19
+ function planSelection(thread, newThreadActive = false) {
20
+ const sameThread = state.selectedId === thread.id;
21
+ return {
22
+ previousThreadId: state.selectedId || '',
23
+ retainedTimeline: sameThread ? state.timeline : null,
24
+ switchingThread: newThreadActive || !sameThread,
25
+ };
26
+ }
27
+
28
+ function activateThread(thread, retainedTimeline = null) {
29
+ state.selectedId = thread.id;
30
+ state.selectedThread = thread;
31
+ state.timeline = retainedTimeline;
32
+ state.currentModel = '';
33
+ state.currentThinking = '';
34
+ state.currentServiceTier = null;
35
+ state.currentPermissionMode = null;
36
+ state.permissionModes = [];
37
+ state.historyLoading = true;
38
+ state.historyProgress = '正在同步会话…';
39
+ state.timelineAtBottom = true;
40
+ return nextSelection();
41
+ }
42
+
43
+ function clear(options = {}) {
44
+ nextSelection();
45
+ state.selectedId = null;
46
+ state.selectedThread = null;
47
+ state.timeline = null;
48
+ state.historyLoading = false;
49
+ state.historyProgress = '';
50
+ if (options.resetSettings === true) {
51
+ state.currentModel = '';
52
+ state.currentThinking = '';
53
+ state.currentServiceTier = null;
54
+ state.currentPermissionMode = null;
55
+ state.permissionModes = [];
56
+ }
57
+ }
58
+
59
+ function restoreCached(cached, threads = []) {
60
+ const selectedThread = threads.find(thread => thread.id === cached.thread.id) || cached.thread;
61
+ state.timeline = { ...cached, cached: true };
62
+ state.selectedThread = selectedThread;
63
+ state.selectedId = selectedThread.id;
64
+ state.historyLoading = true;
65
+ state.historyProgress = '正在同步会话…';
66
+ return selectedThread;
67
+ }
68
+
69
+ function beginSync() {
70
+ state.historyLoading = true;
71
+ if (!state.historyProgress) state.historyProgress = '正在同步会话…';
72
+ return nextSelection();
73
+ }
74
+
75
+ function isCurrent(selection, threadId = '') {
76
+ return selection === state.selectionVersion && (!threadId || state.selectedId === threadId);
77
+ }
78
+
79
+ function completeSync(selection, timeline) {
80
+ if (!isCurrent(selection, timeline?.thread?.id)) return false;
81
+ state.timeline = timeline;
82
+ state.historyLoading = false;
83
+ state.historyProgress = '';
84
+ return true;
85
+ }
86
+
87
+ function failSync(selection) {
88
+ if (!isCurrent(selection)) return false;
89
+ state.historyLoading = false;
90
+ state.historyProgress = '';
91
+ return true;
92
+ }
93
+
94
+ return Object.freeze({ generation, invalidate, planSelection, activateThread, clear, restoreCached,
95
+ beginSync, isCurrent, completeSync, failSync });
96
+ }
97
+
98
+ root.createConversationController = createConversationController;
99
+ })(globalThis);
@@ -0,0 +1,95 @@
1
+ (function (root) {
2
+ function createDisclosureStateController(options = {}) {
3
+ const states = new Map();
4
+ const limit = Math.max(1, Number(options.limit) || 600);
5
+ const getThreadId = typeof options.getThreadId === 'function' ? options.getThreadId : () => '';
6
+
7
+ function scopedKey(key, threadId = getThreadId()) {
8
+ return `${threadId || '__new__'}\n${key}`;
9
+ }
10
+
11
+ function detailsWithin(element) {
12
+ const nested = [...element.querySelectorAll('details[data-disclosure-key]')];
13
+ return element.matches?.('details[data-disclosure-key]') ? [element, ...nested] : nested;
14
+ }
15
+
16
+ function remember(key, open, threadId = getThreadId()) {
17
+ if (!key) return;
18
+ const storedKey = scopedKey(key, threadId);
19
+ if (states.has(storedKey)) states.delete(storedKey);
20
+ states.set(storedKey, Boolean(open));
21
+ while (states.size > limit) states.delete(states.keys().next().value);
22
+ }
23
+
24
+ function forget(key, threadId = getThreadId()) {
25
+ if (key) states.delete(scopedKey(key, threadId));
26
+ }
27
+
28
+ function forgetWithin(element, threadId = getThreadId()) {
29
+ for (const details of detailsWithin(element)) forget(details.dataset.disclosureKey, threadId);
30
+ }
31
+
32
+ function collapseWithin(element, threadId = getThreadId()) {
33
+ for (const details of detailsWithin(element)) {
34
+ details.open = false;
35
+ forget(details.dataset.disclosureKey, threadId);
36
+ }
37
+ }
38
+
39
+ function captureWithin(element, threadId = getThreadId()) {
40
+ for (const details of element.querySelectorAll('details[data-disclosure-key]')) {
41
+ remember(details.dataset.disclosureKey, details.open, threadId);
42
+ }
43
+ }
44
+
45
+ function preserve(existing, replacement, threadId = getThreadId()) {
46
+ const current = new Map(detailsWithin(existing)
47
+ .map(details => [details.dataset.disclosureKey, details]));
48
+ const fresh = detailsWithin(replacement);
49
+ const archived = new Set();
50
+ for (const details of fresh) {
51
+ const previous = current.get(details.dataset.disclosureKey);
52
+ if (!previous?.closest('.active-process') || details.closest('.active-process')) continue;
53
+ const historyRoot = details.closest('.settled-process, .completed-process') || details;
54
+ for (const archivedDetails of detailsWithin(historyRoot)) archived.add(archivedDetails.dataset.disclosureKey);
55
+ collapseWithin(historyRoot, threadId);
56
+ }
57
+ for (const details of fresh) {
58
+ const previous = current.get(details.dataset.disclosureKey);
59
+ if (!previous || archived.has(details.dataset.disclosureKey)) continue;
60
+ details.open = previous.open;
61
+ remember(details.dataset.disclosureKey, details.open, threadId);
62
+ }
63
+ }
64
+
65
+ function bind(details, key, defaultOpen = false, threadId = getThreadId()) {
66
+ if (!key) {
67
+ details.open = defaultOpen;
68
+ return details;
69
+ }
70
+ details.dataset.disclosureKey = key;
71
+ const storedKey = scopedKey(key, threadId);
72
+ details.open = states.has(storedKey) ? states.get(storedKey) : defaultOpen;
73
+ details.addEventListener('toggle', () => remember(key, details.open, threadId));
74
+ return details;
75
+ }
76
+
77
+ function restore(entries) {
78
+ if (!Array.isArray(entries)) return;
79
+ for (const [key, open] of entries) states.set(key, open === true);
80
+ while (states.size > limit) states.delete(states.keys().next().value);
81
+ }
82
+
83
+ return Object.freeze({
84
+ bind,
85
+ captureWithin,
86
+ collapseWithin,
87
+ entries: () => [...states],
88
+ forgetWithin,
89
+ preserve,
90
+ restore,
91
+ });
92
+ }
93
+
94
+ root.createDisclosureStateController = createDisclosureStateController;
95
+ })(globalThis);
@@ -0,0 +1,103 @@
1
+ (function (root, factory) {
2
+ const create = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = create;
4
+ else root.createDraftController = create;
5
+ })(globalThis, function () {
6
+ function createDraftController(options) {
7
+ const { platform, transport, localStorage, scope, onPersistenceError } = options;
8
+ const nativeStorage = platform.has('readDraft') && platform.has('saveDraft');
9
+ const newThreads = new Map();
10
+ const conversations = new Map();
11
+ let saveTimer;
12
+
13
+ function newThreadKey(project) {
14
+ return `new:${project?.hostId || 'local'}:${project?.projectId || '__unassigned__'}`;
15
+ }
16
+
17
+ function conversationKey(threadId) {
18
+ return `thread:${threadId}`;
19
+ }
20
+
21
+ function localKey(key) {
22
+ return 'codexRemoteDraftV2:' + JSON.stringify([transport.draftScope(), key]);
23
+ }
24
+
25
+ function readText(key) {
26
+ try {
27
+ return nativeStorage
28
+ ? platform.readDraft(scope(), key) || ''
29
+ : localStorage.getItem(localKey(key)) || '';
30
+ } catch {
31
+ return '';
32
+ }
33
+ }
34
+
35
+ function writeText(key, text) {
36
+ try {
37
+ if (nativeStorage) platform.saveDraft(scope(), key, text);
38
+ else if (text) localStorage.setItem(localKey(key), text);
39
+ else localStorage.removeItem(localKey(key));
40
+ } catch {
41
+ onPersistenceError?.();
42
+ }
43
+ }
44
+
45
+ function loadNewThread(project) {
46
+ const key = newThreadKey(project);
47
+ const draft = newThreads.get(key);
48
+ return { key, text: draft?.text ?? readText(key), images: draft?.images || [] };
49
+ }
50
+
51
+ function saveNewThread(project, text, images) {
52
+ if (!project) return;
53
+ const key = newThreadKey(project);
54
+ newThreads.set(key, { text, images: [...images] });
55
+ writeText(key, text);
56
+ }
57
+
58
+ function clearNewThread(project) {
59
+ const key = typeof project === 'string' ? project : newThreadKey(project);
60
+ newThreads.delete(key);
61
+ writeText(key, '');
62
+ }
63
+
64
+ function loadConversation(threadId) {
65
+ const draft = conversations.get(threadId);
66
+ conversations.delete(threadId);
67
+ return { text: draft?.text ?? readText(conversationKey(threadId)), images: draft?.images || [] };
68
+ }
69
+
70
+ function saveConversation(threadId, text, images) {
71
+ if (!threadId) return;
72
+ conversations.set(threadId, { text, images: [...images] });
73
+ writeText(conversationKey(threadId), text);
74
+ }
75
+
76
+ function clearConversation(threadId) {
77
+ conversations.delete(threadId);
78
+ writeText(conversationKey(threadId), '');
79
+ }
80
+
81
+ function scheduleSave(save, delay = 300) {
82
+ clearTimeout(saveTimer);
83
+ saveTimer = setTimeout(() => {
84
+ saveTimer = undefined;
85
+ save();
86
+ }, delay);
87
+ }
88
+
89
+ function flush(save) {
90
+ clearTimeout(saveTimer);
91
+ saveTimer = undefined;
92
+ save();
93
+ }
94
+
95
+ return {
96
+ newThreadKey, loadNewThread, saveNewThread, clearNewThread,
97
+ loadConversation, saveConversation, clearConversation,
98
+ scheduleSave, flush,
99
+ };
100
+ }
101
+
102
+ return createDraftController;
103
+ });
@@ -8,6 +8,7 @@
8
8
  // so shared chat code does not depend on the HarmonyOS bridge ABI.
9
9
  const names = {
10
10
  debugAccessChanged: 'harmonyDebugAccessChanged',
11
+ announcementChanged: 'harmonyAnnouncementChanged',
11
12
  externalUiChanged: 'harmonyExternalUiChanged', showFeedback: 'harmonyShowFeedback', appLogFeedback: 'harmonyAppLogFeedback',
12
13
  beginVoice: 'harmonyBeginVoice', sendVoice: 'harmonySendVoice',
13
14
  voiceText: 'harmonyVoiceText', endVoice: 'harmonyEndVoice', pause: 'harmonyPause',
@@ -28,8 +29,6 @@
28
29
  has: name => typeof root.codexNative?.[name] === 'function',
29
30
  reportStatus: (...args) => root.codexNative?.reportStatus?.(...args),
30
31
  reportEntry: active => root.codexNative?.reportEntry?.(active),
31
- reportTitle: title => root.codexNative?.reportTitle?.(title),
32
- reportLayout: wide => root.codexNative?.reportLayout?.(wide),
33
32
  reportTransportMode: mode => root.codexNative?.reportTransportMode?.(mode),
34
33
  readLastThread: scope => root.codexNative.readLastThread(scope),
35
34
  saveLastThread: (scope, value) => root.codexNative.saveLastThread(scope, value),
@@ -43,6 +42,8 @@
43
42
  reloadPage: (threadId, snapshot) => root.codexNative.reloadPage(threadId, snapshot),
44
43
  openSettings: () => root.codexNative.openSettings(),
45
44
  openNetworkDiagnostics: () => root.codexNative.openNetworkDiagnostics(),
45
+ openAppLog: () => root.codexNative.openAppLog(),
46
+ openAnnouncement: () => root.codexNative.openAnnouncement(),
46
47
  sendAppLog: () => root.codexNative.sendAppLog(),
47
48
  clearAppLog: () => root.codexNative.clearAppLog(),
48
49
  returnToComputer: () => root.codexNative.returnToComputer(),