@lawrenceliang-btc/atel-sdk 1.1.9 → 1.1.10

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
  // ═══════════════════════════════════════════════════════════════════
@@ -440,6 +454,18 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
440
454
  if (command.length === 0) {
441
455
  return { ok: false, skipped: true, reason: 'empty_command' };
442
456
  }
457
+ const actionKey = JSON.stringify(command);
458
+ if (globalThis.__atelActiveDirectActionKeys?.has(actionKey)) {
459
+ log({
460
+ event: 'recommended_action_direct_skip',
461
+ eventType,
462
+ dedupeKey,
463
+ action: action.action,
464
+ command,
465
+ reason: 'inflight_duplicate',
466
+ });
467
+ return { ok: true, skipped: true, reason: 'inflight_duplicate' };
468
+ }
443
469
 
444
470
  // Idempotency guard: short-circuit duplicate milestone plan/submit/verify actions
445
471
  // if the order or milestone has already advanced past the required state.
@@ -476,8 +502,10 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
476
502
  }
477
503
  const milestone = needsMilestoneIndex && Array.isArray(state?.milestones) ? state.milestones.find((m) => m.index === index) : null;
478
504
  if (needsMilestoneIndex && milestone) {
479
- const expectedStatus = command[1] === 'milestone-verify' ? 'submitted' : 'pending';
480
- if (milestone.status !== expectedStatus) {
505
+ const expectedStatuses = command[1] === 'milestone-verify'
506
+ ? ['submitted']
507
+ : (eventType === 'milestone_rejected' ? ['pending', 'rejected'] : ['pending']);
508
+ if (!expectedStatuses.includes(milestone.status)) {
481
509
  log({
482
510
  event: 'recommended_action_direct_skip',
483
511
  eventType,
@@ -509,10 +537,65 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
509
537
  const childCwd = command[0] === 'atel' ? getAtelWorkspaceRoot() : cwd;
510
538
 
511
539
  log({ event: 'recommended_action_direct_trigger', eventType, dedupeKey, action: action.action, command });
540
+ globalThis.__atelActiveDirectActionKeys ??= new Set();
541
+ globalThis.__atelActiveDirectActionKeys.add(actionKey);
512
542
 
513
543
  return await new Promise((resolve) => {
514
544
  execFile(childCmd, childArgs, { timeout: 180000, cwd: childCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
545
+ globalThis.__atelActiveDirectActionKeys?.delete(actionKey);
515
546
  if (err) {
547
+ const combinedErrorText = String(stderr || err.message || '');
548
+ if (
549
+ command[0] === 'atel' &&
550
+ command[1] === 'milestone-feedback' &&
551
+ command.includes('--approve') &&
552
+ combinedErrorText.includes('order not in milestone_review status')
553
+ ) {
554
+ log({
555
+ event: 'recommended_action_direct_skip',
556
+ eventType,
557
+ dedupeKey,
558
+ action: action.action,
559
+ command,
560
+ reason: 'order_status_executing',
561
+ });
562
+ resolve({ ok: true, skipped: true, reason: 'order_status_executing' });
563
+ return;
564
+ }
565
+ if (
566
+ command[0] === 'atel' &&
567
+ command[1] === 'milestone-submit' &&
568
+ combinedErrorText.includes('milestone cannot be submitted in status: submitted')
569
+ ) {
570
+ log({
571
+ event: 'recommended_action_direct_skip',
572
+ eventType,
573
+ dedupeKey,
574
+ action: action.action,
575
+ command,
576
+ reason: 'milestone_status_submitted',
577
+ });
578
+ resolve({ ok: true, skipped: true, reason: 'milestone_status_submitted' });
579
+ return;
580
+ }
581
+ if (
582
+ command[0] === 'atel' &&
583
+ command[1] === 'milestone-verify' &&
584
+ (combinedErrorText.includes('milestone cannot be verified in status: verified') ||
585
+ combinedErrorText.includes('milestone not in submitted status') ||
586
+ combinedErrorText.includes('milestone cannot be verified in status: settled'))
587
+ ) {
588
+ log({
589
+ event: 'recommended_action_direct_skip',
590
+ eventType,
591
+ dedupeKey,
592
+ action: action.action,
593
+ command,
594
+ reason: 'milestone_already_processed',
595
+ });
596
+ resolve({ ok: true, skipped: true, reason: 'milestone_already_processed' });
597
+ return;
598
+ }
516
599
  log({
517
600
  event: 'recommended_action_direct_error',
518
601
  eventType,
@@ -532,7 +615,7 @@ async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey)
532
615
  dedupeKey,
533
616
  action: action.action,
534
617
  command,
535
- stdout: (stdout || '').substring(0, 400),
618
+ stdout: summarizeAgentOutput(stdout, 400),
536
619
  });
537
620
  resolve({ ok: true, stdout });
538
621
  });
@@ -2904,6 +2987,178 @@ ${callbackFailed}
2904
2987
  `;
2905
2988
  }
2906
2989
 
2990
+ function buildLocalAgentPrompt(eventType, promptText) {
2991
+ if (eventType === 'milestone_submitted') {
2992
+ return `${promptText}
2993
+
2994
+ 重要:你不是在和用户聊天。
2995
+ 不要输出 markdown、代码块、解释、分析过程。
2996
+ 你必须只输出一行 JSON。
2997
+
2998
+ 通过时:
2999
+ {"decision":"pass","summary":"简短通过原因"}
3000
+
3001
+ 拒绝时:
3002
+ {"decision":"reject","reason":"具体拒绝原因","summary":"简短审核结论"}`;
3003
+ }
3004
+
3005
+ if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
3006
+ return `${promptText}
3007
+
3008
+ 重要:你不是在和用户聊天。
3009
+ 不要输出 markdown、标题、项目符号、解释、分析过程。
3010
+ 你必须只输出一行 JSON。
3011
+
3012
+ 格式:
3013
+ {"result":"当前里程碑的真实交付内容"}`;
3014
+ }
3015
+
3016
+ return promptText;
3017
+ }
3018
+
3019
+ function normalizeLocalAgentStdout(stdout) {
3020
+ const text = String(stdout || '').trim();
3021
+ if (!text) return '';
3022
+ try {
3023
+ const parsed = JSON.parse(text);
3024
+ if (Array.isArray(parsed?.payloads)) {
3025
+ for (const item of parsed.payloads) {
3026
+ const candidate = String(item?.text || '').trim();
3027
+ if (candidate) return candidate;
3028
+ }
3029
+ }
3030
+ if (typeof parsed?.text === 'string' && parsed.text.trim()) return parsed.text.trim();
3031
+ if (typeof parsed?.result === 'string' && parsed.result.trim()) return JSON.stringify({ result: parsed.result.trim() });
3032
+ if (typeof parsed?.decision === 'string') return JSON.stringify(parsed);
3033
+ } catch {}
3034
+ const fencedJson = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3035
+ if (fencedJson?.[1]) return fencedJson[1].trim();
3036
+ const jsonObjects = text.match(/\{[\s\S]*\}/g);
3037
+ if (jsonObjects?.length) {
3038
+ for (let i = jsonObjects.length - 1; i >= 0; i -= 1) {
3039
+ const candidate = jsonObjects[i].trim();
3040
+ try {
3041
+ const parsed = JSON.parse(candidate);
3042
+ if (Array.isArray(parsed?.payloads)) {
3043
+ for (const item of parsed.payloads) {
3044
+ const nested = String(item?.text || '').trim();
3045
+ if (nested) return nested;
3046
+ }
3047
+ }
3048
+ if (typeof parsed?.text === 'string' && parsed.text.trim()) return parsed.text.trim();
3049
+ if (typeof parsed?.result === 'string' && parsed.result.trim()) return JSON.stringify({ result: parsed.result.trim() });
3050
+ if (typeof parsed?.decision === 'string') return JSON.stringify(parsed);
3051
+ return candidate;
3052
+ } catch {}
3053
+ }
3054
+ }
3055
+ const jsonLines = text.split('\n').map((line) => line.trim()).filter(Boolean);
3056
+ for (let i = jsonLines.length - 1; i >= 0; i -= 1) {
3057
+ const candidate = jsonLines[i];
3058
+ if (!(candidate.startsWith('{') && candidate.endsWith('}'))) continue;
3059
+ try {
3060
+ JSON.parse(candidate);
3061
+ return candidate;
3062
+ } catch {}
3063
+ }
3064
+ return text;
3065
+ }
3066
+
3067
+ function normalizeResult(value) {
3068
+ return String(value || '').replace(/\s+/g, ' ').trim();
3069
+ }
3070
+
3071
+ function sanitizeHookSessionId(value) {
3072
+ const raw = String(value || '').trim();
3073
+ if (!raw) return `atel-hook-${Date.now()}`;
3074
+ const cleaned = raw.replace(/[^a-zA-Z0-9._:-]+/g, '-').replace(/^-+|-+$/g, '');
3075
+ return (cleaned || `atel-hook-${Date.now()}`).slice(0, 120);
3076
+ }
3077
+
3078
+ function isOpenClawAgentInvocation(cmd, args = []) {
3079
+ const argv = [String(cmd || ''), ...args.map((v) => String(v || ''))];
3080
+ if (argv[0] === 'openclaw') return argv[1] === 'agent';
3081
+ if (argv[0] === 'npx') return argv[1] === 'openclaw' && argv[2] === 'agent';
3082
+ if (argv[0] === 'node') return argv.includes('agent') && argv.some((v) => /openclaw/i.test(v));
3083
+ return false;
3084
+ }
3085
+
3086
+ function prepareHookInvocation(cmd, args = [], hookKey, timeoutSeconds) {
3087
+ const nextArgs = [...args];
3088
+ if (!isOpenClawAgentInvocation(cmd, nextArgs)) return { cmd, args: nextArgs };
3089
+
3090
+ if (!nextArgs.includes('--json')) {
3091
+ const messageIndex = nextArgs.lastIndexOf('-m');
3092
+ const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
3093
+ nextArgs.splice(insertAt, 0, '--json');
3094
+ }
3095
+ if (!nextArgs.includes('--session-id')) {
3096
+ const messageIndex = nextArgs.lastIndexOf('-m');
3097
+ const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
3098
+ nextArgs.splice(insertAt, 0, '--session-id', sanitizeHookSessionId(hookKey));
3099
+ }
3100
+ if (!nextArgs.includes('--timeout')) {
3101
+ const messageIndex = nextArgs.lastIndexOf('-m');
3102
+ const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
3103
+ nextArgs.splice(insertAt, 0, '--timeout', String(timeoutSeconds));
3104
+ }
3105
+ if (!nextArgs.includes('--thinking')) {
3106
+ const messageIndex = nextArgs.lastIndexOf('-m');
3107
+ const insertAt = messageIndex >= 0 ? messageIndex : nextArgs.length;
3108
+ nextArgs.splice(insertAt, 0, '--thinking', 'minimal');
3109
+ }
3110
+ return { cmd, args: nextArgs };
3111
+ }
3112
+
3113
+ function buildLocalAgentActionFromStdout(eventType, payload, stdout) {
3114
+ const cleaned = normalizeLocalAgentStdout(stdout);
3115
+ if (!cleaned) return { ok: false, error: 'empty_local_agent_stdout' };
3116
+
3117
+ if (eventType === 'milestone_submitted') {
3118
+ try {
3119
+ const parsed = JSON.parse(cleaned);
3120
+ return buildAgentCallbackAction(eventType, payload, parsed);
3121
+ } catch {
3122
+ const lowered = cleaned.toLowerCase();
3123
+ if (lowered.startsWith('pass') || cleaned.includes('通过')) {
3124
+ return buildAgentCallbackAction(eventType, payload, { decision: 'pass', summary: cleaned });
3125
+ }
3126
+ if (lowered.startsWith('reject') || cleaned.includes('拒绝')) {
3127
+ return buildAgentCallbackAction(eventType, payload, { decision: 'reject', reason: cleaned, summary: cleaned });
3128
+ }
3129
+ return { ok: false, error: 'invalid_local_review_stdout' };
3130
+ }
3131
+ }
3132
+
3133
+ if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
3134
+ try {
3135
+ const parsed = JSON.parse(cleaned);
3136
+ return buildAgentCallbackAction(eventType, payload, parsed);
3137
+ } catch {
3138
+ return buildAgentCallbackAction(eventType, payload, { result: cleaned, summary: cleaned });
3139
+ }
3140
+ }
3141
+
3142
+ return buildAgentCallbackAction(eventType, payload, { result: cleaned, summary: cleaned });
3143
+ }
3144
+
3145
+ function buildMilestoneHookRecoveryKey(eventType, payload = {}) {
3146
+ const orderId = String(payload?.orderId || '').trim();
3147
+ if (!orderId) return '';
3148
+ if (eventType === 'milestone_submitted') {
3149
+ const stage = Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0;
3150
+ const submitCount = Number.isFinite(Number(payload?.submitCount)) ? Number(payload.submitCount) : 0;
3151
+ return `stage:${orderId}:requester:${stage}:${submitCount}`;
3152
+ }
3153
+ if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
3154
+ const stage = eventType === 'milestone_plan_confirmed'
3155
+ ? (Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0)
3156
+ : (Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0);
3157
+ return `stage:${orderId}:executor:${stage}`;
3158
+ }
3159
+ return '';
3160
+ }
3161
+
2907
3162
  async function runGatewayAgentTask(eventType, dedupeKey, promptText, cwd, payload) {
2908
3163
  const gw = discoverGateway();
2909
3164
  const cfg = loadOpenClawConfig();
@@ -3074,9 +3329,10 @@ ${callbackFailed}
3074
3329
  const promptText = currentIndex === 0
3075
3330
  ? `你是ATEL接单方Agent。双方已确认方案,开始执行。\n订单原始要求:${orderDescription || '未提供'}\n当前里程碑 M0:${currentMilestone.title || ''}\n请只围绕这个订单要求完成当前里程碑,并通过回调返回最终交付内容。`
3076
3331
  : `你是ATEL接单方Agent。M${currentIndex - 1} 已通过审核。\n订单原始要求:${orderDescription || '未提供'}\n下一个里程碑 M${currentIndex}:${currentMilestone.title || ''}\n前面已通过的阶段结果如下:\n${previousApprovedOutputs || '无'}\n\n请严格基于这些已通过结果推进当前里程碑,不要自行假设缺失材料,也不要读取本地共享文件来补上下文。完成后通过回调返回最终交付内容。`;
3077
- const recoveryKey = `reconcile:${orderId}:executor:${currentIndex}`;
3332
+ const recoveryKey = buildMilestoneHookRecoveryKey(eventType, payload);
3333
+ log({ event: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
3078
3334
  const queued = queueAgentHook(eventType, recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
3079
- if (queued) log({ event: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
3335
+ if (queued) log({ event: 'trade_reconcile_executor_queued', orderId, currentMilestone: currentIndex, recoveryKey });
3080
3336
  return;
3081
3337
  }
3082
3338
 
@@ -3106,7 +3362,7 @@ ${callbackFailed}
3106
3362
  previousApprovedOutputs,
3107
3363
  };
3108
3364
  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}`;
3365
+ const recoveryKey = buildMilestoneHookRecoveryKey('milestone_submitted', payload);
3110
3366
  const queued = queueAgentHook('milestone_submitted', recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
3111
3367
  if (queued) log({ event: 'trade_reconcile_requester', orderId, milestoneIndex: submittedMilestone.index, recoveryKey });
3112
3368
  }
@@ -3299,6 +3555,12 @@ ${callbackFailed}
3299
3555
  const autoTriggerEvents = ['order_accepted', 'milestone_plan_confirmed', 'milestone_submitted', 'milestone_verified', 'milestone_rejected'];
3300
3556
  const hasGatewayAction = Array.isArray(recommendedActions) && recommendedActions.some((action) => Array.isArray(action?.command) && action.command[0] === 'atel');
3301
3557
  if (agentCmd && prompt && autoTriggerEvents.includes(event) && !shouldSkipAgentHook(event, directExecutionSucceeded)) {
3558
+ const needsActionablePayload = event !== 'milestone_submitted';
3559
+ if (needsActionablePayload && !hasGatewayAction) {
3560
+ log({ event: 'agent_hook_skip_no_action', eventType: event, dedupeKey, reason: 'informational_only_payload' });
3561
+ res.json({ status: 'received', eventId, eventType: event });
3562
+ return;
3563
+ }
3302
3564
  if (shouldUseGatewaySession(event) && !hasGatewayAction) {
3303
3565
  log({ event: 'agent_hook_skip_no_action', eventType: event, dedupeKey, reason: 'informational_only_payload' });
3304
3566
  res.json({ status: 'received', eventId, eventType: event });
@@ -3318,13 +3580,17 @@ ${callbackFailed}
3318
3580
  } else {
3319
3581
  processedEvents.add('hook:' + dedupeKey);
3320
3582
 
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();
3583
+ const queued = queueAgentHook(
3584
+ event,
3585
+ dedupeKey,
3586
+ enrichedPrompt,
3587
+ hookCwd,
3588
+ payload,
3589
+ { recoveryKey: buildMilestoneHookRecoveryKey(event, payload) },
3590
+ );
3591
+ if (!queued) {
3592
+ log({ event: 'agent_cmd_dedup_recovery_key', eventType: event, dedupeKey });
3593
+ }
3328
3594
  }
3329
3595
  }
3330
3596
 
@@ -3353,11 +3619,18 @@ ${callbackFailed}
3353
3619
  return;
3354
3620
  }
3355
3621
  log({ event: 'agent_session_spawn_error', eventType: hookEvent, dedupeKey: hookKey, error: gatewayResult.error, fallback: 'cli' });
3622
+ spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
3623
+ } else if (spawnArgs.length > 0) {
3624
+ const promptArg = spawnArgs[spawnArgs.length - 1] || '';
3625
+ spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
3356
3626
  }
3357
3627
 
3358
3628
  const MAX_ATTEMPTS = 5;
3359
- const runHook = (attempt) => {
3360
- execFile(spawnCmd, spawnArgs, { timeout: 600000, cwd: hookCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
3629
+ const isMilestoneHook = ['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected', 'milestone_submitted'].includes(hookEvent);
3630
+ const localHookTimeoutMs = isMilestoneHook ? 180000 : 600000;
3631
+ const preparedInvocation = prepareHookInvocation(spawnCmd, spawnArgs, hookKey, Math.ceil(localHookTimeoutMs / 1000));
3632
+ const runHook = (attempt, invocation = preparedInvocation) => {
3633
+ execFile(invocation.cmd, invocation.args, { timeout: localHookTimeoutMs, cwd: hookCwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
3361
3634
  const errMsg = (err?.message || '') + (stderr || '');
3362
3635
  const isSessionLock = errMsg.includes('session file locked') || errMsg.includes('session locked');
3363
3636
  const isNetworkError = err && (err.killed || err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET');
@@ -3373,8 +3646,40 @@ ${callbackFailed}
3373
3646
  } else if (err) {
3374
3647
  log({ event: 'agent_cmd_error', eventType: hookEvent, error: err.message, stderr: (stderr || '').substring(0, 200) });
3375
3648
  finishHook();
3649
+ } else if (isKnownUpstreamModelInputError(stdout)) {
3650
+ log({
3651
+ event: 'agent_cmd_upstream_input_error',
3652
+ eventType: hookEvent,
3653
+ dedupeKey: hookKey,
3654
+ note: 'suppressed known upstream model input-length error',
3655
+ });
3656
+ finishHook();
3376
3657
  } else {
3377
- log({ event: 'agent_cmd_done', eventType: hookEvent, stdout: (stdout || '').substring(0, 300) });
3658
+ const localAction = buildLocalAgentActionFromStdout(hookEvent, hookPayload || {}, stdout);
3659
+ if (!localAction.ok && localAction.error === 'empty_local_agent_stdout' && invocation.args.includes('--json')) {
3660
+ const retryArgs = invocation.args.filter((arg) => arg !== '--json');
3661
+ log({ event: 'agent_cmd_retry_without_json', eventType: hookEvent, dedupeKey: hookKey });
3662
+ setTimeout(() => runHook(attempt + 1, { ...invocation, args: retryArgs }), 1000);
3663
+ return;
3664
+ }
3665
+ if (localAction.ok && !localAction.skipped) {
3666
+ executeRecommendedActionDirect(hookEvent, localAction.action, hookCwd || process.cwd(), hookKey)
3667
+ .then((execResult) => {
3668
+ if (execResult.ok) {
3669
+ log({ event: 'agent_cmd_done', eventType: hookEvent, mode: 'local_stdout_action', dedupeKey: hookKey, stdout: summarizeAgentOutput(stdout, 200) });
3670
+ } else {
3671
+ log({ event: 'agent_cmd_local_action_error', eventType: hookEvent, dedupeKey: hookKey, error: execResult.error || 'local_action_failed', stdout: summarizeAgentOutput(stdout, 200) });
3672
+ }
3673
+ finishHook();
3674
+ })
3675
+ .catch((e) => {
3676
+ log({ event: 'agent_cmd_local_action_error', eventType: hookEvent, dedupeKey: hookKey, error: e.message || 'local_action_failed', stdout: summarizeAgentOutput(stdout, 200) });
3677
+ finishHook();
3678
+ });
3679
+ return;
3680
+ }
3681
+
3682
+ log({ event: 'agent_cmd_done', eventType: hookEvent, stdout: summarizeAgentOutput(stdout, 300) });
3378
3683
  finishHook();
3379
3684
  }
3380
3685
  });
@@ -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.10",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",