@ouro.bot/cli 0.1.0-alpha.482 → 0.1.0-alpha.483
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/active-work.js +46 -0
- package/dist/heart/background-operations.js +27 -3
- package/dist/heart/core.js +15 -0
- package/dist/heart/daemon/cli-exec.js +104 -15
- package/dist/heart/daemon/cli-help.js +10 -4
- package/dist/heart/daemon/cli-parse.js +11 -5
- package/dist/heart/daemon/cli-render.js +1 -1
- package/dist/heart/daemon/thoughts.js +22 -8
- package/dist/heart/mail-import-discovery.js +318 -0
- package/dist/heart/outlook/outlook-http-routes.js +1 -1
- package/dist/heart/outlook/outlook-http-static.js +4 -0
- package/dist/heart/outlook/outlook-http.js +2 -2
- package/dist/heart/outlook/outlook-types.js +1 -1
- package/dist/heart/outlook/outlook-view.js +3 -3
- package/dist/heart/outlook/readers/agent-machine.js +34 -11
- package/dist/heart/outlook/readers/continuity-readers.js +5 -1
- package/dist/heart/outlook/readers/mail.js +3 -3
- package/dist/heart/session-events.js +91 -5
- package/dist/heart/turn-context.js +11 -0
- package/dist/mind/context.js +1 -1
- package/dist/mind/prompt.js +6 -3
- package/dist/nerves/coverage/file-completeness.js +1 -0
- package/dist/nerves/observation.js +2 -2
- package/dist/outlook-ui/assets/{index-CPfhbn13.js → index-Cm51CY9W.js} +1 -1
- package/dist/outlook-ui/index.html +2 -2
- package/dist/repertoire/tools-mail.js +2 -2
- package/dist/repertoire/tools-session.js +5 -2
- package/dist/senses/cli/ouro-tui.js +1 -1
- package/dist/senses/inner-dialog.js +5 -7
- package/dist/senses/mail.js +149 -18
- package/dist/senses/pipeline.js +2 -1
- package/package.json +1 -1
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="color-scheme" content="dark" />
|
|
7
|
-
<title>Ouro
|
|
7
|
+
<title>Ouro Mailbox</title>
|
|
8
8
|
<meta name="description" content="The daemon-hosted shared orientation surface for agents alive on this machine." />
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-Cm51CY9W.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-BPr5vNuM.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
@@ -262,12 +262,12 @@ async function renderEmptyMailResult(input) {
|
|
|
262
262
|
`mail onboarding status: Mailroom is provisioned for ${input.config.mailboxAddress}, but this agent's encrypted store has 0 messages.`,
|
|
263
263
|
...renderSourceGrantStatus(input.config, input.agentId),
|
|
264
264
|
"interpretation: this is not evidence that the human's HEY inbox is empty; Agent Mail has not yet received or imported mail visible to this agent.",
|
|
265
|
-
`agent next move: guide setup from docs/agent-mail-setup.md. If HEY mail is needed, ensure the delegated hey alias exists, ask the human for
|
|
265
|
+
`agent next move: guide setup from docs/agent-mail-setup.md. If HEY mail is needed, ensure the delegated hey alias exists, first try ouro mail import-mbox --agent ${input.agentId} --owner-email <human-email> --source hey --discover so Ouro can find a browser-downloaded export in .playwright-mcp or Downloads. Only ask the human for a file path if discovery cannot find a unique MBOX, then run ouro mail import-mbox --agent ${input.agentId} --owner-email <human-email> --source hey --file <mbox-path>. Verify with mail_recent/mail_search/Ouro Mailbox.`,
|
|
266
266
|
"validation golden paths before claiming setup works:",
|
|
267
267
|
"1. HEY archive to work object: import the human-provided HEY MBOX and use delegated mail to update a real work object, such as travel plans.",
|
|
268
268
|
"2. Native mail and Screener: send and receive agent-native mail, confirm unknown senders enter Screener, get family authorization for allow/discard, verify sender policy, and confirm discarded mail is recoverable.",
|
|
269
269
|
"3. Cross-sense reaction: use a mail-derived update or decision to trigger another configured sense, such as texting the family member on iMessage when BlueBubbles is available.",
|
|
270
|
-
"4. Ouro
|
|
270
|
+
"4. Ouro Mailbox audit: inspect the read-only mailbox UI for imported mail, native inbound, Screener decisions, outbound draft/send records, and mail access logs.",
|
|
271
271
|
"supporting diagnostics are separate evidence inside those paths, not additional paths; never answer a golden-path question with command names, tool names, or status checks.",
|
|
272
272
|
].join("\n");
|
|
273
273
|
}
|
|
@@ -46,13 +46,13 @@ const manager_1 = require("../heart/bridges/manager");
|
|
|
46
46
|
const session_transcript_1 = require("../heart/session-transcript");
|
|
47
47
|
const session_activity_1 = require("../heart/session-activity");
|
|
48
48
|
const active_work_1 = require("../heart/active-work");
|
|
49
|
-
const background_operations_1 = require("../heart/background-operations");
|
|
50
49
|
const coding_1 = require("./coding");
|
|
51
50
|
const tasks_1 = require("./tasks");
|
|
52
51
|
const pending_1 = require("../mind/pending");
|
|
53
52
|
const obligations_1 = require("../arc/obligations");
|
|
54
53
|
const progress_story_1 = require("../heart/progress-story");
|
|
55
54
|
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
55
|
+
const mail_import_discovery_1 = require("../heart/mail-import-discovery");
|
|
56
56
|
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
57
57
|
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
58
58
|
async function summarizeSessionTailSafely(options) {
|
|
@@ -264,9 +264,12 @@ async function buildToolActiveWorkFrame(ctx) {
|
|
|
264
264
|
&& obligation.origin.channel === currentSession.channel
|
|
265
265
|
&& obligation.origin.key === currentSession.key)?.content ?? null
|
|
266
266
|
: null;
|
|
267
|
-
const backgroundOperations = (0,
|
|
267
|
+
const backgroundOperations = (0, mail_import_discovery_1.listVisibleBackgroundOperations)({
|
|
268
268
|
agentName: (0, identity_1.getAgentName)(),
|
|
269
269
|
agentRoot,
|
|
270
|
+
repoRoot: process.cwd(),
|
|
271
|
+
homeDir: process.env.HOME,
|
|
272
|
+
nowMs: Date.now(),
|
|
270
273
|
limit: 5,
|
|
271
274
|
});
|
|
272
275
|
return (0, active_work_1.buildActiveWorkFrame)({
|
|
@@ -11,7 +11,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
11
11
|
* Only the "live" area (current streaming + spinner + input) re-renders.
|
|
12
12
|
* This avoids the screen-clearing problem that broke the previous Ink attempt.
|
|
13
13
|
*
|
|
14
|
-
* Design language: ouroboros brand palette from ouroboros.bot /
|
|
14
|
+
* Design language: ouroboros brand palette from ouroboros.bot / Mailbox UI.
|
|
15
15
|
* ZERO business logic here — pure rendering from CliStore state.
|
|
16
16
|
*/
|
|
17
17
|
const react_1 = require("react");
|
|
@@ -187,19 +187,17 @@ function deriveResumeCheckpoint(messages) {
|
|
|
187
187
|
const assistantText = contentToText(lastAssistant.content);
|
|
188
188
|
if (!assistantText)
|
|
189
189
|
return "no prior checkpoint recorded";
|
|
190
|
-
const
|
|
190
|
+
const cleanedLines = assistantText
|
|
191
191
|
.split("\n")
|
|
192
|
-
.map((line) => line.trim())
|
|
192
|
+
.map((line) => line.replace(/<\/?think>/gi, "").trim())
|
|
193
|
+
.filter((line) => line.length > 0);
|
|
194
|
+
const explicitCheckpoint = cleanedLines
|
|
193
195
|
.find((line) => /^checkpoint\s*:/i.test(line));
|
|
194
196
|
if (explicitCheckpoint) {
|
|
195
197
|
const parsed = explicitCheckpoint.replace(/^checkpoint\s*:\s*/i, "").trim();
|
|
196
198
|
return parsed || "no prior checkpoint recorded";
|
|
197
199
|
}
|
|
198
|
-
const firstLine =
|
|
199
|
-
.split("\n")
|
|
200
|
-
.map((line) => line.trim())
|
|
201
|
-
.find((line) => line.length > 0);
|
|
202
|
-
/* v8 ignore next -- unreachable: contentToText().trim() guarantees a non-empty line @preserve */
|
|
200
|
+
const firstLine = cleanedLines[0];
|
|
203
201
|
if (!firstLine)
|
|
204
202
|
return "no prior checkpoint recorded";
|
|
205
203
|
if (firstLine.length <= 220)
|
package/dist/senses/mail.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.scanMailImportDiscoveryAttention = scanMailImportDiscoveryAttention;
|
|
36
37
|
exports.startMailSenseApp = startMailSenseApp;
|
|
37
38
|
const fs = __importStar(require("node:fs"));
|
|
38
39
|
const path = __importStar(require("node:path"));
|
|
@@ -40,6 +41,9 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
40
41
|
const identity_1 = require("../heart/identity");
|
|
41
42
|
const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
42
43
|
const pending_1 = require("../mind/pending");
|
|
44
|
+
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
45
|
+
const background_operations_1 = require("../heart/background-operations");
|
|
46
|
+
const mail_import_discovery_1 = require("../heart/mail-import-discovery");
|
|
43
47
|
const attention_1 = require("../mailroom/attention");
|
|
44
48
|
const reader_1 = require("../mailroom/reader");
|
|
45
49
|
const smtp_ingress_1 = require("../mailroom/smtp-ingress");
|
|
@@ -87,6 +91,9 @@ function runtimeStatePath(agentName) {
|
|
|
87
91
|
function attentionStatePath(agentName) {
|
|
88
92
|
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "mail", "attention.json");
|
|
89
93
|
}
|
|
94
|
+
function importDiscoveryStatePath(agentName) {
|
|
95
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "mail", "import-discovery.json");
|
|
96
|
+
}
|
|
90
97
|
function writeRuntimeState(filePath, state) {
|
|
91
98
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
92
99
|
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
@@ -97,6 +104,107 @@ function writeRuntimeState(filePath, state) {
|
|
|
97
104
|
meta: { agentName: state.agentName, status: state.status, lastQueuedCount: state.lastQueuedCount },
|
|
98
105
|
});
|
|
99
106
|
}
|
|
107
|
+
function emptyImportDiscoveryState(updatedAt) {
|
|
108
|
+
return {
|
|
109
|
+
schemaVersion: 1,
|
|
110
|
+
lastNotifiedFingerprint: null,
|
|
111
|
+
updatedAt,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function readImportDiscoveryState(filePath, updatedAt) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: 1,
|
|
119
|
+
lastNotifiedFingerprint: typeof parsed.lastNotifiedFingerprint === "string" && parsed.lastNotifiedFingerprint.trim().length > 0
|
|
120
|
+
? parsed.lastNotifiedFingerprint
|
|
121
|
+
: null,
|
|
122
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : updatedAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return emptyImportDiscoveryState(updatedAt);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function writeImportDiscoveryState(filePath, state) {
|
|
130
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
131
|
+
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
132
|
+
}
|
|
133
|
+
function stringArray(value) {
|
|
134
|
+
if (!Array.isArray(value))
|
|
135
|
+
return [];
|
|
136
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
137
|
+
}
|
|
138
|
+
function renderImportDiscoveryContent(candidatePaths) {
|
|
139
|
+
return [
|
|
140
|
+
"[Mail Import Ready]",
|
|
141
|
+
"A local MBOX archive is ready for delegated-mail backfill.",
|
|
142
|
+
"This may live in a worktree-local Playwright sandbox rather than ~/Downloads.",
|
|
143
|
+
"",
|
|
144
|
+
"recent candidates:",
|
|
145
|
+
...candidatePaths.map((candidatePath) => `- ${candidatePath}`),
|
|
146
|
+
"",
|
|
147
|
+
"If this matches an expected mailbox backfill, run `ouro mail import-mbox --discover --owner-email <email> --source hey --agent <agent>` first so Ouro can pick the matching archive or report ambiguity.",
|
|
148
|
+
].join("\n");
|
|
149
|
+
}
|
|
150
|
+
async function scanMailImportDiscoveryAttention(input) {
|
|
151
|
+
const nowMs = input.now?.() ?? Date.now();
|
|
152
|
+
const updatedAt = new Date(nowMs).toISOString();
|
|
153
|
+
const statePath = input.statePath ?? importDiscoveryStatePath(input.agentName);
|
|
154
|
+
const pendingDir = input.pendingDir ?? (0, pending_1.getInnerDialogPendingDir)(input.agentName);
|
|
155
|
+
const state = readImportDiscoveryState(statePath, updatedAt);
|
|
156
|
+
const existingOperations = (0, background_operations_1.listBackgroundOperations)({
|
|
157
|
+
agentName: input.agentName,
|
|
158
|
+
agentRoot: (0, identity_1.getAgentRoot)(input.agentName),
|
|
159
|
+
limit: 10,
|
|
160
|
+
});
|
|
161
|
+
const discovered = (0, mail_import_discovery_1.listAmbientMailImportOperations)({
|
|
162
|
+
agentName: input.agentName,
|
|
163
|
+
agentRoot: (0, identity_1.getAgentRoot)(input.agentName),
|
|
164
|
+
existingOperations,
|
|
165
|
+
repoRoot: (0, identity_1.getRepoRoot)(),
|
|
166
|
+
homeDir: process.env.HOME,
|
|
167
|
+
nowMs,
|
|
168
|
+
})[0] ?? null;
|
|
169
|
+
const fingerprint = typeof discovered?.spec?.fingerprint === "string" && discovered.spec.fingerprint.trim().length > 0
|
|
170
|
+
? discovered.spec.fingerprint
|
|
171
|
+
: null;
|
|
172
|
+
const candidatePaths = stringArray(discovered?.spec?.candidatePaths);
|
|
173
|
+
const shouldQueue = Boolean(discovered && fingerprint && fingerprint !== state.lastNotifiedFingerprint);
|
|
174
|
+
if (shouldQueue) {
|
|
175
|
+
(0, pending_1.queuePendingMessage)(pendingDir, {
|
|
176
|
+
from: "mailroom",
|
|
177
|
+
friendId: "self",
|
|
178
|
+
channel: "mail",
|
|
179
|
+
key: "import-ready",
|
|
180
|
+
content: renderImportDiscoveryContent(candidatePaths),
|
|
181
|
+
timestamp: nowMs,
|
|
182
|
+
mode: "reflect",
|
|
183
|
+
});
|
|
184
|
+
await (0, socket_client_1.requestInnerWake)(input.agentName).catch(() => undefined);
|
|
185
|
+
}
|
|
186
|
+
writeImportDiscoveryState(statePath, {
|
|
187
|
+
schemaVersion: 1,
|
|
188
|
+
lastNotifiedFingerprint: shouldQueue ? fingerprint : state.lastNotifiedFingerprint,
|
|
189
|
+
updatedAt,
|
|
190
|
+
});
|
|
191
|
+
(0, runtime_1.emitNervesEvent)({
|
|
192
|
+
component: "senses",
|
|
193
|
+
event: "senses.mail_import_discovery_scanned",
|
|
194
|
+
message: "mail import discovery scanned",
|
|
195
|
+
meta: {
|
|
196
|
+
agentName: input.agentName,
|
|
197
|
+
queued: shouldQueue,
|
|
198
|
+
candidateCount: candidatePaths.length,
|
|
199
|
+
fingerprint,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
queued: shouldQueue,
|
|
204
|
+
fingerprint,
|
|
205
|
+
candidatePaths,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
100
208
|
function closeServer(server) {
|
|
101
209
|
return new Promise((resolve) => {
|
|
102
210
|
server.close(resolve);
|
|
@@ -137,34 +245,21 @@ async function startMailSenseApp(options) {
|
|
|
137
245
|
const activeHttpPort = () => ingress ? serverPort(ingress.health) : null;
|
|
138
246
|
const runtimePath = runtimeStatePath(options.agentName);
|
|
139
247
|
const attentionPath = attentionStatePath(options.agentName);
|
|
248
|
+
const importDiscoveryPath = importDiscoveryStatePath(options.agentName);
|
|
140
249
|
let lastScanAt = null;
|
|
141
250
|
let lastQueuedCount = 0;
|
|
142
251
|
const scan = async () => {
|
|
252
|
+
const scanStartedAt = new Date(now()).toISOString();
|
|
253
|
+
let queuedCount = 0;
|
|
143
254
|
try {
|
|
144
|
-
const
|
|
145
|
-
const result = await (0, attention_1.scanMailScreenerAttention)({
|
|
255
|
+
const screener = await (0, attention_1.scanMailScreenerAttention)({
|
|
146
256
|
agentName: options.agentName,
|
|
147
257
|
store: resolved.store,
|
|
148
258
|
pendingDir: (0, pending_1.getInnerDialogPendingDir)(options.agentName),
|
|
149
259
|
statePath: attentionPath,
|
|
150
260
|
now,
|
|
151
261
|
});
|
|
152
|
-
|
|
153
|
-
lastQueuedCount = result.queued.length;
|
|
154
|
-
writeRuntimeState(runtimePath, {
|
|
155
|
-
schemaVersion: 1,
|
|
156
|
-
agentName: options.agentName,
|
|
157
|
-
status: "running",
|
|
158
|
-
mailboxAddress: resolved.config.mailboxAddress,
|
|
159
|
-
smtpPort: activeSmtpPort(),
|
|
160
|
-
httpPort: activeHttpPort(),
|
|
161
|
-
host,
|
|
162
|
-
storeKind: resolved.storeKind,
|
|
163
|
-
storeLabel: resolved.storeLabel,
|
|
164
|
-
lastScanAt,
|
|
165
|
-
lastQueuedCount,
|
|
166
|
-
updatedAt: new Date(now()).toISOString(),
|
|
167
|
-
});
|
|
262
|
+
queuedCount += screener.queued.length;
|
|
168
263
|
}
|
|
169
264
|
catch (error) {
|
|
170
265
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -175,6 +270,42 @@ async function startMailSenseApp(options) {
|
|
|
175
270
|
meta: { agentName: options.agentName, error: error instanceof Error ? error.message : String(error) },
|
|
176
271
|
});
|
|
177
272
|
}
|
|
273
|
+
try {
|
|
274
|
+
const importDiscovery = await scanMailImportDiscoveryAttention({
|
|
275
|
+
agentName: options.agentName,
|
|
276
|
+
pendingDir: (0, pending_1.getInnerDialogPendingDir)(options.agentName),
|
|
277
|
+
statePath: importDiscoveryPath,
|
|
278
|
+
now,
|
|
279
|
+
});
|
|
280
|
+
if (importDiscovery.queued) {
|
|
281
|
+
queuedCount += 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
(0, runtime_1.emitNervesEvent)({
|
|
286
|
+
level: "error",
|
|
287
|
+
component: "senses",
|
|
288
|
+
event: "senses.mail_import_discovery_scan_error",
|
|
289
|
+
message: "mail import discovery scan failed",
|
|
290
|
+
meta: { agentName: options.agentName, error: error instanceof Error ? error.message : String(error) },
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
lastScanAt = scanStartedAt;
|
|
294
|
+
lastQueuedCount = queuedCount;
|
|
295
|
+
writeRuntimeState(runtimePath, {
|
|
296
|
+
schemaVersion: 1,
|
|
297
|
+
agentName: options.agentName,
|
|
298
|
+
status: "running",
|
|
299
|
+
mailboxAddress: resolved.config.mailboxAddress,
|
|
300
|
+
smtpPort: activeSmtpPort(),
|
|
301
|
+
httpPort: activeHttpPort(),
|
|
302
|
+
host,
|
|
303
|
+
storeKind: resolved.storeKind,
|
|
304
|
+
storeLabel: resolved.storeLabel,
|
|
305
|
+
lastScanAt,
|
|
306
|
+
lastQueuedCount,
|
|
307
|
+
updatedAt: new Date(now()).toISOString(),
|
|
308
|
+
});
|
|
178
309
|
};
|
|
179
310
|
await scan();
|
|
180
311
|
const intervalMs = Math.max(5_000, resolved.config.attentionIntervalMs ?? 30_000);
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -357,7 +357,7 @@ async function handleInboundTurn(input) {
|
|
|
357
357
|
});
|
|
358
358
|
// Propagate sync failure from pre-turn pull
|
|
359
359
|
ctx.syncFailure = syncFailure;
|
|
360
|
-
const { activeBridges, sessionActivity, pendingObligations, codingSessions, otherCodingSessions } = ctx;
|
|
360
|
+
const { activeBridges, sessionActivity, pendingObligations, codingSessions, otherCodingSessions, backgroundOperations } = ctx;
|
|
361
361
|
const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
|
|
362
362
|
const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
|
|
363
363
|
currentSession,
|
|
@@ -366,6 +366,7 @@ async function handleInboundTurn(input) {
|
|
|
366
366
|
inner: ctx.innerWorkState,
|
|
367
367
|
bridges: activeBridges,
|
|
368
368
|
codingSessions,
|
|
369
|
+
backgroundOperations,
|
|
369
370
|
otherCodingSessions,
|
|
370
371
|
pendingObligations,
|
|
371
372
|
taskBoard: ctx.taskBoard,
|