@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 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,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.920",
2
+ "runtimeVersion": "0.1.0-alpha.921",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -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.920</Repository>
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, sense) {
432
+ function runtimeCredentialBootstrapFor(agent) {
433
433
  const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
434
- const usesMachineConfig = sense === "bluebubbles" || sense === "voice" || sense === "a2a" || sense === "telegram" || sense === "shepherd";
435
- const machineId = usesMachineConfig ? currentMachineId() : undefined;
436
- const machine = usesMachineConfig ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
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?.ok ? machine.config : undefined,
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, sense),
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
- return 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
+ 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(({ id, ref, workspace, title, agent, focused }) => ({ id, ref, workspace, title, agent, focused }));
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
- const hook = hooked.get(surface.id);
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, ref) {
82
- const agent = record.agent ? plain(record.agent, NAME_MAX) : "";
83
- const folder = record.cwd ? plain(path.basename(record.cwd), NAME_MAX) : "";
84
- const where = plain(ref ?? record.session.slice(0, 8), NAME_MAX);
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, ref) {
89
- const subject = who(record, ref);
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.ref);
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
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.920",
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.920",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.920",
3
+ "version": "0.1.0-alpha.921",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },