@auto-ai/agent 2.1.121 → 2.1.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.env.example +2 -1
  2. package/dist/404/index.html +1 -1
  3. package/dist/404.html +1 -1
  4. package/dist/_next/static/chunks/19aa69c07b3642d5.js +1 -0
  5. package/dist/_next/static/chunks/24459c7365a2b28b.js +1 -0
  6. package/dist/_next/static/chunks/{d3e2070a86378cfb.js → 33eeef286c328da0.js} +1 -1
  7. package/dist/_next/static/chunks/3407244006d6a98a.js +1 -0
  8. package/dist/_next/static/chunks/566e152e480b267a.js +1 -0
  9. package/dist/_next/static/chunks/6ad4268160f361a6.js +1 -0
  10. package/dist/_next/static/chunks/{d9a278a2a26e8ee5.js → 6f9a48a7f83e669a.js} +1 -1
  11. package/dist/_next/static/chunks/7658b5b9c2865eb1.js +1 -0
  12. package/dist/_next/static/chunks/7a5fd448b280dd64.js +1 -0
  13. package/dist/_next/static/chunks/{fa677aa06f1c0539.js → 98f0c5604e839ba2.js} +1 -1
  14. package/dist/_next/static/chunks/a38eccf0bb5ca1e9.js +1 -0
  15. package/dist/_next/static/chunks/b29a354245bfc377.css +1 -0
  16. package/dist/_next/static/chunks/b76778c5811ef7a1.js +1 -0
  17. package/dist/_next/static/chunks/c2bed9d5fa7be4bd.js +1 -0
  18. package/dist/_next/static/chunks/c5a4977aae6a008c.js +1 -0
  19. package/dist/_next/static/chunks/e7bd145455a541af.css +4 -0
  20. package/dist/_next/static/chunks/f2bb685629307d4d.js +1 -0
  21. package/dist/_next/static/chunks/f7e71ce1c236f806.js +1 -0
  22. package/dist/_next/static/chunks/{67acc15b8a448e1a.js → fc4be3bcf72559a0.js} +1 -1
  23. package/dist/index.html +1 -1
  24. package/dist/index.txt +16 -16
  25. package/dist/manage/about/index.html +2 -2
  26. package/dist/manage/about/index.txt +19 -19
  27. package/dist/manage/add-account/basic/index.html +2 -2
  28. package/dist/manage/add-account/basic/index.txt +21 -21
  29. package/dist/manage/add-account/index.html +2 -2
  30. package/dist/manage/add-account/index.txt +21 -21
  31. package/dist/manage/agent-teams/index.html +2 -2
  32. package/dist/manage/agent-teams/index.txt +21 -21
  33. package/dist/manage/env/index.html +2 -2
  34. package/dist/manage/env/index.txt +21 -21
  35. package/dist/manage/general/index.html +2 -2
  36. package/dist/manage/general/index.txt +19 -19
  37. package/dist/manage/index.html +1 -1
  38. package/dist/manage/index.txt +16 -16
  39. package/dist/manage/mcp/index.html +2 -2
  40. package/dist/manage/mcp/index.txt +21 -21
  41. package/dist/manage/permissions/index.html +2 -2
  42. package/dist/manage/permissions/index.txt +19 -19
  43. package/dist/manage/skills/index.html +2 -2
  44. package/dist/manage/skills/index.txt +21 -21
  45. package/dist/manage/task/index.html +2 -2
  46. package/dist/manage/task/index.txt +19 -19
  47. package/dist/manage/teams/index.html +2 -2
  48. package/dist/manage/teams/index.txt +19 -19
  49. package/dist/manage/tools/index.html +2 -2
  50. package/dist/manage/tools/index.txt +21 -21
  51. package/dist/ws-test.html +90 -64
  52. package/mcps-runtime/claude-geelib-channel/.mcp.json +18 -0
  53. package/mcps-runtime/claude-geelib-channel/server/boot.mjs +32 -0
  54. package/mcps-runtime/claude-geelib-channel/server/geelib-api.mjs +192 -0
  55. package/mcps-runtime/claude-geelib-channel/server/geelib-auth.mjs +147 -0
  56. package/mcps-runtime/claude-geelib-channel/server/geelib-client.mjs +63 -0
  57. package/mcps-runtime/claude-geelib-channel/server/index.mjs +451 -0
  58. package/mcps-runtime/claude-geelib-channel/server/poll.mjs +457 -0
  59. package/mcps-runtime/claude-geelib-channel/server/state.mjs +216 -0
  60. package/package.json +6 -6
  61. package/tools-runtime/git-tool/index.cjs +51 -3
  62. package/tools-runtime/git-tool/src/workspace.ts +101 -4
  63. package/dist/_next/static/chunks/249606605eed1993.js +0 -1
  64. package/dist/_next/static/chunks/424d3f3f176d43d7.js +0 -1
  65. package/dist/_next/static/chunks/4302af16b34bf750.js +0 -1
  66. package/dist/_next/static/chunks/4a073924e062c459.css +0 -1
  67. package/dist/_next/static/chunks/6209b42cbfcf288a.js +0 -1
  68. package/dist/_next/static/chunks/83bf6e4bc0aad48b.css +0 -4
  69. package/dist/_next/static/chunks/8b34d657c6bbb8df.js +0 -1
  70. package/dist/_next/static/chunks/904ad158fd721117.js +0 -1
  71. package/dist/_next/static/chunks/99063f8d79783308.js +0 -1
  72. package/dist/_next/static/chunks/9cd912a1967f10df.js +0 -1
  73. package/dist/_next/static/chunks/c52dcbe269bb02d0.js +0 -1
  74. package/dist/_next/static/chunks/d01957b1055b357a.js +0 -1
  75. package/dist/_next/static/chunks/dacc94cf10289334.js +0 -1
  76. package/dist/_next/static/chunks/fff9d250e482f00e.js +0 -1
  77. package/skills-runtime/geelib/SKILL.md +0 -229
  78. package/skills-runtime/geelib/geelib-poller-state.json +0 -17
  79. package/skills-runtime/geelib/geelib.mjs +0 -762
  80. /package/dist/_next/static/{Uk25KzeENFNl8wcmEtL4K → pVB1xZbEW09ksdJlV12Zn}/_buildManifest.js +0 -0
  81. /package/dist/_next/static/{Uk25KzeENFNl8wcmEtL4K → pVB1xZbEW09ksdJlV12Zn}/_clientMiddlewareManifest.json +0 -0
  82. /package/dist/_next/static/{Uk25KzeENFNl8wcmEtL4K → pVB1xZbEW09ksdJlV12Zn}/_ssgManifest.js +0 -0
package/dist/ws-test.html CHANGED
@@ -2901,7 +2901,7 @@
2901
2901
  </button>
2902
2902
  </div>
2903
2903
  <p id="mcpItemConfigHint" class="tool-files-sub" style="margin: 0 1rem 0.5rem; line-height: 1.4">
2904
- 标准 mcp.json 单条 server 配置(与 Cursor mcp.json 相同):<code>{ "command": "...", "args": [...], "env": {...} }</code>。保存后写入 agent .mcp.json <code>mcpServers.&lt;serverName&gt;</code>。
2904
+ 标准 mcp.json 单条 server 配置(与 Cursor mcp.json 相同):<code>{ "command": "...", "args": [...], "env": {...} }</code>。runtime MCP 保存到当前包目录 <code>.mcp.json</code>;remote MCP 保存到 agent <code>.mcp.json</code>。
2905
2905
  </p>
2906
2906
  <textarea id="mcpItemConfigEditor" class="agent-md-editor" spellcheck="false" placeholder="加载中…"></textarea>
2907
2907
  <div class="agent-md-modal-foot">
@@ -3228,7 +3228,6 @@
3228
3228
  <div class="schedule-manage-toolbar">
3229
3229
  <button type="button" class="btn-system-settings" id="btnScheduleRefresh">刷新</button>
3230
3230
  <button type="button" class="btn-system-settings" id="btnScheduleCreateToggle">新建任务</button>
3231
- <button type="button" class="btn-system-settings" id="btnScheduleGeelibPreset">Geelib 巡检</button>
3232
3231
  </div>
3233
3232
  <div id="scheduleCreateForm" class="schedule-create-form" hidden>
3234
3233
  <div class="schedule-create-row">
@@ -3403,7 +3402,6 @@
3403
3402
  const btnScheduleManageModalClose = $('btnScheduleManageModalClose')
3404
3403
  const btnScheduleRefresh = $('btnScheduleRefresh')
3405
3404
  const btnScheduleCreateToggle = $('btnScheduleCreateToggle')
3406
- const btnScheduleGeelibPreset = $('btnScheduleGeelibPreset')
3407
3405
  const scheduleCreateForm = $('scheduleCreateForm')
3408
3406
  const scheduleSessionSelect = $('scheduleSessionSelect')
3409
3407
  const schedulePromptInput = $('schedulePromptInput')
@@ -3964,9 +3962,17 @@
3964
3962
  }
3965
3963
  const SHOW_TEAMMATE_EXIT_LIFECYCLE = true
3966
3964
  let renderScheduled = false
3965
+ /** HTTP 历史回放进行中:暂存实时 conversation.delta,避免与回放竞态写乱 transcript 下标 */
3966
+ let sessionReplayInProgress = false
3967
+ const pendingConversationDeltaEnvelopes = []
3967
3968
 
3968
3969
  function sourceAgentPayloadToText(payload) {
3969
3970
  if (!payload || typeof payload !== 'object') return ''
3971
+ if (payload.state === 'console') {
3972
+ const consoleLine =
3973
+ typeof payload.message === 'string' ? payload.message.trim() : ''
3974
+ if (consoleLine) return consoleLine
3975
+ }
3970
3976
  const t = typeof payload.type === 'string' ? payload.type : 'unknown'
3971
3977
  if (t === 'turn.terminal') {
3972
3978
  const resultText = typeof payload.resultText === 'string' ? payload.resultText : ''
@@ -4058,6 +4064,44 @@
4058
4064
  return buffer
4059
4065
  }
4060
4066
 
4067
+ /**
4068
+ * 业务语义:conversation.delta 的 index 是 transcript 全局下标(续聊时可能远大于当前 buffer 长度)。
4069
+ * 必须把槽位扩到 index 再写入,禁止把 index=206 的消息 push 到 buffer[0]。
4070
+ */
4071
+ function padRunBufferThroughIndex(runBuffer, index) {
4072
+ while (runBuffer.length <= index) {
4073
+ runBuffer.push(null)
4074
+ }
4075
+ }
4076
+
4077
+ function applyConversationDeltaToRunBuffer(runBuffer, action, index, runMessage) {
4078
+ if (index < 0) return
4079
+ if (action === 'message_removed') {
4080
+ if (index < runBuffer.length) {
4081
+ runBuffer.splice(index, 1)
4082
+ }
4083
+ return
4084
+ }
4085
+ if (action === 'message_added') {
4086
+ if (index <= runBuffer.length) {
4087
+ runBuffer.splice(index, 0, runMessage)
4088
+ } else {
4089
+ padRunBufferThroughIndex(runBuffer, index - 1)
4090
+ runBuffer.push(runMessage)
4091
+ }
4092
+ return
4093
+ }
4094
+ padRunBufferThroughIndex(runBuffer, index)
4095
+ runBuffer[index] = runMessage
4096
+ }
4097
+
4098
+ function flushPendingConversationDeltas() {
4099
+ const pending = pendingConversationDeltaEnvelopes.splice(0)
4100
+ for (let i = 0; i < pending.length; i++) {
4101
+ applyWsEnvelopeV3(pending[i])
4102
+ }
4103
+ }
4104
+
4061
4105
  function isNearBottom(el) {
4062
4106
  if (!el) return true
4063
4107
  const gap = el.scrollHeight - (el.scrollTop + el.clientHeight)
@@ -4298,12 +4342,20 @@
4298
4342
  if (t === 'user') {
4299
4343
  const body = extractMessageBody(m)
4300
4344
  if (!body.trim()) continue
4345
+ const isToolResult =
4346
+ Array.isArray(m.message && m.message.content) &&
4347
+ m.message.content.some(function (block) {
4348
+ return block && block.type === 'tool_result'
4349
+ })
4301
4350
  const div = document.createElement('div')
4302
4351
  div.className = 'chat-msg chat-msg-user'
4303
4352
  applyScheduledMsgClass(div, isScheduled)
4304
4353
  const lab = document.createElement('div')
4305
4354
  lab.className = 'chat-msg-label'
4306
- lab.textContent = formatScheduledLabel('User · agent=' + messageAgent, isScheduled)
4355
+ lab.textContent = formatScheduledLabel(
4356
+ (isToolResult ? 'Tool Result' : 'User') + ' · agent=' + messageAgent,
4357
+ isScheduled,
4358
+ )
4307
4359
  const bodyEl = document.createElement('div')
4308
4360
  bodyEl.className = 'chat-user-body'
4309
4361
  setMessageBodyEl(bodyEl, body)
@@ -4314,12 +4366,20 @@
4314
4366
  } else if (t === 'assistant') {
4315
4367
  const body = extractMessageBody(m)
4316
4368
  if (!body.trim()) continue
4369
+ const isToolCall =
4370
+ Array.isArray(m.message && m.message.content) &&
4371
+ m.message.content.some(function (block) {
4372
+ return block && block.type === 'tool_use'
4373
+ })
4317
4374
  const div = document.createElement('div')
4318
4375
  div.className = 'chat-msg chat-msg-assistant'
4319
4376
  applyScheduledMsgClass(div, isScheduled)
4320
4377
  const lab = document.createElement('div')
4321
4378
  lab.className = 'chat-msg-label'
4322
- lab.textContent = formatScheduledLabel('Assistant · agent=' + messageAgent, isScheduled)
4379
+ lab.textContent = formatScheduledLabel(
4380
+ (isToolCall ? 'Tool Call' : 'Assistant') + ' · agent=' + messageAgent,
4381
+ isScheduled,
4382
+ )
4323
4383
  const md = document.createElement('div')
4324
4384
  md.className = 'chat-md'
4325
4385
  md.innerHTML = renderMd(body)
@@ -4584,6 +4644,8 @@
4584
4644
  chatState.streamByRun.clear()
4585
4645
  chatState.sourceAgentMessages = []
4586
4646
  chatState.seenEventIds.clear()
4647
+ sessionReplayInProgress = false
4648
+ pendingConversationDeltaEnvelopes.length = 0
4587
4649
  autoScrollState.mode = 'STICKY_BOTTOM'
4588
4650
  if (logEl) logEl.textContent = ''
4589
4651
  requestRenderChat()
@@ -4665,13 +4727,17 @@
4665
4727
  function applyWsEnvelopeV3(data) {
4666
4728
  if (!data || typeof data !== 'object') return false
4667
4729
  if (data.protocolVersion !== 3) return false
4730
+ const runId = typeof data.runId === 'string' ? data.runId : 'main'
4731
+ const payload = data.data && typeof data.data === 'object' ? data.data : {}
4732
+ const type = typeof data.type === 'string' ? data.type : 'unknown'
4733
+ if (type === 'conversation.delta' && sessionReplayInProgress) {
4734
+ pendingConversationDeltaEnvelopes.push(data)
4735
+ return true
4736
+ }
4668
4737
  if (typeof data.eventId === 'string' && data.eventId) {
4669
4738
  if (chatState.seenEventIds.has(data.eventId)) return true
4670
4739
  chatState.seenEventIds.add(data.eventId)
4671
4740
  }
4672
- const runId = typeof data.runId === 'string' ? data.runId : 'main'
4673
- const payload = data.data && typeof data.data === 'object' ? data.data : {}
4674
- const type = typeof data.type === 'string' ? data.type : 'unknown'
4675
4741
  if (type === 'conversation.delta') {
4676
4742
  const action = typeof payload.action === 'string' ? payload.action : ''
4677
4743
  const index = Number.isInteger(payload.index) ? payload.index : -1
@@ -4688,32 +4754,7 @@
4688
4754
  : '',
4689
4755
  arrivalSeq: ++chatState.eventClock,
4690
4756
  }
4691
- if (action === 'message_removed') {
4692
- if (index >= 0 && index < runBuffer.length) {
4693
- runBuffer.splice(index, 1)
4694
- }
4695
- } else if (action === 'message_added') {
4696
- if (index >= 0) {
4697
- if (index >= runBuffer.length) {
4698
- runBuffer.push(runMessage)
4699
- } else {
4700
- runBuffer.splice(index, 0, runMessage)
4701
- }
4702
- }
4703
- } else if (
4704
- action === 'message_updated' ||
4705
- action === 'tool_call' ||
4706
- action === 'tool_result' ||
4707
- action === 'message_completed'
4708
- ) {
4709
- if (index >= 0) {
4710
- if (index >= runBuffer.length) {
4711
- runBuffer.push(runMessage)
4712
- } else {
4713
- runBuffer[index] = runMessage
4714
- }
4715
- }
4716
- }
4757
+ applyConversationDeltaToRunBuffer(runBuffer, action, index, runMessage)
4717
4758
  requestRenderChat()
4718
4759
  return true
4719
4760
  }
@@ -5170,6 +5211,8 @@
5170
5211
  async function loadSessionReplayViaHttp(sessionId) {
5171
5212
  const sid = typeof sessionId === 'string' ? sessionId.trim() : ''
5172
5213
  if (!sid) return
5214
+ sessionReplayInProgress = true
5215
+ pendingConversationDeltaEnvelopes.length = 0
5173
5216
  try {
5174
5217
  const r = await fetch(buildSessionReplayUrl(sid))
5175
5218
  const data = await r.json()
@@ -5182,7 +5225,6 @@
5182
5225
  for (let i = 0; i < envelopes.length; i++) {
5183
5226
  applyWsEnvelopeV3(envelopes[i])
5184
5227
  }
5185
- requestRenderChat()
5186
5228
  logLine('session-replay-http', {
5187
5229
  sessionId: sid,
5188
5230
  messageCount: data.messageCount,
@@ -5191,6 +5233,10 @@
5191
5233
  } catch (e) {
5192
5234
  logLine('session-replay-http', String(e))
5193
5235
  setStatus('历史回放失败: ' + e, 'err')
5236
+ } finally {
5237
+ sessionReplayInProgress = false
5238
+ flushPendingConversationDeltas()
5239
+ requestRenderChat()
5194
5240
  }
5195
5241
  }
5196
5242
 
@@ -5262,20 +5308,6 @@
5262
5308
  }
5263
5309
  }
5264
5310
 
5265
- /** Geelib 定时巡检快捷模板:/geelib poll,每分钟循环 */
5266
- function applyGeelibSchedulePreset() {
5267
- if (scheduleCreateForm) scheduleCreateForm.hidden = false
5268
- populateScheduleSessionSelect()
5269
- if (schedulePromptInput) schedulePromptInput.value = '/geelib poll'
5270
- document.querySelectorAll('input[name="scheduleTriggerMode"]').forEach(function (el) {
5271
- el.checked = el.value === 'cron'
5272
- })
5273
- if (scheduleDelayRow) scheduleDelayRow.hidden = true
5274
- if (scheduleCronRow) scheduleCronRow.hidden = false
5275
- if (scheduleCronInput) scheduleCronInput.value = '* * * * *'
5276
- if (scheduleRecurringInput) scheduleRecurringInput.checked = true
5277
- }
5278
-
5279
5311
  /** 渲染定时任务表格 */
5280
5312
  function renderScheduleTaskTable(tasks, timezone) {
5281
5313
  if (!scheduleTaskTableBody || !scheduleManageEmpty) return
@@ -9026,11 +9058,6 @@
9026
9058
  if (!scheduleCreateForm.hidden) populateScheduleSessionSelect()
9027
9059
  })
9028
9060
  }
9029
- if (btnScheduleGeelibPreset) {
9030
- btnScheduleGeelibPreset.addEventListener('click', function () {
9031
- applyGeelibSchedulePreset()
9032
- })
9033
- }
9034
9061
  if (btnScheduleCreateCancel && scheduleCreateForm) {
9035
9062
  btnScheduleCreateCancel.addEventListener('click', function () {
9036
9063
  scheduleCreateForm.hidden = true
@@ -9243,16 +9270,15 @@
9243
9270
  agentEnvSessionTabStrip.setAttribute('aria-label', '环境变量、编辑 agent.md、设置')
9244
9271
  agentEnvSessionTabStrip.removeAttribute('hidden')
9245
9272
  }
9246
- if (!agentEnvSchema.length) {
9247
- void loadProjectEnvDefaults().then(function () {
9248
- const latestEnvObj =
9249
- agentConfigState.env && typeof agentConfigState.env === 'object'
9250
- ? agentConfigState.env
9251
- : {}
9252
- agentEnvFieldValues = buildAgentEnvFieldValues(latestEnvObj)
9253
- renderAgentEnvList()
9254
- })
9255
- }
9273
+ /** 每次打开设置弹窗都拉最新 schema,避免后端新增字段后页面仍用旧缓存。 */
9274
+ void loadProjectEnvDefaults().then(function () {
9275
+ const latestEnvObj =
9276
+ agentConfigState.env && typeof agentConfigState.env === 'object'
9277
+ ? agentConfigState.env
9278
+ : {}
9279
+ agentEnvFieldValues = buildAgentEnvFieldValues(latestEnvObj)
9280
+ renderAgentEnvList()
9281
+ })
9256
9282
  renderAgentEnvHeaderTabs()
9257
9283
  syncAgentEnvTabPanels()
9258
9284
  agentEnvModal.classList.add('is-open')
@@ -0,0 +1,18 @@
1
+ {
2
+ "mcpServers": {
3
+ "geelib": {
4
+ "command": "node",
5
+ "args": [
6
+ "${CLAUDE_PLUGIN_ROOT}/server/boot.mjs"
7
+ ],
8
+ "env": {
9
+ "GEELIB_USER": "${GEELIB_USER}",
10
+ "GEELIB_PASSWD": "${GEELIB_PASSWD}",
11
+ "GEELIB_BASE_URL": "${GEELIB_BASE_URL:-https://geelib.qihoo.net}",
12
+ "GEELIB_SUB_ID": "${GEELIB_SUB_ID}",
13
+ "GEELIB_STATE_DIR": "${WS_AGENTS_ROOT}/mcps-runtime/claude-geelib-channel/.state",
14
+ "GEELIB_POLL_INTERVAL_MS": "${GEELIB_POLL_INTERVAL_MS:-60000}"
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,32 @@
1
+ import { mkdirSync, appendFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { homedir } from 'node:os'
4
+
5
+ const stateDir =
6
+ process.env.GEELIB_STATE_DIR ||
7
+ join(homedir(), '.claude', 'channels', 'geelib')
8
+ const bootLog = join(stateDir, 'boot.log')
9
+ mkdirSync(dirname(bootLog), { recursive: true })
10
+
11
+ function log(line) {
12
+ appendFileSync(bootLog, `${new Date().toISOString()} ${line}\n`, {
13
+ encoding: 'utf8',
14
+ mode: 0o600,
15
+ flag: 'a',
16
+ })
17
+ }
18
+
19
+ log(`[BOOT] geelib-channel boot.mjs start pid=${process.pid}`)
20
+ log(
21
+ `[BOOT] env GEELIB_STATE_DIR=${process.env.GEELIB_STATE_DIR || ''} GEELIB_POLL_INTERVAL_MS=${process.env.GEELIB_POLL_INTERVAL_MS || ''} GEELIB_SUB_ID=${process.env.GEELIB_SUB_ID || ''}`,
22
+ )
23
+
24
+ process.on('uncaughtException', err => {
25
+ log(`[BOOT] uncaughtException ${err?.stack || err}`)
26
+ })
27
+ process.on('unhandledRejection', err => {
28
+ log(`[BOOT] unhandledRejection ${err?.stack || err}`)
29
+ })
30
+
31
+ await import('./index.mjs')
32
+ log('[BOOT] imported index.mjs successfully')
@@ -0,0 +1,192 @@
1
+ import { geelibGet, geelibPost } from './geelib-client.mjs'
2
+
3
+ const PRIORITY_MAP = {
4
+ low: 1,
5
+ medium: 2,
6
+ high: 3,
7
+ urgent: 4,
8
+ }
9
+
10
+ export async function cmdGetUserInfo() {
11
+ return geelibGet('/ones/user/userInfo', { need_share: 0 })
12
+ }
13
+
14
+ export async function cmdListProjects(query = '') {
15
+ return geelibGet('/projects/subBusMgr/getCanAccessSubList', {
16
+ query,
17
+ simple: 2,
18
+ })
19
+ }
20
+
21
+ export async function cmdGetProjectDetail(subId) {
22
+ return geelibGet('/projects/subBusMgr/getSubDetail', { sub_id: subId })
23
+ }
24
+
25
+ export async function cmdListProjectMembers(subId) {
26
+ return geelibGet('/projects/subBusMgr/getSubUserListSortByInitial', {
27
+ sub_id: subId,
28
+ })
29
+ }
30
+
31
+ export async function cmdListMatterTypes(subId) {
32
+ return geelibGet('/matter/Matter/getMatterType', {
33
+ sub_id: subId,
34
+ page: 0,
35
+ pageSize: 9999,
36
+ })
37
+ }
38
+
39
+ export async function cmdListMatterStatuses(subId, typeId) {
40
+ return geelibGet('/matter/matterType/getMatterTypeStatusList', {
41
+ sub_id: subId,
42
+ type_id: typeId,
43
+ })
44
+ }
45
+
46
+ /** 从 getMatterList 响应中解析工作项列表(GeeLib 返回 matterList)。 */
47
+ export function extractMatterList(data) {
48
+ if (!data || typeof data !== 'object') {
49
+ return []
50
+ }
51
+ if (Array.isArray(data.matterList)) {
52
+ return data.matterList
53
+ }
54
+ if (Array.isArray(data.list)) {
55
+ return data.list
56
+ }
57
+ if (Array.isArray(data.data)) {
58
+ return data.data
59
+ }
60
+ return []
61
+ }
62
+
63
+ export async function cmdListMatters(opts) {
64
+ const customContent = {}
65
+ if (opts.statusId !== undefined) {
66
+ customContent.status = [opts.statusId]
67
+ }
68
+ if (opts.assigneeId !== undefined) {
69
+ customContent.executor = [opts.assigneeId]
70
+ }
71
+ const body = {
72
+ sub_id: opts.subId,
73
+ type_id: [opts.typeId],
74
+ page: opts.page ?? 1,
75
+ pageSize: opts.pageSize ?? 20,
76
+ title: opts.keyword ?? '',
77
+ order_by: [],
78
+ customContent,
79
+ is_favor: 0,
80
+ simple: 0,
81
+ from_v3: 1,
82
+ tree: 0,
83
+ need_user_name: 1,
84
+ }
85
+ return geelibPost('/matter/Matter/getMatterList', body)
86
+ }
87
+
88
+ export async function cmdGetMatter(subId, matterId) {
89
+ return geelibGet('/matter/Matter/getMatterDetail', {
90
+ sub_id: subId,
91
+ id: matterId,
92
+ need_options: 1,
93
+ })
94
+ }
95
+
96
+ export async function cmdUpdateMatter(opts) {
97
+ const results = []
98
+
99
+ const hasOtherFields =
100
+ opts.title !== undefined ||
101
+ opts.description !== undefined ||
102
+ opts.assigneeId !== undefined ||
103
+ opts.priority !== undefined ||
104
+ opts.progress !== undefined ||
105
+ opts.sprintId !== undefined
106
+
107
+ if (hasOtherFields) {
108
+ const body = {
109
+ sub_id: opts.subId,
110
+ id: opts.matterId,
111
+ }
112
+ if (opts.title !== undefined) body.title = opts.title
113
+ if (opts.description !== undefined) body.content = opts.description
114
+ if (opts.assigneeId !== undefined) body.executor = opts.assigneeId
115
+ if (opts.priority !== undefined) {
116
+ body.priority = PRIORITY_MAP[opts.priority] ?? opts.priority
117
+ }
118
+ if (opts.progress !== undefined) body.progress = opts.progress
119
+ if (opts.sprintId !== undefined) body.sprint_id = opts.sprintId
120
+ const r = await geelibPost('/matter/Matter/editMatter', body)
121
+ results.push({ fields: 'other', ...r })
122
+ }
123
+
124
+ if (opts.statusId !== undefined) {
125
+ const statusBody = {
126
+ sub_id: String(opts.subId),
127
+ id: opts.matterId,
128
+ data: { cf_id: '1', cf_value: String(opts.statusId) },
129
+ additional_attribute: [],
130
+ remark: '',
131
+ toUsers: [],
132
+ }
133
+ const sr = await geelibPost('/matter/Matter/editMatter', statusBody)
134
+
135
+ if (sr.errno === 2000) {
136
+ const detail = await geelibGet('/matter/Matter/getMatterDetail', {
137
+ sub_id: opts.subId,
138
+ id: opts.matterId,
139
+ need_options: 1,
140
+ })
141
+ const actualStatus = detail.data?.matter_status
142
+ if (actualStatus !== opts.statusId) {
143
+ const missingFields = (detail.data?.matterCustomAttributes ?? [])
144
+ .filter(a => a.is_required && a.matterCustomAttributeValue === null)
145
+ .map(a => `「${a.cf_name}」(${a.cf_key})`)
146
+ const hint =
147
+ missingFields.length > 0
148
+ ? `原因:工作项存在未填的必填字段:${missingFields.join('、')}。请先在 Geelib 网页端补全后重试。`
149
+ : '状态未变更,可能存在未知的状态流转限制。'
150
+ results.push({
151
+ fields: 'status',
152
+ errno: sr.errno,
153
+ errmsg: `API 返回成功但状态实际未变更(当前状态:${detail.data?.status_name ?? actualStatus})。${hint}`,
154
+ })
155
+ } else {
156
+ results.push({
157
+ fields: 'status',
158
+ errno: 2000,
159
+ errmsg: '状态变更成功',
160
+ actual_status: detail.data?.status_name,
161
+ })
162
+ }
163
+ } else {
164
+ results.push({ fields: 'status', ...sr })
165
+ }
166
+ }
167
+
168
+ if (results.length === 0) {
169
+ return { errno: 2000, errmsg: '无需更新(未提供任何修改字段)' }
170
+ }
171
+ if (results.length === 1) {
172
+ return results[0]
173
+ }
174
+ return results
175
+ }
176
+
177
+ export async function cmdGetComments(subId, matterId) {
178
+ return geelibGet('/bservice/Comment/getList', {
179
+ sub_id: subId,
180
+ type: 'matter',
181
+ id: matterId,
182
+ })
183
+ }
184
+
185
+ export async function cmdAddComment(subId, matterId, content) {
186
+ return geelibPost('/bservice/Comment/add', {
187
+ sub_id: subId,
188
+ type: 'matter',
189
+ id: matterId,
190
+ content,
191
+ })
192
+ }
@@ -0,0 +1,147 @@
1
+ /** GeeLib SSO 登录与 Cookie 会话管理 */
2
+
3
+ const SSO_LOGIN_URL = 'https://login.ops.qihoo.net:4436/sec/login'
4
+ const GEELIB_DO_LOGIN_URL = 'https://geelib.qihoo.net/ones/Login/doLogin'
5
+ const GEELIB_VERIFY_URL =
6
+ 'https://geelib.qihoo.net/ones/user/userInfo?need_share=0'
7
+ const BOUNDARY = '----WebKitFormBoundaryGeelibCli'
8
+
9
+ let sessionCookie = ''
10
+
11
+ export function getCookie() {
12
+ return sessionCookie
13
+ }
14
+
15
+ function buildFormData(fields) {
16
+ let body = ''
17
+ for (const [name, value] of Object.entries(fields)) {
18
+ body += `--${BOUNDARY}\r\n`
19
+ body += `Content-Disposition: form-data; name="${name}"\r\n\r\n`
20
+ body += `${value}\r\n`
21
+ }
22
+ body += `--${BOUNDARY}--\r\n`
23
+ return body
24
+ }
25
+
26
+ function parseCookies(headers) {
27
+ const setCookies = headers.getSetCookie?.() ?? []
28
+ const cookies = {}
29
+ for (const c of setCookies) {
30
+ const [pair] = c.split(';')
31
+ const [name, ...rest] = pair.trim().split('=')
32
+ cookies[name.trim()] = rest.join('=').trim()
33
+ }
34
+ return cookies
35
+ }
36
+
37
+ /** SSO 换取 sid */
38
+ async function ssoLogin(user, passwd) {
39
+ const body = buildFormData({
40
+ user,
41
+ passwd,
42
+ src: 'qihoo',
43
+ syzm: '',
44
+ ref: 'https://geelib.qihoo.net/geelib/',
45
+ })
46
+
47
+ const resp = await fetch(SSO_LOGIN_URL, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': `multipart/form-data; boundary=${BOUNDARY}`,
51
+ Accept: 'application/json, text/plain, */*',
52
+ Cookie: 'SSO_PREFERED_DOMAIN=qihoo',
53
+ Referer: 'https://login.ops.qihoo.net:4436/sec/login',
54
+ 'User-Agent':
55
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
56
+ },
57
+ body,
58
+ })
59
+
60
+ if (!resp.ok) {
61
+ throw new Error(`SSO login failed: ${resp.status} ${resp.statusText}`)
62
+ }
63
+ const data = await resp.json()
64
+ if (data.status !== 0) {
65
+ throw new Error(`SSO login error: ${JSON.stringify(data)}`)
66
+ }
67
+ return data.sid
68
+ }
69
+
70
+ /** sid 换 GeeLib Cookie */
71
+ async function doLogin(sid) {
72
+ const resp = await fetch(GEELIB_DO_LOGIN_URL, {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ Accept: 'application/json, text/plain, */*',
77
+ Referer: 'https://geelib.qihoo.net/geelib/',
78
+ Origin: 'https://geelib.qihoo.net',
79
+ 'X-Requested-With': 'XMLHttpRequest',
80
+ 'User-Agent':
81
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
82
+ },
83
+ body: JSON.stringify({ sid, ref: 'https://geelib.qihoo.net/geelib/' }),
84
+ })
85
+
86
+ if (!resp.ok) {
87
+ throw new Error(`doLogin failed: ${resp.status}`)
88
+ }
89
+
90
+ const cookies = parseCookies(resp.headers)
91
+ const data = await resp.json()
92
+
93
+ if (data.errno !== 2000) {
94
+ throw new Error(`doLogin error: errno=${data.errno} errmsg=${data.errmsg}`)
95
+ }
96
+
97
+ const cookieStr = Object.entries(cookies)
98
+ .map(([k, v]) => `${k}=${v}`)
99
+ .join('; ')
100
+
101
+ return {
102
+ cookieStr,
103
+ userName: data.data?.real_name ?? data.data?.name ?? '',
104
+ }
105
+ }
106
+
107
+ async function verifyCookie(cookieStr) {
108
+ try {
109
+ const resp = await fetch(GEELIB_VERIFY_URL, {
110
+ headers: {
111
+ Cookie: cookieStr,
112
+ Referer: 'https://geelib.qihoo.net/geelib/',
113
+ 'User-Agent':
114
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
115
+ Accept: 'application/json, text/plain, */*',
116
+ 'X-Requested-With': 'XMLHttpRequest',
117
+ },
118
+ })
119
+ const data = await resp.json()
120
+ return data.errno === 2000
121
+ } catch {
122
+ return false
123
+ }
124
+ }
125
+
126
+ /** 使用 GEELIB_USER / GEELIB_PASSWD 登录 */
127
+ export async function login() {
128
+ const user = process.env.GEELIB_USER
129
+ const passwd = process.env.GEELIB_PASSWD
130
+
131
+ if (!user || !passwd) {
132
+ throw new Error('请配置 GEELIB_USER 和 GEELIB_PASSWD 环境变量')
133
+ }
134
+
135
+ const sid = await ssoLogin(user, passwd)
136
+ const { cookieStr } = await doLogin(sid)
137
+ sessionCookie = cookieStr
138
+ return cookieStr
139
+ }
140
+
141
+ /** Cookie 失效时自动重新登录 */
142
+ export async function ensureAuth() {
143
+ if (sessionCookie && (await verifyCookie(sessionCookie))) {
144
+ return
145
+ }
146
+ await login()
147
+ }