@botlearn-course/daemon 0.0.13 → 0.0.14

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 CHANGED
@@ -114,8 +114,11 @@ runtime 凭据。
114
114
  - 本包 production 依赖为零,发布 tarball 只包含 `dist/`、`README.md`、`package.json`、
115
115
  `LICENSE`(CI 发布前强制校验)。
116
116
  - 托管 session 的 control token/reconnect state 位于 runtime UID 不可访问的 `0700` control home;
117
- runtime workspace 独立可写,Prompt Pack/Skill 则通过 control-owned、group-readable 的只读视图
118
- 提供。长期模型 provider key 留在 Agent Service,runtime 只拿 generation-scoped proxy grant。
117
+ runtime workspace 独立可写。activation-scoped Skill Provider binding 只留在 daemon control
118
+ process 内存;DeepSeek 只通过 clean-env `course_skills` Unix Socket relay 按需读取当前授权
119
+ `SKILL.md`/reference,endpoint/token 不进入 runtime env、MCP config、state、workspace 或日志。
120
+ Prompt Pack 仍通过受信 system context 应用。长期模型 provider key 留在 Agent Service,runtime
121
+ 只拿 generation-scoped proxy grant。
119
122
 
120
123
  ## 发布
121
124
 
@@ -1,4 +1,5 @@
1
1
  import { type Logger } from "./log.js";
2
+ import { type RuntimeSkillProviderFactory } from "./runtime-skills.js";
2
3
  import { type PersistentSessionExecution, type PreparedPersistentTurn, type RunReportingClient } from "./run-dispatcher.js";
3
4
  import type { CourseRuntime, CourseRuntimeProfile, RunEvent, RunFileCandidate, RunFileRecord, RunStartPayload } from "./types.js";
4
5
  export interface AgentServiceSandboxOptions {
@@ -11,6 +12,8 @@ export interface AgentServiceSandboxOptions {
11
12
  log?: Logger;
12
13
  random?: () => number;
13
14
  sleep?: (ms: number) => Promise<void>;
15
+ /** Test/provider injection; production uses the bounded HTTP Runtime Skill Provider. */
16
+ prepareSkillProvider?: RuntimeSkillProviderFactory;
14
17
  }
15
18
  /**
16
19
  * Long-running daemon client for one user-scoped managed sandbox (ADR-015).
@@ -24,6 +27,7 @@ export declare class AgentServiceSandboxClient implements RunReportingClient, Pe
24
27
  private readonly log;
25
28
  private readonly random;
26
29
  private readonly sleep;
30
+ private readonly prepareSkillProvider;
27
31
  private readonly state;
28
32
  private readonly dispatcher;
29
33
  /** agent_run_id → TURN scope(session/attempt/activation),事件与文件帧路由用。 */
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { ensureDaemonHome } from "./auth-store.js";
4
4
  import { AGENT_SERVICE_WS_SCHEMA, AGENT_SERVICE_WS_SUBPROTOCOL, createSandboxFrame, parseSandboxFrame, UnsupportedSandboxProtocolError, } from "./agent-service-ws-protocol.js";
5
5
  import { log as defaultLog } from "./log.js";
6
- import { availableRunCapabilities } from "./runtime-capabilities.js";
6
+ import { availableRunCapabilities, runtimeSupportsCourseSkills, } from "./runtime-capabilities.js";
7
7
  import { activationRuntimeEnv, runtimeChildEnv } from "./runtime-env.js";
8
8
  import { redactSecretString } from "./redaction.js";
9
+ import { parseRuntimeSkillProviderGrantSet, prepareRuntimeSkillProvider, RuntimeSkillProviderError, } from "./runtime-skills.js";
9
10
  import { RunDispatcher, } from "./run-dispatcher.js";
10
11
  import { ensureRuntimeSessionDirectories, ensureRuntimeSessionWorkspace, exposeRuntimeSessionWorkspace, removeRuntimeSessionWorkspace, revokeRuntimeSessionWorkspace, } from "./workspace.js";
11
12
  import { WebSocketClient, } from "./websocket-client.js";
@@ -44,6 +45,9 @@ function inputAttachmentGrant(value) {
44
45
  }
45
46
  return { baseUrl: parsed.toString().replace(/\/$/, ""), token };
46
47
  }
48
+ function runtimeSkillGrantKey(grants) {
49
+ return JSON.stringify(grants);
50
+ }
47
51
  class SandboxClosedError extends Error {
48
52
  code;
49
53
  reason;
@@ -244,6 +248,7 @@ export class AgentServiceSandboxClient {
244
248
  log;
245
249
  random;
246
250
  sleep;
251
+ prepareSkillProvider;
247
252
  state;
248
253
  dispatcher;
249
254
  /** agent_run_id → TURN scope(session/attempt/activation),事件与文件帧路由用。 */
@@ -277,6 +282,8 @@ export class AgentServiceSandboxClient {
277
282
  this.log = options.log ?? defaultLog;
278
283
  this.random = options.random ?? Math.random;
279
284
  this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
285
+ this.prepareSkillProvider =
286
+ options.prepareSkillProvider ?? ((raw) => prepareRuntimeSkillProvider(raw));
280
287
  this.state = loadState(options.sandboxId, options.sandboxToken);
281
288
  this.dispatcher = new RunDispatcher(this, options.runtimes, {
282
289
  persistentSession: this,
@@ -318,6 +325,7 @@ export class AgentServiceSandboxClient {
318
325
  contextRevision,
319
326
  runtimeEnv,
320
327
  inputAttachmentGrant: activation.inputAttachmentGrant,
328
+ ...(activation.skillProvider ? { skillProvider: activation.skillProvider } : {}),
321
329
  };
322
330
  }
323
331
  persistNativeSession(nativeSessionId) {
@@ -849,33 +857,59 @@ export class AgentServiceSandboxClient {
849
857
  await this.sendCommandAck(frame, "rejected", error instanceof Error ? error.message : "invalid_instructions");
850
858
  return;
851
859
  }
860
+ const rawSkillProvider = frame.payload.skill_provider;
861
+ const hasSkillProvider = rawSkillProvider !== undefined && rawSkillProvider !== null;
862
+ if (hasSkillProvider && !runtimeSupportsCourseSkills(runtimeId)) {
863
+ await this.sendCommandAck(frame, "rejected", "skill_provider_runtime_unsupported");
864
+ return;
865
+ }
866
+ let skillGrantKey;
867
+ if (hasSkillProvider) {
868
+ try {
869
+ skillGrantKey = runtimeSkillGrantKey(parseRuntimeSkillProviderGrantSet(rawSkillProvider));
870
+ }
871
+ catch (error) {
872
+ const code = error instanceof RuntimeSkillProviderError
873
+ ? error.code
874
+ : "skill_provider_binding_invalid";
875
+ await this.sendCommandAck(frame, "rejected", code);
876
+ return;
877
+ }
878
+ }
852
879
  const capabilities = Array.isArray(frame.payload.capabilities)
853
880
  ? frame.payload.capabilities.filter((item) => typeof item === "string" && item.length > 0)
854
881
  : [];
855
- if (capabilities.length > 0) {
856
- const workspace = ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration);
857
- const available = new Set(availableRunCapabilities({
858
- agent_run_id: "activation-probe",
859
- course_run_id: session.courseRunId ?? "",
860
- lesson_id: null,
861
- task_id: null,
862
- agent_instance_id: null,
863
- runtime: { id: runtimeId },
864
- input: {},
865
- context: {},
866
- limits: {},
867
- }, workspace.workspaceDir));
868
- const missing = capabilities.filter((item) => !available.has(item)).sort();
869
- if (missing.length > 0) {
870
- await this.sendCommandAck(frame, "rejected", `missing_capabilities:${missing.join(",")}`);
871
- return;
882
+ let availableCapabilities;
883
+ const missingCapabilities = (required) => {
884
+ if (required.length === 0)
885
+ return [];
886
+ if (!availableCapabilities) {
887
+ const workspace = ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration);
888
+ availableCapabilities = new Set(availableRunCapabilities({
889
+ agent_run_id: "activation-probe",
890
+ course_run_id: session.courseRunId ?? "",
891
+ lesson_id: null,
892
+ task_id: null,
893
+ agent_instance_id: null,
894
+ runtime: { id: runtimeId },
895
+ input: {},
896
+ context: {},
897
+ limits: {},
898
+ }, workspace.workspaceDir));
872
899
  }
900
+ return Array.from(new Set(required.filter((item) => !availableCapabilities.has(item)))).sort();
901
+ };
902
+ const missingDeclaredCapabilities = missingCapabilities(capabilities);
903
+ if (missingDeclaredCapabilities.length > 0) {
904
+ await this.sendCommandAck(frame, "rejected", `missing_capabilities:${missingDeclaredCapabilities.join(",")}`);
905
+ return;
873
906
  }
874
907
  const existingActivation = this.activationContexts.get(sessionId);
875
908
  if (existingActivation?.activationId === activationId) {
876
909
  if (this.activeSessionId !== sessionId ||
877
910
  session.runtimeId !== runtimeId ||
878
- session.contextRevision !== contextRevision) {
911
+ session.contextRevision !== contextRevision ||
912
+ existingActivation.skillGrantKey !== skillGrantKey) {
879
913
  await this.sendCommandAck(frame, "rejected", "activation_redefinition");
880
914
  return;
881
915
  }
@@ -891,6 +925,25 @@ export class AgentServiceSandboxClient {
891
925
  await this.sendCommandAck(frame, "ok");
892
926
  return;
893
927
  }
928
+ let skillProvider;
929
+ if (hasSkillProvider) {
930
+ try {
931
+ skillProvider = await this.prepareSkillProvider(rawSkillProvider);
932
+ }
933
+ catch (error) {
934
+ const code = error instanceof RuntimeSkillProviderError
935
+ ? error.code
936
+ : "skill_provider_prepare_failed";
937
+ await this.sendCommandAck(frame, "rejected", code);
938
+ return;
939
+ }
940
+ const requiredBySkills = skillProvider.catalog.flatMap((entry) => entry.requiredCapabilities);
941
+ const missingSkillCapabilities = missingCapabilities(requiredBySkills);
942
+ if (missingSkillCapabilities.length > 0) {
943
+ await this.sendCommandAck(frame, "rejected", `missing_capabilities:${missingSkillCapabilities.join(",")}`);
944
+ return;
945
+ }
946
+ }
894
947
  // 同一时刻只允许一个 active session/activation。先撤销上一 workspace 的
895
948
  // runtime 访问并等待进程退出,再暴露新 workspace,避免同 UID 跨 Session 读取。
896
949
  const previousSessionId = this.activeSessionId;
@@ -920,6 +973,8 @@ export class AgentServiceSandboxClient {
920
973
  runtimeEnv,
921
974
  instructions,
922
975
  inputAttachmentGrant: attachmentGrant,
976
+ ...(skillProvider ? { skillProvider } : {}),
977
+ ...(skillGrantKey ? { skillGrantKey } : {}),
923
978
  });
924
979
  this.activeSessionId = sessionId;
925
980
  this.persist();
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./log.js";
18
18
  export * from "./redaction.js";
19
19
  export * from "./runtime-profile.js";
20
20
  export * from "./runtime-env.js";
21
+ export * from "./runtime-skills.js";
21
22
  export * from "./mcp/report-progress.js";
22
23
  export * from "./runtimes/index.js";
23
24
  export * from "./runtimes/engine.js";
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ export * from "./log.js";
18
18
  export * from "./redaction.js";
19
19
  export * from "./runtime-profile.js";
20
20
  export * from "./runtime-env.js";
21
+ export * from "./runtime-skills.js";
21
22
  export * from "./mcp/report-progress.js";
22
23
  export * from "./runtimes/index.js";
23
24
  export * from "./runtimes/engine.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runCourseSkillsRelay(argv?: string[]): void;
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import net from "node:net";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ export function runCourseSkillsRelay(argv = process.argv.slice(2)) {
7
+ const socketPath = relaySocketPath(argv);
8
+ const socket = net.createConnection({ path: socketPath });
9
+ const fail = () => {
10
+ process.stderr.write("course_skills relay unavailable\n");
11
+ process.exitCode = 1;
12
+ };
13
+ socket.once("error", fail);
14
+ socket.once("connect", () => {
15
+ process.stdin.pipe(socket);
16
+ socket.pipe(process.stdout);
17
+ });
18
+ socket.once("close", () => {
19
+ if (!process.stdin.readableEnded)
20
+ process.stdin.destroy();
21
+ });
22
+ }
23
+ function relaySocketPath(argv) {
24
+ if (argv.length !== 2 || argv[0] !== "--socket") {
25
+ throw new Error("course_skills relay requires --socket");
26
+ }
27
+ const value = argv[1] ?? "";
28
+ if (!path.isAbsolute(value)
29
+ || value.includes("\0")
30
+ || Buffer.byteLength(value, "utf8") > 240) {
31
+ throw new Error("course_skills relay socket is invalid");
32
+ }
33
+ return value;
34
+ }
35
+ function isMainModule() {
36
+ const entry = process.argv[1];
37
+ if (!entry)
38
+ return false;
39
+ try {
40
+ return realpathSync(entry) === fileURLToPath(import.meta.url);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ if (isMainModule()) {
47
+ try {
48
+ runCourseSkillsRelay();
49
+ }
50
+ catch {
51
+ process.stderr.write("course_skills relay configuration invalid\n");
52
+ process.exitCode = 1;
53
+ }
54
+ }
@@ -0,0 +1,49 @@
1
+ import { type PreparedRuntimeSkillProvider, type RuntimeSkillCatalogEntry, type RuntimeSkillEvent } from "../runtime-skills.js";
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
+ export interface CourseSkillsMcpServer {
19
+ socketPath: string;
20
+ catalog: RuntimeSkillCatalogEntry[];
21
+ markApplied(): Promise<void>;
22
+ close(): Promise<void>;
23
+ }
24
+ export interface CourseSkillsMcpServerOptions {
25
+ prepared: PreparedRuntimeSkillProvider;
26
+ onEvent(event: RuntimeSkillEvent): Promise<void>;
27
+ /**
28
+ * Managed Agent Service root. `undefined` auto-detects it; `null` is only for
29
+ * private same-UID tests/BYOA experiments.
30
+ */
31
+ managedRoot?: string | null;
32
+ }
33
+ export declare class CourseSkillsMcpRuntime {
34
+ private readonly options;
35
+ private readonly grantsByRef;
36
+ private readonly loadedSkills;
37
+ private readonly loadedReferences;
38
+ private referenceBytes;
39
+ constructor(options: CourseSkillsMcpServerOptions);
40
+ emitAppliedEvents(): Promise<void>;
41
+ handle(request: JsonRpcRequest): Promise<JsonRpcResponse | null>;
42
+ private loadSkill;
43
+ private loadReference;
44
+ private requireGrant;
45
+ private emit;
46
+ private emitLoadFailed;
47
+ }
48
+ export declare function startCourseSkillsMcpServer(options: CourseSkillsMcpServerOptions): Promise<CourseSkillsMcpServer>;
49
+ export {};