@downcity/agent 1.1.259 → 1.1.261

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.
@@ -1,93 +1,80 @@
1
1
  /**
2
- * @file 验证 shell safe sandbox 注入的临时目录环境变量。
3
- *
4
- * 关键点(中文)
5
- * - 测试编译后的 shell 包导出,确保最终 package 产物包含一致行为。
6
- * - zsh heredoc 会读取 `TMPPREFIX` 创建临时文件,因此必须指向 sandbox tmp。
2
+ * @file 验证 Safe Sandbox 环境变量收敛与临时目录映射。
7
3
  */
8
4
 
9
5
  import test from "node:test";
10
6
  import assert from "node:assert/strict";
11
7
  import path from "node:path";
12
8
 
13
- import { buildMacOsSeatbeltSandboxEnv } from "@downcity/shell/sandbox/MacOsSeatbeltSandbox.js";
14
- import { buildLinuxBubblewrapSandboxEnv } from "@downcity/shell/sandbox/LinuxBubblewrapSandbox.js";
15
- import {
16
- resolveSandboxEnvAllowlist,
17
- } from "@downcity/shell/sandbox/SandboxConfigResolver.js";
18
-
19
- function createParams() {
20
- const rootPath = "/tmp/downcity-shell-env/project";
21
- const sandboxDir = path.join(rootPath, ".downcity", "sandbox");
22
- const tmpDir = path.join(sandboxDir, "tmp");
23
- const cacheDir = path.join(sandboxDir, ".cache");
9
+ import { build_macos_sandbox_env } from "@downcity/shell/sandbox/MacOsSeatbelt.js";
10
+ import { build_linux_sandbox_env } from "@downcity/shell/sandbox/LinuxBubblewrap.js";
11
+ import { resolve_sandbox_policy } from "@downcity/shell/sandbox/SandboxPolicy.js";
24
12
 
13
+ function create_request() {
14
+ const root_path = "/tmp/downcity-shell-env/project";
15
+ const sandbox_dir = path.join(root_path, ".downcity", "sandbox");
25
16
  return {
26
- executionId: "sh_test",
27
- executionDir: path.join(rootPath, ".downcity", "shell", "sh_test"),
17
+ execution_id: "sh_test",
18
+ execution_dir: path.join(root_path, ".downcity", "shell", "sh_test"),
28
19
  cmd: "printf hello",
29
- cwd: rootPath,
30
- shellPath: "/bin/zsh",
20
+ cwd: root_path,
21
+ shell_path: "/bin/zsh",
31
22
  login: true,
32
- baseEnv: {
23
+ base_env: {
33
24
  PATH: "/usr/bin:/bin",
34
25
  LANG: "C.UTF-8",
35
26
  DC_SESSION_ID: "session_test",
36
27
  },
37
- config: {
28
+ policy: {
38
29
  backend: "macos-seatbelt",
39
- rootPath,
40
- sandboxDir,
41
- homeDir: sandboxDir,
42
- tmpDir,
43
- cacheDir,
44
- envAllowlist: ["PATH", "LANG"],
45
- writablePaths: [rootPath, sandboxDir],
46
- networkMode: "full",
30
+ root_path,
31
+ sandbox_dir,
32
+ home_dir: sandbox_dir,
33
+ tmp_dir: path.join(sandbox_dir, "tmp"),
34
+ cache_dir: path.join(sandbox_dir, ".cache"),
35
+ env_allowlist: ["PATH", "LANG"],
36
+ read_only_paths: ["/usr", "/bin"],
37
+ read_write_paths: [root_path],
38
+ network_mode: "full",
39
+ fingerprint: "policy_test",
47
40
  },
48
41
  };
49
42
  }
50
43
 
51
- function assertSandboxTmpEnv(env, tmpDir) {
52
- assert.equal(env.TMPDIR, tmpDir);
53
- assert.equal(env.TMP, tmpDir);
54
- assert.equal(env.TEMP, tmpDir);
55
- assert.equal(env.TEMPDIR, tmpDir);
56
- assert.equal(env.TMPPREFIX, path.join(tmpDir, "zsh"));
57
- assert.equal(env.DC_SANDBOX_TMP, tmpDir);
44
+ function assert_sandbox_tmp_env(env, tmp_dir) {
45
+ assert.equal(env.TMPDIR, tmp_dir);
46
+ assert.equal(env.TMP, tmp_dir);
47
+ assert.equal(env.TEMP, tmp_dir);
48
+ assert.equal(env.TEMPDIR, tmp_dir);
49
+ assert.equal(env.TMPPREFIX, path.join(tmp_dir, "zsh"));
50
+ assert.equal(env.DC_SANDBOX_TMP, tmp_dir);
58
51
  }
59
52
 
60
- test("macOS seatbelt sandbox env points zsh TMPPREFIX at sandbox tmp", () => {
61
- const params = createParams();
62
- const env = buildMacOsSeatbeltSandboxEnv(params);
63
-
64
- assertSandboxTmpEnv(env, params.config.tmpDir);
53
+ test("macOS safe env points temporary variables at sandbox tmp", () => {
54
+ const request = create_request();
55
+ const env = build_macos_sandbox_env(request);
56
+ assert_sandbox_tmp_env(env, request.policy.tmp_dir);
65
57
  });
66
58
 
67
- test("Linux bubblewrap sandbox env points zsh TMPPREFIX at sandbox tmp", () => {
68
- const params = createParams();
69
- const env = buildLinuxBubblewrapSandboxEnv({
70
- ...params,
71
- config: {
72
- ...params.config,
73
- backend: "linux-bubblewrap",
74
- },
59
+ test("Linux safe env points temporary variables at sandbox tmp", () => {
60
+ const request = create_request();
61
+ const env = build_linux_sandbox_env({
62
+ ...request,
63
+ policy: { ...request.policy, backend: "linux-bubblewrap" },
75
64
  });
76
-
77
- assertSandboxTmpEnv(env, params.config.tmpDir);
65
+ assert_sandbox_tmp_env(env, request.policy.tmp_dir);
78
66
  });
79
67
 
80
- test("safe sandbox env allowlist includes explicit agent env keys only", () => {
81
- const allowlist = resolveSandboxEnvAllowlist({
68
+ test("safe policy exports explicit agent env keys only", async () => {
69
+ const policy = await resolve_sandbox_policy({
82
70
  rootPath: "/tmp/downcity-shell-env/project",
83
71
  env: {
84
72
  DYNAMIC_ENV_REPRO: "dynamic_value",
85
73
  DC_DYNAMIC_REPRO: "dc_value",
86
74
  },
87
- });
88
-
89
- assert.equal(allowlist.includes("PATH"), true);
90
- assert.equal(allowlist.includes("DYNAMIC_ENV_REPRO"), true);
91
- assert.equal(allowlist.includes("DC_DYNAMIC_REPRO"), true);
92
- assert.equal(allowlist.includes("HOST_ONLY_ENV_REPRO"), false);
75
+ }, {});
76
+ assert.equal(policy.env_allowlist.includes("PATH"), true);
77
+ assert.equal(policy.env_allowlist.includes("DYNAMIC_ENV_REPRO"), true);
78
+ assert.equal(policy.env_allowlist.includes("DC_DYNAMIC_REPRO"), true);
79
+ assert.equal(policy.env_allowlist.includes("HOST_ONLY_ENV_REPRO"), false);
93
80
  });
@@ -3,51 +3,24 @@
3
3
  *
4
4
  * 关键点(中文)
5
5
  * - Agent 与 Session 对外统一接收 AgentModel,executor 内部只处理 AI SDK LanguageModel。
6
- * - CityModel 保留模型目录信息;运行时连接信息通过隐藏协议提供。
7
- * - 这里直接创建 OpenAI-compatible LanguageModel,不再保留旧的 text/stream 反向适配。
6
+ * - CityModel 自身实现 LanguageModelV3,executor 可以直接调用。
8
7
  * - 这里不依赖 @downcity/city,只依赖 @downcity/type 的共享协议。
9
8
  */
10
9
 
11
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
12
10
  import {
13
- CITY_MODEL_INVOKER,
14
11
  isCityModel,
15
- type CityModel,
16
12
  } from "@downcity/type";
17
13
  import type { LanguageModel } from "ai";
18
14
 
19
- /** 已完成转换的 CityModel 与 LanguageModel 对应关系。 */
20
- const normalized_models = new WeakMap<object, LanguageModel>();
21
-
22
15
  /**
23
16
  * Agent SDK 可接受的模型输入。
24
17
  */
25
- export type AgentModel = LanguageModel | CityModel;
26
-
27
- /**
28
- * 将 CityModel 的 hidden connection 转换为 AI SDK LanguageModel。
29
- */
30
- function cityModelToLanguageModel(model: CityModel): LanguageModel {
31
- const connection = model[CITY_MODEL_INVOKER].connection();
32
- const provider = createOpenAICompatible({
33
- name: "downcity",
34
- baseURL: connection.base_url,
35
- apiKey: connection.api_key,
36
- });
37
- return provider.languageModel(connection.model_id) as LanguageModel;
38
- }
18
+ export type AgentModel = LanguageModel;
39
19
 
40
20
  /**
41
21
  * 将 Agent 可接受的模型输入归一为 AI SDK LanguageModel。
42
22
  */
43
23
  export function normalizeAgentModel(model: AgentModel): LanguageModel {
44
- if (isCityModel(model)) {
45
- const cached = normalized_models.get(model);
46
- if (cached) return cached;
47
- const normalized = cityModelToLanguageModel(model);
48
- normalized_models.set(model, normalized);
49
- return normalized;
50
- }
51
24
  return model;
52
25
  }
53
26