zcode-pro 0.18.0 → 0.19.0

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.
@@ -0,0 +1,169 @@
1
+ // 任务名提示词(自动起名):官方在新会话首轮后自动起名的提示词写死在运行时
2
+ // 里、不可配置,起出的名字常不合适。本功能在“新会话”出现后,把会话区的
3
+ // 用户消息交给 helper(/title-gen/run),用设置弹窗里选择的供应商与提示词
4
+ // 重新生成标题,并按“手动重命名”写入任务索引(title_overridden=1):
5
+ // - 触发信号 = 侧栏出现一个全新的任务行(本页面生命周期内没见过的
6
+ // data-task-item-key)且它是当前选中会话——项目展开/滚动复现的旧行、切换
7
+ // 到老会话、后台建的任务都不会触发;
8
+ // - 生成时机 = 模型开始作答(会话区出现用户消息以外的内容变动)。提问阶段
9
+ // 的全部用户消息都会进入标题——新建任务连问两次、第一次还没得到回答的
10
+ // 场景,两次提问都算数;迟迟没有作答时按静默时长兜底生成;
11
+ // - 官方自动起名从此不会再覆盖索引里的名字(索引层尊重重命名标记),界面
12
+ // 层偶发的覆盖显示由 title-lock 按锁定表钉回;写库成功后立即更新侧栏行
13
+ // 并刷新锁定表,新名即刻上屏;
14
+ // - 失败静默(一次生成失败不打扰用户),每个会话只尝试一次。
15
+ // 扫描事件驱动(任务行相关变动去抖 300ms)+ 低频兜底轮询,选法同 title-lock。
16
+ import { rpc, getConfig } from '../core.js';
17
+ import { setRowTitle, refreshPinnedTitles } from './title-lock.js';
18
+
19
+ const CONV = '[class*="@md/conversation"]';
20
+ const USER_ROW = '[class*="user-row"]';
21
+ const SCAN_DEBOUNCE_MS = 300; // 任务行相关 DOM 变动后的去抖
22
+ const SCAN_FALLBACK_MS = 5000; // 兜底轮询周期(补观察器覆盖不到的路径)
23
+ const SELECT_WAIT_MS = 2000; // 新行出现后等它成为当前会话的时间
24
+ const MSG_WAIT_MS = 10000; // 任务行先于消息上屏时,等首条消息的最长时间
25
+ const QUIET_FALLBACK_MS = 120000; // 迟迟没有作答时的静默兜底(期间追加的提问也会带上)
26
+ const COLLECT_MAX_MS = 600000; // 收集期上限(10 分钟内始终没等到作答就放弃)
27
+
28
+ let installed = false;
29
+ const knownKeys = new Set(); // 页面生命周期内见过的任务键(含启动时已在侧栏的)
30
+ const attempted = new Set(); // 已处理过(成功或失败)的会话 id,防重复
31
+
32
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
33
+
34
+ function sessIdOf(key) {
35
+ // 会话键格式 <workspace_path>:<sess_id>;sess 段固定以 "sess_" 开头(见 session-switch.js)
36
+ const i = key.indexOf(':sess_');
37
+ return i > 0 ? key.slice(i + 1) : '';
38
+ }
39
+
40
+ function currentKey() {
41
+ for (const el of document.querySelectorAll('[data-task-item-key]')) {
42
+ if ((el.className + '').includes('bg-selected')) return el.getAttribute('data-task-item-key') || '';
43
+ }
44
+ return '';
45
+ }
46
+
47
+ // 会话区全部用户消息文本(按 DOM 顺序);没有用户消息时返回空数组
48
+ function userTexts() {
49
+ return [...document.querySelectorAll(`${CONV} ${USER_ROW}`)]
50
+ .map((r) => {
51
+ const el = r.querySelector('.whitespace-pre-wrap');
52
+ return ((el ? el.textContent : r.innerText) || '').trim();
53
+ })
54
+ .filter(Boolean);
55
+ }
56
+
57
+ async function onNewTaskRow(key) {
58
+ // 新任务行创建时选中标记可能晚一拍;短暂等待它成为当前会话,
59
+ // 期间没选中(其他入口建的任务)就不处理
60
+ const selDeadline = Date.now() + SELECT_WAIT_MS;
61
+ while (currentKey() !== key && Date.now() < selDeadline) await sleep(200);
62
+ if (currentKey() !== key) return;
63
+
64
+ const cfg = await getConfig().catch(() => null);
65
+ if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
66
+
67
+ const sessId = sessIdOf(key);
68
+ if (!sessId || attempted.has(sessId)) return;
69
+ attempted.add(sessId);
70
+
71
+ // 任务行可能先于消息渲染出现:等首条用户消息上屏
72
+ let texts = [];
73
+ const msgDeadline = Date.now() + MSG_WAIT_MS;
74
+ while (Date.now() < msgDeadline) {
75
+ texts = userTexts();
76
+ if (texts.length) break;
77
+ await sleep(400);
78
+ }
79
+ if (!texts.length) return;
80
+
81
+ // 收集期:会话区出现用户消息以外的内容变动 = 模型开始作答,提问阶段结束,
82
+ // 立即生成;期间追加的用户消息会刷新收集与静默计时。切到别的会话时暂停
83
+ // 收集(不把别的会话的内容混进来),切回来继续
84
+ let assistantSeen = false;
85
+ const convObserver = new MutationObserver((muts) => {
86
+ if (currentKey() !== key) return;
87
+ for (const m of muts) {
88
+ const t = m.type === 'characterData' ? m.target.parentElement : m.target;
89
+ if (t instanceof Element && t.closest(USER_ROW)) continue; // 用户消息自身的变动不算作答
90
+ for (const n of [t, ...m.addedNodes]) {
91
+ if (n instanceof Element && n.closest && n.closest(CONV)) { assistantSeen = true; return; }
92
+ }
93
+ }
94
+ });
95
+ convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
96
+ const startAt = Date.now();
97
+ let lastCount = texts.length;
98
+ let lastChange = Date.now();
99
+ try {
100
+ while (Date.now() - startAt < COLLECT_MAX_MS) {
101
+ await sleep(500);
102
+ if (currentKey() !== key) { lastChange = Date.now(); continue; } // 切走:暂停收集
103
+ texts = userTexts();
104
+ if (texts.length !== lastCount) {
105
+ lastCount = texts.length;
106
+ lastChange = Date.now();
107
+ }
108
+ if (assistantSeen) break;
109
+ if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
110
+ }
111
+ } finally { convObserver.disconnect(); }
112
+ if (!texts.length) return;
113
+
114
+ const res = await rpc('/title-gen/run', { method: 'POST', body: { sessId, text: texts.join('\n') } }).catch(() => null);
115
+ // written=false 常见于用户已抢先手动重命名——尊重手动命名,不提示
116
+ if (res && res.ok && res.written && res.title) {
117
+ setRowTitle(key, res.title);
118
+ void refreshPinnedTitles();
119
+ }
120
+ }
121
+
122
+ function scanForNewRows() {
123
+ for (const el of document.querySelectorAll('[data-task-item-key]')) {
124
+ const key = el.getAttribute('data-task-item-key') || '';
125
+ if (!key || knownKeys.has(key)) continue;
126
+ knownKeys.add(key);
127
+ void onNewTaskRow(key);
128
+ }
129
+ }
130
+
131
+ // 只有任务条目相关变动才重扫(选法同 title-lock.js);聊天区流式输出等
132
+ // 高频变动在此滤掉
133
+ const TASK_SEL = '[data-task-item-key]';
134
+ const inTaskRow = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL));
135
+ function taskRowMutations(muts) {
136
+ for (const m of muts) {
137
+ if (m.type === 'attributes') {
138
+ if (inTaskRow(m.target)) return true;
139
+ } else if (m.type === 'characterData') {
140
+ if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
141
+ } else if (inTaskRow(m.target)) {
142
+ return true;
143
+ } else {
144
+ for (const n of m.addedNodes) {
145
+ if (n instanceof Element && (n.matches(TASK_SEL) || !!n.querySelector(TASK_SEL))) return true;
146
+ }
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+
152
+ let scanDebounce = 0;
153
+ function scheduleScan() {
154
+ if (scanDebounce) return;
155
+ scanDebounce = setTimeout(() => { scanDebounce = 0; scanForNewRows(); }, SCAN_DEBOUNCE_MS);
156
+ }
157
+
158
+ export function startTitleGen() {
159
+ if (installed || typeof document === 'undefined') return;
160
+ installed = true;
161
+ // 启动先记下已在侧栏的任务键:此后出现的才是新会话
162
+ scanForNewRows();
163
+
164
+ new MutationObserver((muts) => { if (taskRowMutations(muts)) scheduleScan(); })
165
+ .observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'], characterData: true });
166
+
167
+ setInterval(() => { if (!document.hidden) scanForNewRows(); }, SCAN_FALLBACK_MS);
168
+ document.addEventListener('visibilitychange', () => { if (!document.hidden) scanForNewRows(); });
169
+ }
@@ -202,6 +202,20 @@ async function bootstrap() {
202
202
  if (map) scan();
203
203
  }
204
204
 
205
+ // 供智能起名(title-gen.js)复用:把指定会话键的所有侧栏行标题改为 text
206
+ // (同一会话可能多行呈现,与失配回写共用同一套元素定位与写法)
207
+ export function setRowTitle(key, text) {
208
+ for (const item of findRows(key)) {
209
+ for (const el of titleElsOf(item)) writeTitle(el, text);
210
+ }
211
+ }
212
+
213
+ // 供智能起名在写库成功后立即刷新锁定表(拉最新标题表并全量复核一次),
214
+ // 之后官方推送的自动标题由本功能按新表钉回
215
+ export function refreshPinnedTitles() {
216
+ return fetchPinned().then((m) => { if (m) scan(); });
217
+ }
218
+
205
219
  export function startTitleLock() {
206
220
  if (installed || typeof document === 'undefined') return;
207
221
  installed = true;
@@ -103,6 +103,9 @@ function rebuild() {
103
103
  menu.classList.add(HIDE_CLASS);
104
104
  origs = next;
105
105
  rowEl?.remove();
106
+ // 原生顶栏新建按钮(文件子页会展开的那个)钉死收起:新建入口统一由我们
107
+ // 的行提供,省去随子页进出显隐自己按钮的同步
108
+ navRow.classList.add('zcodepro-tb-hidden-native');
106
109
  const L = t();
107
110
  const mk = (kind, orig, nameSel, fallback) => h('button', {
108
111
  type: 'button',
@@ -129,7 +132,15 @@ function rebuild() {
129
132
  const pIcon = iconOf(next.plugin);
130
133
  if (pIcon) pluginBtn.append(pIcon);
131
134
  rowEl = h('div', { class: 'zcodepro-tb-row' }, newBtn, searchBtn, autoBtn, pluginBtn);
132
- navRow.append(rowEl);
135
+ // 插在容器内“更新”按钮之前(实测容器子序:侧栏切换/后退/前进/原生新建
136
+ // 容器/更新按钮)。更新按钮无 testid,按绿色胶囊的 bg-success 样式类定位
137
+ //(实测当前主题;老主题为 bg-emerald,一并匹配),找不到退回末尾追加——
138
+ // 更新按钮只在有新版本时出现。容器内追加/前插都是框架长期容忍的位置,
139
+ // 不要插到容器外侧(React 重渲染会把外侧插入点挤漂移)
140
+ const updBtn = [...navRow.querySelectorAll('button')]
141
+ .find((b) => /bg-(success|emerald)/.test(b.className || ''));
142
+ if (updBtn) updBtn.before(rowEl);
143
+ else navRow.append(rowEl);
133
144
  return true;
134
145
  }
135
146
 
@@ -171,14 +182,32 @@ function sync() {
171
182
  if (sidebarCollapsed() || sidebarTooNarrow()) {
172
183
  if (rowEl) rowEl.style.display = 'none';
173
184
  menu.classList.remove(HIDE_CLASS);
185
+ // 这两种形态藏起我们的整行:原生新建按钮要放出来(去掉钉死类),
186
+ // 否则收起侧栏时顶栏没有任何新建入口
187
+ document.querySelectorAll('.zcodepro-tb-hidden-native').forEach((el) => el.classList.remove('zcodepro-tb-hidden-native'));
174
188
  return;
175
189
  }
190
+ // 旧引用是否全部仍可用;不可用时先试重建,重建未就绪(React 分批挂载,
191
+ // 菜单/导航容器还没上屏)则保留现有行原样等待重试——若在这里提前把旧行
192
+ // 拆掉,重试成功前图标会整行消失,只能等兜底轮询补上(表现为返回会话后
193
+ // 图标迟两三秒才回来)
176
194
  const alive = origs && origs.menu === menu
177
195
  && origs.newTask.isConnected && origs.search.isConnected
178
196
  && origs.auto.isConnected && origs.plugin.isConnected
179
197
  && rowEl && rowEl.isConnected;
180
- if (!alive && !rebuild()) return;
181
- // 界面重渲染可能整体重写容器的 class,隐藏类兜底重加
198
+ if (!alive && !rebuild()) {
199
+ // 菜单在而原节点断连:界面正在换节点,行先藏着等重试。观察器监听菜单
200
+ // 挂载、命中即恢复(150ms 去抖);万一变动被滤掉,这里再排短程重试
201
+ // 兜底,不依赖 5 秒轮询
202
+ if (rowEl) rowEl.style.display = 'none';
203
+ setTimeout(scheduleSync, 400);
204
+ setTimeout(scheduleSync, 1200);
205
+ return;
206
+ }
207
+ // 界面重渲染可能整体重写容器的 class,隐藏类兜底重加;钉死原生新建按钮的
208
+ // 类同理(收起分支会摘掉它,展开回来若节点仍活着不会触发 rebuild,须在此补)
209
+ const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
210
+ if (navRow) navRow.classList.add('zcodepro-tb-hidden-native');
182
211
  menu.classList.add(HIDE_CLASS);
183
212
  if (rowEl) rowEl.style.display = '';
184
213
  syncActive();
@@ -219,7 +248,12 @@ function relevantMutations(muts) {
219
248
  function teardown() {
220
249
  rowEl?.remove();
221
250
  rowEl = null;
222
- if (origs) { origs.menu.classList.remove(HIDE_CLASS); origs = null; }
251
+ if (origs) {
252
+ origs.menu.classList.remove(HIDE_CLASS);
253
+ // 恢复原生子页新建按钮(功能关闭时不再钉死)
254
+ document.querySelectorAll('.zcodepro-tb-hidden-native').forEach((el) => el.classList.remove('zcodepro-tb-hidden-native'));
255
+ origs = null;
256
+ }
223
257
  }
224
258
 
225
259
  async function refreshConfig() {
@@ -10,6 +10,7 @@ import { startAliasWatcher } from './features/alias.js';
10
10
  import { startTaskOrderWatcher } from './features/task-order.js';
11
11
  import { startSessionSwitch } from './features/session-switch.js';
12
12
  import { startTitleLock } from './features/title-lock.js';
13
+ import { startTitleGen } from './features/title-gen.js';
13
14
  import { startPinnedExpandSuppression } from './features/pinned-expand.js';
14
15
  import { startWsRunningSpin } from './features/ws-running.js';
15
16
  import { startIdleReclaim } from './features/idle-reclaim.js';
@@ -50,6 +51,7 @@ import { ensureStyle } from './ui.js';
50
51
  try { startTaskOrderWatcher(); } catch { /* ignore */ }
51
52
  try { startSessionSwitch(); } catch { /* ignore */ }
52
53
  try { startTitleLock(); } catch { /* ignore */ }
54
+ try { startTitleGen(); } catch { /* ignore */ }
53
55
  try { startImageMenu(); } catch { /* ignore */ }
54
56
  try { startPinnedExpandSuppression(); } catch { /* ignore */ }
55
57
  try { startWsRunningSpin(); } catch { /* ignore */ }
@@ -0,0 +1,71 @@
1
+ // 供应商 / 模型 / 接口地址 / API Key 的共用选择逻辑:智能起名与视觉代理两个
2
+ // 设置面板共用,后续其他面板直接复用这里的组件与规则。供应商取值约定:
3
+ // - '':不跟随(自填接口地址 + API Key + 协议直连);
4
+ // - 'session':跟随当前会话(连接由会话侧解析,不存地址密钥);
5
+ // - 其余:供应商 id;已删的 id 保留为「已删除」选项,调用方靠保存的地址/
6
+ // 密钥副本继续直连。
7
+ import { h } from './core.js';
8
+
9
+ export const PROVIDER_FIELD_CLS = 'h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40';
10
+
11
+ // 「下拉 + 手输」组合(供应商与模型选择共用):下拉选择列表项,也可以直接
12
+ // 输入;当前值不在列表里(条目被删或手输 id)时保留为带 staleLabel 标注的
13
+ // 选项,不清空、不跳值;当前值为空时插入一个 emptyLabel 空选项,避免下拉框
14
+ // 假装选中第一项而配置里其实没选
15
+ export function comboField(current, options, onChange, { disabled = false, placeholder = '', staleLabel = '(手动输入)', emptyLabel = '' } = {}) {
16
+ const sel = h('select', { class: PROVIDER_FIELD_CLS, disabled: disabled || undefined });
17
+ const input = h('input', { type: 'text', value: current, placeholder, class: PROVIDER_FIELD_CLS, disabled: disabled || undefined });
18
+ const sync = () => {
19
+ const cur = input.value;
20
+ const opts = [...options];
21
+ if (cur && !opts.some((o) => o.value === cur)) opts.push({ value: cur, label: `${cur}${staleLabel}` });
22
+ if (!cur && !opts.some((o) => o.value === '')) opts.unshift({ value: '', label: emptyLabel });
23
+ sel.replaceChildren(...opts.map((o) => h('option', { value: o.value, title: o.value }, o.label)));
24
+ sel.value = cur;
25
+ };
26
+ sel.addEventListener('change', () => { input.value = sel.value; onChange(sel.value, 'select'); });
27
+ input.addEventListener('input', () => onChange(input.value, 'type'));
28
+ input.addEventListener('blur', () => sync());
29
+ sync();
30
+ return h('div', { class: 'flex flex-col gap-1' }, sel, input);
31
+ }
32
+
33
+ // 供应商下拉选项:不跟随(自填地址)/ 跟随当前会话 / 供应商列表
34
+ export function providerOptionsOf(providers, L) {
35
+ return [
36
+ { value: '', label: L.titleGenProviderNone },
37
+ { value: 'session', label: L.titleGenProviderSession },
38
+ ...providers.map((p) => ({ value: p.id, label: p.name })),
39
+ ];
40
+ }
41
+
42
+ // 模型下拉选项:具体供应商 = 该供应商的模型;跟随会话 = 汇总全部供应商的
43
+ // 模型(去重,方便快速选一个);不跟随 / 已删供应商 = 空列表(纯手输)。
44
+ // followSession = true 时,跟随会话的选项表头部多一个“跟随当前会话当前模型”
45
+ //(值为空):任务名提示词与视觉代理(zcode-vision ≥1.6.1)都在运行时把它
46
+ // 解析为会话当前模型;不需要该能力的场景不传即可
47
+ export function modelOptionsOf(providerValue, providers, { followSession = false, followSessionLabel = '' } = {}) {
48
+ const v = (providerValue || '').trim();
49
+ const cur = providers.find((p) => p.id === v);
50
+ if (cur) return cur.models.map((m) => ({ value: m, label: m }));
51
+ if (v === 'session') {
52
+ const head = followSession && followSessionLabel ? [{ value: '', label: followSessionLabel }] : [];
53
+ return [...head, ...[...new Set(providers.flatMap((p) => p.models))].map((m) => ({ value: m, label: m }))];
54
+ }
55
+ return [];
56
+ }
57
+
58
+ // 模型输入框的提示文案(按供应商模式选)
59
+ export function modelPlaceholderOf(providerValue, L) {
60
+ const v = (providerValue || '').trim();
61
+ if (v === 'session') return L.titleGenModelSessionHint;
62
+ if (v === '') return L.titleGenModelManualHint;
63
+ return L.titleGenModelHint;
64
+ }
65
+
66
+ // 是否手动连接(不跟随,或供应商 id 已不在列表里):调用方据此决定接口
67
+ // 地址 / API Key / 协议字段是否显示
68
+ export function isManualConn(providerValue, providers) {
69
+ const v = (providerValue || '').trim();
70
+ return v === '' || (v !== 'session' && !providers.some((p) => p.id === v));
71
+ }
package/src/inject/ui.js CHANGED
@@ -38,7 +38,7 @@ const contentClass = 'w-full sm:max-w-md rounded-2xl border-none bg-popover/98 p
38
38
  // Esc 只关最上层,避免一次 Esc 把两层一起关掉
39
39
  let dialogStack = [];
40
40
 
41
- export function openDialog({ title, description, onMount, onClose, width = 'sm:max-w-md', overlay = 'dim', draggable = false, posKey = '', dismissOnOutside = true }) {
41
+ export function openDialog({ title, description, onMount, onClose, width = 'sm:max-w-md', overlay = 'dim', draggable = false, posKey = '', dismissOnOutside = true, bodyClass = '' }) {
42
42
  const L = t();
43
43
  const prevActive = document.activeElement;
44
44
  let cleanupDrag = null;
@@ -77,7 +77,9 @@ export function openDialog({ title, description, onMount, onClose, width = 'sm:m
77
77
  // 注意不能把 null 直接传给 append:DOM 会把 null 渲染成字面量 "null" 文本
78
78
  ...(description ? [h('p', { class: 'mt-2 text-ui-sm/relaxed text-foreground-subtle' }, description)] : [])
79
79
  );
80
- const body = h('div', { class: 'mt-4 zcodepro-dialog-body' });
80
+ // bodyClass 覆盖默认的 mt-4(标题与内容间距):设置弹窗标题下还有状态行和
81
+ // 标签栏,需要更紧凑时传更小的间距类
82
+ const body = h('div', { class: 'zcodepro-dialog-body ' + (bodyClass || 'mt-4') });
81
83
  content.append(body);
82
84
  let restorePos = null;
83
85
  if (draggable) {
@@ -329,14 +331,16 @@ export function settingRow(name, desc, checked, onToggle, extra) {
329
331
  }, knob);
330
332
  const extraEl = extra
331
333
  ? h('div', {
332
- class: 'flex shrink-0 items-center pt-0.5',
334
+ class: 'flex shrink-0 items-center',
333
335
  onClick: (e) => { e.stopPropagation(); },
334
336
  onMousedown: (e) => { e.stopPropagation(); },
335
337
  onWheel: (e) => { e.stopPropagation(); },
336
338
  }, extra)
337
339
  : null;
340
+ // items-center:简介换行成多行时,开关与整个“标题+简介”块垂直居中
341
+ //(顶对齐会把开关吊在第一行的高度上)
338
342
  const row = h('div', {
339
- class: `flex cursor-pointer items-start gap-2 rounded-lg ${desc ? 'p-2' : 'p-1.5'} transition-colors hover:bg-surface-hover`,
343
+ class: `flex cursor-pointer items-center gap-2 rounded-lg ${desc ? 'p-2' : 'p-1.5'} transition-colors hover:bg-surface-hover`,
340
344
  onClick: () => { onToggle(); },
341
345
  },
342
346
  h('div', { class: 'min-w-0 flex-1' },
@@ -420,13 +424,14 @@ export function ensureStyle() {
420
424
  align-items: center;
421
425
  justify-content: center;
422
426
  height: 24px;
423
- padding: 0 14px;
427
+ padding: 0 8px;
424
428
  border: none;
425
429
  border-radius: 9999px;
426
430
  background: transparent;
427
431
  cursor: pointer;
428
432
  font-size: 12px;
429
433
  line-height: 1;
434
+ white-space: nowrap;
430
435
  color: var(--color-muted-foreground, #888);
431
436
  transition: color 0.15s ease, background-color 0.15s ease;
432
437
  }
@@ -515,6 +520,8 @@ export function ensureStyle() {
515
520
  .zcodepro-textarea:disabled { opacity: 0.5; }
516
521
  /* 小型文本域(视觉代理提示词等):11rem 最小高度只属于全局提示词大编辑框,这里按 rows 定高 */
517
522
  .zcodepro-textarea.zcodepro-textarea-sm { min-height: 0; }
523
+ /* 全局提示词大编辑框:默认/最大高度都比默认文本域更高 */
524
+ .zcodepro-textarea.zcodepro-textarea-lg { min-height: 16rem; max-height: 40rem; }
518
525
  /* 行高滑杆(样式调整标签页) */
519
526
  .zcodepro-range {
520
527
  -webkit-appearance: none;
@@ -700,6 +707,14 @@ export function ensureStyle() {
700
707
  }
701
708
  /* 原侧栏菜单整体隐藏(toolbar-icons 开启时) */
702
709
  .zcodepro-tb-hidden { display: none !important; }
710
+ /* 原生顶栏新建任务按钮(文件查看等子页展开的那个):常驻 DOM、靠动画容器
711
+ 在 w-7/w-0 间切换显隐。功能开启期间整体钉死收起态——新建入口统一由我们
712
+ 的按钮行提供,也就不再需要随子页进出动态显隐自己的新建按钮 */
713
+ .zcodepro-tb-hidden-native div[class*="transition-[opacity,width]"] {
714
+ width: 0 !important;
715
+ opacity: 0 !important;
716
+ pointer-events: none !important;
717
+ }
703
718
  /* 已置顶分区可折叠(pinned-collapse.js):标题整行可点,折叠时藏起任务列表,
704
719
  chevron 旋向与“项目”分区一致(收起指向右侧) */
705
720
  .zcodepro-pin-head {