@ouro.bot/cli 0.1.0-alpha.281 → 0.1.0-alpha.283
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 +18 -3
- package/dist/heart/core.js +1 -0
- package/dist/heart/daemon/daemon.js +50 -46
- package/dist/senses/cli.js +6 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
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.283",
|
|
6
|
+
"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` — the two real agents on this machine — remain.",
|
|
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 — the daemon was already filtering them out of `listEnabledBundleAgents`, so deletion is invisible to the runtime but stops confusing anyone inspecting the bundles directory.",
|
|
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` — 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.",
|
|
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 — just reordered steps in `.github/workflows/coverage.yml`."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.282",
|
|
15
|
+
"changes": [
|
|
16
|
+
"feat(cli): crash-resilient sessions \u2014 saves after each tool result, repairs orphaned tool calls on resume."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.281",
|
|
6
21
|
"changes": [
|
|
7
|
-
"feat(bundle): new `src/heart/bundle-state.ts` module exports `detectBundleState(agentRoot)` which returns a structured `BundleStateIssue[]` describing git-level problems the agent can remediate. Enum cases: `not_a_git_repo`, `no_remote_configured`, `first_commit_never_happened`, `pending_sync_exists`. Detection never throws
|
|
8
|
-
"feat(bundle): `StartOfTurnPacket` gains an optional `bundleState?: BundleStateIssue[]` field, populated by the senses pipeline in `handleInboundTurn` at packet assembly time. Renders via a new `case \"bundleState\":` branch in `formatSections` with the `**Bundle:**` prefix, at the same priority tier as the legacy `syncFailure` free-form string (priority 7, truncated last). The two signals coexist during the transition
|
|
22
|
+
"feat(bundle): new `src/heart/bundle-state.ts` module exports `detectBundleState(agentRoot)` which returns a structured `BundleStateIssue[]` describing git-level problems the agent can remediate. Enum cases: `not_a_git_repo`, `no_remote_configured`, `first_commit_never_happened`, `pending_sync_exists`. Detection never throws \u2014 every git call is wrapped in try/catch so a broken bundle degrades to a clear signal rather than crashing the turn pipeline. Also exports `renderBundleStateHint(issues)` which produces first-person remediation guidance (per the memory rule) that tells the agent to call `bundle_check_sync_status` and the `bundle_*` tools shipping in a follow-up PR.",
|
|
23
|
+
"feat(bundle): `StartOfTurnPacket` gains an optional `bundleState?: BundleStateIssue[]` field, populated by the senses pipeline in `handleInboundTurn` at packet assembly time. Renders via a new `case \"bundleState\":` branch in `formatSections` with the `**Bundle:**` prefix, at the same priority tier as the legacy `syncFailure` free-form string (priority 7, truncated last). The two signals coexist during the transition \u2014 `syncFailure` is still emitted by sync.ts for humans, while `bundleState` is the structured form the agent pattern-matches on. Deferred to follow-up: `pending-sync.json` schema extension with `classification` / `conflictFiles` so sync.ts can distinguish push-rejected from pull-rebase-conflict."
|
|
9
24
|
]
|
|
10
25
|
},
|
|
11
26
|
{
|
|
12
27
|
"version": "0.1.0-alpha.280",
|
|
13
28
|
"changes": [
|
|
14
|
-
"chore(tests): prod-path isolation ratchet + tmpBundle leak guard + no-rm-rf contract. Extends `src/__tests__/heart/daemon/test-isolation.contract.test.ts` with three new rules and adds a runtime leak guard. (1) Three new prod-path block rules mirror the existing ~/AgentBundles check: no test file may construct a write path under `~/.ouro-cli`, `~/.agentsecrets`, or `~/.claude` without mocking fs. Each rule has its own empty-seeded ratchet allowlist (except `.ouro-cli` and `.agentsecrets` which are seeded with the pre-existing offenders that this rule newly catches
|
|
29
|
+
"chore(tests): prod-path isolation ratchet + tmpBundle leak guard + no-rm-rf contract. Extends `src/__tests__/heart/daemon/test-isolation.contract.test.ts` with three new rules and adds a runtime leak guard. (1) Three new prod-path block rules mirror the existing ~/AgentBundles check: no test file may construct a write path under `~/.ouro-cli`, `~/.agentsecrets`, or `~/.claude` without mocking fs. Each rule has its own empty-seeded ratchet allowlist (except `.ouro-cli` and `.agentsecrets` which are seeded with the pre-existing offenders that this rule newly catches \u2014 follow-up PRs can convert those to mocked-fs and ratchet down). The path-scan loop is factored into a shared `runProdPathCheck` helper. (2) New Directive-A contract rule: agent-callable production code under `src/` (excluding `src/__tests__/`) must not call `fs.rmSync(..., { recursive: true })` or shell out to `rm -rf` / `rm -fr` / `rm --recursive --force`. The rule is about making deletion auditable and interruptible: an agent should enumerate the files it wants to delete instead of recursively blasting a directory. Four legitimate infrastructure callsites are on `RM_RECURSIVE_ALLOWLIST` with explicit justifications: specialist-tools.ts (adoption rollback), ouro-version-manager.ts (CLI version pruning), ouro-uti.ts (macOS icon pipeline), cli-defaults.ts (self-setup temp dir). Three files that are themselves the rm-rf enforcement layer (guardrails.ts, shell-sessions.ts, prompt.ts) are in `RM_RULE_ENFORCEMENT_FILES` and skipped by the scan since they contain the literal \"rm -rf\" only as regex patterns or prompt strings designed to BLOCK the call. (3) `createTmpBundle` in `src/__tests__/test-helpers/tmpdir-bundle.ts` now tracks live handles in a module-level `_liveHandles: Set<TmpBundleHandle>`. `cleanup()` removes the handle from the set; a new `__getLiveTmpBundleHandles()` export returns a readonly view. (4) `src/__tests__/nerves/global-capture.ts` adds a global vitest `afterEach` leak guard that iterates `__getLiveTmpBundleHandles` and calls `cleanup()` on any remaining handles, logging a `console.warn` naming the test that leaked them. Runs AFTER the pairing guard from alpha.276 so pairing failures surface first. Both guards co-exist cleanly."
|
|
15
30
|
]
|
|
16
31
|
},
|
|
17
32
|
{
|
package/dist/heart/core.js
CHANGED
|
@@ -911,6 +911,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
911
911
|
callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, toolResult, success), success);
|
|
912
912
|
messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
913
913
|
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
914
|
+
callbacks.onToolResult?.(messages);
|
|
914
915
|
}
|
|
915
916
|
}
|
|
916
917
|
}
|
|
@@ -353,6 +353,7 @@ class OuroDaemon {
|
|
|
353
353
|
mode;
|
|
354
354
|
server = null;
|
|
355
355
|
outlookServer = null;
|
|
356
|
+
outlookServerFactory;
|
|
356
357
|
constructor(options) {
|
|
357
358
|
this.socketPath = options.socketPath;
|
|
358
359
|
this.processManager = options.processManager;
|
|
@@ -362,7 +363,42 @@ class OuroDaemon {
|
|
|
362
363
|
this.senseManager = options.senseManager ?? null;
|
|
363
364
|
this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
364
365
|
this.mode = options.mode ?? "production";
|
|
366
|
+
this.outlookServerFactory = options.outlookServerFactory ?? this.createDefaultOutlookServer.bind(this);
|
|
365
367
|
}
|
|
368
|
+
/* v8 ignore start -- default outlook server wiring: production-only path, tests inject outlookServerFactory stub instead. startOutlookHttpServer itself has full coverage in outlook-http.test.ts @preserve */
|
|
369
|
+
createDefaultOutlookServer() {
|
|
370
|
+
return (0, outlook_http_1.startOutlookHttpServer)({
|
|
371
|
+
host: "127.0.0.1",
|
|
372
|
+
port: outlook_types_1.OUTLOOK_DEFAULT_PORT,
|
|
373
|
+
bundlesRoot: this.bundlesRoot,
|
|
374
|
+
readMachineState: () => (0, outlook_read_1.readOutlookMachineState)({ bundlesRoot: this.bundlesRoot }),
|
|
375
|
+
readMachineView: ({ machine }) => {
|
|
376
|
+
const overview = this.buildStatusPayload().overview;
|
|
377
|
+
return (0, outlook_view_1.buildOutlookMachineView)({
|
|
378
|
+
machine,
|
|
379
|
+
daemon: {
|
|
380
|
+
status: overview.daemon,
|
|
381
|
+
health: overview.health,
|
|
382
|
+
mode: overview.mode,
|
|
383
|
+
socketPath: overview.socketPath,
|
|
384
|
+
outlookUrl: overview.outlookUrl,
|
|
385
|
+
entryPath: overview.entryPath,
|
|
386
|
+
workerCount: overview.workerCount,
|
|
387
|
+
senseCount: overview.senseCount,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
},
|
|
391
|
+
readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
|
|
392
|
+
readAgentView: (agentName) => {
|
|
393
|
+
const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
|
|
394
|
+
return (0, outlook_view_1.buildOutlookAgentView)({
|
|
395
|
+
agent,
|
|
396
|
+
viewer: { kind: "human" },
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/* v8 ignore stop */
|
|
366
402
|
buildStatusPayload() {
|
|
367
403
|
const snapshots = this.processManager.listAgentSnapshots();
|
|
368
404
|
const workers = buildWorkerRows(snapshots);
|
|
@@ -513,51 +549,21 @@ class OuroDaemon {
|
|
|
513
549
|
await this.scheduler.reconcile?.();
|
|
514
550
|
await this.drainPendingBundleMessages();
|
|
515
551
|
await this.drainPendingSenseMessages();
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
health: overview.health,
|
|
531
|
-
mode: overview.mode,
|
|
532
|
-
socketPath: overview.socketPath,
|
|
533
|
-
outlookUrl: overview.outlookUrl,
|
|
534
|
-
entryPath: overview.entryPath,
|
|
535
|
-
workerCount: overview.workerCount,
|
|
536
|
-
senseCount: overview.senseCount,
|
|
537
|
-
},
|
|
538
|
-
});
|
|
539
|
-
},
|
|
540
|
-
readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
|
|
541
|
-
readAgentView: (agentName) => {
|
|
542
|
-
const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
|
|
543
|
-
return (0, outlook_view_1.buildOutlookAgentView)({
|
|
544
|
-
agent,
|
|
545
|
-
viewer: { kind: "human" },
|
|
546
|
-
});
|
|
547
|
-
},
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
catch (error) {
|
|
551
|
-
(0, runtime_1.emitNervesEvent)({
|
|
552
|
-
level: "warn",
|
|
553
|
-
component: "daemon",
|
|
554
|
-
event: "daemon.outlook_start_failed",
|
|
555
|
-
message: `Outlook server failed to start: ${String(error)}`,
|
|
556
|
-
meta: { port: outlook_types_1.OUTLOOK_DEFAULT_PORT },
|
|
557
|
-
});
|
|
558
|
-
}
|
|
552
|
+
// startInner is only reachable when this.server is null (guarded in
|
|
553
|
+
// start()), and stop() nulls out this.outlookServer alongside this.server,
|
|
554
|
+
// so outlookServer is guaranteed unset here — no need for a guard.
|
|
555
|
+
try {
|
|
556
|
+
this.outlookServer = await this.outlookServerFactory();
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
(0, runtime_1.emitNervesEvent)({
|
|
560
|
+
level: "warn",
|
|
561
|
+
component: "daemon",
|
|
562
|
+
event: "daemon.outlook_start_failed",
|
|
563
|
+
message: `Outlook server failed to start: ${String(error)}`,
|
|
564
|
+
meta: { port: outlook_types_1.OUTLOOK_DEFAULT_PORT },
|
|
565
|
+
});
|
|
559
566
|
}
|
|
560
|
-
/* v8 ignore stop */
|
|
561
567
|
if (fs.existsSync(this.socketPath)) {
|
|
562
568
|
fs.unlinkSync(this.socketPath);
|
|
563
569
|
}
|
|
@@ -815,12 +821,10 @@ class OuroDaemon {
|
|
|
815
821
|
this.server.close();
|
|
816
822
|
this.server = null;
|
|
817
823
|
}
|
|
818
|
-
/* v8 ignore start -- outlookServer stop: only reachable when the outlook HTTP server successfully bound to port 6876 during start(). Coverage fluctuates locally because a running production daemon holds the port and tests get EADDRINUSE, leaving outlookServer null. In CI the port is free and this block runs. Not worth fighting port contention to cover two trivial lines. @preserve */
|
|
819
824
|
if (this.outlookServer) {
|
|
820
825
|
await this.outlookServer.stop();
|
|
821
826
|
this.outlookServer = null;
|
|
822
827
|
}
|
|
823
|
-
/* v8 ignore stop */
|
|
824
828
|
if (fs.existsSync(this.socketPath)) {
|
|
825
829
|
fs.unlinkSync(this.socketPath);
|
|
826
830
|
}
|
package/dist/senses/cli.js
CHANGED
|
@@ -976,6 +976,8 @@ async function main(agentName, options) {
|
|
|
976
976
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
977
977
|
? existing.messages
|
|
978
978
|
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", {}, resolvedContext) }];
|
|
979
|
+
// Repair any orphaned tool calls from a crash mid-turn
|
|
980
|
+
(0, core_1.repairOrphanedToolCalls)(sessionMessages);
|
|
979
981
|
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
980
982
|
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
981
983
|
const currentAgentName = (0, identity_1.getAgentName)();
|
|
@@ -1002,6 +1004,10 @@ async function main(agentName, options) {
|
|
|
1002
1004
|
/* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
|
|
1003
1005
|
const failoverAwareCallbacks = {
|
|
1004
1006
|
...callbacks,
|
|
1007
|
+
// Save session after each tool result for crash recovery
|
|
1008
|
+
onToolResult: (turnMessages) => {
|
|
1009
|
+
(0, context_1.postTurn)(turnMessages, sessPath, undefined, undefined, sessionState);
|
|
1010
|
+
},
|
|
1005
1011
|
onError: (error, severity) => {
|
|
1006
1012
|
if (severity === "terminal" && failoverState) {
|
|
1007
1013
|
capturedTerminalError = error;
|