@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.
Files changed (138) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +151 -205
  4. package/changelog.json +902 -0
  5. package/dist/heart/active-work.js +633 -0
  6. package/dist/heart/bridges/manager.js +358 -0
  7. package/dist/heart/bridges/state-machine.js +135 -0
  8. package/dist/heart/bridges/store.js +123 -0
  9. package/dist/heart/commitments.js +105 -0
  10. package/dist/heart/config.js +66 -21
  11. package/dist/heart/core.js +579 -112
  12. package/dist/heart/cross-chat-delivery.js +146 -0
  13. package/dist/heart/daemon/agent-discovery.js +81 -0
  14. package/dist/heart/daemon/agent-service.js +467 -0
  15. package/dist/heart/daemon/auth-flow.js +457 -0
  16. package/dist/heart/daemon/daemon-cli.js +1840 -196
  17. package/dist/heart/daemon/daemon-entry.js +80 -3
  18. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  19. package/dist/heart/daemon/daemon.js +301 -1
  20. package/dist/heart/daemon/hatch-animation.js +10 -3
  21. package/dist/heart/daemon/hatch-flow.js +15 -80
  22. package/dist/heart/daemon/heartbeat-timer.js +131 -0
  23. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  24. package/dist/heart/daemon/launchd.js +159 -0
  25. package/dist/heart/daemon/log-tailer.js +4 -3
  26. package/dist/heart/daemon/mcp-server.js +464 -0
  27. package/dist/heart/daemon/message-router.js +17 -8
  28. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  29. package/dist/heart/daemon/ouro-path-installer.js +67 -34
  30. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  31. package/dist/heart/daemon/process-manager.js +13 -0
  32. package/dist/heart/daemon/run-hooks.js +37 -0
  33. package/dist/heart/daemon/runtime-logging.js +58 -15
  34. package/dist/heart/daemon/runtime-metadata.js +219 -0
  35. package/dist/heart/daemon/runtime-mode.js +67 -0
  36. package/dist/heart/daemon/sense-manager.js +50 -2
  37. package/dist/heart/daemon/skill-management-installer.js +94 -0
  38. package/dist/heart/daemon/socket-client.js +202 -0
  39. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  40. package/dist/heart/daemon/specialist-prompt.js +10 -7
  41. package/dist/heart/daemon/specialist-tools.js +52 -3
  42. package/dist/heart/daemon/staged-restart.js +114 -0
  43. package/dist/heart/daemon/thoughts.js +507 -0
  44. package/dist/heart/daemon/update-checker.js +111 -0
  45. package/dist/heart/daemon/update-hooks.js +138 -0
  46. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  47. package/dist/heart/delegation.js +62 -0
  48. package/dist/heart/identity.js +64 -21
  49. package/dist/heart/kicks.js +2 -20
  50. package/dist/heart/model-capabilities.js +48 -0
  51. package/dist/heart/obligations.js +197 -0
  52. package/dist/heart/progress-story.js +42 -0
  53. package/dist/heart/provider-failover.js +88 -0
  54. package/dist/heart/provider-ping.js +159 -0
  55. package/dist/heart/providers/anthropic-token.js +163 -0
  56. package/dist/heart/providers/anthropic.js +195 -34
  57. package/dist/heart/providers/azure.js +115 -9
  58. package/dist/heart/providers/github-copilot.js +157 -0
  59. package/dist/heart/providers/minimax.js +33 -3
  60. package/dist/heart/providers/openai-codex.js +49 -14
  61. package/dist/heart/session-activity.js +173 -0
  62. package/dist/heart/session-recall.js +216 -0
  63. package/dist/heart/streaming.js +117 -33
  64. package/dist/heart/target-resolution.js +123 -0
  65. package/dist/heart/tool-loop.js +198 -0
  66. package/dist/heart/turn-coordinator.js +28 -0
  67. package/dist/mind/associative-recall.js +122 -32
  68. package/dist/mind/bundle-manifest.js +14 -0
  69. package/dist/mind/context.js +124 -14
  70. package/dist/mind/{memory.js → diary.js} +70 -56
  71. package/dist/mind/first-impressions.js +16 -2
  72. package/dist/mind/friends/channel.js +35 -0
  73. package/dist/mind/friends/group-context.js +144 -0
  74. package/dist/mind/friends/store-file.js +19 -0
  75. package/dist/mind/friends/trust-explanation.js +74 -0
  76. package/dist/mind/friends/types.js +8 -0
  77. package/dist/mind/journal-index.js +161 -0
  78. package/dist/mind/obligation-steering.js +221 -0
  79. package/dist/mind/obligations.js +134 -0
  80. package/dist/mind/pending.js +76 -9
  81. package/dist/mind/phrases.js +1 -0
  82. package/dist/mind/prompt.js +554 -88
  83. package/dist/mind/token-estimate.js +8 -12
  84. package/dist/nerves/cli-logging.js +15 -2
  85. package/dist/nerves/coverage/run-artifacts.js +1 -1
  86. package/dist/nerves/index.js +12 -0
  87. package/dist/nerves/runtime.js +5 -1
  88. package/dist/repertoire/ado-client.js +4 -2
  89. package/dist/repertoire/ado-semantic.js +6 -0
  90. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  91. package/dist/repertoire/coding/context-pack.js +254 -0
  92. package/dist/repertoire/coding/feedback.js +301 -0
  93. package/dist/repertoire/coding/index.js +4 -1
  94. package/dist/repertoire/coding/manager.js +210 -4
  95. package/dist/repertoire/coding/spawner.js +59 -12
  96. package/dist/repertoire/coding/tools.js +176 -4
  97. package/dist/repertoire/data/ado-endpoints.json +188 -0
  98. package/dist/repertoire/guardrails.js +290 -0
  99. package/dist/repertoire/mcp-client.js +254 -0
  100. package/dist/repertoire/mcp-manager.js +198 -0
  101. package/dist/repertoire/skills.js +14 -23
  102. package/dist/repertoire/tasks/board.js +12 -0
  103. package/dist/repertoire/tasks/index.js +23 -9
  104. package/dist/repertoire/tasks/transitions.js +1 -2
  105. package/dist/repertoire/tools-base.js +975 -271
  106. package/dist/repertoire/tools-bluebubbles.js +94 -0
  107. package/dist/repertoire/tools-github.js +1 -6
  108. package/dist/repertoire/tools-teams.js +65 -59
  109. package/dist/repertoire/tools.js +265 -91
  110. package/dist/senses/attention-queue.js +97 -0
  111. package/dist/senses/bluebubbles-client.js +210 -5
  112. package/dist/senses/bluebubbles-entry.js +2 -0
  113. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  114. package/dist/senses/bluebubbles-media.js +339 -0
  115. package/dist/senses/bluebubbles-model.js +12 -4
  116. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  117. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  118. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  119. package/dist/senses/bluebubbles.js +944 -45
  120. package/dist/senses/cli-entry.js +1 -1
  121. package/dist/senses/cli-layout.js +187 -0
  122. package/dist/senses/cli.js +374 -131
  123. package/dist/senses/contextual-heartbeat.js +102 -0
  124. package/dist/senses/continuity.js +94 -0
  125. package/dist/senses/debug-activity.js +154 -0
  126. package/dist/senses/inner-dialog-worker.js +47 -18
  127. package/dist/senses/inner-dialog.js +500 -84
  128. package/dist/senses/pipeline.js +454 -0
  129. package/dist/senses/surface-tool.js +82 -0
  130. package/dist/senses/teams.js +607 -129
  131. package/dist/senses/trust-gate.js +112 -2
  132. package/package.json +12 -4
  133. package/skills/configure-dev-tools.md +109 -0
  134. package/subagents/README.md +4 -70
  135. package/dist/heart/daemon/subagent-installer.js +0 -134
  136. package/subagents/work-doer.md +0 -233
  137. package/subagents/work-merger.md +0 -624
  138. 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
- if (trustLevel !== "stranger") {
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.14",
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
- "assets/"
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/senses/cli-entry.js --agent ouroboros",
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
- "openai": "^6.27.0"
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
+ ```
@@ -1,73 +1,7 @@
1
- # Sub-agents
1
+ # Workflow Skills (Moved)
2
2
 
3
- These are source-of-truth workflow definitions (`work-planner`, `work-doer`, `work-merger`) for planning, execution, and merge. They can be consumed either as Claude sub-agents (`.md` files with YAML frontmatter) or as Codex-style skills (`SKILL.md`).
3
+ The workflow skills (`work-planner`, `work-doer`, `work-merger`) have moved to the shared skills repository:
4
4
 
5
- ## Installation
5
+ **https://github.com/ouroborosbot/ouroboros-skills**
6
6
 
7
- ### Claude Code (sub-agents)
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
- }