@botlearn-course/daemon 0.0.1 → 0.0.2
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/README.md +3 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +2 -11
- package/dist/course-client.js +4 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/report-progress-server.d.ts +21 -0
- package/dist/mcp/report-progress-server.js +136 -0
- package/dist/mcp/report-progress.d.ts +29 -0
- package/dist/mcp/report-progress.js +60 -0
- package/dist/run-dispatcher.js +84 -0
- package/dist/runtime-env.d.ts +4 -0
- package/dist/runtime-env.js +17 -0
- package/dist/runtimes/deepseek-tui.d.ts +6 -2
- package/dist/runtimes/deepseek-tui.js +232 -36
- package/dist/runtimes/engine.d.ts +13 -3
- package/dist/runtimes/engine.js +29 -4
- package/dist/runtimes/progress.d.ts +50 -0
- package/dist/runtimes/progress.js +339 -0
- package/dist/transcript.js +6 -0
- package/dist/types.d.ts +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ run 的 `runtime.id` 决定用哪个 adapter 执行;未指定时默认 `codex`
|
|
|
47
47
|
| `claude-code` | Claude Code | `claude` | `BOTLEARN_CLAUDE_BIN` | doctor 会额外探测登录状态 |
|
|
48
48
|
| `gemini` | Gemini CLI | `gemini` | `BOTLEARN_GEMINI_BIN` | |
|
|
49
49
|
| `kimi-cli` | Kimi CLI | `kimi` | `BOTLEARN_KIMI_CLI_BIN` | |
|
|
50
|
-
| `deepseek-tui` | DeepSeek TUI | `deepseek` | `BOTLEARN_DEEPSEEK_TUI_BIN` | |
|
|
50
|
+
| `deepseek-tui` | DeepSeek TUI | `deepseek` | `BOTLEARN_DEEPSEEK_TUI_BIN` | Linux/macOS 本地 server 会保留现有 MCP 配置并注入零依赖 `report_progress`;`botlearn` 是保留 server key。Windows 因固定 runtime 无法保证 MCP clean-env,首版 fail closed、不注入 progress |
|
|
51
51
|
| `hermes-agent` | Hermes Agent | `hermes-acp` | `BOTLEARN_HERMES_AGENT_BIN` | ACP stdio 协议 |
|
|
52
52
|
| `openclaw-acp` | OpenClaw (ACP) | `openclaw` | `BOTLEARN_OPENCLAW_BIN` | 还需配置 `BOTLEARN_OPENCLAW_URL`(可选 `BOTLEARN_OPENCLAW_TOKEN` / `BOTLEARN_OPENCLAW_AGENT`),二进制或 URL 缺失时 doctor 标记不可用 |
|
|
53
53
|
| `fake` | 假 runtime | 无 | — | 仅测试/演示用:产出确定性文本与一个 `draft.md`。默认隐藏,只有 `BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME=1` 时才注册并上报 |
|
|
@@ -93,6 +93,8 @@ runtime 凭据。
|
|
|
93
93
|
终态前把完整文件上传到 Course Service 的受保护存储。
|
|
94
94
|
- 每个 run 在独立 workspace 内执行并有超时上限;runtime 进程在取消/超时后会被终止
|
|
95
95
|
(SIGTERM → SIGKILL)。
|
|
96
|
+
- `report_progress` MCP 子进程以 clean environment 启动,不继承 Course 控制字段或模型凭据;
|
|
97
|
+
transcript 与 Course event 只保留严格归一化后的 `summary` / `status`,不保存原始 tool arguments。
|
|
96
98
|
- `course logout` 只删除本机凭据;要让服务端立即吊销该 daemon,请在前端 daemon 列表执行
|
|
97
99
|
revoke。
|
|
98
100
|
- 本包 production 依赖为零,发布 tarball 只包含 `dist/`、`README.md`、`package.json`、
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type Logger } from "./log.js";
|
|
3
3
|
import type { RunStartPayload } from "./types.js";
|
|
4
|
-
|
|
5
|
-
export declare function clearAgentServiceControlEnv(env?: NodeJS.ProcessEnv): void;
|
|
4
|
+
export { clearAgentServiceControlEnv } from "./runtime-env.js";
|
|
6
5
|
export interface CliArgs {
|
|
7
6
|
positional: string[];
|
|
8
7
|
flags: Record<string, string | boolean>;
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { log } from "./log.js";
|
|
|
11
11
|
import { augmentProcessPath } from "./path-env.js";
|
|
12
12
|
import { redactSecretString } from "./redaction.js";
|
|
13
13
|
import { RunDispatcher } from "./run-dispatcher.js";
|
|
14
|
+
import { clearAgentServiceControlEnv } from "./runtime-env.js";
|
|
14
15
|
import { RUNTIME_MODULES, detectAvailableRuntimeIds, fakeRuntimeEnabled, } from "./runtimes/index.js";
|
|
15
16
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
16
17
|
const HELP = `botlearn-course-daemon ${pkg.version} — run BotLearn Course tasks on your own machine (BYOA)
|
|
@@ -46,17 +47,7 @@ Agent Service sandbox environment:
|
|
|
46
47
|
// flag parser(极简:--k v / --k=v / 布尔开关)
|
|
47
48
|
// ---------------------------------------------------------------
|
|
48
49
|
const BOOLEAN_FLAGS = new Set(["once", "help", "version"]);
|
|
49
|
-
|
|
50
|
-
"BOTLEARN_COURSE_API_URL",
|
|
51
|
-
"BOTLEARN_AGENT_SERVICE_RUN_ID",
|
|
52
|
-
"BOTLEARN_AGENT_SERVICE_RUN_TOKEN",
|
|
53
|
-
"BOTLEARN_AGENT_SERVICE_WORKER_ID",
|
|
54
|
-
];
|
|
55
|
-
/** Keep the run-scoped Course credential out of runtime subprocess environments. */
|
|
56
|
-
export function clearAgentServiceControlEnv(env = process.env) {
|
|
57
|
-
for (const key of AGENT_SERVICE_CONTROL_ENV_KEYS)
|
|
58
|
-
delete env[key];
|
|
59
|
-
}
|
|
50
|
+
export { clearAgentServiceControlEnv } from "./runtime-env.js";
|
|
60
51
|
export function parseCliArgs(argv) {
|
|
61
52
|
const positional = [];
|
|
62
53
|
const flags = {};
|
package/dist/course-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { redactSecretString } from "./redaction.js";
|
|
1
|
+
import { redactSecretString, redactSecretsDeep } from "./redaction.js";
|
|
2
2
|
export class CourseClientError extends Error {
|
|
3
3
|
status;
|
|
4
4
|
constructor(status, message) {
|
|
@@ -100,7 +100,9 @@ export class CourseClient {
|
|
|
100
100
|
return this.request("GET", "/api/v1/daemon/runs/next");
|
|
101
101
|
}
|
|
102
102
|
async postEvent(agentRunId, event) {
|
|
103
|
-
|
|
103
|
+
const credentials = [this.accessToken, this.refreshToken].filter((value) => typeof value === "string");
|
|
104
|
+
const sanitized = redactSecretsDeep(event, 8, credentials);
|
|
105
|
+
await this.request("POST", `/api/v1/daemon/runs/${agentRunId}/events`, sanitized);
|
|
104
106
|
}
|
|
105
107
|
async postFile(agentRunId, file) {
|
|
106
108
|
return this.request("POST", `/api/v1/daemon/runs/${agentRunId}/files`, file);
|
package/dist/index.d.ts
CHANGED
|
@@ -15,5 +15,7 @@ export * from "./doctor.js";
|
|
|
15
15
|
export * from "./log.js";
|
|
16
16
|
export * from "./redaction.js";
|
|
17
17
|
export * from "./runtime-profile.js";
|
|
18
|
+
export * from "./runtime-env.js";
|
|
19
|
+
export * from "./mcp/report-progress.js";
|
|
18
20
|
export * from "./runtimes/index.js";
|
|
19
21
|
export * from "./runtimes/engine.js";
|
package/dist/index.js
CHANGED
|
@@ -15,5 +15,7 @@ export * from "./doctor.js";
|
|
|
15
15
|
export * from "./log.js";
|
|
16
16
|
export * from "./redaction.js";
|
|
17
17
|
export * from "./runtime-profile.js";
|
|
18
|
+
export * from "./runtime-env.js";
|
|
19
|
+
export * from "./mcp/report-progress.js";
|
|
18
20
|
export * from "./runtimes/index.js";
|
|
19
21
|
export * from "./runtimes/engine.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type JsonRpcId = string | number | null;
|
|
3
|
+
interface JsonRpcRequest {
|
|
4
|
+
jsonrpc?: unknown;
|
|
5
|
+
id?: unknown;
|
|
6
|
+
method?: unknown;
|
|
7
|
+
params?: unknown;
|
|
8
|
+
}
|
|
9
|
+
interface JsonRpcResponse {
|
|
10
|
+
jsonrpc: "2.0";
|
|
11
|
+
id: JsonRpcId;
|
|
12
|
+
result?: unknown;
|
|
13
|
+
error?: {
|
|
14
|
+
code: number;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Pure JSON-RPC request handler used by both stdio and deterministic unit tests. */
|
|
19
|
+
export declare function handleReportProgressRequest(request: JsonRpcRequest): JsonRpcResponse | null;
|
|
20
|
+
export declare function runReportProgressStdioServer(): void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { normalizeProgressReport, ProgressValidationError, REPORT_PROGRESS_INPUT_SCHEMA, } from "./report-progress.js";
|
|
6
|
+
const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
|
|
7
|
+
const TOOL = {
|
|
8
|
+
name: "report_progress",
|
|
9
|
+
description: "Report a concise, learner-visible execution phase update. This does not complete course progress.",
|
|
10
|
+
inputSchema: REPORT_PROGRESS_INPUT_SCHEMA,
|
|
11
|
+
};
|
|
12
|
+
function responseId(value) {
|
|
13
|
+
return typeof value === "string" || typeof value === "number" || value === null ? value : null;
|
|
14
|
+
}
|
|
15
|
+
function toolError(code, message) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
schemaVersion: "tool-result/0.1",
|
|
22
|
+
ok: false,
|
|
23
|
+
code,
|
|
24
|
+
error: { message },
|
|
25
|
+
evidence: [],
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
isError: true,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Pure JSON-RPC request handler used by both stdio and deterministic unit tests. */
|
|
33
|
+
export function handleReportProgressRequest(request) {
|
|
34
|
+
if (request.id === undefined)
|
|
35
|
+
return null;
|
|
36
|
+
const id = responseId(request.id);
|
|
37
|
+
if (request.jsonrpc !== "2.0" || typeof request.method !== "string") {
|
|
38
|
+
return { jsonrpc: "2.0", id, error: { code: -32600, message: "Invalid Request" } };
|
|
39
|
+
}
|
|
40
|
+
if (request.method === "initialize") {
|
|
41
|
+
const requested = request.params && typeof request.params === "object"
|
|
42
|
+
? request.params.protocolVersion
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
jsonrpc: "2.0",
|
|
46
|
+
id,
|
|
47
|
+
result: {
|
|
48
|
+
protocolVersion: typeof requested === "string" && requested.length > 0
|
|
49
|
+
? requested
|
|
50
|
+
: DEFAULT_PROTOCOL_VERSION,
|
|
51
|
+
capabilities: { tools: {} },
|
|
52
|
+
serverInfo: { name: "botlearn-report-progress", version: "0.1.0" },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (request.method === "tools/list") {
|
|
57
|
+
return { jsonrpc: "2.0", id, result: { tools: [TOOL] } };
|
|
58
|
+
}
|
|
59
|
+
if (request.method === "tools/call") {
|
|
60
|
+
const params = request.params && typeof request.params === "object"
|
|
61
|
+
? request.params
|
|
62
|
+
: {};
|
|
63
|
+
if (params.name !== TOOL.name) {
|
|
64
|
+
return {
|
|
65
|
+
jsonrpc: "2.0",
|
|
66
|
+
id,
|
|
67
|
+
result: toolError("unknown_tool", "Unknown tool"),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const progress = normalizeProgressReport(params.arguments);
|
|
72
|
+
return {
|
|
73
|
+
jsonrpc: "2.0",
|
|
74
|
+
id,
|
|
75
|
+
result: {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: JSON.stringify({
|
|
80
|
+
schemaVersion: "tool-result/0.1",
|
|
81
|
+
ok: true,
|
|
82
|
+
code: "progress_reported",
|
|
83
|
+
data: progress,
|
|
84
|
+
evidence: [],
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
isError: false,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (!(error instanceof ProgressValidationError))
|
|
94
|
+
throw error;
|
|
95
|
+
return {
|
|
96
|
+
jsonrpc: "2.0",
|
|
97
|
+
id,
|
|
98
|
+
result: toolError("invalid_progress", error.message),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { jsonrpc: "2.0", id, error: { code: -32601, message: "Method not found" } };
|
|
103
|
+
}
|
|
104
|
+
export function runReportProgressStdioServer() {
|
|
105
|
+
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
106
|
+
lines.on("line", (line) => {
|
|
107
|
+
if (!line.trim())
|
|
108
|
+
return;
|
|
109
|
+
let response;
|
|
110
|
+
try {
|
|
111
|
+
response = handleReportProgressRequest(JSON.parse(line));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
response = {
|
|
115
|
+
jsonrpc: "2.0",
|
|
116
|
+
id: null,
|
|
117
|
+
error: { code: -32700, message: "Parse error" },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (response)
|
|
121
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function isMainModule() {
|
|
125
|
+
const entry = process.argv[1];
|
|
126
|
+
if (!entry)
|
|
127
|
+
return false;
|
|
128
|
+
try {
|
|
129
|
+
return realpathSync(entry) === fileURLToPath(import.meta.url);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (isMainModule())
|
|
136
|
+
runReportProgressStdioServer();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const PROGRESS_STATUSES: readonly ["in_progress", "completed"];
|
|
2
|
+
export type ProgressStatus = (typeof PROGRESS_STATUSES)[number];
|
|
3
|
+
export interface ProgressReport {
|
|
4
|
+
summary: string;
|
|
5
|
+
status: ProgressStatus;
|
|
6
|
+
}
|
|
7
|
+
export declare const MAX_PROGRESS_EVENTS_PER_ATTEMPT = 64;
|
|
8
|
+
export declare const REPORT_PROGRESS_INPUT_SCHEMA: {
|
|
9
|
+
readonly type: "object";
|
|
10
|
+
readonly additionalProperties: false;
|
|
11
|
+
readonly required: readonly ["summary", "status"];
|
|
12
|
+
readonly properties: {
|
|
13
|
+
readonly summary: {
|
|
14
|
+
readonly type: "string";
|
|
15
|
+
readonly minLength: 1;
|
|
16
|
+
readonly maxLength: 240;
|
|
17
|
+
};
|
|
18
|
+
readonly status: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly enum: readonly ["in_progress", "completed"];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare class ProgressValidationError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
/** Strict shared normalizer: Unicode trim, exact fields/types, then code-point length. */
|
|
28
|
+
export declare function normalizeProgressReport(value: unknown): ProgressReport;
|
|
29
|
+
export declare function tryNormalizeProgressReport(value: unknown): ProgressReport | null;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const PROGRESS_STATUSES = ["in_progress", "completed"];
|
|
2
|
+
export const MAX_PROGRESS_EVENTS_PER_ATTEMPT = 64;
|
|
3
|
+
export const REPORT_PROGRESS_INPUT_SCHEMA = {
|
|
4
|
+
type: "object",
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
required: ["summary", "status"],
|
|
7
|
+
properties: {
|
|
8
|
+
summary: {
|
|
9
|
+
type: "string",
|
|
10
|
+
minLength: 1,
|
|
11
|
+
maxLength: 240,
|
|
12
|
+
},
|
|
13
|
+
status: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: PROGRESS_STATUSES,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
// Unicode White_Space plus BOM/ZWNBSP, matching the platform's user-facing trim behavior.
|
|
20
|
+
const UNICODE_WHITESPACE_AT_EDGES = /^(?:\p{White_Space}|\uFEFF)+|(?:\p{White_Space}|\uFEFF)+$/gu;
|
|
21
|
+
const MAX_PROGRESS_CODE_POINTS = 240;
|
|
22
|
+
export class ProgressValidationError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ProgressValidationError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Strict shared normalizer: Unicode trim, exact fields/types, then code-point length. */
|
|
29
|
+
export function normalizeProgressReport(value) {
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
31
|
+
throw new ProgressValidationError("progress arguments must be an object");
|
|
32
|
+
}
|
|
33
|
+
const record = value;
|
|
34
|
+
const keys = Object.keys(record).sort();
|
|
35
|
+
if (keys.length !== 2 || keys[0] !== "status" || keys[1] !== "summary") {
|
|
36
|
+
throw new ProgressValidationError("progress arguments must contain only summary and status");
|
|
37
|
+
}
|
|
38
|
+
if (typeof record.summary !== "string") {
|
|
39
|
+
throw new ProgressValidationError("progress summary must be a string");
|
|
40
|
+
}
|
|
41
|
+
if (record.status !== "in_progress" && record.status !== "completed") {
|
|
42
|
+
throw new ProgressValidationError("progress status is invalid");
|
|
43
|
+
}
|
|
44
|
+
const summary = record.summary.replace(UNICODE_WHITESPACE_AT_EDGES, "");
|
|
45
|
+
const length = Array.from(summary).length;
|
|
46
|
+
if (length < 1 || length > MAX_PROGRESS_CODE_POINTS) {
|
|
47
|
+
throw new ProgressValidationError("progress summary must contain 1 to 240 Unicode code points");
|
|
48
|
+
}
|
|
49
|
+
return { summary, status: record.status };
|
|
50
|
+
}
|
|
51
|
+
export function tryNormalizeProgressReport(value) {
|
|
52
|
+
try {
|
|
53
|
+
return normalizeProgressReport(value);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error instanceof ProgressValidationError)
|
|
57
|
+
return null;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/run-dispatcher.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { CourseClientError, isRunTerminal } from "./course-client.js";
|
|
3
3
|
import { reportFileCandidates } from "./file-candidates.js";
|
|
4
4
|
import { log as defaultLog } from "./log.js";
|
|
5
|
+
import { MAX_PROGRESS_EVENTS_PER_ATTEMPT, tryNormalizeProgressReport, } from "./mcp/report-progress.js";
|
|
5
6
|
import { errorInfo, redactSecretString, truncateText } from "./redaction.js";
|
|
6
7
|
import { missingRunCapabilities } from "./runtime-capabilities.js";
|
|
7
8
|
import { RunQueue } from "./run-queue.js";
|
|
@@ -90,6 +91,15 @@ export class RunDispatcher {
|
|
|
90
91
|
let profileApplied = false;
|
|
91
92
|
let toolCalls = 0;
|
|
92
93
|
let toolLimitExceeded = false;
|
|
94
|
+
let progressEvents = 0;
|
|
95
|
+
let lastProgressKey = null;
|
|
96
|
+
const progressDisposition = {
|
|
97
|
+
accepted: 0,
|
|
98
|
+
deduplicated: 0,
|
|
99
|
+
invalid: 0,
|
|
100
|
+
over_limit: 0,
|
|
101
|
+
server_rejected: 0,
|
|
102
|
+
};
|
|
93
103
|
const startedAt = this.now();
|
|
94
104
|
const usage = () => ({
|
|
95
105
|
wall_time_ms: Math.max(0, this.now() - startedAt),
|
|
@@ -178,7 +188,74 @@ export class RunDispatcher {
|
|
|
178
188
|
// 上一次真正上了 wire 的块 kind:thinking/status 只在 kind 切换时上报一次,避免刷屏。
|
|
179
189
|
let lastReportedKind = null;
|
|
180
190
|
const sink = {
|
|
191
|
+
progressDispositions: async (dispositions) => {
|
|
192
|
+
progressDisposition.invalid += dispositions.invalid;
|
|
193
|
+
progressDisposition.deduplicated += dispositions.deduplicated;
|
|
194
|
+
progressDisposition.over_limit += dispositions.over_limit;
|
|
195
|
+
},
|
|
181
196
|
block: async (block) => {
|
|
197
|
+
if (block.kind === "progress") {
|
|
198
|
+
const normalized = tryNormalizeProgressReport({
|
|
199
|
+
summary: block.summary,
|
|
200
|
+
status: block.status,
|
|
201
|
+
});
|
|
202
|
+
if (!normalized) {
|
|
203
|
+
progressDisposition.invalid += 1;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Redact before the local transcript and every reporting client boundary.
|
|
207
|
+
const progress = tryNormalizeProgressReport({
|
|
208
|
+
summary: redactSecretString(normalized.summary),
|
|
209
|
+
status: normalized.status,
|
|
210
|
+
});
|
|
211
|
+
// Redaction can expand a token marker past 240 code points; never truncate or send it.
|
|
212
|
+
if (!progress) {
|
|
213
|
+
progressDisposition.invalid += 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const key = `${progress.status}\0${progress.summary}`;
|
|
217
|
+
if (key === lastProgressKey) {
|
|
218
|
+
progressDisposition.deduplicated += 1;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (progressEvents >= MAX_PROGRESS_EVENTS_PER_ATTEMPT) {
|
|
222
|
+
progressDisposition.over_limit += 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
lastProgressKey = key;
|
|
226
|
+
progressEvents += 1;
|
|
227
|
+
transcript.writeBlock({
|
|
228
|
+
kind: "progress",
|
|
229
|
+
runtime: runtime.id,
|
|
230
|
+
summary: progress.summary,
|
|
231
|
+
status: progress.status,
|
|
232
|
+
});
|
|
233
|
+
if (serverTerminal)
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
await send({
|
|
237
|
+
type: "run.block",
|
|
238
|
+
payload: {
|
|
239
|
+
schema_version: "agent-progress/0.1",
|
|
240
|
+
kind: "progress",
|
|
241
|
+
runtime: runtime.id,
|
|
242
|
+
summary: progress.summary,
|
|
243
|
+
status: progress.status,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
if (!serverTerminal)
|
|
247
|
+
progressDisposition.accepted += 1;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (error instanceof CourseClientError &&
|
|
251
|
+
(error.status === 400 || error.status === 422)) {
|
|
252
|
+
progressDisposition.server_rejected += 1;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
182
259
|
transcript.writeBlock(block);
|
|
183
260
|
if (block.kind === "tool_call") {
|
|
184
261
|
toolCalls += 1;
|
|
@@ -288,6 +365,13 @@ export class RunDispatcher {
|
|
|
288
365
|
clearTimeout(timer);
|
|
289
366
|
if (profileApplied)
|
|
290
367
|
cleanupRunRuntimeProfile(runId);
|
|
368
|
+
if (Object.values(progressDisposition).some((count) => count > 0)) {
|
|
369
|
+
this.log.info("progress event dispositions", {
|
|
370
|
+
agentRunId: runId,
|
|
371
|
+
runtime: runtimeId,
|
|
372
|
+
...progressDisposition,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
291
375
|
this.inflight.delete(runId);
|
|
292
376
|
}
|
|
293
377
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Remove Course control-plane coordinates before any model/runtime child is created. */
|
|
2
|
+
export declare function clearAgentServiceControlEnv(env?: NodeJS.ProcessEnv): void;
|
|
3
|
+
/** Copy an environment for a runtime child without leaking Agent Service control values. */
|
|
4
|
+
export declare function runtimeChildEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const AGENT_SERVICE_CONTROL_ENV_KEYS = [
|
|
2
|
+
"BOTLEARN_COURSE_API_URL",
|
|
3
|
+
"BOTLEARN_AGENT_SERVICE_RUN_ID",
|
|
4
|
+
"BOTLEARN_AGENT_SERVICE_RUN_TOKEN",
|
|
5
|
+
"BOTLEARN_AGENT_SERVICE_WORKER_ID",
|
|
6
|
+
];
|
|
7
|
+
/** Remove Course control-plane coordinates before any model/runtime child is created. */
|
|
8
|
+
export function clearAgentServiceControlEnv(env = process.env) {
|
|
9
|
+
for (const key of AGENT_SERVICE_CONTROL_ENV_KEYS)
|
|
10
|
+
delete env[key];
|
|
11
|
+
}
|
|
12
|
+
/** Copy an environment for a runtime child without leaking Agent Service control values. */
|
|
13
|
+
export function runtimeChildEnv(env = process.env) {
|
|
14
|
+
const childEnv = { ...env };
|
|
15
|
+
clearAgentServiceControlEnv(childEnv);
|
|
16
|
+
return childEnv;
|
|
17
|
+
}
|
|
@@ -2,13 +2,15 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { type ProbeDeps } from "./probe.js";
|
|
3
3
|
import { type EngineAdapter, type EngineRunOptions, type EngineRunResult } from "./engine.js";
|
|
4
4
|
import type { RuntimeModule, RuntimeProbe } from "../types.js";
|
|
5
|
-
interface DeepseekAdapterDeps {
|
|
5
|
+
export interface DeepseekAdapterDeps {
|
|
6
6
|
binary?: string;
|
|
7
7
|
/** 测试注入:使用现成的兼容 server,不 spawn `deepseek`。 */
|
|
8
8
|
serverUrl?: string;
|
|
9
9
|
authToken?: string;
|
|
10
10
|
fetchFn?: typeof fetch;
|
|
11
11
|
spawnFn?: typeof spawn;
|
|
12
|
+
/** External servers must explicitly attest compatible progress tool events. */
|
|
13
|
+
progressEventMapping?: boolean;
|
|
12
14
|
}
|
|
13
15
|
/** 解析 PATH 上的 `deepseek` dispatcher CLI。 */
|
|
14
16
|
export declare function resolveDeepseekCommand(deps?: ProbeDeps): string | null;
|
|
@@ -28,6 +30,8 @@ export declare class DeepseekTuiAdapter implements EngineAdapter {
|
|
|
28
30
|
private readonly explicitAuthToken;
|
|
29
31
|
private readonly fetchFn;
|
|
30
32
|
private readonly spawnFn;
|
|
33
|
+
readonly progressEventMappingEnabled: boolean;
|
|
34
|
+
readonly progressPromptInjectionEnabled: boolean;
|
|
31
35
|
private resolvedBinary;
|
|
32
36
|
constructor(deps?: DeepseekAdapterDeps);
|
|
33
37
|
run(opts: EngineRunOptions): Promise<EngineRunResult>;
|
|
@@ -41,10 +45,10 @@ export declare class DeepseekTuiAdapter implements EngineAdapter {
|
|
|
41
45
|
private createThread;
|
|
42
46
|
private patchThreadSystemContext;
|
|
43
47
|
private startTurnAndReadEvents;
|
|
48
|
+
private interruptTurn;
|
|
44
49
|
private readEvents;
|
|
45
50
|
private requestJson;
|
|
46
51
|
}
|
|
47
52
|
/** 仅测试用:清空进程池。 */
|
|
48
53
|
export declare function __resetDeepseekTuiPoolForTests(): void;
|
|
49
54
|
export declare const deepseekTuiModule: RuntimeModule;
|
|
50
|
-
export {};
|