@ouro.bot/cli 0.1.0-alpha.14 → 0.1.0-alpha.140
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/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +151 -205
- package/changelog.json +902 -0
- package/dist/heart/active-work.js +633 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +579 -112
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +467 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1840 -196
- package/dist/heart/daemon/daemon-entry.js +80 -3
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +301 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +15 -80
- package/dist/heart/daemon/heartbeat-timer.js +131 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/mcp-server.js +464 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +67 -34
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +10 -7
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +117 -33
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +198 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +122 -32
- package/dist/mind/bundle-manifest.js +14 -0
- package/dist/mind/context.js +124 -14
- package/dist/mind/{memory.js → diary.js} +70 -56
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/obligations.js +134 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +554 -88
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/ado-semantic.js +6 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +59 -12
- package/dist/repertoire/coding/tools.js +176 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +975 -271
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-teams.js +65 -59
- package/dist/repertoire/tools.js +265 -91
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +944 -45
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/contextual-heartbeat.js +102 -0
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +500 -84
- package/dist/senses/pipeline.js +454 -0
- package/dist/senses/surface-tool.js +82 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +12 -4
- package/skills/configure-dev-tools.md +109 -0
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -39,6 +39,9 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const identity_1 = require("../heart/identity");
|
|
41
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const types_1 = require("../mind/friends/types");
|
|
43
|
+
const pending_1 = require("../mind/pending");
|
|
44
|
+
// Canned reply; eventually agents should compose their own first-contact message
|
|
42
45
|
exports.STRANGER_AUTO_REPLY = "I'm sorry, I'm not allowed to talk to strangers";
|
|
43
46
|
function buildExternalKey(provider, externalId, tenantId) {
|
|
44
47
|
return `${provider}:${tenantId ?? ""}:${externalId}`;
|
|
@@ -77,16 +80,107 @@ function appendPrimaryNotification(bundleRoot, provider, externalId, tenantId, n
|
|
|
77
80
|
fs.mkdirSync(inboxDir, { recursive: true });
|
|
78
81
|
fs.appendFileSync(notificationsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
79
82
|
}
|
|
83
|
+
function writeInnerPendingNotice(bundleRoot, noticeContent, nowIso) {
|
|
84
|
+
const innerPendingDir = path.join(bundleRoot, "state", "pending", pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
|
|
85
|
+
const fileName = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
|
|
86
|
+
const filePath = path.join(innerPendingDir, fileName);
|
|
87
|
+
const payload = {
|
|
88
|
+
from: "instinct",
|
|
89
|
+
content: noticeContent,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
at: nowIso,
|
|
92
|
+
};
|
|
93
|
+
fs.mkdirSync(innerPendingDir, { recursive: true });
|
|
94
|
+
fs.writeFileSync(filePath, JSON.stringify(payload), "utf-8");
|
|
95
|
+
}
|
|
80
96
|
function enforceTrustGate(input) {
|
|
97
|
+
const { senseType } = input;
|
|
98
|
+
// Local (CLI) and internal (inner dialog) — always allow
|
|
99
|
+
if (senseType === "local" || senseType === "internal") {
|
|
100
|
+
return { allowed: true };
|
|
101
|
+
}
|
|
102
|
+
// Closed senses (Teams) — org already gates access, allow all trust levels
|
|
103
|
+
if (senseType === "closed") {
|
|
104
|
+
return { allowed: true };
|
|
105
|
+
}
|
|
106
|
+
// Open senses (BlueBubbles/iMessage) — enforce trust rules
|
|
81
107
|
const trustLevel = input.friend.trustLevel ?? "friend";
|
|
82
|
-
|
|
108
|
+
// Family and friend — always allow on open
|
|
109
|
+
if ((0, types_1.isTrustedLevel)(trustLevel)) {
|
|
83
110
|
return { allowed: true };
|
|
84
111
|
}
|
|
85
112
|
const bundleRoot = input.bundleRoot ?? (0, identity_1.getAgentRoot)();
|
|
86
|
-
const repliesPath = path.join(bundleRoot, "stranger-replies.json");
|
|
87
113
|
const nowIso = (input.now ?? (() => new Date()))().toISOString();
|
|
114
|
+
// Acquaintance rules
|
|
115
|
+
if (trustLevel === "acquaintance") {
|
|
116
|
+
return handleAcquaintance(input, bundleRoot, nowIso);
|
|
117
|
+
}
|
|
118
|
+
// Stranger rules (trustLevel === "stranger")
|
|
119
|
+
return handleStranger(input, bundleRoot, nowIso);
|
|
120
|
+
}
|
|
121
|
+
function handleAcquaintance(input, bundleRoot, nowIso) {
|
|
122
|
+
const { isGroupChat, groupHasFamilyMember, hasExistingGroupWithFamily } = input;
|
|
123
|
+
// Group chat with family member present — allow
|
|
124
|
+
if (isGroupChat && groupHasFamilyMember) {
|
|
125
|
+
return { allowed: true };
|
|
126
|
+
}
|
|
127
|
+
let result;
|
|
128
|
+
let noticeDetail;
|
|
129
|
+
if (isGroupChat) {
|
|
130
|
+
// Group chat without family member — reject silently
|
|
131
|
+
result = { allowed: false, reason: "acquaintance_group_no_family" };
|
|
132
|
+
noticeDetail = `acquaintance "${input.friend.name}" messaged in a group chat without a family member present`;
|
|
133
|
+
}
|
|
134
|
+
else if (hasExistingGroupWithFamily) {
|
|
135
|
+
// 1:1 but shares a group with family — redirect
|
|
136
|
+
result = {
|
|
137
|
+
allowed: false,
|
|
138
|
+
reason: "acquaintance_1on1_has_group",
|
|
139
|
+
autoReply: "Hey! Reach me in our group chat instead.",
|
|
140
|
+
};
|
|
141
|
+
noticeDetail = `acquaintance "${input.friend.name}" DMed me directly — redirected to our group chat`;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// 1:1, no shared group with family — redirect to any group
|
|
145
|
+
result = {
|
|
146
|
+
allowed: false,
|
|
147
|
+
reason: "acquaintance_1on1_no_group",
|
|
148
|
+
autoReply: "Hey! Reach me in a group chat instead.",
|
|
149
|
+
};
|
|
150
|
+
noticeDetail = `acquaintance "${input.friend.name}" DMed me directly — asked to reach me in a group chat`;
|
|
151
|
+
}
|
|
152
|
+
(0, runtime_1.emitNervesEvent)({
|
|
153
|
+
level: "warn",
|
|
154
|
+
component: "senses",
|
|
155
|
+
event: "senses.trust_gate",
|
|
156
|
+
message: "acquaintance message blocked",
|
|
157
|
+
meta: {
|
|
158
|
+
channel: input.channel,
|
|
159
|
+
provider: input.provider,
|
|
160
|
+
reason: result.reason,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
writeInnerPendingNotice(bundleRoot, noticeDetail, nowIso);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
(0, runtime_1.emitNervesEvent)({
|
|
168
|
+
level: "error",
|
|
169
|
+
component: "senses",
|
|
170
|
+
event: "senses.trust_gate_error",
|
|
171
|
+
message: "failed to write inner pending notice",
|
|
172
|
+
meta: {
|
|
173
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
function handleStranger(input, bundleRoot, nowIso) {
|
|
180
|
+
const repliesPath = path.join(bundleRoot, "stranger-replies.json");
|
|
88
181
|
const externalKey = buildExternalKey(input.provider, input.externalId, input.tenantId);
|
|
89
182
|
const state = loadRepliesState(repliesPath);
|
|
183
|
+
// Subsequent contact — silent drop
|
|
90
184
|
if (state[externalKey]) {
|
|
91
185
|
(0, runtime_1.emitNervesEvent)({
|
|
92
186
|
level: "warn",
|
|
@@ -103,6 +197,7 @@ function enforceTrustGate(input) {
|
|
|
103
197
|
reason: "stranger_silent_drop",
|
|
104
198
|
};
|
|
105
199
|
}
|
|
200
|
+
// First contact — auto-reply, persist state, notify agent
|
|
106
201
|
state[externalKey] = nowIso;
|
|
107
202
|
try {
|
|
108
203
|
persistRepliesState(repliesPath, state);
|
|
@@ -132,6 +227,21 @@ function enforceTrustGate(input) {
|
|
|
132
227
|
},
|
|
133
228
|
});
|
|
134
229
|
}
|
|
230
|
+
const noticeDetail = `stranger "${input.friend.name}" tried to reach me via ${input.channel}. Auto-replied once.`;
|
|
231
|
+
try {
|
|
232
|
+
writeInnerPendingNotice(bundleRoot, noticeDetail, nowIso);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
(0, runtime_1.emitNervesEvent)({
|
|
236
|
+
level: "error",
|
|
237
|
+
component: "senses",
|
|
238
|
+
event: "senses.trust_gate_error",
|
|
239
|
+
message: "failed to write inner pending notice",
|
|
240
|
+
meta: {
|
|
241
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
135
245
|
(0, runtime_1.emitNervesEvent)({
|
|
136
246
|
level: "warn",
|
|
137
247
|
component: "senses",
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.140",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
|
+
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
6
7
|
"ouro": "dist/heart/daemon/ouro-entry.js",
|
|
7
8
|
"ouro.bot": "dist/heart/daemon/ouro-bot-entry.js"
|
|
8
9
|
},
|
|
@@ -10,14 +11,17 @@
|
|
|
10
11
|
"dist/",
|
|
11
12
|
"AdoptionSpecialist.ouro/",
|
|
12
13
|
"subagents/",
|
|
13
|
-
"
|
|
14
|
+
"skills/",
|
|
15
|
+
"assets/",
|
|
16
|
+
"changelog.json"
|
|
14
17
|
],
|
|
15
18
|
"exports": {
|
|
16
19
|
".": "./dist/heart/daemon/daemon-cli.js",
|
|
17
20
|
"./runOuroCli": "./dist/heart/daemon/daemon-cli.js"
|
|
18
21
|
},
|
|
19
22
|
"scripts": {
|
|
20
|
-
"dev": "tsc && node dist/
|
|
23
|
+
"dev": "tsc && node dist/heart/daemon/ouro-bot-entry.js dev",
|
|
24
|
+
"cli": "tsc && node dist/senses/cli-entry.js",
|
|
21
25
|
"daemon": "tsc && node dist/heart/daemon/daemon-entry.js",
|
|
22
26
|
"ouro": "tsc && node dist/heart/daemon/ouro-entry.js",
|
|
23
27
|
"teams": "tsc && node dist/senses/teams-entry.js --agent ouroboros",
|
|
@@ -31,15 +35,19 @@
|
|
|
31
35
|
},
|
|
32
36
|
"dependencies": {
|
|
33
37
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
38
|
+
"@azure/identity": "^4.13.0",
|
|
34
39
|
"@microsoft/teams.apps": "^2.0.5",
|
|
35
40
|
"@microsoft/teams.dev": "^2.0.5",
|
|
36
|
-
"
|
|
41
|
+
"fast-glob": "^3.3.3",
|
|
42
|
+
"openai": "^6.27.0",
|
|
43
|
+
"semver": "^7.7.4"
|
|
37
44
|
},
|
|
38
45
|
"repository": {
|
|
39
46
|
"type": "git",
|
|
40
47
|
"url": "https://github.com/ouroborosbot/ouroboros"
|
|
41
48
|
},
|
|
42
49
|
"devDependencies": {
|
|
50
|
+
"@types/semver": "^7.7.1",
|
|
43
51
|
"@vitest/coverage-v8": "^4.0.18",
|
|
44
52
|
"eslint": "^10.0.2",
|
|
45
53
|
"typescript": "^5.7.0",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Configure Dev Tools for MCP Agent Bridge
|
|
2
|
+
|
|
3
|
+
Set up your development tools (Claude Code, Codex) to communicate with Ouroboros agents via MCP (Model Context Protocol). This enables bidirectional structured communication between dev tools and running agents.
|
|
4
|
+
|
|
5
|
+
## Claude Code Setup
|
|
6
|
+
|
|
7
|
+
Add an MCP server entry to your Claude Code settings at `~/.claude/settings.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"ouro": {
|
|
13
|
+
"command": "ouro",
|
|
14
|
+
"args": ["mcp-serve", "--agent", "<agent-name>"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Replace `<agent-name>` with the name of the agent you want to connect to (e.g., `slugger`).
|
|
21
|
+
|
|
22
|
+
### Per-project configuration
|
|
23
|
+
|
|
24
|
+
You can also add the MCP entry to a project-level `.claude/settings.json` to scope it to a specific workspace.
|
|
25
|
+
|
|
26
|
+
## Codex Setup
|
|
27
|
+
|
|
28
|
+
Add the MCP server using the Codex CLI:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
codex mcp add ouro -- ouro mcp-serve --agent <agent-name>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Spawned session injection
|
|
35
|
+
|
|
36
|
+
When the agent spawns Codex for coding tasks, MCP configuration is injected automatically via the `-c` flag:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
codex -c '{"mcp_servers":{"ouro":{"command":"ouro","args":["mcp-serve","--agent","<agent-name>"]}}}' ...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You do not need to configure this manually -- the harness handles it.
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
After configuration, verify the connection:
|
|
47
|
+
|
|
48
|
+
1. **Check daemon is running**: The agent daemon must be active. Start it with `ouro daemon --agent <agent-name>` if needed.
|
|
49
|
+
2. **Test from Claude Code**: Ask Claude Code to use the `status` tool to check the agent's state.
|
|
50
|
+
3. **Test from Codex**: Run `codex exec "Use the ouro status tool to check the agent"`.
|
|
51
|
+
|
|
52
|
+
## Available MCP Tools
|
|
53
|
+
|
|
54
|
+
Once connected, the following tools are available:
|
|
55
|
+
|
|
56
|
+
- **ask** -- Ask the agent a question (uses memory and context)
|
|
57
|
+
- **status** -- Get agent's current status and activity
|
|
58
|
+
- **catchup** -- Get recent activity summary
|
|
59
|
+
- **delegate** -- Request the agent to handle a task
|
|
60
|
+
- **get_context** -- Get agent's current working context
|
|
61
|
+
- **search_memory** -- Search agent's memory for specific topics
|
|
62
|
+
- **get_task** -- Get details of the agent's current task
|
|
63
|
+
- **check_scope** -- Verify if something is in scope for current work
|
|
64
|
+
- **request_decision** -- Ask agent to make a decision about something
|
|
65
|
+
- **check_guidance** -- Get guidance on how to approach something
|
|
66
|
+
- **report_progress** -- Report progress on delegated work
|
|
67
|
+
- **report_blocker** -- Report a blocker on delegated work
|
|
68
|
+
- **report_complete** -- Report completion of delegated work
|
|
69
|
+
|
|
70
|
+
## Hybrid Model
|
|
71
|
+
|
|
72
|
+
The MCP bridge creates a hybrid collaboration model:
|
|
73
|
+
|
|
74
|
+
- **Dev tool** (Claude Code / Codex) handles code editing, file operations, and tool execution
|
|
75
|
+
- **Agent** (Ouroboros) provides context, memory, task awareness, and decision-making
|
|
76
|
+
- **Communication** flows both directions via MCP tool calls
|
|
77
|
+
|
|
78
|
+
This means the dev tool can ask the agent for guidance mid-task, and the agent can monitor progress through structured reports.
|
|
79
|
+
|
|
80
|
+
## Troubleshooting
|
|
81
|
+
|
|
82
|
+
### "Daemon not running" error
|
|
83
|
+
The agent daemon must be active before the MCP server can forward requests. Start it with:
|
|
84
|
+
```bash
|
|
85
|
+
ouro daemon --agent <agent-name>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### MCP server not appearing in tool list
|
|
89
|
+
- Verify the `ouro` command is available in your PATH (install via `npm install -g @ouro.bot/cli`)
|
|
90
|
+
- Check that the settings file is valid JSON
|
|
91
|
+
- Restart your dev tool after changing MCP configuration
|
|
92
|
+
|
|
93
|
+
### Connection timeouts
|
|
94
|
+
The MCP server connects to the daemon via Unix socket. If the socket file is stale:
|
|
95
|
+
1. Stop the daemon: `ouro daemon stop --agent <agent-name>`
|
|
96
|
+
2. Restart: `ouro daemon --agent <agent-name>`
|
|
97
|
+
|
|
98
|
+
### Friend identity
|
|
99
|
+
By default, the MCP server identifies as the local user. To use a specific friend identity:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"ouro": {
|
|
104
|
+
"command": "ouro",
|
|
105
|
+
"args": ["mcp-serve", "--agent", "<agent-name>", "--friend", "<friend-id>"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
package/subagents/README.md
CHANGED
|
@@ -1,73 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Workflow Skills (Moved)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The workflow skills (`work-planner`, `work-doer`, `work-merger`) have moved to the shared skills repository:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**https://github.com/ouroborosbot/ouroboros-skills**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Copy or symlink these files into Claude's sub-agent directory:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Claude Code
|
|
13
|
-
cp subagents/*.md ~/.claude/agents/
|
|
14
|
-
# or
|
|
15
|
-
ln -s "$(pwd)"/subagents/*.md ~/.claude/agents/
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Codex / skill-based harnesses
|
|
19
|
-
|
|
20
|
-
For tools that support skills but not Claude sub-agents, install these as skills:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
mkdir -p ~/.codex/skills/work-planner ~/.codex/skills/work-doer ~/.codex/skills/work-merger
|
|
24
|
-
|
|
25
|
-
# Hard-link to keep one source of truth
|
|
26
|
-
ln -f "$(pwd)/subagents/work-planner.md" ~/.codex/skills/work-planner/SKILL.md
|
|
27
|
-
ln -f "$(pwd)/subagents/work-doer.md" ~/.codex/skills/work-doer/SKILL.md
|
|
28
|
-
ln -f "$(pwd)/subagents/work-merger.md" ~/.codex/skills/work-merger/SKILL.md
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Important:** Hard-links break when editors save by replacing the file (new inode). After editing any `subagents/*.md` file, re-run the `ln -f` command for that file to restore the link. You can verify with `stat -f '%i'` — both files should share the same inode.
|
|
32
|
-
|
|
33
|
-
Optional UI metadata:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
mkdir -p ~/.codex/skills/work-planner/agents ~/.codex/skills/work-doer/agents ~/.codex/skills/work-merger/agents
|
|
37
|
-
cat > ~/.codex/skills/work-planner/agents/openai.yaml << 'EOF'
|
|
38
|
-
interface:
|
|
39
|
-
display_name: "Work Planner"
|
|
40
|
-
short_description: "Create and gate planning/doing task docs"
|
|
41
|
-
default_prompt: "Use $work-planner to create or update a planning doc, then stop at NEEDS_REVIEW."
|
|
42
|
-
EOF
|
|
43
|
-
cat > ~/.codex/skills/work-doer/agents/openai.yaml << 'EOF'
|
|
44
|
-
interface:
|
|
45
|
-
display_name: "Work Doer"
|
|
46
|
-
short_description: "Execute approved doing docs with strict TDD"
|
|
47
|
-
default_prompt: "Use $work-doer to execute an approved doing doc unit by unit."
|
|
48
|
-
EOF
|
|
49
|
-
cat > ~/.codex/skills/work-merger/agents/openai.yaml << 'EOF'
|
|
50
|
-
interface:
|
|
51
|
-
display_name: "Work Merger"
|
|
52
|
-
short_description: "Merge feature branch into main via PR after work-doer completes"
|
|
53
|
-
default_prompt: "Use $work-merger to merge the current feature branch into main."
|
|
54
|
-
EOF
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Restart the harness after install so new skills are discovered.
|
|
58
|
-
|
|
59
|
-
## Available sub-agents
|
|
60
|
-
|
|
61
|
-
| File | Purpose |
|
|
62
|
-
|------|---------|
|
|
63
|
-
| `work-planner.md` | Interactive task planner. Generates planning docs through conversation, then converts to doing docs after human approval. |
|
|
64
|
-
| `work-doer.md` | Task executor. Reads a doing doc and works through each unit sequentially with strict TDD. |
|
|
65
|
-
| `work-merger.md` | Sync-and-merge agent. Merges feature branch into main via PR after work-doer completes. Handles conflicts, CI failures, and race conditions. |
|
|
66
|
-
|
|
67
|
-
## Workflow
|
|
68
|
-
|
|
69
|
-
1. Human describes a task
|
|
70
|
-
2. Agent invokes **work-planner** to create a planning doc → human approves → planner converts to doing doc
|
|
71
|
-
3. Agent invokes **work-doer** to execute the doing doc unit by unit
|
|
72
|
-
4. Each unit is committed independently with progress tracked in the doing doc
|
|
73
|
-
5. Agent invokes **work-merger** to merge the feature branch into main via PR (fetch, merge, resolve conflicts, CI gate, merge PR, cleanup)
|
|
7
|
+
To install or update these skills, use the **skill-management** skill from that repo. It covers browsing available skills, installing them into your agent's local skills directory, and keeping them up to date.
|
|
@@ -1,134 +0,0 @@
|
|
|
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.installSubagentsForAvailableCli = installSubagentsForAvailableCli;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
|
-
const child_process_1 = require("child_process");
|
|
41
|
-
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
-
function detectCliBinary(binary) {
|
|
43
|
-
const result = (0, child_process_1.spawnSync)("which", [binary], { encoding: "utf-8" });
|
|
44
|
-
if (result.status !== 0)
|
|
45
|
-
return null;
|
|
46
|
-
const resolved = result.stdout.trim();
|
|
47
|
-
return resolved.length > 0 ? resolved : null;
|
|
48
|
-
}
|
|
49
|
-
function listSubagentSources(subagentsDir) {
|
|
50
|
-
if (!fs.existsSync(subagentsDir))
|
|
51
|
-
return [];
|
|
52
|
-
return fs.readdirSync(subagentsDir)
|
|
53
|
-
.filter((name) => name.endsWith(".md"))
|
|
54
|
-
.filter((name) => name !== "README.md")
|
|
55
|
-
.map((name) => path.join(subagentsDir, name))
|
|
56
|
-
.sort((a, b) => a.localeCompare(b));
|
|
57
|
-
}
|
|
58
|
-
function pathExists(target) {
|
|
59
|
-
try {
|
|
60
|
-
fs.lstatSync(target);
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function ensureSymlink(source, target) {
|
|
68
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
69
|
-
if (pathExists(target)) {
|
|
70
|
-
const stats = fs.lstatSync(target);
|
|
71
|
-
if (stats.isSymbolicLink()) {
|
|
72
|
-
const linkedPath = fs.readlinkSync(target);
|
|
73
|
-
if (linkedPath === source)
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
fs.unlinkSync(target);
|
|
77
|
-
}
|
|
78
|
-
fs.symlinkSync(source, target);
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
async function installSubagentsForAvailableCli(options = {}) {
|
|
82
|
-
const repoRoot = options.repoRoot ?? path.resolve(__dirname, "..", "..", "..");
|
|
83
|
-
const homeDir = options.homeDir ?? os.homedir();
|
|
84
|
-
const which = options.which ?? detectCliBinary;
|
|
85
|
-
const subagentsDir = path.join(repoRoot, "subagents");
|
|
86
|
-
const sources = listSubagentSources(subagentsDir);
|
|
87
|
-
const notes = [];
|
|
88
|
-
(0, runtime_1.emitNervesEvent)({
|
|
89
|
-
component: "daemon",
|
|
90
|
-
event: "daemon.subagent_install_start",
|
|
91
|
-
message: "starting subagent auto-install",
|
|
92
|
-
meta: { sources: sources.length },
|
|
93
|
-
});
|
|
94
|
-
if (sources.length === 0) {
|
|
95
|
-
notes.push(`no subagent files found at ${subagentsDir}`);
|
|
96
|
-
return { claudeInstalled: 0, codexInstalled: 0, notes };
|
|
97
|
-
}
|
|
98
|
-
let claudeInstalled = 0;
|
|
99
|
-
let codexInstalled = 0;
|
|
100
|
-
const claudePath = which("claude");
|
|
101
|
-
if (!claudePath) {
|
|
102
|
-
notes.push("claude CLI not found; skipping subagent install");
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
const claudeAgentsDir = path.join(homeDir, ".claude", "agents");
|
|
106
|
-
for (const source of sources) {
|
|
107
|
-
const target = path.join(claudeAgentsDir, path.basename(source));
|
|
108
|
-
if (ensureSymlink(source, target)) {
|
|
109
|
-
claudeInstalled += 1;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const codexPath = which("codex");
|
|
114
|
-
if (!codexPath) {
|
|
115
|
-
notes.push("codex CLI not found; skipping subagent install");
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
const codexSkillsDir = path.join(homeDir, ".codex", "skills");
|
|
119
|
-
for (const source of sources) {
|
|
120
|
-
const skillName = path.basename(source, ".md");
|
|
121
|
-
const target = path.join(codexSkillsDir, skillName, "SKILL.md");
|
|
122
|
-
if (ensureSymlink(source, target)) {
|
|
123
|
-
codexInstalled += 1;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
(0, runtime_1.emitNervesEvent)({
|
|
128
|
-
component: "daemon",
|
|
129
|
-
event: "daemon.subagent_install_end",
|
|
130
|
-
message: "completed subagent auto-install",
|
|
131
|
-
meta: { claudeInstalled, codexInstalled, notes: notes.length },
|
|
132
|
-
});
|
|
133
|
-
return { claudeInstalled, codexInstalled, notes };
|
|
134
|
-
}
|