@lawrenceliang-btc/atel-sdk 1.1.9 → 1.1.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/bin/atel.mjs +339 -20
- package/bin/notification-action-helpers.mjs +4 -7
- package/package.json +1 -1
package/bin/atel.mjs
CHANGED
|
@@ -192,6 +192,20 @@ function sanitizeAgentPrompt(promptText, meta = {}) {
|
|
|
192
192
|
return truncated;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
function isKnownUpstreamModelInputError(text) {
|
|
196
|
+
const value = String(text || '');
|
|
197
|
+
return value.includes('InternalError.Algo.InvalidParameter')
|
|
198
|
+
|| value.includes('Range of input length should be [1, 258048]');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function summarizeAgentOutput(text, maxChars = 300) {
|
|
202
|
+
const raw = String(text || '');
|
|
203
|
+
const trimmed = raw.trim();
|
|
204
|
+
if (!trimmed) return '';
|
|
205
|
+
if (isKnownUpstreamModelInputError(trimmed)) return '[suppressed upstream model input-length error]';
|
|
206
|
+
return trimmed.substring(0, maxChars);
|
|
207
|
+
}
|
|
208
|
+
|
|
195
209
|
// ═══════════════════════════════════════════════════════════════════
|
|
196
210
|
// Notification Target System — auto-discover gateway, manage targets
|
|
197
211
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -298,10 +312,24 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
298
312
|
const templates = {
|
|
299
313
|
'order_created': (p) => `📥 收到新订单\n订单: ${p.orderId || body?.orderId || '?'}\n金额: $${p.priceAmount ?? '?'} USDC\n来自: ${p.requesterDid || '未知请求方'}\n请审核后决定是否接单`,
|
|
300
314
|
'order_accepted': (p) => `📋 订单已被接单\n订单: ${p.orderId || body?.orderId || '?'}\n执行方已开始处理,进入里程碑阶段`,
|
|
301
|
-
'milestone_submitted': (p) =>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
315
|
+
'milestone_submitted': (p) => {
|
|
316
|
+
const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
|
|
317
|
+
const content = p.resultSummary ? `\n提交内容: ${String(p.resultSummary).substring(0, 200)}` : '';
|
|
318
|
+
return `📝 里程碑 M${p.milestoneIndex ?? '?'} 已提交\n订单: ${p.orderId || body?.orderId || '?'}${desc}${content}\n等待审核`;
|
|
319
|
+
},
|
|
320
|
+
'milestone_verified': (p) => {
|
|
321
|
+
const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
|
|
322
|
+
const progress = p.totalMilestones ? `\n进度: ${(p.milestoneIndex ?? 0) + 1}/${p.totalMilestones}` : '';
|
|
323
|
+
return `✅ 里程碑 M${p.milestoneIndex ?? '?'} 审核通过\n订单: ${p.orderId || body?.orderId || '?'}${desc}${progress}`;
|
|
324
|
+
},
|
|
325
|
+
'milestone_rejected': (p) => {
|
|
326
|
+
const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
|
|
327
|
+
return `❌ 里程碑 M${p.milestoneIndex ?? '?'} 被拒绝\n订单: ${p.orderId || body?.orderId || '?'}${desc}\n原因: ${p.rejectReason || '未说明'}`;
|
|
328
|
+
},
|
|
329
|
+
'order_settled': (p) => {
|
|
330
|
+
const amount = p.priceAmount ? `\n金额: $${p.priceAmount} USDC` : '';
|
|
331
|
+
return `💰 订单已结算完成\n订单: ${p.orderId || body?.orderId || '?'}${amount}\nUSDC 已支付`;
|
|
332
|
+
},
|
|
305
333
|
};
|
|
306
334
|
const tmpl = templates[eventType];
|
|
307
335
|
if (!tmpl) return;
|
|
@@ -440,6 +468,18 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
|
|
|
440
468
|
if (command.length === 0) {
|
|
441
469
|
return { ok: false, skipped: true, reason: 'empty_command' };
|
|
442
470
|
}
|
|
471
|
+
const actionKey = JSON.stringify(command);
|
|
472
|
+
if (globalThis.__atelActiveDirectActionKeys?.has(actionKey)) {
|
|
473
|
+
log({
|
|
474
|
+
event: 'recommended_action_direct_skip',
|
|
475
|
+
eventType,
|
|
476
|
+
dedupeKey,
|
|
477
|
+
action: action.action,
|
|
478
|
+
command,
|
|
479
|
+
reason: 'inflight_duplicate',
|
|
480
|
+
});
|
|
481
|
+
return { ok: true, skipped: true, reason: 'inflight_duplicate' };
|
|
482
|
+
}
|
|
443
483
|
|
|
444
484
|
// Idempotency guard: short-circuit duplicate milestone plan/submit/verify actions
|
|
445
485
|
// if the order or milestone has already advanced past the required state.
|
|
@@ -476,8 +516,10 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
|
|
|
476
516
|
}
|
|
477
517
|
const milestone = needsMilestoneIndex && Array.isArray(state?.milestones) ? state.milestones.find((m) => m.index === index) : null;
|
|
478
518
|
if (needsMilestoneIndex && milestone) {
|
|
479
|
-
const
|
|
480
|
-
|
|
519
|
+
const expectedStatuses = command[1] === 'milestone-verify'
|
|
520
|
+
? ['submitted']
|
|
521
|
+
: (eventType === 'milestone_rejected' ? ['pending', 'rejected'] : ['pending']);
|
|
522
|
+
if (!expectedStatuses.includes(milestone.status)) {
|
|
481
523
|
log({
|
|
482
524
|
event: 'recommended_action_direct_skip',
|
|
483
525
|
eventType,
|
|
@@ -509,10 +551,65 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
|
|
|
509
551
|
const childCwd = command[0] === 'atel' ? getAtelWorkspaceRoot() : cwd;
|
|
510
552
|
|
|
511
553
|
log({ event: 'recommended_action_direct_trigger', eventType, dedupeKey, action: action.action, command });
|
|
554
|
+
globalThis.__atelActiveDirectActionKeys ??= new Set();
|
|
555
|
+
globalThis.__atelActiveDirectActionKeys.add(actionKey);
|
|
512
556
|
|
|
513
557
|
return await new Promise((resolve) => {
|
|
514
558
|
execFile(childCmd, childArgs, { timeout: 180000, cwd: childCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
559
|
+
globalThis.__atelActiveDirectActionKeys?.delete(actionKey);
|
|
515
560
|
if (err) {
|
|
561
|
+
const combinedErrorText = String(stderr || err.message || '');
|
|
562
|
+
if (
|
|
563
|
+
command[0] === 'atel' &&
|
|
564
|
+
command[1] === 'milestone-feedback' &&
|
|
565
|
+
command.includes('--approve') &&
|
|
566
|
+
combinedErrorText.includes('order not in milestone_review status')
|
|
567
|
+
) {
|
|
568
|
+
log({
|
|
569
|
+
event: 'recommended_action_direct_skip',
|
|
570
|
+
eventType,
|
|
571
|
+
dedupeKey,
|
|
572
|
+
action: action.action,
|
|
573
|
+
command,
|
|
574
|
+
reason: 'order_status_executing',
|
|
575
|
+
});
|
|
576
|
+
resolve({ ok: true, skipped: true, reason: 'order_status_executing' });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (
|
|
580
|
+
command[0] === 'atel' &&
|
|
581
|
+
command[1] === 'milestone-submit' &&
|
|
582
|
+
combinedErrorText.includes('milestone cannot be submitted in status: submitted')
|
|
583
|
+
) {
|
|
584
|
+
log({
|
|
585
|
+
event: 'recommended_action_direct_skip',
|
|
586
|
+
eventType,
|
|
587
|
+
dedupeKey,
|
|
588
|
+
action: action.action,
|
|
589
|
+
command,
|
|
590
|
+
reason: 'milestone_status_submitted',
|
|
591
|
+
});
|
|
592
|
+
resolve({ ok: true, skipped: true, reason: 'milestone_status_submitted' });
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (
|
|
596
|
+
command[0] === 'atel' &&
|
|
597
|
+
command[1] === 'milestone-verify' &&
|
|
598
|
+
(combinedErrorText.includes('milestone cannot be verified in status: verified') ||
|
|
599
|
+
combinedErrorText.includes('milestone not in submitted status') ||
|
|
600
|
+
combinedErrorText.includes('milestone cannot be verified in status: settled'))
|
|
601
|
+
) {
|
|
602
|
+
log({
|
|
603
|
+
event: 'recommended_action_direct_skip',
|
|
604
|
+
eventType,
|
|
605
|
+
dedupeKey,
|
|
606
|
+
action: action.action,
|
|
607
|
+
command,
|
|
608
|
+
reason: 'milestone_already_processed',
|
|
609
|
+
});
|
|
610
|
+
resolve({ ok: true, skipped: true, reason: 'milestone_already_processed' });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
516
613
|
log({
|
|
517
614
|
event: 'recommended_action_direct_error',
|
|
518
615
|
eventType,
|
|
@@ -532,7 +629,7 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
|
|
|
532
629
|
dedupeKey,
|
|
533
630
|
action: action.action,
|
|
534
631
|
command,
|
|
535
|
-
stdout: (stdout
|
|
632
|
+
stdout: summarizeAgentOutput(stdout, 400),
|
|
536
633
|
});
|
|
537
634
|
resolve({ ok: true, stdout });
|
|
538
635
|
});
|
|
@@ -2904,6 +3001,178 @@ ${callbackFailed}
|
|
|
2904
3001
|
`;
|
|
2905
3002
|
}
|
|
2906
3003
|
|
|
3004
|
+
function buildLocalAgentPrompt(eventType, promptText) {
|
|
3005
|
+
if (eventType === 'milestone_submitted') {
|
|
3006
|
+
return `${promptText}
|
|
3007
|
+
|
|
3008
|
+
重要:你不是在和用户聊天。
|
|
3009
|
+
不要输出 markdown、代码块、解释、分析过程。
|
|
3010
|
+
你必须只输出一行 JSON。
|
|
3011
|
+
|
|
3012
|
+
通过时:
|
|
3013
|
+
{"decision":"pass","summary":"简短通过原因"}
|
|
3014
|
+
|
|
3015
|
+
拒绝时:
|
|
3016
|
+
{"decision":"reject","reason":"具体拒绝原因","summary":"简短审核结论"}`;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
3020
|
+
return `${promptText}
|
|
3021
|
+
|
|
3022
|
+
重要:你不是在和用户聊天。
|
|
3023
|
+
不要输出 markdown、标题、项目符号、解释、分析过程。
|
|
3024
|
+
你必须只输出一行 JSON。
|
|
3025
|
+
|
|
3026
|
+
格式:
|
|
3027
|
+
{"result":"当前里程碑的真实交付内容"}`;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
return promptText;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
function normalizeLocalAgentStdout(stdout) {
|
|
3034
|
+
const text = String(stdout || '').trim();
|
|
3035
|
+
if (!text) return '';
|
|
3036
|
+
try {
|
|
3037
|
+
const parsed = JSON.parse(text);
|
|
3038
|
+
if (Array.isArray(parsed?.payloads)) {
|
|
3039
|
+
for (const item of parsed.payloads) {
|
|
3040
|
+
const candidate = String(item?.text || '').trim();
|
|
3041
|
+
if (candidate) return candidate;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
if (typeof parsed?.text === 'string' && parsed.text.trim()) return parsed.text.trim();
|
|
3045
|
+
if (typeof parsed?.result === 'string' && parsed.result.trim()) return JSON.stringify({ result: parsed.result.trim() });
|
|
3046
|
+
if (typeof parsed?.decision === 'string') return JSON.stringify(parsed);
|
|
3047
|
+
} catch {}
|
|
3048
|
+
const fencedJson = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3049
|
+
if (fencedJson?.[1]) return fencedJson[1].trim();
|
|
3050
|
+
const jsonObjects = text.match(/\{[\s\S]*\}/g);
|
|
3051
|
+
if (jsonObjects?.length) {
|
|
3052
|
+
for (let i = jsonObjects.length - 1; i >= 0; i -= 1) {
|
|
3053
|
+
const candidate = jsonObjects[i].trim();
|
|
3054
|
+
try {
|
|
3055
|
+
const parsed = JSON.parse(candidate);
|
|
3056
|
+
if (Array.isArray(parsed?.payloads)) {
|
|
3057
|
+
for (const item of parsed.payloads) {
|
|
3058
|
+
const nested = String(item?.text || '').trim();
|
|
3059
|
+
if (nested) return nested;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
if (typeof parsed?.text === 'string' && parsed.text.trim()) return parsed.text.trim();
|
|
3063
|
+
if (typeof parsed?.result === 'string' && parsed.result.trim()) return JSON.stringify({ result: parsed.result.trim() });
|
|
3064
|
+
if (typeof parsed?.decision === 'string') return JSON.stringify(parsed);
|
|
3065
|
+
return candidate;
|
|
3066
|
+
} catch {}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
const jsonLines = text.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
3070
|
+
for (let i = jsonLines.length - 1; i >= 0; i -= 1) {
|
|
3071
|
+
const candidate = jsonLines[i];
|
|
3072
|
+
if (!(candidate.startsWith('{') && candidate.endsWith('}'))) continue;
|
|
3073
|
+
try {
|
|
3074
|
+
JSON.parse(candidate);
|
|
3075
|
+
return candidate;
|
|
3076
|
+
} catch {}
|
|
3077
|
+
}
|
|
3078
|
+
return text;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
function normalizeResult(value) {
|
|
3082
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function sanitizeHookSessionId(value) {
|
|
3086
|
+
const raw = String(value || '').trim();
|
|
3087
|
+
if (!raw) return `atel-hook-${Date.now()}`;
|
|
3088
|
+
const cleaned = raw.replace(/[^a-zA-Z0-9._:-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
3089
|
+
return (cleaned || `atel-hook-${Date.now()}`).slice(0, 120);
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
function isOpenClawAgentInvocation(cmd, args = []) {
|
|
3093
|
+
const argv = [String(cmd || ''), ...args.map((v) => String(v || ''))];
|
|
3094
|
+
if (argv[0] === 'openclaw') return argv[1] === 'agent';
|
|
3095
|
+
if (argv[0] === 'npx') return argv[1] === 'openclaw' && argv[2] === 'agent';
|
|
3096
|
+
if (argv[0] === 'node') return argv.includes('agent') && argv.some((v) => /openclaw/i.test(v));
|
|
3097
|
+
return false;
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
function prepareHookInvocation(cmd, args = [], hookKey, timeoutSeconds) {
|
|
3101
|
+
const nextArgs = [...args];
|
|
3102
|
+
if (!isOpenClawAgentInvocation(cmd, nextArgs)) return { cmd, args: nextArgs };
|
|
3103
|
+
|
|
3104
|
+
if (!nextArgs.includes('--json')) {
|
|
3105
|
+
const messageIndex = nextArgs.lastIndexOf('-m');
|
|
3106
|
+
const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
|
|
3107
|
+
nextArgs.splice(insertAt, 0, '--json');
|
|
3108
|
+
}
|
|
3109
|
+
if (!nextArgs.includes('--session-id')) {
|
|
3110
|
+
const messageIndex = nextArgs.lastIndexOf('-m');
|
|
3111
|
+
const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
|
|
3112
|
+
nextArgs.splice(insertAt, 0, '--session-id', sanitizeHookSessionId(hookKey));
|
|
3113
|
+
}
|
|
3114
|
+
if (!nextArgs.includes('--timeout')) {
|
|
3115
|
+
const messageIndex = nextArgs.lastIndexOf('-m');
|
|
3116
|
+
const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
|
|
3117
|
+
nextArgs.splice(insertAt, 0, '--timeout', String(timeoutSeconds));
|
|
3118
|
+
}
|
|
3119
|
+
if (!nextArgs.includes('--thinking')) {
|
|
3120
|
+
const messageIndex = nextArgs.lastIndexOf('-m');
|
|
3121
|
+
const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
|
|
3122
|
+
nextArgs.splice(insertAt, 0, '--thinking', 'minimal');
|
|
3123
|
+
}
|
|
3124
|
+
return { cmd, args: nextArgs };
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
function buildLocalAgentActionFromStdout(eventType, payload, stdout) {
|
|
3128
|
+
const cleaned = normalizeLocalAgentStdout(stdout);
|
|
3129
|
+
if (!cleaned) return { ok: false, error: 'empty_local_agent_stdout' };
|
|
3130
|
+
|
|
3131
|
+
if (eventType === 'milestone_submitted') {
|
|
3132
|
+
try {
|
|
3133
|
+
const parsed = JSON.parse(cleaned);
|
|
3134
|
+
return buildAgentCallbackAction(eventType, payload, parsed);
|
|
3135
|
+
} catch {
|
|
3136
|
+
const lowered = cleaned.toLowerCase();
|
|
3137
|
+
if (lowered.startsWith('pass') || cleaned.includes('通过')) {
|
|
3138
|
+
return buildAgentCallbackAction(eventType, payload, { decision: 'pass', summary: cleaned });
|
|
3139
|
+
}
|
|
3140
|
+
if (lowered.startsWith('reject') || cleaned.includes('拒绝')) {
|
|
3141
|
+
return buildAgentCallbackAction(eventType, payload, { decision: 'reject', reason: cleaned, summary: cleaned });
|
|
3142
|
+
}
|
|
3143
|
+
return { ok: false, error: 'invalid_local_review_stdout' };
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
3148
|
+
try {
|
|
3149
|
+
const parsed = JSON.parse(cleaned);
|
|
3150
|
+
return buildAgentCallbackAction(eventType, payload, parsed);
|
|
3151
|
+
} catch {
|
|
3152
|
+
return buildAgentCallbackAction(eventType, payload, { result: cleaned, summary: cleaned });
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
return buildAgentCallbackAction(eventType, payload, { result: cleaned, summary: cleaned });
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
function buildMilestoneHookRecoveryKey(eventType, payload = {}) {
|
|
3160
|
+
const orderId = String(payload?.orderId || '').trim();
|
|
3161
|
+
if (!orderId) return '';
|
|
3162
|
+
if (eventType === 'milestone_submitted') {
|
|
3163
|
+
const stage = Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0;
|
|
3164
|
+
const submitCount = Number.isFinite(Number(payload?.submitCount)) ? Number(payload.submitCount) : 0;
|
|
3165
|
+
return `stage:${orderId}:requester:${stage}:${submitCount}`;
|
|
3166
|
+
}
|
|
3167
|
+
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
3168
|
+
const stage = eventType === 'milestone_plan_confirmed'
|
|
3169
|
+
? (Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0)
|
|
3170
|
+
: (Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0);
|
|
3171
|
+
return `stage:${orderId}:executor:${stage}`;
|
|
3172
|
+
}
|
|
3173
|
+
return '';
|
|
3174
|
+
}
|
|
3175
|
+
|
|
2907
3176
|
async function runGatewayAgentTask(eventType, dedupeKey, promptText, cwd, payload) {
|
|
2908
3177
|
const gw = discoverGateway();
|
|
2909
3178
|
const cfg = loadOpenClawConfig();
|
|
@@ -3074,9 +3343,10 @@ ${callbackFailed}
|
|
|
3074
3343
|
const promptText = currentIndex === 0
|
|
3075
3344
|
? `你是ATEL接单方Agent。双方已确认方案,开始执行。\n订单原始要求:${orderDescription || '未提供'}\n当前里程碑 M0:${currentMilestone.title || ''}\n请只围绕这个订单要求完成当前里程碑,并通过回调返回最终交付内容。`
|
|
3076
3345
|
: `你是ATEL接单方Agent。M${currentIndex - 1} 已通过审核。\n订单原始要求:${orderDescription || '未提供'}\n下一个里程碑 M${currentIndex}:${currentMilestone.title || ''}\n前面已通过的阶段结果如下:\n${previousApprovedOutputs || '无'}\n\n请严格基于这些已通过结果推进当前里程碑,不要自行假设缺失材料,也不要读取本地共享文件来补上下文。完成后通过回调返回最终交付内容。`;
|
|
3077
|
-
const recoveryKey =
|
|
3346
|
+
const recoveryKey = buildMilestoneHookRecoveryKey(eventType, payload);
|
|
3347
|
+
log({ event: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
|
|
3078
3348
|
const queued = queueAgentHook(eventType, recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
|
|
3079
|
-
if (queued) log({ event: '
|
|
3349
|
+
if (queued) log({ event: 'trade_reconcile_executor_queued', orderId, currentMilestone: currentIndex, recoveryKey });
|
|
3080
3350
|
return;
|
|
3081
3351
|
}
|
|
3082
3352
|
|
|
@@ -3106,7 +3376,7 @@ ${callbackFailed}
|
|
|
3106
3376
|
previousApprovedOutputs,
|
|
3107
3377
|
};
|
|
3108
3378
|
const promptText = `你是ATEL发单方Agent,需要审核执行方提交的工作。\n订单原始要求:${orderDescription || '未提供'}\n里程碑目标:${submittedMilestone.title || ''}\n前面已通过的阶段结果如下:\n${previousApprovedOutputs || '无'}\n提交内容:${submittedMilestone.resultSummary || ''}\n请只按该订单要求和前序已通过结果审慎决定通过还是拒绝,并通过回调返回 decision=pass 或 decision=reject。`;
|
|
3109
|
-
const recoveryKey =
|
|
3379
|
+
const recoveryKey = buildMilestoneHookRecoveryKey('milestone_submitted', payload);
|
|
3110
3380
|
const queued = queueAgentHook('milestone_submitted', recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
|
|
3111
3381
|
if (queued) log({ event: 'trade_reconcile_requester', orderId, milestoneIndex: submittedMilestone.index, recoveryKey });
|
|
3112
3382
|
}
|
|
@@ -3299,6 +3569,12 @@ ${callbackFailed}
|
|
|
3299
3569
|
const autoTriggerEvents = ['order_accepted', 'milestone_plan_confirmed', 'milestone_submitted', 'milestone_verified', 'milestone_rejected'];
|
|
3300
3570
|
const hasGatewayAction = Array.isArray(recommendedActions) && recommendedActions.some((action) => Array.isArray(action?.command) && action.command[0] === 'atel');
|
|
3301
3571
|
if (agentCmd && prompt && autoTriggerEvents.includes(event) && !shouldSkipAgentHook(event, directExecutionSucceeded)) {
|
|
3572
|
+
const needsActionablePayload = event !== 'milestone_submitted';
|
|
3573
|
+
if (needsActionablePayload && !hasGatewayAction) {
|
|
3574
|
+
log({ event: 'agent_hook_skip_no_action', eventType: event, dedupeKey, reason: 'informational_only_payload' });
|
|
3575
|
+
res.json({ status: 'received', eventId, eventType: event });
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3302
3578
|
if (shouldUseGatewaySession(event) && !hasGatewayAction) {
|
|
3303
3579
|
log({ event: 'agent_hook_skip_no_action', eventType: event, dedupeKey, reason: 'informational_only_payload' });
|
|
3304
3580
|
res.json({ status: 'received', eventId, eventType: event });
|
|
@@ -3318,13 +3594,17 @@ ${callbackFailed}
|
|
|
3318
3594
|
} else {
|
|
3319
3595
|
processedEvents.add('hook:' + dedupeKey);
|
|
3320
3596
|
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3597
|
+
const queued = queueAgentHook(
|
|
3598
|
+
event,
|
|
3599
|
+
dedupeKey,
|
|
3600
|
+
enrichedPrompt,
|
|
3601
|
+
hookCwd,
|
|
3602
|
+
payload,
|
|
3603
|
+
{ recoveryKey: buildMilestoneHookRecoveryKey(event, payload) },
|
|
3604
|
+
);
|
|
3605
|
+
if (!queued) {
|
|
3606
|
+
log({ event: 'agent_cmd_dedup_recovery_key', eventType: event, dedupeKey });
|
|
3607
|
+
}
|
|
3328
3608
|
}
|
|
3329
3609
|
}
|
|
3330
3610
|
|
|
@@ -3353,11 +3633,18 @@ ${callbackFailed}
|
|
|
3353
3633
|
return;
|
|
3354
3634
|
}
|
|
3355
3635
|
log({ event: 'agent_session_spawn_error', eventType: hookEvent, dedupeKey: hookKey, error: gatewayResult.error, fallback: 'cli' });
|
|
3636
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
3637
|
+
} else if (spawnArgs.length > 0) {
|
|
3638
|
+
const promptArg = spawnArgs[spawnArgs.length - 1] || '';
|
|
3639
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
3356
3640
|
}
|
|
3357
3641
|
|
|
3358
3642
|
const MAX_ATTEMPTS = 5;
|
|
3359
|
-
const
|
|
3360
|
-
|
|
3643
|
+
const isMilestoneHook = ['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected', 'milestone_submitted'].includes(hookEvent);
|
|
3644
|
+
const localHookTimeoutMs = isMilestoneHook ? 180000 : 600000;
|
|
3645
|
+
const preparedInvocation = prepareHookInvocation(spawnCmd, spawnArgs, hookKey, Math.ceil(localHookTimeoutMs / 1000));
|
|
3646
|
+
const runHook = (attempt, invocation = preparedInvocation) => {
|
|
3647
|
+
execFile(invocation.cmd, invocation.args, { timeout: localHookTimeoutMs, cwd: hookCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
3361
3648
|
const errMsg = (err?.message || '') + (stderr || '');
|
|
3362
3649
|
const isSessionLock = errMsg.includes('session file locked') || errMsg.includes('session locked');
|
|
3363
3650
|
const isNetworkError = err && (err.killed || err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET');
|
|
@@ -3373,8 +3660,40 @@ ${callbackFailed}
|
|
|
3373
3660
|
} else if (err) {
|
|
3374
3661
|
log({ event: 'agent_cmd_error', eventType: hookEvent, error: err.message, stderr: (stderr || '').substring(0, 200) });
|
|
3375
3662
|
finishHook();
|
|
3663
|
+
} else if (isKnownUpstreamModelInputError(stdout)) {
|
|
3664
|
+
log({
|
|
3665
|
+
event: 'agent_cmd_upstream_input_error',
|
|
3666
|
+
eventType: hookEvent,
|
|
3667
|
+
dedupeKey: hookKey,
|
|
3668
|
+
note: 'suppressed known upstream model input-length error',
|
|
3669
|
+
});
|
|
3670
|
+
finishHook();
|
|
3376
3671
|
} else {
|
|
3377
|
-
|
|
3672
|
+
const localAction = buildLocalAgentActionFromStdout(hookEvent, hookPayload || {}, stdout);
|
|
3673
|
+
if (!localAction.ok && localAction.error === 'empty_local_agent_stdout' && invocation.args.includes('--json')) {
|
|
3674
|
+
const retryArgs = invocation.args.filter((arg) => arg !== '--json');
|
|
3675
|
+
log({ event: 'agent_cmd_retry_without_json', eventType: hookEvent, dedupeKey: hookKey });
|
|
3676
|
+
setTimeout(() => runHook(attempt + 1, { ...invocation, args: retryArgs }), 1000);
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
if (localAction.ok && !localAction.skipped) {
|
|
3680
|
+
executeRecommendedActionDirect(hookEvent, localAction.action, hookCwd || process.cwd(), hookKey)
|
|
3681
|
+
.then((execResult) => {
|
|
3682
|
+
if (execResult.ok) {
|
|
3683
|
+
log({ event: 'agent_cmd_done', eventType: hookEvent, mode: 'local_stdout_action', dedupeKey: hookKey, stdout: summarizeAgentOutput(stdout, 200) });
|
|
3684
|
+
} else {
|
|
3685
|
+
log({ event: 'agent_cmd_local_action_error', eventType: hookEvent, dedupeKey: hookKey, error: execResult.error || 'local_action_failed', stdout: summarizeAgentOutput(stdout, 200) });
|
|
3686
|
+
}
|
|
3687
|
+
finishHook();
|
|
3688
|
+
})
|
|
3689
|
+
.catch((e) => {
|
|
3690
|
+
log({ event: 'agent_cmd_local_action_error', eventType: hookEvent, dedupeKey: hookKey, error: e.message || 'local_action_failed', stdout: summarizeAgentOutput(stdout, 200) });
|
|
3691
|
+
finishHook();
|
|
3692
|
+
});
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
log({ event: 'agent_cmd_done', eventType: hookEvent, stdout: summarizeAgentOutput(stdout, 300) });
|
|
3378
3697
|
finishHook();
|
|
3379
3698
|
}
|
|
3380
3699
|
});
|
|
@@ -18,13 +18,10 @@ export function shouldSkipAgentHook(eventType, directExecutionSucceeded) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function shouldUseGatewaySession(eventType) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'milestone_verified',
|
|
26
|
-
'milestone_rejected',
|
|
27
|
-
].includes(eventType);
|
|
21
|
+
// Keep gateway sub-sessions only for explicit P2P task execution.
|
|
22
|
+
// Milestone automation must not depend on gateway callback health; use the
|
|
23
|
+
// local structured fallback so order progression cannot stall on subagent I/O.
|
|
24
|
+
return eventType === 'p2p_task';
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
export function normalizeGatewayBind(bind) {
|