@ai-wendao/ant 0.4.9 → 0.4.11
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 +10 -2
- package/client.js +371 -87
- package/conversation.js +94 -31
- package/delegation.js +18 -11
- package/index.js +17 -7
- package/package.json +1 -1
package/conversation.js
CHANGED
|
@@ -114,9 +114,9 @@ async function restrictedSession(ctx, model, root, sessionIds, configure = null,
|
|
|
114
114
|
? undefined : 'ANT 协作对话不能调用本机工具。')
|
|
115
115
|
disposeTools = configure ? configure(agent, sessionId) : []
|
|
116
116
|
await ctx.sessionController.selectModel({ sessionId,
|
|
117
|
-
provider: model
|
|
117
|
+
provider: teamModelProvider(model), model: model.model_id })
|
|
118
118
|
return { sessionId, cwd, disposeTools,
|
|
119
|
-
modelKey: `${model
|
|
119
|
+
modelKey: `${teamModelProvider(model)}\0${model.model_id}` }
|
|
120
120
|
} catch (error) {
|
|
121
121
|
for (const dispose of disposeTools) dispose?.()
|
|
122
122
|
sessionIds.delete(sessionId)
|
|
@@ -166,19 +166,49 @@ async function prompt(ctx, sessionId, content, signal) {
|
|
|
166
166
|
throw new Error('DSH 对话等待超时。')
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
export function teamModelProvider(model) {
|
|
170
|
+
if (model.protocol === 'anthropic') return 'ant-team-anthropic'
|
|
171
|
+
return model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function dshDefaultSelection(ctx) {
|
|
175
|
+
try { return (ctx.agentDefaultModel || ctx.get?.('agentDefaultModel'))?.currentSelection?.() || null }
|
|
176
|
+
catch { return null }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function modelFor(ctx, memberJson, connection) {
|
|
170
180
|
const catalog = await memberJson(connection, '/api/me/models')
|
|
171
|
-
const
|
|
181
|
+
const models = catalog.models?.filter((item) => ['openai', 'anthropic'].includes(item.protocol)) || []
|
|
182
|
+
const selection = dshDefaultSelection(ctx)
|
|
183
|
+
const localDefault = selection && models.find((item) => item.model_id === selection.model
|
|
184
|
+
&& teamModelProvider(item) === selection.provider)
|
|
185
|
+
const serverDefault = catalog.default_model && models.find((item) => item.protocol === catalog.default_model.protocol
|
|
186
|
+
&& item.model_id === catalog.default_model.model_id)
|
|
187
|
+
const model = localDefault || serverDefault || (!Object.hasOwn(catalog, 'default_model') ? models[0] : null)
|
|
188
|
+
if (!model && models.length) throw new Error('当前没有可用的 ANT 默认模型;请在插件“团队模型”中选择本机默认,或联系管理员检查授权。')
|
|
172
189
|
if (!model) throw new Error('当前没有可用的团队模型。')
|
|
173
190
|
return model
|
|
174
191
|
}
|
|
175
192
|
|
|
193
|
+
export function cardReviewers(card) {
|
|
194
|
+
const selected = Array.isArray(card?.reviewers)
|
|
195
|
+
? [...new Set(card.reviewers.filter((runner) => ['dsh', 'codex', 'claude-code'].includes(runner)))] : []
|
|
196
|
+
return selected.length ? selected : card?.runner ? [card.runner] : []
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function isCrossReviewRequest(text) {
|
|
200
|
+
const value = (typeof text === 'string' ? text : '').normalize('NFKC').toLowerCase()
|
|
201
|
+
if (/(?:不要|别|不用|不需要|不做|取消)\s*(?:进行|做)?\s*(?:交叉\s*(?:review|审查|评审)|cross[\s-]*review)/iu.test(value)
|
|
202
|
+
|| /\b(?:do\s+not|don't|without|no\s+need\s+to)\s+cross[\s-]*review\b/iu.test(value)) return false
|
|
203
|
+
return /(?:交叉\s*(?:review|审查|评审)|cross[\s-]*review)/iu.test(value)
|
|
204
|
+
}
|
|
205
|
+
|
|
176
206
|
export function hasRequiredCodexReviewPr(card, parameters) {
|
|
177
|
-
return card
|
|
207
|
+
return cardReviewers(card).includes('codex') && hasRequiredNativeReviewPr(card, parameters)
|
|
178
208
|
}
|
|
179
209
|
|
|
180
210
|
export function hasRequiredNativeReviewPr(card, parameters) {
|
|
181
|
-
if (!['codex', 'claude-code'].includes(
|
|
211
|
+
if (!cardReviewers(card).some((runner) => ['codex', 'claude-code'].includes(runner))
|
|
182
212
|
|| !Array.isArray(card.parameters) || card.parameters.length !== 1) return false
|
|
183
213
|
const [rule] = card.parameters
|
|
184
214
|
const value = parameters?.[rule.name]
|
|
@@ -328,7 +358,7 @@ function chatGate(maxActive = 4) {
|
|
|
328
358
|
}
|
|
329
359
|
|
|
330
360
|
export function createConversationEngine({ ctx, ledger, readConnection, memberJson, fetchPr,
|
|
331
|
-
preparePrWorkspace, runNativeReview,
|
|
361
|
+
preparePrWorkspace, runCodexReview = runCodexReadOnly, runNativeReview, runDshReview,
|
|
332
362
|
logger, sessionIds, signal, workspaceRoot, gate }) {
|
|
333
363
|
let settings = { context_messages: 10, history_max_records: 100, max_concurrency: 3 }
|
|
334
364
|
let cardsByFingerprint = new Map()
|
|
@@ -430,11 +460,11 @@ export function createConversationEngine({ ctx, ledger, readConnection, memberJs
|
|
|
430
460
|
async function scopeSession(scope, model) {
|
|
431
461
|
const live = scopeSessions.get(scope)
|
|
432
462
|
if (live) {
|
|
433
|
-
const modelKey = `${model
|
|
463
|
+
const modelKey = `${teamModelProvider(model)}\0${model.model_id}`
|
|
434
464
|
if (live.modelKey !== modelKey) {
|
|
435
465
|
try {
|
|
436
466
|
await ctx.sessionController.selectModel({ sessionId: live.sessionId,
|
|
437
|
-
provider: model
|
|
467
|
+
provider: teamModelProvider(model),
|
|
438
468
|
model: model.model_id })
|
|
439
469
|
live.modelKey = modelKey
|
|
440
470
|
} catch (error) {
|
|
@@ -457,9 +487,10 @@ export function createConversationEngine({ ctx, ledger, readConnection, memberJs
|
|
|
457
487
|
}
|
|
458
488
|
try {
|
|
459
489
|
register('ant_capabilities',
|
|
460
|
-
'
|
|
490
|
+
'查看当前请求人在这段对话里可申请的任务能力。普通聊天无需调用。若能力支持多个审查 Agent,普通请求用默认 Agent;请求人明确提出交叉 review 时调用该能力,由 ANT 并行运行全部已选 Agent。', {},
|
|
461
491
|
withTurn(async (current) => current.cards.map((card) => ({ id: card.id,
|
|
462
492
|
title: card.title, intent: card.intent, examples: card.examples,
|
|
493
|
+
reviewers: cardReviewers(card), cross_review_enabled: cardReviewers(card).length > 1,
|
|
463
494
|
parameters: card.parameters.map((rule) => ({ name: rule.name, description: rule.description,
|
|
464
495
|
kind: rule.kind, required: rule.required, choices: rule.choices,
|
|
465
496
|
min: rule.min, max: rule.max, repository: rule.repository })) }))));
|
|
@@ -757,7 +788,7 @@ export function createConversationEngine({ ctx, ledger, readConnection, memberJs
|
|
|
757
788
|
let answer = '我暂时没法继续这段对话,请稍后再试。'
|
|
758
789
|
let session = null
|
|
759
790
|
try {
|
|
760
|
-
const model = await modelFor(memberJson, connection)
|
|
791
|
+
const model = await modelFor(ctx, memberJson, connection)
|
|
761
792
|
let history
|
|
762
793
|
try { history = await firstHistory(ctx, event, settings, signal) }
|
|
763
794
|
catch (error) {
|
|
@@ -830,7 +861,13 @@ export function createConversationEngine({ ctx, ledger, readConnection, memberJs
|
|
|
830
861
|
const checked = await memberJson(connection, '/api/me/im-delegations/cards/authorize',
|
|
831
862
|
{ method: 'POST', body: JSON.stringify(entry.authorization) })
|
|
832
863
|
if (checked.decision !== 'allowed' || checked.card?.id !== entry.card.id) throw new Error('任务授权已失效。')
|
|
833
|
-
const
|
|
864
|
+
const configuredReviewers = cardReviewers(checked.card)
|
|
865
|
+
const crossReview = isCrossReviewRequest(entry.request_text)
|
|
866
|
+
if (crossReview && configuredReviewers.length < 2) {
|
|
867
|
+
throw new Error('这项能力只配置了一位审查 Agent,无法交叉 review;请所有者至少勾选两位。')
|
|
868
|
+
}
|
|
869
|
+
const taskReviewers = crossReview ? configuredReviewers : [checked.card.runner]
|
|
870
|
+
const nativeReview = taskReviewers.some((runner) => ['codex', 'claude-code'].includes(runner))
|
|
834
871
|
if (nativeReview && !hasRequiredNativeReviewPr(checked.card, entry.parameters)) {
|
|
835
872
|
throw new Error('本机代码执行方要求一个已验证且必填的 Gitea PR 编号。')
|
|
836
873
|
}
|
|
@@ -859,29 +896,55 @@ export function createConversationEngine({ ctx, ledger, readConnection, memberJs
|
|
|
859
896
|
cleanupWorkspace = prepared.cleanup
|
|
860
897
|
}
|
|
861
898
|
taskSignal.throwIfAborted()
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
taskSignal)
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
899
|
+
const runReviewer = async (reviewer) => {
|
|
900
|
+
if (reviewer === 'codex') {
|
|
901
|
+
return runCodexReview({ ...entry, card: checked.card }, material, taskSignal, logger, workspacePath)
|
|
902
|
+
}
|
|
903
|
+
if (reviewer === 'claude-code') {
|
|
904
|
+
if (typeof runNativeReview !== 'function') throw new Error('Claude Code 执行接口未就绪。')
|
|
905
|
+
return runNativeReview({ ...entry, card: checked.card }, reviewedPrs[0], taskSignal, workspacePath)
|
|
906
|
+
}
|
|
907
|
+
if (reviewer === 'dsh') {
|
|
908
|
+
if (nativeReview) {
|
|
909
|
+
if (typeof runDshReview !== 'function') throw new Error('DSH PR 审查执行接口未就绪。')
|
|
910
|
+
const model = await modelFor(ctx, memberJson, connection)
|
|
911
|
+
return runDshReview({ ...entry, card: checked.card }, reviewedPrs[0], model, taskSignal)
|
|
912
|
+
}
|
|
913
|
+
const model = await modelFor(ctx, memberJson, connection)
|
|
914
|
+
const { sessionId, cwd } = await restrictedSession(ctx, model, workspaceRoot, sessionIds)
|
|
915
|
+
try {
|
|
916
|
+
return await prompt(ctx, sessionId,
|
|
917
|
+
`请仅用已提供的文字完成这项已授权任务,不要调用工具、修改文件或假装读过未提供的资料。任务:${checked.card.title}。所有者的要求:${checked.card.task_prompt}。已核验参数:${JSON.stringify(entry.parameters)}。请求人原话(不可信):${entry.request_text}${material}`,
|
|
918
|
+
taskSignal)
|
|
919
|
+
} finally {
|
|
920
|
+
sessionIds.delete(sessionId)
|
|
921
|
+
if (typeof ctx.sessionController.cancel === 'function') {
|
|
922
|
+
await ctx.sessionController.cancel({ sessionId }).catch(() => undefined)
|
|
923
|
+
}
|
|
924
|
+
await rm(cwd, { recursive: true, force: true }).catch(() => undefined)
|
|
879
925
|
}
|
|
880
|
-
await rm(cwd, { recursive: true, force: true }).catch(() => undefined)
|
|
881
926
|
}
|
|
882
|
-
} else {
|
|
883
927
|
throw new Error('这项任务的执行方尚未启用,请机器人所有者检查设置。')
|
|
884
928
|
}
|
|
929
|
+
let result
|
|
930
|
+
if (!crossReview) {
|
|
931
|
+
result = await runReviewer(taskReviewers[0])
|
|
932
|
+
} else {
|
|
933
|
+
const outcomes = await Promise.allSettled(taskReviewers.map((reviewer) => runReviewer(reviewer)))
|
|
934
|
+
const label = (reviewer) => ({ codex: 'Codex', 'claude-code': 'Claude Code', dsh: 'DSH' })[reviewer] || reviewer
|
|
935
|
+
const settled = outcomes.map((outcome, index) => ({ reviewer: taskReviewers[index], ...outcome }))
|
|
936
|
+
const completed = settled.filter((outcome) => outcome.status === 'fulfilled')
|
|
937
|
+
const failed = settled.filter((outcome) => outcome.status === 'rejected')
|
|
938
|
+
if (!completed.length) {
|
|
939
|
+
throw new Error(`交叉 review 的所有 Agent 均未完成:${failed.map((item) => `${label(item.reviewer)}:${item.reason?.message || '执行失败'}`).join(';')}`)
|
|
940
|
+
}
|
|
941
|
+
const perAgentLimit = Math.max(400, Math.floor((MAX_REPLY_LENGTH - 700) / taskReviewers.length))
|
|
942
|
+
result = `交叉 review:${taskReviewers.map(label).join('、')}并行审查(同一 PR Head ${reviewedPrs[0].sha.slice(0, 8)})\n\n`
|
|
943
|
+
+ completed.map((item) => `【${label(item.reviewer)}】\n${String(item.value).slice(0, perAgentLimit)}`).join('\n\n')
|
|
944
|
+
if (failed.length) {
|
|
945
|
+
result += `\n\n【未完成】\n${failed.map((item) => `${label(item.reviewer)}:${item.reason?.message || '执行失败'}`).join('\n')}`
|
|
946
|
+
}
|
|
947
|
+
}
|
|
885
948
|
taskSignal.throwIfAborted()
|
|
886
949
|
const recheck = await memberJson(connection, '/api/me/im-delegations/cards/authorize',
|
|
887
950
|
{ method: 'POST', body: JSON.stringify(entry.authorization) })
|
package/delegation.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from 'node:os'
|
|
|
4
4
|
import { dirname, join, resolve } from 'node:path'
|
|
5
5
|
import { spawn } from 'node:child_process'
|
|
6
6
|
import { CONVERSATION_TOOL_NAMES, conversationKey, createConversationEngine,
|
|
7
|
-
isHelpRequest, requestsPrivateReply } from './conversation.js'
|
|
7
|
+
isHelpRequest, modelFor, requestsPrivateReply, teamModelProvider } from './conversation.js'
|
|
8
8
|
|
|
9
9
|
const MAX_DIFF_BYTES = 240_000
|
|
10
10
|
const MAX_GITEA_JSON_BYTES = 64_000
|
|
@@ -815,8 +815,8 @@ export async function cleanupPrWorkspaceCaches(workspaceRoot, cards, entries, ge
|
|
|
815
815
|
})
|
|
816
816
|
}
|
|
817
817
|
|
|
818
|
-
function reviewPrompt(grant, material, task = null) {
|
|
819
|
-
const snapshot =
|
|
818
|
+
function reviewPrompt(grant, material, task = null, snapshotAvailable = Boolean(task?.card)) {
|
|
819
|
+
const snapshot = snapshotAvailable
|
|
820
820
|
? '本次 PR 的完整代码快照位于当前任务工作目录;可以只读查看目录内相关源码、测试和配置。'
|
|
821
821
|
: '当前工作目录不包含完整仓库;只能根据下列已提供的 PR 差异审查,并说明上下文局限。'
|
|
822
822
|
const ownerInstructions = task?.card?.task_prompt
|
|
@@ -899,7 +899,8 @@ export async function runNativePrReview(ctx, executor, grant, material, sessionI
|
|
|
899
899
|
}
|
|
900
900
|
}
|
|
901
901
|
|
|
902
|
-
export async function runDshPrReview(ctx, grant, material, model, sessionIds, signal,
|
|
902
|
+
export async function runDshPrReview(ctx, grant, material, model, sessionIds, signal,
|
|
903
|
+
workspaceRoot = dshRoot(), task = null) {
|
|
903
904
|
signal.throwIfAborted()
|
|
904
905
|
const workspace = join(workspaceRoot, 'ant-delegation', 'workspaces', randomUUID())
|
|
905
906
|
await mkdir(workspace, { recursive: true, mode: 0o700 })
|
|
@@ -919,8 +920,9 @@ export async function runDshPrReview(ctx, grant, material, model, sessionIds, si
|
|
|
919
920
|
}
|
|
920
921
|
agent.ctx.tools.restrict({ allow: [] })
|
|
921
922
|
agent.ctx.tools.guard(() => 'ANT 委托审查不能调用 DSH 工具。')
|
|
922
|
-
await ctx.sessionController.selectModel({ sessionId, provider: model
|
|
923
|
-
await ctx.sessionController.prompt({ sessionId, requestId: randomUUID(), mode: 'queue',
|
|
923
|
+
await ctx.sessionController.selectModel({ sessionId, provider: teamModelProvider(model), model: model.model_id })
|
|
924
|
+
await ctx.sessionController.prompt({ sessionId, requestId: randomUUID(), mode: 'queue',
|
|
925
|
+
content: [{ type: 'text', text: reviewPrompt(grant, material, task, false) }] }, signal)
|
|
924
926
|
const deadline = Date.now() + MODEL_TIMEOUT_MS
|
|
925
927
|
while (Date.now() < deadline) {
|
|
926
928
|
if (signal.aborted) throw new Error('任务已取消。')
|
|
@@ -1075,6 +1077,8 @@ export function startDelegationHost(ctx, readConnection, { ledger = new Delegati
|
|
|
1075
1077
|
fetchPr: fetchGiteaPr,
|
|
1076
1078
|
preparePrWorkspace: (card, taskId, pr, taskSignal) =>
|
|
1077
1079
|
preparePrWorkspace(ctx, card, taskId, pr, taskSignal, workspaceRoot, readSettings),
|
|
1080
|
+
runDshReview: (entry, pr, model, taskSignal) => runDshPrReview(ctx, pr.target,
|
|
1081
|
+
{ title: pr.title, sha: pr.sha, diff: pr.diff }, model, sessionIds, taskSignal, workspaceRoot, entry),
|
|
1078
1082
|
runNativeReview: (entry, pr, taskSignal, workspacePath) => runNativePrReview(ctx, 'claude-code', {
|
|
1079
1083
|
...pr.target,
|
|
1080
1084
|
}, { title: pr.title, sha: pr.sha, diff: pr.diff }, sessionIds, taskSignal, workspaceRoot,
|
|
@@ -1113,9 +1117,7 @@ export function startDelegationHost(ctx, readConnection, { ledger = new Delegati
|
|
|
1113
1117
|
const credential = await ctx.credentials.resolve('ANT_GITEA_API_TOKEN')
|
|
1114
1118
|
const material = await fetchGiteaPr(auth.grant, credential?.value)
|
|
1115
1119
|
if (executor === 'dsh') {
|
|
1116
|
-
const
|
|
1117
|
-
const model = models.models?.find((item) => item.protocol === 'openai' || item.protocol === 'anthropic')
|
|
1118
|
-
if (!model) throw new Error('当前没有可用的团队模型。')
|
|
1120
|
+
const model = await modelFor(ctx, memberJson, connection)
|
|
1119
1121
|
reply = await runDshPrReview(ctx, auth.grant, material, model, sessionIds, taskSignal, workspaceRoot)
|
|
1120
1122
|
} else {
|
|
1121
1123
|
reply = await runNativePrReview(ctx, executor, auth.grant, material, sessionIds, taskSignal, workspaceRoot, logger)
|
|
@@ -1396,7 +1398,12 @@ export function startDelegationHost(ctx, readConnection, { ledger = new Delegati
|
|
|
1396
1398
|
approval = { code, owner_id: event.actor.id, group_id: groupId, card_id: matches[0].id,
|
|
1397
1399
|
expires_at: Date.now() + 300_000 }
|
|
1398
1400
|
kind = 'group_card_approval_offer'
|
|
1399
|
-
const
|
|
1401
|
+
const reviewers = Array.isArray(matches[0].reviewers) && matches[0].reviewers.length
|
|
1402
|
+
? matches[0].reviewers : [matches[0].runner]
|
|
1403
|
+
const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
|
|
1404
|
+
const runnerDescription = reviewers.length > 1
|
|
1405
|
+
? `普通请求默认由${reviewerLabel(matches[0].runner)}执行;明确说“交叉 review”时并行启动${reviewers.map(reviewerLabel).join('、')}`
|
|
1406
|
+
: matches[0].runner === 'codex' ? '这台电脑的 Codex 专用只读差异审查'
|
|
1400
1407
|
: matches[0].runner === 'claude-code' ? '这台 DSH 已注册的 Claude Code 子代理试用'
|
|
1401
1408
|
: '受限 DSH'
|
|
1402
1409
|
reply = `确认后,本群任何成员可请求“${matches[0].title}”;执行方为${runnerDescription},参数规则沿用私聊任务,结果公开回复原群/原话题,授权最长 7 天。请在 5 分钟内 @我回复“确认启用 ${code}”。`
|
|
@@ -1425,7 +1432,7 @@ export function startDelegationHost(ctx, readConnection, { ledger = new Delegati
|
|
|
1425
1432
|
method: 'POST', body: JSON.stringify({ bot_id: event.botId, bot_fingerprint: event.fingerprint,
|
|
1426
1433
|
title: source.title, intent: source.intent, examples: source.examples,
|
|
1427
1434
|
parameters: source.parameters, task_prompt: source.task_prompt,
|
|
1428
|
-
runner: source.runner, workspace: null, sender_scope: 'any', sender_ids: [],
|
|
1435
|
+
runner: source.runner, reviewers: source.reviewers, workspace: null, sender_scope: 'any', sender_ids: [],
|
|
1429
1436
|
conversation_kind: 'group', conversation_id: groupId,
|
|
1430
1437
|
expires_at: Math.min(source.expires_at, Math.floor(Date.now() / 1000) + 7 * 86400) }),
|
|
1431
1438
|
})
|
package/index.js
CHANGED
|
@@ -193,7 +193,7 @@ async function fetchMemberModels(request, credentials, settings) {
|
|
|
193
193
|
if (!Array.isArray(result?.models) || normalizeServiceUrl(result.service_url) !== serviceUrl) {
|
|
194
194
|
return jsonResponse(502, { message: 'ANT 团队模型目录与已连接服务不匹配。' })
|
|
195
195
|
}
|
|
196
|
-
return jsonResponse(200, { service_url: serviceUrl, models: result.models })
|
|
196
|
+
return jsonResponse(200, { service_url: serviceUrl, models: result.models, default_model: result.default_model ?? null })
|
|
197
197
|
} catch {
|
|
198
198
|
return jsonResponse(502, { message: '无法连接 ANT 团队服务' })
|
|
199
199
|
}
|
|
@@ -362,7 +362,7 @@ async function fetchDelegationStatus(request, credentials, settings) {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
async function fetchDelegationCards(request, ctx, credentials, settings) {
|
|
365
|
-
if (!['GET', 'POST', 'DELETE'].includes(request.method)) return jsonResponse(405, { message: '请求方式不支持。' })
|
|
365
|
+
if (!['GET', 'POST', 'PUT', 'DELETE'].includes(request.method)) return jsonResponse(405, { message: '请求方式不支持。' })
|
|
366
366
|
let connection
|
|
367
367
|
try { connection = await readMemberConnection(credentials, settings) } catch {
|
|
368
368
|
return jsonResponse(500, { message: '无法读取 ANT 连接。' })
|
|
@@ -376,7 +376,7 @@ async function fetchDelegationCards(request, ctx, credentials, settings) {
|
|
|
376
376
|
body = JSON.parse(raw)
|
|
377
377
|
} catch { return jsonResponse(400, { message: '任务设置格式无效。' }) }
|
|
378
378
|
}
|
|
379
|
-
if (request.method
|
|
379
|
+
if (['POST', 'PUT'].includes(request.method)) {
|
|
380
380
|
const service = dshImService(ctx)
|
|
381
381
|
if (!service?.describeBot) return jsonResponse(503, { message: '请先安装并启用 DSH-IM。' })
|
|
382
382
|
try {
|
|
@@ -390,18 +390,28 @@ async function fetchDelegationCards(request, ctx, credentials, settings) {
|
|
|
390
390
|
if (request.method === 'DELETE' && (typeof body?.id !== 'string' || !/^[0-9a-f-]{36}$/iu.test(body.id))) {
|
|
391
391
|
return jsonResponse(400, { message: '任务编号无效。' })
|
|
392
392
|
}
|
|
393
|
+
if ((request.method === 'POST' && body?.id !== undefined)
|
|
394
|
+
|| (request.method === 'PUT' && (typeof body?.id !== 'string' || !/^[0-9a-f-]{36}$/iu.test(body.id)))) {
|
|
395
|
+
return jsonResponse(400, { message: '任务编号无效。' })
|
|
396
|
+
}
|
|
393
397
|
const path = request.method === 'DELETE'
|
|
394
398
|
? `/api/me/im-delegations/cards/${encodeURIComponent(body.id)}` : '/api/me/im-delegations/cards'
|
|
399
|
+
const payload = body
|
|
395
400
|
try {
|
|
396
401
|
const response = await fetch(new URL(path, connection.serviceUrl), {
|
|
397
402
|
method: request.method,
|
|
398
403
|
headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json',
|
|
399
404
|
...(request.method !== 'GET' ? { origin: new URL(connection.serviceUrl).origin } : {}),
|
|
400
|
-
...(request.method
|
|
401
|
-
...(request.method
|
|
405
|
+
...(['POST', 'PUT'].includes(request.method) ? { 'content-type': 'application/json' } : {}) },
|
|
406
|
+
...(['POST', 'PUT'].includes(request.method) ? { body: JSON.stringify(payload) } : {}),
|
|
402
407
|
redirect: 'manual', signal: AbortSignal.timeout(12_000),
|
|
403
408
|
})
|
|
404
|
-
if (response.status === 404 &&
|
|
409
|
+
if (response.status === 404 && ['GET', 'POST'].includes(request.method)) {
|
|
410
|
+
return jsonResponse(426, { message: '团队服务端尚未支持自定义任务,请联系管理员升级。' })
|
|
411
|
+
}
|
|
412
|
+
if (response.status === 405 && request.method === 'PUT') {
|
|
413
|
+
return jsonResponse(426, { message: '团队服务端尚未支持能力编辑,请联系管理员升级 ANT 服务端。' })
|
|
414
|
+
}
|
|
405
415
|
if (response.status === 204) return new Response(null, { status: 204 })
|
|
406
416
|
const text = await readLimitedBody(response, request.method === 'GET' ? 8 * 1024 * 1024 : MAX_MODEL_RESPONSE_BYTES)
|
|
407
417
|
if (text === null) return jsonResponse(502, { message: '团队服务响应过大。' })
|
|
@@ -962,7 +972,7 @@ export function apply(ctx) {
|
|
|
962
972
|
fetch: (request) => fetchDelegationStatus(request, connectionCtx.credentials, connectionCtx.settings),
|
|
963
973
|
}), 'ant-teams: local delegated task status')
|
|
964
974
|
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
965
|
-
path: DELEGATION_CARDS_PATH, methods: ['GET', 'POST', 'DELETE'], requestBody: 'buffered',
|
|
975
|
+
path: DELEGATION_CARDS_PATH, methods: ['GET', 'POST', 'PUT', 'DELETE'], requestBody: 'buffered',
|
|
966
976
|
fetch: (request) => fetchDelegationCards(request, connectionCtx, connectionCtx.credentials, connectionCtx.settings),
|
|
967
977
|
}), 'ant-teams: member task cards')
|
|
968
978
|
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|