@gt-fe/eap-runtime 0.0.32 → 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 (45) hide show
  1. package/dist/api/handlers/invoke.handler.d.ts +0 -1
  2. package/dist/api/handlers/invoke.handler.js +25 -6
  3. package/dist/api/handlers/stream.handler.d.ts +0 -1
  4. package/dist/api/handlers/stream.handler.js +22 -18
  5. package/dist/common/types.d.ts +4 -3
  6. package/dist/graph/adapters/langgraph.adapter.d.ts +0 -1
  7. package/dist/graph/adapters/langgraph.adapter.js +117 -82
  8. package/dist/graph/adapters/skill-entry-span.js +4 -1
  9. package/dist/graph/adapters/subgraph-compiler.js +11 -9
  10. package/dist/graph/contracts/executable-graph.d.ts +2 -0
  11. package/dist/graph/execution-trace-context.d.ts +3 -0
  12. package/dist/graph/nodes/llm-node.d.ts +7 -2
  13. package/dist/graph/nodes/llm-node.js +8 -3
  14. package/dist/graph/nodes/skill-bind-node.d.ts +1 -1
  15. package/dist/graph/nodes/skill-bind-node.js +6 -1
  16. package/dist/graph/nodes/tool-node.js +1 -0
  17. package/dist/providers/http-llm-provider.js +27 -10
  18. package/dist/registry/graph-loader.d.ts +11 -0
  19. package/dist/registry/graph-loader.js +28 -4
  20. package/dist/runtime/agent-runtime.d.ts +7 -0
  21. package/dist/runtime/agent-runtime.js +69 -38
  22. package/dist/runtime/types.d.ts +1 -1
  23. package/dist/session/persist-turn-messages.d.ts +10 -2
  24. package/dist/session/persist-turn-messages.js +35 -10
  25. package/dist/sse/events/index.d.ts +6 -4
  26. package/dist/sse/events/index.js +3 -1
  27. package/dist/sse/events/reasoning-events.d.ts +24 -2
  28. package/dist/sse/events/reasoning-events.js +3 -1
  29. package/dist/sse/events/skill-events.d.ts +2 -0
  30. package/dist/task/contracts/task-manager.contract.d.ts +2 -2
  31. package/dist/task/contracts/task-manager.contract.js +1 -1
  32. package/dist/task/index.d.ts +2 -0
  33. package/dist/task/index.js +1 -0
  34. package/dist/task/task-abort.d.ts +14 -0
  35. package/dist/task/task-abort.js +33 -0
  36. package/dist/task/task-timeout.d.ts +11 -0
  37. package/dist/task/task-timeout.js +40 -4
  38. package/dist/task/task.service.js +11 -5
  39. package/dist/tool/index.d.ts +1 -1
  40. package/dist/tool/index.js +1 -1
  41. package/dist/tool/mcp-list.d.ts +3 -1
  42. package/dist/tool/mcp-list.js +4 -1
  43. package/dist/tool/system-prompt-builder.d.ts +10 -8
  44. package/dist/tool/system-prompt-builder.js +36 -26
  45. package/package.json +1 -1
@@ -13,7 +13,7 @@ import { LangGraphAdapter } from './langgraph.adapter.js';
13
13
  import { assertSkillGraphHasNoSubgraphNodes } from './graph-nesting.js';
14
14
  import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
15
15
  import { skillSpanFields, skillTraceIdentity } from './skill-entry-span.js';
16
- import { filterLoadedToolsForSkill, } from '../../tool/system-prompt-builder.js';
16
+ import { filterLoadedToolsForSkill, spokenName, } from '../../tool/system-prompt-builder.js';
17
17
  import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
18
18
  import { pickContextFields } from '../../token/context-window.js';
19
19
  import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../registry/skill-io-guard.js';
@@ -65,11 +65,14 @@ export class SubGraphCompiler {
65
65
  ? state['skillSpanId']
66
66
  : '';
67
67
  const resuming = Boolean(existingSpanId);
68
+ const identity = skillTraceIdentity(state, resolvedSkillId);
69
+ const spanName = resolvedSkillId || dsl.name || 'skill';
70
+ const skillName = spokenName(identity.skillName, resolvedSkillId) ?? spanName;
68
71
  const auditCtx = resuming
69
72
  ? undefined
70
73
  : this.skillAuditRecorder?.startInvocation({
71
74
  skillId: resolvedSkillId,
72
- skillName: dsl.name,
75
+ skillName,
73
76
  agentCode: (state['agentCode'] ?? state['agentId']),
74
77
  userId: state['userId'] ?? '',
75
78
  taskId: state['taskId'],
@@ -77,8 +80,6 @@ export class SubGraphCompiler {
77
80
  });
78
81
  // 用父图这次调用上的 collector;内层 invoke 只补 collector/traceId,沿用外层流式袋。
79
82
  const { collector: traceCollector, traceId } = resolveExecutionTrace(state);
80
- const skillName = dsl.name || resolvedSkillId;
81
- const identity = skillTraceIdentity(state, resolvedSkillId);
82
83
  if (!resuming) {
83
84
  adapter.emitStreamEvent({
84
85
  type: 'skill.started',
@@ -87,7 +88,7 @@ export class SubGraphCompiler {
87
88
  timestamp: new Date().toISOString(),
88
89
  payload: {
89
90
  skillId: resolvedSkillId,
90
- skillName: identity.skillName || skillName,
91
+ skillName,
91
92
  ...(identity.skillSpace ? { skillSpace: identity.skillSpace } : {}),
92
93
  ...(identity.skillVersion ? { skillVersion: identity.skillVersion } : {}),
93
94
  },
@@ -99,7 +100,7 @@ export class SubGraphCompiler {
99
100
  const fields = skillSpanFields(identity, { source: 'subgraph' });
100
101
  skillSpanId = await traceCollector.createNode({
101
102
  traceId,
102
- name: resolvedSkillId || dsl.name || 'skill',
103
+ name: spanName,
103
104
  type: 'skill',
104
105
  agentCode: state['agentCode'] ?? state['agentId'],
105
106
  ...(fields.version ? { version: fields.version } : {}),
@@ -111,7 +112,7 @@ export class SubGraphCompiler {
111
112
  });
112
113
  }
113
114
  catch (err) {
114
- warnTraceSpanFailure('createNode', { type: 'skill', name: resolvedSkillId || dsl.name || 'skill', traceId }, err);
115
+ warnTraceSpanFailure('createNode', { type: 'skill', name: spanName, traceId }, err);
115
116
  skillSpanId = null;
116
117
  }
117
118
  }
@@ -154,6 +155,7 @@ export class SubGraphCompiler {
154
155
  userId,
155
156
  delegationToken,
156
157
  agentCode,
158
+ agentName: typeof state['agentName'] === 'string' ? state['agentName'] : undefined,
157
159
  taskId: state['taskId'],
158
160
  correlationId: state['correlationId'],
159
161
  sessionId: state['sessionId'],
@@ -194,7 +196,7 @@ export class SubGraphCompiler {
194
196
  });
195
197
  }
196
198
  catch (err) {
197
- warnTraceSpanFailure('completeNode', { type: 'skill', name: skillName, traceId, spanId: skillSpanId }, err);
199
+ warnTraceSpanFailure('completeNode', { type: 'skill', name: spanName, traceId, spanId: skillSpanId }, err);
198
200
  }
199
201
  }
200
202
  // 4. 只回写子图本步新增消息(含内部 tool 回合),禁止把入口历史再 append
@@ -269,7 +271,7 @@ export class SubGraphCompiler {
269
271
  });
270
272
  }
271
273
  catch (completeErr) {
272
- warnTraceSpanFailure('completeNode', { type: 'skill', name: skillName, traceId, spanId: skillSpanId }, completeErr);
274
+ warnTraceSpanFailure('completeNode', { type: 'skill', name: spanName, traceId, spanId: skillSpanId }, completeErr);
273
275
  }
274
276
  }
275
277
  adapter.emitStreamEvent({
@@ -181,6 +181,8 @@ export interface GraphState {
181
181
  agentId?: string;
182
182
  /** Agent 对外主键 */
183
183
  agentCode?: string;
184
+ /** Agent 展示名(manifest.name);身份段对用户说话用,缺省回退 agentCode */
185
+ agentName?: string;
184
186
  /** Hosting 路由选定的预装版本(AR-3b;写入 Checkpoint 供 HITL/Fork 恢复) */
185
187
  agentVersion?: string;
186
188
  correlationId?: string;
@@ -8,9 +8,12 @@ export interface ExecutionStreamBag {
8
8
  correlationId?: string;
9
9
  };
10
10
  chunkIndex: number;
11
+ /** 本轮 reasoning.chunk 序号,与 chunkIndex 分开从 0 计 */
12
+ reasoningChunkIndex: number;
11
13
  iteration: number;
12
14
  reasoningStartedThisTurn: boolean;
13
15
  streamedTextThisTurn: boolean;
16
+ graphId?: string;
14
17
  }
15
18
  export interface ExecutionRunContext {
16
19
  collector?: ITraceCollector;
@@ -107,6 +107,11 @@ export interface LLMChunk {
107
107
  /** thinking 增量;最终聚合后写入 assistant 消息 */
108
108
  reasoningContent?: string;
109
109
  }
110
+ /** 流式旁路增量:思考与正文分字段,空字段不传 */
111
+ export type LLMStreamDelta = {
112
+ content?: string;
113
+ reasoningContent?: string;
114
+ };
110
115
  /**
111
116
  * ILLMProvider — LLM 调用提供者接口
112
117
  *
@@ -145,9 +150,9 @@ export declare class LLMNode {
145
150
  * @param config - LLM 节点配置
146
151
  * @param provider - LLM 调用提供者
147
152
  * @param streaming - 是否启用流式输出(默认 false)
148
- * @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
153
+ * @param onChunk - 流式旁路:思考走 reasoningContent,正文走 content
149
154
  */
150
- constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (chunk: string) => void, interceptors?: Pick<ExecutionInterceptors, "beforeModel" | "afterModel"> | undefined);
155
+ constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (delta: LLMStreamDelta) => void, interceptors?: Pick<ExecutionInterceptors, "beforeModel" | "afterModel"> | undefined);
151
156
  /**
152
157
  * execute — StateGraph 节点函数
153
158
  *
@@ -52,7 +52,7 @@ export class LLMNode {
52
52
  * @param config - LLM 节点配置
53
53
  * @param provider - LLM 调用提供者
54
54
  * @param streaming - 是否启用流式输出(默认 false)
55
- * @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
55
+ * @param onChunk - 流式旁路:思考走 reasoningContent,正文走 content
56
56
  */
57
57
  constructor(nodeId, config, provider, streaming = false, onChunk, interceptors) {
58
58
  this.interceptors = interceptors;
@@ -247,8 +247,13 @@ export class LLMNode {
247
247
  let toolCalls;
248
248
  const iterator = this.provider.stream(messages, this.config);
249
249
  for await (const chunk of iterator) {
250
- if (chunk.content && this.onChunk) {
251
- this.onChunk(chunk.content);
250
+ const content = chunk.content || '';
251
+ const reasoningContent = chunk.reasoningContent || '';
252
+ if (this.onChunk && (content || reasoningContent)) {
253
+ this.onChunk({
254
+ ...(content ? { content } : {}),
255
+ ...(reasoningContent ? { reasoningContent } : {}),
256
+ });
252
257
  }
253
258
  chunks.push(chunk.content);
254
259
  if (chunk.reasoningContent) {
@@ -2,7 +2,7 @@
2
2
  * 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
3
3
  * 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
4
4
  */
5
- import type { LoadedSkillInfo, LoadedSkillMap } from '../../tool/system-prompt-builder.js';
5
+ import { type LoadedSkillInfo, type LoadedSkillMap } from '../../tool/system-prompt-builder.js';
6
6
  export type SkillRoute = 'skill' | 'clarify' | 'none';
7
7
  export interface SkillBindPatch {
8
8
  skillRoute: SkillRoute;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
3
+ * 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
4
+ */
5
+ import { spokenName } from '../../tool/system-prompt-builder.js';
1
6
  function lastAssistantText(messages) {
2
7
  for (let i = messages.length - 1; i >= 0; i--) {
3
8
  const msg = messages[i];
@@ -89,7 +94,7 @@ export function bindSelectedSkill(state) {
89
94
  return {
90
95
  skillRoute: 'skill',
91
96
  activatedSkill: skill.skillId,
92
- skillName: skill.name || skill.skillId,
97
+ skillName: spokenName(skill.name, skill.skillId),
93
98
  skillInput: params,
94
99
  messages: nextMessages(),
95
100
  };
@@ -203,6 +203,7 @@ export class ToolNode {
203
203
  correlationId: req.correlationId,
204
204
  skillId,
205
205
  ...(identity.toolset ? { toolset: identity.toolset } : {}),
206
+ ...(identity.toolDisplayName ? { toolDisplayName: identity.toolDisplayName } : {}),
206
207
  },
207
208
  });
208
209
  }
@@ -119,7 +119,7 @@ export class HttpLLMProvider {
119
119
  });
120
120
  }
121
121
  catch (err) {
122
- throwIfGraphTimedOut();
122
+ throwIfGraphAborted();
123
123
  throw new Error(this.formatNetworkError(url, err));
124
124
  }
125
125
  if (!response.ok) {
@@ -170,6 +170,7 @@ export class HttpLLMProvider {
170
170
  return result;
171
171
  }
172
172
  catch (err) {
173
+ const propagated = graphAbortOr(err);
173
174
  await this.writeTurn({
174
175
  url,
175
176
  messages,
@@ -180,10 +181,10 @@ export class HttpLLMProvider {
180
181
  inputTokens: 0,
181
182
  outputTokens: 0,
182
183
  finishReason: 'error',
183
- error: err instanceof Error ? err.message : String(err),
184
+ error: propagated instanceof Error ? propagated.message : String(propagated),
184
185
  },
185
186
  });
186
- throw err;
187
+ throw propagated;
187
188
  }
188
189
  finally {
189
190
  clearTimeout(timeout);
@@ -244,7 +245,7 @@ export class HttpLLMProvider {
244
245
  });
245
246
  }
246
247
  catch (err) {
247
- throwIfGraphTimedOut();
248
+ throwIfGraphAborted();
248
249
  throw new Error(this.formatNetworkError(url, err));
249
250
  }
250
251
  if (!response.ok) {
@@ -397,6 +398,7 @@ export class HttpLLMProvider {
397
398
  });
398
399
  }
399
400
  catch (err) {
401
+ const propagated = graphAbortOr(err);
400
402
  await this.writeTurn({
401
403
  url,
402
404
  messages,
@@ -411,10 +413,10 @@ export class HttpLLMProvider {
411
413
  inputTokens,
412
414
  outputTokens,
413
415
  finishReason: 'error',
414
- error: err instanceof Error ? err.message : String(err),
416
+ error: propagated instanceof Error ? propagated.message : String(propagated),
415
417
  },
416
418
  });
417
- throw err;
419
+ throw propagated;
418
420
  }
419
421
  finally {
420
422
  clearTimeout(timeout);
@@ -528,11 +530,26 @@ function linkGraphAbort(controller) {
528
530
  graphSignal.addEventListener('abort', onAbort, { once: true });
529
531
  return () => graphSignal.removeEventListener('abort', onAbort);
530
532
  }
531
- function throwIfGraphTimedOut() {
532
- const reason = getExecutionTrace()?.abortSignal?.reason;
533
- if (reason instanceof Error && /timed out/i.test(reason.message)) {
533
+ /** 这一轮 signal 已 abort 就抛出 reason。超时和用户停止都走这里,不再只认 timed out。 */
534
+ function throwIfGraphAborted() {
535
+ const reason = graphAbortReason();
536
+ if (reason)
534
537
  throw reason;
535
- }
538
+ }
539
+ /** 流式 read 抛出的 DOMException 换成这一轮 signal 上的 reason。 */
540
+ function graphAbortOr(err) {
541
+ return graphAbortReason() ?? err;
542
+ }
543
+ function graphAbortReason() {
544
+ const signal = getExecutionTrace()?.abortSignal;
545
+ if (!signal?.aborted)
546
+ return undefined;
547
+ const reason = signal.reason;
548
+ if (reason instanceof Error)
549
+ return reason;
550
+ const err = new Error(reason == null ? 'Task aborted' : String(reason));
551
+ err.name = 'AbortError';
552
+ return err;
536
553
  }
537
554
  function formatNetworkDetail(error, depth = 0) {
538
555
  if (depth > 5)
@@ -20,8 +20,13 @@ export interface LoadAgentOptions {
20
20
  export declare function resolveAgentSystemPromptFromLoader(loader: {
21
21
  resolveAgentSystemPrompt?: (agentCode: string, version?: string) => Promise<string | undefined>;
22
22
  } | undefined, agentCode: string, version?: string): Promise<string | undefined>;
23
+ export declare function resolveAgentDisplayNameFromLoader(loader: {
24
+ resolveAgentDisplayName?: (agentCode: string, version?: string) => Promise<string | undefined>;
25
+ } | undefined, agentCode: string, version?: string): Promise<string | undefined>;
23
26
  /** manifest.prompts.system;空白视为缺省。 */
24
27
  export declare function extractAgentSystemPrompt(manifest: unknown): string | undefined;
28
+ /** manifest.name;空白视为缺省。身份段对用户说话用,不当路由键。 */
29
+ export declare function extractAgentDisplayName(manifest: unknown): string | undefined;
25
30
  export interface GraphLoaderConfig {
26
31
  registryClient?: AgentRegistryClient;
27
32
  deploymentContext?: DeploymentContext;
@@ -116,6 +121,12 @@ export declare class GraphLoader {
116
121
  * 每轮 execute/stream 注入 GraphState.agentSystemPrompt;不写进节点 config。
117
122
  */
118
123
  resolveAgentSystemPrompt(agentCode: string, version?: string): Promise<string | undefined>;
124
+ /**
125
+ * Agent 展示名(manifest.name)。
126
+ * 每轮 execute/stream 注入 GraphState.agentName;身份段对用户说话用。
127
+ */
128
+ resolveAgentDisplayName(agentCode: string, version?: string): Promise<string | undefined>;
129
+ private fromAgentManifest;
119
130
  /**
120
131
  * 解析 agent tools[] + graph tool 节点 union;
121
132
  * server+预装:以预装 manifest 为准;desktop 合并 ILocalToolInstallStore。
@@ -64,6 +64,11 @@ export async function resolveAgentSystemPromptFromLoader(loader, agentCode, vers
64
64
  return undefined;
65
65
  return loader.resolveAgentSystemPrompt(agentCode, version);
66
66
  }
67
+ export async function resolveAgentDisplayNameFromLoader(loader, agentCode, version) {
68
+ if (!loader || typeof loader.resolveAgentDisplayName !== 'function')
69
+ return undefined;
70
+ return loader.resolveAgentDisplayName(agentCode, version);
71
+ }
67
72
  /** manifest.prompts.system;空白视为缺省。 */
68
73
  export function extractAgentSystemPrompt(manifest) {
69
74
  if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
@@ -77,6 +82,16 @@ export function extractAgentSystemPrompt(manifest) {
77
82
  const trimmed = system.trim();
78
83
  return trimmed ? trimmed : undefined;
79
84
  }
85
+ /** manifest.name;空白视为缺省。身份段对用户说话用,不当路由键。 */
86
+ export function extractAgentDisplayName(manifest) {
87
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
88
+ return undefined;
89
+ const name = manifest.name;
90
+ if (typeof name !== 'string')
91
+ return undefined;
92
+ const trimmed = name.trim();
93
+ return trimmed ? trimmed : undefined;
94
+ }
80
95
  function extractSkillPrompt(manifest) {
81
96
  const prompt = manifest.prompt;
82
97
  if (typeof prompt === 'string' && prompt.trim())
@@ -769,18 +784,27 @@ export class GraphLoader {
769
784
  * 每轮 execute/stream 注入 GraphState.agentSystemPrompt;不写进节点 config。
770
785
  */
771
786
  async resolveAgentSystemPrompt(agentCode, version) {
787
+ return this.fromAgentManifest(agentCode, version, extractAgentSystemPrompt);
788
+ }
789
+ /**
790
+ * Agent 展示名(manifest.name)。
791
+ * 每轮 execute/stream 注入 GraphState.agentName;身份段对用户说话用。
792
+ */
793
+ async resolveAgentDisplayName(agentCode, version) {
794
+ return this.fromAgentManifest(agentCode, version, extractAgentDisplayName);
795
+ }
796
+ async fromAgentManifest(agentCode, version, extract) {
772
797
  if (this.deploymentSnapshot) {
773
798
  const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, version);
774
- if (snap) {
775
- return extractAgentSystemPrompt(snap.manifest);
776
- }
799
+ if (snap)
800
+ return extract(snap.manifest);
777
801
  if (this.blockRegistryDefinitions)
778
802
  return undefined;
779
803
  }
780
804
  if (this.blockRegistryDefinitions || !this.registryClient)
781
805
  return undefined;
782
806
  const record = await this.registryClient.getAgent(agentCode);
783
- return extractAgentSystemPrompt(record?.manifest);
807
+ return extract(record?.manifest);
784
808
  }
785
809
  /**
786
810
  * 解析 agent tools[] + graph tool 节点 union;
@@ -11,6 +11,7 @@ import type { ICheckpointSaver } from '../checkpoint/contracts/checkpoint-saver.
11
11
  import type { ITraceCollector } from '../contracts/trace-collector.js';
12
12
  import type { IStateGraphEngine } from '../graph/contracts/state-graph.contract.js';
13
13
  import type { TaskTimeoutManager } from '../task/task-timeout.js';
14
+ import { type TaskAbortKind } from '../task/task-abort.js';
14
15
  import { HitlHandler } from '../graph/hitl/hitl-handler.js';
15
16
  import { type GraphLoader } from '../registry/graph-loader.js';
16
17
  import type { SessionContextService } from '../session/session-context.service.js';
@@ -88,6 +89,12 @@ export interface ConversationDetail {
88
89
  export declare class AgentRuntime {
89
90
  private readonly deps;
90
91
  constructor(deps: AgentRuntimeDeps);
92
+ /**
93
+ * 停止这一轮。超时和用户停止共用 timeoutManager 上的那条 AbortSignal。
94
+ * 计时器还没启动时先记下,startTimer 会返回已 abort 的 signal,且不调用 onTimeout。
95
+ * @returns 已 abort 或已记下。没有 timeoutManager 时返回 false。
96
+ */
97
+ abort(taskId: string, kind?: TaskAbortKind): boolean;
91
98
  /** 从 GraphLoader 解析 tool manifest 映射(invoke/stream/resume/fork 共用) */
92
99
  private resolveLoadedTools;
93
100
  /** 从预装重载 Skill 定义(HITL/Fork 时 ephemeral 字段可能丢失) */
@@ -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
- import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
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)
@@ -330,6 +368,7 @@ export class AgentRuntime {
330
368
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
331
369
  : undefined;
332
370
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
371
+ const agentName = await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
333
372
  const facing = await loadModelFacingHistory({
334
373
  sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
335
374
  checkpointSaver: this.deps.checkpointSaver,
@@ -375,8 +414,8 @@ export class AgentRuntime {
375
414
  });
376
415
  const startTime = Date.now();
377
416
  const assistantBuckets = createStreamAssistantBuckets();
417
+ const reasoningState = createStreamReasoningState();
378
418
  let toolCallReasoningContent;
379
- let assistantReasoningContent;
380
419
  const pendingToolCalls = [];
381
420
  const toolResults = [];
382
421
  const artifactLinks = [];
@@ -402,6 +441,7 @@ export class AgentRuntime {
402
441
  promptRoots,
403
442
  agentDir,
404
443
  agentSystemPrompt,
444
+ agentName,
405
445
  skillInput: resolveSkillInputFromInvokeInput(params.input),
406
446
  traceId: resolveTraceId(params),
407
447
  traceCollector: this.deps.traceCollector,
@@ -417,16 +457,12 @@ export class AgentRuntime {
417
457
  arguments: p.arguments ?? {},
418
458
  });
419
459
  }
420
- if (typeof p.reasoningContent === 'string' && p.reasoningContent) {
421
- toolCallReasoningContent = p.reasoningContent;
422
- }
460
+ toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
423
461
  }
424
462
  // 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
425
463
  appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
426
464
  collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
427
- const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
428
- if (streamedReasoning)
429
- assistantReasoningContent = streamedReasoning;
465
+ applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
430
466
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
431
467
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
432
468
  if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
@@ -460,7 +496,7 @@ export class AgentRuntime {
460
496
  assistantText: injected,
461
497
  finishPoints: graph.finishPoints,
462
498
  toolCallReasoningContent,
463
- assistantReasoningContent,
499
+ assistantReasoningContent: resolvedStreamReasoning(reasoningState),
464
500
  }),
465
501
  assistantTokenCount: tokenSummary.total.output,
466
502
  assistantDurationMs: Date.now() - startTime,
@@ -493,15 +529,11 @@ export class AgentRuntime {
493
529
  yield this.toSSE('hitl.waited', correlationId, task.id, buildHitlWaitedPayload(hitlPaused));
494
530
  return;
495
531
  }
496
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
497
- if (timedOut) {
532
+ const abortKind = taskAbortKindOf(err, abortSignal);
533
+ if (abortKind) {
498
534
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
499
- await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
500
- yield this.toSSE('task.error', correlationId, task.id, {
501
- code: ErrorCodes.TASK_TIMEOUT,
502
- message: 'Task execution timed out',
503
- recoverable: false,
504
- });
535
+ await settleAbortedTask(this.deps.taskService, task.id, abortKind);
536
+ yield this.toSSE('task.error', correlationId, task.id, taskAbortSseData(abortKind));
505
537
  return;
506
538
  }
507
539
  const message = err instanceof Error ? err.message : 'Unknown error';
@@ -617,6 +649,9 @@ export class AgentRuntime {
617
649
  agentSystemPrompt: (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof resumedState.agentSystemPrompt === 'string'
618
650
  ? resumedState.agentSystemPrompt
619
651
  : undefined),
652
+ agentName: (await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof resumedState.agentName === 'string' && resumedState.agentName.trim()
653
+ ? resumedState.agentName.trim()
654
+ : undefined),
620
655
  activatedSkill: resumedState.activatedSkill,
621
656
  skillRoute: resumedState.skillRoute ??
622
657
  (typeof resumedState.activatedSkill === 'string' && resumedState.activatedSkill
@@ -633,8 +668,8 @@ export class AgentRuntime {
633
668
  };
634
669
  const startTime = Date.now();
635
670
  const assistantBuckets = createStreamAssistantBuckets();
671
+ const reasoningState = createStreamReasoningState();
636
672
  let toolCallReasoningContent;
637
- let assistantReasoningContent;
638
673
  const pendingToolCalls = [];
639
674
  const toolResults = [];
640
675
  const artifactLinks = [];
@@ -650,15 +685,11 @@ export class AgentRuntime {
650
685
  arguments: p.arguments ?? {},
651
686
  });
652
687
  }
653
- if (typeof p.reasoningContent === 'string' && p.reasoningContent) {
654
- toolCallReasoningContent = p.reasoningContent;
655
- }
688
+ toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
656
689
  }
657
690
  appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
658
691
  collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
659
- const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
660
- if (streamedReasoning)
661
- assistantReasoningContent = streamedReasoning;
692
+ applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
662
693
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
663
694
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
664
695
  if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
@@ -693,7 +724,7 @@ export class AgentRuntime {
693
724
  assistantText: injected,
694
725
  finishPoints: graph.finishPoints,
695
726
  toolCallReasoningContent,
696
- assistantReasoningContent,
727
+ assistantReasoningContent: resolvedStreamReasoning(reasoningState),
697
728
  }),
698
729
  assistantTokenCount: tokenSummary.total.output,
699
730
  assistantDurationMs: Date.now() - startTime,
@@ -729,15 +760,11 @@ export class AgentRuntime {
729
760
  yield this.toSSE('hitl.waited', correlationId, taskId, buildHitlWaitedPayload(hitlPaused));
730
761
  return;
731
762
  }
732
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
733
- if (timedOut) {
763
+ const abortKind = taskAbortKindOf(err, abortSignal);
764
+ if (abortKind) {
734
765
  await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
735
- await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
736
- yield this.toSSE('task.error', correlationId, taskId, {
737
- code: ErrorCodes.TASK_TIMEOUT,
738
- message: 'Task execution timed out',
739
- recoverable: false,
740
- });
766
+ await settleAbortedTask(this.deps.taskService, taskId, abortKind);
767
+ yield this.toSSE('task.error', correlationId, taskId, taskAbortSseData(abortKind));
741
768
  return;
742
769
  }
743
770
  const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
@@ -814,6 +841,9 @@ export class AgentRuntime {
814
841
  const agentSystemPrompt = (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentSystemPrompt === 'string'
815
842
  ? forkedCheckpoint.state.agentSystemPrompt
816
843
  : undefined);
844
+ const agentName = (await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentName === 'string' && forkedCheckpoint.state.agentName.trim()
845
+ ? forkedCheckpoint.state.agentName.trim()
846
+ : undefined);
817
847
  const abortDeadlineMs = Date.now() + timeout;
818
848
  try {
819
849
  const forkState = { ...forkedCheckpoint.state };
@@ -827,6 +857,7 @@ export class AgentRuntime {
827
857
  agentVersion,
828
858
  agentDir,
829
859
  agentSystemPrompt,
860
+ agentName,
830
861
  userId,
831
862
  delegationToken: overrides.executionContext?.delegationToken,
832
863
  executionContext: overrides.executionContext,
@@ -896,15 +927,15 @@ export class AgentRuntime {
896
927
  await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
897
928
  const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
898
929
  const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
899
- const timedOut = err instanceof Error && /timed out/i.test(err.message);
900
- if (timedOut) {
901
- 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);
902
933
  return {
903
934
  newTraceId: traceId,
904
935
  originalTraceId,
905
936
  newTaskId: task.id,
906
937
  checkpointId,
907
- status: 'timeout',
938
+ status: abortKind === 'timeout' ? 'timeout' : 'cancelled',
908
939
  output: message,
909
940
  tokenUsage: {
910
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;