@ouro.bot/cli 0.1.0-alpha.283 → 0.1.0-alpha.285
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
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
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.285",
|
|
6
|
+
"changes": [
|
|
7
|
+
"feat(daemon): wire startPeriodicReconciliation() after scheduler.start() in daemon-entry.ts — prevents silent habit death when OS cron fails by giving the daemon a self-healing reconciliation loop.",
|
|
8
|
+
"feat(guardrails): protect agent.json from agent self-modification — closes a vector where prompt injection could alter the agent's own identity, provider, or model settings.",
|
|
9
|
+
"feat(habits): add tools field to HabitFile interface and habit-parser — habits can now declare tools: [read, web_fetch] in frontmatter. Schema-only; runtime enforcement ships in a follow-up PR."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.284",
|
|
14
|
+
"changes": [
|
|
15
|
+
"fix(tui): Ctrl+C safety net + spinner 120ms to 300ms."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.283",
|
|
6
20
|
"changes": [
|
|
7
|
-
"chore(housekeeping): clean up ~3600 leaked test secret dirs in `~/.agentsecrets/`. The auth CLI test suite was creating ephemeral agent secret dirs (auth-local-*, auth-store-*, auth-no-switch-*, etc.) without cleaning them up, accreting over weeks into thousands of orphaned entries. Also purged three non-test orphans (`testagent`, `model-reviews`, `config-model-facing-*`) that were leftovers from manual probe sessions. Only `slugger` and `ouroboros`
|
|
8
|
-
"chore(housekeeping): remove orphan bundle stubs from `~/AgentBundles/` (default.ouro, thoughts-test-*, an empty `friends/` dir, and a .DS_Store). These were skeletal test leftovers with no real identity
|
|
9
|
-
"chore(tests): replace the `outlookServer.stop` v8 ignore band-aid in `daemon.ts` with a proper test. `OuroDaemonOptions` gains an `outlookServerFactory` seam that lets tests inject an in-memory stub handle instead of binding port 6876 (which a running production daemon holds on dev machines, causing EADDRINUSE flakes). New `daemon-outlook-lifecycle.test.ts` covers the happy path (factory runs, stop is called), the error path (factory throws, daemon logs warn and keeps going, stop is a no-op), and the double-start guard. The default production factory path (`createDefaultOutlookServer`
|
|
10
|
-
"chore(ci): tighten the wrapper publish-sync check timing. The version-bump check and wrapper-publish-sync check now run BEFORE the ~2+ minute coverage gate, not after. Previously, a contributor would wait for the full test suite to pass only to then see a 'version already published' error. Now the fast checks surface in under a minute, and coverage only runs once version/wrapper gates have cleared. Same checks, same logic
|
|
21
|
+
"chore(housekeeping): clean up ~3600 leaked test secret dirs in `~/.agentsecrets/`. The auth CLI test suite was creating ephemeral agent secret dirs (auth-local-*, auth-store-*, auth-no-switch-*, etc.) without cleaning them up, accreting over weeks into thousands of orphaned entries. Also purged three non-test orphans (`testagent`, `model-reviews`, `config-model-facing-*`) that were leftovers from manual probe sessions. Only `slugger` and `ouroboros` \u2014 the two real agents on this machine \u2014 remain.",
|
|
22
|
+
"chore(housekeeping): remove orphan bundle stubs from `~/AgentBundles/` (default.ouro, thoughts-test-*, an empty `friends/` dir, and a .DS_Store). These were skeletal test leftovers with no real identity \u2014 the daemon was already filtering them out of `listEnabledBundleAgents`, so deletion is invisible to the runtime but stops confusing anyone inspecting the bundles directory.",
|
|
23
|
+
"chore(tests): replace the `outlookServer.stop` v8 ignore band-aid in `daemon.ts` with a proper test. `OuroDaemonOptions` gains an `outlookServerFactory` seam that lets tests inject an in-memory stub handle instead of binding port 6876 (which a running production daemon holds on dev machines, causing EADDRINUSE flakes). New `daemon-outlook-lifecycle.test.ts` covers the happy path (factory runs, stop is called), the error path (factory throws, daemon logs warn and keeps going, stop is a no-op), and the double-start guard. The default production factory path (`createDefaultOutlookServer` \u2014 wires the real `startOutlookHttpServer` with bundlesRoot + view builders) is v8-ignored because it only runs under real bind on port 6876; `startOutlookHttpServer` itself has full coverage in `outlook-http.test.ts`. Removed the redundant `if (!this.outlookServer)` wrapper in `startInner()` that was guarding against an unreachable retry scenario. `daemon.ts` now hits 100% statements / branches / functions / lines with zero port binding in the test suite.",
|
|
24
|
+
"chore(ci): tighten the wrapper publish-sync check timing. The version-bump check and wrapper-publish-sync check now run BEFORE the ~2+ minute coverage gate, not after. Previously, a contributor would wait for the full test suite to pass only to then see a 'version already published' error. Now the fast checks surface in under a minute, and coverage only runs once version/wrapper gates have cleared. Same checks, same logic \u2014 just reordered steps in `.github/workflows/coverage.yml`."
|
|
11
25
|
]
|
|
12
26
|
},
|
|
13
27
|
{
|
|
@@ -41,6 +41,22 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
41
41
|
function isHabitStatus(value) {
|
|
42
42
|
return value === "active" || value === "paused";
|
|
43
43
|
}
|
|
44
|
+
function parseToolsField(raw) {
|
|
45
|
+
if (raw === undefined || raw === null)
|
|
46
|
+
return undefined;
|
|
47
|
+
// YAML dash-list: parseFrontmatter returns unknown[]
|
|
48
|
+
if (Array.isArray(raw)) {
|
|
49
|
+
return raw.filter((item) => typeof item === "string");
|
|
50
|
+
}
|
|
51
|
+
// Inline bracket format: parseFrontmatter returns string like "[a, b, c]"
|
|
52
|
+
if (typeof raw === "string" && raw.startsWith("[") && raw.endsWith("]")) {
|
|
53
|
+
const inner = raw.slice(1, -1);
|
|
54
|
+
if (inner.trim().length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
return inner.split(",").map((s) => s.trim()).filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
44
60
|
function extractFrontmatterAndBody(content) {
|
|
45
61
|
const lines = content.split(/\r?\n/);
|
|
46
62
|
if (lines[0]?.trim() !== "---") {
|
|
@@ -71,6 +87,7 @@ function parseHabitFile(content, filePath) {
|
|
|
71
87
|
status: "active",
|
|
72
88
|
lastRun: null,
|
|
73
89
|
created: null,
|
|
90
|
+
tools: undefined,
|
|
74
91
|
body: content.trim(),
|
|
75
92
|
};
|
|
76
93
|
}
|
|
@@ -85,6 +102,7 @@ function parseHabitFile(content, filePath) {
|
|
|
85
102
|
const lastRun = typeof rawLastRun === "string" && rawLastRun.length > 0 ? rawLastRun : null;
|
|
86
103
|
const rawCreated = frontmatter.created;
|
|
87
104
|
const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
|
|
105
|
+
const tools = parseToolsField(frontmatter.tools);
|
|
88
106
|
return {
|
|
89
107
|
name: stem,
|
|
90
108
|
title,
|
|
@@ -92,12 +110,15 @@ function parseHabitFile(content, filePath) {
|
|
|
92
110
|
status,
|
|
93
111
|
lastRun,
|
|
94
112
|
created,
|
|
113
|
+
tools,
|
|
95
114
|
body,
|
|
96
115
|
};
|
|
97
116
|
}
|
|
98
117
|
function formatFrontmatterValue(value) {
|
|
99
118
|
if (value === null || value === undefined)
|
|
100
119
|
return "null";
|
|
120
|
+
if (Array.isArray(value))
|
|
121
|
+
return `[${value.join(", ")}]`;
|
|
101
122
|
return String(value);
|
|
102
123
|
}
|
|
103
124
|
function renderHabitFile(frontmatter, body) {
|
|
@@ -37,6 +37,7 @@ exports.OURO_CLI_TRUST_MANIFEST = void 0;
|
|
|
37
37
|
exports.guardInvocation = guardInvocation;
|
|
38
38
|
const fs = __importStar(require("node:fs"));
|
|
39
39
|
const os = __importStar(require("node:os"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
40
41
|
const types_1 = require("../mind/friends/types");
|
|
41
42
|
const runtime_1 = require("../nerves/runtime");
|
|
42
43
|
const deny = (reason) => ({ allowed: false, reason });
|
|
@@ -56,6 +57,7 @@ const REASONS = {
|
|
|
56
57
|
const READ_ONLY_TOOLS = new Set(["read_file", "glob", "grep"]);
|
|
57
58
|
// --- protected path detection ---
|
|
58
59
|
const PROTECTED_PATH_SEGMENTS = [".git/"];
|
|
60
|
+
const PROTECTED_FILENAMES = ["agent.json"];
|
|
59
61
|
function getProtectedAbsolutePrefixes() {
|
|
60
62
|
return [`${os.homedir()}/.agentsecrets/`];
|
|
61
63
|
}
|
|
@@ -68,6 +70,10 @@ function isProtectedPath(filePath) {
|
|
|
68
70
|
if (filePath.startsWith(prefix))
|
|
69
71
|
return true;
|
|
70
72
|
}
|
|
73
|
+
for (const name of PROTECTED_FILENAMES) {
|
|
74
|
+
if (path.basename(filePath) === name)
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
71
77
|
return false;
|
|
72
78
|
}
|
|
73
79
|
// --- destructive shell patterns ---
|
|
@@ -103,7 +103,7 @@ function Spinner({ phrasePool, elapsed }) {
|
|
|
103
103
|
const [currentPhrase, setCurrentPhrase] = (0, react_1.useState)(() => phrasePool.length > 0 ? phrasePool[Math.floor(Math.random() * phrasePool.length)] : "");
|
|
104
104
|
// Animate ring frames
|
|
105
105
|
(0, react_1.useEffect)(() => {
|
|
106
|
-
const iv = setInterval(() => setFrame(f => (f + 1) % RING_FRAMES.length),
|
|
106
|
+
const iv = setInterval(() => setFrame(f => (f + 1) % RING_FRAMES.length), 300);
|
|
107
107
|
return () => clearInterval(iv);
|
|
108
108
|
}, []);
|
|
109
109
|
// Rotate phrases every 3 seconds
|
package/dist/senses/cli.js
CHANGED
|
@@ -929,6 +929,19 @@ async function main(agentName, options) {
|
|
|
929
929
|
if (agentName)
|
|
930
930
|
(0, identity_1.setAgentName)(agentName);
|
|
931
931
|
const pasteDebounceMs = options?.pasteDebounceMs ?? 50;
|
|
932
|
+
// Safety net: process-level SIGINT handler ensures Ctrl+C always exits,
|
|
933
|
+
// even when Ink's event loop is blocked by expensive renders.
|
|
934
|
+
/* v8 ignore start -- process signal handler @preserve */
|
|
935
|
+
let sigintCount = 0;
|
|
936
|
+
const sigintHandler = () => {
|
|
937
|
+
sigintCount++;
|
|
938
|
+
if (sigintCount >= 2)
|
|
939
|
+
process.exit(1);
|
|
940
|
+
};
|
|
941
|
+
if (!options?._testInputSource) {
|
|
942
|
+
process.on("SIGINT", sigintHandler);
|
|
943
|
+
}
|
|
944
|
+
/* v8 ignore stop */
|
|
932
945
|
// Register spinner hooks so log output clears the spinner before printing
|
|
933
946
|
(0, nerves_1.registerSpinnerHooks)(pauseActiveSpinner, resumeActiveSpinner);
|
|
934
947
|
// Fallback: apply pending updates for daemon-less direct CLI usage
|