@gt-fe/eap-runtime 0.0.22 → 0.0.24
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/dist/api/handlers/invoke.handler.d.ts +4 -0
- package/dist/api/handlers/invoke.handler.js +38 -19
- package/dist/api/handlers/stream.handler.d.ts +4 -0
- package/dist/api/handlers/stream.handler.js +24 -15
- package/dist/api/routes/agent.routes.js +6 -5
- package/dist/checkpoint/checkpoint-ref-notify.js +1 -1
- package/dist/common/types.d.ts +6 -2
- package/dist/common/types.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +6 -4
- package/dist/graph/adapters/langgraph.adapter.js +103 -18
- package/dist/graph/contracts/executable-graph.d.ts +9 -0
- package/dist/graph/execution-trace-context.d.ts +4 -0
- package/dist/graph/execution-trace-context.js +3 -1
- package/dist/graph/nodes/rag/types.d.ts +4 -1
- package/dist/graph/nodes/tool-node.js +10 -1
- package/dist/providers/http-llm-provider.js +25 -0
- package/dist/registry/graph-loader.js +65 -9
- package/dist/runtime/agent-runtime.d.ts +3 -0
- package/dist/runtime/agent-runtime.js +95 -15
- package/dist/runtime/dev-runtime.js +9 -2
- package/dist/runtime/types.d.ts +1 -1
- package/dist/sse/events/response-events.d.ts +2 -2
- package/dist/sse/events/response-events.js +1 -1
- package/dist/task/contracts/task-manager.contract.d.ts +2 -0
- package/dist/task/index.d.ts +1 -0
- package/dist/task/index.js +1 -0
- package/dist/task/resolve-task-timeout.d.ts +6 -0
- package/dist/task/resolve-task-timeout.js +16 -0
- package/dist/task/task-timeout.d.ts +1 -1
- package/dist/task/task-timeout.js +3 -2
- package/dist/task/task.service.js +1 -0
- package/dist/tool/http-tool-executor.js +7 -3
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/loaded-tool-definition.d.ts +1 -1
- package/dist/tool/local-mcp-client.d.ts +1 -1
- package/dist/tool/local-mcp-client.js +2 -39
- package/dist/tool/local-tool-executor.js +32 -5
- package/dist/tool/mcp-list.d.ts +21 -0
- package/dist/tool/mcp-list.js +49 -3
- package/dist/tool/mcp-tool-executor.d.ts +1 -1
- package/dist/tool/mcp-tool-executor.js +58 -22
- package/dist/tool/model-visible-tool-content.d.ts +16 -0
- package/dist/tool/model-visible-tool-content.js +266 -0
- package/dist/tool/timeout.d.ts +5 -12
- package/dist/tool/timeout.js +12 -34
- package/dist/tool/tool-executor.interface.d.ts +7 -1
- package/dist/tool/tool-orchestrator.js +6 -1
- package/dist/tool/tool-result-injector.js +2 -1
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ import { inferHarnessSkillDir } from '../../tool/skill-dir-infer.js';
|
|
|
16
16
|
import { isHitlDecision, unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
17
17
|
import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
|
|
18
18
|
import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
|
|
19
|
-
import { toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
19
|
+
import { mcpToolsetCode, toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
20
20
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
21
21
|
import { emitRuntimeLog } from '../../common/logger.js';
|
|
22
22
|
import { decideBeforeTool, } from '../../execution/interceptors.js';
|
|
@@ -92,6 +92,7 @@ export class ToolNode {
|
|
|
92
92
|
// 4. 转换为 ToolCallRequest[](附带 manifest 路由字段)
|
|
93
93
|
const requests = lastAssistantMessage.toolCalls.map((tc) => {
|
|
94
94
|
const loaded = loadedTools?.[tc.name];
|
|
95
|
+
const toolsetCode = mcpToolsetCode(loaded);
|
|
95
96
|
const correlationId = String(state['correlationId'] ?? tc.id);
|
|
96
97
|
const taskId = String(state['taskId'] ?? state['correlationId'] ?? tc.id);
|
|
97
98
|
const command = typeof tc.arguments?.command === 'string' ? tc.arguments.command : undefined;
|
|
@@ -114,6 +115,7 @@ export class ToolNode {
|
|
|
114
115
|
// dev 上下文 DevMcpExecutor 直连所需;server 上下文会忽略
|
|
115
116
|
execution: loaded?.manifest.execution,
|
|
116
117
|
...(loaded?.manifest.version ? { toolVersion: loaded.manifest.version } : {}),
|
|
118
|
+
...(toolsetCode ? { toolsetCode } : {}),
|
|
117
119
|
correlationId,
|
|
118
120
|
taskId,
|
|
119
121
|
...(jail.workdir ? { jailWorkdir: jail.workdir } : {}),
|
|
@@ -506,10 +508,17 @@ export class ToolNode {
|
|
|
506
508
|
const delegationToken = String(executionContext?.delegationToken ??
|
|
507
509
|
state['delegationToken'] ??
|
|
508
510
|
this.defaultAuthContext.delegationToken);
|
|
511
|
+
const fromState = typeof state['agentVersion'] === 'string' ? state['agentVersion'].trim() : '';
|
|
512
|
+
const agentDir = typeof state['agentDir'] === 'string' ? state['agentDir'].trim() : '';
|
|
513
|
+
const fromDir = agentDir
|
|
514
|
+
? agentDir.split(/[/\\]/).filter(Boolean).pop()
|
|
515
|
+
: undefined;
|
|
516
|
+
const agentVersion = fromState || fromDir;
|
|
509
517
|
return {
|
|
510
518
|
userId,
|
|
511
519
|
delegationToken,
|
|
512
520
|
agentCode,
|
|
521
|
+
...(agentVersion ? { agentVersion } : {}),
|
|
513
522
|
};
|
|
514
523
|
}
|
|
515
524
|
shouldForceServer(state, toolName) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { resolveLlmModelName } from './resolve-llm-model.js';
|
|
10
10
|
import { createFileLlmTurnLogFromEnv, maskApiKey } from '../debug/llm-turn-log.js';
|
|
11
11
|
import { buildContextUsage, turnSectionChars } from '../token/context-window.js';
|
|
12
|
+
import { getExecutionTrace } from '../graph/execution-trace-context.js';
|
|
12
13
|
export const DEFAULT_LLM_HTTP_TIMEOUT_MS = 180_000;
|
|
13
14
|
export class HttpLLMProvider {
|
|
14
15
|
apiKey;
|
|
@@ -101,6 +102,7 @@ export class HttpLLMProvider {
|
|
|
101
102
|
}
|
|
102
103
|
const controller = new AbortController();
|
|
103
104
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
105
|
+
const unlinkGraphAbort = linkGraphAbort(controller);
|
|
104
106
|
const url = `${this.baseUrl}/chat/completions`;
|
|
105
107
|
const startedAt = Date.now();
|
|
106
108
|
try {
|
|
@@ -117,6 +119,7 @@ export class HttpLLMProvider {
|
|
|
117
119
|
});
|
|
118
120
|
}
|
|
119
121
|
catch (err) {
|
|
122
|
+
throwIfGraphTimedOut();
|
|
120
123
|
throw new Error(this.formatNetworkError(url, err));
|
|
121
124
|
}
|
|
122
125
|
if (!response.ok) {
|
|
@@ -184,6 +187,7 @@ export class HttpLLMProvider {
|
|
|
184
187
|
}
|
|
185
188
|
finally {
|
|
186
189
|
clearTimeout(timeout);
|
|
190
|
+
unlinkGraphAbort();
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
async *stream(messages, config) {
|
|
@@ -217,6 +221,7 @@ export class HttpLLMProvider {
|
|
|
217
221
|
}
|
|
218
222
|
const controller = new AbortController();
|
|
219
223
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
224
|
+
const unlinkGraphAbort = linkGraphAbort(controller);
|
|
220
225
|
const url = `${this.baseUrl}/chat/completions`;
|
|
221
226
|
const startedAt = Date.now();
|
|
222
227
|
const contentChunks = [];
|
|
@@ -239,6 +244,7 @@ export class HttpLLMProvider {
|
|
|
239
244
|
});
|
|
240
245
|
}
|
|
241
246
|
catch (err) {
|
|
247
|
+
throwIfGraphTimedOut();
|
|
242
248
|
throw new Error(this.formatNetworkError(url, err));
|
|
243
249
|
}
|
|
244
250
|
if (!response.ok) {
|
|
@@ -412,6 +418,7 @@ export class HttpLLMProvider {
|
|
|
412
418
|
}
|
|
413
419
|
finally {
|
|
414
420
|
clearTimeout(timeout);
|
|
421
|
+
unlinkGraphAbort();
|
|
415
422
|
}
|
|
416
423
|
}
|
|
417
424
|
// ─────────────────────────────────────────────
|
|
@@ -509,6 +516,24 @@ export class HttpLLMProvider {
|
|
|
509
516
|
return `LLM network error calling ${url}: ${detail}`;
|
|
510
517
|
}
|
|
511
518
|
}
|
|
519
|
+
function linkGraphAbort(controller) {
|
|
520
|
+
const graphSignal = getExecutionTrace()?.abortSignal;
|
|
521
|
+
if (!graphSignal)
|
|
522
|
+
return () => undefined;
|
|
523
|
+
if (graphSignal.aborted) {
|
|
524
|
+
controller.abort(graphSignal.reason);
|
|
525
|
+
return () => undefined;
|
|
526
|
+
}
|
|
527
|
+
const onAbort = () => controller.abort(graphSignal.reason);
|
|
528
|
+
graphSignal.addEventListener('abort', onAbort, { once: true });
|
|
529
|
+
return () => graphSignal.removeEventListener('abort', onAbort);
|
|
530
|
+
}
|
|
531
|
+
function throwIfGraphTimedOut() {
|
|
532
|
+
const reason = getExecutionTrace()?.abortSignal?.reason;
|
|
533
|
+
if (reason instanceof Error && /timed out/i.test(reason.message)) {
|
|
534
|
+
throw reason;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
512
537
|
function formatNetworkDetail(error, depth = 0) {
|
|
513
538
|
if (depth > 5)
|
|
514
539
|
return '...';
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* 生产(server + EAP_PREINSTALL_ROOT):以预装为准,缺失则失败(04 Req 8.1)。
|
|
5
5
|
* 开发态:Registry + 可选 ./tools 本地 manifest。
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
8
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
8
9
|
import path from 'node:path';
|
|
9
10
|
import { resolveAgentModelAllowlist, resolveAgentDefaultModel } from './agent-models.js';
|
|
10
11
|
import { DEFAULT_HTTP_TOOL_TIMEOUT_SEC, DEFAULT_TOOL_TIMEOUT_SEC } from '../tool/timeout.js';
|
|
@@ -142,7 +143,7 @@ const DEFAULT_GRAPH_DSL = {
|
|
|
142
143
|
],
|
|
143
144
|
entryPoint: 'llm',
|
|
144
145
|
finishPoints: ['__end__'],
|
|
145
|
-
config: { timeout:
|
|
146
|
+
config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
|
|
146
147
|
};
|
|
147
148
|
/** 源图有非空字符串则保留;缺省或空数组为 `['__end__']`。预装 graph.json 与清单内嵌图同一收口。 */
|
|
148
149
|
function finishPointsFromGraphDocument(graph) {
|
|
@@ -155,8 +156,8 @@ function finishPointsFromGraphDocument(graph) {
|
|
|
155
156
|
.filter((id) => id.length > 0);
|
|
156
157
|
return ids.length > 0 ? ids : ['__end__'];
|
|
157
158
|
}
|
|
158
|
-
const DEFAULT_LOADED_GRAPH_CONFIG = { timeout:
|
|
159
|
-
/** 默认 timeout
|
|
159
|
+
const DEFAULT_LOADED_GRAPH_CONFIG = { timeout: DEFAULT_GRAPH_TIMEOUT_MS };
|
|
160
|
+
/** 默认 timeout {@link DEFAULT_GRAPH_TIMEOUT_MS};源图 `config` 覆盖。`maxIterations` 可留在 DSL 上,引擎忽略。 */
|
|
160
161
|
function configFromGraphDocument(graph) {
|
|
161
162
|
const raw = graph.config;
|
|
162
163
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
@@ -463,16 +464,71 @@ function fromInlineToolDeclaration(tool) {
|
|
|
463
464
|
inputSchema: tool.input,
|
|
464
465
|
};
|
|
465
466
|
}
|
|
467
|
+
const DEV_LOCAL_MANIFEST_FILES = [
|
|
468
|
+
'toolset.manifest.json',
|
|
469
|
+
'tool.manifest.json',
|
|
470
|
+
'manifest.json',
|
|
471
|
+
];
|
|
472
|
+
async function readJsonObject(filePath) {
|
|
473
|
+
try {
|
|
474
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
475
|
+
const parsed = JSON.parse(raw);
|
|
476
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
477
|
+
return null;
|
|
478
|
+
return parsed;
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function loadedToolFromDevDir(dir, toolName, doc, toolsetOnly) {
|
|
485
|
+
const tools = doc.tools;
|
|
486
|
+
if (Array.isArray(tools) && tools.length > 0 && typeof tools[0] === 'object') {
|
|
487
|
+
const pre = {
|
|
488
|
+
toolId: String(doc.code ?? path.basename(dir)),
|
|
489
|
+
name: String(doc.name ?? path.basename(dir)),
|
|
490
|
+
version: String(doc.version ?? '0.0.0'),
|
|
491
|
+
manifest: doc,
|
|
492
|
+
bundlePath: dir,
|
|
493
|
+
};
|
|
494
|
+
const expanded = expandPreinstallToolset(pre);
|
|
495
|
+
return expanded.find((d) => d.name === toolName) ?? null;
|
|
496
|
+
}
|
|
497
|
+
if (toolsetOnly)
|
|
498
|
+
return null;
|
|
499
|
+
return fromLocalManifestFile(toolName, doc);
|
|
500
|
+
}
|
|
501
|
+
async function loadDevLocalManifestFromDir(dir, toolName, toolsetOnly) {
|
|
502
|
+
const files = toolsetOnly ? ['toolset.manifest.json'] : DEV_LOCAL_MANIFEST_FILES;
|
|
503
|
+
for (const file of files) {
|
|
504
|
+
const doc = await readJsonObject(path.join(dir, file));
|
|
505
|
+
if (!doc)
|
|
506
|
+
continue;
|
|
507
|
+
const hit = loadedToolFromDevDir(dir, toolName, doc, toolsetOnly);
|
|
508
|
+
if (hit)
|
|
509
|
+
return hit;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
466
513
|
async function loadDevLocalManifest(toolName, toolsDir) {
|
|
467
|
-
const
|
|
514
|
+
const direct = await loadDevLocalManifestFromDir(path.join(toolsDir, toolName), toolName, false);
|
|
515
|
+
if (direct)
|
|
516
|
+
return direct;
|
|
517
|
+
let entries;
|
|
468
518
|
try {
|
|
469
|
-
|
|
470
|
-
const manifest = JSON.parse(raw);
|
|
471
|
-
return fromLocalManifestFile(toolName, manifest);
|
|
519
|
+
entries = await readdir(toolsDir, { withFileTypes: true });
|
|
472
520
|
}
|
|
473
521
|
catch {
|
|
474
522
|
return null;
|
|
475
523
|
}
|
|
524
|
+
for (const ent of entries) {
|
|
525
|
+
if (!ent.isDirectory() || ent.name === toolName)
|
|
526
|
+
continue;
|
|
527
|
+
const hit = await loadDevLocalManifestFromDir(path.join(toolsDir, ent.name), toolName, true);
|
|
528
|
+
if (hit)
|
|
529
|
+
return hit;
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
476
532
|
}
|
|
477
533
|
function fromLocalInstall(install) {
|
|
478
534
|
return {
|
|
@@ -703,7 +759,7 @@ export class GraphLoader {
|
|
|
703
759
|
},
|
|
704
760
|
],
|
|
705
761
|
config: {
|
|
706
|
-
timeout: manifest.config?.timeout ??
|
|
762
|
+
timeout: manifest.config?.timeout ?? DEFAULT_GRAPH_TIMEOUT_MS,
|
|
707
763
|
maxRetries: manifest.config?.retryCount,
|
|
708
764
|
},
|
|
709
765
|
};
|
|
@@ -10,6 +10,7 @@ import type { InvokeOrchestrator } from '../api/handlers/invoke.handler.js';
|
|
|
10
10
|
import type { ICheckpointSaver } from '../checkpoint/contracts/checkpoint-saver.contract.js';
|
|
11
11
|
import type { ITraceCollector } from '../contracts/trace-collector.js';
|
|
12
12
|
import type { IStateGraphEngine } from '../graph/contracts/state-graph.contract.js';
|
|
13
|
+
import type { TaskTimeoutManager } from '../task/task-timeout.js';
|
|
13
14
|
import { HitlHandler } from '../graph/hitl/hitl-handler.js';
|
|
14
15
|
import { type GraphLoader } from '../registry/graph-loader.js';
|
|
15
16
|
import type { SessionContextService } from '../session/session-context.service.js';
|
|
@@ -51,6 +52,8 @@ export interface AgentRuntimeDeps {
|
|
|
51
52
|
sessionCompression?: SessionCompressionOptions;
|
|
52
53
|
/** 长期记忆注入;默认关。见 EAP_RUNTIME_MEMORY */
|
|
53
54
|
sessionMemory?: import('../session/apply-session-memory.js').SessionMemoryOptions;
|
|
55
|
+
/** 未注入时不启动整图超时(测试桩) */
|
|
56
|
+
timeoutManager?: TaskTimeoutManager;
|
|
54
57
|
}
|
|
55
58
|
/** 一次对话详情:消息 + 每步 LLM 入参/响应。File / 内存 / Postgres 同一套结构。 */
|
|
56
59
|
export interface ConversationDetail {
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* @see 04-runtime-core/design.md §1.5 / §1.7
|
|
8
8
|
*/
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
11
|
+
import { resolveTaskTimeoutMs } from '../task/resolve-task-timeout.js';
|
|
12
|
+
import { runWithExecutionTrace } from '../graph/execution-trace-context.js';
|
|
10
13
|
import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
|
|
11
14
|
import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
|
|
12
15
|
import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
|
|
@@ -37,7 +40,7 @@ const DEFAULT_GRAPH_DSL = {
|
|
|
37
40
|
{ from: '__start__', to: 'llm' },
|
|
38
41
|
{ from: 'llm', to: '__end__' },
|
|
39
42
|
],
|
|
40
|
-
config: { timeout:
|
|
43
|
+
config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
|
|
41
44
|
};
|
|
42
45
|
/**
|
|
43
46
|
* AgentRuntime — 管理 Graph 执行的 SDK 门面
|
|
@@ -214,6 +217,14 @@ export class AgentRuntime {
|
|
|
214
217
|
ragScope: params.ragScope,
|
|
215
218
|
ragSources: params.ragSources,
|
|
216
219
|
}, userId);
|
|
220
|
+
if (response.code !== 'ok') {
|
|
221
|
+
const output = typeof response.data?.output === 'string' && response.data.output
|
|
222
|
+
? response.data.output
|
|
223
|
+
: response.code;
|
|
224
|
+
throw new AppError(response.code, output, {
|
|
225
|
+
exposeMessage: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
217
228
|
return response.data;
|
|
218
229
|
}
|
|
219
230
|
/**
|
|
@@ -241,6 +252,11 @@ export class AgentRuntime {
|
|
|
241
252
|
userId,
|
|
242
253
|
});
|
|
243
254
|
}
|
|
255
|
+
const loadOpts = { agentVersion: params.agentVersion };
|
|
256
|
+
const graphDsl = this.deps.graphLoader
|
|
257
|
+
? await this.deps.graphLoader.loadGraph(agentCode, loadOpts)
|
|
258
|
+
: DEFAULT_GRAPH_DSL;
|
|
259
|
+
const timeout = resolveTaskTimeoutMs(params.config?.timeout, graphDsl.config?.timeout);
|
|
244
260
|
const task = await this.deps.taskService.create({
|
|
245
261
|
id: params.taskId,
|
|
246
262
|
agentId: agentCode,
|
|
@@ -248,9 +264,12 @@ export class AgentRuntime {
|
|
|
248
264
|
userId,
|
|
249
265
|
correlationId,
|
|
250
266
|
input: params.input,
|
|
251
|
-
timeout
|
|
267
|
+
timeout,
|
|
252
268
|
});
|
|
253
269
|
await this.deps.taskService.updateStatus(task.id, 'running');
|
|
270
|
+
const abortSignal = this.deps.timeoutManager?.startTimer(task.id, timeout, async () => {
|
|
271
|
+
await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
|
|
272
|
+
});
|
|
254
273
|
await persistUserTurnMessage(this.deps.messageService, {
|
|
255
274
|
sessionId,
|
|
256
275
|
taskId: task.id,
|
|
@@ -258,10 +277,6 @@ export class AgentRuntime {
|
|
|
258
277
|
content: params.input,
|
|
259
278
|
});
|
|
260
279
|
yield this.toSSE('task.started', correlationId, task.id, { taskId: task.id, sessionId });
|
|
261
|
-
const loadOpts = { agentVersion: params.agentVersion };
|
|
262
|
-
const graphDsl = this.deps.graphLoader
|
|
263
|
-
? await this.deps.graphLoader.loadGraph(agentCode, loadOpts)
|
|
264
|
-
: DEFAULT_GRAPH_DSL;
|
|
265
280
|
const graph = await this.deps.graphEngine.buildGraph(graphDsl);
|
|
266
281
|
const loadedToolsBase = await this.resolveLoadedTools(agentCode, params.agentVersion);
|
|
267
282
|
const loadedSkills = this.deps.graphLoader
|
|
@@ -321,7 +336,7 @@ export class AgentRuntime {
|
|
|
321
336
|
const toolResults = [];
|
|
322
337
|
const nodeTokenUsages = [];
|
|
323
338
|
try {
|
|
324
|
-
for await (const runtimeEvent of this.deps.graphEngine.stream(graph, {
|
|
339
|
+
for await (const runtimeEvent of runWithExecutionTrace({ abortSignal }, () => this.deps.graphEngine.stream(graph, {
|
|
325
340
|
messages,
|
|
326
341
|
userId,
|
|
327
342
|
delegationToken: ctx.delegationToken,
|
|
@@ -343,7 +358,9 @@ export class AgentRuntime {
|
|
|
343
358
|
skillInput: resolveSkillInputFromInvokeInput(params.input),
|
|
344
359
|
traceId: resolveTraceId(params),
|
|
345
360
|
traceCollector: this.deps.traceCollector,
|
|
346
|
-
|
|
361
|
+
abortSignal,
|
|
362
|
+
abortDeadlineMs: Date.now() + timeout,
|
|
363
|
+
}))) {
|
|
347
364
|
if (runtimeEvent.type === 'tool.called') {
|
|
348
365
|
const p = runtimeEvent.payload;
|
|
349
366
|
if (p.toolCallId && p.toolName) {
|
|
@@ -415,6 +432,17 @@ export class AgentRuntime {
|
|
|
415
432
|
yield this.toSSE('hitl.waited', correlationId, task.id, buildHitlWaitedPayload(hitlPaused));
|
|
416
433
|
return;
|
|
417
434
|
}
|
|
435
|
+
const timedOut = err instanceof Error && /timed out/i.test(err.message);
|
|
436
|
+
if (timedOut) {
|
|
437
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
438
|
+
await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
|
|
439
|
+
yield this.toSSE('task.error', correlationId, task.id, {
|
|
440
|
+
code: ErrorCodes.TASK_TIMEOUT,
|
|
441
|
+
message: 'Task execution timed out',
|
|
442
|
+
recoverable: false,
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
418
446
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
419
447
|
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
420
448
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
@@ -425,6 +453,9 @@ export class AgentRuntime {
|
|
|
425
453
|
});
|
|
426
454
|
throw err;
|
|
427
455
|
}
|
|
456
|
+
finally {
|
|
457
|
+
this.deps.timeoutManager?.clearTimer(task.id);
|
|
458
|
+
}
|
|
428
459
|
}
|
|
429
460
|
/**
|
|
430
461
|
* HITL 用户响应后继续执行(同步结果)。
|
|
@@ -483,6 +514,9 @@ export class AgentRuntime {
|
|
|
483
514
|
if (!claimed) {
|
|
484
515
|
throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
|
|
485
516
|
}
|
|
517
|
+
const abortSignal = this.deps.timeoutManager?.startTimer(taskId, resolveTaskTimeoutMs(task.timeout), async () => {
|
|
518
|
+
await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout', 'Task execution timed out');
|
|
519
|
+
});
|
|
486
520
|
const tokenDetail = resolveTokenDetail(response.tokenDetail);
|
|
487
521
|
yield this.toSSE('hitl.resumed', correlationId, taskId, {
|
|
488
522
|
choice: response.choice,
|
|
@@ -507,6 +541,8 @@ export class AgentRuntime {
|
|
|
507
541
|
: await this.resolveLoadedSkills(agentCode, response.userId, agentVersion);
|
|
508
542
|
const continueState = {
|
|
509
543
|
...resumedState,
|
|
544
|
+
abortSignal,
|
|
545
|
+
abortDeadlineMs: Date.now() + resolveTaskTimeoutMs(task.timeout),
|
|
510
546
|
messages: resumedState.messages ?? [],
|
|
511
547
|
replaceCheckpointMessages: true,
|
|
512
548
|
sessionId: resumedState.sessionId ?? sessionId,
|
|
@@ -542,7 +578,7 @@ export class AgentRuntime {
|
|
|
542
578
|
const toolResults = [];
|
|
543
579
|
const nodeTokenUsages = [];
|
|
544
580
|
try {
|
|
545
|
-
for await (const runtimeEvent of this.deps.graphEngine.stream(graph, continueState)) {
|
|
581
|
+
for await (const runtimeEvent of runWithExecutionTrace({ abortSignal, abortDeadlineMs: continueState.abortDeadlineMs }, () => this.deps.graphEngine.stream(graph, continueState))) {
|
|
546
582
|
if (runtimeEvent.type === 'tool.called') {
|
|
547
583
|
const p = runtimeEvent.payload;
|
|
548
584
|
if (p.toolCallId && p.toolName) {
|
|
@@ -617,6 +653,17 @@ export class AgentRuntime {
|
|
|
617
653
|
yield this.toSSE('hitl.waited', correlationId, taskId, buildHitlWaitedPayload(hitlPaused));
|
|
618
654
|
return;
|
|
619
655
|
}
|
|
656
|
+
const timedOut = err instanceof Error && /timed out/i.test(err.message);
|
|
657
|
+
if (timedOut) {
|
|
658
|
+
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
659
|
+
await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
|
|
660
|
+
yield this.toSSE('task.error', correlationId, taskId, {
|
|
661
|
+
code: ErrorCodes.TASK_TIMEOUT,
|
|
662
|
+
message: 'Task execution timed out',
|
|
663
|
+
recoverable: false,
|
|
664
|
+
});
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
620
667
|
const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
|
|
621
668
|
const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
|
|
622
669
|
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
@@ -628,6 +675,9 @@ export class AgentRuntime {
|
|
|
628
675
|
});
|
|
629
676
|
throw err instanceof AppError ? err : appErr;
|
|
630
677
|
}
|
|
678
|
+
finally {
|
|
679
|
+
this.deps.timeoutManager?.clearTimer(taskId);
|
|
680
|
+
}
|
|
631
681
|
}
|
|
632
682
|
/**
|
|
633
683
|
* 从 Checkpoint Fork 并真实重新执行。
|
|
@@ -658,6 +708,12 @@ export class AgentRuntime {
|
|
|
658
708
|
const newThreadId = randomUUID();
|
|
659
709
|
const forkedCheckpoint = await this.deps.checkpointSaver.fork(checkpointId, newThreadId, overrides);
|
|
660
710
|
const correlationId = overrides.executionContext?.correlationId ?? randomUUID();
|
|
711
|
+
const agentVersion = overrides.agentVersion ??
|
|
712
|
+
(typeof source.state.agentVersion === 'string' ? source.state.agentVersion : undefined);
|
|
713
|
+
const graphDsl = this.deps.graphLoader && typeof this.deps.graphLoader.loadGraph === 'function'
|
|
714
|
+
? await this.deps.graphLoader.loadGraph(agentCode, { agentVersion })
|
|
715
|
+
: DEFAULT_GRAPH_DSL;
|
|
716
|
+
const timeout = resolveTaskTimeoutMs(undefined, graphDsl.config?.timeout);
|
|
661
717
|
const task = await this.deps.taskService.create({
|
|
662
718
|
id: taskId,
|
|
663
719
|
agentId: agentCode,
|
|
@@ -665,12 +721,13 @@ export class AgentRuntime {
|
|
|
665
721
|
userId,
|
|
666
722
|
correlationId,
|
|
667
723
|
input: { forkFrom: checkpointId, overrides },
|
|
668
|
-
timeout
|
|
724
|
+
timeout,
|
|
669
725
|
});
|
|
670
726
|
await this.deps.taskService.updateStatus(task.id, 'running');
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
727
|
+
const abortSignal = this.deps.timeoutManager?.startTimer(task.id, timeout, async () => {
|
|
728
|
+
await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
|
|
729
|
+
});
|
|
730
|
+
const graph = await this.deps.graphEngine.buildGraph(graphDsl);
|
|
674
731
|
const loadedToolsBase = await this.resolveLoadedTools(agentCode, agentVersion);
|
|
675
732
|
const skillBundle = await this.resolveLoadedSkills(agentCode, userId, agentVersion);
|
|
676
733
|
const loadedTools = assembleLoadedTools(loadedToolsBase);
|
|
@@ -681,11 +738,12 @@ export class AgentRuntime {
|
|
|
681
738
|
const agentSystemPrompt = (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentSystemPrompt === 'string'
|
|
682
739
|
? forkedCheckpoint.state.agentSystemPrompt
|
|
683
740
|
: undefined);
|
|
741
|
+
const abortDeadlineMs = Date.now() + timeout;
|
|
684
742
|
try {
|
|
685
743
|
const forkState = { ...forkedCheckpoint.state };
|
|
686
744
|
delete forkState.toolSpanId;
|
|
687
745
|
delete forkState.hitlToolCallId;
|
|
688
|
-
const result = await this.deps.graphEngine.invoke(graph, {
|
|
746
|
+
const result = await runWithExecutionTrace({ abortSignal, abortDeadlineMs }, () => this.deps.graphEngine.invoke(graph, {
|
|
689
747
|
messages: Array.isArray(forkState.messages)
|
|
690
748
|
? forkState.messages
|
|
691
749
|
: [],
|
|
@@ -701,7 +759,9 @@ export class AgentRuntime {
|
|
|
701
759
|
taskId: task.id,
|
|
702
760
|
traceId,
|
|
703
761
|
traceCollector: this.deps.traceCollector,
|
|
704
|
-
|
|
762
|
+
abortSignal,
|
|
763
|
+
abortDeadlineMs,
|
|
764
|
+
}));
|
|
705
765
|
if (result.tokenUsage.perNode && result.tokenUsage.perNode.length > 0) {
|
|
706
766
|
await recordTaskTokenUsages(this.deps.taskService, task.id, result.tokenUsage.perNode);
|
|
707
767
|
}
|
|
@@ -760,6 +820,23 @@ export class AgentRuntime {
|
|
|
760
820
|
await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
|
|
761
821
|
const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
762
822
|
const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
|
|
823
|
+
const timedOut = err instanceof Error && /timed out/i.test(err.message);
|
|
824
|
+
if (timedOut) {
|
|
825
|
+
await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
|
|
826
|
+
return {
|
|
827
|
+
newTraceId: traceId,
|
|
828
|
+
originalTraceId,
|
|
829
|
+
newTaskId: task.id,
|
|
830
|
+
checkpointId,
|
|
831
|
+
status: 'timeout',
|
|
832
|
+
output: message,
|
|
833
|
+
tokenUsage: {
|
|
834
|
+
input_tokens,
|
|
835
|
+
output_tokens,
|
|
836
|
+
total_tokens: input_tokens + output_tokens,
|
|
837
|
+
},
|
|
838
|
+
};
|
|
839
|
+
}
|
|
763
840
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
764
841
|
return {
|
|
765
842
|
newTraceId: traceId,
|
|
@@ -775,6 +852,9 @@ export class AgentRuntime {
|
|
|
775
852
|
},
|
|
776
853
|
};
|
|
777
854
|
}
|
|
855
|
+
finally {
|
|
856
|
+
this.deps.timeoutManager?.clearTimer(task.id);
|
|
857
|
+
}
|
|
778
858
|
}
|
|
779
859
|
async prepareHitlResume(taskId, response) {
|
|
780
860
|
const task = await this.deps.taskService.getTask(taskId);
|
|
@@ -31,7 +31,7 @@ import { createToolExecutor } from '../tool/create-tool-executor.js';
|
|
|
31
31
|
import { DevToolExecutor } from '../tool/dev-tool-executor.js';
|
|
32
32
|
import { captureOverlayJailOptions, withHarnessPrimitives } from '../tool/harness-primitives.js';
|
|
33
33
|
import { AllowAllGovernanceClient, HttpGovernanceClient, } from '../governance/index.js';
|
|
34
|
-
import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService, SessionContextService, TaskService, LangGraphAdapter, HitlHandler, TokenBudgetService, TokenStatsService, TokenBudgetGuard, SessionBudgetChecker, createAgentRegistryClient, GraphLoader, } from '../index.js';
|
|
34
|
+
import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService, SessionContextService, TaskService, TaskTimeoutManager, LangGraphAdapter, HitlHandler, TokenBudgetService, TokenStatsService, TokenBudgetGuard, SessionBudgetChecker, createAgentRegistryClient, GraphLoader, } from '../index.js';
|
|
35
35
|
import { createAgentRuntime } from './agent-runtime.js';
|
|
36
36
|
import { assembleRuntimeStorage } from './storage-profile.js';
|
|
37
37
|
import { createDevRagNodeDeps } from './dev-rag-deps.js';
|
|
@@ -139,6 +139,7 @@ export function createDevRuntime(options) {
|
|
|
139
139
|
const sessionContextService = new SessionContextService(sessionService, messageService);
|
|
140
140
|
// ── Task / Token ──
|
|
141
141
|
const taskService = new TaskService();
|
|
142
|
+
const timeoutManager = new TaskTimeoutManager();
|
|
142
143
|
const budgetService = new TokenBudgetService(storage.tokenBudgetRepository);
|
|
143
144
|
const tokenStatsService = new TokenStatsService();
|
|
144
145
|
const tokenBudgetGuard = new TokenBudgetGuard(budgetService);
|
|
@@ -202,6 +203,7 @@ export function createDevRuntime(options) {
|
|
|
202
203
|
checkpointSaver: storage.checkpointSaver,
|
|
203
204
|
sessionCompression,
|
|
204
205
|
sessionMemory,
|
|
206
|
+
timeoutManager,
|
|
205
207
|
});
|
|
206
208
|
const streamOrchestrator = new StreamOrchestrator({
|
|
207
209
|
sessionContextService,
|
|
@@ -215,6 +217,7 @@ export function createDevRuntime(options) {
|
|
|
215
217
|
checkpointSaver: storage.checkpointSaver,
|
|
216
218
|
sessionCompression,
|
|
217
219
|
sessionMemory,
|
|
220
|
+
timeoutManager,
|
|
218
221
|
});
|
|
219
222
|
const agentRuntime = createAgentRuntime({
|
|
220
223
|
invokeOrchestrator,
|
|
@@ -229,6 +232,7 @@ export function createDevRuntime(options) {
|
|
|
229
232
|
turnLog,
|
|
230
233
|
sessionCompression,
|
|
231
234
|
sessionMemory,
|
|
235
|
+
timeoutManager,
|
|
232
236
|
});
|
|
233
237
|
return {
|
|
234
238
|
invokeOrchestrator,
|
|
@@ -243,7 +247,10 @@ export function createDevRuntime(options) {
|
|
|
243
247
|
taskService,
|
|
244
248
|
tokenStatsService,
|
|
245
249
|
graphLoader,
|
|
246
|
-
dispose:
|
|
250
|
+
dispose: () => {
|
|
251
|
+
stopSkillWatch();
|
|
252
|
+
timeoutManager.dispose();
|
|
253
|
+
},
|
|
247
254
|
diagnostics: {
|
|
248
255
|
packageVersion: RUNTIME_PACKAGE_VERSION,
|
|
249
256
|
deploymentContext,
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - response.final
|
|
6
6
|
*
|
|
7
7
|
* 可选事件:
|
|
8
|
-
* - response.max_iterations_reached
|
|
8
|
+
* - response.max_iterations_reached(契约保留;调度步上限走 task.error / runtime.graph.max_iterations,不发本事件)
|
|
9
9
|
*
|
|
10
10
|
* @see design.md §1.6 SSE事件协议定义
|
|
11
11
|
*/
|
|
@@ -16,7 +16,7 @@ export interface ResponseFinalPayload {
|
|
|
16
16
|
/** 回复格式(如 text, markdown, json) */
|
|
17
17
|
format: string;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/** 契约保留。整图调度步上限不发此事件。 */
|
|
20
20
|
export interface ResponseMaxIterationsReachedPayload {
|
|
21
21
|
/** 最大迭代次数 */
|
|
22
22
|
maxIterations: number;
|
package/dist/task/index.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ 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 { resolveTaskTimeoutMs } from './resolve-task-timeout.js';
|
|
6
7
|
export { TaskProgressTracker } from './task-progress-tracker.js';
|
|
7
8
|
export type { TaskProgressInfo } from './task-progress-tracker.js';
|
package/dist/task/index.js
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
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 { resolveTaskTimeoutMs } from './resolve-task-timeout.js';
|
|
5
6
|
export { TaskProgressTracker } from './task-progress-tracker.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 整图超时:请求 config.timeout,否则图 config.timeout,否则平台缺省 1200000 毫秒(20 分钟)。
|
|
3
|
+
* 官方模板图上不写 timeout,用平台缺省。LLM HTTP 超时、HITL 等待另算。
|
|
4
|
+
*/
|
|
5
|
+
/** 请求显式值优先,否则图配置,否则 {@link DEFAULT_GRAPH_TIMEOUT_MS}。 */
|
|
6
|
+
export declare function resolveTaskTimeoutMs(requestTimeout?: number, graphTimeout?: number): number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 整图超时:请求 config.timeout,否则图 config.timeout,否则平台缺省 1200000 毫秒(20 分钟)。
|
|
3
|
+
* 官方模板图上不写 timeout,用平台缺省。LLM HTTP 超时、HITL 等待另算。
|
|
4
|
+
*/
|
|
5
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
6
|
+
function positiveMs(value) {
|
|
7
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
8
|
+
return undefined;
|
|
9
|
+
return Math.trunc(value);
|
|
10
|
+
}
|
|
11
|
+
/** 请求显式值优先,否则图配置,否则 {@link DEFAULT_GRAPH_TIMEOUT_MS}。 */
|
|
12
|
+
export function resolveTaskTimeoutMs(requestTimeout, graphTimeout) {
|
|
13
|
+
return (positiveMs(requestTimeout) ??
|
|
14
|
+
positiveMs(graphTimeout) ??
|
|
15
|
+
DEFAULT_GRAPH_TIMEOUT_MS);
|
|
16
|
+
}
|