@ouro.bot/cli 0.1.0-alpha.249 → 0.1.0-alpha.250
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 +6 -0
- package/dist/heart/daemon/agent-discovery.js +24 -17
- package/dist/heart/daemon/cli-render.js +32 -9
- package/dist/heart/sync.js +45 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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.250",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(sync): surface 'bundle is not a git repo' as an actionable error instead of silently failing. Previously, enabling `sync.enabled` on a bundle that had never been `git init`'d produced a generic `git status` failure buried in nerves logs; the agent saw nothing in its start-of-turn packet and the user saw nothing in `ouro status`. Now: (1) `preTurnPull` and `postTurnPush` detect the missing `.git` directory before touching git and return an actionable error with the bundle path and the `git init` hint; (2) this error propagates via `ctx.syncFailure` into the agent's Sync warning, so the agent can offer to run `git init` or just do it; (3) `ouro status` shows a red `error` state with `not a git repo — run \\`git init\\` to enable sync` next to the offending bundle. New nerves event: `heart.sync_not_a_repo`."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.249",
|
|
6
12
|
"changes": [
|
|
@@ -87,15 +87,16 @@ function listEnabledBundleAgents(options = {}) {
|
|
|
87
87
|
* sync rows without depending on argv-derived global identity. Bundles that
|
|
88
88
|
* cannot be read are skipped silently.
|
|
89
89
|
*
|
|
90
|
-
* For rows with sync enabled, also
|
|
91
|
-
*
|
|
92
|
-
* remote
|
|
93
|
-
* status renderer
|
|
90
|
+
* For rows with sync enabled, also checks whether the bundle is a git repo
|
|
91
|
+
* (via .git directory presence) and resolves the remote URL via
|
|
92
|
+
* `git remote get-url <remote>`. On any error the URL is left undefined and
|
|
93
|
+
* the status renderer falls back to "local only" or "not a git repo".
|
|
94
94
|
*/
|
|
95
95
|
function listBundleSyncRows(options = {}) {
|
|
96
96
|
const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
97
97
|
const readFileSync = options.readFileSync ?? fs.readFileSync;
|
|
98
98
|
const execFileSync = options.execFileSync ?? child_process_1.execFileSync;
|
|
99
|
+
const existsSync = options.existsSync ?? fs.existsSync;
|
|
99
100
|
const agents = listEnabledBundleAgents(options);
|
|
100
101
|
const rows = [];
|
|
101
102
|
for (const agent of agents) {
|
|
@@ -117,22 +118,28 @@ function listBundleSyncRows(options = {}) {
|
|
|
117
118
|
catch {
|
|
118
119
|
// Best-effort: bundle without readable config still gets a row with defaults
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
+
const row = { agent, enabled, remote };
|
|
121
122
|
if (enabled) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
// Only meaningful when sync is enabled — we don't care about disabled bundles'
|
|
124
|
+
// git state. Check .git presence before attempting any git invocation.
|
|
125
|
+
const gitInitialized = existsSync(path.join(bundleRoot, ".git"));
|
|
126
|
+
row.gitInitialized = gitInitialized;
|
|
127
|
+
if (gitInitialized) {
|
|
128
|
+
try {
|
|
129
|
+
const out = execFileSync("git", ["remote", "get-url", remote], {
|
|
130
|
+
cwd: bundleRoot,
|
|
131
|
+
stdio: "pipe",
|
|
132
|
+
timeout: 5000,
|
|
133
|
+
}).toString().trim();
|
|
134
|
+
if (out.length > 0)
|
|
135
|
+
row.remoteUrl = out;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// No remote configured or git missing — leave remoteUrl undefined.
|
|
139
|
+
}
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
|
-
rows.push(
|
|
142
|
+
rows.push(row);
|
|
136
143
|
}
|
|
137
144
|
return rows;
|
|
138
145
|
}
|
|
@@ -147,10 +147,14 @@ function parseStatusPayload(data) {
|
|
|
147
147
|
const remote = stringField(row.remote);
|
|
148
148
|
if (!agent || enabled === null || !remote)
|
|
149
149
|
return null;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
const parsed = { agent, enabled, remote };
|
|
151
|
+
const remoteUrl = stringField(row.remoteUrl);
|
|
152
|
+
if (remoteUrl !== null)
|
|
153
|
+
parsed.remoteUrl = remoteUrl;
|
|
154
|
+
const gitInitialized = booleanField(row.gitInitialized);
|
|
155
|
+
if (gitInitialized !== null)
|
|
156
|
+
parsed.gitInitialized = gitInitialized;
|
|
157
|
+
return parsed;
|
|
154
158
|
});
|
|
155
159
|
if (parsedSenses.some((row) => row === null) ||
|
|
156
160
|
parsedWorkers.some((row) => row === null) ||
|
|
@@ -318,11 +322,30 @@ function formatDaemonStatusOutput(response, fallback) {
|
|
|
318
322
|
const agentNameWidth = Math.max(12, ...payload.sync.map((r) => r.agent.length));
|
|
319
323
|
for (const row of payload.sync) {
|
|
320
324
|
const name = row.agent.padEnd(agentNameWidth);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
let
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
// Three states for enabled rows: error (not a repo), ok (remote URL), ok (local only).
|
|
326
|
+
// Disabled rows show the usual dim dot + "disabled".
|
|
327
|
+
let dot;
|
|
328
|
+
let stateText;
|
|
329
|
+
let detail;
|
|
330
|
+
if (!row.enabled) {
|
|
331
|
+
dot = dim("○");
|
|
332
|
+
stateText = "disabled".padEnd(10);
|
|
333
|
+
detail = "";
|
|
334
|
+
}
|
|
335
|
+
else if (row.gitInitialized === false) {
|
|
336
|
+
dot = red("●");
|
|
337
|
+
stateText = "error ".padEnd(10);
|
|
338
|
+
detail = "not a git repo — run `git init` to enable sync";
|
|
339
|
+
}
|
|
340
|
+
else if (row.remoteUrl !== undefined) {
|
|
341
|
+
dot = green("●");
|
|
342
|
+
stateText = "enabled ".padEnd(10);
|
|
343
|
+
detail = `${row.remote} → ${row.remoteUrl}`;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
dot = green("●");
|
|
347
|
+
stateText = "enabled ".padEnd(10);
|
|
348
|
+
detail = "local only";
|
|
326
349
|
}
|
|
327
350
|
lines.push(` ${name} ${dot} ${stateText} ${dim(detail)}`);
|
|
328
351
|
}
|
package/dist/heart/sync.js
CHANGED
|
@@ -39,6 +39,21 @@ const child_process_1 = require("child_process");
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
/**
|
|
43
|
+
* Check whether the bundle is initialized as a git repo.
|
|
44
|
+
* Used by both pre-turn pull and post-turn push to surface a clear,
|
|
45
|
+
* actionable error when sync is enabled but the user never ran `git init`
|
|
46
|
+
* inside their bundle. This error is propagated all the way into the
|
|
47
|
+
* agent's start-of-turn packet as a Sync warning, so the agent can
|
|
48
|
+
* either ask the user or run `git init` itself.
|
|
49
|
+
*/
|
|
50
|
+
function ensureGitRepo(agentRoot) {
|
|
51
|
+
if (fs.existsSync(path.join(agentRoot, ".git"))) {
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
54
|
+
const error = `bundle is not a git repo; run \`git init\` inside ${agentRoot} to enable sync (or disable sync in agent.json)`;
|
|
55
|
+
return { ok: false, error };
|
|
56
|
+
}
|
|
42
57
|
/**
|
|
43
58
|
* Pre-turn pull: sync the agent bundle from remote before assembling the start-of-turn packet.
|
|
44
59
|
*
|
|
@@ -54,6 +69,20 @@ function preTurnPull(agentRoot, config) {
|
|
|
54
69
|
message: "pre-turn pull starting",
|
|
55
70
|
meta: { agentRoot, remote: config.remote },
|
|
56
71
|
});
|
|
72
|
+
// Check that the bundle is actually a git repo before touching git at all.
|
|
73
|
+
// Surfaces a clear, actionable error via syncFailure → start-of-turn packet
|
|
74
|
+
// so the agent can propose running `git init` (or just do it).
|
|
75
|
+
const repoCheck = ensureGitRepo(agentRoot);
|
|
76
|
+
if (!repoCheck.ok) {
|
|
77
|
+
(0, runtime_1.emitNervesEvent)({
|
|
78
|
+
level: "warn",
|
|
79
|
+
component: "heart",
|
|
80
|
+
event: "heart.sync_not_a_repo",
|
|
81
|
+
message: "pre-turn pull failed: bundle is not a git repo",
|
|
82
|
+
meta: { agentRoot },
|
|
83
|
+
});
|
|
84
|
+
return repoCheck;
|
|
85
|
+
}
|
|
57
86
|
// Check if any remote is configured. If not, skip the pull (local-only mode).
|
|
58
87
|
try {
|
|
59
88
|
const remoteOutput = (0, child_process_1.execFileSync)("git", ["remote"], {
|
|
@@ -118,6 +147,22 @@ function postTurnPush(agentRoot, config) {
|
|
|
118
147
|
message: "post-turn push starting",
|
|
119
148
|
meta: { agentRoot, remote: config.remote },
|
|
120
149
|
});
|
|
150
|
+
// Same git-repo check as preTurnPull. This is the more common failure path
|
|
151
|
+
// since postTurnPush runs after every turn while preTurnPull only runs on
|
|
152
|
+
// user-initiated turns. Prior to this guard, an un-init'd bundle would fail
|
|
153
|
+
// the git-status invocation below with a generic "not a git repository"
|
|
154
|
+
// error; now we catch it explicitly with an actionable message.
|
|
155
|
+
const repoCheck = ensureGitRepo(agentRoot);
|
|
156
|
+
if (!repoCheck.ok) {
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({
|
|
158
|
+
level: "warn",
|
|
159
|
+
component: "heart",
|
|
160
|
+
event: "heart.sync_not_a_repo",
|
|
161
|
+
message: "post-turn push failed: bundle is not a git repo",
|
|
162
|
+
meta: { agentRoot },
|
|
163
|
+
});
|
|
164
|
+
return repoCheck;
|
|
165
|
+
}
|
|
121
166
|
let statusOutput;
|
|
122
167
|
try {
|
|
123
168
|
statusOutput = (0, child_process_1.execFileSync)("git", ["status", "--porcelain"], {
|