@ouro.bot/cli 0.1.0-alpha.3 → 0.1.0-alpha.30
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/AdoptionSpecialist.ouro/psyche/identities/python.md +30 -0
- package/assets/ouroboros.png +0 -0
- package/changelog.json +80 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +562 -64
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +87 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +2 -11
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +134 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +129 -0
- package/dist/heart/daemon/specialist-prompt.js +98 -0
- package/dist/heart/daemon/specialist-tools.js +237 -0
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/daemon/update-checker.js +103 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/identity.js +85 -1
- package/dist/heart/providers/anthropic.js +19 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +99 -21
- package/dist/mind/bundle-manifest.js +69 -0
- package/dist/mind/first-impressions.js +2 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +94 -3
- package/dist/nerves/cli-logging.js +15 -2
- 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 +61 -2
- 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/tools-base.js +69 -5
- package/dist/repertoire/tools-teams.js +57 -4
- package/dist/repertoire/tools.js +44 -11
- package/dist/senses/bluebubbles-client.js +434 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +251 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles-session-cleanup.js +73 -0
- package/dist/senses/bluebubbles.js +449 -0
- package/dist/senses/cli.js +299 -133
- package/dist/senses/debug-activity.js +108 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +15 -6
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- package/dist/inner-worker-entry.js +0 -4
|
@@ -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
|
]
|
|
@@ -46,6 +46,25 @@ const tasks_1 = require("./tasks");
|
|
|
46
46
|
const tools_1 = require("./coding/tools");
|
|
47
47
|
const memory_1 = require("../mind/memory");
|
|
48
48
|
const postIt = (msg) => `post-it from past you:\n${msg}`;
|
|
49
|
+
function normalizeOptionalText(value) {
|
|
50
|
+
if (typeof value !== "string")
|
|
51
|
+
return null;
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
54
|
+
}
|
|
55
|
+
function buildTaskCreateInput(args) {
|
|
56
|
+
return {
|
|
57
|
+
title: args.title,
|
|
58
|
+
type: args.type,
|
|
59
|
+
category: args.category,
|
|
60
|
+
body: args.body,
|
|
61
|
+
status: normalizeOptionalText(args.status) ?? undefined,
|
|
62
|
+
validator: normalizeOptionalText(args.validator),
|
|
63
|
+
requester: normalizeOptionalText(args.requester),
|
|
64
|
+
cadence: normalizeOptionalText(args.cadence),
|
|
65
|
+
scheduledAt: normalizeOptionalText(args.scheduledAt),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
49
68
|
exports.baseToolDefinitions = [
|
|
50
69
|
{
|
|
51
70
|
tool: {
|
|
@@ -75,7 +94,11 @@ exports.baseToolDefinitions = [
|
|
|
75
94
|
},
|
|
76
95
|
},
|
|
77
96
|
},
|
|
78
|
-
handler: (a) =>
|
|
97
|
+
handler: (a) => {
|
|
98
|
+
fs.mkdirSync(path.dirname(a.path), { recursive: true });
|
|
99
|
+
fs.writeFileSync(a.path, a.content, "utf-8");
|
|
100
|
+
return "ok";
|
|
101
|
+
},
|
|
79
102
|
},
|
|
80
103
|
{
|
|
81
104
|
tool: {
|
|
@@ -235,7 +258,7 @@ exports.baseToolDefinitions = [
|
|
|
235
258
|
},
|
|
236
259
|
handler: (a) => {
|
|
237
260
|
try {
|
|
238
|
-
const result = (0, child_process_1.spawnSync)("claude", ["-p", "--dangerously-skip-permissions", "--add-dir", "."], {
|
|
261
|
+
const result = (0, child_process_1.spawnSync)("claude", ["-p", "--no-session-persistence", "--dangerously-skip-permissions", "--add-dir", "."], {
|
|
239
262
|
input: a.prompt,
|
|
240
263
|
encoding: "utf-8",
|
|
241
264
|
timeout: 60000,
|
|
@@ -394,7 +417,7 @@ exports.baseToolDefinitions = [
|
|
|
394
417
|
type: "function",
|
|
395
418
|
function: {
|
|
396
419
|
name: "task_create",
|
|
397
|
-
description: "create a new task in the bundle task system",
|
|
420
|
+
description: "create a new task in the bundle task system. optionally set `scheduledAt` for a one-time reminder or `cadence` for recurring daemon-scheduled work.",
|
|
398
421
|
parameters: {
|
|
399
422
|
type: "object",
|
|
400
423
|
properties: {
|
|
@@ -402,18 +425,59 @@ exports.baseToolDefinitions = [
|
|
|
402
425
|
type: { type: "string", enum: ["one-shot", "ongoing", "habit"] },
|
|
403
426
|
category: { type: "string" },
|
|
404
427
|
body: { type: "string" },
|
|
428
|
+
status: { type: "string" },
|
|
429
|
+
validator: { type: "string" },
|
|
430
|
+
requester: { type: "string" },
|
|
431
|
+
scheduledAt: { type: "string", description: "ISO timestamp for a one-time scheduled run/reminder" },
|
|
432
|
+
cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
|
|
405
433
|
},
|
|
406
434
|
required: ["title", "type", "category", "body"],
|
|
407
435
|
},
|
|
408
436
|
},
|
|
409
437
|
},
|
|
410
438
|
handler: (a) => {
|
|
439
|
+
try {
|
|
440
|
+
const created = (0, tasks_1.getTaskModule)().createTask(buildTaskCreateInput(a));
|
|
441
|
+
return `created: ${created}`;
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
return `error: ${error instanceof Error ? error.message : String(error)}`;
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
tool: {
|
|
450
|
+
type: "function",
|
|
451
|
+
function: {
|
|
452
|
+
name: "schedule_reminder",
|
|
453
|
+
description: "create a scheduled reminder or recurring daemon job. use `scheduledAt` for one-time reminders and `cadence` for recurring reminders. this writes canonical task fields that the daemon reconciles into OS-level jobs.",
|
|
454
|
+
parameters: {
|
|
455
|
+
type: "object",
|
|
456
|
+
properties: {
|
|
457
|
+
title: { type: "string" },
|
|
458
|
+
body: { type: "string" },
|
|
459
|
+
category: { type: "string" },
|
|
460
|
+
scheduledAt: { type: "string", description: "ISO timestamp for a one-time reminder" },
|
|
461
|
+
cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
|
|
462
|
+
},
|
|
463
|
+
required: ["title", "body"],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
handler: (a) => {
|
|
468
|
+
const scheduledAt = normalizeOptionalText(a.scheduledAt);
|
|
469
|
+
const cadence = normalizeOptionalText(a.cadence);
|
|
470
|
+
if (!scheduledAt && !cadence) {
|
|
471
|
+
return "error: provide scheduledAt or cadence";
|
|
472
|
+
}
|
|
411
473
|
try {
|
|
412
474
|
const created = (0, tasks_1.getTaskModule)().createTask({
|
|
413
475
|
title: a.title,
|
|
414
|
-
type:
|
|
415
|
-
category: a.category,
|
|
476
|
+
type: cadence ? "habit" : "one-shot",
|
|
477
|
+
category: normalizeOptionalText(a.category) ?? "reminder",
|
|
416
478
|
body: a.body,
|
|
479
|
+
scheduledAt,
|
|
480
|
+
cadence,
|
|
417
481
|
});
|
|
418
482
|
return `created: ${created}`;
|
|
419
483
|
}
|
|
@@ -82,7 +82,7 @@ exports.teamsToolDefinitions = [
|
|
|
82
82
|
type: "function",
|
|
83
83
|
function: {
|
|
84
84
|
name: "ado_query",
|
|
85
|
-
description: "GET or POST (for WIQL read queries) any Azure DevOps API endpoint. Use ado_docs first to look up the correct path.",
|
|
85
|
+
description: "GET or POST (for WIQL read queries) any Azure DevOps API endpoint. Use ado_docs first to look up the correct path and host.",
|
|
86
86
|
parameters: {
|
|
87
87
|
type: "object",
|
|
88
88
|
properties: {
|
|
@@ -90,6 +90,7 @@ exports.teamsToolDefinitions = [
|
|
|
90
90
|
path: { type: "string", description: "ADO API path after /{org}, e.g. /_apis/wit/wiql" },
|
|
91
91
|
method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (defaults to GET)" },
|
|
92
92
|
body: { type: "string", description: "JSON request body (optional, used with POST for WIQL)" },
|
|
93
|
+
host: { type: "string", description: "API host override for non-standard APIs (e.g. 'vsapm.dev.azure.com' for entitlements, 'vssps.dev.azure.com' for users). Omit for standard dev.azure.com." },
|
|
93
94
|
},
|
|
94
95
|
required: ["organization", "path"],
|
|
95
96
|
},
|
|
@@ -100,7 +101,7 @@ exports.teamsToolDefinitions = [
|
|
|
100
101
|
return "AUTH_REQUIRED:ado -- I need access to Azure DevOps. Please sign in when prompted.";
|
|
101
102
|
}
|
|
102
103
|
const method = args.method || "GET";
|
|
103
|
-
const result = await (0, ado_client_1.adoRequest)(ctx.adoToken, method, args.organization, args.path, args.body);
|
|
104
|
+
const result = await (0, ado_client_1.adoRequest)(ctx.adoToken, method, args.organization, args.path, args.body, args.host);
|
|
104
105
|
checkAndRecord403(result, "ado", args.organization, method, ctx);
|
|
105
106
|
return result;
|
|
106
107
|
},
|
|
@@ -111,7 +112,7 @@ exports.teamsToolDefinitions = [
|
|
|
111
112
|
type: "function",
|
|
112
113
|
function: {
|
|
113
114
|
name: "ado_mutate",
|
|
114
|
-
description: "POST/PATCH/DELETE any Azure DevOps API endpoint for actual mutations. Use ado_docs first to look up the correct path.",
|
|
115
|
+
description: "POST/PATCH/DELETE any Azure DevOps API endpoint for actual mutations. Use ado_docs first to look up the correct path and host.",
|
|
115
116
|
parameters: {
|
|
116
117
|
type: "object",
|
|
117
118
|
properties: {
|
|
@@ -119,6 +120,7 @@ exports.teamsToolDefinitions = [
|
|
|
119
120
|
organization: { type: "string", description: "Azure DevOps organization name" },
|
|
120
121
|
path: { type: "string", description: "ADO API path after /{org}" },
|
|
121
122
|
body: { type: "string", description: "JSON request body (optional)" },
|
|
123
|
+
host: { type: "string", description: "API host override for non-standard APIs (e.g. 'vsapm.dev.azure.com' for entitlements, 'vssps.dev.azure.com' for users). Omit for standard dev.azure.com." },
|
|
122
124
|
},
|
|
123
125
|
required: ["method", "organization", "path"],
|
|
124
126
|
},
|
|
@@ -133,7 +135,7 @@ exports.teamsToolDefinitions = [
|
|
|
133
135
|
}
|
|
134
136
|
/* v8 ignore next -- fallback unreachable: method is validated against MUTATE_METHODS above @preserve */
|
|
135
137
|
const action = METHOD_TO_ACTION[args.method] || args.method;
|
|
136
|
-
const result = await (0, ado_client_1.adoRequest)(ctx.adoToken, args.method, args.organization, args.path, args.body);
|
|
138
|
+
const result = await (0, ado_client_1.adoRequest)(ctx.adoToken, args.method, args.organization, args.path, args.body, args.host);
|
|
137
139
|
checkAndRecord403(result, "ado", args.organization, action, ctx);
|
|
138
140
|
return result;
|
|
139
141
|
},
|
|
@@ -201,6 +203,53 @@ exports.teamsToolDefinitions = [
|
|
|
201
203
|
},
|
|
202
204
|
integration: "ado",
|
|
203
205
|
},
|
|
206
|
+
// -- Proactive messaging --
|
|
207
|
+
{
|
|
208
|
+
tool: {
|
|
209
|
+
type: "function",
|
|
210
|
+
function: {
|
|
211
|
+
name: "teams_send_message",
|
|
212
|
+
description: "send a proactive 1:1 Teams message to a user. requires their AAD object ID (use graph_query /users to find it). the message appears as coming from the bot.",
|
|
213
|
+
parameters: {
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
user_id: { type: "string", description: "AAD object ID of the user to message" },
|
|
217
|
+
user_name: { type: "string", description: "display name of the user (for logging)" },
|
|
218
|
+
message: { type: "string", description: "message text to send" },
|
|
219
|
+
tenant_id: { type: "string", description: "tenant ID (optional, defaults to current conversation tenant)" },
|
|
220
|
+
},
|
|
221
|
+
required: ["user_id", "message"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
/* v8 ignore start -- proactive messaging requires live Teams SDK conversation client @preserve */
|
|
226
|
+
handler: async (args, ctx) => {
|
|
227
|
+
if (!ctx?.botApi) {
|
|
228
|
+
return "proactive messaging is not available -- no bot API context (this tool only works in the Teams channel)";
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const tenantId = args.tenant_id || ctx.tenantId;
|
|
232
|
+
// Cast to the SDK's ConversationClient shape (kept as `unknown` in ToolContext to avoid type coupling)
|
|
233
|
+
const conversations = ctx.botApi.conversations;
|
|
234
|
+
const conversation = await conversations.create({
|
|
235
|
+
bot: { id: ctx.botApi.id },
|
|
236
|
+
members: [{ id: args.user_id, role: "user", name: args.user_name || args.user_id }],
|
|
237
|
+
tenantId,
|
|
238
|
+
isGroup: false,
|
|
239
|
+
});
|
|
240
|
+
await conversations.activities(conversation.id).create({
|
|
241
|
+
type: "message",
|
|
242
|
+
text: args.message,
|
|
243
|
+
});
|
|
244
|
+
return `message sent to ${args.user_name || args.user_id}`;
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
return `failed to send proactive message: ${e instanceof Error ? e.message : String(e)}`;
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
/* v8 ignore stop */
|
|
251
|
+
confirmationRequired: true,
|
|
252
|
+
},
|
|
204
253
|
// -- Documentation tools --
|
|
205
254
|
{
|
|
206
255
|
tool: {
|
|
@@ -268,6 +317,8 @@ function searchEndpoints(entries, query) {
|
|
|
268
317
|
` ${e.description}`,
|
|
269
318
|
` Params: ${e.params || "none"}`,
|
|
270
319
|
];
|
|
320
|
+
if (e.host)
|
|
321
|
+
lines.push(` Host: ${e.host}`);
|
|
271
322
|
if (e.scopes)
|
|
272
323
|
lines.push(` Scopes: ${e.scopes}`);
|
|
273
324
|
return lines.join("\n");
|
|
@@ -304,5 +355,7 @@ function summarizeTeamsArgs(name, args) {
|
|
|
304
355
|
return summarizeKeyValues(["query"]);
|
|
305
356
|
if (name === "ado_docs")
|
|
306
357
|
return summarizeKeyValues(["query"]);
|
|
358
|
+
if (name === "teams_send_message")
|
|
359
|
+
return summarizeKeyValues(["user_name", "user_id"]);
|
|
307
360
|
return undefined;
|
|
308
361
|
}
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -19,9 +19,29 @@ Object.defineProperty(exports, "teamsTools", { enumerable: true, get: function (
|
|
|
19
19
|
// All tool definitions in a single registry
|
|
20
20
|
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
21
21
|
const REMOTE_BLOCKED_LOCAL_TOOLS = new Set(["shell", "read_file", "write_file", "git_commit", "gh_cli"]);
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
function isRemoteChannel(capabilities) {
|
|
23
|
+
return capabilities?.channel === "teams" || capabilities?.channel === "bluebubbles";
|
|
24
|
+
}
|
|
25
|
+
function isSharedRemoteContext(friend) {
|
|
26
|
+
const externalIds = friend.externalIds ?? [];
|
|
27
|
+
return externalIds.some((externalId) => externalId.externalId.startsWith("group:") || externalId.provider === "teams-conversation");
|
|
28
|
+
}
|
|
29
|
+
function isTrustedRemoteContext(context) {
|
|
30
|
+
if (!context?.friend || !isRemoteChannel(context.channel))
|
|
31
|
+
return false;
|
|
32
|
+
const trustLevel = context.friend.trustLevel ?? "stranger";
|
|
33
|
+
return trustLevel !== "stranger" && !isSharedRemoteContext(context.friend);
|
|
34
|
+
}
|
|
35
|
+
function shouldBlockLocalTools(capabilities, context) {
|
|
36
|
+
if (!isRemoteChannel(capabilities))
|
|
37
|
+
return false;
|
|
38
|
+
return !isTrustedRemoteContext(context);
|
|
39
|
+
}
|
|
40
|
+
function blockedLocalToolMessage() {
|
|
41
|
+
return "I can't do that from here because I'm talking to multiple people in a shared remote channel, and local shell/file/git/gh operations could let conversations interfere with each other. Ask me for a remote-safe alternative (Graph/ADO/web), or run that operation from CLI.";
|
|
42
|
+
}
|
|
43
|
+
function baseToolsForCapabilities(capabilities, context) {
|
|
44
|
+
if (!shouldBlockLocalTools(capabilities, context))
|
|
25
45
|
return tools_base_1.tools;
|
|
26
46
|
return tools_base_1.tools.filter((tool) => !REMOTE_BLOCKED_LOCAL_TOOLS.has(tool.function.name));
|
|
27
47
|
}
|
|
@@ -39,13 +59,15 @@ function applyPreference(tool, pref) {
|
|
|
39
59
|
// Base tools (no integration) are always included.
|
|
40
60
|
// Teams/integration tools are included only if their integration is in availableIntegrations.
|
|
41
61
|
// When toolPreferences is provided, matching preferences are appended to tool descriptions.
|
|
42
|
-
function getToolsForChannel(capabilities, toolPreferences) {
|
|
43
|
-
const baseTools = baseToolsForCapabilities(capabilities);
|
|
62
|
+
function getToolsForChannel(capabilities, toolPreferences, context) {
|
|
63
|
+
const baseTools = baseToolsForCapabilities(capabilities, context);
|
|
44
64
|
if (!capabilities || capabilities.availableIntegrations.length === 0) {
|
|
45
65
|
return baseTools;
|
|
46
66
|
}
|
|
47
67
|
const available = new Set(capabilities.availableIntegrations);
|
|
48
|
-
const
|
|
68
|
+
const channelDefs = [...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
69
|
+
// Include tools whose integration is available, plus channel tools with no integration gate (e.g. teams_send_message)
|
|
70
|
+
const integrationDefs = channelDefs.filter((d) => d.integration ? available.has(d.integration) : capabilities.channel === "teams");
|
|
49
71
|
if (!toolPreferences || Object.keys(toolPreferences).length === 0) {
|
|
50
72
|
return [...baseTools, ...integrationDefs.map((d) => d.tool)];
|
|
51
73
|
}
|
|
@@ -87,15 +109,14 @@ async function execTool(name, args, ctx) {
|
|
|
87
109
|
});
|
|
88
110
|
return `unknown: ${name}`;
|
|
89
111
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const message = "I can't do that from here because I'm talking to multiple people in a shared remote channel, and local shell/file/git/gh operations could let conversations interfere with each other. Ask me for a remote-safe alternative (Graph/ADO/web), or run that operation from CLI.";
|
|
112
|
+
if (shouldBlockLocalTools(ctx?.context?.channel, ctx?.context) && REMOTE_BLOCKED_LOCAL_TOOLS.has(name)) {
|
|
113
|
+
const message = blockedLocalToolMessage();
|
|
93
114
|
(0, runtime_1.emitNervesEvent)({
|
|
94
115
|
level: "warn",
|
|
95
116
|
event: "tool.error",
|
|
96
117
|
component: "tools",
|
|
97
118
|
message: "blocked local tool in remote channel",
|
|
98
|
-
meta: { name, channel:
|
|
119
|
+
meta: { name, channel: ctx?.context?.channel?.channel },
|
|
99
120
|
});
|
|
100
121
|
return message;
|
|
101
122
|
}
|
|
@@ -163,7 +184,9 @@ function summarizeArgs(name, args) {
|
|
|
163
184
|
if (name === "load_skill")
|
|
164
185
|
return summarizeKeyValues(args, ["name"]);
|
|
165
186
|
if (name === "task_create")
|
|
166
|
-
return summarizeKeyValues(args, ["title", "type", "category"]);
|
|
187
|
+
return summarizeKeyValues(args, ["title", "type", "category", "scheduledAt", "cadence"]);
|
|
188
|
+
if (name === "schedule_reminder")
|
|
189
|
+
return summarizeKeyValues(args, ["title", "scheduledAt", "cadence"]);
|
|
167
190
|
if (name === "task_update_status")
|
|
168
191
|
return summarizeKeyValues(args, ["name", "status"]);
|
|
169
192
|
if (name === "task_board_status")
|
|
@@ -176,6 +199,8 @@ function summarizeArgs(name, args) {
|
|
|
176
199
|
return summarizeKeyValues(args, ["runner", "workdir", "taskRef"]);
|
|
177
200
|
if (name === "coding_status")
|
|
178
201
|
return summarizeKeyValues(args, ["sessionId"]);
|
|
202
|
+
if (name === "coding_tail")
|
|
203
|
+
return summarizeKeyValues(args, ["sessionId"]);
|
|
179
204
|
if (name === "coding_send_input")
|
|
180
205
|
return summarizeKeyValues(args, ["sessionId", "input"]);
|
|
181
206
|
if (name === "coding_kill")
|
|
@@ -195,5 +220,13 @@ function summarizeArgs(name, args) {
|
|
|
195
220
|
}
|
|
196
221
|
if (name === "ado_backlog_list")
|
|
197
222
|
return summarizeKeyValues(args, ["organization", "project"]);
|
|
223
|
+
if (name === "ado_batch_update")
|
|
224
|
+
return summarizeKeyValues(args, ["organization", "project"]);
|
|
225
|
+
if (name === "ado_create_epic" || name === "ado_create_issue")
|
|
226
|
+
return summarizeKeyValues(args, ["organization", "project", "title"]);
|
|
227
|
+
if (name === "ado_move_items")
|
|
228
|
+
return summarizeKeyValues(args, ["organization", "project", "workItemIds"]);
|
|
229
|
+
if (name === "ado_restructure_backlog")
|
|
230
|
+
return summarizeKeyValues(args, ["organization", "project"]);
|
|
198
231
|
return summarizeUnknownArgs(args);
|
|
199
232
|
}
|