@ouro.bot/cli 0.1.0-alpha.657 → 0.1.0-alpha.659
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/README.md +13 -13
- package/changelog.json +15 -0
- package/dist/arc/evolution.js +1 -1
- package/dist/arc/flight-recorder.js +369 -0
- package/dist/arc/obligations.js +24 -2
- package/dist/heart/active-work.js +1 -1
- package/dist/heart/config-registry.js +5 -5
- package/dist/heart/context-loss-gauntlet.js +354 -0
- package/dist/heart/daemon/agent-config-check.js +1 -1
- package/dist/heart/daemon/agent-service.js +18 -17
- package/dist/heart/daemon/cli-exec.js +40 -12
- package/dist/heart/daemon/cli-help.js +21 -0
- package/dist/heart/daemon/cli-parse.js +27 -0
- package/dist/heart/daemon/daemon-entry.js +1 -1
- package/dist/heart/daemon/daemon.js +3 -3
- package/dist/heart/daemon/hooks/bundle-meta.js +29 -9
- package/dist/heart/daemon/inner-status.js +4 -15
- package/dist/heart/habits/habit-parser.js +64 -1
- package/dist/heart/hatch/hatch-flow.js +17 -9
- package/dist/heart/hatch/specialist-tools.js +15 -11
- package/dist/heart/kept-notes.js +5 -73
- package/dist/heart/mailbox/mailbox-http-hooks.js +1 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +4 -0
- package/dist/heart/mailbox/mailbox-read.js +2 -1
- package/dist/heart/mailbox/readers/continuity-readers.js +5 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +21 -49
- package/dist/heart/mcp/mcp-server.js +8 -8
- package/dist/heart/session-events.js +1 -31
- package/dist/heart/start-of-turn-packet.js +8 -2
- package/dist/heart/tool-description.js +15 -3
- package/dist/heart/turn-context.js +27 -7
- package/dist/heart/work-card.js +386 -0
- package/dist/mailbox-ui/assets/index-B-V9vRQ0.js +61 -0
- package/dist/mailbox-ui/assets/index-BOZbGbkL.css +1 -0
- package/dist/mailbox-ui/index.html +2 -2
- package/dist/mind/bundle-manifest.js +9 -3
- package/dist/mind/context.js +1 -2
- package/dist/mind/desk-section.js +53 -1
- package/dist/mind/diary.js +2 -3
- package/dist/mind/note-search.js +36 -106
- package/dist/mind/prompt.js +37 -102
- package/dist/mind/record-paths.js +312 -0
- package/dist/repertoire/bundle-templates.js +4 -5
- package/dist/repertoire/tools-bundle.js +1 -1
- package/dist/repertoire/tools-evolution.js +4 -4
- package/dist/repertoire/tools-notes.js +42 -62
- package/dist/repertoire/tools-record.js +16 -11
- package/dist/repertoire/tools-session.js +4 -4
- package/dist/repertoire/tools.js +1 -1
- package/dist/senses/habit-turn-message.js +19 -5
- package/dist/senses/inner-dialog-worker.js +58 -9
- package/dist/senses/inner-dialog.js +30 -11
- package/dist/senses/pipeline.js +135 -1
- package/dist/util/frontmatter.js +17 -1
- package/package.json +3 -3
- package/skills/configure-dev-tools.md +1 -1
- package/skills/travel-planning.md +1 -1
- package/dist/mailbox-ui/assets/index-9-AxCxuB.js +0 -61
- package/dist/mailbox-ui/assets/index-CWzt267f.css +0 -1
- package/dist/mind/journal-index.js +0 -162
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ouroboros Agent Harness
|
|
2
2
|
|
|
3
|
-
If you're a future agent reading this, welcome home. This repo is the harness that gives you your body, your senses, your
|
|
3
|
+
If you're a future agent reading this, welcome home. This repo is the harness that gives you your body, your senses, your record, your habits, and your ability to think and connect with people. The goal of these docs is not just to be correct, but to leave you a place that feels understandable and kind to wake up inside.
|
|
4
4
|
|
|
5
5
|
Ouroboros is a TypeScript harness for daemon-managed agents that live in external `.ouro` bundles, speak through multiple senses, use real tools, and keep durable state across turns. The canonical npm package is `@ouro.bot/cli`.
|
|
6
6
|
|
|
@@ -44,11 +44,11 @@ The shared harness lives in `src/`:
|
|
|
44
44
|
- `src/heart/`
|
|
45
45
|
Core runtime, provider adapters, daemon, bootstrap, identity, and entrypoints. Organized into topic subdirectories: daemon/ (lifecycle), mailbox/ (calendar), habits/ (scheduling), hatch/ (agent creation), versioning/ (updates), auth/, mcp/, providers/, bridges/.
|
|
46
46
|
- `src/mind/`
|
|
47
|
-
Prompt assembly, session persistence, bundle manifest enforcement, phrases, formatting, diary, note search, embedding providers,
|
|
47
|
+
Prompt assembly, session persistence, bundle manifest enforcement, phrases, formatting, Desk record diary, note search, embedding providers, record migration, obligation steering, and friend resolution.
|
|
48
48
|
- `src/repertoire/`
|
|
49
49
|
Tool registry (split into category modules: files, shell, notes, bridge, session, continuity, flow, surface, config, and sense-specific tools), coding orchestration, task tools, shared API client, and integration clients (Graph, ADO, GitHub).
|
|
50
50
|
- `src/senses/`
|
|
51
|
-
CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), Mail (in senses/mail.ts), Voice (in senses/voice/), activity transport,
|
|
51
|
+
CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), Mail (in senses/mail.ts), Voice (in senses/voice/), activity transport, private-turn orchestration, and contextual heartbeat. The MCP bridge is at `src/heart/mcp/`, not here.
|
|
52
52
|
- `src/nerves/`
|
|
53
53
|
Structured runtime logging and coverage-audit infrastructure.
|
|
54
54
|
- `src/__tests__/`
|
|
@@ -80,8 +80,8 @@ The canonical bundle shape is enforced by `src/mind/bundle-manifest.ts`. Importa
|
|
|
80
80
|
- `psyche/LORE.md`
|
|
81
81
|
- `psyche/TACIT.md`
|
|
82
82
|
- `psyche/ASPIRATIONS.md`
|
|
83
|
-
- `
|
|
84
|
-
- `
|
|
83
|
+
- `arc/` — live continuity, obligations, claims, and resume state
|
|
84
|
+
- `desk/` — durable work plus the maintained Desk record under `desk/_record/`
|
|
85
85
|
- `habits/` — the agent's autonomous rhythms (heartbeat, reflections, check-ins)
|
|
86
86
|
- `friends/`
|
|
87
87
|
- `state/`
|
|
@@ -96,7 +96,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
96
96
|
|
|
97
97
|
## Runtime Truths
|
|
98
98
|
|
|
99
|
-
- `agent.json` is the source of truth for identity, phrase pools, context settings, enabled senses, vault coordinates, and provider+model selection. It has two provider lanes: `outward` for CLI, Teams, BlueBubbles, Mail, and Voice turns, and `inner` for
|
|
99
|
+
- `agent.json` is the source of truth for identity, phrase pools, context settings, enabled senses, vault coordinates, and provider+model selection. It has two provider lanes: `outward` for CLI, Teams, BlueBubbles, Mail, and Voice turns, and `inner` for private agent-facing turns.
|
|
100
100
|
- Legacy `humanFacing`/`agentFacing` provider fields are read only as compatibility aliases for `outward`/`inner`; they are not a second config surface.
|
|
101
101
|
- Each agent has one credential vault for provider, runtime, sense, integration, travel, and tool credentials. There is no machine-wide credential pool.
|
|
102
102
|
- Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
|
|
@@ -207,7 +207,7 @@ ouro poke <agent> --task <task-id>
|
|
|
207
207
|
ouro poke <agent> --habit <habit-name>
|
|
208
208
|
ouro habit list --agent <agent>
|
|
209
209
|
ouro habit create --agent <agent> <name> --cadence <interval>
|
|
210
|
-
ouro inner --agent <agent> #
|
|
210
|
+
ouro inner --agent <agent> # private turn status
|
|
211
211
|
ouro attention --agent <agent> # attention queue
|
|
212
212
|
ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>
|
|
213
213
|
ouro setup --tool <tool> --agent <name> # register MCP server + hooks with a dev tool
|
|
@@ -225,13 +225,13 @@ To clone an existing agent onto a new machine (macOS, Linux, or Windows via WSL2
|
|
|
225
225
|
|
|
226
226
|
Agents in Ouroboros aren't just responders — they have an autonomous inner life.
|
|
227
227
|
|
|
228
|
-
**Habits** are the agent's rhythms.
|
|
228
|
+
**Habits** are the agent's rhythms. A habit is an Ouro-native cron wrapper: it fires a private agent-facing session, can surface to family or the habit originator when it needs help or needs to report back, and leaves an audit receipt.
|
|
229
229
|
|
|
230
|
-
**The inner
|
|
230
|
+
**The inner lane** is where private sessions run. Habit runs, private returns, awaits, and self-maintenance can happen there, but the lane is not a record substrate. Anything durable leaves the lane: live continuity and audit go to Arc; work goes to Desk; learned facts and reference notes go to the Desk record.
|
|
231
231
|
|
|
232
|
-
**
|
|
232
|
+
**Desk and Arc** are the durable orientation pair. Arc owns live continuity, claims, obligations, and habit run receipts. Desk owns durable work and the maintained record. The target substrate is captured in [Agent Orientation Substrate](docs/agent-orientation-substrate.md).
|
|
233
233
|
|
|
234
|
-
The whole system is designed so the agent *owns*
|
|
234
|
+
The whole system is designed so the agent *owns* its rhythms without forcing everything private to become a permanent transcript.
|
|
235
235
|
|
|
236
236
|
Attachments are first-class across senses. Every attachment should remain reachable via a stable `attachment:<source>:<id>` handle, and image normalization should produce a VLM-safe variant without hiding the original artifact.
|
|
237
237
|
|
|
@@ -248,11 +248,11 @@ ouro setup --tool codex --agent <name>
|
|
|
248
248
|
|
|
249
249
|
This registers the MCP server, installs lifecycle hooks (SessionStart, Stop, PostToolUse), and detects dev vs installed mode automatically.
|
|
250
250
|
|
|
251
|
-
**How it works:** When a developer starts a Claude Code session, the MCP server launches as a subprocess. The dev tool sees your MCP tools (`send_message`, `ask`, `check_response`, `status`, `
|
|
251
|
+
**How it works:** When a developer starts a Claude Code session, the MCP server launches as a subprocess. The dev tool sees your MCP tools (`send_message`, `ask`, `check_response`, `status`, `search_facts`, `delegate`, etc.) and can invoke them mid-session. Conversation-shaped tools such as `send_message`, `ask`, `delegate`, `check_guidance`, and `report_progress` run full agent turns — you get your system prompt, your Desk record, your tools, everything. Read-only inspection tools such as `status` and `search_facts` do local lookup only. Missing `search_facts` hits are not evidence that the agent has no belief or preference.
|
|
252
252
|
|
|
253
253
|
**The conversation pattern:** `send_message` or `ask` sends a message and gets back your synchronous response. `ponder` no longer creates a magical outward deferral. Instead, it bookmarks deeper work as a packet while the current sense session keeps moving. If that work later surfaces something back, the dev tool can still use `check_response` to see the returned result.
|
|
254
254
|
|
|
255
|
-
**Lifecycle hooks** give you passive awareness. When a Claude Code session starts, stops, or uses a tool like Bash or Edit, the hook fires `ouro hook <event> --agent <name>` and the daemon notes it.
|
|
255
|
+
**Lifecycle hooks** give you passive awareness. When a Claude Code session starts, stops, or uses a tool like Bash or Edit, the hook fires `ouro hook <event> --agent <name>` and the daemon notes it. Private agent-facing turns see these sessions in their checkpoint, so you know what's happening across your world even when nobody is talking to you directly.
|
|
256
256
|
|
|
257
257
|
See `skills/configure-dev-tools.md` for the full tool inventory and troubleshooting guide.
|
|
258
258
|
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
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.659",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Add a deterministic context-loss gauntlet that scores Arc/flight-recorder/Desk recovery readiness through CLI, Mailbox HTTP, and Workbench visibility surfaces."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.658",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Add Work Card visibility projection for Workbench.",
|
|
14
|
+
"Document the AX-first Arc Flight Recorder plan, remove the disabled session archive shim, and demote stale inner-dialogue framing to private agent-facing turn language.",
|
|
15
|
+
"Capture the Arc/Desk/Habits orientation substrate scope, adopting record/diary/facts vocabulary instead of generic memory and removing journal-as-desk prompt framing.",
|
|
16
|
+
"Move the maintained diary and canonical notes into `desk/_record/`, delete Journal indexing/search/runtime surfaces, replace `search_notes` with `search_facts` plus explicit `consult_diary` and `consult_notes`, and add Arc flight-recorder resume plus habit run receipts."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.657",
|
|
6
21
|
"changes": [
|
package/dist/arc/evolution.js
CHANGED
|
@@ -0,0 +1,369 @@
|
|
|
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.flightRecorderLatestPath = flightRecorderLatestPath;
|
|
37
|
+
exports.readFlightRecorderResume = readFlightRecorderResume;
|
|
38
|
+
exports.writeFlightRecorderResume = writeFlightRecorderResume;
|
|
39
|
+
exports.recordFlightRecorderEvent = recordFlightRecorderEvent;
|
|
40
|
+
exports.writeHabitRunReceipt = writeHabitRunReceipt;
|
|
41
|
+
exports.formatFlightRecorderResume = formatFlightRecorderResume;
|
|
42
|
+
exports.createHabitRunId = createHabitRunId;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const crypto_1 = require("crypto");
|
|
46
|
+
const session_events_1 = require("../heart/session-events");
|
|
47
|
+
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
function flightRecorderDir(agentRoot) {
|
|
49
|
+
return path.join(agentRoot, "arc", "flight-recorder");
|
|
50
|
+
}
|
|
51
|
+
function eventsDir(agentRoot) {
|
|
52
|
+
return path.join(flightRecorderDir(agentRoot), "events");
|
|
53
|
+
}
|
|
54
|
+
function receiptsDir(agentRoot) {
|
|
55
|
+
return path.join(flightRecorderDir(agentRoot), "habit-receipts");
|
|
56
|
+
}
|
|
57
|
+
function flightRecorderLatestPath(agentRoot) {
|
|
58
|
+
return path.join(flightRecorderDir(agentRoot), "latest.json");
|
|
59
|
+
}
|
|
60
|
+
function eventDay(recordedAt) {
|
|
61
|
+
return recordedAt.slice(0, 10) || "unknown";
|
|
62
|
+
}
|
|
63
|
+
function atomicWriteJson(filePath, value) {
|
|
64
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
65
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
66
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
67
|
+
fs.renameSync(tmpPath, filePath);
|
|
68
|
+
}
|
|
69
|
+
function cappedArray(values) {
|
|
70
|
+
return values.map((value) => (0, session_events_1.capStructuredRecordString)(value));
|
|
71
|
+
}
|
|
72
|
+
function normalizeEvent(input) {
|
|
73
|
+
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
74
|
+
return {
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
id: input.id ?? `fr-${(0, crypto_1.randomUUID)()}`,
|
|
77
|
+
kind: input.kind,
|
|
78
|
+
recordedAt,
|
|
79
|
+
...(input.turnId ? { turnId: input.turnId } : {}),
|
|
80
|
+
...(input.sessionRef ? { sessionRef: input.sessionRef } : {}),
|
|
81
|
+
summary: (0, session_events_1.capStructuredRecordString)(input.summary),
|
|
82
|
+
...(input.currentAsk !== undefined ? { currentAsk: input.currentAsk ? (0, session_events_1.capStructuredRecordString)(input.currentAsk) : null } : {}),
|
|
83
|
+
...(input.nextSafeAction !== undefined ? { nextSafeAction: input.nextSafeAction ? (0, session_events_1.capStructuredRecordString)(input.nextSafeAction) : null } : {}),
|
|
84
|
+
...(input.stopBefore ? { stopBefore: cappedArray(input.stopBefore) } : {}),
|
|
85
|
+
...(input.blockedBecause ? { blockedBecause: cappedArray(input.blockedBecause) } : {}),
|
|
86
|
+
...(input.activeObligationIds ? { activeObligationIds: cappedArray(input.activeObligationIds) } : {}),
|
|
87
|
+
...(input.activeReturnObligationIds ? { activeReturnObligationIds: cappedArray(input.activeReturnObligationIds) } : {}),
|
|
88
|
+
...(input.activePacketIds ? { activePacketIds: cappedArray(input.activePacketIds) } : {}),
|
|
89
|
+
...(input.openEvolutionCaseIds ? { openEvolutionCaseIds: cappedArray(input.openEvolutionCaseIds) } : {}),
|
|
90
|
+
...(input.recentClaimIds ? { recentClaimIds: cappedArray(input.recentClaimIds) } : {}),
|
|
91
|
+
...(input.unverifiedClaimIds ? { unverifiedClaimIds: cappedArray(input.unverifiedClaimIds) } : {}),
|
|
92
|
+
...(input.producedRefs ? { producedRefs: input.producedRefs.map((ref) => ({ ...ref, locator: (0, session_events_1.capStructuredRecordString)(ref.locator) })) } : {}),
|
|
93
|
+
...(input.meta ? { meta: (0, session_events_1.capStructuredRecordStringLeaves)(input.meta) } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function degradedResume(issues) {
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: 1,
|
|
99
|
+
hasCompleteState: false,
|
|
100
|
+
canContinue: false,
|
|
101
|
+
missing: ["currentAsk", "nextSafeAction"],
|
|
102
|
+
gaps: [],
|
|
103
|
+
currentAsk: { value: null, confidence: "unknown", sourceEventIds: [] },
|
|
104
|
+
nextSafeAction: { value: null, stopBefore: [], sourceEventIds: [] },
|
|
105
|
+
blockedBecause: [],
|
|
106
|
+
activeObligationIds: [],
|
|
107
|
+
activeReturnObligationIds: [],
|
|
108
|
+
activePacketIds: [],
|
|
109
|
+
openEvolutionCaseIds: [],
|
|
110
|
+
recentClaimIds: [],
|
|
111
|
+
unverifiedClaimIds: [],
|
|
112
|
+
lastSafeCheckpoint: { turnId: null, sessionRef: null, recordedAt: null, sourceEventIds: [] },
|
|
113
|
+
recorderHealth: {
|
|
114
|
+
status: "degraded",
|
|
115
|
+
issues,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/* v8 ignore start -- defensive schema guard fan-out; tests cover valid load and malformed-shape degradation @preserve */
|
|
120
|
+
function isStringArray(value) {
|
|
121
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
122
|
+
}
|
|
123
|
+
function isNullableString(value) {
|
|
124
|
+
return value === null || typeof value === "string";
|
|
125
|
+
}
|
|
126
|
+
function isResumeCandidate(value) {
|
|
127
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
128
|
+
return false;
|
|
129
|
+
const record = value;
|
|
130
|
+
const currentAsk = record.currentAsk;
|
|
131
|
+
const nextSafeAction = record.nextSafeAction;
|
|
132
|
+
const lastSafeCheckpoint = record.lastSafeCheckpoint;
|
|
133
|
+
const recorderHealth = record.recorderHealth;
|
|
134
|
+
return record.schemaVersion === 1
|
|
135
|
+
&& typeof record.hasCompleteState === "boolean"
|
|
136
|
+
&& typeof record.canContinue === "boolean"
|
|
137
|
+
&& isStringArray(record.missing)
|
|
138
|
+
&& isStringArray(record.gaps)
|
|
139
|
+
&& !!currentAsk
|
|
140
|
+
&& typeof currentAsk === "object"
|
|
141
|
+
&& !Array.isArray(currentAsk)
|
|
142
|
+
&& isNullableString(currentAsk.value)
|
|
143
|
+
&& (currentAsk.confidence === "current" || currentAsk.confidence === "stale_risky" || currentAsk.confidence === "unknown")
|
|
144
|
+
&& isStringArray(currentAsk.sourceEventIds)
|
|
145
|
+
&& !!nextSafeAction
|
|
146
|
+
&& typeof nextSafeAction === "object"
|
|
147
|
+
&& !Array.isArray(nextSafeAction)
|
|
148
|
+
&& isNullableString(nextSafeAction.value)
|
|
149
|
+
&& isStringArray(nextSafeAction.stopBefore)
|
|
150
|
+
&& isStringArray(nextSafeAction.sourceEventIds)
|
|
151
|
+
&& isStringArray(record.blockedBecause)
|
|
152
|
+
&& isStringArray(record.activeObligationIds)
|
|
153
|
+
&& isStringArray(record.activeReturnObligationIds)
|
|
154
|
+
&& isStringArray(record.activePacketIds)
|
|
155
|
+
&& isStringArray(record.openEvolutionCaseIds)
|
|
156
|
+
&& isStringArray(record.recentClaimIds)
|
|
157
|
+
&& isStringArray(record.unverifiedClaimIds)
|
|
158
|
+
&& !!lastSafeCheckpoint
|
|
159
|
+
&& typeof lastSafeCheckpoint === "object"
|
|
160
|
+
&& !Array.isArray(lastSafeCheckpoint)
|
|
161
|
+
&& isNullableString(lastSafeCheckpoint.turnId)
|
|
162
|
+
&& isNullableString(lastSafeCheckpoint.sessionRef)
|
|
163
|
+
&& isNullableString(lastSafeCheckpoint.recordedAt)
|
|
164
|
+
&& isStringArray(lastSafeCheckpoint.sourceEventIds)
|
|
165
|
+
&& !!recorderHealth
|
|
166
|
+
&& typeof recorderHealth === "object"
|
|
167
|
+
&& !Array.isArray(recorderHealth)
|
|
168
|
+
&& (recorderHealth.status === "ok" || recorderHealth.status === "degraded" || recorderHealth.status === "unavailable")
|
|
169
|
+
&& isStringArray(recorderHealth.issues);
|
|
170
|
+
}
|
|
171
|
+
/* v8 ignore stop */
|
|
172
|
+
/* v8 ignore start -- semantic invariant fan-out is defensive; regression tests cover unsafe continuation normalization @preserve */
|
|
173
|
+
function nonEmpty(value) {
|
|
174
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
175
|
+
}
|
|
176
|
+
function uniqueStrings(values) {
|
|
177
|
+
return [...new Set(values)];
|
|
178
|
+
}
|
|
179
|
+
function normalizeResumeInvariants(resume) {
|
|
180
|
+
const hasCurrentAsk = nonEmpty(resume.currentAsk.value);
|
|
181
|
+
const hasNextSafeAction = nonEmpty(resume.nextSafeAction.value);
|
|
182
|
+
const hasCompleteState = hasCurrentAsk && hasNextSafeAction;
|
|
183
|
+
const missing = [
|
|
184
|
+
...(hasCurrentAsk ? [] : ["currentAsk"]),
|
|
185
|
+
...(hasNextSafeAction ? [] : ["nextSafeAction"]),
|
|
186
|
+
];
|
|
187
|
+
const missingChanged = resume.missing.join("\n") !== missing.join("\n");
|
|
188
|
+
const issues = [
|
|
189
|
+
...(resume.canContinue && !resume.hasCompleteState ? ["canContinue true while hasCompleteState false"] : []),
|
|
190
|
+
...(resume.canContinue && !hasCurrentAsk ? ["canContinue true without currentAsk"] : []),
|
|
191
|
+
...(resume.canContinue && !hasNextSafeAction ? ["canContinue true without nextSafeAction"] : []),
|
|
192
|
+
...(resume.canContinue && resume.blockedBecause.length > 0 ? ["canContinue true while blocked"] : []),
|
|
193
|
+
...(resume.canContinue && resume.recorderHealth.status !== "ok" ? ["canContinue true while recorder health is not ok"] : []),
|
|
194
|
+
];
|
|
195
|
+
if (issues.length === 0 && resume.hasCompleteState === hasCompleteState && !missingChanged)
|
|
196
|
+
return resume;
|
|
197
|
+
return {
|
|
198
|
+
...resume,
|
|
199
|
+
hasCompleteState,
|
|
200
|
+
canContinue: issues.length === 0 ? resume.canContinue : false,
|
|
201
|
+
missing,
|
|
202
|
+
currentAsk: {
|
|
203
|
+
...resume.currentAsk,
|
|
204
|
+
confidence: hasCurrentAsk ? resume.currentAsk.confidence : "unknown",
|
|
205
|
+
},
|
|
206
|
+
recorderHealth: issues.length > 0
|
|
207
|
+
? {
|
|
208
|
+
status: resume.recorderHealth.status === "unavailable" ? "unavailable" : "degraded",
|
|
209
|
+
issues: uniqueStrings([...resume.recorderHealth.issues, ...issues]),
|
|
210
|
+
}
|
|
211
|
+
: resume.recorderHealth,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/* v8 ignore stop */
|
|
215
|
+
function latestFromEvent(event, previous) {
|
|
216
|
+
const currentAskValue = event.currentAsk !== undefined ? event.currentAsk : previous.currentAsk.value;
|
|
217
|
+
const nextSafeActionValue = event.nextSafeAction !== undefined ? event.nextSafeAction : previous.nextSafeAction.value;
|
|
218
|
+
const currentAskSourceEventIds = event.currentAsk !== undefined ? [event.id] : previous.currentAsk.sourceEventIds;
|
|
219
|
+
const nextSafeActionSourceEventIds = event.nextSafeAction !== undefined ? [event.id] : previous.nextSafeAction.sourceEventIds;
|
|
220
|
+
const hasCurrentAsk = typeof currentAskValue === "string" && currentAskValue.trim().length > 0;
|
|
221
|
+
const hasNextSafeAction = typeof nextSafeActionValue === "string" && nextSafeActionValue.trim().length > 0;
|
|
222
|
+
return {
|
|
223
|
+
...previous,
|
|
224
|
+
hasCompleteState: hasCurrentAsk && hasNextSafeAction,
|
|
225
|
+
canContinue: hasCurrentAsk && hasNextSafeAction && (event.blockedBecause?.length ?? previous.blockedBecause.length) === 0,
|
|
226
|
+
missing: [
|
|
227
|
+
...(hasCurrentAsk ? [] : ["currentAsk"]),
|
|
228
|
+
...(hasNextSafeAction ? [] : ["nextSafeAction"]),
|
|
229
|
+
],
|
|
230
|
+
currentAsk: {
|
|
231
|
+
value: currentAskValue,
|
|
232
|
+
confidence: hasCurrentAsk ? "current" : "unknown",
|
|
233
|
+
sourceEventIds: currentAskSourceEventIds,
|
|
234
|
+
},
|
|
235
|
+
nextSafeAction: {
|
|
236
|
+
value: nextSafeActionValue,
|
|
237
|
+
stopBefore: event.stopBefore ?? previous.nextSafeAction.stopBefore,
|
|
238
|
+
sourceEventIds: nextSafeActionSourceEventIds,
|
|
239
|
+
},
|
|
240
|
+
blockedBecause: event.blockedBecause ?? previous.blockedBecause,
|
|
241
|
+
activeObligationIds: event.activeObligationIds ?? previous.activeObligationIds,
|
|
242
|
+
activeReturnObligationIds: event.activeReturnObligationIds ?? previous.activeReturnObligationIds,
|
|
243
|
+
activePacketIds: event.activePacketIds ?? previous.activePacketIds,
|
|
244
|
+
openEvolutionCaseIds: event.openEvolutionCaseIds ?? previous.openEvolutionCaseIds,
|
|
245
|
+
recentClaimIds: event.recentClaimIds ?? previous.recentClaimIds,
|
|
246
|
+
unverifiedClaimIds: event.unverifiedClaimIds ?? previous.unverifiedClaimIds,
|
|
247
|
+
lastSafeCheckpoint: {
|
|
248
|
+
turnId: event.turnId ?? previous.lastSafeCheckpoint.turnId,
|
|
249
|
+
sessionRef: event.sessionRef ?? previous.lastSafeCheckpoint.sessionRef,
|
|
250
|
+
recordedAt: event.recordedAt,
|
|
251
|
+
sourceEventIds: [event.id],
|
|
252
|
+
},
|
|
253
|
+
recorderHealth: { status: "ok", issues: [] },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function readFlightRecorderResume(agentRoot) {
|
|
257
|
+
const latestPath = flightRecorderLatestPath(agentRoot);
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(fs.readFileSync(latestPath, "utf-8"));
|
|
260
|
+
if (!isResumeCandidate(parsed)) {
|
|
261
|
+
throw new Error("latest.json has invalid flight-recorder resume shape");
|
|
262
|
+
}
|
|
263
|
+
const resume = normalizeResumeInvariants(parsed);
|
|
264
|
+
(0, runtime_1.emitNervesEvent)({
|
|
265
|
+
component: "mind",
|
|
266
|
+
event: "mind.flight_recorder_resume_read",
|
|
267
|
+
message: "flight recorder resume read",
|
|
268
|
+
meta: { agentRoot, canContinue: resume.canContinue, hasCompleteState: resume.hasCompleteState },
|
|
269
|
+
});
|
|
270
|
+
return resume;
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
const issue = fs.existsSync(latestPath)
|
|
274
|
+
? `latest.json unreadable: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`
|
|
275
|
+
: "latest.json missing";
|
|
276
|
+
(0, runtime_1.emitNervesEvent)({
|
|
277
|
+
component: "mind",
|
|
278
|
+
event: "mind.flight_recorder_resume_read",
|
|
279
|
+
message: "flight recorder resume missing or degraded",
|
|
280
|
+
meta: { agentRoot, issue },
|
|
281
|
+
});
|
|
282
|
+
return degradedResume([issue]);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function writeFlightRecorderResume(agentRoot, resume) {
|
|
286
|
+
const safeResume = normalizeResumeInvariants(resume);
|
|
287
|
+
atomicWriteJson(flightRecorderLatestPath(agentRoot), safeResume);
|
|
288
|
+
(0, runtime_1.emitNervesEvent)({
|
|
289
|
+
component: "mind",
|
|
290
|
+
event: "mind.flight_recorder_resume_written",
|
|
291
|
+
message: "flight recorder resume written",
|
|
292
|
+
meta: { agentRoot, canContinue: safeResume.canContinue, hasCompleteState: safeResume.hasCompleteState },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function recordFlightRecorderEvent(agentRoot, input) {
|
|
296
|
+
const event = normalizeEvent(input);
|
|
297
|
+
fs.mkdirSync(eventsDir(agentRoot), { recursive: true });
|
|
298
|
+
fs.appendFileSync(path.join(eventsDir(agentRoot), `${eventDay(event.recordedAt)}.jsonl`), `${JSON.stringify(event)}\n`, "utf-8");
|
|
299
|
+
const latest = latestFromEvent(event, readFlightRecorderResume(agentRoot));
|
|
300
|
+
writeFlightRecorderResume(agentRoot, latest);
|
|
301
|
+
(0, runtime_1.emitNervesEvent)({
|
|
302
|
+
component: "mind",
|
|
303
|
+
event: "mind.flight_recorder_event_recorded",
|
|
304
|
+
message: "flight recorder event recorded",
|
|
305
|
+
meta: { agentRoot, eventId: event.id, kind: event.kind },
|
|
306
|
+
});
|
|
307
|
+
return event;
|
|
308
|
+
}
|
|
309
|
+
function writeHabitRunReceipt(agentRoot, receipt) {
|
|
310
|
+
fs.mkdirSync(receiptsDir(agentRoot), { recursive: true });
|
|
311
|
+
const safeReceipt = {
|
|
312
|
+
...receipt,
|
|
313
|
+
habitName: (0, session_events_1.capStructuredRecordString)(receipt.habitName),
|
|
314
|
+
producedRefs: receipt.producedRefs.map((ref) => ({ ...ref, locator: (0, session_events_1.capStructuredRecordString)(ref.locator) })),
|
|
315
|
+
surfaceAttempts: receipt.surfaceAttempts.map((attempt) => ({
|
|
316
|
+
...attempt,
|
|
317
|
+
recipient: (0, session_events_1.capStructuredRecordString)(attempt.recipient),
|
|
318
|
+
channel: (0, session_events_1.capStructuredRecordString)(attempt.channel),
|
|
319
|
+
})),
|
|
320
|
+
errors: receipt.errors.map((error) => (0, session_events_1.capStructuredRecordString)(error)),
|
|
321
|
+
};
|
|
322
|
+
atomicWriteJson(path.join(receiptsDir(agentRoot), `${safeReceipt.runId}.json`), safeReceipt);
|
|
323
|
+
recordFlightRecorderEvent(agentRoot, {
|
|
324
|
+
kind: "habit_run",
|
|
325
|
+
recordedAt: safeReceipt.endedAt,
|
|
326
|
+
summary: `habit ${safeReceipt.habitName} finished with ${safeReceipt.outcome}`,
|
|
327
|
+
producedRefs: safeReceipt.producedRefs,
|
|
328
|
+
meta: { receiptPath: path.join("arc", "flight-recorder", "habit-receipts", `${safeReceipt.runId}.json`) },
|
|
329
|
+
});
|
|
330
|
+
(0, runtime_1.emitNervesEvent)({
|
|
331
|
+
component: "mind",
|
|
332
|
+
event: "mind.flight_recorder_habit_receipt_written",
|
|
333
|
+
message: "flight recorder habit receipt written",
|
|
334
|
+
meta: { agentRoot, runId: safeReceipt.runId, habitName: safeReceipt.habitName, outcome: safeReceipt.outcome },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function formatFlightRecorderResume(resume) {
|
|
338
|
+
const lines = ["## Arc resume"];
|
|
339
|
+
lines.push(`can continue: ${resume.canContinue ? "yes" : "no"}`);
|
|
340
|
+
lines.push(`complete state: ${resume.hasCompleteState ? "yes" : "no"}`);
|
|
341
|
+
if (resume.currentAsk.value)
|
|
342
|
+
lines.push(`current ask: ${resume.currentAsk.value}`);
|
|
343
|
+
if (resume.nextSafeAction.value)
|
|
344
|
+
lines.push(`next safe action: ${resume.nextSafeAction.value}`);
|
|
345
|
+
if (resume.nextSafeAction.stopBefore.length > 0)
|
|
346
|
+
lines.push(`stop before: ${resume.nextSafeAction.stopBefore.join(", ")}`);
|
|
347
|
+
if (resume.blockedBecause.length > 0)
|
|
348
|
+
lines.push(`blocked: ${resume.blockedBecause.join("; ")}`);
|
|
349
|
+
if (resume.missing.length > 0)
|
|
350
|
+
lines.push(`missing: ${resume.missing.join(", ")}`);
|
|
351
|
+
if (resume.gaps.length > 0)
|
|
352
|
+
lines.push(`gaps: ${resume.gaps.join("; ")}`);
|
|
353
|
+
if (resume.activeObligationIds.length > 0)
|
|
354
|
+
lines.push(`obligations: ${resume.activeObligationIds.join(", ")}`);
|
|
355
|
+
if (resume.activeReturnObligationIds.length > 0)
|
|
356
|
+
lines.push(`return obligations: ${resume.activeReturnObligationIds.join(", ")}`);
|
|
357
|
+
if (resume.activePacketIds.length > 0)
|
|
358
|
+
lines.push(`packets: ${resume.activePacketIds.join(", ")}`);
|
|
359
|
+
if (resume.unverifiedClaimIds.length > 0)
|
|
360
|
+
lines.push(`unverified claims: ${resume.unverifiedClaimIds.join(", ")}`);
|
|
361
|
+
if (resume.recorderHealth.status !== "ok") {
|
|
362
|
+
lines.push(`recorder health: ${resume.recorderHealth.status} (${resume.recorderHealth.issues.join("; ")})`);
|
|
363
|
+
}
|
|
364
|
+
return lines.join("\n");
|
|
365
|
+
}
|
|
366
|
+
function createHabitRunId(habitName, now = new Date()) {
|
|
367
|
+
const safeName = habitName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "habit";
|
|
368
|
+
return `${now.toISOString().replace(/[:.]/g, "-")}-${safeName}-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
|
|
369
|
+
}
|
package/dist/arc/obligations.js
CHANGED
|
@@ -44,10 +44,12 @@ exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
|
|
|
44
44
|
exports.enrichObligation = enrichObligation;
|
|
45
45
|
exports.generateObligationId = generateObligationId;
|
|
46
46
|
exports.getReturnObligationsDir = getReturnObligationsDir;
|
|
47
|
+
exports.getReturnObligationsDirForRoot = getReturnObligationsDirForRoot;
|
|
47
48
|
exports.createReturnObligation = createReturnObligation;
|
|
48
49
|
exports.readReturnObligation = readReturnObligation;
|
|
49
50
|
exports.advanceReturnObligation = advanceReturnObligation;
|
|
50
51
|
exports.listActiveReturnObligations = listActiveReturnObligations;
|
|
52
|
+
exports.listActiveReturnObligationsForRoot = listActiveReturnObligationsForRoot;
|
|
51
53
|
const path = __importStar(require("path"));
|
|
52
54
|
const identity_1 = require("../heart/identity");
|
|
53
55
|
const session_events_1 = require("../heart/session-events");
|
|
@@ -194,6 +196,9 @@ function generateObligationId(timestamp) {
|
|
|
194
196
|
function getReturnObligationsDir(agentName) {
|
|
195
197
|
return path.join((0, identity_1.getAgentRoot)(agentName), "arc", "obligations", "inner");
|
|
196
198
|
}
|
|
199
|
+
function getReturnObligationsDirForRoot(agentRoot) {
|
|
200
|
+
return path.join(agentRoot, "arc", "obligations", "inner");
|
|
201
|
+
}
|
|
197
202
|
function createReturnObligation(agentName, obligation) {
|
|
198
203
|
const dir = getReturnObligationsDir(agentName);
|
|
199
204
|
const cappedObligation = {
|
|
@@ -257,12 +262,29 @@ const RETURN_OBLIGATION_INJECTION_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
|
257
262
|
// it. This is a read-time defense; the underlying file is left as-is.
|
|
258
263
|
const ACTIVE_RETURN_OBLIGATION_STATUSES = new Set(["queued", "running"]);
|
|
259
264
|
function isSelfInnerReturnObligation(obligation) {
|
|
260
|
-
return obligation.origin
|
|
265
|
+
return obligation.origin?.friendId === "self" && obligation.origin.channel === "inner";
|
|
266
|
+
}
|
|
267
|
+
function isReturnObligationRecord(value) {
|
|
268
|
+
if (!value || typeof value !== "object")
|
|
269
|
+
return false;
|
|
270
|
+
const candidate = value;
|
|
271
|
+
return typeof candidate.id === "string"
|
|
272
|
+
&& typeof candidate.status === "string"
|
|
273
|
+
&& typeof candidate.delegatedContent === "string"
|
|
274
|
+
&& typeof candidate.createdAt === "number"
|
|
275
|
+
&& !!candidate.origin
|
|
276
|
+
&& typeof candidate.origin.friendId === "string"
|
|
277
|
+
&& typeof candidate.origin.channel === "string"
|
|
278
|
+
&& typeof candidate.origin.key === "string";
|
|
261
279
|
}
|
|
262
280
|
function listActiveReturnObligations(agentName, options = {}) {
|
|
263
|
-
|
|
281
|
+
return listActiveReturnObligationsForRoot((0, identity_1.getAgentRoot)(agentName), options);
|
|
282
|
+
}
|
|
283
|
+
function listActiveReturnObligationsForRoot(agentRoot, options = {}) {
|
|
284
|
+
const all = (0, json_store_1.readJsonDir)(getReturnObligationsDirForRoot(agentRoot));
|
|
264
285
|
const nowMs = (options.now ?? Date.now)();
|
|
265
286
|
return all
|
|
287
|
+
.filter(isReturnObligationRecord)
|
|
266
288
|
.filter((parsed) => ACTIVE_RETURN_OBLIGATION_STATUSES.has(parsed.status))
|
|
267
289
|
.filter((parsed) => !isSelfInnerReturnObligation(parsed))
|
|
268
290
|
.filter((parsed) => nowMs - parsed.createdAt <= RETURN_OBLIGATION_INJECTION_MAX_AGE_MS)
|
|
@@ -131,10 +131,10 @@ const registryData = [
|
|
|
131
131
|
{
|
|
132
132
|
path: "agentFacing.provider",
|
|
133
133
|
tier: "self",
|
|
134
|
-
description: "Provider for agent-facing interactions (inner
|
|
134
|
+
description: "Provider for agent-facing interactions (inner lane, delegation).",
|
|
135
135
|
default: "anthropic",
|
|
136
|
-
effects: "Changes the LLM provider used for
|
|
137
|
-
topics: ["model", "provider", "llm", "agent", "inner-
|
|
136
|
+
effects: "Changes the LLM provider used for private agent-facing turns and agent-to-agent communication.",
|
|
137
|
+
topics: ["model", "provider", "llm", "agent", "inner-lane"],
|
|
138
138
|
validate: validateStringEnum(KNOWN_PROVIDERS),
|
|
139
139
|
},
|
|
140
140
|
{
|
|
@@ -142,8 +142,8 @@ const registryData = [
|
|
|
142
142
|
tier: "self",
|
|
143
143
|
description: "Model name for agent-facing interactions.",
|
|
144
144
|
default: "claude-opus-4-6",
|
|
145
|
-
effects: "Changes the specific model used for
|
|
146
|
-
topics: ["model", "provider", "llm", "agent", "inner-
|
|
145
|
+
effects: "Changes the specific model used for private agent-facing turns and agent-to-agent communication.",
|
|
146
|
+
topics: ["model", "provider", "llm", "agent", "inner-lane"],
|
|
147
147
|
validate: validateString,
|
|
148
148
|
},
|
|
149
149
|
{
|