@ouro.bot/cli 0.1.0-alpha.920 → 0.1.0-alpha.921
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 +7 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/sense-manager.js +6 -6
- package/dist/senses/shepherd/cmux.js +12 -4
- package/dist/senses/shepherd/owner-notice.js +13 -7
- package/dist/senses/shepherd/sense.js +1 -0
- package/dist/senses/shepherd/terminal-process.js +99 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.921",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Make Teams chat tools share Shepherd’s machine attachment and identify cmux agents by foreground process, folder, workspace and tab title.",
|
|
8
|
+
"Initialize foreground process execution only when attached terminals need it, keeping unrelated prompt and daemon consumers free of lookup startup side effects."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.920",
|
|
6
13
|
"changes": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.921</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -429,11 +429,11 @@ function managedSenseEntry(sense) {
|
|
|
429
429
|
return "senses/shepherd-entry.js";
|
|
430
430
|
return "senses/mail-entry.js";
|
|
431
431
|
}
|
|
432
|
-
function runtimeCredentialBootstrapFor(agent
|
|
432
|
+
function runtimeCredentialBootstrapFor(agent) {
|
|
433
433
|
const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
|
|
434
|
-
|
|
435
|
-
const machineId =
|
|
436
|
-
const machine =
|
|
434
|
+
// Every sense can run chat-turn tools against this machine's attachments.
|
|
435
|
+
const machineId = currentMachineId();
|
|
436
|
+
const machine = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
|
|
437
437
|
const providerPool = (0, provider_credentials_1.readProviderCredentialPool)(agent);
|
|
438
438
|
const providerCredentialRecords = providerPool.ok
|
|
439
439
|
? Object.values(providerPool.pool.providers).filter((record) => !!record)
|
|
@@ -441,7 +441,7 @@ function runtimeCredentialBootstrapFor(agent, sense) {
|
|
|
441
441
|
const bootstrap = {
|
|
442
442
|
agentName: agent,
|
|
443
443
|
runtimeConfig: runtime.ok ? runtime.config : undefined,
|
|
444
|
-
machineRuntimeConfig: machine
|
|
444
|
+
machineRuntimeConfig: machine.ok ? machine.config : undefined,
|
|
445
445
|
machineId,
|
|
446
446
|
providerCredentialRecords: providerCredentialRecords.length > 0 ? providerCredentialRecords : undefined,
|
|
447
447
|
};
|
|
@@ -611,7 +611,7 @@ class DaemonSenseManager {
|
|
|
611
611
|
channel: sense,
|
|
612
612
|
autoStart: true,
|
|
613
613
|
...(sense === "a2a" ? { args: managedA2AArgs(agent), getArgs: () => managedA2AArgs(agent) } : {}),
|
|
614
|
-
getRuntimeCredentialBootstrap: () => runtimeCredentialBootstrapFor(agent
|
|
614
|
+
getRuntimeCredentialBootstrap: () => runtimeCredentialBootstrapFor(agent),
|
|
615
615
|
}));
|
|
616
616
|
});
|
|
617
617
|
this.processManager = options.processManager ?? new process_manager_1.DaemonProcessManager({
|
|
@@ -42,6 +42,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
42
42
|
const os = __importStar(require("node:os"));
|
|
43
43
|
const path = __importStar(require("node:path"));
|
|
44
44
|
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
const terminal_process_1 = require("./terminal-process");
|
|
45
46
|
/**
|
|
46
47
|
* The cmux host (0.64.22 or later). A return is cmux's own "agent stopped" signal: an
|
|
47
48
|
* `agent.hook.Stop` event, which cmux publishes for every agent its hooks cover (Claude Code,
|
|
@@ -128,17 +129,25 @@ function createCmuxHost(client, options = {}) {
|
|
|
128
129
|
};
|
|
129
130
|
async function surfaces() {
|
|
130
131
|
const tree = await client.call("system.tree", { all_windows: true });
|
|
131
|
-
|
|
132
|
+
const entries = list(tree.windows).flatMap((window) => list(window.workspaces).map((workspace) => ({ window, workspace }))).flatMap(({ window, workspace }) => list(workspace.panes).flatMap((pane) => list(pane.surfaces)).map((surface) => ({
|
|
132
133
|
id: str(surface.id) ?? "",
|
|
133
134
|
ref: str(surface.ref) ?? "",
|
|
134
135
|
workspace: str(workspace.ref),
|
|
136
|
+
workspaceTitle: str(workspace.title),
|
|
135
137
|
workspaceId: str(workspace.id) ?? "",
|
|
136
138
|
title: str(surface.title) ?? "",
|
|
139
|
+
tty: str(surface.tty),
|
|
137
140
|
type: str(surface.type) ?? "",
|
|
138
141
|
agent: hooked.get(str(surface.id) ?? "")?.agent ?? null,
|
|
142
|
+
cwd: hooked.get(str(surface.id) ?? "")?.cwd ?? null,
|
|
139
143
|
// The human's terminal: the focused surface of the selected workspace in a visible window.
|
|
140
144
|
focused: surface.focused === true && workspace.selected === true && window.visible !== false,
|
|
141
145
|
})));
|
|
146
|
+
const identities = await (options.identifyTerminals ?? terminal_process_1.identifyTerminalProcesses)(entries.filter((entry) => entry.type === "terminal" && entry.tty).map((entry) => entry.tty));
|
|
147
|
+
return entries.map((entry) => {
|
|
148
|
+
const identity = entry.tty ? identities.get(entry.tty) : undefined;
|
|
149
|
+
return { ...entry, agent: identity?.agent ?? entry.agent, cwd: identity?.cwd ?? entry.cwd };
|
|
150
|
+
});
|
|
142
151
|
}
|
|
143
152
|
async function locate(sessionId) {
|
|
144
153
|
const surface = (await surfaces()).find((entry) => entry.id === sessionId || entry.ref === sessionId);
|
|
@@ -153,7 +162,7 @@ function createCmuxHost(client, options = {}) {
|
|
|
153
162
|
return {
|
|
154
163
|
name: "cmux",
|
|
155
164
|
async list() {
|
|
156
|
-
return (await surfaces()).filter((surface) => surface.type === "terminal").map(({
|
|
165
|
+
return (await surfaces()).filter((surface) => surface.type === "terminal").map(({ workspaceId: _workspaceId, type: _type, ...session }) => session);
|
|
157
166
|
},
|
|
158
167
|
read,
|
|
159
168
|
async prompt(sessionId, line) {
|
|
@@ -316,8 +325,7 @@ function createCmuxHost(client, options = {}) {
|
|
|
316
325
|
}
|
|
317
326
|
else if (last.active && at - last.changedAt >= Math.max(quietMs.get(surface.id) ?? exports.QUIET_MS, hooked.has(surface.id) ? exports.HOOKED_QUIET_MS : 0)) {
|
|
318
327
|
last.active = false;
|
|
319
|
-
|
|
320
|
-
handlers.returned({ sessionId: surface.id, transitionId: `cmux:quiet:${surface.id}:${last.changedAt}`, agent: hook?.agent ?? null, cwd: hook?.cwd ?? null, lastBody: null, backgroundWork: null });
|
|
328
|
+
handlers.returned({ sessionId: surface.id, transitionId: `cmux:quiet:${surface.id}:${last.changedAt}`, agent: surface.agent, cwd: surface.cwd ?? null, title: surface.title, workspaceTitle: surface.workspaceTitle, lastBody: null, backgroundWork: null });
|
|
321
329
|
}
|
|
322
330
|
}
|
|
323
331
|
}
|
|
@@ -78,15 +78,21 @@ function plain(value, max) {
|
|
|
78
78
|
.trim();
|
|
79
79
|
return flat.length <= max ? flat : `${flat.slice(0, max - 1).trimEnd()}…`;
|
|
80
80
|
}
|
|
81
|
-
function who(record,
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
81
|
+
function who(record, location) {
|
|
82
|
+
const session = typeof location === "object" ? location : undefined;
|
|
83
|
+
const agentName = record.agent ?? session?.agent;
|
|
84
|
+
const cwd = record.cwd ?? session?.cwd;
|
|
85
|
+
const agent = agentName ? plain(agentName, NAME_MAX) : "";
|
|
86
|
+
const folder = cwd ? plain(path.basename(cwd), NAME_MAX) : "";
|
|
87
|
+
const ref = typeof location === "string" ? location : session?.ref;
|
|
88
|
+
const handle = plain(ref ?? record.session.slice(0, 8), NAME_MAX);
|
|
89
|
+
const labels = [record.workspaceTitle ?? session?.workspaceTitle, record.title ?? session?.title].filter((label) => !!label).map((label) => plain(label, NAME_MAX));
|
|
90
|
+
const where = labels.length ? `${labels.join(" / ")}; ${handle}` : handle;
|
|
85
91
|
return `${agent || "A coding agent"}${folder ? ` in ${folder}` : ""}${where ? ` (${where})` : ""}`;
|
|
86
92
|
}
|
|
87
93
|
/** One return as one sentence pair: who returned, what Shepherd did, and why. */
|
|
88
|
-
function noticeLine(record,
|
|
89
|
-
const subject = who(record,
|
|
94
|
+
function noticeLine(record, location) {
|
|
95
|
+
const subject = who(record, location);
|
|
90
96
|
const why = `Why: ${plain(record.reason, REASON_MAX) || "no reason given"}`;
|
|
91
97
|
switch (record.kind) {
|
|
92
98
|
case "premature":
|
|
@@ -198,7 +204,7 @@ function createOwnerNotifier(options) {
|
|
|
198
204
|
const refs = new Map();
|
|
199
205
|
try {
|
|
200
206
|
for (const session of await options.listSessions())
|
|
201
|
-
refs.set(session.id, session
|
|
207
|
+
refs.set(session.id, session);
|
|
202
208
|
}
|
|
203
209
|
catch {
|
|
204
210
|
// Without the host's handles, each notice names a short session id instead.
|
|
@@ -204,6 +204,7 @@ async function startShepherdSenseApp(options) {
|
|
|
204
204
|
const record = {
|
|
205
205
|
at: new Date(now()).toISOString(), host: host.name, session: event.sessionId, transition: event.transitionId,
|
|
206
206
|
agent: event.agent, cwd: event.cwd, task: null, kind: "unclear", action: "let_through", reason: "", reply: null, latencyMs: null, inputTokens: null,
|
|
207
|
+
...(event.title !== undefined ? { title: event.title, workspaceTitle: event.workspaceTitle } : {}),
|
|
207
208
|
};
|
|
208
209
|
const state = guardFor(event.sessionId);
|
|
209
210
|
/** False for a return nothing new was decided about: the loop guard still holds, or the same background wait. */
|
|
@@ -0,0 +1,99 @@
|
|
|
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.identifyTerminalProcesses = identifyTerminalProcesses;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const node_util_1 = require("node:util");
|
|
39
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
40
|
+
async function runCommand(command, args) {
|
|
41
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
|
|
42
|
+
const exec = (0, node_util_1.promisify)(execFile);
|
|
43
|
+
const { stdout } = await exec(command, args, { timeout: 3_000, maxBuffer: 4 * 1024 * 1024 });
|
|
44
|
+
return stdout;
|
|
45
|
+
}
|
|
46
|
+
/** One process snapshot and one cwd lookup for all terminals, not a subprocess per polling surface. */
|
|
47
|
+
async function identifyTerminalProcesses(ttys, run = runCommand) {
|
|
48
|
+
const result = new Map();
|
|
49
|
+
if (ttys.length === 0)
|
|
50
|
+
return result;
|
|
51
|
+
const wanted = new Set(ttys.map((tty) => tty.replace(/^\/dev\//, "")));
|
|
52
|
+
const agents = new Map();
|
|
53
|
+
try {
|
|
54
|
+
for (const line of (await run("ps", ["-axo", "pid=,ppid=,pgid=,tpgid=,tty=,comm="])).split("\n")) {
|
|
55
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)\s+(-?\d+)\s+(\S+)\s+(.+)$/);
|
|
56
|
+
if (!match)
|
|
57
|
+
continue;
|
|
58
|
+
const [, pid, parent, group, foreground, tty, command] = match;
|
|
59
|
+
if (!wanted.has(tty) || group !== foreground)
|
|
60
|
+
continue;
|
|
61
|
+
const executable = path.basename(command);
|
|
62
|
+
const agent = executable === "claude" ? "Claude Code" : executable === "copilot" ? "Copilot" : executable === "codex" ? "Codex" : null;
|
|
63
|
+
if (!agent)
|
|
64
|
+
continue;
|
|
65
|
+
const candidates = agents.get(tty) ?? [];
|
|
66
|
+
candidates.push({ pid: Number(pid), parent: Number(parent), agent });
|
|
67
|
+
agents.set(tty, candidates);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.shepherd_process_lookup_error", message: "could not identify terminal foreground agents", meta: { error: String(error) } });
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
const selected = new Map();
|
|
75
|
+
for (const [tty, candidates] of agents) {
|
|
76
|
+
const outer = candidates.find((candidate) => !candidates.some((parent) => parent.pid === candidate.parent));
|
|
77
|
+
selected.set(tty, outer);
|
|
78
|
+
result.set(tty, { agent: outer.agent, cwd: null });
|
|
79
|
+
}
|
|
80
|
+
if (selected.size === 0)
|
|
81
|
+
return result;
|
|
82
|
+
try {
|
|
83
|
+
const cwdByPid = new Map();
|
|
84
|
+
let pid = 0;
|
|
85
|
+
const output = await run("lsof", ["-a", "-p", [...selected.values()].map((agent) => agent.pid).join(","), "-d", "cwd", "-Fn"]);
|
|
86
|
+
for (const line of output.split("\n")) {
|
|
87
|
+
if (line.startsWith("p"))
|
|
88
|
+
pid = Number(line.slice(1));
|
|
89
|
+
else if (line.startsWith("n") && pid)
|
|
90
|
+
cwdByPid.set(pid, line.slice(1));
|
|
91
|
+
}
|
|
92
|
+
for (const [tty, agent] of selected)
|
|
93
|
+
result.set(tty, { agent: agent.agent, cwd: cwdByPid.get(agent.pid) ?? null });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.shepherd_cwd_lookup_error", message: "could not read foreground agent folders", meta: { error: String(error) } });
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.921",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.921",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|