@auto-ai/agent 2.1.95 → 2.1.96

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/dist/ws-test.html CHANGED
@@ -1126,6 +1126,34 @@
1126
1126
  gap: 0.5rem;
1127
1127
  margin-bottom: 0.5rem;
1128
1128
  }
1129
+ .agent-env-fixed-section {
1130
+ margin-bottom: 0.9rem;
1131
+ padding: 0.65rem;
1132
+ border: 1px solid var(--ds-separator);
1133
+ border-radius: 10px;
1134
+ background: var(--ds-bg-subtle);
1135
+ }
1136
+ .agent-env-fixed-section-title {
1137
+ margin: 0 0 0.55rem;
1138
+ font-size: 0.82rem;
1139
+ color: var(--ds-text-soft);
1140
+ }
1141
+ .agent-env-fixed-row {
1142
+ display: grid;
1143
+ grid-template-columns: minmax(170px, 0.9fr) 1.8fr;
1144
+ gap: 0.5rem;
1145
+ margin-bottom: 0.45rem;
1146
+ align-items: center;
1147
+ }
1148
+ .agent-env-fixed-row:last-child {
1149
+ margin-bottom: 0;
1150
+ }
1151
+ .agent-env-fixed-label {
1152
+ color: var(--ds-text-soft);
1153
+ font-size: 0.78rem;
1154
+ line-height: 1.35;
1155
+ word-break: break-all;
1156
+ }
1129
1157
  .agent-env-kv-input {
1130
1158
  width: 100%;
1131
1159
  box-sizing: border-box;
@@ -2712,6 +2740,10 @@
2712
2740
  <option value="dontAsk">dontAsk</option>
2713
2741
  <option value="bypassPermissions">bypassPermissions</option>
2714
2742
  </select>
2743
+ <select id="composerToolAskSelect" class="composer-mode-select" aria-label="工具确认模式">
2744
+ <option value="1">自动</option>
2745
+ <option value="0">手动</option>
2746
+ </select>
2715
2747
  </div>
2716
2748
  <div class="composer-tools-right" aria-label="发送操作">
2717
2749
  <button type="button" class="sidebar-icon-btn composer-action-btn" id="btnAbort" disabled title="中断" aria-label="中断">
@@ -2893,15 +2925,7 @@
2893
2925
  </div>
2894
2926
  <div class="agent-env-tab-panels">
2895
2927
  <div id="agentEnvPanelEnv" class="tool-files-body agent-env-panel" hidden>
2896
- <div style="display: grid; grid-template-columns: 1fr 1fr auto; gap: 0.5rem; margin-bottom: 0.5rem; color: #8892a8; font-size: 0.72rem">
2897
- <span>KEY</span>
2898
- <span>VALUE</span>
2899
- <span>操作</span>
2900
- </div>
2901
2928
  <div id="agentEnvList" class="tool-files-list"></div>
2902
- <div style="margin-top: 0.75rem">
2903
- <button type="button" class="btn-system-settings" id="btnAddAgentEnvRow">新增变量</button>
2904
- </div>
2905
2929
  </div>
2906
2930
  <div
2907
2931
  id="agentEnvPanelAgentMd"
@@ -3245,7 +3269,6 @@
3245
3269
  const btnAgentEnvModalClose = $('btnAgentEnvModalClose')
3246
3270
  const btnAgentEnvCancel = $('btnAgentEnvCancel')
3247
3271
  const btnAgentEnvSave = $('btnAgentEnvSave')
3248
- const btnAddAgentEnvRow = $('btnAddAgentEnvRow')
3249
3272
  const toolFilesModal = $('toolFilesModal')
3250
3273
  const toolFilesModalBackdrop = $('toolFilesModalBackdrop')
3251
3274
  const toolFilesModalTitle = $('toolFilesModalTitle')
@@ -3407,6 +3430,7 @@
3407
3430
  const composerModelSuggestions = $('composerModelSuggestions')
3408
3431
  const composerModelField = $('composerModelField')
3409
3432
  const composerModeSelect = $('composerModeSelect')
3433
+ const composerToolAskSelect = $('composerToolAskSelect')
3410
3434
  const btnSaveAgentConfig = $('btnSaveAgentConfig')
3411
3435
  const agentPickerModal = $('agentPickerModal')
3412
3436
  const agentPickerModalBackdrop = $('agentPickerModalBackdrop')
@@ -3459,7 +3483,16 @@
3459
3483
  let sessionOverrideState = { model: '', env: {} }
3460
3484
  // 未分配 sessionId 时暂存,收到 {type:'session', sessionId} 后补写
3461
3485
  let pendingSessionOverrides = null
3462
- let agentEnvDraft = []
3486
+ const AGENT_ENV_GROUP_ORDER = ['llm', 'git', 'geelib', 'tutui', 'system']
3487
+ const AGENT_ENV_GROUP_LABELS = {
3488
+ llm: 'llm设置',
3489
+ git: 'git设置',
3490
+ geelib: 'geelib设置',
3491
+ tutui: 'tutui设置',
3492
+ system: '系统设置',
3493
+ }
3494
+ let agentEnvSchema = []
3495
+ let agentEnvFieldValues = {}
3463
3496
  let agentEnvActiveTab = 'env'
3464
3497
  let agentEnvAgentMdLoadOk = false
3465
3498
  let pickerKind = 'tools'
@@ -3470,6 +3503,7 @@
3470
3503
  let remoteMcpServers = []
3471
3504
  const WS_PERMISSION_MODE_KEY = 'WS_PERMISSION_MODE'
3472
3505
  const LLM_MODEL_KEY = 'LLM_MODEL'
3506
+ const WS_ALLOW_TOOL_ASK_KEY = 'WS_ALLOW_TOOL_ASK'
3473
3507
  const WS_PERMISSION_MODE_ALLOWED = new Set([
3474
3508
  'acceptEdits',
3475
3509
  'plan',
@@ -3477,6 +3511,7 @@
3477
3511
  'dontAsk',
3478
3512
  'bypassPermissions',
3479
3513
  ])
3514
+ const WS_ALLOW_TOOL_ASK_ALLOWED = new Set(['0', '1'])
3480
3515
 
3481
3516
  function readAgentEnvString(key) {
3482
3517
  const envObj = agentConfigState.env && typeof agentConfigState.env === 'object'
@@ -3485,8 +3520,14 @@
3485
3520
  return typeof envObj[key] === 'string' ? envObj[key].trim() : ''
3486
3521
  }
3487
3522
 
3488
- /** 项目根 .env 中的 LLM 默认值(agent.json 未配置时用于新 session 展示) */
3489
- let projectEnvDefaults = { LLM_MODEL: '' }
3523
+ /** 项目根 .env 中的默认值(agent.json 未配置时用于新 session 展示) */
3524
+ let projectEnvDefaults = {
3525
+ LLM_MODEL: '',
3526
+ WS_PERMISSION_MODE: '',
3527
+ WS_ALLOW_TOOL_ASK: '',
3528
+ }
3529
+ /** 固定表单字段在项目 .env 的默认值(用于环境变量面板回显) */
3530
+ let projectEnvFormDefaults = {}
3490
3531
 
3491
3532
  function parseDotEnvValue(content, key) {
3492
3533
  if (!content || typeof content !== 'string' || !key) return ''
@@ -3523,14 +3564,181 @@
3523
3564
  return {}
3524
3565
  })
3525
3566
  const content = typeof body.content === 'string' ? body.content : ''
3567
+ agentEnvSchema = normalizeAgentEnvSchema(body.schema)
3568
+ if (body.groupLabels && typeof body.groupLabels === 'object') {
3569
+ for (let i = 0; i < AGENT_ENV_GROUP_ORDER.length; i++) {
3570
+ const group = AGENT_ENV_GROUP_ORDER[i]
3571
+ const maybeLabel = body.groupLabels[group]
3572
+ if (typeof maybeLabel === 'string' && maybeLabel.trim()) {
3573
+ AGENT_ENV_GROUP_LABELS[group] = maybeLabel.trim()
3574
+ }
3575
+ }
3576
+ }
3526
3577
  projectEnvDefaults = {
3527
3578
  LLM_MODEL: parseDotEnvValue(content, LLM_MODEL_KEY),
3579
+ WS_PERMISSION_MODE: parseDotEnvValue(content, WS_PERMISSION_MODE_KEY),
3580
+ WS_ALLOW_TOOL_ASK: parseDotEnvValue(content, WS_ALLOW_TOOL_ASK_KEY),
3581
+ }
3582
+ const formDefaults = {}
3583
+ for (let i = 0; i < agentEnvSchema.length; i++) {
3584
+ const key = agentEnvSchema[i].key
3585
+ formDefaults[key] = parseDotEnvValue(content, key)
3528
3586
  }
3587
+ projectEnvFormDefaults = formDefaults
3529
3588
  } catch {
3530
3589
  /* 项目 .env 不可读时不阻塞 composer */
3531
3590
  }
3532
3591
  }
3533
3592
 
3593
+ /**
3594
+ * 业务逻辑:
3595
+ * 1) 固定表单回显优先读取 agent.json.env,保证已保存配置优先;
3596
+ * 2) agent.json 未配置的字段回退到项目 .env,便于直接看到当前运行值;
3597
+ * 3) 两处都没有时展示为空字符串。
3598
+ */
3599
+ function buildAgentEnvFieldValues(envObj) {
3600
+ const out = {}
3601
+ for (let i = 0; i < agentEnvSchema.length; i++) {
3602
+ const key = agentEnvSchema[i].key
3603
+ const rawAgentValue = envObj[key]
3604
+ if (typeof rawAgentValue === 'string' && rawAgentValue.length > 0) {
3605
+ out[key] = rawAgentValue
3606
+ continue
3607
+ }
3608
+ const rawProjectValue = projectEnvFormDefaults[key]
3609
+ out[key] = typeof rawProjectValue === 'string' ? rawProjectValue : ''
3610
+ }
3611
+ return out
3612
+ }
3613
+
3614
+ /**
3615
+ * 业务逻辑:
3616
+ * 1) 环境变量固定表单必须由后端 schema 驱动,避免前后端字段漂移;
3617
+ * 2) 仅接收 5 个固定分组字段,其他分组直接忽略;
3618
+ * 3) 字段顺序保持后端下发顺序,确保展示和保存稳定可预期。
3619
+ */
3620
+ function normalizeAgentEnvSchema(rawSchema) {
3621
+ if (!Array.isArray(rawSchema)) {
3622
+ return []
3623
+ }
3624
+ const out = []
3625
+ for (let i = 0; i < rawSchema.length; i++) {
3626
+ const item = rawSchema[i]
3627
+ if (!item || typeof item !== 'object') continue
3628
+ const key = typeof item.key === 'string' ? item.key.trim() : ''
3629
+ const group = typeof item.group === 'string' ? item.group.trim() : ''
3630
+ if (!key) continue
3631
+ if (AGENT_ENV_GROUP_ORDER.indexOf(group) < 0) continue
3632
+ out.push({
3633
+ key: key,
3634
+ group: group,
3635
+ label: typeof item.label === 'string' && item.label.trim() ? item.label.trim() : key,
3636
+ placeholder:
3637
+ typeof item.placeholder === 'string' ? item.placeholder : '',
3638
+ })
3639
+ }
3640
+ return out
3641
+ }
3642
+
3643
+ /**
3644
+ * 业务逻辑:
3645
+ * 1) 固定表单只允许 schema 内字段,所有输入值写入内存 map;
3646
+ * 2) 保存前由 collectAndValidateAgentEnvDraft 统一做 trim 与空值过滤;
3647
+ * 3) 渲染时按分组插入标题,保证 UI 与需求分区一致。
3648
+ */
3649
+ function renderAgentEnvList() {
3650
+ if (!agentEnvList) return
3651
+ agentEnvList.innerHTML = ''
3652
+ if (!agentEnvSchema.length) {
3653
+ const empty = document.createElement('div')
3654
+ empty.className = 'tool-files-empty'
3655
+ empty.textContent = '未加载到环境变量表单配置'
3656
+ agentEnvList.appendChild(empty)
3657
+ return
3658
+ }
3659
+ const schemaByGroup = {}
3660
+ for (let i = 0; i < AGENT_ENV_GROUP_ORDER.length; i++) {
3661
+ schemaByGroup[AGENT_ENV_GROUP_ORDER[i]] = []
3662
+ }
3663
+ for (let i = 0; i < agentEnvSchema.length; i++) {
3664
+ const field = agentEnvSchema[i]
3665
+ schemaByGroup[field.group].push(field)
3666
+ }
3667
+ const frag = document.createDocumentFragment()
3668
+ for (let i = 0; i < AGENT_ENV_GROUP_ORDER.length; i++) {
3669
+ const group = AGENT_ENV_GROUP_ORDER[i]
3670
+ const fields = schemaByGroup[group]
3671
+ if (!fields || !fields.length) continue
3672
+ const section = document.createElement('section')
3673
+ section.className = 'agent-env-fixed-section'
3674
+ const title = document.createElement('h3')
3675
+ title.className = 'agent-env-fixed-section-title'
3676
+ title.textContent = AGENT_ENV_GROUP_LABELS[group]
3677
+ section.appendChild(title)
3678
+ for (let j = 0; j < fields.length; j++) {
3679
+ const field = fields[j]
3680
+ const row = document.createElement('div')
3681
+ row.className = 'agent-env-fixed-row'
3682
+ const label = document.createElement('label')
3683
+ label.className = 'agent-env-fixed-label'
3684
+ label.setAttribute('for', 'agentEnvField_' + field.key)
3685
+ label.textContent = field.label
3686
+ const inputId = 'agentEnvField_' + field.key
3687
+ let input
3688
+ if (field.key === 'LLM_PROVIDER') {
3689
+ /**
3690
+ * 业务逻辑:模型类型是受控枚举,使用下拉框约束可选值,
3691
+ * 避免手输导致 provider 非法。
3692
+ */
3693
+ const select = document.createElement('select')
3694
+ select.id = inputId
3695
+ select.className = 'agent-env-kv-input'
3696
+ const providerOptions = [
3697
+ { value: '', label: '请选择' },
3698
+ { value: 'anthropic', label: 'anthropic' },
3699
+ { value: 'openai', label: 'openai' },
3700
+ ]
3701
+ for (let k = 0; k < providerOptions.length; k++) {
3702
+ const option = document.createElement('option')
3703
+ option.value = providerOptions[k].value
3704
+ option.textContent = providerOptions[k].label
3705
+ select.appendChild(option)
3706
+ }
3707
+ const currentValue =
3708
+ typeof agentEnvFieldValues[field.key] === 'string'
3709
+ ? agentEnvFieldValues[field.key]
3710
+ : ''
3711
+ select.value = currentValue === 'anthropic' || currentValue === 'openai'
3712
+ ? currentValue
3713
+ : ''
3714
+ select.addEventListener('change', function () {
3715
+ agentEnvFieldValues[field.key] = select.value
3716
+ })
3717
+ input = select
3718
+ } else {
3719
+ const textInput = document.createElement('input')
3720
+ textInput.id = inputId
3721
+ textInput.type = 'text'
3722
+ textInput.spellcheck = false
3723
+ textInput.className = 'agent-env-kv-input'
3724
+ textInput.placeholder = field.placeholder || ''
3725
+ textInput.value = typeof agentEnvFieldValues[field.key] === 'string'
3726
+ ? agentEnvFieldValues[field.key]
3727
+ : ''
3728
+ textInput.addEventListener('input', function () {
3729
+ agentEnvFieldValues[field.key] = textInput.value
3730
+ })
3731
+ input = textInput
3732
+ }
3733
+ row.appendChild(label)
3734
+ row.appendChild(input)
3735
+ section.appendChild(row)
3736
+ }
3737
+ frag.appendChild(section)
3738
+ }
3739
+ agentEnvList.appendChild(frag)
3740
+ }
3741
+
3534
3742
  /** 新 session 默认模型:agent.json.env → agent.json.model → 项目 .env */
3535
3743
  function readDefaultLlmModel() {
3536
3744
  const fromAgentEnv = readAgentEnvString(LLM_MODEL_KEY)
@@ -3541,6 +3749,58 @@
3541
3749
  return projectEnvDefaults.LLM_MODEL || ''
3542
3750
  }
3543
3751
 
3752
+ /**
3753
+ * 业务语义:权限模式回显与实际运行来源保持一致:
3754
+ * 1) 优先 agent.json.env 的 WS_PERMISSION_MODE;
3755
+ * 2) 再看项目 .env 的 WS_PERMISSION_MODE;
3756
+ * 3) 两处都未配置时使用 default。
3757
+ * 非法值不兜底,直接抛错暴露配置错误。
3758
+ */
3759
+ function readDefaultWsPermissionMode() {
3760
+ const fromAgentEnv = readAgentEnvString(WS_PERMISSION_MODE_KEY)
3761
+ if (fromAgentEnv) {
3762
+ const v = normalizePermissionMode(fromAgentEnv)
3763
+ if (!v) {
3764
+ throw new Error('WS_PERMISSION_MODE 配置非法: ' + fromAgentEnv)
3765
+ }
3766
+ return v
3767
+ }
3768
+ const fromProjectEnv =
3769
+ typeof projectEnvDefaults.WS_PERMISSION_MODE === 'string'
3770
+ ? projectEnvDefaults.WS_PERMISSION_MODE.trim()
3771
+ : ''
3772
+ if (fromProjectEnv) {
3773
+ const v = normalizePermissionMode(fromProjectEnv)
3774
+ if (!v) {
3775
+ throw new Error('WS_PERMISSION_MODE 配置非法: ' + fromProjectEnv)
3776
+ }
3777
+ return v
3778
+ }
3779
+ return 'default'
3780
+ }
3781
+
3782
+ /**
3783
+ * 业务语义:自动/手动回显必须对齐真实环境来源:
3784
+ * 1) 优先 agent.json.env 的 WS_ALLOW_TOOL_ASK;
3785
+ * 2) 再看项目 .env 的 WS_ALLOW_TOOL_ASK;
3786
+ * 3) 两处都未配置时使用手动(0)。
3787
+ * 若出现非 0/1 的显式配置,直接抛错暴露配置问题,避免 UI 与运行时语义偏差。
3788
+ */
3789
+ function readDefaultWsAllowToolAsk() {
3790
+ const fromAgentEnv = readAgentEnvString(WS_ALLOW_TOOL_ASK_KEY)
3791
+ if (fromAgentEnv) {
3792
+ return normalizeWsAllowToolAsk(fromAgentEnv)
3793
+ }
3794
+ const fromProjectEnv =
3795
+ typeof projectEnvDefaults.WS_ALLOW_TOOL_ASK === 'string'
3796
+ ? projectEnvDefaults.WS_ALLOW_TOOL_ASK.trim()
3797
+ : ''
3798
+ if (fromProjectEnv) {
3799
+ return normalizeWsAllowToolAsk(fromProjectEnv)
3800
+ }
3801
+ return '0'
3802
+ }
3803
+
3544
3804
  /**
3545
3805
  * 业务语义:三个会话级配置的初始化顺序必须稳定:
3546
3806
  * 1) 先读取 agent.json.env / agent.json.model;
@@ -3551,7 +3811,8 @@
3551
3811
  return {
3552
3812
  model: readDefaultLlmModel(),
3553
3813
  env: {
3554
- [WS_PERMISSION_MODE_KEY]: 'default',
3814
+ [WS_PERMISSION_MODE_KEY]: readDefaultWsPermissionMode(),
3815
+ [WS_ALLOW_TOOL_ASK_KEY]: readDefaultWsAllowToolAsk(),
3555
3816
  },
3556
3817
  }
3557
3818
  }
@@ -4527,19 +4788,15 @@
4527
4788
 
4528
4789
  /** 构建 agent 列表 API 地址,用于侧栏头像切换 workspace agent */
4529
4790
  function buildAgentsListUrl() {
4530
- const u = new URL('/api/agents', httpOriginForApi() + '/')
4531
- u.searchParams.set('agent', currentAgentParam())
4532
- return u.toString()
4791
+ return new URL('/api/agents', httpOriginForApi() + '/').toString()
4533
4792
  }
4534
4793
 
4535
4794
  /** 构建单个 agent 的 REST 地址,用于详情/修改/删除 */
4536
4795
  function buildAgentItemUrl(id) {
4537
- const u = new URL(
4796
+ return new URL(
4538
4797
  '/api/agents/' + encodeURIComponent(String(id || '').trim()),
4539
4798
  httpOriginForApi() + '/'
4540
- )
4541
- u.searchParams.set('agent', currentAgentParam())
4542
- return u.toString()
4799
+ ).toString()
4543
4800
  }
4544
4801
 
4545
4802
  /** 关闭 agent 切换下拉,恢复头像按钮状态 */
@@ -6152,9 +6409,10 @@
6152
6409
 
6153
6410
  function buildAgentApiUrl() {
6154
6411
  const id = currentAgentParam()
6155
- const u = new URL('/api/agents/' + encodeURIComponent(id), httpOriginForApi() + '/')
6156
- u.searchParams.set('agent', id)
6157
- return u.toString()
6412
+ return new URL(
6413
+ '/api/agents/' + encodeURIComponent(id),
6414
+ httpOriginForApi() + '/',
6415
+ ).toString()
6158
6416
  }
6159
6417
 
6160
6418
  function buildSessionOverridesApiUrl(sid) {
@@ -6436,6 +6694,33 @@
6436
6694
  env[WS_PERMISSION_MODE_KEY] = selected || 'default'
6437
6695
  }
6438
6696
 
6697
+ function normalizeWsAllowToolAsk(value) {
6698
+ const v = String(value || '').trim()
6699
+ if (!WS_ALLOW_TOOL_ASK_ALLOWED.has(v)) {
6700
+ throw new Error('WS_ALLOW_TOOL_ASK 只允许 0/1')
6701
+ }
6702
+ return v
6703
+ }
6704
+
6705
+ /**
6706
+ * 业务语义:工具确认模式是 session 级强约束配置,UI 必须与 overrides 中的值一一对应。
6707
+ * 自动=1(不走 ask);手动=0(走 ask)。不做隐式兜底映射,非法值直接抛错暴露问题。
6708
+ */
6709
+ function syncToolAskSelectFromState() {
6710
+ if (!composerToolAskSelect) return
6711
+ const env = getSessionOverrideEnv()
6712
+ const raw = typeof env[WS_ALLOW_TOOL_ASK_KEY] === 'string'
6713
+ ? env[WS_ALLOW_TOOL_ASK_KEY]
6714
+ : '0'
6715
+ composerToolAskSelect.value = normalizeWsAllowToolAsk(raw)
6716
+ }
6717
+
6718
+ function syncToolAskStateFromSelect() {
6719
+ if (!composerToolAskSelect) return
6720
+ const env = getSessionOverrideEnv()
6721
+ env[WS_ALLOW_TOOL_ASK_KEY] = normalizeWsAllowToolAsk(composerToolAskSelect.value)
6722
+ }
6723
+
6439
6724
  function initComposerModelUI() {
6440
6725
  if (!composerModelInput || !composerModelSuggestions) return
6441
6726
 
@@ -6514,6 +6799,12 @@
6514
6799
  void persistSessionOverridesAndRestart({ silent: true })
6515
6800
  })
6516
6801
  }
6802
+ if (composerToolAskSelect) {
6803
+ composerToolAskSelect.addEventListener('change', function () {
6804
+ syncToolAskStateFromSelect()
6805
+ void persistSessionOverridesAndRestart({ silent: true })
6806
+ })
6807
+ }
6517
6808
  }
6518
6809
 
6519
6810
  function syncModelInputFromState() {
@@ -6528,6 +6819,7 @@
6528
6819
  }
6529
6820
  syncModelStateToEnv()
6530
6821
  syncPermissionModeSelectFromState()
6822
+ syncToolAskSelectFromState()
6531
6823
  }
6532
6824
 
6533
6825
  function resetSessionOverrideStateToDefault() {
@@ -6572,6 +6864,12 @@
6572
6864
  [WS_PERMISSION_MODE_KEY]:
6573
6865
  normalizePermissionMode(ovEnv[WS_PERMISSION_MODE_KEY]) ||
6574
6866
  defaults.env[WS_PERMISSION_MODE_KEY],
6867
+ [WS_ALLOW_TOOL_ASK_KEY]:
6868
+ normalizeWsAllowToolAsk(
6869
+ typeof ovEnv[WS_ALLOW_TOOL_ASK_KEY] === 'string'
6870
+ ? ovEnv[WS_ALLOW_TOOL_ASK_KEY]
6871
+ : defaults.env[WS_ALLOW_TOOL_ASK_KEY],
6872
+ ),
6575
6873
  },
6576
6874
  }
6577
6875
  renderComposerFromSessionOverrideState()
@@ -6592,6 +6890,12 @@
6592
6890
  patchEnv[WS_PERMISSION_MODE_KEY] =
6593
6891
  normalizePermissionMode(env[WS_PERMISSION_MODE_KEY]) ||
6594
6892
  defaults.env[WS_PERMISSION_MODE_KEY]
6893
+ patchEnv[WS_ALLOW_TOOL_ASK_KEY] =
6894
+ normalizeWsAllowToolAsk(
6895
+ typeof env[WS_ALLOW_TOOL_ASK_KEY] === 'string'
6896
+ ? env[WS_ALLOW_TOOL_ASK_KEY]
6897
+ : defaults.env[WS_ALLOW_TOOL_ASK_KEY],
6898
+ )
6595
6899
  return {
6596
6900
  env: patchEnv,
6597
6901
  }
@@ -6622,15 +6926,22 @@
6622
6926
  window.alert('保存 session overrides 失败:' + (body.message || body.error || r.statusText))
6623
6927
  return
6624
6928
  }
6625
- restartCurrentSessionSubprocessWithReason('persist-session-overrides')
6929
+ if (socket && socket.readyState === WebSocket.OPEN) {
6930
+ socket.send(
6931
+ JSON.stringify({
6932
+ type: 'reloadSessionOverrides',
6933
+ reason: 'persist-session-overrides',
6934
+ }),
6935
+ )
6936
+ }
6626
6937
  if (silent) {
6627
6938
  logLine('session-overrides', 'saved for ' + sid)
6628
6939
  setStatus(
6629
- 'session overrides 已保存,已重启当前 session 子进程',
6940
+ 'session overrides 已保存,当前 session 配置已热更新',
6630
6941
  serverReady ? 'ready' : '',
6631
6942
  )
6632
6943
  } else {
6633
- window.alert('已保存并已重启当前 session 子进程。')
6944
+ window.alert('已保存并触发当前 session 热更新。')
6634
6945
  }
6635
6946
  } catch (e) {
6636
6947
  window.alert('保存 session overrides 失败:' + e)
@@ -6656,9 +6967,16 @@
6656
6967
  window.alert('刷写 pending session overrides 失败:' + (body.message || body.error || r.statusText))
6657
6968
  return
6658
6969
  }
6659
- restartCurrentSessionSubprocessWithReason('persist-session-overrides-pending')
6970
+ if (socket && socket.readyState === WebSocket.OPEN) {
6971
+ socket.send(
6972
+ JSON.stringify({
6973
+ type: 'reloadSessionOverrides',
6974
+ reason: 'persist-session-overrides-pending',
6975
+ }),
6976
+ )
6977
+ }
6660
6978
  logLine('session-overrides', 'flushed pending for ' + sid)
6661
- setStatus('session overrides 已落盘并重启子进程', serverReady ? 'ready' : '')
6979
+ setStatus('session overrides 已落盘并触发热更新', serverReady ? 'ready' : '')
6662
6980
  } catch (e) {
6663
6981
  logLine('session-overrides', 'flush pending failed: ' + String(e))
6664
6982
  }
@@ -8413,59 +8731,6 @@
8413
8731
  )
8414
8732
  }
8415
8733
 
8416
- function renderAgentEnvList() {
8417
- if (!agentEnvList) return
8418
- agentEnvList.innerHTML = ''
8419
- if (!agentEnvDraft.length) {
8420
- const empty = document.createElement('div')
8421
- empty.className = 'tool-files-empty'
8422
- empty.textContent = '(暂无变量,点击“新增变量”)'
8423
- agentEnvList.appendChild(empty)
8424
- return
8425
- }
8426
- const frag = document.createDocumentFragment()
8427
- for (let i = 0; i < agentEnvDraft.length; i++) {
8428
- const rowData = agentEnvDraft[i]
8429
- const row = document.createElement('div')
8430
- row.className = 'agent-env-kv-row'
8431
-
8432
- const keyInput = document.createElement('input')
8433
- keyInput.type = 'text'
8434
- keyInput.value = rowData.key
8435
- keyInput.placeholder = '例如 API_KEY'
8436
- keyInput.spellcheck = false
8437
- keyInput.className = 'agent-env-kv-input'
8438
- keyInput.addEventListener('input', function () {
8439
- rowData.key = keyInput.value
8440
- })
8441
-
8442
- const valInput = document.createElement('input')
8443
- valInput.type = 'text'
8444
- valInput.value = rowData.value
8445
- valInput.placeholder = 'value'
8446
- valInput.spellcheck = false
8447
- valInput.className = 'agent-env-kv-input'
8448
- valInput.addEventListener('input', function () {
8449
- rowData.value = valInput.value
8450
- })
8451
-
8452
- const delBtn = document.createElement('button')
8453
- delBtn.type = 'button'
8454
- delBtn.className = 'btn-agent-md-cancel'
8455
- delBtn.textContent = '删除'
8456
- delBtn.addEventListener('click', function () {
8457
- agentEnvDraft.splice(i, 1)
8458
- renderAgentEnvList()
8459
- })
8460
-
8461
- row.appendChild(keyInput)
8462
- row.appendChild(valInput)
8463
- row.appendChild(delBtn)
8464
- frag.appendChild(row)
8465
- }
8466
- agentEnvList.appendChild(frag)
8467
- }
8468
-
8469
8734
  function loadAgentEnvAgentMdFromApi() {
8470
8735
  if (!agentEnvAgentMdEditor) {
8471
8736
  return Promise.resolve()
@@ -8521,12 +8786,11 @@
8521
8786
  } else {
8522
8787
  agentEnvActiveTab = 'env'
8523
8788
  }
8524
- const envObj = agentConfigState.env && typeof agentConfigState.env === 'object' ? agentConfigState.env : {}
8525
- agentEnvDraft = Object.keys(envObj)
8526
- .sort()
8527
- .map(function (k) {
8528
- return { key: k, value: String(envObj[k]) }
8529
- })
8789
+ const envObj =
8790
+ agentConfigState.env && typeof agentConfigState.env === 'object'
8791
+ ? agentConfigState.env
8792
+ : {}
8793
+ agentEnvFieldValues = buildAgentEnvFieldValues(envObj)
8530
8794
  if (agentEnvModalCard) {
8531
8795
  agentEnvModalCard.setAttribute('aria-label', '环境变量、编辑 agent.md 与设置')
8532
8796
  agentEnvModalCard.removeAttribute('aria-labelledby')
@@ -8535,6 +8799,16 @@
8535
8799
  agentEnvSessionTabStrip.setAttribute('aria-label', '环境变量、编辑 agent.md、设置')
8536
8800
  agentEnvSessionTabStrip.removeAttribute('hidden')
8537
8801
  }
8802
+ if (!agentEnvSchema.length) {
8803
+ void loadProjectEnvDefaults().then(function () {
8804
+ const latestEnvObj =
8805
+ agentConfigState.env && typeof agentConfigState.env === 'object'
8806
+ ? agentConfigState.env
8807
+ : {}
8808
+ agentEnvFieldValues = buildAgentEnvFieldValues(latestEnvObj)
8809
+ renderAgentEnvList()
8810
+ })
8811
+ }
8538
8812
  renderAgentEnvHeaderTabs()
8539
8813
  syncAgentEnvTabPanels()
8540
8814
  agentEnvModal.classList.add('is-open')
@@ -8675,21 +8949,14 @@
8675
8949
 
8676
8950
  function collectAndValidateAgentEnvDraft() {
8677
8951
  const out = {}
8678
- const keySet = new Set()
8679
- for (let i = 0; i < agentEnvDraft.length; i++) {
8680
- const row = agentEnvDraft[i]
8681
- const key = String(row.key || '').trim()
8682
- if (!key) {
8683
- continue
8684
- }
8685
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
8686
- return { ok: false, message: '环境变量名不合法:' + key + '(仅允许字母/数字/下划线,且不能数字开头)' }
8687
- }
8688
- if (keySet.has(key)) {
8689
- return { ok: false, message: '存在重复环境变量名:' + key }
8690
- }
8691
- keySet.add(key)
8692
- out[key] = String(row.value ?? '')
8952
+ for (let i = 0; i < agentEnvSchema.length; i++) {
8953
+ const field = agentEnvSchema[i]
8954
+ const raw = Object.prototype.hasOwnProperty.call(agentEnvFieldValues, field.key)
8955
+ ? agentEnvFieldValues[field.key]
8956
+ : ''
8957
+ const value = String(raw ?? '').trim()
8958
+ if (!value) continue
8959
+ out[field.key] = value
8693
8960
  }
8694
8961
  return { ok: true, env: out }
8695
8962
  }
@@ -9090,12 +9357,6 @@
9090
9357
  if (agentEnvModalBackdrop) {
9091
9358
  agentEnvModalBackdrop.addEventListener('click', closeAgentEnvModal)
9092
9359
  }
9093
- if (btnAddAgentEnvRow) {
9094
- btnAddAgentEnvRow.addEventListener('click', function () {
9095
- agentEnvDraft.push({ key: '', value: '' })
9096
- renderAgentEnvList()
9097
- })
9098
- }
9099
9360
  if (btnAgentEnvSave) {
9100
9361
  btnAgentEnvSave.addEventListener('click', function () {
9101
9362
  void saveAgentEnvAndReconnect()
@@ -0,0 +1,18 @@
1
+ {
2
+ "mcpServers": {
3
+ "tuitui": {
4
+ "command": "node",
5
+ "args": ["${WS_PROJECT_ROOT}/mcps-runtime/claude-tuitui/server/boot.mjs"],
6
+ "env": {
7
+ "TUITUI_APP_ID": "${TUITUI_APP_ID}",
8
+ "TUITUI_APP_SECRET": "${TUITUI_APP_SECRET}",
9
+ "TUITUI_STATE_DIR": "${WS_PROJECT_ROOT}/mcps-runtime/claude-tuitui/.state",
10
+ "TUITUI_DM_POLICY": "allowlist",
11
+ "TUITUI_GROUP_POLICY": "allowlist",
12
+ "TUITUI_REQUIRE_MENTION": "true",
13
+ "TUITUI_CHANNEL_CONTEXT": "thread",
14
+ "TUITUI_AUTO_REPLY": "true"
15
+ }
16
+ }
17
+ }
18
+ }