@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 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) => `📝 里程碑 M${p.milestoneIndex ?? '?'} 已提交\n订单: ${p.orderId || body?.orderId || '?'}\n等待审核`,
302
- 'milestone_verified': (p) => `✅ 里程碑 M${p.milestoneIndex ?? '?'} 审核通过\n订单: ${p.orderId || body?.orderId || '?'}`,
303
- 'milestone_rejected': (p) => `❌ 里程碑 M${p.milestoneIndex ?? '?'} 被拒绝\n订单: ${p.orderId || body?.orderId || '?'}\n原因: ${p.rejectReason || '未说明'}`,
304
- 'order_settled': (p) => `💰 订单已结算完成\n订单: ${p.orderId || body?.orderId || '?'}\nUSDC 已支付`,
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 expectedStatus = command[1] === 'milestone-verify' ? 'submitted' : 'pending';
480
- if (milestone.status !== expectedStatus) {
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 || '').substring(0, 400),
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 = `reconcile:${orderId}:executor:${currentIndex}`;
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: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
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 = `reconcile:${orderId}:requester:${submittedMilestone.index}:${submittedMilestone.submitCount || 0}`;
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
- // Build argv array
3322
- const parsedCmd = agentCmd.trim().split(/\s+/);
3323
- parsedCmd.push(enrichedPrompt);
3324
-
3325
- // Queue the hook (serialize to avoid session lock conflicts)
3326
- hookQueue.push({ event, dedupeKey, cmd: parsedCmd[0], args: parsedCmd.slice(1), cwd: hookCwd, payload, recoveryKey: '' });
3327
- if (!hookBusy) processHookQueue();
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 runHook = (attempt) => {
3360
- execFile(spawnCmd, spawnArgs, { timeout: 600000, cwd: hookCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
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
- log({ event: 'agent_cmd_done', eventType: hookEvent, stdout: (stdout || '').substring(0, 300) });
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
- return [
22
- 'p2p_task',
23
- 'milestone_plan_confirmed',
24
- 'milestone_submitted',
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawrenceliang-btc/atel-sdk",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",