@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.81
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/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +468 -0
- package/dist/heart/active-work.js +218 -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 +89 -0
- package/dist/heart/config.js +68 -23
- package/dist/heart/core.js +452 -93
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +430 -0
- package/dist/heart/daemon/daemon-cli.js +1779 -247
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- 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/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +260 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/ouro-version-manager.js +164 -0
- package/dist/heart/daemon/process-manager.js +14 -1
- 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 +307 -0
- 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 +53 -84
- package/dist/heart/daemon/specialist-prompt.js +63 -11
- package/dist/heart/daemon/specialist-tools.js +211 -60
- 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 +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +141 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- 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/memory.js +27 -26
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +445 -77
- 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/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- 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 +195 -0
- package/dist/repertoire/skills.js +3 -26
- 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 +686 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +95 -53
- 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 +894 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +405 -156
- 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 +377 -83
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"description": "Delete a work item (moves to recycle bin)",
|
|
36
36
|
"params": "destroy (boolean, permanently delete)"
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
"path": "/{project}/_apis/wit/workitemtypes",
|
|
40
|
+
"method": "GET",
|
|
41
|
+
"description": "List all work item types available in a project (Bug, Task, Epic, User Story, etc.)",
|
|
42
|
+
"params": ""
|
|
43
|
+
},
|
|
38
44
|
{
|
|
39
45
|
"path": "/_apis/git/repositories",
|
|
40
46
|
"method": "GET",
|
|
@@ -118,5 +124,187 @@
|
|
|
118
124
|
"method": "GET",
|
|
119
125
|
"description": "List saved work item queries (shared and personal)",
|
|
120
126
|
"params": "$depth, $expand"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"path": "/_apis/groupentitlements?api-version=7.1",
|
|
130
|
+
"method": "GET",
|
|
131
|
+
"host": "vsaex.dev.azure.com",
|
|
132
|
+
"description": "List group entitlements (group rules that auto-assign licenses). Use host vsaex.dev.azure.com.",
|
|
133
|
+
"params": ""
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"path": "/_apis/groupentitlements?api-version=7.1",
|
|
137
|
+
"method": "POST",
|
|
138
|
+
"host": "vsaex.dev.azure.com",
|
|
139
|
+
"description": "Create a group entitlement rule — maps an AAD group to an access level (e.g. Basic) and project membership. All members of the AAD group automatically get the specified license. Use host vsaex.dev.azure.com. This is the best way to bulk-provision users.",
|
|
140
|
+
"params": "body: { group: { origin: 'aad', originId: '<AAD-group-object-id>', subjectKind: 'group' }, licenseRule: { licensingSource: 'account', accountLicenseType: 'express', licenseDisplayName: 'Basic' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: '<project-id>' } }] }"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
|
|
144
|
+
"method": "GET",
|
|
145
|
+
"host": "vsaex.dev.azure.com",
|
|
146
|
+
"description": "Get a specific group entitlement by ID. Use host vsaex.dev.azure.com.",
|
|
147
|
+
"params": ""
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
|
|
151
|
+
"method": "PATCH",
|
|
152
|
+
"host": "vsaex.dev.azure.com",
|
|
153
|
+
"description": "Update a group entitlement (change license rule, project access). Use host vsaex.dev.azure.com.",
|
|
154
|
+
"params": "JSON Patch array: [{op, path, value}]"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
|
|
158
|
+
"method": "DELETE",
|
|
159
|
+
"host": "vsaex.dev.azure.com",
|
|
160
|
+
"description": "Delete a group entitlement rule. Use host vsaex.dev.azure.com.",
|
|
161
|
+
"params": ""
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
|
|
165
|
+
"method": "GET",
|
|
166
|
+
"host": "vsapm.dev.azure.com",
|
|
167
|
+
"description": "List individual member entitlements (users and their access levels). Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
|
|
168
|
+
"params": "$top, $skip, $filter, $orderBy, $select"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
|
|
172
|
+
"method": "POST",
|
|
173
|
+
"host": "vsapm.dev.azure.com",
|
|
174
|
+
"description": "Add a single member entitlement. Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
|
|
175
|
+
"params": "body: { accessLevel: { accountLicenseType: 'express'|'stakeholder', licensingSource: 'account' }, user: { principalName: 'user@domain.com', subjectKind: 'user' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: projectId } }] }"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
|
|
179
|
+
"method": "PATCH",
|
|
180
|
+
"host": "vsapm.dev.azure.com",
|
|
181
|
+
"description": "Update a member entitlement (change access level, project access). Use host vsapm.dev.azure.com.",
|
|
182
|
+
"params": "JSON Patch array: [{op, path, value}]"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
|
|
186
|
+
"method": "DELETE",
|
|
187
|
+
"host": "vsapm.dev.azure.com",
|
|
188
|
+
"description": "Remove a member entitlement (revoke user access). Use host vsapm.dev.azure.com.",
|
|
189
|
+
"params": ""
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"path": "/_apis/graph/users?api-version=7.1-preview.1",
|
|
193
|
+
"method": "GET",
|
|
194
|
+
"host": "vssps.dev.azure.com",
|
|
195
|
+
"description": "List users in the organization (Graph API). Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
|
|
196
|
+
"params": "subjectTypes (aad, msa, etc.), continuationToken"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"path": "/_apis/graph/groups?api-version=7.1-preview.1",
|
|
200
|
+
"method": "GET",
|
|
201
|
+
"host": "vssps.dev.azure.com",
|
|
202
|
+
"description": "List groups in the organization. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
|
|
203
|
+
"params": "subjectTypes, continuationToken"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"path": "/_apis/graph/memberships/{subjectDescriptor}?api-version=7.1-preview.1",
|
|
207
|
+
"method": "GET",
|
|
208
|
+
"host": "vssps.dev.azure.com",
|
|
209
|
+
"description": "List group memberships for a user or group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
|
|
210
|
+
"params": "direction (up = groups user belongs to, down = members of group)"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
|
|
214
|
+
"method": "PUT",
|
|
215
|
+
"host": "vssps.dev.azure.com",
|
|
216
|
+
"description": "Add a user to a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
|
|
217
|
+
"params": ""
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
|
|
221
|
+
"method": "DELETE",
|
|
222
|
+
"host": "vssps.dev.azure.com",
|
|
223
|
+
"description": "Remove a user from a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
|
|
224
|
+
"params": ""
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"path": "/_apis/projects/{projectId}/teams",
|
|
228
|
+
"method": "GET",
|
|
229
|
+
"description": "List teams in a project",
|
|
230
|
+
"params": "$top, $skip"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"path": "/_apis/projects/{projectId}/teams/{teamId}",
|
|
234
|
+
"method": "GET",
|
|
235
|
+
"description": "Get a specific team by ID",
|
|
236
|
+
"params": ""
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"path": "/_apis/projects/{projectId}/teams",
|
|
240
|
+
"method": "POST",
|
|
241
|
+
"description": "Create a new team in a project",
|
|
242
|
+
"params": "name, description"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"path": "/_apis/projects/{projectId}/teams/{teamId}/members",
|
|
246
|
+
"method": "GET",
|
|
247
|
+
"description": "List members of a team",
|
|
248
|
+
"params": "$top, $skip"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"path": "/{project}/{team}/_apis/work/teamsettings/iterations",
|
|
252
|
+
"method": "GET",
|
|
253
|
+
"description": "List iterations (sprints) for a team",
|
|
254
|
+
"params": "$timeframe (current, past, future)"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"path": "/{project}/{team}/_apis/work/teamsettings/iterations",
|
|
258
|
+
"method": "POST",
|
|
259
|
+
"description": "Add an iteration to a team's sprint schedule",
|
|
260
|
+
"params": "id (iteration node ID)"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"path": "/{project}/{team}/_apis/work/teamsettings/iterations/{iterationId}",
|
|
264
|
+
"method": "DELETE",
|
|
265
|
+
"description": "Remove an iteration from a team's sprint schedule",
|
|
266
|
+
"params": ""
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"path": "/{project}/_apis/wit/classificationnodes/iterations",
|
|
270
|
+
"method": "GET",
|
|
271
|
+
"description": "List iteration path tree (project-level iteration nodes)",
|
|
272
|
+
"params": "$depth"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"path": "/{project}/_apis/wit/classificationnodes/iterations",
|
|
276
|
+
"method": "POST",
|
|
277
|
+
"description": "Create a new iteration node (sprint)",
|
|
278
|
+
"params": "name, attributes: { startDate, finishDate }"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"path": "/{project}/_apis/wit/classificationnodes/areas",
|
|
282
|
+
"method": "GET",
|
|
283
|
+
"description": "List area path tree (project-level area nodes)",
|
|
284
|
+
"params": "$depth"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"path": "/{project}/_apis/wit/classificationnodes/areas",
|
|
288
|
+
"method": "POST",
|
|
289
|
+
"description": "Create a new area path node",
|
|
290
|
+
"params": "name"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"path": "/{project}/_apis/wit/classificationnodes/{structureGroup}/{path}",
|
|
294
|
+
"method": "DELETE",
|
|
295
|
+
"description": "Delete a classification node (area or iteration). structureGroup is 'areas' or 'iterations'.",
|
|
296
|
+
"params": "$reclassifyId (move items to this node before deleting)"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"path": "/_apis/hooks/subscriptions",
|
|
300
|
+
"method": "GET",
|
|
301
|
+
"description": "List service hook subscriptions (webhooks for events)",
|
|
302
|
+
"params": ""
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"path": "/_apis/hooks/subscriptions",
|
|
306
|
+
"method": "POST",
|
|
307
|
+
"description": "Create a service hook subscription (webhook)",
|
|
308
|
+
"params": "publisherId, eventType, consumerId, consumerActionId, publisherInputs, consumerInputs"
|
|
121
309
|
}
|
|
122
310
|
]
|
|
@@ -0,0 +1,290 @@
|
|
|
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.OURO_CLI_TRUST_MANIFEST = void 0;
|
|
37
|
+
exports.guardInvocation = guardInvocation;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const os = __importStar(require("node:os"));
|
|
40
|
+
const types_1 = require("../mind/friends/types");
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const deny = (reason) => ({ allowed: false, reason });
|
|
43
|
+
const allow = { allowed: true };
|
|
44
|
+
// --- reason templates ---
|
|
45
|
+
// Structural reasons (always-on, apply to everyone)
|
|
46
|
+
const REASONS = {
|
|
47
|
+
readBeforeEdit: "i need to read that file first before i can edit it.",
|
|
48
|
+
readBeforeOverwrite: "i need to read that file first before i can overwrite it.",
|
|
49
|
+
protectedPath: "that path is protected — i can read it but not modify it.",
|
|
50
|
+
destructiveCommand: "that command is too dangerous to run — it could cause irreversible damage.",
|
|
51
|
+
// Trust reasons (vary by relationship)
|
|
52
|
+
needsTrust: "i'd need a closer friend to vouch for you before i can do that.",
|
|
53
|
+
needsTrustForWrite: "i'd need a closer friend to vouch for you before i can write files outside my home.",
|
|
54
|
+
};
|
|
55
|
+
// --- read-only tools that never need guardrails ---
|
|
56
|
+
const READ_ONLY_TOOLS = new Set(["read_file", "glob", "grep"]);
|
|
57
|
+
// --- protected path detection ---
|
|
58
|
+
const PROTECTED_PATH_SEGMENTS = [".git/"];
|
|
59
|
+
function getProtectedAbsolutePrefixes() {
|
|
60
|
+
return [`${os.homedir()}/.agentsecrets/`];
|
|
61
|
+
}
|
|
62
|
+
function isProtectedPath(filePath) {
|
|
63
|
+
for (const segment of PROTECTED_PATH_SEGMENTS) {
|
|
64
|
+
if (filePath.includes(`/${segment}`) || filePath.startsWith(segment))
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
for (const prefix of getProtectedAbsolutePrefixes()) {
|
|
68
|
+
if (filePath.startsWith(prefix))
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// --- destructive shell patterns ---
|
|
74
|
+
const DESTRUCTIVE_PATTERNS = [
|
|
75
|
+
/\brm\s+(-\w*\s+)*-\w*r\w*\s+(-\w+\s+)*[/~]/, // rm -rf / or rm -rf ~
|
|
76
|
+
/\bchmod\s+(-\w*\s+)*-\w*R\w*\s+\d+\s+\//, // chmod -R 777 /
|
|
77
|
+
/\bmkfs\b/, // mkfs.*
|
|
78
|
+
/\bdd\s+if=/, // dd if=
|
|
79
|
+
];
|
|
80
|
+
function isDestructiveShellCommand(command) {
|
|
81
|
+
return DESTRUCTIVE_PATTERNS.some((p) => p.test(command));
|
|
82
|
+
}
|
|
83
|
+
// --- compound command splitting ---
|
|
84
|
+
// Shell operators that chain commands: &&, ||, ;, |, $(), backticks
|
|
85
|
+
const COMPOUND_SEPARATORS = /\s*(?:&&|\|\||;|\|)\s*/;
|
|
86
|
+
const SUBSHELL_PATTERN = /\$\(|`/;
|
|
87
|
+
function splitShellCommands(command) {
|
|
88
|
+
if (SUBSHELL_PATTERN.test(command))
|
|
89
|
+
return [command];
|
|
90
|
+
return command.split(COMPOUND_SEPARATORS).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
// --- shell commands that write to protected paths ---
|
|
93
|
+
function shellWritesToProtectedPath(command) {
|
|
94
|
+
const redirectMatch = command.match(/>\s*(\S+)/);
|
|
95
|
+
if (redirectMatch && isProtectedPath(redirectMatch[1]))
|
|
96
|
+
return true;
|
|
97
|
+
const teeMatch = command.match(/tee\s+(?:-\w+\s+)*(\S+)/);
|
|
98
|
+
if (teeMatch && isProtectedPath(teeMatch[1]))
|
|
99
|
+
return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
// --- structural guardrail checks (always on, all trust levels) ---
|
|
103
|
+
function checkReadBeforeWrite(toolName, args, context) {
|
|
104
|
+
if (toolName === "edit_file") {
|
|
105
|
+
const filePath = args.path || "";
|
|
106
|
+
if (!context.readPaths.has(filePath))
|
|
107
|
+
return deny(REASONS.readBeforeEdit);
|
|
108
|
+
}
|
|
109
|
+
if (toolName === "write_file") {
|
|
110
|
+
const filePath = args.path || "";
|
|
111
|
+
if (context.readPaths.has(filePath))
|
|
112
|
+
return allow;
|
|
113
|
+
if (!fs.existsSync(filePath))
|
|
114
|
+
return allow;
|
|
115
|
+
return deny(REASONS.readBeforeOverwrite);
|
|
116
|
+
}
|
|
117
|
+
return allow;
|
|
118
|
+
}
|
|
119
|
+
function checkDestructiveShellPatterns(toolName, args) {
|
|
120
|
+
if (toolName !== "shell")
|
|
121
|
+
return allow;
|
|
122
|
+
const command = args.command || "";
|
|
123
|
+
// Check each subcommand in compound commands for destructive patterns
|
|
124
|
+
for (const sub of splitShellCommands(command)) {
|
|
125
|
+
if (isDestructiveShellCommand(sub))
|
|
126
|
+
return deny(REASONS.destructiveCommand);
|
|
127
|
+
}
|
|
128
|
+
return allow;
|
|
129
|
+
}
|
|
130
|
+
function checkProtectedPaths(toolName, args) {
|
|
131
|
+
if (toolName === "write_file" || toolName === "edit_file") {
|
|
132
|
+
const filePath = args.path || "";
|
|
133
|
+
if (isProtectedPath(filePath))
|
|
134
|
+
return deny(REASONS.protectedPath);
|
|
135
|
+
}
|
|
136
|
+
if (toolName === "shell") {
|
|
137
|
+
const command = args.command || "";
|
|
138
|
+
if (shellWritesToProtectedPath(command))
|
|
139
|
+
return deny(REASONS.protectedPath);
|
|
140
|
+
}
|
|
141
|
+
return allow;
|
|
142
|
+
}
|
|
143
|
+
function checkStructuralGuardrails(toolName, args, context) {
|
|
144
|
+
const protectedResult = checkProtectedPaths(toolName, args);
|
|
145
|
+
if (!protectedResult.allowed)
|
|
146
|
+
return protectedResult;
|
|
147
|
+
const destructiveResult = checkDestructiveShellPatterns(toolName, args);
|
|
148
|
+
if (!destructiveResult.allowed)
|
|
149
|
+
return destructiveResult;
|
|
150
|
+
return checkReadBeforeWrite(toolName, args, context);
|
|
151
|
+
}
|
|
152
|
+
// --- ouro CLI trust manifest ---
|
|
153
|
+
/** Minimum trust level required for each ouro CLI subcommand. */
|
|
154
|
+
exports.OURO_CLI_TRUST_MANIFEST = {
|
|
155
|
+
whoami: "acquaintance",
|
|
156
|
+
changelog: "acquaintance",
|
|
157
|
+
"session list": "acquaintance",
|
|
158
|
+
"task board": "friend",
|
|
159
|
+
"task create": "friend",
|
|
160
|
+
"task update": "friend",
|
|
161
|
+
"task show": "friend",
|
|
162
|
+
"task actionable": "friend",
|
|
163
|
+
"task deps": "friend",
|
|
164
|
+
"task sessions": "friend",
|
|
165
|
+
"friend list": "friend",
|
|
166
|
+
"friend show": "friend",
|
|
167
|
+
"friend create": "friend",
|
|
168
|
+
"friend update": "family",
|
|
169
|
+
"reminder create": "friend",
|
|
170
|
+
"config model": "friend",
|
|
171
|
+
"config models": "friend",
|
|
172
|
+
"mcp list": "acquaintance",
|
|
173
|
+
"mcp call": "friend",
|
|
174
|
+
auth: "family",
|
|
175
|
+
"auth verify": "family",
|
|
176
|
+
"auth switch": "family",
|
|
177
|
+
rollback: "family",
|
|
178
|
+
versions: "acquaintance",
|
|
179
|
+
};
|
|
180
|
+
// --- trust level comparison ---
|
|
181
|
+
const LEVEL_ORDER = {
|
|
182
|
+
stranger: 0,
|
|
183
|
+
acquaintance: 1,
|
|
184
|
+
friend: 2,
|
|
185
|
+
family: 3,
|
|
186
|
+
};
|
|
187
|
+
function trustLevelSatisfied(required, actual) {
|
|
188
|
+
return LEVEL_ORDER[actual] >= LEVEL_ORDER[required];
|
|
189
|
+
}
|
|
190
|
+
// --- general CLI allowlists for acquaintance ---
|
|
191
|
+
const ACQUAINTANCE_SHELL_ALLOWLIST = new Set([
|
|
192
|
+
"cat", "ls", "head", "tail", "wc", "file", "stat", "which", "echo",
|
|
193
|
+
"pwd", "env", "printenv", "whoami", "date", "uname",
|
|
194
|
+
]);
|
|
195
|
+
const ACQUAINTANCE_GIT_ALLOWLIST = new Set([
|
|
196
|
+
"status", "log", "show", "diff", "branch",
|
|
197
|
+
]);
|
|
198
|
+
// --- trust-level shell guardrails ---
|
|
199
|
+
function resolveOuroSubcommand(command) {
|
|
200
|
+
const afterOuro = command.replace(/^ouro\s+/, "").trim();
|
|
201
|
+
/* v8 ignore next -- bare "ouro" is caught upstream by checkShellTrustGuardrails @preserve */
|
|
202
|
+
if (!afterOuro)
|
|
203
|
+
return null;
|
|
204
|
+
const tokens = afterOuro.split(/\s+/);
|
|
205
|
+
const twoWord = tokens.length >= 2 ? `${tokens[0]} ${tokens[1]}` : null;
|
|
206
|
+
// Two-word match first (e.g. "task board"), then one-word (e.g. "whoami")
|
|
207
|
+
if (twoWord && exports.OURO_CLI_TRUST_MANIFEST[twoWord])
|
|
208
|
+
return twoWord;
|
|
209
|
+
if (exports.OURO_CLI_TRUST_MANIFEST[tokens[0]])
|
|
210
|
+
return tokens[0];
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
function checkSingleShellCommandTrust(command, trustLevel) {
|
|
214
|
+
const trimmed = command.trim();
|
|
215
|
+
const tokens = trimmed.split(/\s+/);
|
|
216
|
+
const firstToken = tokens[0] || "";
|
|
217
|
+
// ouro CLI — check per-subcommand trust manifest
|
|
218
|
+
if (firstToken === "ouro") {
|
|
219
|
+
const subcommand = resolveOuroSubcommand(trimmed);
|
|
220
|
+
const requiredLevel = subcommand ? exports.OURO_CLI_TRUST_MANIFEST[subcommand] : "friend";
|
|
221
|
+
if (trustLevelSatisfied(requiredLevel, trustLevel))
|
|
222
|
+
return allow;
|
|
223
|
+
return deny(REASONS.needsTrust);
|
|
224
|
+
}
|
|
225
|
+
// git — check subcommand allowlist
|
|
226
|
+
if (firstToken === "git") {
|
|
227
|
+
const gitSub = tokens[1] || "";
|
|
228
|
+
if (ACQUAINTANCE_GIT_ALLOWLIST.has(gitSub))
|
|
229
|
+
return allow;
|
|
230
|
+
return deny(REASONS.needsTrust);
|
|
231
|
+
}
|
|
232
|
+
// General CLI — check allowlist
|
|
233
|
+
if (ACQUAINTANCE_SHELL_ALLOWLIST.has(firstToken))
|
|
234
|
+
return allow;
|
|
235
|
+
return deny(REASONS.needsTrust);
|
|
236
|
+
}
|
|
237
|
+
function checkShellTrustGuardrails(command, trustLevel) {
|
|
238
|
+
// Subshell patterns ($(), backticks) can't be reliably split — check as single command
|
|
239
|
+
/* v8 ignore next -- subshell branch: tested via guardrails.test.ts @preserve */
|
|
240
|
+
if (SUBSHELL_PATTERN.test(command)) {
|
|
241
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
242
|
+
}
|
|
243
|
+
// Compound commands: check each subcommand individually
|
|
244
|
+
const subcommands = splitShellCommands(command);
|
|
245
|
+
if (subcommands.length === 0)
|
|
246
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
247
|
+
for (const sub of subcommands) {
|
|
248
|
+
const result = checkSingleShellCommandTrust(sub, trustLevel);
|
|
249
|
+
if (!result.allowed)
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
return allow;
|
|
253
|
+
}
|
|
254
|
+
function checkWriteTrustGuardrails(toolName, args, context) {
|
|
255
|
+
if (toolName !== "write_file" && toolName !== "edit_file")
|
|
256
|
+
return allow;
|
|
257
|
+
const filePath = args.path || "";
|
|
258
|
+
if (context.agentRoot && filePath.startsWith(context.agentRoot))
|
|
259
|
+
return allow;
|
|
260
|
+
if (!context.agentRoot)
|
|
261
|
+
return allow;
|
|
262
|
+
return deny(REASONS.needsTrustForWrite);
|
|
263
|
+
}
|
|
264
|
+
function checkTrustLevelGuardrails(toolName, args, context) {
|
|
265
|
+
// Trusted levels (family/friend) — no trust guardrails. Undefined defaults to friend.
|
|
266
|
+
if ((0, types_1.isTrustedLevel)(context.trustLevel))
|
|
267
|
+
return allow;
|
|
268
|
+
if (toolName === "shell") {
|
|
269
|
+
return checkShellTrustGuardrails(args.command || "", context.trustLevel);
|
|
270
|
+
}
|
|
271
|
+
return checkWriteTrustGuardrails(toolName, args, context);
|
|
272
|
+
}
|
|
273
|
+
// --- main entry point ---
|
|
274
|
+
function guardInvocation(toolName, args, context) {
|
|
275
|
+
(0, runtime_1.emitNervesEvent)({
|
|
276
|
+
component: "tools",
|
|
277
|
+
event: "tools.guard_check",
|
|
278
|
+
message: "guardrail check",
|
|
279
|
+
meta: { toolName },
|
|
280
|
+
});
|
|
281
|
+
// Read-only tools are always allowed (no structural or trust guardrails)
|
|
282
|
+
if (READ_ONLY_TOOLS.has(toolName))
|
|
283
|
+
return allow;
|
|
284
|
+
// Layer 1: structural guardrails (always on)
|
|
285
|
+
const structuralResult = checkStructuralGuardrails(toolName, args, context);
|
|
286
|
+
if (!structuralResult.allowed)
|
|
287
|
+
return structuralResult;
|
|
288
|
+
// Layer 2: trust-level guardrails (varies by friend's trust)
|
|
289
|
+
return checkTrustLevelGuardrails(toolName, args, context);
|
|
290
|
+
}
|