@ai-wendao/ant 0.4.0 → 0.4.4

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/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export const name = 'ant'
2
2
 
3
3
  import { createHash } from 'node:crypto'
4
+ import { spawn } from 'node:child_process'
4
5
  import { createRequire } from 'node:module'
5
6
  import { installSkill, listSkills, uninstallSkill } from './skill-install.js'
6
- import { recentDelegationStatus, startDelegationHost } from './delegation.js'
7
+ import { readLocalDelegationSettings, recentDelegationStatus, startDelegationHost,
8
+ writeLocalDelegationSettings } from './delegation.js'
7
9
 
8
10
  export const inject = []
9
11
 
@@ -25,6 +27,11 @@ const DELEGATION_ENROLLED_BOTS_PATH = '/api/ant-teams/delegation/enrolled-bots'
25
27
  const DELEGATION_GROUPS_PATH = '/api/ant-teams/delegation/groups'
26
28
  const DELEGATION_OWNER_CHALLENGE_PATH = '/api/ant-teams/delegation/owner-challenge'
27
29
  const DELEGATION_STATUS_PATH = '/api/ant-teams/delegation/status'
30
+ const DELEGATION_CARDS_PATH = '/api/ant-teams/delegation/cards'
31
+ const DELEGATION_SETTINGS_PATH = '/api/ant-teams/delegation/settings'
32
+ const DELEGATION_EXECUTORS_PATH = '/api/ant-teams/delegation/executors'
33
+ const DELEGATION_LOCAL_SETTINGS_PATH = '/api/ant-teams/delegation/local-settings'
34
+ const DELEGATION_CANCEL_PATH = '/api/ant-teams/delegation/cancel'
28
35
  const ANT_NPM_PACKAGE_NAME = '@ai-wendao/ant'
29
36
  const BUNDLE_VERSION = createRequire(import.meta.url)('./package.json').version
30
37
  const MAX_MODEL_RESPONSE_BYTES = 1024 * 1024
@@ -124,7 +131,7 @@ async function updateModelSession(serviceUrl, token, action) {
124
131
  })
125
132
  }
126
133
 
127
- async function readLimitedBody(response) {
134
+ async function readLimitedBody(response, maxBytes = MAX_MODEL_RESPONSE_BYTES) {
128
135
  if (!response.body) return ''
129
136
  const reader = response.body.getReader()
130
137
  const chunks = []
@@ -134,7 +141,7 @@ async function readLimitedBody(response) {
134
141
  const { done, value } = await reader.read()
135
142
  if (done) break
136
143
  size += value.byteLength
137
- if (size > MAX_MODEL_RESPONSE_BYTES) {
144
+ if (size > maxBytes) {
138
145
  await reader.cancel()
139
146
  return null
140
147
  }
@@ -195,6 +202,10 @@ function dshImService(ctx) {
195
202
  try { return ctx.get?.('dshIm') ?? ctx.dshIm ?? null } catch { return null }
196
203
  }
197
204
 
205
+ function optionalHostService(ctx, name) {
206
+ try { return ctx.get?.(name) ?? ctx[name] ?? null } catch { return null }
207
+ }
208
+
198
209
  async function fetchDelegationBots(request, ctx) {
199
210
  if (request.method !== 'GET') return jsonResponse(405, { message: '只支持 GET 请求。' })
200
211
  const service = dshImService(ctx)
@@ -210,6 +221,7 @@ async function fetchDelegationBots(request, ctx) {
210
221
  connected: details.connected === true,
211
222
  available: details.capabilities?.includes('exclusive-text-consumer')
212
223
  && details.capabilities?.includes('reply-text-checked'),
224
+ history_available: details.capabilities?.includes('history-text-checked') === true,
213
225
  }
214
226
  } catch { return null }
215
227
  }))
@@ -252,6 +264,7 @@ async function fetchDelegationGrants(request, ctx, credentials, settings) {
252
264
  const response = await fetch(new URL(path, connection.serviceUrl), {
253
265
  method: request.method,
254
266
  headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json',
267
+ ...(request.method !== 'GET' ? { origin: new URL(connection.serviceUrl).origin } : {}),
255
268
  ...(request.method === 'POST' ? { 'content-type': 'application/json' } : {}) },
256
269
  ...(request.method === 'POST' ? { body: JSON.stringify(body) } : {}),
257
270
  redirect: 'manual', signal: AbortSignal.timeout(12_000),
@@ -267,8 +280,8 @@ async function fetchDelegationGrants(request, ctx, credentials, settings) {
267
280
  } catch { return jsonResponse(502, { message: '暂时无法连接 ANT 团队服务。' }) }
268
281
  }
269
282
 
270
- async function fetchDelegationGroupSettings(request, ctx, credentials, settings, kind) {
271
- const methods = kind === 'groups' ? ['GET'] : kind === 'challenge' ? ['POST'] : ['GET', 'POST', 'DELETE']
283
+ async function fetchDelegationGroupSettings(request, ctx, credentials, settings, kind, getHost) {
284
+ const methods = kind === 'groups' ? ['GET', 'PUT'] : kind === 'challenge' ? ['POST'] : ['GET', 'POST', 'DELETE']
272
285
  if (!methods.includes(request.method)) return jsonResponse(405, { message: '请求方式不支持。' })
273
286
  let connection
274
287
  try { connection = await readMemberConnection(credentials, settings) } catch {
@@ -304,17 +317,29 @@ async function fetchDelegationGroupSettings(request, ctx, credentials, settings,
304
317
  ? `/api/me/im-delegations/bots/${encodeURIComponent(body.bot_id)}`
305
318
  : '/api/me/im-delegations/bots'
306
319
  try {
307
- const response = await fetch(new URL(path, connection.serviceUrl), {
320
+ const destination = new URL(path, connection.serviceUrl)
321
+ if (kind === 'bots' && request.method === 'GET'
322
+ && request.headers.get('x-ant-include-unbound') === 'true') {
323
+ destination.searchParams.set('include_unbound', 'true')
324
+ }
325
+ const response = await fetch(destination, {
308
326
  method: request.method,
309
327
  headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json',
310
- ...(request.method === 'POST' ? { 'content-type': 'application/json' } : {}) },
311
- ...(request.method === 'POST' ? { body: JSON.stringify(body) } : {}),
328
+ ...(request.method !== 'GET' ? { origin: new URL(connection.serviceUrl).origin } : {}),
329
+ ...(['POST', 'PUT'].includes(request.method) ? { 'content-type': 'application/json' } : {}) },
330
+ ...(['POST', 'PUT'].includes(request.method) ? { body: JSON.stringify(body) } : {}),
312
331
  redirect: 'manual', signal: AbortSignal.timeout(12_000),
313
332
  })
314
333
  if (response.status === 404 && request.method !== 'DELETE') {
315
334
  return jsonResponse(426, { message: '团队服务端尚未支持群聊委托,请联系管理员升级 ANT 服务端。' })
316
335
  }
317
- if (response.status === 204) return new Response(null, { status: 204 })
336
+ if (response.status === 204) {
337
+ if (kind === 'groups' && request.method === 'PUT') {
338
+ const refresh = getHost?.()?.refresh
339
+ if (refresh) await refresh().catch(() => undefined)
340
+ }
341
+ return new Response(null, { status: 204 })
342
+ }
318
343
  const text = await readLimitedBody(response)
319
344
  if (text === null) return jsonResponse(502, { message: '团队服务响应过大。' })
320
345
  return new Response(text, { status: response.status,
@@ -331,6 +356,132 @@ async function fetchDelegationStatus(request, credentials, settings) {
331
356
  } catch { return jsonResponse(503, { message: '本机委托任务记录暂时无法读取,请联系管理员检查。' }) }
332
357
  }
333
358
 
359
+ async function fetchDelegationCards(request, ctx, credentials, settings) {
360
+ if (!['GET', 'POST', 'DELETE'].includes(request.method)) return jsonResponse(405, { message: '请求方式不支持。' })
361
+ let connection
362
+ try { connection = await readMemberConnection(credentials, settings) } catch {
363
+ return jsonResponse(500, { message: '无法读取 ANT 连接。' })
364
+ }
365
+ if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
366
+ let body = null
367
+ if (request.method !== 'GET') {
368
+ try {
369
+ const raw = await request.text()
370
+ if (raw.length > 32_768) return jsonResponse(413, { message: '任务设置过大。' })
371
+ body = JSON.parse(raw)
372
+ } catch { return jsonResponse(400, { message: '任务设置格式无效。' }) }
373
+ }
374
+ if (request.method === 'POST') {
375
+ const service = dshImService(ctx)
376
+ if (!service?.describeBot) return jsonResponse(503, { message: '请先安装并启用 DSH-IM。' })
377
+ try {
378
+ const bot = await service.describeBot(body.bot_id)
379
+ if (bot.channel !== 'feishu' || !bot.connected || bot.account?.fingerprint !== body.bot_fingerprint
380
+ || !bot.capabilities?.includes('exclusive-text-consumer') || !bot.capabilities?.includes('reply-text-checked')) {
381
+ return jsonResponse(400, { message: '请选择已连接且支持协作收件的飞书机器人。' })
382
+ }
383
+ } catch { return jsonResponse(400, { message: '机器人当前不可用。' }) }
384
+ }
385
+ if (request.method === 'DELETE' && (typeof body?.id !== 'string' || !/^[0-9a-f-]{36}$/iu.test(body.id))) {
386
+ return jsonResponse(400, { message: '任务编号无效。' })
387
+ }
388
+ const path = request.method === 'DELETE'
389
+ ? `/api/me/im-delegations/cards/${encodeURIComponent(body.id)}` : '/api/me/im-delegations/cards'
390
+ try {
391
+ const response = await fetch(new URL(path, connection.serviceUrl), {
392
+ method: request.method,
393
+ headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json',
394
+ ...(request.method !== 'GET' ? { origin: new URL(connection.serviceUrl).origin } : {}),
395
+ ...(request.method === 'POST' ? { 'content-type': 'application/json' } : {}) },
396
+ ...(request.method === 'POST' ? { body: JSON.stringify(body) } : {}),
397
+ redirect: 'manual', signal: AbortSignal.timeout(12_000),
398
+ })
399
+ if (response.status === 404 && request.method !== 'DELETE') return jsonResponse(426, { message: '团队服务端尚未支持自定义任务,请联系管理员升级。' })
400
+ if (response.status === 204) return new Response(null, { status: 204 })
401
+ const text = await readLimitedBody(response, request.method === 'GET' ? 8 * 1024 * 1024 : MAX_MODEL_RESPONSE_BYTES)
402
+ if (text === null) return jsonResponse(502, { message: '团队服务响应过大。' })
403
+ return new Response(text, { status: response.status,
404
+ headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' } })
405
+ } catch { return jsonResponse(502, { message: '暂时无法连接 ANT 团队服务。' }) }
406
+ }
407
+
408
+ async function fetchDelegationRuntimeSettings(request, credentials, settings) {
409
+ if (request.method !== 'GET') return jsonResponse(405, { message: '请求方式不支持。' })
410
+ try {
411
+ const connection = await readMemberConnection(credentials, settings)
412
+ if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
413
+ const response = await fetch(new URL('/api/me/im-delegations/settings', connection.serviceUrl), {
414
+ headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json' },
415
+ redirect: 'manual', signal: AbortSignal.timeout(12_000),
416
+ })
417
+ if (!response.ok) return jsonResponse(response.status, { message: '暂时无法读取团队协作设置。' })
418
+ return jsonResponse(200, await response.json())
419
+ } catch { return jsonResponse(502, { message: '暂时无法读取团队协作设置。' }) }
420
+ }
421
+
422
+ async function fetchDelegationExecutors(request, ctx) {
423
+ if (request.method !== 'GET') return jsonResponse(405, { message: '请求方式不支持。' })
424
+ let providers = []
425
+ try {
426
+ const listed = optionalHostService(ctx, 'subagents')?.list?.()
427
+ if (Array.isArray(listed)) providers = listed
428
+ } catch {}
429
+ const available = new Set(providers)
430
+ const codexCliAvailable = await new Promise((resolve) => {
431
+ const child = spawn('codex', ['exec', '--help'], { stdio: ['ignore', 'pipe', 'ignore'] })
432
+ let help = ''
433
+ child.stdout.on('data', (chunk) => { help = (help + chunk.toString()).slice(0, 32_000) })
434
+ const timer = setTimeout(() => { child.kill(); resolve(false) }, 3_000)
435
+ child.once('error', () => { clearTimeout(timer); resolve(false) })
436
+ child.once('close', (code) => { clearTimeout(timer)
437
+ resolve(code === 0 && ['--ignore-user-config', '--strict-config', '--output-last-message']
438
+ .every((flag) => help.includes(flag))) })
439
+ })
440
+ const nativePrSnapshotSupported = process.platform !== 'win32'
441
+ const nativePrUnavailableReason = nativePrSnapshotSupported ? null
442
+ : '当前 Windows 环境无法限制 Git 仓库缓存容量'
443
+ return jsonResponse(200, [
444
+ { id: 'dsh', title: 'DSH', available: Boolean(optionalHostService(ctx, 'sessionController')
445
+ && optionalHostService(ctx, 'agents')) },
446
+ { id: 'codex', title: 'Codex', available: available.has('codex') },
447
+ { id: 'codex-readonly', title: 'Codex 专用只读差异审查', available: codexCliAvailable && nativePrSnapshotSupported,
448
+ ...(nativePrUnavailableReason ? { reason: nativePrUnavailableReason } : {}) },
449
+ { id: 'claude-code', title: 'Claude Code', available: available.has('claude-code') && nativePrSnapshotSupported,
450
+ ...(nativePrUnavailableReason ? { reason: nativePrUnavailableReason } : {}) },
451
+ { id: 'acp', title: 'DSH ACP', available: available.has('acp') },
452
+ { id: 'dsh-sdk', title: 'DSH SDK 子代理', available: available.has('dsh-sdk') },
453
+ ])
454
+ }
455
+
456
+ async function fetchDelegationLocalSettings(request, credentials, settings) {
457
+ if (!['GET', 'PUT'].includes(request.method)) return jsonResponse(405, { message: '请求方式不支持。' })
458
+ try {
459
+ if (!await readMemberConnection(credentials, settings)) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
460
+ if (request.method === 'GET') return jsonResponse(200, await readLocalDelegationSettings())
461
+ const raw = await request.text()
462
+ if (raw.length > 256) return jsonResponse(413, { message: '设置内容过大。' })
463
+ const body = JSON.parse(raw)
464
+ return jsonResponse(200, await writeLocalDelegationSettings(body))
465
+ } catch (error) { return jsonResponse(400, { message: error?.message || '本机设置无法保存。' }) }
466
+ }
467
+
468
+ async function fetchDelegationCancel(request, credentials, settings, getHost) {
469
+ if (request.method !== 'POST') return jsonResponse(405, { message: '请求方式不支持。' })
470
+ try {
471
+ const connection = await readMemberConnection(credentials, settings)
472
+ if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
473
+ const raw = await request.text()
474
+ if (raw.length > 256) return jsonResponse(413, { message: '请求内容过大。' })
475
+ const body = JSON.parse(raw)
476
+ if (typeof body.task_id !== 'string' || !/^[A-F0-9]{10}$/u.test(body.task_id)) {
477
+ return jsonResponse(400, { message: '任务编号无效。' })
478
+ }
479
+ const host = getHost()
480
+ if (!host) return jsonResponse(503, { message: '协作机器人尚未就绪。' })
481
+ return jsonResponse(200, { canceled: await host.cancelTask(body.task_id, connection.serviceUrl) })
482
+ } catch { return jsonResponse(503, { message: '暂时无法取消任务,请稍后重试。' }) }
483
+ }
484
+
334
485
  async function fetchServiceInfo(request, credentials, settings) {
335
486
  if (request.method !== 'POST') return jsonResponse(405, { message: '只支持 POST 请求' })
336
487
  let body
@@ -735,6 +886,7 @@ async function fetchAuthRequest(request, endpoint, credentials, settings) {
735
886
 
736
887
  export function apply(ctx) {
737
888
  ctx.inject(['connection', 'credentials', 'settings'], (connectionCtx) => {
889
+ let delegationHost = null
738
890
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
739
891
  path: SERVICE_INFO_PATH,
740
892
  methods: ['POST'],
@@ -790,19 +942,40 @@ export function apply(ctx) {
790
942
  }), 'ant-teams: member delegation settings')
791
943
  for (const [path, methods, kind] of [
792
944
  [DELEGATION_ENROLLED_BOTS_PATH, ['GET', 'POST', 'DELETE'], 'bots'],
793
- [DELEGATION_GROUPS_PATH, ['GET'], 'groups'],
945
+ [DELEGATION_GROUPS_PATH, ['GET', 'PUT'], 'groups'],
794
946
  [DELEGATION_OWNER_CHALLENGE_PATH, ['POST'], 'challenge'],
795
947
  ]) {
796
948
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
797
949
  path, methods, requestBody: 'buffered',
798
950
  fetch: (request) => fetchDelegationGroupSettings(request, connectionCtx,
799
- connectionCtx.credentials, connectionCtx.settings, kind),
951
+ connectionCtx.credentials, connectionCtx.settings, kind, () => delegationHost),
800
952
  }), `ant-teams: member delegation ${kind}`)
801
953
  }
802
954
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
803
955
  path: DELEGATION_STATUS_PATH, methods: ['GET'], requestBody: 'buffered',
804
956
  fetch: (request) => fetchDelegationStatus(request, connectionCtx.credentials, connectionCtx.settings),
805
957
  }), 'ant-teams: local delegated task status')
958
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
959
+ path: DELEGATION_CARDS_PATH, methods: ['GET', 'POST', 'DELETE'], requestBody: 'buffered',
960
+ fetch: (request) => fetchDelegationCards(request, connectionCtx, connectionCtx.credentials, connectionCtx.settings),
961
+ }), 'ant-teams: member task cards')
962
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
963
+ path: DELEGATION_SETTINGS_PATH, methods: ['GET'], requestBody: 'buffered',
964
+ fetch: (request) => fetchDelegationRuntimeSettings(request, connectionCtx.credentials, connectionCtx.settings),
965
+ }), 'ant-teams: collaboration defaults')
966
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
967
+ path: DELEGATION_EXECUTORS_PATH, methods: ['GET'], requestBody: 'buffered',
968
+ fetch: (request) => fetchDelegationExecutors(request, connectionCtx),
969
+ }), 'ant-teams: local executor availability')
970
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
971
+ path: DELEGATION_LOCAL_SETTINGS_PATH, methods: ['GET', 'PUT'], requestBody: 'buffered',
972
+ fetch: (request) => fetchDelegationLocalSettings(request, connectionCtx.credentials, connectionCtx.settings),
973
+ }), 'ant-teams: local delegated-task concurrency')
974
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
975
+ path: DELEGATION_CANCEL_PATH, methods: ['POST'], requestBody: 'buffered',
976
+ fetch: (request) => fetchDelegationCancel(request, connectionCtx.credentials,
977
+ connectionCtx.settings, () => delegationHost),
978
+ }), 'ant-teams: owner cancellation for delegated tasks')
806
979
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
807
980
  path: DISCONNECT_PATH,
808
981
  methods: ['POST'],
@@ -848,8 +1021,15 @@ export function apply(ctx) {
848
1021
  }
849
1022
  }, 'ant-teams: ANT model activity session')
850
1023
  connectionCtx.inject?.(['dshIm', 'sessionController', 'sessions', 'agents', 'tools'], (delegationCtx) => {
851
- delegationCtx.effect(() => startDelegationHost(delegationCtx,
852
- () => readMemberConnection(delegationCtx.credentials, delegationCtx.settings)),
1024
+ delegationCtx.effect(() => {
1025
+ const host = startDelegationHost(delegationCtx,
1026
+ () => readMemberConnection(delegationCtx.credentials, delegationCtx.settings))
1027
+ delegationHost = host
1028
+ return async () => {
1029
+ if (delegationHost === host) delegationHost = null
1030
+ await host()
1031
+ }
1032
+ },
853
1033
  'ant-teams: delegated IM review intake')
854
1034
  })
855
1035
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.4.0",
3
+ "version": "0.4.4",
4
4
  "description": "ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to team identity and approved models.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -16,7 +16,7 @@
16
16
  "./client": "./client.js",
17
17
  "./locale/*.json": "./locale/*.json"
18
18
  },
19
- "files": ["index.js", "client.js", "skill-install.js", "delegation.js", "cordis.patch.yml", "locale/*.json", "tools/migrate-legacy.mjs", "MIGRATION.md", "README.md"],
19
+ "files": ["index.js", "client.js", "skill-install.js", "delegation.js", "conversation.js", "cordis.patch.yml", "locale/*.json", "tools/migrate-legacy.mjs", "MIGRATION.md", "README.md"],
20
20
  "dsh": {
21
21
  "bundle": {
22
22
  "patch": "./cordis.patch.yml"