File size: 5,233 Bytes
63e0fa3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import { collectRootPackageExcludedExtensionDirs } from "../scripts/lib/bundled-plugin-build-entries.mjs";
import { isLiveTestEnabled } from "../src/agents/live-test-helpers.js";
import type { AgentExecEnvelope } from "../src/commands/agent-exec-result.js";
import { useAutoCleanupTempDirTracker } from "./helpers/temp-dir.js";

const execFileAsync = promisify(execFile);
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const openAiApiKey = process.env.OPENAI_API_KEY?.trim() ?? "";
const describeLive = isLiveTestEnabled() && openAiApiKey.length > 0 ? describe : describe.skip;

describeLive("agent exec Code Mode with environment authentication", () => {
  it("starts without configuration and completes dependent filesystem calls", async () => {
    const root = tempDirs.make("openclaw-agent-exec-code-mode-live-");
    const home = path.join(root, "home");
    const stateDir = path.join(root, "state");
    const workspace = path.join(root, "workspace");
    const tmpDir = path.join(root, "tmp");
    await Promise.all([home, stateDir, workspace, tmpDir].map((dir) => fs.mkdir(dir)));
    const repoRoot = path.resolve(import.meta.dirname, "..");
    const installedRoot = path.join(root, "node_modules", "openclaw");
    const sourceDist = path.join(repoRoot, "dist");
    const excludedPlugins = collectRootPackageExcludedExtensionDirs({ cwd: repoRoot });
    await fs.mkdir(installedRoot, { recursive: true });
    await fs.copyFile(
      path.join(repoRoot, "package.json"),
      path.join(installedRoot, "package.json"),
    );
    // A source checkout discovers external plugins beside dist. Copy the built
    // distribution with its package exclusions so discovery sees a clean install.
    await fs.cp(sourceDist, path.join(installedRoot, "dist"), {
      recursive: true,
      mode: constants.COPYFILE_FICLONE,
      filter: (source) => {
        const [directory, pluginId] = path.relative(sourceDist, source).split(path.sep);
        return (
          directory !== "extensions" || pluginId === undefined || !excludedPlugins.has(pluginId)
        );
      },
    });
    await fs.symlink(
      await fs.realpath(path.join(repoRoot, "node_modules")),
      path.join(installedRoot, "node_modules"),
      process.platform === "win32" ? "junction" : "dir",
    );
    const input = `code-mode-${randomUUID()}\n`;
    await fs.writeFile(path.join(workspace, "input.txt"), input);

    // Exercise production discovery: no inherited test shortcuts, authored
    // config, installed harness, external CLI credentials, or runtime override.
    const env: NodeJS.ProcessEnv = {
      PATH: process.env.PATH,
      SystemRoot: process.env.SystemRoot,
      HOME: home,
      USERPROFILE: home,
      TMPDIR: tmpDir,
      TMP: tmpDir,
      TEMP: tmpDir,
      OPENCLAW_STATE_DIR: stateDir,
      OPENAI_API_KEY: openAiApiKey,
    };
    let stdout: string;
    let stderr: string;
    try {
      ({ stdout, stderr } = await execFileAsync(
        process.execPath,
        [
          path.join(installedRoot, "dist", "entry.js"),
          "agent",
          "exec",
          "Use Code Mode and the filesystem read and write tools to read input.txt, then write " +
            "output.txt containing exactly the input bytes followed by processed and a newline. " +
            "Do not use a shell command. Reply with exactly DONE after the write completes.",
          "--auth-env-only",
          "--model",
          "openai/gpt-5.6-luna",
          "--code-mode",
          "code",
          "--local-model-lean",
          "--thinking",
          "low",
          "--cwd",
          workspace,
          "--state-dir",
          stateDir,
          "--timeout",
          "180",
          "--json",
        ],
        {
          cwd: workspace,
          env,
          encoding: "utf8",
          maxBuffer: 2 * 1024 * 1024,
          timeout: 300_000,
        },
      ));
    } catch (error) {
      // Child-process failures retain stdout/stderr; never attach that raw
      // error as a cause because live credentials could appear in diagnostics.
      const message = error instanceof Error ? error.message : String(error);
      // eslint-disable-next-line preserve-caught-error -- The raw cause can expose live credentials.
      throw new Error(message.replaceAll(openAiApiKey, "[REDACTED]"));
    }

    expect(stdout.includes(openAiApiKey) || stderr.includes(openAiApiKey)).toBe(false);
    const result = JSON.parse(stdout) as AgentExecEnvelope;
    expect(result).toMatchObject({
      ok: true,
      status: "ok",
      provider: "openai",
      model: "gpt-5.6-luna",
      codeModeEngaged: true,
      final: "DONE",
    });
    expect(result.toolSummary?.tools).toContain("exec");
    expect(result.bridgeCalls?.call).toBeGreaterThanOrEqual(2);
    await expect(fs.readFile(path.join(workspace, "output.txt"), "utf8")).resolves.toBe(
      `${input}processed\n`,
    );
  }, 330_000);
});