@botlearn-course/daemon 0.0.13 → 0.0.15

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.
@@ -0,0 +1,120 @@
1
+ import path from "node:path";
2
+ import { REDACTED, redactSecretsDeep, } from "./redaction.js";
3
+ export const AGENT_TOOL_OBSERVATION_SCHEMA_VERSION = "agent-tool-observation/0.1";
4
+ const DETAIL_MAX_CHARS = 4_000;
5
+ const MAX_PROJECT_DEPTH = 8;
6
+ const SAFE_TOOL_NAME = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,79}$/;
7
+ const CALL_FIELDS = new Set([
8
+ "arguments",
9
+ "args",
10
+ "input",
11
+ "command",
12
+ "cmd",
13
+ "cwd",
14
+ "path",
15
+ "file_path",
16
+ "query",
17
+ "url",
18
+ "pattern",
19
+ "patch",
20
+ "changes",
21
+ "edits",
22
+ ]);
23
+ const RESULT_FIELDS = new Set([
24
+ "result",
25
+ "output",
26
+ "stdout",
27
+ "stderr",
28
+ "exit_code",
29
+ "status",
30
+ "status_code",
31
+ "error",
32
+ "diff",
33
+ "path",
34
+ "file_path",
35
+ "bytes",
36
+ ]);
37
+ /**
38
+ * Project a provider tool envelope into durable teacher evidence.
39
+ *
40
+ * The projection is deliberately lossy: only operational argument/result fields survive,
41
+ * credentials are removed, host paths become workspace-relative, and the JSON preview is
42
+ * bounded. Provider reasoning, message text, request IDs, and arbitrary envelope metadata
43
+ * never cross this boundary.
44
+ */
45
+ export function buildToolObservation(block, runtime, workspaceDir) {
46
+ if (block.kind !== "tool_call" && block.kind !== "tool_result")
47
+ return null;
48
+ const fields = block.kind === "tool_call" ? CALL_FIELDS : RESULT_FIELDS;
49
+ const projected = collectAllowedFields(block.raw, fields);
50
+ if (typeof block.text === "string" && block.text) {
51
+ projected[block.kind === "tool_call" ? "target" : "summary"] = block.text;
52
+ }
53
+ const normalized = normalizeWorkspacePaths(redactSecretsDeep(projected), path.resolve(workspaceDir));
54
+ const serialized = Object.keys(projected).length > 0
55
+ ? JSON.stringify(normalized, null, 2)
56
+ : "";
57
+ const detailTruncated = serialized.length > DETAIL_MAX_CHARS;
58
+ const detailPreview = detailTruncated
59
+ ? `${serialized.slice(0, DETAIL_MAX_CHARS - 1)}…`
60
+ : serialized;
61
+ return {
62
+ schema_version: AGENT_TOOL_OBSERVATION_SCHEMA_VERSION,
63
+ kind: block.kind,
64
+ runtime,
65
+ status: block.kind === "tool_call"
66
+ ? "started"
67
+ : block.status === "error"
68
+ ? "error"
69
+ : "completed",
70
+ ...(block.name && SAFE_TOOL_NAME.test(block.name) ? { name: block.name } : {}),
71
+ ...(detailPreview ? { detail_preview: detailPreview } : {}),
72
+ detail_truncated: detailTruncated,
73
+ redacted: detailPreview.includes(REDACTED),
74
+ };
75
+ }
76
+ function collectAllowedFields(value, allowed, depth = 0, out = {}) {
77
+ if (value === null || typeof value !== "object" || depth >= MAX_PROJECT_DEPTH) {
78
+ return out;
79
+ }
80
+ if (Array.isArray(value)) {
81
+ for (const item of value.slice(0, 50)) {
82
+ collectAllowedFields(item, allowed, depth + 1, out);
83
+ }
84
+ return out;
85
+ }
86
+ for (const [key, item] of Object.entries(value)) {
87
+ if (allowed === RESULT_FIELDS
88
+ && key === "content"
89
+ && value.type === "tool_result") {
90
+ if (!("content" in out))
91
+ out.content = item;
92
+ continue;
93
+ }
94
+ if (allowed.has(key)) {
95
+ if (!(key in out))
96
+ out[key] = item;
97
+ continue;
98
+ }
99
+ collectAllowedFields(item, allowed, depth + 1, out);
100
+ }
101
+ return out;
102
+ }
103
+ function normalizeWorkspacePaths(value, workspaceDir) {
104
+ if (typeof value === "string") {
105
+ const normalizedWorkspace = workspaceDir.replaceAll("\\", "/").replace(/\/+$/, "");
106
+ const normalizedValue = value.replaceAll("\\", "/");
107
+ return normalizedValue
108
+ .replaceAll(`${normalizedWorkspace}/`, "")
109
+ .replaceAll(normalizedWorkspace, ".");
110
+ }
111
+ if (Array.isArray(value)) {
112
+ return value.map((item) => normalizeWorkspacePaths(item, workspaceDir));
113
+ }
114
+ if (value === null || typeof value !== "object")
115
+ return value;
116
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
117
+ key,
118
+ normalizeWorkspacePaths(item, workspaceDir),
119
+ ]));
120
+ }
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * `services/course-api/botlearn_course/schemas.py` 的 daemon 契约严格对齐。
6
6
  */
7
7
  import type { ProgressStatus } from "./mcp/report-progress.js";
8
+ import type { PreparedRuntimeSkillProvider, RuntimeSkillEvent } from "./runtime-skills.js";
8
9
  export interface RunInputAttachment {
9
10
  attachment_id: string;
10
11
  filename: string;
@@ -53,7 +54,7 @@ export interface RunStartPayload {
53
54
  * `POST /daemon/runs/{id}/events` 接受的事件类型。
54
55
  * 与后端 DaemonRunEventIn.type 的 Literal 严格一致 —— 发送其他类型会得到 422。
55
56
  */
56
- export type RunEventType = "run.accepted" | "run.started" | "run.block" | "run.message" | "run.completed" | "run.failed" | "run.cancelled";
57
+ export type RunEventType = "run.accepted" | "run.started" | "run.block" | "run.observation" | "run.message" | "run.completed" | "run.failed" | "run.cancelled";
57
58
  export interface RunEvent {
58
59
  type: RunEventType;
59
60
  /** End-to-end correlation id assigned by Course Service. */
@@ -125,7 +126,10 @@ export interface AppliedRunRuntimeProfile {
125
126
  export interface RuntimeContentBlock {
126
127
  kind: "text_delta" | "text" | "thinking" | "tool_call" | "tool_result" | "status" | "error";
127
128
  text?: string;
128
- /** Safe provider-normalized tool identifier. Raw arguments/results stay in the transcript only. */
129
+ /**
130
+ * Safe provider-normalized tool identifier. Raw envelopes stay local; the dispatcher may
131
+ * derive a separate bounded/redacted teacher observation from explicit operation fields.
132
+ */
129
133
  name?: string;
130
134
  /** Public lifecycle metadata; never carries provider reasoning or tool output. */
131
135
  phase?: "in_progress" | "completed";
@@ -149,13 +153,26 @@ export interface RuntimeProgressDispositions {
149
153
  }
150
154
  /** Content-free model usage; adapters populate only fields actually reported upstream. */
151
155
  export interface RuntimeUsage {
156
+ /** Model identity resolved by the runtime/provider, not inferred by the daemon. */
157
+ model?: string;
152
158
  input_tokens?: number;
153
159
  cached_input_tokens?: number;
154
160
  output_tokens?: number;
161
+ reasoning_tokens?: number;
155
162
  total_tokens?: number;
156
163
  cost_usd?: number;
157
164
  provider_request_ids?: string[];
158
165
  }
166
+ /** Content-free shape of one runtime-native completion. */
167
+ export interface RuntimeCompletionSummary {
168
+ assistant_message_count: number;
169
+ reasoning_message_count: number;
170
+ assistant_content_present: boolean;
171
+ reasoning_content_present: boolean;
172
+ tool_call_count: number;
173
+ turn_status?: string;
174
+ finish_reason?: string;
175
+ }
159
176
  export interface RuntimeAuthProbe {
160
177
  checked: boolean;
161
178
  ok: boolean;
@@ -179,6 +196,8 @@ export interface CourseRuntimeSink {
179
196
  usage?(usage: RuntimeUsage): Promise<void>;
180
197
  /** Persist the runtime-native thread/session id before a terminal turn event is emitted. */
181
198
  runtimeSession?(sessionId: string): Promise<void>;
199
+ /** Content-free technical evidence from the control-side Runtime Skill Provider. */
200
+ skillEvent?(event: RuntimeSkillEvent): Promise<void>;
182
201
  }
183
202
  /** 一次 run 的本地执行上下文:服务器 payload + daemon 本地准备产物。 */
184
203
  export interface RunExecution {
@@ -195,6 +214,8 @@ export interface RunExecution {
195
214
  runtimeEnv?: NodeJS.ProcessEnv;
196
215
  /** Verified learner files materialized beneath the runtime workspace. */
197
216
  inputAttachments?: MaterializedInputAttachment[];
217
+ /** Activation-scoped provider held by the daemon control process, never runtime env. */
218
+ skillProvider?: PreparedRuntimeSkillProvider;
198
219
  }
199
220
  export interface CourseRuntime {
200
221
  id: string;
@@ -226,7 +247,10 @@ export interface RuntimeFailureSummary {
226
247
  stderr_tail?: string;
227
248
  stdout_tail?: string;
228
249
  error_name?: string;
250
+ error_code?: string;
229
251
  error_message?: string;
252
+ model?: string;
253
+ completion?: RuntimeCompletionSummary;
230
254
  }
231
255
  export interface RuntimeModule {
232
256
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botlearn-course/daemon",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Lightweight BotLearn Course daemon: run course tasks on your own machine with your own agent runtime (BYOA).",
5
5
  "type": "module",
6
6
  "bin": {