dsh-lcx-codex 0.3.4 → 0.4.0-rc.13

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/lib/client.js CHANGED
@@ -5,244 +5,167 @@ window.__ModuleLoader__.load({
5
5
  const exports = module.exports
6
6
  const React = require('react')
7
7
  const { createSnapshotStore } = require('@deepseek-ai/dsh-client-runtime/client')
8
-
9
8
  const NAMESPACE = 'lcx-codex'
10
9
  const DEFAULTS = {
11
10
  enabled: false,
12
11
  webSearch: false,
12
+ advancedHostedSearch: false,
13
13
  alphaSearch: false,
14
14
  remoteCompaction: false,
15
15
  fallbackToBasicCompaction: true,
16
+ autoCompaction: true,
17
+ webSearchTimeoutSeconds: 240,
18
+ autoCompactionThresholdPercent: 90,
19
+ emergencyPruneThresholdPercent: 95,
16
20
  provider: 'lcx',
17
21
  baseURL: 'https://api.lcxbot.com/v1',
18
22
  apiKeyEnv: 'LCX_API_KEY',
19
23
  model: 'gpt-5.6-sol',
20
- compactTransport: 'native-v2',
21
24
  }
22
-
23
- const css = `
24
- .lcx-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none}
25
- .lcx-card-head{width:100%;display:flex;align-items:center;gap:12px;padding:14px 16px;border:0;background:transparent;color:inherit;text-align:left;font:inherit;cursor:pointer;border-radius:12px}
26
- .lcx-card-head:hover{background:var(--dsw-alias-interactive-bg-hover)}
27
- .lcx-card-title{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}
28
- .lcx-card-title strong{font-size:15px}.lcx-card-title span,.lcx-help{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
29
- .lcx-card-body{border-top:1px solid var(--dsw-alias-border-l2);padding:14px 16px 10px}
30
- .lcx-row{display:flex;align-items:flex-start;gap:9px;padding:9px 0}.lcx-row input[type=checkbox]{margin-top:3px;accent-color:var(--dsw-alias-brand-primary)}
31
- .lcx-row label{font-size:13px;color:var(--dsw-alias-label-primary);line-height:19px}.lcx-row small{display:block;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
32
- .lcx-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px}.lcx-field{display:flex;flex-direction:column;gap:5px}.lcx-field label{font-size:12px;color:var(--dsw-alias-label-secondary)}
33
- .lcx-input{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:7px 9px;font:inherit;font-size:12px}.lcx-input:disabled{color:var(--dsw-alias-label-tertiary)}
34
- .lcx-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0 4px}.lcx-footer button{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 14px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;cursor:pointer}.lcx-footer button.primary{background:var(--dsw-alias-state-business-primary);border-color:transparent;color:var(--dsw-alias-label-primary-inverted)}.lcx-footer button:disabled{opacity:.5;cursor:default}.lcx-error{flex:1;color:var(--dsw-alias-state-error-primary);font-size:12px}
35
- @media(max-width:640px){.lcx-fields{grid-template-columns:1fr}}
36
- `
37
- if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-lcx-codex"]') === null) {
25
+ const css = `.lcx-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;list-style:none}.lcx-head{width:100%;display:flex;justify-content:space-between;padding:14px 16px;border:0;background:transparent;color:inherit}.lcx-body{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 16px}.lcx-row{display:flex;gap:9px;padding:8px 0}.lcx-row small,.lcx-help,.lcx-field small{display:block;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.lcx-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px}.lcx-field{display:flex;flex-direction:column;gap:4px}.lcx-input{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px;background:transparent;color:inherit}.lcx-foot{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.lcx-foot button{padding:6px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:inherit}`
26
+ if (typeof document !== 'undefined' && !document.querySelector('style[data-plugin-css="dsh-lcx-codex"]')) {
38
27
  const tag = document.createElement('style')
39
- tag.dataset.plugin = 'dsh-lcx-codex'
40
28
  tag.dataset.pluginCss = 'dsh-lcx-codex'
41
29
  tag.textContent = css
42
30
  document.head.appendChild(tag)
43
31
  }
44
-
45
32
  const copy = {
46
33
  zh: {
47
34
  title: 'Responses / Codex 能力',
48
- description: '可选的 hosted Web Search 与 Responses 原生远程压缩。',
49
- enabled: '启用 LCX/Codex 能力',
50
- enabledHelp: '关闭时不改变当前模型、搜索提供方或压缩流程。',
51
- webSearch: '启用 Responses Hosted 搜索(websearch_gpt)',
52
- webSearchHelp: '开启后由本插件接管 DSH 搜索 provider,任意主模型都可通过已配置的 GPT Responses 路由调用 /responses + web_search;关闭或卸载后恢复原 provider。',
53
- alphaSearch: '启用 Alpha command 搜索(websearch_alpha)',
54
- alphaSearchHelp: '仅当配置的 GPT endpoint、provider、model 和 schema 的 capability probe 已通过时注册;任意主模型可调用,PAT 模拟或未知能力不会暴露该工具。',
55
- compaction: '启用 GPT 专属原生远程压缩',
56
- compactionHelp: '只适用于兼容 Responses 的 GPT 路由;使用与 Sub2API 压缩测试一致的 native V2(/responses + compaction_trigger),保存完整 output 并在后续请求中回放。Legacy compact transport 已不支持,相关配置会被拒绝。',
57
- fallback: 'native 压缩失败时回退 DSH 普通压缩',
58
- fallbackHelp: '推荐开启:首次远程压缩失败且尚未生成 checkpoint 时,继续使用 DSH 原有压缩。已有 native checkpoint 的回放失败仍会明确报错。',
59
- endpoint: '旧版回退 Responses 地址',
60
- model: '默认 Hosted / Alpha 模型',
61
- save: '保存',
62
- saving: '保存中…',
63
- discard: '放弃修改',
64
- unsaved: '未保存',
65
- readOnly: '当前部署的设置为只读。',
66
- unavailable: '此插件的设置服务暂不可用。',
67
- saveFailed: '保存失败,请重试。',
68
- hint: '请求优先复用当前 DSH openai-responses 模型的地址与凭据;这里的地址仅用于旧版直连兼容。Hosted 与 Alpha 不会互相静默降级。',
35
+ desc: '复用现有 GPT Responses 路由;普通搜索走 DSH web_search,Native V2 压缩采用 90% 主动压缩 + 95% 紧急保护。',
36
+ enabled: '启用插件',
37
+ web: '使用 GPT Hosted Search 作为 DSH web_search 后端',
38
+ webHelp: '不会新增第二个普通搜索工具;普通 web_search 自动跟随当前 Agent 的 GPT Responses 模型。',
39
+ searchTimeout: 'web_search 超时(秒)',
40
+ searchTimeoutHelp: '只调整 DSH 工具外层超时,不改变模型可见 schema。默认 240 秒;Hosted Search 较慢时避免 60 秒提前中断。',
41
+ advanced: '启用高级 Hosted 工具(websearch_gpt_advanced)',
42
+ advancedHelp: '只在需要域名过滤、位置、search context、图片等原生 Hosted 参数时使用;默认关闭以保持工具 schema 稳定。',
43
+ alpha: '启用 Alpha command(websearch_alpha)',
44
+ alphaHelp: '仅 capability probe 对当前 endpoint/provider/model/schema 验证通过后才真正注册。',
45
+ compact: '启用 Native V2 远程压缩',
46
+ compactHelp: 'DSH 仍负责范围、事务和 /compact;Native state 保存在 DSH session log。',
47
+ autoCompact: '启用 Native-first 自动压缩',
48
+ autoCompactHelp: '到主动阈值前不让 DSH 的 80% tool-result prune 改写历史;到主动阈值后优先 Native V2。',
49
+ autoThreshold: 'Native 自动压缩阈值(%)',
50
+ autoThresholdHelp: '建议 90%。允许 85–95%;262k 窗口在 90% 时仍约剩 26k tokens。',
51
+ emergencyThreshold: 'DSH 紧急 prune 阈值(%)',
52
+ emergencyThresholdHelp: '建议 95%。达到这里才允许 DSH tool-result-pruner 先救场;必须高于 Native 阈值。',
53
+ fallback: 'Native 失败后回退 DSH basic compaction',
54
+ fallbackHelp: 'Remote-first:只有 Native 请求失败才调用 basic summary,不并行双跑。',
55
+ endpoint: '回退 Responses 地址',
56
+ model: '回退 GPT 模型',
57
+ save: '保存', discard: '放弃修改', saving: '保存中…',
69
58
  },
70
59
  en: {
71
60
  title: 'Responses / Codex capabilities',
72
- description: 'Optional hosted Web Search and native Responses compaction.',
73
- enabled: 'Enable LCX/Codex capabilities',
74
- enabledHelp: 'When off, the current model, search provider, and compaction flow are unchanged.',
75
- webSearch: 'Enable Responses Hosted search (websearch_gpt)',
76
- webSearchHelp: 'When on, this plugin takes over the DSH search provider and any primary model can use the configured GPT Responses route. Disabling or removing it restores the previous provider.',
77
- alphaSearch: 'Enable Alpha command search (websearch_alpha)',
78
- alphaSearchHelp: 'Registered only after a capability probe matches the configured GPT endpoint, provider, model, and schema. Any primary model can call it; PAT emulation and unknown capabilities remain hidden.',
79
- compaction: 'Enable GPT-only native remote compaction',
80
- compactionHelp: 'Works only on a compatible Responses GPT route; uses the native V2 request used by Sub2API compact tests (/responses + compaction_trigger), stores the complete output, and replays it. Legacy compact transport is unsupported and rejected.',
81
- fallback: 'Fall back to normal DSH compaction on native failure',
82
- fallbackHelp: 'Recommended: if the first native request fails before a checkpoint is saved, use the existing DSH compaction. Replay failures for an existing native checkpoint still report an error.',
83
- endpoint: 'Legacy fallback Responses endpoint',
84
- model: 'Default Hosted / Alpha model',
85
- save: 'Save',
86
- saving: 'Saving…',
87
- discard: 'Discard',
88
- unsaved: 'Unsaved',
89
- readOnly: 'This deployment stores settings read-only.',
90
- unavailable: 'This plugin settings service is unavailable.',
91
- saveFailed: 'Save failed; please try again.',
92
- hint: 'Requests prefer the active DSH openai-responses model route and credential; this endpoint is only a legacy direct-route fallback. Hosted and Alpha never silently fall back to each other.',
61
+ desc: 'Reuse existing GPT Responses routes; Native V2 uses a 90% proactive threshold with 95% emergency protection.',
62
+ enabled: 'Enable plugin',
63
+ web: 'Use GPT Hosted Search as DSH web_search backend',
64
+ webHelp: 'Keeps DSH web_search as the single ordinary search tool and follows the active Agent GPT Responses model.',
65
+ searchTimeout: 'web_search timeout (seconds)',
66
+ searchTimeoutHelp: 'Changes only the DSH tool deadline, not the model-visible schema. Default 240s to avoid premature 60s timeouts.',
67
+ advanced: 'Enable advanced Hosted tool (websearch_gpt_advanced)',
68
+ advancedHelp: 'Only for native Hosted controls such as domains, location, context size and image search; off by default for stable tool schemas.',
69
+ alpha: 'Enable Alpha command (websearch_alpha)',
70
+ alphaHelp: 'Registered only after a matching capability probe.',
71
+ compact: 'Enable Native V2 remote compaction',
72
+ compactHelp: 'DSH still owns range/transactions and /compact; native state is persisted in the DSH session log.',
73
+ autoCompact: 'Enable Native-first automatic compaction',
74
+ autoCompactHelp: 'Suppresses the stock 80% tool-result prune before the proactive threshold, then prefers Native V2.',
75
+ autoThreshold: 'Native auto-compaction threshold (%)',
76
+ autoThresholdHelp: 'Default 90%. Allowed 85–95%.',
77
+ emergencyThreshold: 'DSH emergency prune threshold (%)',
78
+ emergencyThresholdHelp: 'Default 95%. DSH tool-result pruning is allowed only in this emergency zone; must exceed the Native threshold.',
79
+ fallback: 'Fall back to DSH basic compaction on Native failure',
80
+ fallbackHelp: 'Remote-first: basic summary runs only after Native failure, never in parallel.',
81
+ endpoint: 'Fallback Responses endpoint', model: 'Fallback GPT model',
82
+ save: 'Save', discard: 'Discard', saving: 'Saving…',
93
83
  },
94
84
  }
95
-
96
- function language() {
97
- return String(globalThis.navigator?.language ?? '').toLowerCase().startsWith('zh') ? 'zh' : 'en'
98
- }
99
-
100
- function valueFrom(snapshot) {
101
- return { ...DEFAULTS, ...(snapshot.value ?? {}) }
102
- }
103
-
104
- class SettingsController {
85
+ const lang = () => String(globalThis.navigator?.language ?? '').toLowerCase().startsWith('zh') ? 'zh' : 'en'
86
+ function valueFrom(snapshot) { return { ...DEFAULTS, ...(snapshot.value ?? {}) } }
87
+ class Controller {
105
88
  constructor(scope) {
106
- this.scope = scope
107
- this.draft = null
108
- this.dirty = false
109
- this.saving = false
110
- this.failed = false
111
- this.listeners = new Set()
89
+ this.scope = scope; this.draft = null; this.dirty = false; this.saving = false
112
90
  this.store = createSnapshotStore(this.projection())
113
- this.stop = scope.subscribe(() => {
114
- if (!this.dirty) this.draft = null
115
- this.publish()
116
- })
91
+ this.stop = scope.subscribe(() => { if (!this.dirty) this.draft = null; this.publish() })
117
92
  }
118
-
119
93
  snapshot() { return this.scope.getSnapshot() }
120
94
  value() { return valueFrom(this.snapshot()) }
121
95
  draftValue() { return { ...this.value(), ...(this.draft ?? {}) } }
122
96
  field(name) { return this.draftValue()[name] }
123
- overridden(name) { return Object.prototype.hasOwnProperty.call(this.snapshot().user ?? {}, name) }
124
97
  projection() {
125
- const snapshot = this.snapshot()
98
+ const s = this.snapshot()
99
+ const bools = ['enabled','webSearch','advancedHostedSearch','alphaSearch','remoteCompaction','fallbackToBasicCompaction','autoCompaction']
126
100
  return {
127
- available: snapshot.status === 'ready',
128
- writable: snapshot.writable,
129
- dirty: this.dirty,
130
- saving: this.saving,
131
- failed: this.failed,
132
- enabled: { value: Boolean(this.field('enabled')), overridden: this.overridden('enabled') },
133
- webSearch: { value: Boolean(this.field('webSearch')), overridden: this.overridden('webSearch') },
134
- alphaSearch: { value: Boolean(this.field('alphaSearch')), overridden: this.overridden('alphaSearch') },
135
- remoteCompaction: { value: Boolean(this.field('remoteCompaction')), overridden: this.overridden('remoteCompaction') },
136
- fallbackToBasicCompaction: { value: this.field('fallbackToBasicCompaction') !== false, overridden: this.overridden('fallbackToBasicCompaction') },
137
- baseURL: String(this.field('baseURL') ?? ''),
138
- model: String(this.field('model') ?? ''),
139
- hint: language() === 'zh' ? copy.zh.hint : copy.en.hint,
101
+ available: s.status === 'ready', writable: s.writable, dirty: this.dirty, saving: this.saving,
102
+ ...Object.fromEntries(bools.map((f) => [f, { value: f === 'fallbackToBasicCompaction' || f === 'autoCompaction' ? this.field(f) !== false : Boolean(this.field(f)) }])),
103
+ webSearchTimeoutSeconds: Number(this.field('webSearchTimeoutSeconds') ?? 240),
104
+ autoCompactionThresholdPercent: Number(this.field('autoCompactionThresholdPercent') ?? 90),
105
+ emergencyPruneThresholdPercent: Number(this.field('emergencyPruneThresholdPercent') ?? 95),
106
+ baseURL: String(this.field('baseURL') ?? ''), model: String(this.field('model') ?? ''),
140
107
  }
141
108
  }
142
- bind() { return this.store }
143
- subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener) }
144
- publish() {
145
- this.store.set(this.projection())
146
- for (const listener of this.listeners) listener()
147
- }
148
- edit(field, value) {
149
- this.draft = { ...(this.draft ?? this.value()), [field]: value }
150
- this.dirty = true
151
- this.failed = false
152
- this.publish()
153
- }
154
- discard() {
155
- this.draft = null
156
- this.dirty = false
157
- this.failed = false
158
- this.publish()
159
- }
109
+ publish() { this.store.set(this.projection()) }
110
+ edit(field, value) { this.draft = { ...(this.draft ?? this.value()), [field]: value }; this.dirty = true; this.publish() }
111
+ discard() { this.draft = null; this.dirty = false; this.publish() }
160
112
  async save() {
161
113
  if (!this.dirty || this.saving || !this.snapshot().writable) return
162
- const next = this.draftValue()
163
- const previous = this.value()
164
- const changes = Object.keys(next).filter((field) => next[field] !== previous[field])
165
- this.saving = true
166
- this.failed = false
167
- this.publish()
114
+ const next = this.draftValue(), prev = this.value(); this.saving = true; this.publish()
168
115
  try {
169
- for (const field of changes) await this.scope.set(field, next[field])
170
- this.draft = null
171
- this.dirty = false
172
- } catch {
173
- this.failed = true
174
- } finally {
175
- this.saving = false
176
- this.publish()
177
- }
178
- }
179
- inject() { return { hooks: { lcxCard: this.bind() }, ...this.actions() } }
180
- actions() {
181
- return {
182
- edit: (field, value) => this.edit(field, value),
183
- save: () => { void this.save() },
184
- discard: () => this.discard(),
185
- }
116
+ for (const f of Object.keys(next)) if (next[f] !== prev[f]) await this.scope.set(f, next[f])
117
+ this.draft = null; this.dirty = false
118
+ } finally { this.saving = false; this.publish() }
186
119
  }
120
+ inject() { return { hooks: { lcxCard: this.store }, edit: (f,v) => this.edit(f,v), save: () => void this.save(), discard: () => this.discard() } }
121
+ }
122
+ function Row({ id, label, help, checked, disabled, onChange }) {
123
+ return React.createElement('div', { className:'lcx-row' },
124
+ React.createElement('input', { id, type:'checkbox', checked, disabled, onChange:e=>onChange(e.target.checked) }),
125
+ React.createElement('label', { htmlFor:id }, label, React.createElement('small', null, help)))
187
126
  }
188
-
189
- function CheckRow({ id, label, help, checked, disabled, onChange }) {
190
- return React.createElement('div', { className: 'lcx-row' },
191
- React.createElement('input', { id, type: 'checkbox', checked, disabled, onChange: (event) => onChange(event.target.checked) }),
192
- React.createElement('label', { htmlFor: id }, label, React.createElement('small', null, help)),
193
- )
127
+ function Field({ label, help, value, disabled, type='text', min, max, step, onChange }) {
128
+ return React.createElement('div', { className:'lcx-field' },
129
+ React.createElement('label', null, label),
130
+ React.createElement('input', { className:'lcx-input', type, value, disabled, min, max, step, onChange }),
131
+ help ? React.createElement('small', null, help) : null)
194
132
  }
195
-
196
- function LcxCard(props) {
197
- const t = copy[language()]
198
- const state = props.useLcxCard((snapshot) => snapshot)
199
- const [open, setOpen] = React.useState(false)
200
- if (!state.available) return null
201
- const disabled = !state.writable || state.saving
202
- return React.createElement('li', { className: 'lcx-card' },
203
- React.createElement('button', { className: 'lcx-card-head', type: 'button', 'aria-expanded': open, onClick: () => setOpen(!open) },
204
- React.createElement('span', { className: 'lcx-card-title' }, React.createElement('strong', null, t.title), React.createElement('span', null, t.description)),
205
- state.dirty ? React.createElement('span', null, t.unsaved) : null,
206
- React.createElement('span', null, open ? '⌃' : '⌄'),
207
- ),
208
- open ? React.createElement('div', { className: 'lcx-card-body' },
209
- React.createElement(CheckRow, { id: 'lcx-enabled', label: t.enabled, help: t.enabledHelp, checked: state.enabled.value, disabled, onChange: (value) => props.edit('enabled', value) }),
210
- React.createElement(CheckRow, { id: 'lcx-web-search', label: t.webSearch, help: t.webSearchHelp, checked: state.webSearch.value, disabled: disabled || !state.enabled.value, onChange: (value) => props.edit('webSearch', value) }),
211
- React.createElement(CheckRow, { id: 'lcx-alpha-search', label: t.alphaSearch, help: t.alphaSearchHelp, checked: state.alphaSearch.value, disabled: disabled || !state.enabled.value, onChange: (value) => props.edit('alphaSearch', value) }),
212
- React.createElement(CheckRow, { id: 'lcx-compaction', label: t.compaction, help: t.compactionHelp, checked: state.remoteCompaction.value, disabled: disabled || !state.enabled.value, onChange: (value) => props.edit('remoteCompaction', value) }),
213
- React.createElement(CheckRow, { id: 'lcx-fallback', label: t.fallback, help: t.fallbackHelp, checked: state.fallbackToBasicCompaction.value, disabled: disabled || !state.enabled.value || !state.remoteCompaction.value, onChange: (value) => props.edit('fallbackToBasicCompaction', value) }),
214
- React.createElement('div', { className: 'lcx-fields' },
215
- React.createElement('div', { className: 'lcx-field' }, React.createElement('label', { htmlFor: 'lcx-endpoint' }, t.endpoint), React.createElement('input', { id: 'lcx-endpoint', className: 'lcx-input', value: state.baseURL, disabled, onChange: (event) => props.edit('baseURL', event.target.value) })),
216
- React.createElement('div', { className: 'lcx-field' }, React.createElement('label', { htmlFor: 'lcx-model' }, t.model), React.createElement('input', { id: 'lcx-model', className: 'lcx-input', value: state.model, disabled, onChange: (event) => props.edit('model', event.target.value) })),
217
- ),
218
- React.createElement('p', { className: 'lcx-help' }, state.hint),
219
- React.createElement('div', { className: 'lcx-footer' },
220
- state.failed ? React.createElement('span', { className: 'lcx-error' }, t.saveFailed) : null,
221
- React.createElement('button', { type: 'button', disabled: !state.dirty || disabled, onClick: props.discard }, t.discard),
222
- React.createElement('button', { className: 'primary', type: 'button', disabled: !state.dirty || disabled, onClick: props.save }, state.saving ? t.saving : t.save),
223
- ),
224
- ) : null,
225
- )
133
+ function Card(props) {
134
+ const t = copy[lang()], s = props.useLcxCard(x=>x), [open,setOpen] = React.useState(false)
135
+ if (!s.available) return null
136
+ const disabled = !s.writable || s.saving
137
+ const numeric = (field) => (e) => props.edit(field, Number(e.target.value))
138
+ return React.createElement('li', { className:'lcx-card' },
139
+ React.createElement('button', { className:'lcx-head', type:'button', onClick:()=>setOpen(!open) }, React.createElement('strong',null,t.title), React.createElement('span',null,open?'⌃':'⌄')),
140
+ open ? React.createElement('div', { className:'lcx-body' },
141
+ React.createElement('p', { className:'lcx-help' }, t.desc),
142
+ React.createElement(Row, { id:'lcx-enabled', label:t.enabled, checked:s.enabled.value, disabled, onChange:v=>props.edit('enabled',v) }),
143
+ React.createElement(Row, { id:'lcx-web', label:t.web, help:t.webHelp, checked:s.webSearch.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('webSearch',v) }),
144
+ React.createElement('div', { className:'lcx-fields' },
145
+ React.createElement(Field, { label:t.searchTimeout, help:t.searchTimeoutHelp, type:'number', min:30, max:600, step:30, value:s.webSearchTimeoutSeconds, disabled:disabled||!s.enabled.value||!s.webSearch.value, onChange:numeric('webSearchTimeoutSeconds') })),
146
+ React.createElement(Row, { id:'lcx-advanced', label:t.advanced, help:t.advancedHelp, checked:s.advancedHostedSearch.value, disabled:disabled||!s.enabled.value||!s.webSearch.value, onChange:v=>props.edit('advancedHostedSearch',v) }),
147
+ React.createElement(Row, { id:'lcx-alpha', label:t.alpha, help:t.alphaHelp, checked:s.alphaSearch.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('alphaSearch',v) }),
148
+ React.createElement(Row, { id:'lcx-compact', label:t.compact, help:t.compactHelp, checked:s.remoteCompaction.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('remoteCompaction',v) }),
149
+ React.createElement(Row, { id:'lcx-auto-compact', label:t.autoCompact, help:t.autoCompactHelp, checked:s.autoCompaction.value, disabled:disabled||!s.enabled.value||!s.remoteCompaction.value, onChange:v=>props.edit('autoCompaction',v) }),
150
+ React.createElement('div', { className:'lcx-fields' },
151
+ React.createElement(Field, { label:t.autoThreshold, help:t.autoThresholdHelp, type:'number', min:85, max:95, step:1, value:s.autoCompactionThresholdPercent, disabled:disabled||!s.enabled.value||!s.remoteCompaction.value||!s.autoCompaction.value, onChange:numeric('autoCompactionThresholdPercent') }),
152
+ React.createElement(Field, { label:t.emergencyThreshold, help:t.emergencyThresholdHelp, type:'number', min:90, max:99, step:1, value:s.emergencyPruneThresholdPercent, disabled:disabled||!s.enabled.value||!s.remoteCompaction.value||!s.autoCompaction.value, onChange:numeric('emergencyPruneThresholdPercent') })),
153
+ React.createElement(Row, { id:'lcx-fallback', label:t.fallback, help:t.fallbackHelp, checked:s.fallbackToBasicCompaction.value, disabled:disabled||!s.enabled.value||!s.remoteCompaction.value, onChange:v=>props.edit('fallbackToBasicCompaction',v) }),
154
+ React.createElement('div', { className:'lcx-fields' },
155
+ React.createElement(Field, { label:t.endpoint, value:s.baseURL, disabled, onChange:e=>props.edit('baseURL',e.target.value) }),
156
+ React.createElement(Field, { label:t.model, value:s.model, disabled, onChange:e=>props.edit('model',e.target.value) })),
157
+ React.createElement('div', { className:'lcx-foot' },
158
+ React.createElement('button', { disabled:!s.dirty||disabled, onClick:props.discard }, t.discard),
159
+ React.createElement('button', { disabled:!s.dirty||disabled, onClick:props.save }, s.saving?t.saving:t.save))) : null)
226
160
  }
227
-
228
- const inject = ['slots', 'locale', 'settingsScope']
229
-
161
+ const inject = ['slots','locale','settingsScope']
230
162
  function apply(ctx) {
231
- const slots = ctx.slots ?? ctx.get('slots')
232
- const scopeService = ctx.settingsScope ?? ctx.get('settingsScope')
233
- if (slots === undefined || scopeService === undefined) return
234
- const scope = scopeService.bind({ namespace: NAMESPACE })
235
- const controller = new SettingsController(scope)
236
- slots.inject('settings.plugin.item', () => slots.register({
237
- name: 'settings.plugin.item',
238
- key: NAMESPACE,
239
- inject: () => controller.inject(),
240
- }, LcxCard))
241
- ctx.effect(() => () => controller.stop(), 'lcx-codex: settings card')
163
+ const slots = ctx.slots ?? ctx.get('slots'), svc = ctx.settingsScope ?? ctx.get('settingsScope')
164
+ if (!slots || !svc) return
165
+ const controller = new Controller(svc.bind({ namespace:NAMESPACE }))
166
+ slots.inject('settings.plugin.item', () => slots.register({ name:'settings.plugin.item', key:NAMESPACE, inject:()=>controller.inject() }, Card))
167
+ ctx.effect(() => () => controller.stop(), 'lcx-codex settings card')
242
168
  }
243
-
244
- exports.apply = apply
245
- exports.inject = inject
246
- return module.exports
169
+ exports.apply = apply; exports.inject = inject; return module.exports
247
170
  },
248
171
  })