@ouro.bot/cli 0.1.0-alpha.58 → 0.1.0-alpha.59
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/changelog.json +8 -0
- package/dist/heart/daemon/daemon-cli.js +1 -1
- package/dist/heart/daemon/daemon.js +1 -1
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +2 -2
- package/dist/heart/daemon/runtime-logging.js +20 -4
- package/dist/heart/daemon/runtime-metadata.js +1 -1
- package/dist/heart/identity.js +38 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/repertoire/tools-base.js +16 -9
- package/dist/senses/bluebubbles-media.js +2 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.59",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Repo file edits now acquire a safe workspace before local tool writes, creating a dedicated worktree from `origin/main` when running from a clone and a scratch clone from the canonical GitHub source when running from an installed runtime.",
|
|
8
|
+
"The harness now has bundle-local workspace primitives for repo work plus bundle-local daemon logs, daemon logging config, message routing storage, and tool caches instead of leaning on `~/.agentstate` paths.",
|
|
9
|
+
"Coverage/test-run artifacts and related runtime expectations now use the new non-`~/.agentstate` locations, and daemon/tool tests cover the safe workspace decision table and the bundle-local path migration."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.58",
|
|
6
14
|
"changes": [
|
|
@@ -790,7 +790,7 @@ function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
|
790
790
|
meta: { entryPath },
|
|
791
791
|
});
|
|
792
792
|
}
|
|
793
|
-
const logDir =
|
|
793
|
+
const logDir = (0, identity_1.getAgentDaemonLogsDir)();
|
|
794
794
|
(0, launchd_1.installLaunchAgent)(launchdDeps, {
|
|
795
795
|
nodePath: process.execPath,
|
|
796
796
|
entryPath,
|
|
@@ -405,7 +405,7 @@ class OuroDaemon {
|
|
|
405
405
|
ok: true,
|
|
406
406
|
summary: "logs: use `ouro logs` to tail daemon and agent output",
|
|
407
407
|
message: "log streaming available via ouro logs",
|
|
408
|
-
data: { logDir: "
|
|
408
|
+
data: { logDir: "~/AgentBundles/<agent>.ouro/state/daemon/logs" },
|
|
409
409
|
};
|
|
410
410
|
case "agent.start":
|
|
411
411
|
await this.processManager.startAgent(command.agent);
|
|
@@ -37,10 +37,10 @@ exports.discoverLogFiles = discoverLogFiles;
|
|
|
37
37
|
exports.readLastLines = readLastLines;
|
|
38
38
|
exports.formatLogLine = formatLogLine;
|
|
39
39
|
exports.tailLogs = tailLogs;
|
|
40
|
-
const os = __importStar(require("os"));
|
|
41
40
|
const path = __importStar(require("path"));
|
|
42
41
|
const nerves_1 = require("../../nerves");
|
|
43
42
|
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const identity_1 = require("../identity");
|
|
44
44
|
const LEVEL_COLORS = {
|
|
45
45
|
debug: "\x1b[2m",
|
|
46
46
|
info: "\x1b[36m",
|
|
@@ -49,11 +49,12 @@ const LEVEL_COLORS = {
|
|
|
49
49
|
};
|
|
50
50
|
function discoverLogFiles(options) {
|
|
51
51
|
/* v8 ignore start -- integration: default DI stubs for real OS @preserve */
|
|
52
|
-
const homeDir = options.homeDir ?? os.homedir();
|
|
53
52
|
const existsSync = options.existsSync ?? (() => false);
|
|
54
53
|
const readdirSync = options.readdirSync ?? (() => []);
|
|
55
54
|
/* v8 ignore stop */
|
|
56
|
-
const logDir =
|
|
55
|
+
const logDir = options.homeDir
|
|
56
|
+
? path.join(options.homeDir, "AgentBundles", `${options.agentName ?? "slugger"}.ouro`, "state", "daemon", "logs")
|
|
57
|
+
: (0, identity_1.getAgentDaemonLogsDir)(options.agentName);
|
|
57
58
|
const files = [];
|
|
58
59
|
if (existsSync(logDir)) {
|
|
59
60
|
for (const name of readdirSync(logDir)) {
|
|
@@ -35,9 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.FileMessageRouter = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const runtime_1 = require("../../nerves/runtime");
|
|
40
|
+
const identity_1 = require("../identity");
|
|
41
41
|
function messageId(nowIso) {
|
|
42
42
|
return `msg-${nowIso.replace(/[^0-9]/g, "")}`;
|
|
43
43
|
}
|
|
@@ -45,7 +45,7 @@ class FileMessageRouter {
|
|
|
45
45
|
baseDir;
|
|
46
46
|
now;
|
|
47
47
|
constructor(options = {}) {
|
|
48
|
-
this.baseDir = options.baseDir ??
|
|
48
|
+
this.baseDir = options.baseDir ?? (0, identity_1.getAgentMessagesRoot)();
|
|
49
49
|
this.now = options.now ?? (() => new Date().toISOString());
|
|
50
50
|
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
51
51
|
}
|
|
@@ -35,10 +35,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.configureDaemonRuntimeLogger = configureDaemonRuntimeLogger;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const nerves_1 = require("../../nerves");
|
|
41
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
const identity_1 = require("../identity");
|
|
42
42
|
const LEGACY_SHARED_RUNTIME_LOGGING = {
|
|
43
43
|
level: "info",
|
|
44
44
|
sinks: ["terminal", "ndjson"],
|
|
@@ -99,15 +99,31 @@ function resolveRuntimeLoggingConfig(configPath, processName) {
|
|
|
99
99
|
sinks,
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
function resolveAgentNameForPaths(explicit) {
|
|
103
|
+
if (explicit && explicit.trim().length > 0)
|
|
104
|
+
return explicit.trim();
|
|
105
|
+
try {
|
|
106
|
+
return (0, identity_1.getAgentName)();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return "slugger";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
102
112
|
function configureDaemonRuntimeLogger(processName, options = {}) {
|
|
103
|
-
const
|
|
104
|
-
const configPath = options.configPath
|
|
113
|
+
const agentName = resolveAgentNameForPaths(options.agentName);
|
|
114
|
+
const configPath = options.configPath
|
|
115
|
+
?? (options.homeDir
|
|
116
|
+
? path.join(options.homeDir, "AgentBundles", `${agentName}.ouro`, "state", "daemon", "logging.json")
|
|
117
|
+
: (0, identity_1.getAgentDaemonLoggingConfigPath)(agentName));
|
|
105
118
|
const config = resolveRuntimeLoggingConfig(configPath, processName);
|
|
119
|
+
const logsDir = options.homeDir
|
|
120
|
+
? path.join(options.homeDir, "AgentBundles", `${agentName}.ouro`, "state", "daemon", "logs")
|
|
121
|
+
: (0, identity_1.getAgentDaemonLogsDir)(agentName);
|
|
106
122
|
const sinks = config.sinks.map((sinkName) => {
|
|
107
123
|
if (sinkName === "terminal") {
|
|
108
124
|
return (0, nerves_1.createTerminalSink)();
|
|
109
125
|
}
|
|
110
|
-
const ndjsonPath = path.join(
|
|
126
|
+
const ndjsonPath = path.join(logsDir, `${processName}.ndjson`);
|
|
111
127
|
return (0, nerves_1.createNdjsonFileSink)(ndjsonPath);
|
|
112
128
|
});
|
|
113
129
|
const logger = (0, nerves_1.createLogger)({
|
|
@@ -174,7 +174,7 @@ function getRuntimeMetadata(deps = {}) {
|
|
|
174
174
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
175
175
|
const homeDir = readHomeDir();
|
|
176
176
|
const secretsRoot = deps.secretsRoot ?? (homeDir ? path.join(homeDir, ".agentsecrets") : null);
|
|
177
|
-
const daemonLoggingPath = deps.daemonLoggingPath ?? (
|
|
177
|
+
const daemonLoggingPath = deps.daemonLoggingPath ?? (0, identity_1.getAgentDaemonLoggingConfigPath)();
|
|
178
178
|
const readFileSyncImpl = deps.readFileSync ?? optionalFunction(fs, "readFileSync")?.bind(fs) ?? null;
|
|
179
179
|
const statSyncImpl = deps.statSync ?? optionalFunction(fs, "statSync")?.bind(fs) ?? null;
|
|
180
180
|
const readdirSyncImpl = deps.readdirSync ?? optionalFunction(fs, "readdirSync")?.bind(fs) ?? null;
|
package/dist/heart/identity.js
CHANGED
|
@@ -33,13 +33,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
36
|
+
exports.HARNESS_CANONICAL_REPO_URL = exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
37
37
|
exports.buildDefaultAgentTemplate = buildDefaultAgentTemplate;
|
|
38
38
|
exports.getAgentName = getAgentName;
|
|
39
39
|
exports.getRepoRoot = getRepoRoot;
|
|
40
40
|
exports.getAgentBundlesRoot = getAgentBundlesRoot;
|
|
41
41
|
exports.getAgentRoot = getAgentRoot;
|
|
42
42
|
exports.getAgentStateRoot = getAgentStateRoot;
|
|
43
|
+
exports.getAgentRepoWorkspacesRoot = getAgentRepoWorkspacesRoot;
|
|
44
|
+
exports.getAgentDaemonStateRoot = getAgentDaemonStateRoot;
|
|
45
|
+
exports.getAgentDaemonLogsDir = getAgentDaemonLogsDir;
|
|
46
|
+
exports.getAgentDaemonLoggingConfigPath = getAgentDaemonLoggingConfigPath;
|
|
47
|
+
exports.getAgentMessagesRoot = getAgentMessagesRoot;
|
|
48
|
+
exports.getAgentToolsRoot = getAgentToolsRoot;
|
|
43
49
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
44
50
|
exports.loadAgentConfig = loadAgentConfig;
|
|
45
51
|
exports.setAgentName = setAgentName;
|
|
@@ -184,11 +190,40 @@ function getAgentBundlesRoot() {
|
|
|
184
190
|
function getAgentRoot(agentName = getAgentName()) {
|
|
185
191
|
return path.join(getAgentBundlesRoot(), `${agentName}.ouro`);
|
|
186
192
|
}
|
|
193
|
+
function resolveOptionalAgentName(agentName) {
|
|
194
|
+
if (agentName && agentName.trim().length > 0)
|
|
195
|
+
return agentName.trim();
|
|
196
|
+
try {
|
|
197
|
+
return getAgentName();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return "slugger";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
187
203
|
/**
|
|
188
204
|
* Returns the bundle-local runtime state directory: `~/AgentBundles/<agentName>.ouro/state/`
|
|
189
205
|
*/
|
|
190
|
-
function getAgentStateRoot(agentName
|
|
191
|
-
return path.join(getAgentRoot(agentName), "state");
|
|
206
|
+
function getAgentStateRoot(agentName) {
|
|
207
|
+
return path.join(getAgentRoot(resolveOptionalAgentName(agentName)), "state");
|
|
208
|
+
}
|
|
209
|
+
exports.HARNESS_CANONICAL_REPO_URL = "https://github.com/ouroborosbot/ouroboros.git";
|
|
210
|
+
function getAgentRepoWorkspacesRoot(agentName) {
|
|
211
|
+
return path.join(getAgentStateRoot(resolveOptionalAgentName(agentName)), "workspaces");
|
|
212
|
+
}
|
|
213
|
+
function getAgentDaemonStateRoot(agentName) {
|
|
214
|
+
return path.join(getAgentStateRoot(resolveOptionalAgentName(agentName)), "daemon");
|
|
215
|
+
}
|
|
216
|
+
function getAgentDaemonLogsDir(agentName) {
|
|
217
|
+
return path.join(getAgentDaemonStateRoot(resolveOptionalAgentName(agentName)), "logs");
|
|
218
|
+
}
|
|
219
|
+
function getAgentDaemonLoggingConfigPath(agentName) {
|
|
220
|
+
return path.join(getAgentDaemonStateRoot(resolveOptionalAgentName(agentName)), "logging.json");
|
|
221
|
+
}
|
|
222
|
+
function getAgentMessagesRoot(agentName) {
|
|
223
|
+
return path.join(getAgentStateRoot(resolveOptionalAgentName(agentName)), "messages");
|
|
224
|
+
}
|
|
225
|
+
function getAgentToolsRoot(agentName) {
|
|
226
|
+
return path.join(getAgentStateRoot(resolveOptionalAgentName(agentName)), "tools");
|
|
192
227
|
}
|
|
193
228
|
/**
|
|
194
229
|
* Returns the conventional secrets path: `~/.agentsecrets/<agentName>/secrets.json`
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resetSafeWorkspaceSelection = resetSafeWorkspaceSelection;
|
|
37
|
+
exports.getActiveSafeWorkspaceSelection = getActiveSafeWorkspaceSelection;
|
|
38
|
+
exports.ensureSafeRepoWorkspace = ensureSafeRepoWorkspace;
|
|
39
|
+
exports.resolveSafeRepoPath = resolveSafeRepoPath;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const identity_1 = require("./identity");
|
|
44
|
+
const runtime_1 = require("../nerves/runtime");
|
|
45
|
+
let activeSelection = null;
|
|
46
|
+
let cleanupHookRegistered = false;
|
|
47
|
+
function defaultNow() {
|
|
48
|
+
return Date.now();
|
|
49
|
+
}
|
|
50
|
+
function resolveAgentName(explicit) {
|
|
51
|
+
if (explicit && explicit.trim().length > 0)
|
|
52
|
+
return explicit.trim();
|
|
53
|
+
try {
|
|
54
|
+
return (0, identity_1.getAgentName)();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return "slugger";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function runGit(cwd, args, spawnSync) {
|
|
61
|
+
return spawnSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
62
|
+
}
|
|
63
|
+
function readStdout(result) {
|
|
64
|
+
return (result.stdout ?? Buffer.from("")).toString("utf-8").trim();
|
|
65
|
+
}
|
|
66
|
+
function readStderr(result) {
|
|
67
|
+
return (result.stderr ?? Buffer.from("")).toString("utf-8").trim();
|
|
68
|
+
}
|
|
69
|
+
function assertGitOk(result, action) {
|
|
70
|
+
if (result.error) {
|
|
71
|
+
throw result.error;
|
|
72
|
+
}
|
|
73
|
+
if (result.status !== 0) {
|
|
74
|
+
const detail = readStderr(result) || readStdout(result) || `exit ${result.status ?? "unknown"}`;
|
|
75
|
+
throw new Error(`${action} failed: ${detail}`);
|
|
76
|
+
}
|
|
77
|
+
return readStdout(result);
|
|
78
|
+
}
|
|
79
|
+
function isGitClone(repoRoot, spawnSync) {
|
|
80
|
+
const result = runGit(repoRoot, ["rev-parse", "--is-inside-work-tree"], spawnSync);
|
|
81
|
+
return result.status === 0 && readStdout(result) === "true";
|
|
82
|
+
}
|
|
83
|
+
function readCurrentBranch(repoRoot, spawnSync) {
|
|
84
|
+
return assertGitOk(runGit(repoRoot, ["rev-parse", "--abbrev-ref", "HEAD"], spawnSync), "git branch read");
|
|
85
|
+
}
|
|
86
|
+
function ensureFetchedOrigin(repoRoot, spawnSync) {
|
|
87
|
+
assertGitOk(runGit(repoRoot, ["fetch", "origin"], spawnSync), "git fetch origin");
|
|
88
|
+
}
|
|
89
|
+
function ensureMainFastForward(repoRoot, spawnSync) {
|
|
90
|
+
assertGitOk(runGit(repoRoot, ["pull", "--ff-only", "origin", "main"], spawnSync), "git pull --ff-only origin main");
|
|
91
|
+
}
|
|
92
|
+
function createDedicatedWorktree(repoRoot, workspaceRoot, branchSuffix, existsSync, mkdirSync, rmSync, spawnSync) {
|
|
93
|
+
mkdirSync(path.dirname(workspaceRoot), { recursive: true });
|
|
94
|
+
const branchName = `slugger/${branchSuffix}`;
|
|
95
|
+
if (existsSync(workspaceRoot)) {
|
|
96
|
+
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
assertGitOk(runGit(repoRoot, ["worktree", "add", "-B", branchName, workspaceRoot, "origin/main"], spawnSync), "git worktree add");
|
|
99
|
+
return { workspaceRoot, created: true };
|
|
100
|
+
}
|
|
101
|
+
function createScratchClone(workspaceRoot, cloneUrl, existsSync, mkdirSync, rmSync, spawnSync) {
|
|
102
|
+
mkdirSync(path.dirname(workspaceRoot), { recursive: true });
|
|
103
|
+
if (existsSync(workspaceRoot)) {
|
|
104
|
+
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
const result = spawnSync("git", ["clone", "--depth", "1", "--branch", "main", cloneUrl, workspaceRoot], {
|
|
107
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
108
|
+
});
|
|
109
|
+
assertGitOk(result, "git clone");
|
|
110
|
+
return { workspaceRoot, created: true };
|
|
111
|
+
}
|
|
112
|
+
function registerCleanupHook(options) {
|
|
113
|
+
if (cleanupHookRegistered)
|
|
114
|
+
return;
|
|
115
|
+
cleanupHookRegistered = true;
|
|
116
|
+
process.on("exit", () => {
|
|
117
|
+
if (!activeSelection?.cleanupAfterMerge)
|
|
118
|
+
return;
|
|
119
|
+
try {
|
|
120
|
+
options.rmSync(activeSelection.workspaceRoot, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// best effort
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function resetSafeWorkspaceSelection(options = {}) {
|
|
128
|
+
activeSelection = null;
|
|
129
|
+
if (!options.keepCleanupHookRegistered) {
|
|
130
|
+
cleanupHookRegistered = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function getActiveSafeWorkspaceSelection() {
|
|
134
|
+
return activeSelection;
|
|
135
|
+
}
|
|
136
|
+
function ensureSafeRepoWorkspace(options = {}) {
|
|
137
|
+
if (activeSelection) {
|
|
138
|
+
return activeSelection;
|
|
139
|
+
}
|
|
140
|
+
const repoRoot = options.repoRoot ?? (0, identity_1.getRepoRoot)();
|
|
141
|
+
const agentName = resolveAgentName(options.agentName);
|
|
142
|
+
const canonicalRepoUrl = options.canonicalRepoUrl ?? identity_1.HARNESS_CANONICAL_REPO_URL;
|
|
143
|
+
const workspaceBase = options.workspaceRoot ?? (0, identity_1.getAgentRepoWorkspacesRoot)(agentName);
|
|
144
|
+
const spawnSync = options.spawnSync ?? child_process_1.spawnSync;
|
|
145
|
+
const existsSync = options.existsSync ?? fs.existsSync;
|
|
146
|
+
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
|
147
|
+
const rmSync = options.rmSync ?? fs.rmSync;
|
|
148
|
+
const now = options.now ?? defaultNow;
|
|
149
|
+
const stamp = String(now());
|
|
150
|
+
registerCleanupHook({ rmSync });
|
|
151
|
+
let selection;
|
|
152
|
+
if (isGitClone(repoRoot, spawnSync)) {
|
|
153
|
+
const branch = readCurrentBranch(repoRoot, spawnSync);
|
|
154
|
+
ensureFetchedOrigin(repoRoot, spawnSync);
|
|
155
|
+
if (branch === "main") {
|
|
156
|
+
ensureMainFastForward(repoRoot, spawnSync);
|
|
157
|
+
const worktreeRoot = path.join(workspaceBase, `ouroboros-main-${stamp}`);
|
|
158
|
+
const created = createDedicatedWorktree(repoRoot, worktreeRoot, `safe-workspace-${stamp}`, existsSync, mkdirSync, rmSync, spawnSync);
|
|
159
|
+
selection = {
|
|
160
|
+
runtimeKind: "clone-main",
|
|
161
|
+
repoRoot,
|
|
162
|
+
workspaceRoot: created.workspaceRoot,
|
|
163
|
+
sourceBranch: branch,
|
|
164
|
+
sourceCloneUrl: canonicalRepoUrl,
|
|
165
|
+
cleanupAfterMerge: false,
|
|
166
|
+
created: created.created,
|
|
167
|
+
note: `running from clone on main; fast-forwarded and created dedicated worktree ${created.workspaceRoot}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const worktreeRoot = path.join(workspaceBase, `ouroboros-origin-main-${stamp}`);
|
|
172
|
+
const created = createDedicatedWorktree(repoRoot, worktreeRoot, `safe-workspace-${stamp}`, existsSync, mkdirSync, rmSync, spawnSync);
|
|
173
|
+
selection = {
|
|
174
|
+
runtimeKind: "clone-non-main",
|
|
175
|
+
repoRoot,
|
|
176
|
+
workspaceRoot: created.workspaceRoot,
|
|
177
|
+
sourceBranch: branch,
|
|
178
|
+
sourceCloneUrl: canonicalRepoUrl,
|
|
179
|
+
cleanupAfterMerge: false,
|
|
180
|
+
created: created.created,
|
|
181
|
+
note: `running from branch ${branch}; defaulted new work from origin/main in dedicated worktree ${created.workspaceRoot}`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const scratchRoot = path.join(workspaceBase, `ouroboros-scratch-${stamp}`);
|
|
187
|
+
const created = createScratchClone(scratchRoot, canonicalRepoUrl, existsSync, mkdirSync, rmSync, spawnSync);
|
|
188
|
+
selection = {
|
|
189
|
+
runtimeKind: "installed-runtime",
|
|
190
|
+
repoRoot,
|
|
191
|
+
workspaceRoot: created.workspaceRoot,
|
|
192
|
+
sourceBranch: null,
|
|
193
|
+
sourceCloneUrl: canonicalRepoUrl,
|
|
194
|
+
cleanupAfterMerge: true,
|
|
195
|
+
created: created.created,
|
|
196
|
+
note: `running from installed runtime/wrapper; created scratch clone ${created.workspaceRoot} from ${canonicalRepoUrl}`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
activeSelection = selection;
|
|
200
|
+
(0, runtime_1.emitNervesEvent)({
|
|
201
|
+
component: "workspace",
|
|
202
|
+
event: "workspace.safe_repo_acquired",
|
|
203
|
+
message: "acquired safe repo workspace before local edits",
|
|
204
|
+
meta: {
|
|
205
|
+
runtimeKind: selection.runtimeKind,
|
|
206
|
+
repoRoot: selection.repoRoot,
|
|
207
|
+
workspaceRoot: selection.workspaceRoot,
|
|
208
|
+
sourceBranch: selection.sourceBranch,
|
|
209
|
+
sourceCloneUrl: selection.sourceCloneUrl,
|
|
210
|
+
cleanupAfterMerge: selection.cleanupAfterMerge,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
return selection;
|
|
214
|
+
}
|
|
215
|
+
function resolveSafeRepoPath(options) {
|
|
216
|
+
const requestedPath = path.resolve(options.requestedPath);
|
|
217
|
+
const repoRoot = path.resolve(options.repoRoot ?? (0, identity_1.getRepoRoot)());
|
|
218
|
+
if (activeSelection && requestedPath.startsWith(activeSelection.workspaceRoot + path.sep)) {
|
|
219
|
+
return { selection: activeSelection, resolvedPath: requestedPath };
|
|
220
|
+
}
|
|
221
|
+
if (requestedPath !== repoRoot && !requestedPath.startsWith(repoRoot + path.sep)) {
|
|
222
|
+
return { selection: activeSelection, resolvedPath: requestedPath };
|
|
223
|
+
}
|
|
224
|
+
const selection = ensureSafeRepoWorkspace(options);
|
|
225
|
+
const relativePath = requestedPath === repoRoot ? "" : path.relative(repoRoot, requestedPath);
|
|
226
|
+
const resolvedPath = relativePath ? path.join(selection.workspaceRoot, relativePath) : selection.workspaceRoot;
|
|
227
|
+
return { selection, resolvedPath };
|
|
228
|
+
}
|
|
@@ -14,7 +14,7 @@ const path_1 = require("path");
|
|
|
14
14
|
const os_1 = require("os");
|
|
15
15
|
exports.REPO_SLUG = "ouroboros-agent-harness";
|
|
16
16
|
function getTestRunsRoot(repoSlug = exports.REPO_SLUG) {
|
|
17
|
-
return (0, path_1.join)((0, os_1.
|
|
17
|
+
return (0, path_1.join)((0, os_1.tmpdir)(), "ouroboros-test-runs", repoSlug);
|
|
18
18
|
}
|
|
19
19
|
function createRunId(now = new Date()) {
|
|
20
20
|
return now.toISOString().replace(/[:.]/g, "-");
|
|
@@ -42,6 +42,7 @@ const skills_1 = require("./skills");
|
|
|
42
42
|
const config_1 = require("../heart/config");
|
|
43
43
|
const runtime_1 = require("../nerves/runtime");
|
|
44
44
|
const identity_1 = require("../heart/identity");
|
|
45
|
+
const safe_workspace_1 = require("../heart/safe-workspace");
|
|
45
46
|
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
46
47
|
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
47
48
|
const manager_1 = require("../heart/bridges/manager");
|
|
@@ -65,6 +66,9 @@ function buildContextDiff(lines, changeStart, changeEnd, contextSize = 3) {
|
|
|
65
66
|
}
|
|
66
67
|
return result.join("\n");
|
|
67
68
|
}
|
|
69
|
+
function resolveLocalToolPath(targetPath) {
|
|
70
|
+
return (0, safe_workspace_1.resolveSafeRepoPath)({ requestedPath: targetPath }).resolvedPath;
|
|
71
|
+
}
|
|
68
72
|
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
69
73
|
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
70
74
|
function findDelegatingBridgeId(ctx) {
|
|
@@ -187,8 +191,9 @@ exports.baseToolDefinitions = [
|
|
|
187
191
|
},
|
|
188
192
|
},
|
|
189
193
|
handler: (a) => {
|
|
190
|
-
const
|
|
191
|
-
|
|
194
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
195
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
196
|
+
exports.editFileReadTracker.add(resolvedPath);
|
|
192
197
|
const offset = a.offset ? parseInt(a.offset, 10) : undefined;
|
|
193
198
|
const limit = a.limit ? parseInt(a.limit, 10) : undefined;
|
|
194
199
|
if (offset === undefined && limit === undefined)
|
|
@@ -213,8 +218,9 @@ exports.baseToolDefinitions = [
|
|
|
213
218
|
},
|
|
214
219
|
},
|
|
215
220
|
handler: (a) => {
|
|
216
|
-
|
|
217
|
-
fs.
|
|
221
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
222
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
223
|
+
fs.writeFileSync(resolvedPath, a.content, "utf-8");
|
|
218
224
|
return "ok";
|
|
219
225
|
},
|
|
220
226
|
},
|
|
@@ -236,12 +242,13 @@ exports.baseToolDefinitions = [
|
|
|
236
242
|
},
|
|
237
243
|
},
|
|
238
244
|
handler: (a) => {
|
|
239
|
-
|
|
245
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
246
|
+
if (!exports.editFileReadTracker.has(resolvedPath)) {
|
|
240
247
|
return `error: you must read the file with read_file before editing it. call read_file on ${a.path} first.`;
|
|
241
248
|
}
|
|
242
249
|
let content;
|
|
243
250
|
try {
|
|
244
|
-
content = fs.readFileSync(
|
|
251
|
+
content = fs.readFileSync(resolvedPath, "utf-8");
|
|
245
252
|
}
|
|
246
253
|
catch (e) {
|
|
247
254
|
return `error: could not read file: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
@@ -265,7 +272,7 @@ exports.baseToolDefinitions = [
|
|
|
265
272
|
// Single unique match -- replace
|
|
266
273
|
const idx = occurrences[0];
|
|
267
274
|
const updated = content.slice(0, idx) + a.new_string + content.slice(idx + a.old_string.length);
|
|
268
|
-
fs.writeFileSync(
|
|
275
|
+
fs.writeFileSync(resolvedPath, updated, "utf-8");
|
|
269
276
|
// Build contextual diff
|
|
270
277
|
const lines = updated.split("\n");
|
|
271
278
|
const prefixLines = content.slice(0, idx).split("\n");
|
|
@@ -292,7 +299,7 @@ exports.baseToolDefinitions = [
|
|
|
292
299
|
},
|
|
293
300
|
},
|
|
294
301
|
handler: (a) => {
|
|
295
|
-
const cwd = a.cwd
|
|
302
|
+
const cwd = a.cwd ? resolveLocalToolPath(a.cwd) : process.cwd();
|
|
296
303
|
const matches = fg.globSync(a.pattern, { cwd, dot: true });
|
|
297
304
|
return matches.sort().join("\n");
|
|
298
305
|
},
|
|
@@ -316,7 +323,7 @@ exports.baseToolDefinitions = [
|
|
|
316
323
|
},
|
|
317
324
|
},
|
|
318
325
|
handler: (a) => {
|
|
319
|
-
const targetPath = a.path;
|
|
326
|
+
const targetPath = resolveLocalToolPath(a.path);
|
|
320
327
|
const regex = new RegExp(a.pattern);
|
|
321
328
|
const contextLines = parseInt(a.context_lines || "0", 10);
|
|
322
329
|
const includeGlob = a.include || undefined;
|
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("node:fs/promises"));
|
|
|
39
39
|
const os = __importStar(require("node:os"));
|
|
40
40
|
const path = __importStar(require("node:path"));
|
|
41
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const identity_1 = require("../heart/identity");
|
|
42
43
|
const MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
|
|
43
44
|
const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".m4a", ".caf", ".ogg"]);
|
|
44
45
|
const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".heic", ".heif"]);
|
|
@@ -65,7 +66,7 @@ const AUDIO_INPUT_FORMAT_BY_EXTENSION = {
|
|
|
65
66
|
const WHISPER_CPP_FORMULA = "whisper-cpp";
|
|
66
67
|
const WHISPER_CPP_MODEL_NAME = "ggml-base.en.bin";
|
|
67
68
|
const WHISPER_CPP_MODEL_URL = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_CPP_MODEL_NAME}`;
|
|
68
|
-
const WHISPER_CPP_TOOLS_DIR = path.join(
|
|
69
|
+
const WHISPER_CPP_TOOLS_DIR = path.join((0, identity_1.getAgentToolsRoot)(), "whisper-cpp");
|
|
69
70
|
const WHISPER_CPP_MODELS_DIR = path.join(WHISPER_CPP_TOOLS_DIR, "models");
|
|
70
71
|
const WHISPER_CPP_MODEL_PATH = path.join(WHISPER_CPP_MODELS_DIR, WHISPER_CPP_MODEL_NAME);
|
|
71
72
|
function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
|