@gt-fe/eap-runtime 0.0.29 → 0.0.31
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.js +9 -5
- package/dist/api/handlers/stream.handler.js +15 -4
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/user-root.d.ts +8 -0
- package/dist/common/user-root.js +19 -0
- package/dist/graph/adapters/langgraph.adapter.js +193 -134
- package/dist/graph/adapters/skill-read.js +13 -4
- package/dist/graph/contracts/executable-graph.d.ts +7 -1
- package/dist/graph/hitl/hitl-handler.js +6 -0
- package/dist/graph/nodes/tool-node.d.ts +4 -1
- package/dist/graph/nodes/tool-node.js +15 -1
- package/dist/graph/trace-span.d.ts +15 -1
- package/dist/graph/trace-span.js +69 -1
- package/dist/registry/deployment-snapshot.d.ts +1 -1
- package/dist/registry/deployment-snapshot.js +6 -3
- package/dist/registry/skill-tree.d.ts +1 -1
- package/dist/registry/skill-tree.js +11 -6
- package/dist/runtime/agent-runtime.js +36 -5
- package/dist/session/persist-turn-messages.d.ts +13 -6
- package/dist/session/persist-turn-messages.js +4 -0
- package/dist/sse/events/tool-events.d.ts +29 -0
- package/dist/tool/harness-command.d.ts +1 -1
- package/dist/tool/harness-command.js +8 -7
- package/dist/tool/harness-primitives.d.ts +3 -3
- package/dist/tool/harness-primitives.js +185 -60
- package/dist/tool/index.d.ts +2 -0
- package/dist/tool/index.js +2 -0
- package/dist/tool/inject-public-artifact-links.d.ts +32 -0
- package/dist/tool/inject-public-artifact-links.js +142 -0
- package/dist/tool/static-artifact-url.d.ts +66 -0
- package/dist/tool/static-artifact-url.js +561 -0
- package/dist/tool/system-prompt-builder.js +8 -4
- package/dist/tool/tool-event-emitter.d.ts +2 -1
- package/dist/tool/tool-event-emitter.js +30 -18
- package/dist/tool/tool-executor.interface.d.ts +8 -1
- package/dist/tool/tool-result-injector.d.ts +3 -3
- package/dist/tool/tool-result-injector.js +7 -3
- package/package.json +2 -2
|
@@ -19,9 +19,17 @@ import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.j
|
|
|
19
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
|
+
import { toolResultHasArtifacts } from '../../tool/inject-public-artifact-links.js';
|
|
22
23
|
import { decideBeforeTool, } from '../../execution/interceptors.js';
|
|
23
24
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
24
25
|
import { isIsolatedSkillState } from '../adapters/skill-isolate.js';
|
|
26
|
+
function toolArtifactFields(result) {
|
|
27
|
+
return {
|
|
28
|
+
...(result.content ? { content: result.content } : {}),
|
|
29
|
+
...(result.file ? { file: result.file } : {}),
|
|
30
|
+
...(result.files?.length ? { files: result.files } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
25
33
|
function skillDirsFromState(state) {
|
|
26
34
|
const defs = state['skillDefinitions'];
|
|
27
35
|
return Object.values(defs ?? {})
|
|
@@ -302,6 +310,7 @@ export class ToolNode {
|
|
|
302
310
|
status: 'completed',
|
|
303
311
|
arguments: req.arguments,
|
|
304
312
|
content: result.content,
|
|
313
|
+
...toolArtifactFields(result),
|
|
305
314
|
executionTime,
|
|
306
315
|
...(skillIdentity?.skillSpace ? { skillSpace: skillIdentity.skillSpace } : {}),
|
|
307
316
|
...(skillIdentity?.skillVersion ? { skillVersion: skillIdentity.skillVersion } : {}),
|
|
@@ -317,6 +326,7 @@ export class ToolNode {
|
|
|
317
326
|
status: 'error',
|
|
318
327
|
arguments: req.arguments,
|
|
319
328
|
error: result.error ?? 'unknown error',
|
|
329
|
+
...toolArtifactFields(result),
|
|
320
330
|
executionTime,
|
|
321
331
|
});
|
|
322
332
|
}
|
|
@@ -421,12 +431,15 @@ export class ToolNode {
|
|
|
421
431
|
role: 'tool',
|
|
422
432
|
content: result.success
|
|
423
433
|
? result.content
|
|
424
|
-
:
|
|
434
|
+
: toolResultHasArtifacts(result) && result.content
|
|
435
|
+
? `${result.content}\n[Tool Error] ${result.error ?? 'unknown error'}`
|
|
436
|
+
: `[Tool Error] ${result.error ?? 'unknown error'}`,
|
|
425
437
|
// 以下字段符合 LLM function-calling 协议(ToolMessage 接口)
|
|
426
438
|
toolCallId: result.toolCallId,
|
|
427
439
|
toolName: result.toolName,
|
|
428
440
|
success: result.success,
|
|
429
441
|
...('file' in result && result.file ? { file: result.file } : {}),
|
|
442
|
+
...('files' in result && result.files?.length ? { files: result.files } : {}),
|
|
430
443
|
}));
|
|
431
444
|
const lastSpanId = [...spanIds].reverse().find((id) => Boolean(id));
|
|
432
445
|
return {
|
|
@@ -477,6 +490,7 @@ export class ToolNode {
|
|
|
477
490
|
status: 'error',
|
|
478
491
|
arguments: req.arguments,
|
|
479
492
|
error: last.error ?? 'unknown error',
|
|
493
|
+
...toolArtifactFields(last),
|
|
480
494
|
willRetry: true,
|
|
481
495
|
});
|
|
482
496
|
await sleepToolRetry(toolRetryDelayMs(attempt, retry.backoff));
|
|
@@ -12,4 +12,18 @@ export declare function completeGraphSpan(state: Record<string, unknown>, spanId
|
|
|
12
12
|
export declare function withGraphSpan(state: Record<string, unknown>, spec: {
|
|
13
13
|
name: string;
|
|
14
14
|
type: ExecutionNodeEvent['type'];
|
|
15
|
-
|
|
15
|
+
extra?: Partial<Omit<ExecutionNodeEvent, 'traceId' | 'name' | 'type' | 'parentNodeId'>>;
|
|
16
|
+
}, run: () => Promise<Record<string, unknown>>, complete?: {
|
|
17
|
+
outputFrom?: (result: Record<string, unknown>) => unknown;
|
|
18
|
+
}): Promise<Record<string, unknown>>;
|
|
19
|
+
/** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
|
|
20
|
+
export declare function llmSpanInput(input: {
|
|
21
|
+
system?: string;
|
|
22
|
+
tools?: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}>;
|
|
26
|
+
messages: unknown;
|
|
27
|
+
}): Record<string, unknown>;
|
|
28
|
+
/** llm Span complete 的 output:本回合新增 assistant(含 toolCalls)或 silentOutput。 */
|
|
29
|
+
export declare function llmSpanOutputFromResult(result: Record<string, unknown>): unknown;
|
package/dist/graph/trace-span.js
CHANGED
|
@@ -43,15 +43,17 @@ export async function completeGraphSpan(state, spanId, params, collectorOverride
|
|
|
43
43
|
warnTraceSpanFailure('completeNode', { spanId, traceId: resolveExecutionTrace(state).traceId }, err);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
export async function withGraphSpan(state, spec, run) {
|
|
46
|
+
export async function withGraphSpan(state, spec, run, complete) {
|
|
47
47
|
const spanId = await startGraphSpan(state, spec);
|
|
48
48
|
try {
|
|
49
49
|
const result = await run();
|
|
50
50
|
const usage = Array.isArray(result['__llmTokenUsage'])
|
|
51
51
|
? result['__llmTokenUsage'].at(-1)
|
|
52
52
|
: undefined;
|
|
53
|
+
const output = complete?.outputFrom?.(result);
|
|
53
54
|
await completeGraphSpan(state, spanId, {
|
|
54
55
|
status: 'completed',
|
|
56
|
+
...(output !== undefined ? { output } : {}),
|
|
55
57
|
inputTokens: usage?.inputTokens,
|
|
56
58
|
outputTokens: usage?.outputTokens,
|
|
57
59
|
totalTokens: usage?.inputTokens != null || usage?.outputTokens != null
|
|
@@ -69,3 +71,69 @@ export async function withGraphSpan(state, spec, run) {
|
|
|
69
71
|
throw err;
|
|
70
72
|
}
|
|
71
73
|
}
|
|
74
|
+
function spanGraphMessages(raw) {
|
|
75
|
+
if (!Array.isArray(raw))
|
|
76
|
+
return [];
|
|
77
|
+
return raw.map((msg) => {
|
|
78
|
+
if (typeof msg !== 'object' || msg === null) {
|
|
79
|
+
return { role: 'user', content: String(msg) };
|
|
80
|
+
}
|
|
81
|
+
const m = msg;
|
|
82
|
+
const out = {
|
|
83
|
+
role: typeof m['role'] === 'string' ? m['role'] : 'user',
|
|
84
|
+
content: typeof m['content'] === 'string' ? m['content'] : '',
|
|
85
|
+
};
|
|
86
|
+
const toolCalls = m['toolCalls'] ?? m['tool_calls'];
|
|
87
|
+
if (toolCalls !== undefined)
|
|
88
|
+
out.toolCalls = toolCalls;
|
|
89
|
+
const toolCallId = typeof m['toolCallId'] === 'string'
|
|
90
|
+
? m['toolCallId']
|
|
91
|
+
: typeof m['tool_call_id'] === 'string'
|
|
92
|
+
? m['tool_call_id']
|
|
93
|
+
: undefined;
|
|
94
|
+
if (toolCallId)
|
|
95
|
+
out.toolCallId = toolCallId;
|
|
96
|
+
if (typeof m['toolName'] === 'string')
|
|
97
|
+
out.toolName = m['toolName'];
|
|
98
|
+
if (typeof m['success'] === 'boolean')
|
|
99
|
+
out.success = m['success'];
|
|
100
|
+
const reasoning = typeof m['reasoningContent'] === 'string'
|
|
101
|
+
? m['reasoningContent']
|
|
102
|
+
: typeof m['reasoning_content'] === 'string'
|
|
103
|
+
? m['reasoning_content']
|
|
104
|
+
: undefined;
|
|
105
|
+
if (reasoning)
|
|
106
|
+
out.reasoningContent = reasoning;
|
|
107
|
+
return out;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
|
|
111
|
+
export function llmSpanInput(input) {
|
|
112
|
+
const tools = input.tools?.filter((t) => t.name);
|
|
113
|
+
return {
|
|
114
|
+
...(input.system ? { system: input.system } : {}),
|
|
115
|
+
...(tools && tools.length > 0 ? { tools } : {}),
|
|
116
|
+
messages: spanGraphMessages(input.messages),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** llm Span complete 的 output:本回合新增 assistant(含 toolCalls)或 silentOutput。 */
|
|
120
|
+
export function llmSpanOutputFromResult(result) {
|
|
121
|
+
if (typeof result['silentOutput'] === 'string') {
|
|
122
|
+
return { content: result['silentOutput'] };
|
|
123
|
+
}
|
|
124
|
+
const messages = Array.isArray(result['messages']) ? result['messages'] : [];
|
|
125
|
+
const added = messages[0];
|
|
126
|
+
if (!added || typeof added !== 'object')
|
|
127
|
+
return undefined;
|
|
128
|
+
const m = added;
|
|
129
|
+
const out = {
|
|
130
|
+
content: typeof m['content'] === 'string' ? m['content'] : '',
|
|
131
|
+
};
|
|
132
|
+
const toolCalls = m['toolCalls'] ?? m['tool_calls'];
|
|
133
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0)
|
|
134
|
+
out.toolCalls = toolCalls;
|
|
135
|
+
if (typeof m['reasoningContent'] === 'string' && m['reasoningContent']) {
|
|
136
|
+
out.reasoningContent = m['reasoningContent'];
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 目录(Agent 自包含,无全局共享层):
|
|
5
5
|
* {root}/agents/<agentCode>/<version>/preinstall/tools/<toolsetCode>/
|
|
6
6
|
* {root}/agents/<agentCode>/<version>/preinstall/skills/<skillCode>/
|
|
7
|
-
* {root}/agents/<agentCode
|
|
7
|
+
* {root}/agents/<agentCode>/users/<userId>/skills/<skillCode>/
|
|
8
8
|
*
|
|
9
9
|
* 命名:agentCode / toolsetCode / skillCode / toolName 均为语义化小写标识;
|
|
10
10
|
* Skill 目录无版本层:包内必含 SKILL.md(Agent Skills)。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 目录(Agent 自包含,无全局共享层):
|
|
5
5
|
* {root}/agents/<agentCode>/<version>/preinstall/tools/<toolsetCode>/
|
|
6
6
|
* {root}/agents/<agentCode>/<version>/preinstall/skills/<skillCode>/
|
|
7
|
-
* {root}/agents/<agentCode
|
|
7
|
+
* {root}/agents/<agentCode>/users/<userId>/skills/<skillCode>/
|
|
8
8
|
*
|
|
9
9
|
* 命名:agentCode / toolsetCode / skillCode / toolName 均为语义化小写标识;
|
|
10
10
|
* Skill 目录无版本层:包内必含 SKILL.md(Agent Skills)。
|
|
@@ -18,6 +18,7 @@ import { findSkillEntryFile } from './skill-tree.js';
|
|
|
18
18
|
import { preloadMissing, preloadPackageCorrupt, } from './preload-errors.js';
|
|
19
19
|
import { isRequireChecksum, resolvePreloadBehaviorFlags, } from './preload-flags.js';
|
|
20
20
|
import { emitRuntimeLog } from '../common/logger.js';
|
|
21
|
+
import { userRoot, USERS_DIR_NAME } from '../common/user-root.js';
|
|
21
22
|
async function pathExists(p) {
|
|
22
23
|
try {
|
|
23
24
|
await access(p, fsConstants.F_OK);
|
|
@@ -141,7 +142,9 @@ async function pickLatestVersionDir(parent) {
|
|
|
141
142
|
if (!(await pathExists(parent)))
|
|
142
143
|
return null;
|
|
143
144
|
const entries = await readdir(parent, { withFileTypes: true });
|
|
144
|
-
const dirs = entries
|
|
145
|
+
const dirs = entries
|
|
146
|
+
.filter((e) => e.isDirectory() && e.name !== USERS_DIR_NAME)
|
|
147
|
+
.map((e) => e.name);
|
|
145
148
|
if (dirs.includes('current')) {
|
|
146
149
|
return path.join(parent, 'current');
|
|
147
150
|
}
|
|
@@ -534,7 +537,7 @@ export class FileSystemDeploymentSnapshot {
|
|
|
534
537
|
// 用户装配 → 开发态作者源 extraRoots → Agent 预装
|
|
535
538
|
const candidates = [];
|
|
536
539
|
if (userId != null && userId !== '') {
|
|
537
|
-
candidates.push(path.join(agentDir,
|
|
540
|
+
candidates.push(path.join(userRoot(agentDir, userId), 'skills', ref.skillId));
|
|
538
541
|
}
|
|
539
542
|
for (const root of extraRoots) {
|
|
540
543
|
if (root)
|
|
@@ -25,6 +25,6 @@ export declare function inspectAgentSkillTree(agentDir: string, userId?: string,
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function watchPreinstallSkillRoot(preinstallRoot: string, onChange: () => void, debounceMs?: number): () => void;
|
|
27
27
|
export declare function watchSkillRoots(roots: string[], onChange: () => void, debounceMs?: number): () => void;
|
|
28
|
-
export declare function watchAgentSkillDirs(agentDir: string, onChange: () => void, debounceMs?: number, extraRoots?: string[]): () => void;
|
|
28
|
+
export declare function watchAgentSkillDirs(agentDir: string, onChange: () => void, debounceMs?: number, extraRoots?: string[], userId?: string): () => void;
|
|
29
29
|
/** EAP_SKILL_HOT_RELOAD:dev 默认开;server/desktop 默认关 */
|
|
30
30
|
export declare function resolveSkillHotReload(deploymentContext: string, explicit?: boolean): boolean;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Agent 自带 Skill 目录扫描(§3.1)
|
|
3
3
|
*
|
|
4
4
|
* agents/<code>/<ver>/preinstall/skills/<skillCode>/
|
|
5
|
-
* agents/<code
|
|
5
|
+
* agents/<code>/users/<userId>/skills/<skillCode>/ 同名覆盖(写根与 <ver> 平级)
|
|
6
6
|
*
|
|
7
7
|
* 热加载用 stamp:目录/文件 mtime 变化则缓存失效。
|
|
8
8
|
*/
|
|
@@ -11,6 +11,7 @@ import { watch } from 'node:fs';
|
|
|
11
11
|
import { constants as fsConstants } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { SKILL_ENTRY_CANDIDATES } from '@gt-fe/eap-contracts';
|
|
14
|
+
import { userRoot } from '../common/user-root.js';
|
|
14
15
|
const SKILL_CODE = /^[a-z0-9][a-z0-9-]*$/;
|
|
15
16
|
async function exists(p) {
|
|
16
17
|
try {
|
|
@@ -75,7 +76,7 @@ export async function readSkillAssembly(agentDir, userId) {
|
|
|
75
76
|
const candidates = [];
|
|
76
77
|
const uid = userId?.trim();
|
|
77
78
|
if (uid)
|
|
78
|
-
candidates.push(path.join(agentDir,
|
|
79
|
+
candidates.push(path.join(userRoot(agentDir, uid), '.assembly.json'));
|
|
79
80
|
candidates.push(path.join(agentDir, '.assembly.json'), path.join(agentDir, 'assembly.json'));
|
|
80
81
|
for (const file of candidates) {
|
|
81
82
|
const parsed = await readAssemblyFile(file);
|
|
@@ -102,7 +103,7 @@ export async function skillAssemblyStamp(agentDir, userId) {
|
|
|
102
103
|
const candidates = [];
|
|
103
104
|
const uid = userId?.trim();
|
|
104
105
|
if (uid)
|
|
105
|
-
candidates.push(path.join(agentDir,
|
|
106
|
+
candidates.push(path.join(userRoot(agentDir, uid), '.assembly.json'));
|
|
106
107
|
candidates.push(path.join(agentDir, '.assembly.json'), path.join(agentDir, 'assembly.json'));
|
|
107
108
|
let latest = 0;
|
|
108
109
|
for (const file of candidates) {
|
|
@@ -114,7 +115,7 @@ export function preinstallSkillsDir(agentDir) {
|
|
|
114
115
|
return path.join(agentDir, 'preinstall', 'skills');
|
|
115
116
|
}
|
|
116
117
|
export function userSkillsDir(agentDir, userId) {
|
|
117
|
-
return path.join(agentDir,
|
|
118
|
+
return path.join(userRoot(agentDir, userId), 'skills');
|
|
118
119
|
}
|
|
119
120
|
export async function inspectAgentSkillTree(agentDir, userId, extraRoots = []) {
|
|
120
121
|
const roots = [preinstallSkillsDir(agentDir), ...extraRoots.filter(Boolean)];
|
|
@@ -205,8 +206,12 @@ export function watchSkillRoots(roots, onChange, debounceMs = 200) {
|
|
|
205
206
|
watcher.close();
|
|
206
207
|
};
|
|
207
208
|
}
|
|
208
|
-
export function watchAgentSkillDirs(agentDir, onChange, debounceMs = 200, extraRoots = []) {
|
|
209
|
-
|
|
209
|
+
export function watchAgentSkillDirs(agentDir, onChange, debounceMs = 200, extraRoots = [], userId) {
|
|
210
|
+
const roots = [preinstallSkillsDir(agentDir), ...extraRoots];
|
|
211
|
+
const uid = userId?.trim();
|
|
212
|
+
if (uid)
|
|
213
|
+
roots.push(userSkillsDir(agentDir, uid));
|
|
214
|
+
return watchSkillRoots(roots, onChange, debounceMs);
|
|
210
215
|
}
|
|
211
216
|
/** EAP_SKILL_HOT_RELOAD:dev 默认开;server/desktop 默认关 */
|
|
212
217
|
export function resolveSkillHotReload(deploymentContext, explicit) {
|
|
@@ -16,6 +16,7 @@ import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
|
|
|
16
16
|
import { assembleLoadedTools } from '../tool/harness-primitives.js';
|
|
17
17
|
import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
|
|
18
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 { collectPublicArtifactLinks, collectPublicArtifactLinksFromEvent, injectPublicArtifactLinks, mergeArtifactLinks, } from '../tool/inject-public-artifact-links.js';
|
|
19
20
|
import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
|
|
20
21
|
import { applySessionMemory } from '../session/apply-session-memory.js';
|
|
21
22
|
import { buildSessionTitleUserPayload, invokeSessionTitle, } from '../session/session-title.js';
|
|
@@ -378,6 +379,7 @@ export class AgentRuntime {
|
|
|
378
379
|
let assistantReasoningContent;
|
|
379
380
|
const pendingToolCalls = [];
|
|
380
381
|
const toolResults = [];
|
|
382
|
+
const artifactLinks = [];
|
|
381
383
|
const nodeTokenUsages = [];
|
|
382
384
|
try {
|
|
383
385
|
for await (const runtimeEvent of runWithExecutionTrace({ abortSignal }, () => this.deps.graphEngine.stream(graph, {
|
|
@@ -421,6 +423,7 @@ export class AgentRuntime {
|
|
|
421
423
|
}
|
|
422
424
|
// 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
|
|
423
425
|
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
426
|
+
collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
|
|
424
427
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
425
428
|
if (streamedReasoning)
|
|
426
429
|
assistantReasoningContent = streamedReasoning;
|
|
@@ -433,6 +436,19 @@ export class AgentRuntime {
|
|
|
433
436
|
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
434
437
|
await this.deps.taskService.updateStatus(task.id, 'success');
|
|
435
438
|
const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
|
|
439
|
+
const visible = visibleStreamAssistantText(assistantBuckets);
|
|
440
|
+
const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
|
|
441
|
+
const { injected, suffix } = injectPublicArtifactLinks(visible, links);
|
|
442
|
+
if (suffix) {
|
|
443
|
+
yield this.toSSE('response.chunk', correlationId, task.id, {
|
|
444
|
+
content: suffix,
|
|
445
|
+
format: 'text',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
yield this.toSSE('response.final', correlationId, task.id, {
|
|
449
|
+
content: injected,
|
|
450
|
+
format: 'text',
|
|
451
|
+
});
|
|
436
452
|
await persistTurnTranscript(this.deps.messageService, {
|
|
437
453
|
sessionId,
|
|
438
454
|
taskId: task.id,
|
|
@@ -440,8 +456,8 @@ export class AgentRuntime {
|
|
|
440
456
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
441
457
|
pendingToolCalls,
|
|
442
458
|
toolResults,
|
|
443
|
-
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
444
|
-
assistantText:
|
|
459
|
+
assistantTexts: suffix ? undefined : streamAssistantTexts(assistantBuckets),
|
|
460
|
+
assistantText: injected,
|
|
445
461
|
finishPoints: graph.finishPoints,
|
|
446
462
|
toolCallReasoningContent,
|
|
447
463
|
assistantReasoningContent,
|
|
@@ -621,6 +637,7 @@ export class AgentRuntime {
|
|
|
621
637
|
let assistantReasoningContent;
|
|
622
638
|
const pendingToolCalls = [];
|
|
623
639
|
const toolResults = [];
|
|
640
|
+
const artifactLinks = [];
|
|
624
641
|
const nodeTokenUsages = [];
|
|
625
642
|
try {
|
|
626
643
|
for await (const runtimeEvent of runWithExecutionTrace({ abortSignal, abortDeadlineMs: continueState.abortDeadlineMs }, () => this.deps.graphEngine.stream(graph, continueState))) {
|
|
@@ -638,6 +655,7 @@ export class AgentRuntime {
|
|
|
638
655
|
}
|
|
639
656
|
}
|
|
640
657
|
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
658
|
+
collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
|
|
641
659
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
642
660
|
if (streamedReasoning)
|
|
643
661
|
assistantReasoningContent = streamedReasoning;
|
|
@@ -647,8 +665,21 @@ export class AgentRuntime {
|
|
|
647
665
|
yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
|
|
648
666
|
}
|
|
649
667
|
}
|
|
668
|
+
const visible = visibleStreamAssistantText(assistantBuckets);
|
|
669
|
+
const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
|
|
670
|
+
const { injected, suffix } = injectPublicArtifactLinks(visible, links);
|
|
671
|
+
if (suffix) {
|
|
672
|
+
yield this.toSSE('response.chunk', correlationId, taskId, {
|
|
673
|
+
content: suffix,
|
|
674
|
+
format: 'text',
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
yield this.toSSE('response.final', correlationId, taskId, {
|
|
678
|
+
content: injected,
|
|
679
|
+
format: 'text',
|
|
680
|
+
});
|
|
650
681
|
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
651
|
-
await this.deps.taskService.updateStatus(taskId, 'success',
|
|
682
|
+
await this.deps.taskService.updateStatus(taskId, 'success', injected || undefined);
|
|
652
683
|
const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
|
|
653
684
|
if (sessionId) {
|
|
654
685
|
await persistTurnTranscript(this.deps.messageService, {
|
|
@@ -658,8 +689,8 @@ export class AgentRuntime {
|
|
|
658
689
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
659
690
|
pendingToolCalls,
|
|
660
691
|
toolResults,
|
|
661
|
-
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
662
|
-
assistantText:
|
|
692
|
+
assistantTexts: suffix ? undefined : streamAssistantTexts(assistantBuckets),
|
|
693
|
+
assistantText: injected,
|
|
663
694
|
finishPoints: graph.finishPoints,
|
|
664
695
|
toolCallReasoningContent,
|
|
665
696
|
assistantReasoningContent,
|
|
@@ -91,12 +91,7 @@ export declare function sessionVisibleGraphMessages(messages: GraphMessage[], fi
|
|
|
91
91
|
export declare function extractStreamReasoning(eventType: string, payload: unknown): string | undefined;
|
|
92
92
|
/** 落盘 / 回填历史只收终态:result,或不会再试的 error。willRetry 的 error 只给直播 UI。 */
|
|
93
93
|
export declare function isTerminalToolStreamEvent(eventType: string, payload: unknown): boolean;
|
|
94
|
-
export declare function appendTerminalToolResult(eventType: string, payload: unknown, bucket:
|
|
95
|
-
toolCallId: string;
|
|
96
|
-
toolName: string;
|
|
97
|
-
content: unknown;
|
|
98
|
-
success: boolean;
|
|
99
|
-
}>): void;
|
|
94
|
+
export declare function appendTerminalToolResult(eventType: string, payload: unknown, bucket: StreamToolResult[]): void;
|
|
100
95
|
/** 历史 MessageRecord → GraphMessage(保留 tool 元数据与 reasoning) */
|
|
101
96
|
export declare function messageRecordsToGraphMessages(records: MessageRecord[]): GraphMessage[];
|
|
102
97
|
type CheckpointLoad = {
|
|
@@ -203,6 +198,18 @@ export type StreamToolResult = {
|
|
|
203
198
|
toolName: string;
|
|
204
199
|
content: unknown;
|
|
205
200
|
success: boolean;
|
|
201
|
+
file?: {
|
|
202
|
+
fileName: string;
|
|
203
|
+
fileType: string;
|
|
204
|
+
size: number;
|
|
205
|
+
url: string;
|
|
206
|
+
};
|
|
207
|
+
files?: Array<{
|
|
208
|
+
fileName: string;
|
|
209
|
+
fileType: string;
|
|
210
|
+
size: number;
|
|
211
|
+
url: string;
|
|
212
|
+
}>;
|
|
206
213
|
};
|
|
207
214
|
/**
|
|
208
215
|
* 将 stream 侧累加的 tool 事件规范为与 invoke GraphState 增量同序的 messages:
|
|
@@ -242,6 +242,8 @@ export function appendTerminalToolResult(eventType, payload, bucket) {
|
|
|
242
242
|
toolName: p.toolName,
|
|
243
243
|
content: p.content ?? p.error,
|
|
244
244
|
success: eventType === 'tool.result' && p.success !== false,
|
|
245
|
+
...(p.file ? { file: p.file } : {}),
|
|
246
|
+
...(p.files?.length ? { files: p.files } : {}),
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
/** 历史 MessageRecord → GraphMessage(保留 tool 元数据与 reasoning) */
|
|
@@ -525,6 +527,8 @@ export function buildTurnTranscriptFromStreamEvents(params) {
|
|
|
525
527
|
toolCallId: tr.toolCallId,
|
|
526
528
|
toolName: tr.toolName,
|
|
527
529
|
success: tr.success,
|
|
530
|
+
...(tr.file ? { file: tr.file } : {}),
|
|
531
|
+
...(tr.files?.length ? { files: tr.files } : {}),
|
|
528
532
|
});
|
|
529
533
|
}
|
|
530
534
|
if (texts.length > 0) {
|
|
@@ -30,6 +30,19 @@ export interface ToolResultPayload {
|
|
|
30
30
|
content: unknown;
|
|
31
31
|
/** 是否执行成功 */
|
|
32
32
|
success: boolean;
|
|
33
|
+
/** 与 ToolCallResult.file 同形;SSE / persist / 注入共用 */
|
|
34
|
+
file?: {
|
|
35
|
+
fileName: string;
|
|
36
|
+
fileType: string;
|
|
37
|
+
size: number;
|
|
38
|
+
url: string;
|
|
39
|
+
};
|
|
40
|
+
files?: Array<{
|
|
41
|
+
fileName: string;
|
|
42
|
+
fileType: string;
|
|
43
|
+
size: number;
|
|
44
|
+
url: string;
|
|
45
|
+
}>;
|
|
33
46
|
}
|
|
34
47
|
/** 工具执行进度 */
|
|
35
48
|
export type ToolProgressStatus = 'started' | 'completed' | 'error' | (string & {});
|
|
@@ -52,6 +65,8 @@ export interface ToolProgressPayload {
|
|
|
52
65
|
content?: unknown;
|
|
53
66
|
/** status=error 时可选:错误信息 */
|
|
54
67
|
error?: string;
|
|
68
|
+
file?: ToolResultPayload['file'];
|
|
69
|
+
files?: ToolResultPayload['files'];
|
|
55
70
|
/** 执行耗时(毫秒),status=completed|error 时可选 */
|
|
56
71
|
executionTime?: number;
|
|
57
72
|
}
|
|
@@ -65,4 +80,18 @@ export interface ToolErrorPayload {
|
|
|
65
80
|
error: string;
|
|
66
81
|
/** 是否会自动重试 */
|
|
67
82
|
willRetry: boolean;
|
|
83
|
+
/** 失败且有交付物时保留原正文(与 ToolCallResult.content 同形) */
|
|
84
|
+
content?: unknown;
|
|
85
|
+
file?: {
|
|
86
|
+
fileName: string;
|
|
87
|
+
fileType: string;
|
|
88
|
+
size: number;
|
|
89
|
+
url: string;
|
|
90
|
+
};
|
|
91
|
+
files?: Array<{
|
|
92
|
+
fileName: string;
|
|
93
|
+
fileType: string;
|
|
94
|
+
size: number;
|
|
95
|
+
url: string;
|
|
96
|
+
}>;
|
|
68
97
|
}
|
|
@@ -27,6 +27,6 @@ export declare function ensurePythonShims(workdir: string): string | undefined;
|
|
|
27
27
|
* spawn 前替换成绝对路径,不依赖壳去展开。
|
|
28
28
|
*/
|
|
29
29
|
export declare function substituteSkillDir(command: string, skillDir?: string): string;
|
|
30
|
-
export declare function sandboxEnv(workdir: string, cwd: string, allowlist: string[], shimDir?: string, skillDir?: string): NodeJS.ProcessEnv;
|
|
30
|
+
export declare function sandboxEnv(workdir: string, cwd: string, allowlist: string[], shimDir?: string, skillDir?: string, homeDir?: string): NodeJS.ProcessEnv;
|
|
31
31
|
export declare function formatSpawnError(err: unknown, shellFile: string): Error;
|
|
32
32
|
export declare function killProcessTree(child: ChildProcess): void;
|
|
@@ -21,7 +21,7 @@ export function commandDialectLines(platform = process.platform) {
|
|
|
21
21
|
else {
|
|
22
22
|
lines.push('基础命令:当前目录 `pwd`;列目录 `ls`;环境变量 `$VAR`;串命令用 `&&`。');
|
|
23
23
|
}
|
|
24
|
-
lines.push('解释器:`python`/`python3`、`node`/`nodejs`、`sh`/`bash`(Windows 上 `sh` 需本机有 Git Bash 等)。', '可执行文件:用绝对路径或相对于 cwd(Windows `.exe`/`.bat`/`.cmd`;Unix 二进制需可执行或用 `sh`)。', '`execute_command` 的 cwd
|
|
24
|
+
lines.push('解释器:`python`/`python3`、`node`/`nodejs`、`sh`/`bash`(Windows 上 `sh` 需本机有 Git Bash 等)。', '可执行文件:用绝对路径或相对于 cwd(Windows `.exe`/`.bat`/`.cmd`;Unix 二进制需可执行或用 `sh`)。', '`execute_command` 的 cwd 默认是用户目录下的 `files/`。技能包只读。包内任意文件用 `EAP_SKILL_DIR` 加相对路径(不限 `scripts/`)。读到的 SKILL.md 里本包存在的相对路径已展开为绝对路径。不要把 cwd 设成技能目录、`work/` 或 `public/`。相对写出在 `files/`。');
|
|
25
25
|
return lines;
|
|
26
26
|
}
|
|
27
27
|
export function resolveShellSpawn(command, platform = process.platform) {
|
|
@@ -179,22 +179,23 @@ export function substituteSkillDir(command, skillDir) {
|
|
|
179
179
|
.replace(/\$EAP_SKILL_DIR\b/g, dir)
|
|
180
180
|
.replace(/%EAP_SKILL_DIR%/gi, dir);
|
|
181
181
|
}
|
|
182
|
-
export function sandboxEnv(workdir, cwd, allowlist, shimDir, skillDir) {
|
|
182
|
+
export function sandboxEnv(workdir, cwd, allowlist, shimDir, skillDir, homeDir) {
|
|
183
183
|
const win = process.platform === 'win32';
|
|
184
184
|
const hostPath = process.env.PATH
|
|
185
185
|
?? (win
|
|
186
186
|
? (process.env.SystemRoot ? path.join(process.env.SystemRoot, 'System32') : 'C:\\Windows\\System32')
|
|
187
187
|
: '/usr/bin:/bin:/usr/local/bin');
|
|
188
188
|
const pathValue = shimDir ? `${shimDir}${path.delimiter}${hostPath}` : hostPath;
|
|
189
|
+
const home = homeDir?.trim() ? path.resolve(homeDir) : workdir;
|
|
189
190
|
const env = {
|
|
190
191
|
PATH: pathValue,
|
|
191
192
|
LANG: process.env.LANG ?? 'C.UTF-8',
|
|
192
193
|
LC_ALL: process.env.LC_ALL ?? process.env.LANG ?? 'C.UTF-8',
|
|
193
|
-
HOME:
|
|
194
|
-
USERPROFILE:
|
|
195
|
-
TMPDIR:
|
|
196
|
-
TMP:
|
|
197
|
-
TEMP:
|
|
194
|
+
HOME: home,
|
|
195
|
+
USERPROFILE: home,
|
|
196
|
+
TMPDIR: home,
|
|
197
|
+
TMP: home,
|
|
198
|
+
TEMP: home,
|
|
198
199
|
PWD: cwd,
|
|
199
200
|
PYTHONUTF8: '1',
|
|
200
201
|
PYTHONIOENCODING: 'utf-8',
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* 平台默认 Harness 原语:read_file / write_file / execute_command。
|
|
3
3
|
*
|
|
4
4
|
* 不写进 agent.manifest.toolsets。同名业务工具优先(问数 file-io 等)。
|
|
5
|
-
* 写:`users/<userId>/`(含用户自己的 skills
|
|
5
|
+
* 写:`dirname(agentDir)/users/<userId>/`(含用户自己的 skills)。产物默认 `files/`,公开副本 `public/`。
|
|
6
6
|
* 读:写根 + 已装配 `preinstall/tools`、`preinstall/skills`。
|
|
7
|
-
* 命令:cwd
|
|
7
|
+
* 命令:cwd 默认 `files/`。技能包只读,EAP_SKILL_DIR 为整包绝对根(不限 scripts/)。不把预装拷进用户装配槽。
|
|
8
8
|
* 开发态 overlay:`EAP_LOCAL_SKILLS_DIR` / `EAP_LOCAL_TOOLS_DIR`(只读)。在 `createDevRuntime` /
|
|
9
9
|
* `createToolExecutor` 时钉进执行器 extraRoots;SDK `createAgent` 构造完会恢复 process.env,
|
|
10
10
|
* 请求时不再读这两项。相对 `read_file` 会把 overlay 根下直接子目录里带 SKILL.md 的包纳入唯一命中。
|
|
@@ -19,7 +19,7 @@ export interface HarnessJailOptions {
|
|
|
19
19
|
workdir?: string;
|
|
20
20
|
extraRoots?: string[];
|
|
21
21
|
}
|
|
22
|
-
/** `
|
|
22
|
+
/** `dirname(agentDir)/users/<userId>`(写根;含用户 skills) */
|
|
23
23
|
export declare function resolveUserDir(agentDir: string, userId: string): string;
|
|
24
24
|
export declare function collectToolBundlePaths(loadedTools?: LoadedToolMap): string[];
|
|
25
25
|
export declare function resolveJailFromState(state: {
|