dsh-bundle-antigravity-host 0.1.27 → 0.1.29
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/README.md +5 -5
- package/dist/adapter/antigravity-adapter.d.ts +7 -1
- package/dist/adapter/antigravity-adapter.js +98 -29
- package/dist/adapter/antigravity-adapter.js.map +1 -1
- package/dist/client.js +182 -92
- package/dist/index.d.ts +7 -2
- package/dist/index.js +29 -12
- package/dist/index.js.map +1 -1
- package/dist/pool/pooled-adapter.d.ts +4 -2
- package/dist/pool/pooled-adapter.js +44 -8
- package/dist/pool/pooled-adapter.js.map +1 -1
- package/dist/quota/quota-remote.d.ts +4 -0
- package/dist/quota/quota-remote.js +22 -13
- package/dist/quota/quota-remote.js.map +1 -1
- package/dist/runtime/agy-cli.js +6 -3
- package/dist/runtime/agy-cli.js.map +1 -1
- package/dist/runtime/proxied-fetch.d.ts +3 -0
- package/dist/runtime/proxied-fetch.js +26 -0
- package/dist/runtime/proxied-fetch.js.map +1 -1
- package/dist/settings.d.ts +28 -20
- package/dist/settings.js +32 -10
- package/dist/settings.js.map +1 -1
- package/docs/changelog.md +14 -0
- package/docs/configuration.md +9 -3
- package/docs/development.md +18 -6
- package/docs/reference.md +5 -3
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -23,20 +23,20 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
const React = require('react')
|
|
24
24
|
const h = React.createElement
|
|
25
25
|
const PROVIDER = 'antigravity-acp'
|
|
26
|
-
/** Settings namespace
|
|
26
|
+
/** Settings namespace projected from Config's volatile fields in src/settings.ts. */
|
|
27
27
|
const ENTRY_ID = 'llm-antigravity-acp'
|
|
28
|
-
/**
|
|
28
|
+
/** Live fields owned by this card; ordinary fields stay in the host config editor. */
|
|
29
29
|
const FIELDS = [
|
|
30
|
-
{ key: 'autoWarmup', checkbox: true, defaultChecked: true, label: '启动时自动预热
|
|
31
|
-
{ key: 'reuseSessions', checkbox: true, defaultChecked: true, label: '复用 ACP 会话(增量发送)', hint: '校验历史与实际输出后只发新增消息;变化的上下文仍发送,不一致时自动重建。' },
|
|
32
|
-
{ key: '
|
|
33
|
-
{ key: '
|
|
34
|
-
{ key: '
|
|
35
|
-
{ key: '
|
|
36
|
-
{ key: '
|
|
37
|
-
{ key: '
|
|
38
|
-
{ key: '
|
|
39
|
-
{ key: '
|
|
30
|
+
{ key: 'autoWarmup', group: 'general', checkbox: true, defaultChecked: true, label: '启动时自动预热', hint: '提前启动并保留连接,不发送模型消息。保存后下次启用插件生效;本次可点击“立即预热”。' },
|
|
31
|
+
{ key: 'reuseSessions', group: 'general', checkbox: true, defaultChecked: true, label: '复用 ACP 会话(增量发送)', hint: '校验历史与实际输出后只发新增消息;变化的上下文仍发送,不一致时自动重建。' },
|
|
32
|
+
{ key: 'preferredModel', group: 'model', label: '默认 / 自定义模型 ID', placeholder: '选择目录中的模型,或输入自定义 ID', hint: '保存后优先列出此模型;目录之外的 ID 标为自定义、未验证。不会替换当前会话已选模型。留空不指定默认项。' },
|
|
33
|
+
{ key: 'proxyUrl', group: 'network', label: '出网代理', placeholder: 'http://127.0.0.1:7890', hint: '此处配置优先;留空则继承宿主代理环境变量,宿主也未配置时才直连。支持 HTTP、HTTPS 与 SOCKS5。' },
|
|
34
|
+
{ key: 'proxyBypass', group: 'network', label: '代理排除列表', placeholder: '*.internal,example.internal', hint: '逗号分隔,支持域名与通配后缀,不支持 CIDR。localhost/127.0.0.1/::1 始终自动排除(私有 MCP 桥在 loopback)。' },
|
|
35
|
+
{ key: 'agyCliPath', group: 'network', label: '官方 CLI 路径(可选)', placeholder: '留空自动查找 agy', hint: '用于模型目录、套餐额度和 AI Credits。自定义安装位置可填写完整可执行文件路径;CLI 登录与 ACP 登录独立。' },
|
|
36
|
+
{ key: 'resumeSessions', group: 'advanced', checkbox: true, label: '重启后恢复 ACP 会话', hint: '尝试加载本地映射,失败则重建;仅保存 ID 与指纹。需要同时开启会话复用。' },
|
|
37
|
+
{ key: 'keepProcessAfterCancel', group: 'advanced', checkbox: true, label: '取消后保留已停止的进程', hint: '仅在 agent 确认停止后保留;等待期间新轮排队,超时或通信失败回收。' },
|
|
38
|
+
{ key: 'cancelGraceMs', group: 'advanced', numeric: true, defaultValue: 30000, min: 0, max: 60000, label: '取消确认宽限(毫秒)', hint: '默认 30000;0 表示不等待并立即回收,不会复用未确认连接。仅开启取消后保留进程时生效。' },
|
|
39
|
+
{ key: 'idleRecycleMs', group: 'advanced', numeric: true, defaultValue: 300000, min: 0, max: 3600000, label: '进程空闲回收(毫秒)', hint: '默认 300000(5 分钟),0 禁用。聊天、预热、目录发现结束后统一计时;等待工具不算空闲。' },
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
function unwrap(result) {
|
|
@@ -71,13 +71,40 @@ window.__ModuleLoader__.load({
|
|
|
71
71
|
const slow = ['spawn', 'initialize'].includes(runtime.phase) ? ';官方进程冷启动首次可能需要约 1 分钟,请耐心等待' : ''
|
|
72
72
|
return (RUNTIME_PHASES[runtime.phase] ?? runtime.phase) + '… 已用 ' + duration(runtime.phaseElapsedMs) + slow
|
|
73
73
|
}
|
|
74
|
+
function connectionStatus(account, registered) {
|
|
75
|
+
const runtime = account?.runtime
|
|
76
|
+
const ready = runtime?.phase === 'ready'
|
|
77
|
+
const busy = runtimeBusy(runtime) || ['connecting', 'cancelling'].includes(account?.state)
|
|
78
|
+
const error = account?.state === 'error' || runtime?.phase === 'error' || registered === false
|
|
79
|
+
const label = registered === false ? 'Provider 未注册'
|
|
80
|
+
: error ? '连接需要检查'
|
|
81
|
+
: busy ? account?.state === 'cancelling' ? '正在取消连接' : '正在准备连接'
|
|
82
|
+
: ready ? account?.state === 'connected' ? '账户已验证' : 'ACP 已预热'
|
|
83
|
+
: account?.state === 'cancelled' || runtime?.phase === 'stopped' ? '连接已停止'
|
|
84
|
+
: account?.state === 'connected' ? '账户已验证' : '尚未验证连接'
|
|
85
|
+
const description = account?.runtime?.phase === 'stopped'
|
|
86
|
+
? 'ACP 连接已停止,可以重新预热。'
|
|
87
|
+
: account?.runtime?.phase === 'error'
|
|
88
|
+
? account.runtime.error ?? account?.message ?? 'ACP 连接异常,请重试。'
|
|
89
|
+
: registered === false ? '请确认插件已启用,Host 与 Client 均已加载。'
|
|
90
|
+
: startupHint(account) ?? account?.message ?? '连接后可验证账户并读取模型目录,不会发送聊天消息。'
|
|
91
|
+
return { label, description, ready, busy, error, color: error ? '#d97757' : ready ? '#25b879' : busy ? '#d99b28' : '#8b909a' }
|
|
92
|
+
}
|
|
93
|
+
function StatusDot({ color }) {
|
|
94
|
+
return h('span', { 'aria-hidden': true, style: { width: '8px', height: '8px', flex: '0 0 auto', borderRadius: '50%', background: color, boxShadow: '0 0 0 4px rgba(128,128,128,.10)' } })
|
|
95
|
+
}
|
|
96
|
+
function StatusNotice({ status }) {
|
|
97
|
+
if (!status) return null
|
|
98
|
+
const error = status.kind === 'error'
|
|
99
|
+
return h('p', { role: error ? 'alert' : 'status', style: { margin: 0, padding: '10px 12px', borderRadius: '10px', fontSize: '12px', lineHeight: 1.6, overflowWrap: 'anywhere', color: error ? '#b42318' : status.kind === 'busy' ? '#946200' : '#177c55', background: error ? 'rgba(217,119,87,.10)' : status.kind === 'busy' ? 'rgba(217,155,40,.10)' : 'rgba(37,184,121,.10)' } }, status.text)
|
|
100
|
+
}
|
|
74
101
|
function RuntimeDetails({ runtime }) {
|
|
75
102
|
if (!runtime) return null
|
|
76
|
-
const row = (label, value) => h('div', { key: label, style: { display: 'flex', justifyContent: 'space-between', gap: '
|
|
77
|
-
h('span', { style: { opacity: .65 } }, label), h('span', { style: { fontVariantNumeric: 'tabular-nums', overflowWrap: 'anywhere' } }, value))
|
|
103
|
+
const row = (label, value) => h('div', { key: label, style: { display: 'flex', justifyContent: 'space-between', gap: '16px' } },
|
|
104
|
+
h('span', { style: { opacity: .65 } }, label), h('span', { style: { textAlign: 'right', fontVariantNumeric: 'tabular-nums', overflowWrap: 'anywhere' } }, value))
|
|
78
105
|
const round = runtime.round
|
|
79
106
|
const queue = runtime.queue
|
|
80
|
-
return h('div', { 'aria-label': 'ACP 生命周期与计时', style: {
|
|
107
|
+
return h('div', { 'aria-label': 'ACP 生命周期与计时', style: { display: 'grid', gap: '8px', fontSize: '12px', lineHeight: 1.5 } },
|
|
81
108
|
row('当前状态', (RUNTIME_PHASES[runtime.phase] ?? runtime.phase) + ' · ' + duration(runtime.phaseElapsedMs)),
|
|
82
109
|
queue ? row('会话调度', '运行 ' + queue.active + ' · 等待工具 ' + queue.parked + ' · 排队 ' + queue.waiting) : null,
|
|
83
110
|
queue ? row('并发连接', queue.workers + ' / ' + queue.capacity) : null,
|
|
@@ -90,12 +117,48 @@ window.__ModuleLoader__.load({
|
|
|
90
117
|
round?.firstChunkMs !== undefined ? row('发送 prompt → 首块', duration(round.firstChunkMs)) : null,
|
|
91
118
|
round?.firstToolMs !== undefined ? row('发送 prompt → 首个工具', duration(round.firstToolMs)) : null,
|
|
92
119
|
round?.continuations ? row('工具续轮', round.continuations + ' 次 · 最近 ' + duration(round.lastContinuationMs)) : null,
|
|
93
|
-
runtime.error ? h('p', { role: 'alert', style: { margin: '4px 0 0', color: '#
|
|
120
|
+
runtime.error ? h('p', { role: 'alert', style: { margin: '4px 0 0', color: '#b42318', overflowWrap: 'anywhere' } }, runtime.error) : null)
|
|
121
|
+
}
|
|
122
|
+
function SectionHeading({ eyebrow, title, description, action }) {
|
|
123
|
+
return h('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '12px', marginBottom: '16px' } },
|
|
124
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
125
|
+
eyebrow ? h('div', { style: { fontSize: '10px', fontWeight: 650, letterSpacing: '.08em', opacity: .5, marginBottom: '4px' } }, eyebrow) : null,
|
|
126
|
+
h('h3', { style: { fontSize: '14px', lineHeight: 1.4, fontWeight: 650, margin: 0 } }, title),
|
|
127
|
+
description ? h('p', { style: { fontSize: '12px', lineHeight: 1.6, opacity: .6, margin: '5px 0 0' } }, description) : null),
|
|
128
|
+
action ?? null)
|
|
129
|
+
}
|
|
130
|
+
function TextField({ field, value, disabled, invalid, onChange }) {
|
|
131
|
+
const inputId = 'agy-setting-' + (React.useId?.() ?? field.key) + '-' + field.key
|
|
132
|
+
return h('div', { style: { display: 'grid', gap: '6px' } },
|
|
133
|
+
h('label', { htmlFor: inputId, style: { fontSize: '12px', fontWeight: 550 } }, field.label),
|
|
134
|
+
h('input', {
|
|
135
|
+
id: inputId, name: field.key, type: field.numeric ? 'number' : 'text',
|
|
136
|
+
...(field.numeric ? { min: field.min, max: field.max, step: 1 } : {}),
|
|
137
|
+
disabled, value: value ?? '', placeholder: field.placeholder,
|
|
138
|
+
'aria-invalid': invalid || undefined, 'aria-describedby': inputId + '-hint',
|
|
139
|
+
style: { ...ACTION, cursor: disabled ? 'not-allowed' : 'text', width: '100%', boxSizing: 'border-box', background: 'rgba(128,128,128,.035)', opacity: disabled ? .6 : 1 },
|
|
140
|
+
onChange: (event) => onChange(field.numeric && event.target.value !== '' ? Number(event.target.value) : event.target.value),
|
|
141
|
+
}),
|
|
142
|
+
h('small', { id: inputId + '-hint', style: { fontSize: '11px', lineHeight: 1.6, opacity: .6 } }, field.hint))
|
|
143
|
+
}
|
|
144
|
+
function SwitchField({ field, checked, disabled, onChange }) {
|
|
145
|
+
return h('label', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', cursor: disabled ? 'default' : 'pointer', padding: '2px 0', opacity: disabled ? .6 : 1 } },
|
|
146
|
+
h('input', { name: field.key, type: 'checkbox', checked: checked === true, disabled, onChange: (event) => onChange(event.target.checked), style: { width: '16px', height: '16px', flex: '0 0 auto', margin: '2px 0 0', accentColor: '#177c55', cursor: disabled ? 'not-allowed' : 'pointer' } }),
|
|
147
|
+
h('span', { style: { display: 'grid', gap: '4px', minWidth: 0 } },
|
|
148
|
+
h('span', { style: { fontSize: '12px', fontWeight: 550 } }, field.label),
|
|
149
|
+
h('small', { style: { fontSize: '11px', lineHeight: 1.6, opacity: .65 } }, field.hint)))
|
|
150
|
+
}
|
|
151
|
+
function SettingsFields({ fields, draft, dirtyKeys, conflicts, disabled, editField }) {
|
|
152
|
+
return h('div', { style: { display: 'grid', gap: '16px' } }, ...fields.map((field) => {
|
|
153
|
+
const onChange = (value) => editField(field.key, value)
|
|
154
|
+
return field.checkbox
|
|
155
|
+
? h(SwitchField, { key: field.key, field, checked: draft[field.key], disabled, onChange })
|
|
156
|
+
: h(TextField, { key: field.key, field, value: draft[field.key], disabled, invalid: dirtyKeys.includes(field.key) && field.numeric && !Number.isSafeInteger(draft[field.key]), onChange })
|
|
157
|
+
}))
|
|
94
158
|
}
|
|
95
159
|
|
|
96
160
|
function AntigravitySettingsCard(props) {
|
|
97
161
|
const remote = props.remote
|
|
98
|
-
const quota = useQuota(props.readQuota)
|
|
99
162
|
const [account, setAccount] = React.useState(undefined)
|
|
100
163
|
const [models, setModels] = React.useState([])
|
|
101
164
|
const [modelBusy, setModelBusy] = React.useState(false)
|
|
@@ -217,7 +280,8 @@ window.__ModuleLoader__.load({
|
|
|
217
280
|
setConflicts((previous) => [...new Set([...previous.filter((key) => dirty.current.has(key)), ...changed])])
|
|
218
281
|
setWritable(settings?.writable === true)
|
|
219
282
|
setRegistered(rows.some((row) => row?.id === PROVIDER))
|
|
220
|
-
if (!
|
|
283
|
+
if (!found) setStatus({ kind: 'error', text: '宿主尚未提供 Antigravity ACP 的可编辑设置。请确认 Host 与 Client 插件版本一致,并重新加载插件后刷新配置。' })
|
|
284
|
+
else if (!preserveStatus) setStatus(null)
|
|
221
285
|
} catch (cause) {
|
|
222
286
|
if (alive.current && version === settingsVersion.current) setStatus({ kind: 'error', text: cause instanceof Error ? cause.message : String(cause) })
|
|
223
287
|
} finally {
|
|
@@ -247,7 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
247
311
|
}
|
|
248
312
|
setDirtyKeys([...dirty.current])
|
|
249
313
|
setConflicts([])
|
|
250
|
-
setStatus({ kind: 'ok', text: '
|
|
314
|
+
setStatus({ kind: 'ok', text: '已保存。会话偏好从下一轮生效;代理用于后续请求及新启动的 ACP 进程,已有进程需重新加载插件才能切换代理。自动预热在下次启用插件时生效;当前已选模型保持不变。' })
|
|
251
315
|
await reload(true)
|
|
252
316
|
} catch (cause) {
|
|
253
317
|
if (!alive.current) return
|
|
@@ -291,73 +355,85 @@ window.__ModuleLoader__.load({
|
|
|
291
355
|
setStatus({ kind: 'ok', text: '已撤销未保存的修改,恢复为当前宿主设置。' })
|
|
292
356
|
}
|
|
293
357
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{ style: {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
h('
|
|
311
|
-
|
|
312
|
-
|
|
358
|
+
const connection = connectionStatus(account, registered)
|
|
359
|
+
const fieldsFor = (group) => FIELDS.filter((field) => field.group === group)
|
|
360
|
+
const fieldProps = { draft, dirtyKeys, conflicts, disabled: editingDisabled, editField }
|
|
361
|
+
const busy = ['connecting', 'cancelling'].includes(account?.state) || runtimeBusy(account?.runtime)
|
|
362
|
+
const readonly = !loading && view && !writable
|
|
363
|
+
const advancedSummary = h('summary', { style: SUMMARY },
|
|
364
|
+
h('span', { style: { display: 'grid', gap: '4px' } },
|
|
365
|
+
h('span', { style: { fontSize: '13px', fontWeight: 650 } }, '高级设置'),
|
|
366
|
+
h('small', { style: { fontSize: '11px', fontWeight: 400, opacity: .6 } }, '网络、CLI、会话恢复与进程生命周期')))
|
|
367
|
+
|
|
368
|
+
return h('div', { style: { padding: '20px', display: 'flex', flexDirection: 'column', gap: '14px', maxWidth: '720px', boxSizing: 'border-box', lineHeight: 1.5 } },
|
|
369
|
+
h('header', { style: { display: 'flex', alignItems: 'flex-start', gap: '12px', padding: '0 2px 2px' } },
|
|
370
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
371
|
+
h('div', { style: { fontSize: '10px', fontWeight: 650, letterSpacing: '.1em', opacity: .5, marginBottom: '5px' } }, 'MODEL PROVIDER'),
|
|
372
|
+
h('h2', { style: { margin: 0, fontSize: '20px', lineHeight: 1.35, fontWeight: 650, letterSpacing: '-.015em' } }, 'Antigravity ACP'),
|
|
373
|
+
h('p', { style: { margin: '6px 0 0', fontSize: '12px', opacity: .6 } }, '连接官方 Antigravity,模型与工具仍由 DSH 管理。')),
|
|
374
|
+
h('span', { title: 'Provider 路由:' + PROVIDER, style: { flex: '0 0 auto', padding: '4px 9px', borderRadius: '99px', fontSize: '11px', color: registered === false ? '#b42318' : registered === true ? '#177c55' : 'inherit', background: registered === false ? 'rgba(217,119,87,.10)' : registered === true ? 'rgba(37,184,121,.10)' : TRACK } }, registered === false ? '未注册' : registered === true ? 'Provider 已注册' : '正在检查路由')),
|
|
375
|
+
|
|
376
|
+
readonly ? h(StatusNotice, { status: { kind: 'busy', text: '当前设置为只读。你仍可查看连接状态与诊断;请在有写入权限的宿主中修改配置。' } }) : null,
|
|
377
|
+
|
|
378
|
+
h('section', { style: PANEL, 'aria-label': '账户连接' },
|
|
379
|
+
h(SectionHeading, { eyebrow: '01 · 连接', title: '账户与连接', description: '先预热减少等待,或验证账户并读取模型目录。' }),
|
|
380
|
+
h('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', padding: '12px', borderRadius: '10px', background: 'rgba(128,128,128,.045)', marginBottom: '14px' } },
|
|
381
|
+
h(StatusDot, { color: connection.color }),
|
|
382
|
+
h('div', { style: { minWidth: 0 } },
|
|
383
|
+
h('div', { style: { fontSize: '12px', fontWeight: 650, color: connection.color } }, connection.label),
|
|
384
|
+
h('p', { role: 'status', style: { fontSize: '12px', lineHeight: 1.6, opacity: .7, overflowWrap: 'anywhere', margin: '3px 0 0' } }, connection.description))),
|
|
313
385
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px' } },
|
|
314
|
-
h('button', { type: 'button', style:
|
|
315
|
-
h('button', { type: 'button', style: ACTION, disabled: !props.control ||
|
|
316
|
-
['connecting', 'cancelling'].includes(account?.state) ? h('button', { type: 'button', style: ACTION, disabled: account?.state === 'cancelling', onClick: () => accountAction('cancel') }, '取消连接') : null),
|
|
317
|
-
h('p', { style: { opacity: .
|
|
318
|
-
|
|
319
|
-
h('section', { style:
|
|
320
|
-
h(
|
|
321
|
-
h('
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
h('
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
{
|
|
333
|
-
h('span', null, field.label),
|
|
334
|
-
h('input', {
|
|
335
|
-
name: field.key,
|
|
336
|
-
type: field.checkbox ? 'checkbox' : field.numeric ? 'number' : 'text',
|
|
337
|
-
...(field.numeric ? { min: field.min, max: field.max, step: 1 } : {}),
|
|
338
|
-
disabled: editingDisabled,
|
|
339
|
-
...(field.checkbox ? { checked: draft[field.key] === true } : { value: draft[field.key] ?? '' }),
|
|
340
|
-
placeholder: field.placeholder,
|
|
341
|
-
style: field.checkbox ? { alignSelf: 'flex-start' } : { ...ACTION, cursor: 'text', width: '100%', boxSizing: 'border-box' },
|
|
342
|
-
onChange: (event) => editField(field.key, field.checkbox ? event.target.checked
|
|
343
|
-
: field.numeric && event.target.value !== '' ? Number(event.target.value) : event.target.value),
|
|
344
|
-
}),
|
|
345
|
-
h('small', { style: { opacity: 0.7 } }, field.hint),
|
|
346
|
-
)),
|
|
347
|
-
h(
|
|
348
|
-
'div',
|
|
349
|
-
{ style: { display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } },
|
|
350
|
-
h('button', { type: 'button', onClick: save, disabled: editingDisabled || dirtyKeys.length === 0, style: ACTION }, saving ? '保存中…' : '保存'),
|
|
351
|
-
h('button', { type: 'button', disabled: loading || saving, onClick: () => reload(), style: ACTION }, loading ? '刷新中…' : '刷新配置'),
|
|
352
|
-
dirtyKeys.length ? h('button', { type: 'button', disabled: loading || saving, onClick: discardDraft, style: ACTION }, '撤销未保存修改') : null,
|
|
353
|
-
),
|
|
354
|
-
!loading && view && !writable ? h('p', { role: 'status', style: { margin: 0, fontSize: '12px', opacity: .7 } }, '当前设置为只读,请在有写入权限的宿主中修改。') : null,
|
|
355
|
-
dirtyKeys.length ? h('p', { role: 'status', style: { margin: 0, fontSize: '12px', opacity: .7 } }, '有 ' + dirtyKeys.length + ' 项未保存的修改;刷新会保留这些输入。') : null,
|
|
356
|
-
conflicts.length ? h('p', { role: 'alert', style: { margin: 0, fontSize: '12px', color: '#d99b28' } }, '其他位置更新了以下设置,已保留你的输入:' + conflicts.map((key) => FIELDS.find((field) => field.key === key)?.label ?? key).join('、') + '。请核对后再保存。') : null,
|
|
357
|
-
status ? h('p', { role: status.kind === 'error' ? 'alert' : 'status', style: { margin: 0, color: status.kind === 'error' ? '#e5534b' : undefined } }, status.text) : null,
|
|
358
|
-
h('section', { style: { marginTop: '8px' }, 'aria-label': '套餐额度管理' }, QuotaDetails({ ...quota, model: draft.preferredModel })),
|
|
386
|
+
h('button', { type: 'button', style: PRIMARY, disabled: !props.control || busy, onClick: () => accountAction('login') }, account?.state === 'connecting' ? '正在连接…' : '连接 / 登录 ACP'),
|
|
387
|
+
h('button', { type: 'button', style: ACTION, disabled: !props.control || busy || connection.ready, onClick: () => accountAction('warmup') }, connection.ready ? '已预热' : '立即预热'),
|
|
388
|
+
['connecting', 'cancelling'].includes(account?.state) ? h('button', { type: 'button', style: ACTION, disabled: account?.state === 'cancelling', onClick: () => accountAction('cancel') }, account?.state === 'cancelling' ? '正在取消…' : '取消连接') : null),
|
|
389
|
+
h('p', { style: { opacity: .55, fontSize: '11px', lineHeight: 1.6, margin: '10px 0 0' } }, '预热不会发送聊天消息。仅在需要登录时打开官方授权页面;思考强度仍由会话菜单设置。')),
|
|
390
|
+
|
|
391
|
+
h('section', { style: PANEL, 'aria-label': '模型管理' },
|
|
392
|
+
h(SectionHeading, { eyebrow: '02 · 模型', title: '默认模型', description: '选择新对话优先使用的模型,不改变当前会话。',
|
|
393
|
+
action: h('button', { type: 'button', title: '从官方 CLI 或 ACP 会话重新读取模型目录', 'aria-busy': modelBusy || undefined, style: { ...ACTION, flex: '0 0 auto', whiteSpace: 'nowrap' }, disabled: modelBusy || !props.control, onClick: () => pullModels(true) }, modelBusy ? '正在刷新…' : '刷新模型目录') }),
|
|
394
|
+
h('div', { style: { display: 'grid', gap: '14px' } },
|
|
395
|
+
h('label', { style: { display: 'grid', gap: '6px', fontSize: '12px', fontWeight: 550 } }, '从模型目录选择',
|
|
396
|
+
h('select', { 'aria-label': '从目录选择默认模型', disabled: editingDisabled, value: models.some((m) => m.id === draft.preferredModel) ? draft.preferredModel : '', onChange: (event) => { if (event.target.value) editField('preferredModel', event.target.value) }, style: { ...ACTION, width: '100%', boxSizing: 'border-box', background: 'rgba(128,128,128,.035)' } },
|
|
397
|
+
h('option', { value: '' }, models.length ? '选择一个模型,或在下方输入 ID' : '暂无目录,可手动输入模型 ID'),
|
|
398
|
+
...models.map((model) => h('option', { key: model.id, value: model.id }, model.name + ' · ' + model.id)))),
|
|
399
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('model') })),
|
|
400
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '5px 10px', marginTop: '10px', fontSize: '11px', opacity: .6 } },
|
|
401
|
+
h('span', null, models.length + ' 个模型'),
|
|
402
|
+
Array.isArray(selectedModalities) && selectedModalities.length ? h('span', null, '支持输入:' + selectedModalities.map((modality) => modalityLabels[modality] ?? modality).join('、')) : null,
|
|
403
|
+
draft.preferredModel && !selectedModel ? h('span', null, '自定义 ID · 尚未验证') : null),
|
|
404
|
+
modelMessage ? h('p', { role: 'status', style: { fontSize: '11px', lineHeight: 1.6, opacity: .65, margin: '8px 0 0', overflowWrap: 'anywhere' } }, modelMessage) : null),
|
|
359
405
|
|
|
360
|
-
|
|
406
|
+
h('section', { style: PANEL, 'aria-label': '常用设置' },
|
|
407
|
+
h(SectionHeading, { eyebrow: '03 · 偏好', title: '常用设置', description: '适合大多数使用场景;其它选项保持默认即可。' }),
|
|
408
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('general') })),
|
|
409
|
+
|
|
410
|
+
h('details', { style: PANEL, 'aria-label': '高级设置' }, advancedSummary,
|
|
411
|
+
h('div', { style: { display: 'grid', gap: '24px', marginTop: '20px', paddingTop: '18px', borderTop: '1px solid rgba(128,128,128,.14)' } },
|
|
412
|
+
h('section', { 'aria-label': '网络与 CLI 设置', style: { display: 'grid', gap: '16px' } },
|
|
413
|
+
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, '网络与 CLI'),
|
|
414
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('network') })),
|
|
415
|
+
h('section', { 'aria-label': '会话与进程设置', style: { display: 'grid', gap: '16px' } },
|
|
416
|
+
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, '会话与进程'),
|
|
417
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('advanced') })))),
|
|
418
|
+
|
|
419
|
+
h('details', { style: PANEL, 'aria-label': '连接诊断' },
|
|
420
|
+
h('summary', { style: SUMMARY },
|
|
421
|
+
h('span', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
422
|
+
h(StatusDot, { color: connection.color }),
|
|
423
|
+
h('span', { style: { fontSize: '13px', fontWeight: 650 } }, '连接诊断')),
|
|
424
|
+
h('small', { style: { fontSize: '11px', opacity: .6 } }, '状态、并发与耗时')),
|
|
425
|
+
h('div', { style: { marginTop: '18px', paddingTop: '16px', borderTop: '1px solid rgba(128,128,128,.14)' } },
|
|
426
|
+
account?.runtime ? RuntimeDetails({ runtime: account.runtime }) : h('p', { style: { margin: 0, fontSize: '12px', opacity: .6 } }, '连接后显示生命周期、并发容量与分阶段耗时。'))),
|
|
427
|
+
|
|
428
|
+
h('footer', { style: { display: 'grid', gap: '10px', padding: '4px 2px 0' } },
|
|
429
|
+
conflicts.length ? h('p', { role: 'alert', style: { margin: 0, fontSize: '12px', lineHeight: 1.6, color: '#946200' } }, '其他位置更新了以下设置,已保留你的输入:' + conflicts.map((key) => FIELDS.find((field) => field.key === key)?.label ?? key).join('、') + '。请核对后再保存。') : null,
|
|
430
|
+
h(StatusNotice, { status }),
|
|
431
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } },
|
|
432
|
+
h('button', { type: 'button', onClick: save, disabled: editingDisabled || dirtyKeys.length === 0, style: PRIMARY }, saving ? '保存中…' : '保存'),
|
|
433
|
+
dirtyKeys.length ? h('button', { type: 'button', disabled: loading || saving, onClick: discardDraft, style: ACTION }, '撤销未保存修改') : null,
|
|
434
|
+
h('button', { type: 'button', title: '重新读取宿主配置,保留未保存的输入', disabled: loading || saving, onClick: () => reload(), style: { ...ACTION, borderColor: 'transparent', background: 'transparent', opacity: .7, marginLeft: 'auto' } }, loading ? '刷新中…' : '刷新配置')),
|
|
435
|
+
dirtyKeys.length ? h('p', { role: 'status', style: { margin: 0, fontSize: '11px', opacity: .65 } }, '有 ' + dirtyKeys.length + ' 项未保存的修改;刷新会保留这些输入。') : null,
|
|
436
|
+
h('p', { style: { margin: '2px 0 0', paddingTop: '12px', borderTop: '1px solid rgba(128,128,128,.12)', fontSize: '11px', lineHeight: 1.6, opacity: .55 } }, '套餐额度与 AI Credits 请在对话输入框旁的额度药丸中查看,此处不再重复展示。')))
|
|
361
437
|
}
|
|
362
438
|
|
|
363
439
|
/**
|
|
@@ -369,7 +445,10 @@ window.__ModuleLoader__.load({
|
|
|
369
445
|
|
|
370
446
|
const TRACK = 'rgba(128,128,128,.18)'
|
|
371
447
|
const CARD = { padding: '12px', borderRadius: '12px', border: '1px solid rgba(128,128,128,.18)', background: 'rgba(128,128,128,.05)' }
|
|
372
|
-
const
|
|
448
|
+
const PANEL = { padding: '18px', borderRadius: '14px', border: '1px solid rgba(128,128,128,.16)', background: 'rgba(128,128,128,.025)' }
|
|
449
|
+
const SUMMARY = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', cursor: 'pointer', listStyle: 'none' }
|
|
450
|
+
const ACTION = { padding: '7px 12px', minHeight: '34px', borderRadius: '8px', border: '1px solid rgba(128,128,128,.22)', background: 'rgba(128,128,128,.06)', color: 'inherit', font: 'inherit', fontSize: '12px', cursor: 'pointer' }
|
|
451
|
+
const PRIMARY = { ...ACTION, background: '#177c55', border: '1px solid #177c55', color: '#fff', fontWeight: 600 }
|
|
373
452
|
const GROUPS = [{ key: 'gemini', name: 'Gemini' }, { key: 'claude', name: 'Claude / GPT' }, { key: 'argon', name: 'Gemini 4 Argon' }]
|
|
374
453
|
function groupKey(name) {
|
|
375
454
|
const text = String(name ?? '').toLowerCase()
|
|
@@ -426,23 +505,29 @@ window.__ModuleLoader__.load({
|
|
|
426
505
|
const [data, setData] = React.useState(null)
|
|
427
506
|
const [refreshing, setRefreshing] = React.useState(false)
|
|
428
507
|
const version = React.useRef(0)
|
|
508
|
+
const update = React.useCallback((value) => setData(value), [])
|
|
429
509
|
const load = React.useCallback((force = false) => {
|
|
430
510
|
if (!enabled || !readQuota) return Promise.resolve()
|
|
431
511
|
const current = ++version.current
|
|
432
512
|
setRefreshing(true)
|
|
433
513
|
return Promise.resolve().then(() => readQuota(force)).then(unwrap).then((value) => {
|
|
434
|
-
if (current === version.current)
|
|
514
|
+
if (current === version.current) update(value)
|
|
435
515
|
}).catch((error) => {
|
|
436
|
-
if (current === version.current)
|
|
516
|
+
if (current === version.current) {
|
|
517
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
518
|
+
setData((previous) => previous?.status === 'ok'
|
|
519
|
+
? { ...previous, stale: true, staleReason: message }
|
|
520
|
+
: { status: 'error', groups: [], error: { message } })
|
|
521
|
+
}
|
|
437
522
|
}).finally(() => { if (current === version.current) setRefreshing(false) })
|
|
438
|
-
}, [readQuota, enabled])
|
|
523
|
+
}, [readQuota, enabled, update])
|
|
439
524
|
React.useEffect(() => {
|
|
440
525
|
if (!enabled || !readQuota) { setData(null); return undefined }
|
|
441
526
|
void load(false)
|
|
442
|
-
const off = readQuota.subscribe?.((value) =>
|
|
527
|
+
const off = readQuota.subscribe?.((value) => { ++version.current; update(value) })
|
|
443
528
|
const timer = setInterval(() => { if (typeof document === 'undefined' || document.visibilityState !== 'hidden') void load(false) }, 300_000)
|
|
444
529
|
return () => { ++version.current; clearInterval(timer); off?.() }
|
|
445
|
-
}, [load, enabled, readQuota])
|
|
530
|
+
}, [load, enabled, readQuota, update])
|
|
446
531
|
return { data, refreshing, load }
|
|
447
532
|
}
|
|
448
533
|
function CreditsDetails({ credits, pending }) {
|
|
@@ -552,7 +637,12 @@ window.__ModuleLoader__.load({
|
|
|
552
637
|
return () => { document.removeEventListener('pointerdown', outside); document.removeEventListener('keydown', key); window.removeEventListener('resize', reposition); window.removeEventListener('scroll', reposition, true) }
|
|
553
638
|
}, [expanded])
|
|
554
639
|
if (!enabled) return null
|
|
555
|
-
const
|
|
640
|
+
const groups = quotaGroups(quota.data)
|
|
641
|
+
const family = modelGroup(model)
|
|
642
|
+
// 官方 /usage 可能只报告 Gemini 共享额度;只有真实 Argon 分组存在时,
|
|
643
|
+
// 才把 Argon 型号绑定到独立组,不能命中一个预建的空组而误报未知。
|
|
644
|
+
const group = groups.find((g) => g.key === family && (family !== 'argon' || g.buckets.length > 0))
|
|
645
|
+
?? (family === 'argon' ? groups.find((g) => g.key === 'gemini') : undefined)
|
|
556
646
|
const buckets = group?.buckets ?? []
|
|
557
647
|
const label = quota.data?.status === 'error' ? '额度读取失败' : !quota.data ? '正在读取额度' : !group ? '额度未匹配,请选择具体模型' : !buckets.length ? '额度未知' : buckets.map((b) => windowName(b) + ' ' + (b.disabled ? '已停用' : percentage(b) === undefined ? '额度未知' : Math.round(percentage(b)) + '% 剩余')).join(' · ')
|
|
558
648
|
const title = String(model ?? '未选择模型') + ' · ' + label + (quota.data?.stale ? ' · 缓存数据' : '')
|
|
@@ -741,7 +831,7 @@ window.__ModuleLoader__.load({
|
|
|
741
831
|
id: 'antigravity-acp',
|
|
742
832
|
order: 42,
|
|
743
833
|
label: () => 'Antigravity ACP',
|
|
744
|
-
inject: () => ({ remote: ctx.remote,
|
|
834
|
+
inject: () => ({ remote: ctx.remote, control }),
|
|
745
835
|
},
|
|
746
836
|
AntigravitySettingsCard,
|
|
747
837
|
),
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,15 @@ import type { AgentLinkHandlers } from './protocol/agent-link.js';
|
|
|
6
6
|
import type { RuntimeDirectoryLease } from './runtime/binary.js';
|
|
7
7
|
import type { AcpCredentialResult } from './runtime/credentials.js';
|
|
8
8
|
import { AcpLifecycle } from './runtime/lifecycle.js';
|
|
9
|
-
import type { AntigravityHostConfig } from './settings.js';
|
|
9
|
+
import type { AntigravityHostConfig, AntigravityPluginConfig } from './settings.js';
|
|
10
10
|
export { AGY_PROVIDER_ID } from './adapter/antigravity-adapter.js';
|
|
11
11
|
export { Config } from './settings.js';
|
|
12
12
|
export type { AntigravityHostConfig } from './settings.js';
|
|
13
|
+
declare module '@deepseek-ai/cordis' {
|
|
14
|
+
interface Events {
|
|
15
|
+
'loader/volatile-update'(paths: string[][]): void;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
13
18
|
export declare const name = "llm-antigravity-acp";
|
|
14
19
|
export declare const inject: string[];
|
|
15
20
|
/**
|
|
@@ -98,4 +103,4 @@ export interface AcpRuntimeDeps {
|
|
|
98
103
|
export declare function ensureAcpRuntime(ctx: Context, config: AntigravityHostConfig, handlers: AgentLinkHandlers, active: ActiveAcpRuntime, deps?: AcpRuntimeDeps): Promise<AgentLink>;
|
|
99
104
|
/** 设置页显式连接:独立运行时,只建空工具会话验证认证,不发送模型 prompt。 */
|
|
100
105
|
export declare function connectAcpAccount(ctx: Context, config: AntigravityHostConfig, signal: AbortSignal, deps?: AcpRuntimeDeps): Promise<import("@agentclientprotocol/sdk").NewSessionResponse>;
|
|
101
|
-
export declare function apply(ctx: Context,
|
|
106
|
+
export declare function apply(ctx: Context, input: AntigravityPluginConfig): void;
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { PooledAcpAdapter } from './pool/pooled-adapter.js';
|
|
|
15
15
|
import { AgentLink, agentStreamFromSubprocess, AGY_CLIENT_INFO, AGY_LINK_ERROR, launchAcpAgent } from './protocol/agent-link.js';
|
|
16
16
|
import { AGY_BINARY_ERROR, cleanupStaleRuntimeDirectories, cleanupStaleStagingDirs, DEFAULT_ACP_SERVER_VERSION, ensureAgentBinary, resolveCacheDir, resolveInstallRoot, createRuntimeDirectory, releaseRuntimeDirectory, defaultAgentArgs, } from './runtime/binary.js';
|
|
17
17
|
import { resolveProxy } from './runtime/proxy-env.js';
|
|
18
|
-
import { createProxiedFetch } from './runtime/proxied-fetch.js';
|
|
18
|
+
import { createLiveProxiedFetch, createProxiedFetch } from './runtime/proxied-fetch.js';
|
|
19
19
|
import { ensureAcpCredentials } from './runtime/credentials.js';
|
|
20
20
|
import { describeLowIntegrity, isLowIntegrityTarget } from './runtime/integrity.js';
|
|
21
21
|
import { describeAuthWait, redactSensitiveText } from './runtime/redact.js';
|
|
@@ -25,6 +25,7 @@ import { agyCliCandidates, createSubprocessCliRun, fetchAgyModels } from './runt
|
|
|
25
25
|
import { AgYQuotaService, registerAgYQuotaRemote } from './quota/quota-remote.js';
|
|
26
26
|
import { SettingsControl } from './quota/control.js';
|
|
27
27
|
import { AcpLifecycle } from './runtime/lifecycle.js';
|
|
28
|
+
import { liveHostConfig } from './settings.js';
|
|
28
29
|
export { AGY_PROVIDER_ID } from './adapter/antigravity-adapter.js';
|
|
29
30
|
export { Config } from './settings.js';
|
|
30
31
|
export const name = 'llm-antigravity-acp';
|
|
@@ -376,7 +377,8 @@ export async function connectAcpAccount(ctx, config, signal, deps = {}) {
|
|
|
376
377
|
await disposeAcpRuntime(runtime);
|
|
377
378
|
}
|
|
378
379
|
}
|
|
379
|
-
export function apply(ctx,
|
|
380
|
+
export function apply(ctx, input) {
|
|
381
|
+
const config = liveHostConfig(input);
|
|
380
382
|
const cacheDir = resolveCacheDir();
|
|
381
383
|
const acpVersion = config.acpVersion ?? DEFAULT_ACP_SERVER_VERSION;
|
|
382
384
|
// 只回收带本插件所有权且已确认退出的运行目录;未知 / 活跃目录不按年龄删除。
|
|
@@ -389,9 +391,12 @@ export function apply(ctx, config) {
|
|
|
389
391
|
// 代理规则与官方 ACP 进程一致:**配置的 proxyUrl 优先,其次宿主环境自身导出的
|
|
390
392
|
// 代理**。CLI 每次启动都会做登录/配置刷新,缺代理会一直挂在网络上(实测),
|
|
391
393
|
// 表现为额度药丸永远停在「额度…」。地址非法时以 AGY_INVALID_PROXY 明确失败。
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
394
|
+
const readProxy = () => resolveProxy({ url: config.proxyUrl, bypass: config.proxyBypass });
|
|
395
|
+
// Validate the initial route before registering resources. Subsequent operations
|
|
396
|
+
// capture the latest settings; existing ACP processes keep their launch env.
|
|
397
|
+
readProxy();
|
|
398
|
+
const pluginFetch = createLiveProxiedFetch(readProxy);
|
|
399
|
+
const cliRun = (request) => createSubprocessCliRun(ctx.subprocess, readProxy())(request);
|
|
395
400
|
const findCli = () => agyCliCandidates(process.env, undefined, config.agyCliPath).find((candidate) => existsSync(candidate));
|
|
396
401
|
let modelCatalogRevision = 0;
|
|
397
402
|
const catalogChanged = () => {
|
|
@@ -402,6 +407,10 @@ export function apply(ctx, config) {
|
|
|
402
407
|
}
|
|
403
408
|
catch { /* Observers cannot veto catalog updates. */ }
|
|
404
409
|
};
|
|
410
|
+
ctx.on('loader/volatile-update', (paths) => {
|
|
411
|
+
if (paths.some(([key]) => key === 'preferredModel'))
|
|
412
|
+
catalogChanged();
|
|
413
|
+
});
|
|
405
414
|
let attachmentStore;
|
|
406
415
|
/**
|
|
407
416
|
* 官方模型清单:缓存先行(选择器即时可用),并在**后台**刷新一遍供下次生效。
|
|
@@ -442,7 +451,8 @@ export function apply(ctx, config) {
|
|
|
442
451
|
await cliRefresh;
|
|
443
452
|
return cliModelRows ?? [];
|
|
444
453
|
};
|
|
445
|
-
|
|
454
|
+
// The store is lazy: no lock or file is opened until recovery is enabled and used.
|
|
455
|
+
const sessionStore = createFileSessionMapStore(cacheDir, () => ctx.logger?.warn?.('antigravity-acp: 会话映射不可用或由另一 Host 占用,跨进程恢复已降级;不影响本地聊天'));
|
|
446
456
|
const workers = [];
|
|
447
457
|
let metadata;
|
|
448
458
|
// Serialize startup/credential preparation, not inference. This avoids competing OAuth imports.
|
|
@@ -458,7 +468,7 @@ export function apply(ctx, config) {
|
|
|
458
468
|
getLink: (handlers, signal, purpose) => {
|
|
459
469
|
const launch = startupBarrier.catch(() => undefined).then(() => {
|
|
460
470
|
signal?.throwIfAborted();
|
|
461
|
-
return ensureAcpRuntime(ctx, config, handlers, active, { signal, fetchImpl: pluginFetch, allowInteractiveAuth: purpose !== 'warmup' && workerIndex === 0 });
|
|
471
|
+
return ensureAcpRuntime(ctx, { ...config }, handlers, active, { signal, fetchImpl: pluginFetch, allowInteractiveAuth: purpose !== 'warmup' && workerIndex === 0 });
|
|
462
472
|
});
|
|
463
473
|
startupBarrier = launch.then(() => undefined, () => undefined);
|
|
464
474
|
return launch;
|
|
@@ -470,13 +480,14 @@ export function apply(ctx, config) {
|
|
|
470
480
|
promptTimeoutMs: config.promptTimeoutMs,
|
|
471
481
|
parkSettleMs: config.bridgeParkSettleMs,
|
|
472
482
|
preferredModel: config.preferredModel,
|
|
483
|
+
readSettings: () => ({ ...config, sessionStore: config.resumeSessions ? sessionStore : undefined }),
|
|
473
484
|
// ACP 会话复用:同一条 DSH 对话的后续回合只发增量;重启恢复默认关闭。
|
|
474
485
|
reuseSessions: config.reuseSessions,
|
|
475
486
|
resumeSessions: config.resumeSessions,
|
|
476
487
|
keepProcessAfterCancel: config.keepProcessAfterCancel,
|
|
477
488
|
cancelGraceMs: config.cancelGraceMs,
|
|
478
489
|
idleRecycleMs: config.idleRecycleMs,
|
|
479
|
-
sessionStore,
|
|
490
|
+
sessionStore: config.resumeSessions ? sessionStore : undefined,
|
|
480
491
|
canReadImages: () => attachmentStore !== undefined,
|
|
481
492
|
readImage: (ref, signal) => {
|
|
482
493
|
if (!attachmentStore)
|
|
@@ -505,12 +516,17 @@ export function apply(ctx, config) {
|
|
|
505
516
|
if (metadata)
|
|
506
517
|
worker.importMetadata(metadata);
|
|
507
518
|
workers.push(worker);
|
|
508
|
-
return {
|
|
519
|
+
return {
|
|
520
|
+
adapter: worker,
|
|
521
|
+
ensureImages: (signal) => worker.prepareImages(signal),
|
|
522
|
+
read: () => lifecycle.read(),
|
|
523
|
+
dispose: () => disposeAcpRuntime(active),
|
|
524
|
+
};
|
|
509
525
|
}, {
|
|
510
526
|
capacity: config.maxConcurrentSessions,
|
|
511
527
|
queueTimeoutMs: config.queueTimeoutMs,
|
|
512
528
|
// 复用会话时同一对话必须粘在同一个 worker;关闭复用时回到纯负载调度。
|
|
513
|
-
sessionAffinity: config.reuseSessions !== false,
|
|
529
|
+
sessionAffinity: () => config.reuseSessions !== false,
|
|
514
530
|
});
|
|
515
531
|
ctx.inject(['attachments'], (scope) => {
|
|
516
532
|
const store = scope.attachments;
|
|
@@ -559,10 +575,11 @@ export function apply(ctx, config) {
|
|
|
559
575
|
warn: (message, error) => ctx.logger?.warn?.(redactSensitiveText(`${message}${error ? `: ${error instanceof Error ? error.message : String(error)}` : ''}`)),
|
|
560
576
|
},
|
|
561
577
|
});
|
|
562
|
-
//
|
|
578
|
+
// Config 的即时字段由 settings 投影;使用自定义卡片,不重复生成页面。
|
|
579
|
+
// 弱依赖 settings,无 Web 的 profile 仍能加载。
|
|
563
580
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
564
581
|
const settings = settingsCtx.get('settings');
|
|
565
|
-
settingsCtx.effect(() => settings.configure({ auto:
|
|
582
|
+
settingsCtx.effect(() => settings.configure({ auto: false }, ctx.fiber), 'antigravity-acp: schema settings form');
|
|
566
583
|
});
|
|
567
584
|
// cordis 4.x 的规范 dispose 挂点:generator effect,卸载时逆序执行。
|
|
568
585
|
ctx.effect(function* () {
|