@gt-fe/eap-runtime 0.0.33 → 0.0.34

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.
Files changed (34) hide show
  1. package/dist/api/handlers/invoke.handler.d.ts +0 -1
  2. package/dist/api/handlers/invoke.handler.js +22 -5
  3. package/dist/api/handlers/stream.handler.d.ts +0 -1
  4. package/dist/api/handlers/stream.handler.js +19 -17
  5. package/dist/common/types.d.ts +4 -3
  6. package/dist/graph/adapters/langgraph.adapter.js +109 -64
  7. package/dist/graph/execution-trace-context.d.ts +3 -0
  8. package/dist/graph/nodes/llm-node.d.ts +7 -2
  9. package/dist/graph/nodes/llm-node.js +8 -3
  10. package/dist/providers/http-llm-provider.js +27 -10
  11. package/dist/runtime/agent-runtime.d.ts +7 -0
  12. package/dist/runtime/agent-runtime.js +59 -37
  13. package/dist/runtime/types.d.ts +1 -1
  14. package/dist/session/persist-turn-messages.d.ts +10 -2
  15. package/dist/session/persist-turn-messages.js +35 -10
  16. package/dist/sse/events/index.d.ts +6 -4
  17. package/dist/sse/events/index.js +3 -1
  18. package/dist/sse/events/reasoning-events.d.ts +24 -2
  19. package/dist/sse/events/reasoning-events.js +3 -1
  20. package/dist/sse/events/skill-events.d.ts +2 -0
  21. package/dist/task/contracts/task-manager.contract.d.ts +2 -2
  22. package/dist/task/contracts/task-manager.contract.js +1 -1
  23. package/dist/task/index.d.ts +2 -0
  24. package/dist/task/index.js +1 -0
  25. package/dist/task/task-abort.d.ts +14 -0
  26. package/dist/task/task-abort.js +33 -0
  27. package/dist/task/task-timeout.d.ts +11 -0
  28. package/dist/task/task-timeout.js +40 -4
  29. package/dist/task/task.service.js +11 -5
  30. package/dist/tool/index.d.ts +1 -1
  31. package/dist/tool/index.js +1 -1
  32. package/dist/tool/system-prompt-builder.d.ts +5 -9
  33. package/dist/tool/system-prompt-builder.js +13 -31
  34. package/package.json +1 -1
@@ -9,13 +9,14 @@
9
9
  import { randomUUID } from 'node:crypto';
10
10
  import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
11
11
  import { resolveTaskTimeoutMs } from '../task/resolve-task-timeout.js';
12
+ import { taskAbortKindOf } from '../task/task-abort.js';
12
13
  import { runWithExecutionTrace } from '../graph/execution-trace-context.js';
13
14
  import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
14
15
  import { resolveAgentDisplayNameFromLoader, resolveAgentSystemPromptFromLoader, } from '../registry/graph-loader.js';
15
16
  import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
16
17
  import { assembleLoadedTools } from '../tool/harness-primitives.js';
17
18
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
18
- import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
19
+ import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractHitlThreadId, applyStreamReasoningEvent, applyStreamTextDelta, createStreamAssistantBuckets, createStreamReasoningState, resolvedStreamReasoning, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
19
20
  import { collectPublicArtifactLinks, collectPublicArtifactLinksFromEvent, injectPublicArtifactLinks, mergeArtifactLinks, } from '../tool/inject-public-artifact-links.js';
20
21
  import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
21
22
  import { applySessionMemory } from '../session/apply-session-memory.js';
@@ -33,7 +34,33 @@ const HITL_TERMINAL_STATUSES = [
33
34
  'failed',
34
35
  'timeout',
35
36
  'token_exhausted',
37
+ 'cancelled',
36
38
  ];
39
+ const TASK_CANCELLED_SSE_CODE = 'runtime.task.cancelled';
40
+ function taskAbortSseData(kind) {
41
+ if (kind === 'timeout') {
42
+ return {
43
+ code: ErrorCodes.TASK_TIMEOUT,
44
+ message: 'Task execution timed out',
45
+ recoverable: false,
46
+ };
47
+ }
48
+ return {
49
+ code: TASK_CANCELLED_SSE_CODE,
50
+ message: 'Task cancelled',
51
+ recoverable: false,
52
+ };
53
+ }
54
+ async function settleAbortedTask(taskService, taskId, kind) {
55
+ if (kind === 'timeout') {
56
+ await taskService.tryUpdateStatus(taskId, 'running', 'timeout');
57
+ return;
58
+ }
59
+ const moved = await taskService.tryUpdateStatus(taskId, 'running', 'cancelled');
60
+ if (!moved) {
61
+ await taskService.tryUpdateStatus(taskId, 'waiting_hitl', 'cancelled');
62
+ }
63
+ }
37
64
  const DEFAULT_GRAPH_DSL = {
38
65
  name: 'default-graph',
39
66
  version: '1.0.0',
@@ -57,6 +84,17 @@ export class AgentRuntime {
57
84
  }
58
85
  this.deps.hitlHandler.setTraceCollector?.(this.deps.traceCollector);
59
86
  }
87
+ /**
88
+ * 停止这一轮。超时和用户停止共用 timeoutManager 上的那条 AbortSignal。
89
+ * 计时器还没启动时先记下,startTimer 会返回已 abort 的 signal,且不调用 onTimeout。
90
+ * @returns 已 abort 或已记下。没有 timeoutManager 时返回 false。
91
+ */
92
+ abort(taskId, kind = 'cancelled') {
93
+ const manager = this.deps.timeoutManager;
94
+ if (!manager || !taskId.trim())
95
+ return false;
96
+ return manager.abort(taskId, kind);
97
+ }
60
98
  /** 从 GraphLoader 解析 tool manifest 映射(invoke/stream/resume/fork 共用) */
61
99
  async resolveLoadedTools(agentCode, agentVersion) {
62
100
  if (!this.deps.graphLoader)
@@ -376,8 +414,8 @@ export class AgentRuntime {
376
414
  });
377
415
  const startTime = Date.now();
378
416
  const assistantBuckets = createStreamAssistantBuckets();
417
+ const reasoningState = createStreamReasoningState();
379
418
  let toolCallReasoningContent;
380
- let assistantReasoningContent;
381
419
  const pendingToolCalls = [];
382
420
  const toolResults = [];
383
421
  const artifactLinks = [];
@@ -419,16 +457,12 @@ export class AgentRuntime {
419
457
  arguments: p.arguments ?? {},
420
458
  });
421
459
  }
422
- if (typeof p.reasoningContent === 'string' && p.reasoningContent) {
423
- toolCallReasoningContent = p.reasoningContent;
424
- }
460
+ toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
425
461
  }
426
462
  // 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
427
463
  appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
428
464
  collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
429
- const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
430
- if (streamedReasoning)
431
- assistantReasoningContent = streamedReasoning;
465
+ applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
432
466
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
433
467
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
434
468
  if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
@@ -462,7 +496,7 @@ export class AgentRuntime {
462
496
  assistantText: injected,
463
497
  finishPoints: graph.finishPoints,
464
498
  toolCallReasoningContent,
465
- assistantReasoningContent,
499
+ assistantReasoningContent: resolvedStreamReasoning(reasoningState),
466
500
  }),
467
501
  assistantTokenCount: tokenSummary.total.output,
468
502
  assistantDurationMs: Date.now() - startTime,
@@ -495,15 +529,11 @@ export class AgentRuntime {
495
529
  yield this.toSSE('hitl.waited', correlationId, task.id, buildHitlWaitedPayload(hitlPaused));
496
530
  return;
497
531
  }
498
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
499
- if (timedOut) {
532
+ const abortKind = taskAbortKindOf(err, abortSignal);
533
+ if (abortKind) {
500
534
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
501
- await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
502
- yield this.toSSE('task.error', correlationId, task.id, {
503
- code: ErrorCodes.TASK_TIMEOUT,
504
- message: 'Task execution timed out',
505
- recoverable: false,
506
- });
535
+ await settleAbortedTask(this.deps.taskService, task.id, abortKind);
536
+ yield this.toSSE('task.error', correlationId, task.id, taskAbortSseData(abortKind));
507
537
  return;
508
538
  }
509
539
  const message = err instanceof Error ? err.message : 'Unknown error';
@@ -638,8 +668,8 @@ export class AgentRuntime {
638
668
  };
639
669
  const startTime = Date.now();
640
670
  const assistantBuckets = createStreamAssistantBuckets();
671
+ const reasoningState = createStreamReasoningState();
641
672
  let toolCallReasoningContent;
642
- let assistantReasoningContent;
643
673
  const pendingToolCalls = [];
644
674
  const toolResults = [];
645
675
  const artifactLinks = [];
@@ -655,15 +685,11 @@ export class AgentRuntime {
655
685
  arguments: p.arguments ?? {},
656
686
  });
657
687
  }
658
- if (typeof p.reasoningContent === 'string' && p.reasoningContent) {
659
- toolCallReasoningContent = p.reasoningContent;
660
- }
688
+ toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
661
689
  }
662
690
  appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
663
691
  collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
664
- const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
665
- if (streamedReasoning)
666
- assistantReasoningContent = streamedReasoning;
692
+ applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
667
693
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
668
694
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
669
695
  if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
@@ -698,7 +724,7 @@ export class AgentRuntime {
698
724
  assistantText: injected,
699
725
  finishPoints: graph.finishPoints,
700
726
  toolCallReasoningContent,
701
- assistantReasoningContent,
727
+ assistantReasoningContent: resolvedStreamReasoning(reasoningState),
702
728
  }),
703
729
  assistantTokenCount: tokenSummary.total.output,
704
730
  assistantDurationMs: Date.now() - startTime,
@@ -734,15 +760,11 @@ export class AgentRuntime {
734
760
  yield this.toSSE('hitl.waited', correlationId, taskId, buildHitlWaitedPayload(hitlPaused));
735
761
  return;
736
762
  }
737
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
738
- if (timedOut) {
763
+ const abortKind = taskAbortKindOf(err, abortSignal);
764
+ if (abortKind) {
739
765
  await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
740
- await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
741
- yield this.toSSE('task.error', correlationId, taskId, {
742
- code: ErrorCodes.TASK_TIMEOUT,
743
- message: 'Task execution timed out',
744
- recoverable: false,
745
- });
766
+ await settleAbortedTask(this.deps.taskService, taskId, abortKind);
767
+ yield this.toSSE('task.error', correlationId, taskId, taskAbortSseData(abortKind));
746
768
  return;
747
769
  }
748
770
  const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
@@ -905,15 +927,15 @@ export class AgentRuntime {
905
927
  await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
906
928
  const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
907
929
  const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
908
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
909
- if (timedOut) {
910
- await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
930
+ const abortKind = taskAbortKindOf(err, abortSignal);
931
+ if (abortKind) {
932
+ await settleAbortedTask(this.deps.taskService, task.id, abortKind);
911
933
  return {
912
934
  newTraceId: traceId,
913
935
  originalTraceId,
914
936
  newTaskId: task.id,
915
937
  checkpointId,
916
- status: 'timeout',
938
+ status: abortKind === 'timeout' ? 'timeout' : 'cancelled',
917
939
  output: message,
918
940
  tokenUsage: {
919
941
  input_tokens,
@@ -91,7 +91,7 @@ export interface ForkResult {
91
91
  originalTraceId: string;
92
92
  newTaskId: string;
93
93
  checkpointId: string;
94
- status: 'completed' | 'failed' | 'timeout';
94
+ status: 'completed' | 'failed' | 'timeout' | 'cancelled';
95
95
  output: unknown;
96
96
  tokenUsage: {
97
97
  input_tokens: number;
@@ -85,10 +85,18 @@ export declare function sessionVisibleAssistantTexts(texts: Array<{
85
85
  */
86
86
  export declare function sessionVisibleGraphMessages(messages: GraphMessage[], finishPoints?: string[]): GraphMessage[];
87
87
  /**
88
- * 从流式事件抽取 assistant reasoning。
89
- * 子图在 skill.completed 上回传(父图不再为 subgraph 节点发空 response.chunk)。
88
+ * 从流式事件抽取本事件上的思考字符串。
89
+ * 认 reasoning.completed / reasoning.chunk 的 content,以及 skill.completed 的 reasoningContent。
90
90
  */
91
91
  export declare function extractStreamReasoning(eventType: string, payload: unknown): string | undefined;
92
+ /** 本回合思考累加:started 清空;completed 优先,没有 completed 时拼接 chunk */
93
+ export type StreamReasoningState = {
94
+ chunkFallback: string;
95
+ lastCompleted?: string;
96
+ };
97
+ export declare function createStreamReasoningState(): StreamReasoningState;
98
+ export declare function applyStreamReasoningEvent(state: StreamReasoningState, eventType: string, payload: unknown): void;
99
+ export declare function resolvedStreamReasoning(state: StreamReasoningState): string | undefined;
92
100
  /** 落盘 / 回填历史只收终态:result,或不会再试的 error。willRetry 的 error 只给直播 UI。 */
93
101
  export declare function isTerminalToolStreamEvent(eventType: string, payload: unknown): boolean;
94
102
  export declare function appendTerminalToolResult(eventType: string, payload: unknown, bucket: StreamToolResult[]): void;
@@ -202,20 +202,45 @@ export function sessionVisibleGraphMessages(messages, finishPoints) {
202
202
  return out;
203
203
  }
204
204
  /**
205
- * 从流式事件抽取 assistant reasoning。
206
- * 子图在 skill.completed 上回传(父图不再为 subgraph 节点发空 response.chunk)。
205
+ * 从流式事件抽取本事件上的思考字符串。
206
+ * 认 reasoning.completed / reasoning.chunk 的 content,以及 skill.completed 的 reasoningContent。
207
207
  */
208
208
  export function extractStreamReasoning(eventType, payload) {
209
- if (eventType !== 'response.chunk' &&
210
- eventType !== 'response.delta' &&
211
- eventType !== 'response.final' &&
212
- eventType !== 'skill.completed') {
213
- return undefined;
214
- }
215
209
  if (!payload || typeof payload !== 'object')
216
210
  return undefined;
217
- const value = payload.reasoningContent;
218
- return typeof value === 'string' && value ? value : undefined;
211
+ const p = payload;
212
+ if (eventType === 'reasoning.chunk' || eventType === 'reasoning.completed') {
213
+ return typeof p.content === 'string' && p.content ? p.content : undefined;
214
+ }
215
+ if (eventType === 'skill.completed') {
216
+ return typeof p.reasoningContent === 'string' && p.reasoningContent
217
+ ? p.reasoningContent
218
+ : undefined;
219
+ }
220
+ return undefined;
221
+ }
222
+ export function createStreamReasoningState() {
223
+ return { chunkFallback: '' };
224
+ }
225
+ export function applyStreamReasoningEvent(state, eventType, payload) {
226
+ if (eventType === 'reasoning.started') {
227
+ state.chunkFallback = '';
228
+ state.lastCompleted = undefined;
229
+ return;
230
+ }
231
+ const text = extractStreamReasoning(eventType, payload);
232
+ if (!text)
233
+ return;
234
+ if (eventType === 'reasoning.chunk') {
235
+ state.chunkFallback += text;
236
+ return;
237
+ }
238
+ state.lastCompleted = text;
239
+ }
240
+ export function resolvedStreamReasoning(state) {
241
+ if (state.lastCompleted)
242
+ return state.lastCompleted;
243
+ return state.chunkFallback ? state.chunkFallback : undefined;
219
244
  }
220
245
  /** 落盘 / 回填历史只收终态:result,或不会再试的 error。willRetry 的 error 只给直播 UI。 */
221
246
  export function isTerminalToolStreamEvent(eventType, payload) {
@@ -10,7 +10,7 @@
10
10
  * - 时态:过去式(started/completed)或名词(final/heartbeat)
11
11
  *
12
12
  * 分层设计:
13
- * - 核心事件(14 种):所有 Agent 必须支持
13
+ * - 核心事件(16 种):所有 Agent 必须支持
14
14
  * - 可选事件:Agent 按需支持
15
15
  *
16
16
  * @see design.md §1.6 SSE事件协议定义
@@ -36,7 +36,7 @@ export type SseEventLevel = 'core' | 'optional';
36
36
  /**
37
37
  * 核心事件类型(所有 Agent 必须支持)
38
38
  */
39
- export declare const CORE_SSE_EVENTS: readonly ["session.created", "task.started", "task.completed", "task.error", "reasoning.started", "response.chunk", "tool.called", "tool.result", "tool.error", "hitl.waited", "hitl.resumed", "response.final", "stats.token_usage", "connection.heartbeat"];
39
+ export declare const CORE_SSE_EVENTS: readonly ["session.created", "task.started", "task.completed", "task.error", "reasoning.started", "reasoning.chunk", "reasoning.completed", "response.chunk", "tool.called", "tool.result", "tool.error", "hitl.waited", "hitl.resumed", "response.final", "stats.token_usage", "connection.heartbeat"];
40
40
  /**
41
41
  * 可选事件类型(Agent 按需支持)
42
42
  */
@@ -44,10 +44,10 @@ export declare const OPTIONAL_SSE_EVENTS: readonly ["skill.matched", "skill.unma
44
44
  /**
45
45
  * SSEEventType — SSE 事件类型字符串联合类型
46
46
  */
47
- export type SSEEventType = 'session.created' | 'task.started' | 'task.completed' | 'task.error' | 'reasoning.started' | 'response.chunk' | 'tool.called' | 'tool.result' | 'tool.error' | 'hitl.waited' | 'hitl.resumed' | 'response.final' | 'stats.token_usage' | 'connection.heartbeat' | 'skill.matched' | 'skill.unmatched' | 'skill.started' | 'skill.completed' | 'skill.failed' | 'skill.loaded' | 'skill.resource_read' | 'reasoning.step' | 'tool.progress' | 'file.created' | 'file.updated' | 'code.executed' | 'collaboration.handed_off' | 'collaboration.subtask.started' | 'collaboration.subtask.completed' | 'plan.created' | 'plan.step.started' | 'plan.step.completed' | 'response.max_iterations_reached' | 'stats.node_token_usage' | 'session.compression.started' | 'session.compression.completed' | 'rag.round' | 'rag.search' | 'rag.denoise' | 'rag.rewrite' | 'rag.finish';
47
+ export type SSEEventType = 'session.created' | 'task.started' | 'task.completed' | 'task.error' | 'reasoning.started' | 'reasoning.chunk' | 'reasoning.completed' | 'response.chunk' | 'tool.called' | 'tool.result' | 'tool.error' | 'hitl.waited' | 'hitl.resumed' | 'response.final' | 'stats.token_usage' | 'connection.heartbeat' | 'skill.matched' | 'skill.unmatched' | 'skill.started' | 'skill.completed' | 'skill.failed' | 'skill.loaded' | 'skill.resource_read' | 'reasoning.step' | 'tool.progress' | 'file.created' | 'file.updated' | 'code.executed' | 'collaboration.handed_off' | 'collaboration.subtask.started' | 'collaboration.subtask.completed' | 'plan.created' | 'plan.step.started' | 'plan.step.completed' | 'response.max_iterations_reached' | 'stats.node_token_usage' | 'session.compression.started' | 'session.compression.completed' | 'rag.round' | 'rag.search' | 'rag.denoise' | 'rag.rewrite' | 'rag.finish';
48
48
  import type { SessionCreatedPayload, TaskStartedPayload, TaskCompletedPayload, TaskErrorPayload, SessionCompressionStartedPayload, SessionCompressionCompletedPayload } from './session-events.js';
49
49
  import type { SkillMatchedPayload, SkillUnmatchedPayload, SkillStartedPayload, SkillCompletedPayload, SkillFailedPayload, SkillLoadedPayload, SkillResourceReadPayload } from './skill-events.js';
50
- import type { ReasoningStartedPayload, ReasoningStepPayload, ResponseChunkPayload } from './reasoning-events.js';
50
+ import type { ReasoningStartedPayload, ReasoningChunkPayload, ReasoningCompletedPayload, ReasoningStepPayload, ResponseChunkPayload } from './reasoning-events.js';
51
51
  import type { ToolCalledPayload, ToolResultPayload, ToolProgressPayload, ToolErrorPayload } from './tool-events.js';
52
52
  import type { FileCreatedPayload, FileUpdatedPayload } from './file-events.js';
53
53
  import type { CodeExecutedPayload } from './code-events.js';
@@ -67,6 +67,8 @@ export interface SSEEventPayloadMap {
67
67
  'task.completed': TaskCompletedPayload;
68
68
  'task.error': TaskErrorPayload;
69
69
  'reasoning.started': ReasoningStartedPayload;
70
+ 'reasoning.chunk': ReasoningChunkPayload;
71
+ 'reasoning.completed': ReasoningCompletedPayload;
70
72
  'response.chunk': ResponseChunkPayload;
71
73
  'tool.called': ToolCalledPayload;
72
74
  'tool.result': ToolResultPayload;
@@ -10,7 +10,7 @@
10
10
  * - 时态:过去式(started/completed)或名词(final/heartbeat)
11
11
  *
12
12
  * 分层设计:
13
- * - 核心事件(14 种):所有 Agent 必须支持
13
+ * - 核心事件(16 种):所有 Agent 必须支持
14
14
  * - 可选事件:Agent 按需支持
15
15
  *
16
16
  * @see design.md §1.6 SSE事件协议定义
@@ -41,6 +41,8 @@ export const CORE_SSE_EVENTS = [
41
41
  'task.completed',
42
42
  'task.error',
43
43
  'reasoning.started',
44
+ 'reasoning.chunk',
45
+ 'reasoning.completed',
44
46
  'response.chunk',
45
47
  'tool.called',
46
48
  'tool.result',
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * 核心事件:
5
5
  * - reasoning.started
6
+ * - reasoning.chunk
7
+ * - reasoning.completed
6
8
  * - response.chunk
7
9
  *
8
10
  * 可选事件:
9
- * - reasoning.step
11
+ * - reasoning.step(ReAct thought/plan,不是模型 CoT)
10
12
  *
11
13
  * @see design.md §1.6 SSE事件协议定义
12
14
  */
@@ -17,7 +19,27 @@ export interface ReasoningStartedPayload {
17
19
  /** 使用的模型名称(可选;未知时可省略) */
18
20
  model?: string;
19
21
  }
20
- /** 推理中间步骤(思考过程) */
22
+ /** 思考增量(与 response.chunk 对称;空增量不发) */
23
+ export interface ReasoningChunkPayload {
24
+ /** 思考片段 */
25
+ content: string;
26
+ /** 片段序号(本轮从 0 开始,与 response.chunk 分开计) */
27
+ index: number;
28
+ /** 发出该增量的 LLM 节点 */
29
+ node?: string;
30
+ /** 所属图 */
31
+ graphId?: string;
32
+ }
33
+ /** 本轮思考全文(有思考才发) */
34
+ export interface ReasoningCompletedPayload {
35
+ /** 本轮思考全文 */
36
+ content: string;
37
+ /** 发出该全文的 LLM 节点 */
38
+ node?: string;
39
+ /** 所属图 */
40
+ graphId?: string;
41
+ }
42
+ /** 推理中间步骤(ReAct thought/plan,不是模型 CoT) */
21
43
  export interface ReasoningStepPayload {
22
44
  /** 步骤序号 */
23
45
  step: number;
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * 核心事件:
5
5
  * - reasoning.started
6
+ * - reasoning.chunk
7
+ * - reasoning.completed
6
8
  * - response.chunk
7
9
  *
8
10
  * 可选事件:
9
- * - reasoning.step
11
+ * - reasoning.step(ReAct thought/plan,不是模型 CoT)
10
12
  *
11
13
  * @see design.md §1.6 SSE事件协议定义
12
14
  */
@@ -39,6 +39,8 @@ export interface SkillCompletedPayload {
39
39
  skillId: string;
40
40
  skillName: string;
41
41
  output?: unknown;
42
+ /** 子图最后一条 assistant 的思考全文;只订 skill 事件的落库用 */
43
+ reasoningContent?: string;
42
44
  }
43
45
  /** Skill 子图执行失败 */
44
46
  export interface SkillFailedPayload {
@@ -2,7 +2,7 @@
2
2
  * Task Manager Contract — 任务生命周期管理接口
3
3
  *
4
4
  * 定义 Task 的创建、状态流转、查询、Token 消耗记录等核心操作。
5
- * 状态流转:pending → running → success/failed/timeout/token_exhausted
5
+ * 状态流转:pending → running → success/failed/timeout/token_exhausted/cancelled
6
6
  *
7
7
  * @see design.md §1.3 数据模型 — tasks 表
8
8
  * @see requirements.md Requirement 7 AC1-AC5
@@ -126,7 +126,7 @@ export interface ITaskManager {
126
126
  /**
127
127
  * 更新 Task 状态
128
128
  *
129
- * 合法流转:pending→running, running→success/failed/timeout/token_exhausted
129
+ * 合法流转:pending→running|cancelled, running→success/failed/timeout/token_exhausted/cancelled/waiting_hitl
130
130
  *
131
131
  * @param taskId - Task ID
132
132
  * @param status - 目标状态
@@ -2,7 +2,7 @@
2
2
  * Task Manager Contract — 任务生命周期管理接口
3
3
  *
4
4
  * 定义 Task 的创建、状态流转、查询、Token 消耗记录等核心操作。
5
- * 状态流转:pending → running → success/failed/timeout/token_exhausted
5
+ * 状态流转:pending → running → success/failed/timeout/token_exhausted/cancelled
6
6
  *
7
7
  * @see design.md §1.3 数据模型 — tasks 表
8
8
  * @see requirements.md Requirement 7 AC1-AC5
@@ -3,6 +3,8 @@ export { TaskService, InMemoryTaskRepository } from './task.service.js';
3
3
  export type { ITaskRepository } from './task.service.js';
4
4
  export { TaskTimeoutManager } from './task-timeout.js';
5
5
  export type { TaskTimeoutManagerOptions } from './task-timeout.js';
6
+ export { TaskAbortError, taskAbortKind, taskAbortKindOf, TASK_CANCELLED_MESSAGE } from './task-abort.js';
7
+ export type { TaskAbortKind } from './task-abort.js';
6
8
  export { resolveTaskTimeoutMs } from './resolve-task-timeout.js';
7
9
  export { TaskProgressTracker } from './task-progress-tracker.js';
8
10
  export type { TaskProgressInfo } from './task-progress-tracker.js';
@@ -2,5 +2,6 @@
2
2
  export * from './contracts/index.js';
3
3
  export { TaskService, InMemoryTaskRepository } from './task.service.js';
4
4
  export { TaskTimeoutManager } from './task-timeout.js';
5
+ export { TaskAbortError, taskAbortKind, taskAbortKindOf, TASK_CANCELLED_MESSAGE } from './task-abort.js';
5
6
  export { resolveTaskTimeoutMs } from './resolve-task-timeout.js';
6
7
  export { TaskProgressTracker } from './task-progress-tracker.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 一次 Task 一条 AbortSignal。超时和用户停止都 abort 它,用 kind 区分。
3
+ * name 固定 AbortError,节点重试会跳过。
4
+ */
5
+ export type TaskAbortKind = 'timeout' | 'cancelled';
6
+ export declare const TASK_CANCELLED_MESSAGE = "Task cancelled";
7
+ export declare class TaskAbortError extends Error {
8
+ readonly kind: TaskAbortKind;
9
+ readonly taskId: string;
10
+ constructor(taskId: string, kind: TaskAbortKind, timeoutMs?: number);
11
+ }
12
+ export declare function taskAbortKind(err: unknown): TaskAbortKind | undefined;
13
+ /** 图把 AbortError 包了一层时,回看这一轮 signal 上的 reason。 */
14
+ export declare function taskAbortKindOf(err: unknown, signal?: AbortSignal): TaskAbortKind | undefined;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 一次 Task 一条 AbortSignal。超时和用户停止都 abort 它,用 kind 区分。
3
+ * name 固定 AbortError,节点重试会跳过。
4
+ */
5
+ export const TASK_CANCELLED_MESSAGE = 'Task cancelled';
6
+ export class TaskAbortError extends Error {
7
+ kind;
8
+ taskId;
9
+ constructor(taskId, kind, timeoutMs) {
10
+ const message = kind === 'timeout'
11
+ ? `Task '${taskId}' timed out after ${timeoutMs ?? 0}ms`
12
+ : `Task '${taskId}' cancelled`;
13
+ super(message);
14
+ this.name = 'AbortError';
15
+ this.kind = kind;
16
+ this.taskId = taskId;
17
+ }
18
+ }
19
+ export function taskAbortKind(err) {
20
+ if (err instanceof TaskAbortError)
21
+ return err.kind;
22
+ if (!(err instanceof Error))
23
+ return undefined;
24
+ if (/timed out/i.test(err.message))
25
+ return 'timeout';
26
+ if (err.name === 'AbortError' && /cancell?ed/i.test(err.message))
27
+ return 'cancelled';
28
+ return undefined;
29
+ }
30
+ /** 图把 AbortError 包了一层时,回看这一轮 signal 上的 reason。 */
31
+ export function taskAbortKindOf(err, signal) {
32
+ return taskAbortKind(err) ?? (signal?.aborted ? taskAbortKind(signal.reason) : undefined);
33
+ }
@@ -9,6 +9,7 @@
9
9
  * @see design.md §1.1 任务管理 — Task级超时
10
10
  * @see requirements.md Requirement 7 AC4
11
11
  */
12
+ import { type TaskAbortKind } from './task-abort.js';
12
13
  /**
13
14
  * TaskTimeoutManager 配置选项
14
15
  */
@@ -27,6 +28,11 @@ export interface TaskTimeoutManagerOptions {
27
28
  */
28
29
  export declare class TaskTimeoutManager {
29
30
  private readonly entries;
31
+ /**
32
+ * startTimer 之前到达的停止。startTimer 消费后返回已 abort 的 signal,且不调用 onTimeout。
33
+ * clearTimer 只在计时器已经启动时清掉残留,避免把还没被 startTimer 看到的停止丢掉。
34
+ */
35
+ private readonly pendingAborts;
30
36
  private readonly defaultTimeoutMs;
31
37
  constructor(options?: TaskTimeoutManagerOptions);
32
38
  /**
@@ -39,6 +45,11 @@ export declare class TaskTimeoutManager {
39
45
  * @throws 如果该 taskId 已存在活跃计时器
40
46
  */
41
47
  startTimer(taskId: string, timeoutMs: number | undefined, onTimeout: () => Promise<void>): AbortSignal;
48
+ /**
49
+ * 停止这一轮。清掉计时器,不调用 onTimeout。
50
+ * 计时器还没启动时记下 kind,等 startTimer 返回已 abort 的 signal。
51
+ */
52
+ abort(taskId: string, kind?: TaskAbortKind): boolean;
42
53
  /**
43
54
  * 取消超时计时器(Task 正常完成时调用)
44
55
  *
@@ -10,6 +10,7 @@
10
10
  * @see requirements.md Requirement 7 AC4
11
11
  */
12
12
  import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
13
+ import { TaskAbortError } from './task-abort.js';
13
14
  // ─────────────────────────────────────────────
14
15
  // Default Constants
15
16
  // ─────────────────────────────────────────────
@@ -31,6 +32,11 @@ const MAX_TIMEOUT_MS = 3_600_000;
31
32
  */
32
33
  export class TaskTimeoutManager {
33
34
  entries = new Map();
35
+ /**
36
+ * startTimer 之前到达的停止。startTimer 消费后返回已 abort 的 signal,且不调用 onTimeout。
37
+ * clearTimer 只在计时器已经启动时清掉残留,避免把还没被 startTimer 看到的停止丢掉。
38
+ */
39
+ pendingAborts = new Map();
34
40
  defaultTimeoutMs;
35
41
  constructor(options) {
36
42
  this.defaultTimeoutMs = options?.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -49,12 +55,22 @@ export class TaskTimeoutManager {
49
55
  throw new Error(`Timeout timer already exists for task '${taskId}'. Call clearTimer() first.`);
50
56
  }
51
57
  const effectiveTimeout = Math.min(Math.max(timeoutMs ?? this.defaultTimeoutMs, 0), MAX_TIMEOUT_MS);
58
+ const pendingKind = this.pendingAborts.get(taskId);
59
+ if (pendingKind) {
60
+ this.pendingAborts.delete(taskId);
61
+ const already = new AbortController();
62
+ already.abort(new TaskAbortError(taskId, pendingKind, effectiveTimeout));
63
+ return already.signal;
64
+ }
52
65
  const abortController = new AbortController();
53
66
  const timer = setTimeout(() => {
54
- // 超时触发:abort signal + 执行回调
55
- abortController.abort(new Error(`Task '${taskId}' timed out after ${effectiveTimeout}ms`));
67
+ // abort() 已取走条目时不再标 timeout
68
+ if (!this.entries.has(taskId))
69
+ return;
56
70
  this.entries.delete(taskId);
57
- // 触发外部回调(fire-and-forget,错误不应阻塞清理)
71
+ if (!abortController.signal.aborted) {
72
+ abortController.abort(new TaskAbortError(taskId, 'timeout', effectiveTimeout));
73
+ }
58
74
  void onTimeout().catch(() => {
59
75
  // onTimeout 回调的错误由调用方处理,这里静默忽略
60
76
  });
@@ -63,9 +79,27 @@ export class TaskTimeoutManager {
63
79
  if (timer.unref) {
64
80
  timer.unref();
65
81
  }
66
- this.entries.set(taskId, { timer, abortController });
82
+ this.entries.set(taskId, { timer, abortController, timeoutMs: effectiveTimeout });
67
83
  return abortController.signal;
68
84
  }
85
+ /**
86
+ * 停止这一轮。清掉计时器,不调用 onTimeout。
87
+ * 计时器还没启动时记下 kind,等 startTimer 返回已 abort 的 signal。
88
+ */
89
+ abort(taskId, kind = 'cancelled') {
90
+ this.pendingAborts.delete(taskId);
91
+ const entry = this.entries.get(taskId);
92
+ if (!entry) {
93
+ this.pendingAborts.set(taskId, kind);
94
+ return true;
95
+ }
96
+ clearTimeout(entry.timer);
97
+ this.entries.delete(taskId);
98
+ if (!entry.abortController.signal.aborted) {
99
+ entry.abortController.abort(new TaskAbortError(taskId, kind, entry.timeoutMs));
100
+ }
101
+ return true;
102
+ }
69
103
  /**
70
104
  * 取消超时计时器(Task 正常完成时调用)
71
105
  *
@@ -79,6 +113,7 @@ export class TaskTimeoutManager {
79
113
  }
80
114
  clearTimeout(entry.timer);
81
115
  this.entries.delete(taskId);
116
+ this.pendingAborts.delete(taskId);
82
117
  return true;
83
118
  }
84
119
  /**
@@ -113,5 +148,6 @@ export class TaskTimeoutManager {
113
148
  entry.abortController.abort(new Error(`TaskTimeoutManager disposed, task '${taskId}' aborted`));
114
149
  }
115
150
  this.entries.clear();
151
+ this.pendingAborts.clear();
116
152
  }
117
153
  }