@ouro.bot/cli 0.1.0-alpha.15 → 0.1.0-alpha.17

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.
@@ -61,6 +61,20 @@ const codingStatusTool = {
61
61
  },
62
62
  },
63
63
  };
64
+ const codingTailTool = {
65
+ type: "function",
66
+ function: {
67
+ name: "coding_tail",
68
+ description: "show recent stdout/stderr tail for a coding session in a readable format",
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ sessionId: { type: "string" },
73
+ },
74
+ required: ["sessionId"],
75
+ },
76
+ },
77
+ };
64
78
  const codingSendInputTool = {
65
79
  type: "function",
66
80
  function: {
@@ -93,7 +107,7 @@ const codingKillTool = {
93
107
  exports.codingToolDefinitions = [
94
108
  {
95
109
  tool: codingSpawnTool,
96
- handler: async (args) => {
110
+ handler: async (args, ctx) => {
97
111
  emitCodingToolEvent("coding_spawn");
98
112
  const rawRunner = requireArg(args, "runner");
99
113
  if (!rawRunner)
@@ -122,7 +136,19 @@ exports.codingToolDefinitions = [
122
136
  const stateFile = optionalArg(args, "stateFile");
123
137
  if (stateFile)
124
138
  request.stateFile = stateFile;
125
- const session = await (0, index_1.getCodingSessionManager)().spawnSession(request);
139
+ const manager = (0, index_1.getCodingSessionManager)();
140
+ const session = await manager.spawnSession(request);
141
+ if (args.runner === "codex" && args.taskRef) {
142
+ (0, runtime_1.emitNervesEvent)({
143
+ component: "repertoire",
144
+ event: "repertoire.coding_codex_spawned",
145
+ message: "spawned codex coding session",
146
+ meta: { sessionId: session.id, taskRef: args.taskRef },
147
+ });
148
+ }
149
+ if (ctx?.codingFeedback) {
150
+ (0, index_1.attachCodingSessionFeedback)(manager, session, ctx.codingFeedback);
151
+ }
126
152
  return JSON.stringify(session);
127
153
  },
128
154
  },
@@ -141,6 +167,19 @@ exports.codingToolDefinitions = [
141
167
  return JSON.stringify(session);
142
168
  },
143
169
  },
170
+ {
171
+ tool: codingTailTool,
172
+ handler: (args) => {
173
+ emitCodingToolEvent("coding_tail");
174
+ const sessionId = requireArg(args, "sessionId");
175
+ if (!sessionId)
176
+ return "sessionId is required";
177
+ const session = (0, index_1.getCodingSessionManager)().getSession(sessionId);
178
+ if (!session)
179
+ return `session not found: ${sessionId}`;
180
+ return (0, index_1.formatCodingTail)(session);
181
+ },
182
+ },
144
183
  {
145
184
  tool: codingSendInputTool,
146
185
  handler: (args) => {
@@ -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
  ]
@@ -235,7 +235,7 @@ exports.baseToolDefinitions = [
235
235
  },
236
236
  handler: (a) => {
237
237
  try {
238
- const result = (0, child_process_1.spawnSync)("claude", ["-p", "--dangerously-skip-permissions", "--add-dir", "."], {
238
+ const result = (0, child_process_1.spawnSync)("claude", ["-p", "--no-session-persistence", "--dangerously-skip-permissions", "--add-dir", "."], {
239
239
  input: a.prompt,
240
240
  encoding: "utf-8",
241
241
  timeout: 60000,
@@ -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
  }
@@ -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 baseToolsForCapabilities(capabilities) {
23
- const isRemoteChannel = capabilities?.channel === "teams" || capabilities?.channel === "bluebubbles";
24
- if (!isRemoteChannel)
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 integrationDefs = [...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions].filter((d) => d.integration && available.has(d.integration));
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,9 +109,8 @@ async function execTool(name, args, ctx) {
87
109
  });
88
110
  return `unknown: ${name}`;
89
111
  }
90
- const isRemoteChannel = ctx?.context?.channel?.channel === "teams" || ctx?.context?.channel?.channel === "bluebubbles";
91
- if (isRemoteChannel && REMOTE_BLOCKED_LOCAL_TOOLS.has(name)) {
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",
@@ -176,6 +197,8 @@ function summarizeArgs(name, args) {
176
197
  return summarizeKeyValues(args, ["runner", "workdir", "taskRef"]);
177
198
  if (name === "coding_status")
178
199
  return summarizeKeyValues(args, ["sessionId"]);
200
+ if (name === "coding_tail")
201
+ return summarizeKeyValues(args, ["sessionId"]);
179
202
  if (name === "coding_send_input")
180
203
  return summarizeKeyValues(args, ["sessionId", "input"]);
181
204
  if (name === "coding_kill")
@@ -195,5 +218,13 @@ function summarizeArgs(name, args) {
195
218
  }
196
219
  if (name === "ado_backlog_list")
197
220
  return summarizeKeyValues(args, ["organization", "project"]);
221
+ if (name === "ado_batch_update")
222
+ return summarizeKeyValues(args, ["organization", "project"]);
223
+ if (name === "ado_create_epic" || name === "ado_create_issue")
224
+ return summarizeKeyValues(args, ["organization", "project", "title"]);
225
+ if (name === "ado_move_items")
226
+ return summarizeKeyValues(args, ["organization", "project", "workItemIds"]);
227
+ if (name === "ado_restructure_backlog")
228
+ return summarizeKeyValues(args, ["organization", "project"]);
198
229
  return summarizeUnknownArgs(args);
199
230
  }