@ouro.bot/cli 0.1.0-alpha.242 → 0.1.0-alpha.244
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 +14 -0
- package/dist/arc/episodes.js +0 -2
- package/dist/arc/json-store.js +0 -3
- package/dist/heart/daemon/agent-discovery.js +34 -0
- package/dist/heart/daemon/cli-render.js +46 -10
- package/dist/heart/daemon/daemon.js +3 -4
- package/dist/heart/sync.js +38 -55
- package/dist/mind/diary.js +0 -5
- package/dist/senses/cli/ouro-tui.js +34 -12
- package/dist/senses/cli/tui-store.js +24 -0
- package/dist/senses/cli.js +11 -1
- package/dist/senses/pipeline.js +408 -415
- package/package.json +1 -1
- package/dist/senses/cli/adapter.js +0 -274
package/changelog.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
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.244",
|
|
6
|
+
"changes": [
|
|
7
|
+
"refactor(sync): git-status-based bundle sync \u2014 postTurnPush discovers dirty files via `git status --porcelain` instead of broken explicit trackSyncWrite tracking (only 3/9 writers used it). Removed dead infrastructure. Remote push optional and non-fatal.",
|
|
8
|
+
"feat(tui): queued input steer \u2014 messages typed while agent is thinking appear dimmed above input area. UP/ESC pops all queued messages back into input for editing. Placeholder hint. Multiple messages supported, each sent as separate turn.",
|
|
9
|
+
"chore: deleted legacy InkCliApp adapter (776 lines dead code)."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.243",
|
|
14
|
+
"changes": [
|
|
15
|
+
"fix(daemon): per-agent Git Sync in `ouro status` \u2014 was always showing `disabled` regardless of any agent's `agent.json`, because the daemon process has no argv-derived agent identity so `getSyncConfig()` fell into its catch. Status payload now carries a per-agent `sync` array (one row per enabled bundle) rendered as its own section like Senses and Workers, instead of a single global field on the overview block."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.241",
|
|
6
20
|
"changes": [
|
package/dist/arc/episodes.js
CHANGED
|
@@ -38,7 +38,6 @@ exports.readRecentEpisodes = readRecentEpisodes;
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../nerves/runtime");
|
|
41
|
-
const sync_1 = require("../heart/sync");
|
|
42
41
|
function episodesDir(agentRoot) {
|
|
43
42
|
return path.join(agentRoot, "arc", "episodes");
|
|
44
43
|
}
|
|
@@ -64,7 +63,6 @@ function emitEpisode(agentRoot, input) {
|
|
|
64
63
|
fs.mkdirSync(dir, { recursive: true });
|
|
65
64
|
const filePath = path.join(dir, `${id}.json`);
|
|
66
65
|
fs.writeFileSync(filePath, JSON.stringify(episode, null, 2), "utf-8");
|
|
67
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
68
66
|
(0, runtime_1.emitNervesEvent)({
|
|
69
67
|
component: "mind",
|
|
70
68
|
event: "mind.episode_emitted",
|
package/dist/arc/json-store.js
CHANGED
|
@@ -47,7 +47,6 @@ exports.readJsonFileOrThrow = readJsonFileOrThrow;
|
|
|
47
47
|
exports.writeJsonFile = writeJsonFile;
|
|
48
48
|
const fs = __importStar(require("fs"));
|
|
49
49
|
const path = __importStar(require("path"));
|
|
50
|
-
const sync_1 = require("../heart/sync");
|
|
51
50
|
/**
|
|
52
51
|
* Generate a timestamped random ID.
|
|
53
52
|
* @param prefix Optional prefix (e.g. "care", "int"). Omit for plain timestamp IDs.
|
|
@@ -110,11 +109,9 @@ function readJsonFileOrThrow(dir, id, label) {
|
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
111
|
* Write a record to a JSON file, creating the directory if needed.
|
|
113
|
-
* Automatically calls trackSyncWrite.
|
|
114
112
|
*/
|
|
115
113
|
function writeJsonFile(dir, id, record) {
|
|
116
114
|
fs.mkdirSync(dir, { recursive: true });
|
|
117
115
|
const filePath = path.join(dir, `${id}.json`);
|
|
118
116
|
fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
119
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
120
117
|
}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.listEnabledBundleAgents = listEnabledBundleAgents;
|
|
37
|
+
exports.listBundleSyncRows = listBundleSyncRows;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const identity_1 = require("../identity");
|
|
@@ -79,3 +80,36 @@ function listEnabledBundleAgents(options = {}) {
|
|
|
79
80
|
}
|
|
80
81
|
return discovered.sort((left, right) => left.localeCompare(right));
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Read the per-agent sync block from each enabled bundle's agent.json.
|
|
85
|
+
* Used by the daemon (and stopped-state status renderer) to build per-agent
|
|
86
|
+
* sync rows without depending on argv-derived global identity. Bundles that
|
|
87
|
+
* cannot be read are skipped silently.
|
|
88
|
+
*/
|
|
89
|
+
function listBundleSyncRows(options = {}) {
|
|
90
|
+
const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
91
|
+
const readFileSync = options.readFileSync ?? fs.readFileSync;
|
|
92
|
+
const agents = listEnabledBundleAgents(options);
|
|
93
|
+
const rows = [];
|
|
94
|
+
for (const agent of agents) {
|
|
95
|
+
const configPath = path.join(bundlesRoot, `${agent}.ouro`, "agent.json");
|
|
96
|
+
let enabled = false;
|
|
97
|
+
let remote = "origin";
|
|
98
|
+
try {
|
|
99
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
100
|
+
const parsed = JSON.parse(raw);
|
|
101
|
+
if (parsed.sync && typeof parsed.sync === "object") {
|
|
102
|
+
if (typeof parsed.sync.enabled === "boolean")
|
|
103
|
+
enabled = parsed.sync.enabled;
|
|
104
|
+
if (typeof parsed.sync.remote === "string" && parsed.sync.remote.length > 0) {
|
|
105
|
+
remote = parsed.sync.remote;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Best-effort: bundle without readable config still gets a row with defaults
|
|
111
|
+
}
|
|
112
|
+
rows.push({ agent, enabled, remote });
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
}
|
|
@@ -54,6 +54,7 @@ const runtime_mode_1 = require("./runtime-mode");
|
|
|
54
54
|
const identity_1 = require("../identity");
|
|
55
55
|
const daemon_tombstone_1 = require("./daemon-tombstone");
|
|
56
56
|
const daemon_health_1 = require("./daemon-health");
|
|
57
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
57
58
|
// ── Field extractors ──
|
|
58
59
|
function stringField(value) {
|
|
59
60
|
return typeof value === "string" ? value : null;
|
|
@@ -72,10 +73,14 @@ function parseStatusPayload(data) {
|
|
|
72
73
|
const overview = raw.overview;
|
|
73
74
|
const senses = raw.senses;
|
|
74
75
|
const workers = raw.workers;
|
|
76
|
+
const sync = raw.sync;
|
|
75
77
|
if (!overview || typeof overview !== "object" || Array.isArray(overview))
|
|
76
78
|
return null;
|
|
77
79
|
if (!Array.isArray(senses) || !Array.isArray(workers))
|
|
78
80
|
return null;
|
|
81
|
+
// sync is optional for backward compatibility — older daemons may omit it
|
|
82
|
+
if (sync !== undefined && !Array.isArray(sync))
|
|
83
|
+
return null;
|
|
79
84
|
const parsedOverview = {
|
|
80
85
|
daemon: stringField(overview.daemon) ?? "unknown",
|
|
81
86
|
health: stringField(overview.health) ?? "unknown",
|
|
@@ -89,8 +94,6 @@ function parseStatusPayload(data) {
|
|
|
89
94
|
senseCount: numberField(overview.senseCount) ?? 0,
|
|
90
95
|
entryPath: stringField(overview.entryPath) ?? "unknown",
|
|
91
96
|
mode: stringField(overview.mode) ?? "unknown",
|
|
92
|
-
syncEnabled: booleanField(overview.syncEnabled) ?? false,
|
|
93
|
-
syncRemote: stringField(overview.syncRemote) ?? "origin",
|
|
94
97
|
};
|
|
95
98
|
const parsedSenses = senses.map((entry) => {
|
|
96
99
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
@@ -135,12 +138,26 @@ function parseStatusPayload(data) {
|
|
|
135
138
|
lastSignal: stringField(row.lastSignal) ?? null,
|
|
136
139
|
};
|
|
137
140
|
});
|
|
138
|
-
|
|
141
|
+
const parsedSync = (sync ?? []).map((entry) => {
|
|
142
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
143
|
+
return null;
|
|
144
|
+
const row = entry;
|
|
145
|
+
const agent = stringField(row.agent);
|
|
146
|
+
const enabled = booleanField(row.enabled);
|
|
147
|
+
const remote = stringField(row.remote);
|
|
148
|
+
if (!agent || enabled === null || !remote)
|
|
149
|
+
return null;
|
|
150
|
+
return { agent, enabled, remote };
|
|
151
|
+
});
|
|
152
|
+
if (parsedSenses.some((row) => row === null) ||
|
|
153
|
+
parsedWorkers.some((row) => row === null) ||
|
|
154
|
+
parsedSync.some((row) => row === null))
|
|
139
155
|
return null;
|
|
140
156
|
return {
|
|
141
157
|
overview: parsedOverview,
|
|
142
158
|
senses: parsedSenses,
|
|
143
159
|
workers: parsedWorkers,
|
|
160
|
+
sync: parsedSync,
|
|
144
161
|
};
|
|
145
162
|
}
|
|
146
163
|
// ── ANSI color helpers (private) ──
|
|
@@ -232,9 +249,6 @@ function formatDaemonStatusOutput(response, fallback) {
|
|
|
232
249
|
lines.push(kvLine("Socket", ov.socketPath));
|
|
233
250
|
lines.push(kvLine("Outlook", ov.outlookUrl));
|
|
234
251
|
lines.push(kvLine("Health", `${statusDot(ov.health)} ${ov.health}`));
|
|
235
|
-
/* v8 ignore next -- cosmetic: sync-enabled branch requires agent with sync configured @preserve */
|
|
236
|
-
const syncLabel = ov.syncEnabled ? `${green("●")} enabled (remote: ${ov.syncRemote})` : `${dim("○")} disabled`;
|
|
237
|
-
lines.push(kvLine("Git Sync", syncLabel));
|
|
238
252
|
lines.push(kvLine("Updated", ov.lastUpdated));
|
|
239
253
|
lines.push("");
|
|
240
254
|
// ── Senses ──
|
|
@@ -295,6 +309,19 @@ function formatDaemonStatusOutput(response, fallback) {
|
|
|
295
309
|
}
|
|
296
310
|
lines.push("");
|
|
297
311
|
}
|
|
312
|
+
// ── Git Sync (per agent) ──
|
|
313
|
+
if (payload.sync.length > 0) {
|
|
314
|
+
lines.push(` ${teal("──")} ${bold("Git Sync")} ${teal("─".repeat(35))}`);
|
|
315
|
+
const agentNameWidth = Math.max(12, ...payload.sync.map((r) => r.agent.length));
|
|
316
|
+
for (const row of payload.sync) {
|
|
317
|
+
const name = row.agent.padEnd(agentNameWidth);
|
|
318
|
+
const dot = row.enabled ? green("●") : dim("○");
|
|
319
|
+
const stateText = (row.enabled ? "enabled " : "disabled").padEnd(10);
|
|
320
|
+
const detail = row.enabled ? `remote: ${row.remote}` : "";
|
|
321
|
+
lines.push(` ${name} ${dot} ${stateText} ${dim(detail)}`);
|
|
322
|
+
}
|
|
323
|
+
lines.push("");
|
|
324
|
+
}
|
|
298
325
|
return lines.join("\n");
|
|
299
326
|
}
|
|
300
327
|
function formatVersionOutput() {
|
|
@@ -304,7 +331,7 @@ function formatVersionOutput() {
|
|
|
304
331
|
return mode === "dev" ? `${version} (dev)` : version;
|
|
305
332
|
/* v8 ignore stop */
|
|
306
333
|
}
|
|
307
|
-
function buildStoppedStatusPayload(socketPath) {
|
|
334
|
+
function buildStoppedStatusPayload(socketPath, syncRows = []) {
|
|
308
335
|
const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
309
336
|
const repoRoot = (0, identity_1.getRepoRoot)();
|
|
310
337
|
return {
|
|
@@ -321,14 +348,23 @@ function buildStoppedStatusPayload(socketPath) {
|
|
|
321
348
|
senseCount: 0,
|
|
322
349
|
entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
|
|
323
350
|
mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
|
|
324
|
-
syncEnabled: false,
|
|
325
|
-
syncRemote: "origin",
|
|
326
351
|
},
|
|
327
352
|
senses: [],
|
|
328
353
|
workers: [],
|
|
354
|
+
sync: syncRows,
|
|
329
355
|
};
|
|
330
356
|
}
|
|
331
357
|
function daemonUnavailableStatusOutput(socketPath, healthFilePath) {
|
|
358
|
+
// Read per-agent sync config from disk so the user still sees it when the daemon is down.
|
|
359
|
+
// Best-effort: any fs error returns [] and the section is omitted.
|
|
360
|
+
let syncRows = [];
|
|
361
|
+
try {
|
|
362
|
+
syncRows = (0, agent_discovery_1.listBundleSyncRows)();
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// listBundleSyncRows already swallows fs errors internally; this catch is a defensive
|
|
366
|
+
// safety net for environments where the fs module itself is partially mocked.
|
|
367
|
+
}
|
|
332
368
|
/* v8 ignore start — tombstone read tested in daemon-status-tombstone.test; branch misreported @preserve */
|
|
333
369
|
const tombstone = (0, daemon_tombstone_1.readDaemonTombstone)();
|
|
334
370
|
const deathLine = tombstone
|
|
@@ -339,7 +375,7 @@ function daemonUnavailableStatusOutput(socketPath, healthFilePath) {
|
|
|
339
375
|
formatDaemonStatusOutput({
|
|
340
376
|
ok: true,
|
|
341
377
|
summary: "daemon not running",
|
|
342
|
-
data: buildStoppedStatusPayload(socketPath),
|
|
378
|
+
data: buildStoppedStatusPayload(socketPath, syncRows),
|
|
343
379
|
}, "daemon not running"),
|
|
344
380
|
"",
|
|
345
381
|
];
|
|
@@ -42,7 +42,7 @@ const net = __importStar(require("net"));
|
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const identity_1 = require("../identity");
|
|
45
|
-
const
|
|
45
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
46
46
|
const runtime_1 = require("../../nerves/runtime");
|
|
47
47
|
const runtime_metadata_1 = require("./runtime-metadata");
|
|
48
48
|
const runtime_mode_1 = require("./runtime-mode");
|
|
@@ -234,7 +234,7 @@ class OuroDaemon {
|
|
|
234
234
|
const workers = buildWorkerRows(snapshots);
|
|
235
235
|
const senses = this.senseManager?.listSenseRows() ?? [];
|
|
236
236
|
const repoRoot = (0, identity_1.getRepoRoot)();
|
|
237
|
-
const
|
|
237
|
+
const sync = (0, agent_discovery_1.listBundleSyncRows)({ bundlesRoot: this.bundlesRoot });
|
|
238
238
|
return {
|
|
239
239
|
overview: {
|
|
240
240
|
daemon: "running",
|
|
@@ -246,11 +246,10 @@ class OuroDaemon {
|
|
|
246
246
|
senseCount: senses.length,
|
|
247
247
|
entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
|
|
248
248
|
mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
|
|
249
|
-
syncEnabled: syncConfig.enabled,
|
|
250
|
-
syncRemote: syncConfig.remote,
|
|
251
249
|
},
|
|
252
250
|
workers,
|
|
253
251
|
senses,
|
|
252
|
+
sync,
|
|
254
253
|
};
|
|
255
254
|
}
|
|
256
255
|
async start() {
|
package/dist/heart/sync.js
CHANGED
|
@@ -33,48 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.runWithSyncContext = runWithSyncContext;
|
|
37
|
-
exports.trackSyncWrite = trackSyncWrite;
|
|
38
|
-
exports.drainSyncWrites = drainSyncWrites;
|
|
39
36
|
exports.preTurnPull = preTurnPull;
|
|
40
37
|
exports.postTurnPush = postTurnPush;
|
|
41
|
-
const node_async_hooks_1 = require("node:async_hooks");
|
|
42
38
|
const child_process_1 = require("child_process");
|
|
43
39
|
const fs = __importStar(require("fs"));
|
|
44
40
|
const path = __importStar(require("path"));
|
|
45
41
|
const runtime_1 = require("../nerves/runtime");
|
|
46
|
-
/**
|
|
47
|
-
* Turn-scoped write tracker using AsyncLocalStorage.
|
|
48
|
-
* Each turn runs inside its own async context (via runWithSyncContext),
|
|
49
|
-
* so concurrent turns from different senses never share or clobber each
|
|
50
|
-
* other's tracked writes.
|
|
51
|
-
*
|
|
52
|
-
* Continuity writers (episodes, obligations, cares, intentions, diary)
|
|
53
|
-
* call trackSyncWrite() after every fs write. drainSyncWrites() returns
|
|
54
|
-
* the current turn's accumulated set and clears it.
|
|
55
|
-
*/
|
|
56
|
-
const syncContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
57
|
-
/**
|
|
58
|
-
* Run a function inside its own sync-write tracking context.
|
|
59
|
-
* All trackSyncWrite / drainSyncWrites calls within `fn` (including
|
|
60
|
-
* across awaits) operate on a private Set scoped to this invocation.
|
|
61
|
-
*/
|
|
62
|
-
function runWithSyncContext(fn) {
|
|
63
|
-
return syncContext.run(new Set(), fn);
|
|
64
|
-
}
|
|
65
|
-
/** Register an absolute file path written during this turn. No-op if called outside a sync context. */
|
|
66
|
-
function trackSyncWrite(absolutePath) {
|
|
67
|
-
syncContext.getStore()?.add(absolutePath);
|
|
68
|
-
}
|
|
69
|
-
/** Return all paths written this turn and clear the set. Returns [] if called outside a sync context. */
|
|
70
|
-
function drainSyncWrites() {
|
|
71
|
-
const store = syncContext.getStore();
|
|
72
|
-
if (!store)
|
|
73
|
-
return [];
|
|
74
|
-
const paths = [...store];
|
|
75
|
-
store.clear();
|
|
76
|
-
return paths;
|
|
77
|
-
}
|
|
78
42
|
/**
|
|
79
43
|
* Pre-turn pull: sync the agent bundle from remote before assembling the start-of-turn packet.
|
|
80
44
|
*/
|
|
@@ -111,42 +75,47 @@ function preTurnPull(agentRoot, config) {
|
|
|
111
75
|
}
|
|
112
76
|
}
|
|
113
77
|
/**
|
|
114
|
-
* Post-turn push:
|
|
115
|
-
*
|
|
116
|
-
*
|
|
78
|
+
* Post-turn push: discover dirty files via `git status`, commit, and push.
|
|
79
|
+
* Uses git-status-based discovery instead of explicit path tracking, ensuring
|
|
80
|
+
* all file writers are captured regardless of whether they call a tracking API.
|
|
117
81
|
*/
|
|
118
|
-
function postTurnPush(agentRoot, config
|
|
82
|
+
function postTurnPush(agentRoot, config) {
|
|
119
83
|
(0, runtime_1.emitNervesEvent)({
|
|
120
84
|
component: "heart",
|
|
121
85
|
event: "heart.sync_push_start",
|
|
122
86
|
message: "post-turn push starting",
|
|
123
87
|
meta: { agentRoot, remote: config.remote },
|
|
124
88
|
});
|
|
125
|
-
|
|
89
|
+
let statusOutput;
|
|
90
|
+
try {
|
|
91
|
+
statusOutput = (0, child_process_1.execFileSync)("git", ["status", "--porcelain"], {
|
|
92
|
+
cwd: agentRoot,
|
|
93
|
+
stdio: "pipe",
|
|
94
|
+
timeout: 10000,
|
|
95
|
+
}).toString().trim();
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
126
99
|
(0, runtime_1.emitNervesEvent)({
|
|
127
100
|
component: "heart",
|
|
128
|
-
event: "heart.
|
|
129
|
-
message: "post-turn push:
|
|
130
|
-
meta: { agentRoot },
|
|
101
|
+
event: "heart.sync_push_error",
|
|
102
|
+
message: "post-turn push: git status failed",
|
|
103
|
+
meta: { agentRoot, error },
|
|
131
104
|
});
|
|
132
|
-
return { ok:
|
|
105
|
+
return { ok: false, error };
|
|
133
106
|
}
|
|
134
|
-
|
|
135
|
-
const relativePaths = writtenPaths
|
|
136
|
-
.map((p) => path.relative(agentRoot, p))
|
|
137
|
-
.filter((p) => p.length > 0 && !p.startsWith(".."));
|
|
138
|
-
if (relativePaths.length === 0) {
|
|
107
|
+
if (statusOutput.length === 0) {
|
|
139
108
|
(0, runtime_1.emitNervesEvent)({
|
|
140
109
|
component: "heart",
|
|
141
110
|
event: "heart.sync_push_end",
|
|
142
|
-
message: "post-turn push: no
|
|
111
|
+
message: "post-turn push: no changes to sync",
|
|
143
112
|
meta: { agentRoot },
|
|
144
113
|
});
|
|
145
114
|
return { ok: true };
|
|
146
115
|
}
|
|
116
|
+
const changedCount = statusOutput.split("\n").length;
|
|
147
117
|
try {
|
|
148
|
-
|
|
149
|
-
(0, child_process_1.execFileSync)("git", ["add", "--", ...relativePaths], {
|
|
118
|
+
(0, child_process_1.execFileSync)("git", ["add", "-A"], {
|
|
150
119
|
cwd: agentRoot,
|
|
151
120
|
stdio: "pipe",
|
|
152
121
|
timeout: 10000,
|
|
@@ -156,6 +125,21 @@ function postTurnPush(agentRoot, config, writtenPaths) {
|
|
|
156
125
|
stdio: "pipe",
|
|
157
126
|
timeout: 10000,
|
|
158
127
|
});
|
|
128
|
+
// Check if a remote exists
|
|
129
|
+
const remoteOutput = (0, child_process_1.execFileSync)("git", ["remote"], {
|
|
130
|
+
cwd: agentRoot,
|
|
131
|
+
stdio: "pipe",
|
|
132
|
+
timeout: 5000,
|
|
133
|
+
}).toString().trim();
|
|
134
|
+
if (remoteOutput.length === 0) {
|
|
135
|
+
(0, runtime_1.emitNervesEvent)({
|
|
136
|
+
component: "heart",
|
|
137
|
+
event: "heart.sync_push_end",
|
|
138
|
+
message: "post-turn push: committed locally, no remote configured",
|
|
139
|
+
meta: { agentRoot, changedCount },
|
|
140
|
+
});
|
|
141
|
+
return { ok: true };
|
|
142
|
+
}
|
|
159
143
|
try {
|
|
160
144
|
(0, child_process_1.execFileSync)("git", ["push", config.remote], {
|
|
161
145
|
cwd: agentRoot,
|
|
@@ -185,7 +169,6 @@ function postTurnPush(agentRoot, config, writtenPaths) {
|
|
|
185
169
|
fs.writeFileSync(pendingSyncPath, JSON.stringify({
|
|
186
170
|
error: retryError,
|
|
187
171
|
failedAt: new Date().toISOString(),
|
|
188
|
-
paths: relativePaths,
|
|
189
172
|
}, null, 2), "utf-8");
|
|
190
173
|
(0, runtime_1.emitNervesEvent)({
|
|
191
174
|
component: "heart",
|
|
@@ -200,7 +183,7 @@ function postTurnPush(agentRoot, config, writtenPaths) {
|
|
|
200
183
|
component: "heart",
|
|
201
184
|
event: "heart.sync_push_end",
|
|
202
185
|
message: "post-turn push complete",
|
|
203
|
-
meta: { agentRoot, changedCount
|
|
186
|
+
meta: { agentRoot, changedCount },
|
|
204
187
|
});
|
|
205
188
|
return { ok: true };
|
|
206
189
|
}
|
package/dist/mind/diary.js
CHANGED
|
@@ -45,7 +45,6 @@ const path = __importStar(require("path"));
|
|
|
45
45
|
const crypto_1 = require("crypto");
|
|
46
46
|
const identity_1 = require("../heart/identity");
|
|
47
47
|
const runtime_1 = require("../nerves/runtime");
|
|
48
|
-
const sync_1 = require("../heart/sync");
|
|
49
48
|
const associative_recall_1 = require("./associative-recall");
|
|
50
49
|
const embedding_provider_1 = require("./embedding-provider");
|
|
51
50
|
const DEDUP_THRESHOLD = 0.6;
|
|
@@ -175,11 +174,8 @@ function appendEntriesWithDedup(stores, incoming, options) {
|
|
|
175
174
|
all.push(fact);
|
|
176
175
|
added++;
|
|
177
176
|
fs.appendFileSync(stores.factsPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
178
|
-
(0, sync_1.trackSyncWrite)(stores.factsPath);
|
|
179
177
|
updateEntityIndex(stores.entitiesPath, fact);
|
|
180
|
-
(0, sync_1.trackSyncWrite)(stores.entitiesPath);
|
|
181
178
|
appendDailyFact(stores.dailyDir, fact);
|
|
182
|
-
(0, sync_1.trackSyncWrite)(path.join(stores.dailyDir, `${(fact.createdAt.slice(0, 10) || "unknown")}.jsonl`));
|
|
183
179
|
}
|
|
184
180
|
(0, runtime_1.emitNervesEvent)({
|
|
185
181
|
component: "mind",
|
|
@@ -295,7 +291,6 @@ async function backfillEmbeddings(options) {
|
|
|
295
291
|
// Rewrite facts file with updated embeddings
|
|
296
292
|
const lines = facts.map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
297
293
|
fs.writeFileSync(factsPath, lines, "utf8");
|
|
298
|
-
(0, sync_1.trackSyncWrite)(factsPath);
|
|
299
294
|
(0, runtime_1.emitNervesEvent)({
|
|
300
295
|
component: "mind",
|
|
301
296
|
event: "mind.diary_backfill_complete",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueuedMessages = QueuedMessages;
|
|
3
4
|
exports.OuroTui = OuroTui;
|
|
4
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
6
|
/**
|
|
@@ -141,8 +142,14 @@ function ActiveToolLine({ tool }) {
|
|
|
141
142
|
function LiveArea({ live, elapsed }) {
|
|
142
143
|
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [live.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: live.streamingText, maxWidth: safeWidth() })) : null, live.activeTool ? ((0, jsx_runtime_1.jsx)(ActiveToolLine, { tool: live.activeTool })) : null, live.loading ? ((0, jsx_runtime_1.jsx)(Spinner, { phrasePool: live.spinnerPhrasePool, elapsed: elapsed })) : null, live.errorMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.fang, children: ["✗ ", live.errorMessage] })) : null, live.kickMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.gold, children: ["↻ ", live.kickMessage] })) : null] }));
|
|
143
144
|
}
|
|
145
|
+
// ─── Queued Messages ───────────────────────────────────────────────
|
|
146
|
+
function QueuedMessages({ items }) {
|
|
147
|
+
if (items.length === 0)
|
|
148
|
+
return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
|
|
149
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: items.map((text, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "\u231B queued: " }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.mist, children: ["\"", text, "\""] })] }, i))) }));
|
|
150
|
+
}
|
|
144
151
|
// ─── Input ──────────────────────────────────────────────────────────
|
|
145
|
-
function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
|
|
152
|
+
function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agentName, model }) {
|
|
146
153
|
const [input, setInput] = (0, react_1.useState)("");
|
|
147
154
|
const [cursorPos, setCursorPos] = (0, react_1.useState)(0); // cursor position within input
|
|
148
155
|
const [tooltip, setTooltip] = (0, react_1.useState)("");
|
|
@@ -202,6 +209,12 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
|
|
|
202
209
|
clearTimeout(tooltipTimerRef.current);
|
|
203
210
|
tooltipTimerRef.current = setTimeout(() => setTooltip(""), 2000);
|
|
204
211
|
}
|
|
212
|
+
else if (queuedInputs.length > 0) {
|
|
213
|
+
// Pop all queued messages into input for editing
|
|
214
|
+
const items = onPopQueue();
|
|
215
|
+
updateInput(items.join("\n"));
|
|
216
|
+
historyIdx.current = -1;
|
|
217
|
+
}
|
|
205
218
|
else {
|
|
206
219
|
setTooltip("");
|
|
207
220
|
}
|
|
@@ -271,16 +284,25 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
|
|
|
271
284
|
}
|
|
272
285
|
return;
|
|
273
286
|
}
|
|
274
|
-
// Up/Down: history
|
|
275
|
-
if (key.upArrow
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
287
|
+
// Up/Down: queue pop takes priority over history
|
|
288
|
+
if (key.upArrow) {
|
|
289
|
+
// If not already browsing history and queue has items, pop queue into input
|
|
290
|
+
if (historyIdx.current === -1 && queuedInputs.length > 0) {
|
|
291
|
+
const items = onPopQueue();
|
|
292
|
+
updateInput(items.join("\n"));
|
|
293
|
+
return;
|
|
279
294
|
}
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
// Otherwise, browse history
|
|
296
|
+
if (history.length > 0) {
|
|
297
|
+
if (historyIdx.current === -1) {
|
|
298
|
+
savedInput.current = inputRef.current;
|
|
299
|
+
historyIdx.current = history.length - 1;
|
|
300
|
+
}
|
|
301
|
+
else if (historyIdx.current > 0) {
|
|
302
|
+
historyIdx.current--;
|
|
303
|
+
}
|
|
304
|
+
updateInput(history[historyIdx.current]);
|
|
282
305
|
}
|
|
283
|
-
updateInput(history[historyIdx.current]);
|
|
284
306
|
return;
|
|
285
307
|
}
|
|
286
308
|
if (key.downArrow) {
|
|
@@ -345,14 +367,14 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
|
|
|
345
367
|
const cols = safeWidth();
|
|
346
368
|
const isMultiline = input.includes("\n");
|
|
347
369
|
const inputLines = input.split("\n");
|
|
348
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })) : null] }, i))) })) : ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(0, cursorPos) }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input[cursorPos] ?? " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(cursorPos + (cursorVisible ? 0 : 1)) })] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [agentName, model ? ` · ${model}` : "", " \u00B7 /help"] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : null] })] }));
|
|
370
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })) : null] }, i))) })) : ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), !input && queuedInputs.length > 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "Press up to edit queued messages" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(0, cursorPos) }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input[cursorPos] ?? " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(cursorPos + (cursorVisible ? 0 : 1)) })] }))] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [agentName, model ? ` · ${model}` : "", " \u00B7 /help"] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : null] })] }));
|
|
349
371
|
}
|
|
350
372
|
// ─── Main TUI Component ─────────────────────────────────────────────
|
|
351
|
-
function OuroTui({ agentName, model, completedMessages, inputHistory, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, cwd, }) {
|
|
373
|
+
function OuroTui({ agentName, model, completedMessages, inputHistory, queuedInputs, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, onPopQueue, cwd, }) {
|
|
352
374
|
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Static, { items: [{ id: "__header__" }, ...completedMessages], children: (item, index) => {
|
|
353
375
|
if (index === 0) {
|
|
354
376
|
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 2, children: [(0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, dimColor: true, children: " Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear \u00b7 Alt+Enter newline" })] }, "header"));
|
|
355
377
|
}
|
|
356
378
|
return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
|
|
357
|
-
} }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, onCtrlC: onCtrlC, history: inputHistory, agentName: agentName, model: model })] }));
|
|
379
|
+
} }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, queuedInputs.length > 0 ? (0, jsx_runtime_1.jsx)(QueuedMessages, { items: queuedInputs }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, onCtrlC: onCtrlC, history: inputHistory, queuedInputs: queuedInputs, onPopQueue: onPopQueue, agentName: agentName, model: model })] }));
|
|
358
380
|
}
|
|
@@ -32,10 +32,12 @@ class TuiStore {
|
|
|
32
32
|
_listeners = new Set();
|
|
33
33
|
_headerShown = false;
|
|
34
34
|
_inputHistory = [];
|
|
35
|
+
_queuedInputs = [];
|
|
35
36
|
get completedMessages() { return this._completed; }
|
|
36
37
|
get live() { return this._live; }
|
|
37
38
|
get headerShown() { return this._headerShown; }
|
|
38
39
|
get inputHistory() { return this._inputHistory; }
|
|
40
|
+
get queuedInputs() { return this._queuedInputs; }
|
|
39
41
|
/** Elapsed seconds since spinner started — computed on demand, not stored */
|
|
40
42
|
getElapsed() {
|
|
41
43
|
if (this._live.loading && this._spinnerStart > 0) {
|
|
@@ -171,6 +173,28 @@ class TuiStore {
|
|
|
171
173
|
seedHistory(texts) {
|
|
172
174
|
this._inputHistory.push(...texts);
|
|
173
175
|
}
|
|
176
|
+
// ─── Queued input display ────────────────────────────────────────
|
|
177
|
+
enqueueInput(text) {
|
|
178
|
+
this._queuedInputs.push(text);
|
|
179
|
+
this.notify();
|
|
180
|
+
}
|
|
181
|
+
dequeueInput(text) {
|
|
182
|
+
const idx = this._queuedInputs.indexOf(text);
|
|
183
|
+
if (idx !== -1) {
|
|
184
|
+
this._queuedInputs.splice(idx, 1);
|
|
185
|
+
}
|
|
186
|
+
this.notify();
|
|
187
|
+
}
|
|
188
|
+
popAllQueuedForEditing() {
|
|
189
|
+
const copy = [...this._queuedInputs];
|
|
190
|
+
this._queuedInputs = [];
|
|
191
|
+
this.notify();
|
|
192
|
+
return copy;
|
|
193
|
+
}
|
|
194
|
+
clearQueue() {
|
|
195
|
+
this._queuedInputs = [];
|
|
196
|
+
this.notify();
|
|
197
|
+
}
|
|
174
198
|
}
|
|
175
199
|
exports.TuiStore = TuiStore;
|
|
176
200
|
// ─── Callbacks factory ──────────────────────────────────────────────
|
package/dist/senses/cli.js
CHANGED
|
@@ -464,6 +464,12 @@ class InputQueue {
|
|
|
464
464
|
r({ value: undefined, done: true });
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
|
+
/** Drain all buffered items, leaving any pending async awaiter untouched. */
|
|
468
|
+
drainAll() {
|
|
469
|
+
const items = [...this.queue];
|
|
470
|
+
this.queue = [];
|
|
471
|
+
return items;
|
|
472
|
+
}
|
|
467
473
|
[Symbol.asyncIterator]() {
|
|
468
474
|
return {
|
|
469
475
|
next: () => {
|
|
@@ -566,11 +572,13 @@ async function runCliSession(options) {
|
|
|
566
572
|
model: (0, identity_1.loadAgentConfig)().humanFacing?.model ?? "",
|
|
567
573
|
completedMessages: storeRef.completedMessages,
|
|
568
574
|
inputHistory: storeRef.inputHistory,
|
|
575
|
+
queuedInputs: storeRef.queuedInputs,
|
|
569
576
|
live: storeRef.live,
|
|
570
577
|
elapsedSeconds: elapsed,
|
|
571
578
|
contextPercent: 0,
|
|
572
|
-
onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); },
|
|
579
|
+
onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); storeRef.enqueueInput(text); },
|
|
573
580
|
onCtrlC: handleCtrlC,
|
|
581
|
+
onPopQueue: () => { const items = storeRef.popAllQueuedForEditing(); inputQueue.drainAll(); return items; },
|
|
574
582
|
headerShown: storeRef.headerShown,
|
|
575
583
|
cwd: process.cwd().replace(process.env.HOME ?? "", "~"),
|
|
576
584
|
});
|
|
@@ -729,6 +737,8 @@ async function runCliSession(options) {
|
|
|
729
737
|
break;
|
|
730
738
|
if (!input.trim())
|
|
731
739
|
continue;
|
|
740
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
741
|
+
tuiStore?.dequeueInput(input);
|
|
732
742
|
// Optional input gate (e.g. trust gate in main)
|
|
733
743
|
if (options.onInput) {
|
|
734
744
|
const gate = options.onInput(input);
|