crow-central-agency 0.26.8 → 0.26.10

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 (60) hide show
  1. package/dist/_shared/index.js +1 -1
  2. package/dist/_shared/schemas/agent-runtime-state.schema.js +4 -0
  3. package/dist/mcp/agents/search-workspace.js +3 -3
  4. package/dist/mcp/artifacts/artifacts-mcp-server-utils.js +2 -0
  5. package/dist/mcp/artifacts/list-artifacts.js +3 -3
  6. package/dist/mcp/artifacts/list-circle-artifacts.js +3 -3
  7. package/dist/mcp/artifacts/read-artifact.js +7 -3
  8. package/dist/mcp/artifacts/read-circle-artifact.js +7 -3
  9. package/dist/mcp/claude-mcp-adapter.js +30 -2
  10. package/dist/mcp/copilot-mcp-adapter.js +10 -2
  11. package/dist/mcp/feed/get-feed-item-content.js +8 -2
  12. package/dist/mcp/feed/get-feed-items.js +3 -3
  13. package/dist/mcp/feed/get-recent-feed-items.js +3 -3
  14. package/dist/mcp/feed/list-feeds.js +3 -3
  15. package/dist/mcp/feed/search-feed-items.js +3 -3
  16. package/dist/mcp/gmail/get-gmail-message-content.js +8 -2
  17. package/dist/mcp/places/geocode-place.js +10 -1
  18. package/dist/mcp/places/places-format-utils.js +24 -0
  19. package/dist/mcp/places/places-routing-input.js +49 -0
  20. package/dist/mcp/places/search-nearby-places.js +12 -1
  21. package/dist/mcp/reminders/list-reminders.js +3 -3
  22. package/dist/mcp/tool-utils.js +30 -10
  23. package/dist/public/assets/{architectureDiagram-3BPJPVTR-DDgVnuoo.js → architectureDiagram-3BPJPVTR-DuUOU6pr.js} +1 -1
  24. package/dist/public/assets/{chunk-727SXJPM-PDiAD8Du.js → chunk-727SXJPM-Bjc8-ytz.js} +1 -1
  25. package/dist/public/assets/{chunk-AQP2D5EJ-wy5_L253.js → chunk-AQP2D5EJ-Ct15bFV1.js} +1 -1
  26. package/dist/public/assets/{classDiagram-v2-Q7XG4LA2-BYJ5bFf6.js → classDiagram-4FO5ZUOK-DU0awUqf.js} +1 -1
  27. package/dist/public/assets/{classDiagram-4FO5ZUOK-BYJ5bFf6.js → classDiagram-v2-Q7XG4LA2-DU0awUqf.js} +1 -1
  28. package/dist/public/assets/{dagre-BM42HDAG-CjoXczSR.js → dagre-BM42HDAG-9AAU2jPb.js} +1 -1
  29. package/dist/public/assets/{diagram-2AECGRRQ-CCKmxsdo.js → diagram-2AECGRRQ-BUgXkqze.js} +1 -1
  30. package/dist/public/assets/{diagram-5GNKFQAL-yKZCpt4W.js → diagram-5GNKFQAL-Dfxv_OUK.js} +1 -1
  31. package/dist/public/assets/{diagram-LMA3HP47-DneZvlxQ.js → diagram-LMA3HP47-BUi_NXCo.js} +1 -1
  32. package/dist/public/assets/{diagram-OG6HWLK6-Ud_EdkZh.js → diagram-OG6HWLK6-D5A09xa3.js} +1 -1
  33. package/dist/public/assets/{erDiagram-TEJ5UH35-B-vZXvBd.js → erDiagram-TEJ5UH35-DP8KMEsf.js} +1 -1
  34. package/dist/public/assets/{flowDiagram-I6XJVG4X-rVn14Wbt.js → flowDiagram-I6XJVG4X-DWRshnM0.js} +1 -1
  35. package/dist/public/assets/index-bzEM4vkn.js +505 -0
  36. package/dist/public/assets/{infoDiagram-5YYISTIA-BbX08lDC.js → infoDiagram-5YYISTIA-DvUU6-Mk.js} +1 -1
  37. package/dist/public/assets/{ishikawaDiagram-YF4QCWOH-CYs43POM.js → ishikawaDiagram-YF4QCWOH-BwkEtQo6.js} +1 -1
  38. package/dist/public/assets/{kanban-definition-UN3LZRKU--Cb13wva.js → kanban-definition-UN3LZRKU-Chsc6lfZ.js} +1 -1
  39. package/dist/public/assets/{mindmap-definition-RKZ34NQL-CQJGU9F-.js → mindmap-definition-RKZ34NQL-SchvENg6.js} +1 -1
  40. package/dist/public/assets/{pieDiagram-4H26LBE5-DMNxEuMG.js → pieDiagram-4H26LBE5-D8PG4kzl.js} +1 -1
  41. package/dist/public/assets/{requirementDiagram-4Y6WPE33-DVzHOXTK.js → requirementDiagram-4Y6WPE33-C0JZopEE.js} +1 -1
  42. package/dist/public/assets/{sequenceDiagram-3UESZ5HK-CrIiBex5.js → sequenceDiagram-3UESZ5HK-DOcoKuQk.js} +1 -1
  43. package/dist/public/assets/{stateDiagram-AJRCARHV-LVBo72aS.js → stateDiagram-AJRCARHV-D7IMWgel.js} +1 -1
  44. package/dist/public/assets/{stateDiagram-v2-BHNVJYJU-pc1U14yi.js → stateDiagram-v2-BHNVJYJU-C5xxJ6Wc.js} +1 -1
  45. package/dist/public/assets/{timeline-definition-PNZ67QCA-COcJo8Ga.js → timeline-definition-PNZ67QCA-oHiSgCnQ.js} +1 -1
  46. package/dist/public/assets/{vennDiagram-CIIHVFJN-qOZm-QC4.js → vennDiagram-CIIHVFJN-wc88IX6S.js} +1 -1
  47. package/dist/public/assets/{wardleyDiagram-YWT4CUSO-idQAwlbA.js → wardleyDiagram-YWT4CUSO-q-LGWHCn.js} +1 -1
  48. package/dist/public/assets/{xychartDiagram-2RQKCTM6-Caywd0sW.js → xychartDiagram-2RQKCTM6-BgxGnvq2.js} +1 -1
  49. package/dist/public/index.html +1 -1
  50. package/dist/routes/agent.routes.js +3 -0
  51. package/dist/routines/gmail-notification-routine.js +6 -1
  52. package/dist/runner/stream-processor.js +3 -0
  53. package/dist/services/places/google/google-place-mapper.js +31 -0
  54. package/dist/services/places/google/google-places-adapter.js +33 -9
  55. package/dist/services/places/google/google-places-adapter.types.js +11 -0
  56. package/dist/services/places/google/google-places-client.js +29 -4
  57. package/dist/services/places/places-manager.types.js +10 -0
  58. package/dist/services/runtime/agent-runtime-manager.js +15 -1
  59. package/package.json +5 -5
  60. package/dist/public/assets/index-BTKdu_X_.js +0 -478
@@ -1,7 +1,7 @@
1
1
  export { CLAUDE_MODELS, GITHUB_COPILOT_MODELS, CLAUDE_CODE_MODEL_OPTIONS, ModelOptionSchema, REASONING_EFFORT, ReasoningEffortSchema, CLAUDE_DEFAULT_MODEL, COPILOT_DEFAULT_MODEL, AgentIdSchema, AgentConfigSchema, AgentConfigTemplateSchema, CreateAgentInputSchema, UpdateAgentInputSchema, ToolConfigSchema, PermissionModeSchema, SettingSourceSchema, DiscoveredSkillSchema, LoopConfigSchema, ConfiguredFeedSchema, MAX_LOOP_TIMES, PERMISSION_MODE, SETTING_SOURCE, DEFAULT_SETTING_SOURCES, TOOL_MODE, DEFAULT_CLAUDE_CODE_AVAILABLE_TOOLS, DEFAULT_GITHUB_COPILOT_AVAILABLE_TOOLS, DEFAULT_AVAILABLE_TOOLS_BY_TYPE, SUBAGENT_TOOL_NAME, AGENT_TYPE, AgentTypeSchema, DEFAULT_AGENT_TYPE, AGENT_NAME_MAX_LENGTH, } from "./schemas/agent.schema.js";
2
2
  export { SchedulerTimeSchema, DAY_OF_WEEK, DayOfWeekSchema, TimeModeSchema, TIME_MODE, } from "./schemas/scheduler.schema.js";
3
3
  export { createApiSuccessSchema, ApiErrorSchema, } from "./schemas/api-response.schema.js";
4
- export { SessionUsageSchema, PendingPermissionInfoSchema, PendingInstructionReminderSchema, AgentRuntimeStateSchema, AgentActivitySchema, AGENT_ACTIVITY_TYPE, AGENT_STATUS, } from "./schemas/agent-runtime-state.schema.js";
4
+ export { SessionUsageSchema, PendingPermissionInfoSchema, PendingInstructionReminderSchema, AgentRuntimeStateSchema, AgentActivitySchema, AGENT_ACTIVITY_TYPE, AGENT_STATUS, MAX_INPUT_HISTORY, } from "./schemas/agent-runtime-state.schema.js";
5
5
  export { ClientMessageSchema, ServerMessageSchema, SendMessageSchema, InjectMessageSchema, PermissionResponseWsSchema, CommandMessageSchema, AgentTextWsMessageSchema, AgentActivityWsMessageSchema, AgentResultWsMessageSchema, AgentStatusWsMessageSchema, AgentCreatedWsMessageSchema, AgentUpdatedWsMessageSchema, AgentDeletedWsMessageSchema, AgentUsageWsMessageSchema, PermissionRequestWsMessageSchema, PermissionCancelledWsMessageSchema, ErrorWsMessageSchema, AgentMessageWsMessageSchema, AgentToolProgressWsMessageSchema, TaskAddedWsMessageSchema, TaskUpdatedWsMessageSchema, TaskAssignedWsMessageSchema, TaskStateChangedWsMessageSchema, TaskDeletedWsMessageSchema, CircleCreatedWsMessageSchema, CircleUpdatedWsMessageSchema, CircleDeletedWsMessageSchema, RelationshipCreatedWsMessageSchema, RelationshipDeletedWsMessageSchema, AgentBuilderDraftUpdatedWsMessageSchema, isAgentServerMessage, CLIENT_MESSAGE_TYPE, SERVER_MESSAGE_TYPE, } from "./schemas/websocket.schema.js";
6
6
  export { PERMISSION_DECISION, PermissionRequestSchema, PermissionResponseSchema, } from "./schemas/permission.schema.js";
7
7
  export { AgentMessageSchema, MessageAnnotationSchema, AGENT_MESSAGE_ROLE, AGENT_MESSAGE_TYPE, } from "./schemas/agent-message.schema.js";
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { MessageSourceSchema } from "./message-source.schema.js";
3
+ /** Maximum number of recent user inputs retained per agent for compose-box recall. */
4
+ export const MAX_INPUT_HISTORY = 30;
3
5
  export const AGENT_STATUS = {
4
6
  IDLE: "idle",
5
7
  ACTIVATING: "activating",
@@ -81,5 +83,7 @@ export const AgentRuntimeStateSchema = z.object({
81
83
  lastError: z.string().optional(),
82
84
  pendingPermissions: z.array(PendingPermissionInfoSchema).optional(),
83
85
  pendingInstructionReminder: PendingInstructionReminderSchema.optional(),
86
+ /** Recent user message inputs (oldest first) for compose-box Up/Down recall; survives new sessions. */
87
+ inputHistory: z.array(z.string()).optional(),
84
88
  });
85
89
  //# sourceMappingURL=agent-runtime-state.schema.js.map
@@ -21,16 +21,16 @@ export function getSearchWorkspaceToolConfig(agentId, documentSearchService, tas
21
21
  .number()
22
22
  .optional()
23
23
  .describe(`Maximum number of hits to return per page. Default ${DEFAULT_SEARCH_LIMIT}.`),
24
- skip: z.number().optional().describe("Number of hits to skip for pagination."),
24
+ offset: z.number().optional().describe("Number of hits to skip before the page starts (0-based offset)."),
25
25
  };
26
- const handler = async ({ query, sources, limit, skip }) => {
26
+ const handler = async ({ query, sources, limit, offset }) => {
27
27
  try {
28
28
  const filter = buildAccessFilter(agentId, taskManager, circleManager, sources);
29
29
  const hits = documentSearchService.search(query, { filter, limit: MAX_SEARCH_LIMIT });
30
30
  if (hits.length === 0) {
31
31
  return textToolResult([`No matches found for query "${query}".`]);
32
32
  }
33
- const pagination = applyPagination(hits, limit || DEFAULT_SEARCH_LIMIT, skip);
33
+ const pagination = applyPagination(hits, limit || DEFAULT_SEARCH_LIMIT, offset);
34
34
  const header = formatPaginationHeader(`Workspace search for query "${query}"`, pagination);
35
35
  return textToolResult(header.concat("", renderHits(pagination.items)));
36
36
  }
@@ -5,6 +5,8 @@ import { processTextContent, textToolResult } from "../tool-utils.js";
5
5
  import { last } from "es-toolkit";
6
6
  export const ARTIFACT_TYPE_VALUES = Object.values(ARTIFACT_TYPE);
7
7
  export const ARTIFACT_CONTENT_TYPE_VALUES = Object.values(ARTIFACT_CONTENT_TYPE);
8
+ /** Default cap on lines returned by read artifact tools to avoid flooding the context with large text artifacts. */
9
+ export const DEFAULT_READ_ARTIFACT_LINE_LIMIT = 100;
8
10
  export const EDIT_ARTIFACT_MODE = {
9
11
  INSERT: "insert",
10
12
  REPLACE: "replace",
@@ -16,9 +16,9 @@ export function getListArtifactsToolConfig(agentId, artifactManager, registry, c
16
16
  .optional()
17
17
  .describe("Filter by tags. Only artifacts that have every specified tag are returned."),
18
18
  limit: z.number().optional().describe("Number of artifacts to return per page."),
19
- skip: z.number().optional().describe("Number of artifacts to skip for pagination."),
19
+ offset: z.number().optional().describe("Number of artifacts to skip before the page starts (0-based offset)."),
20
20
  };
21
- const handler = async ({ agent_id, type, tags, limit, skip }) => {
21
+ const handler = async ({ agent_id, type, tags, limit, offset }) => {
22
22
  const targetId = agent_id ?? agentId;
23
23
  if (agent_id) {
24
24
  try {
@@ -47,7 +47,7 @@ export function getListArtifactsToolConfig(agentId, artifactManager, registry, c
47
47
  const suffix = filterParts.length ? ` matching ${filterParts.join(" and ")}` : "";
48
48
  return textToolResult([`No artifacts found for agent ${targetId}${suffix}.`]);
49
49
  }
50
- const pagination = applyPagination(artifacts, limit || DEFAULT_ARTIFACTS_LIMIT, skip);
50
+ const pagination = applyPagination(artifacts, limit || DEFAULT_ARTIFACTS_LIMIT, offset);
51
51
  const lines = pagination.items.map((artifact) => `- ${artifact.filename} (owner: ${artifact.entityId}, type: ${artifact.type}, modified: ${formatLocalDateTime(new Date(artifact.updatedTimestamp), userTimezone)})`);
52
52
  const header = formatPaginationHeader(`Artifacts for agent ${targetId}`, pagination);
53
53
  return textToolResult(header.concat("", lines));
@@ -16,9 +16,9 @@ export function getListCircleArtifactsToolConfig(agentId, artifactManager, senso
16
16
  .optional()
17
17
  .describe("Filter by tags. Only artifacts that have every specified tag are returned."),
18
18
  limit: z.number().optional().describe("Number of artifacts to return per page."),
19
- skip: z.number().optional().describe("Number of artifacts to skip for pagination."),
19
+ offset: z.number().optional().describe("Number of artifacts to skip before the page starts (0-based offset)."),
20
20
  };
21
- const handler = async ({ circle_id, type, tags, limit, skip }) => {
21
+ const handler = async ({ circle_id, type, tags, limit, offset }) => {
22
22
  if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
23
23
  return textToolResult(["You are not a direct member of this circle."], true);
24
24
  }
@@ -38,7 +38,7 @@ export function getListCircleArtifactsToolConfig(agentId, artifactManager, senso
38
38
  const suffix = filterParts.length ? ` matching ${filterParts.join(" and ")}` : "";
39
39
  return textToolResult([`No artifacts found for circle ${circle_id}${suffix}.`]);
40
40
  }
41
- const pagination = applyPagination(artifacts, limit || DEFAULT_CIRCLE_ARTIFACTS_LIMIT, skip);
41
+ const pagination = applyPagination(artifacts, limit || DEFAULT_CIRCLE_ARTIFACTS_LIMIT, offset);
42
42
  const lines = pagination.items.map((artifact) => `- ${artifact.filename} (type: ${artifact.type}, modified: ${formatLocalDateTime(new Date(artifact.updatedTimestamp), userTimezone)})`);
43
43
  const header = formatPaginationHeader(`Artifacts for circle ${circle_id}`, pagination);
44
44
  return textToolResult(header.concat("", lines));
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { getErrorToolResult, textToolResult } from "../tool-utils.js";
3
- import { buildReadArtifactResult } from "./artifacts-mcp-server-utils.js";
3
+ import { buildReadArtifactResult, DEFAULT_READ_ARTIFACT_LINE_LIMIT } from "./artifacts-mcp-server-utils.js";
4
4
  export const READ_ARTIFACT_TOOL_NAME = "read_artifact";
5
5
  export function getReadArtifactToolConfig(agentId, artifactManager, registry, circleManager, sensorManager) {
6
6
  const inputSchema = {
@@ -16,7 +16,7 @@ export function getReadArtifactToolConfig(agentId, artifactManager, registry, ci
16
16
  .number()
17
17
  .min(1)
18
18
  .optional()
19
- .describe("Optional. Maximum number of lines to return starting from startLine."),
19
+ .describe(`Optional. Maximum number of lines to return starting from startLine (default: ${DEFAULT_READ_ARTIFACT_LINE_LIMIT}).`),
20
20
  };
21
21
  const handler = async ({ agent_id, filename, showLineNumber, startLine, limit }) => {
22
22
  try {
@@ -33,7 +33,11 @@ export function getReadArtifactToolConfig(agentId, artifactManager, registry, ci
33
33
  artifactManager.readArtifact(agent_id, filename, { useAdapter: true }),
34
34
  sensorManager.getUserTimezone(),
35
35
  ]);
36
- return buildReadArtifactResult(content, metadata, userTimezone, { showLineNumber, startLine, limit });
36
+ return buildReadArtifactResult(content, metadata, userTimezone, {
37
+ showLineNumber,
38
+ startLine,
39
+ limit: limit ?? DEFAULT_READ_ARTIFACT_LINE_LIMIT,
40
+ });
37
41
  }
38
42
  catch (error) {
39
43
  return getErrorToolResult(error, "Failed to read artifact.");
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { getErrorToolResult, textToolResult } from "../tool-utils.js";
3
- import { buildReadArtifactResult } from "./artifacts-mcp-server-utils.js";
3
+ import { buildReadArtifactResult, DEFAULT_READ_ARTIFACT_LINE_LIMIT } from "./artifacts-mcp-server-utils.js";
4
4
  export const READ_CIRCLE_ARTIFACT_TOOL_NAME = "read_circle_artifact";
5
5
  export function getReadCircleArtifactToolConfig(agentId, artifactManager, sensorManager) {
6
6
  const inputSchema = {
@@ -16,7 +16,7 @@ export function getReadCircleArtifactToolConfig(agentId, artifactManager, sensor
16
16
  .number()
17
17
  .min(1)
18
18
  .optional()
19
- .describe("Optional. Maximum number of lines to return starting from startLine."),
19
+ .describe(`Optional. Maximum number of lines to return starting from startLine (default: ${DEFAULT_READ_ARTIFACT_LINE_LIMIT}).`),
20
20
  };
21
21
  const handler = async ({ circle_id, filename, showLineNumber, startLine, limit, }) => {
22
22
  if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
@@ -27,7 +27,11 @@ export function getReadCircleArtifactToolConfig(agentId, artifactManager, sensor
27
27
  artifactManager.readCircleArtifact(circle_id, filename, { useAdapter: true }),
28
28
  sensorManager.getUserTimezone(),
29
29
  ]);
30
- return buildReadArtifactResult(content, metadata, userTimezone, { showLineNumber, startLine, limit });
30
+ return buildReadArtifactResult(content, metadata, userTimezone, {
31
+ showLineNumber,
32
+ startLine,
33
+ limit: limit ?? DEFAULT_READ_ARTIFACT_LINE_LIMIT,
34
+ });
31
35
  }
32
36
  catch (error) {
33
37
  return getErrorToolResult(error, "Failed to read circle artifact.");
@@ -1,13 +1,41 @@
1
1
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { MCP_CONFIG_TYPE } from "../_shared/index.js";
3
- /** Wrap a server's registered tools into an in-process Claude SDK MCP server. */
3
+ import { buildStrictToolSchema } from "./tool-utils.js";
4
+ const hasRegisteredTools = (instance) => {
5
+ if (!("_registeredTools" in instance)) {
6
+ return false;
7
+ }
8
+ const registeredTools = instance._registeredTools;
9
+ return typeof registeredTools === "object" && registeredTools !== null;
10
+ };
11
+ /**
12
+ * Swap each registered tool's input schema for a strict variant so unknown parameters are rejected instead of
13
+ * silently dropped. The raw shape is passed to tool() first so the SDK caches per-field descriptions; the strict
14
+ * object reuses the same field instances, so those descriptions survive the swap (a strict object passed directly
15
+ * to tool() would lose them). Guarded so an SDK internals change degrades to the default lenient
16
+ * behavior rather than throwing.
17
+ */
18
+ function applyStrictToolSchemas(instance, tools) {
19
+ if (!hasRegisteredTools(instance)) {
20
+ return;
21
+ }
22
+ for (const registeredTool of tools) {
23
+ const entry = instance._registeredTools[registeredTool.name];
24
+ if (entry) {
25
+ entry.inputSchema = buildStrictToolSchema(registeredTool.inputSchema);
26
+ }
27
+ }
28
+ }
29
+ /** Wrap a server's registered tools into an in-process Claude SDK MCP server with strict parameter validation. */
4
30
  export function toClaudeServer(name, tools) {
5
- return createSdkMcpServer({
31
+ const server = createSdkMcpServer({
6
32
  name,
7
33
  tools: tools.map((registeredTool) => tool(registeredTool.name, registeredTool.description, registeredTool.inputSchema, registeredTool.handler, {
8
34
  annotations: registeredTool.annotations,
9
35
  })),
10
36
  });
37
+ applyStrictToolSchemas(server.instance, tools);
38
+ return server;
11
39
  }
12
40
  /** Map a Crow MCP transport to the Claude SDK's stdio/http/sse server config. */
13
41
  export function toClaudeTransport(transport) {
@@ -1,16 +1,24 @@
1
1
  import { convertMcpCallToolResult } from "@github/copilot-sdk";
2
2
  import { z } from "zod";
3
3
  import { MCP_CONFIG_TYPE } from "../_shared/index.js";
4
+ import { buildStrictToolSchema, getValidationErrorToolResult } from "./tool-utils.js";
4
5
  /** Flatten an internal server's tools into Copilot in-process tools, prefixed to keep `mcp__server__tool` names. */
5
6
  export function toCopilotTools(server) {
6
7
  return server.tools.map((registeredTool) => toCopilotTool(registeredTool, server));
7
8
  }
8
9
  function toCopilotTool(registeredTool, server) {
10
+ const inputSchema = buildStrictToolSchema(registeredTool.inputSchema);
9
11
  return {
10
12
  name: `${server.mcpToolPrefix}${registeredTool.name}`,
11
13
  description: registeredTool.description,
12
- parameters: z.toJSONSchema(z.object(registeredTool.inputSchema)),
13
- handler: async (args, invocation) => toCopilotToolResult(await registeredTool.handler(args, invocation)),
14
+ parameters: z.toJSONSchema(inputSchema),
15
+ handler: async (args, invocation) => {
16
+ const parsed = inputSchema.safeParse(args);
17
+ if (!parsed.success) {
18
+ return toCopilotToolResult(getValidationErrorToolResult(registeredTool.name, parsed.error));
19
+ }
20
+ return toCopilotToolResult(await registeredTool.handler(parsed.data, invocation));
21
+ },
14
22
  skipPermission: server.isAutoApproved,
15
23
  };
16
24
  }
@@ -3,6 +3,8 @@ import { getErrorToolResult, processTextContent, textToolResult } from "../tool-
3
3
  import { formatLocalDateTime } from "../../utils/date-utils.js";
4
4
  import { resolveVisibleFeedIds } from "./feed-tool-utils.js";
5
5
  export const GET_FEED_ITEM_CONTENT_TOOL_NAME = "get_feed_item_content";
6
+ /** Default cap on lines returned for a feed item to avoid flooding the context with long article bodies. */
7
+ export const DEFAULT_FEED_ITEM_CONTENT_LINE_LIMIT = 100;
6
8
  export function getFeedItemContentToolConfig(agentId, registry, feedManager, sensorManager, systemSettingsManager) {
7
9
  const inputSchema = {
8
10
  feedId: z.string().describe("The news/RSS feed ID that owns the item."),
@@ -17,7 +19,7 @@ export function getFeedItemContentToolConfig(agentId, registry, feedManager, sen
17
19
  .number()
18
20
  .min(1)
19
21
  .optional()
20
- .describe("Optional. Maximum number of lines to return starting from startLine."),
22
+ .describe(`Optional. Maximum number of lines to return starting from startLine (default: ${DEFAULT_FEED_ITEM_CONTENT_LINE_LIMIT}).`),
21
23
  };
22
24
  const handler = async ({ feedId, id, showLineNumber, startLine, limit }) => {
23
25
  try {
@@ -39,7 +41,11 @@ export function getFeedItemContentToolConfig(agentId, registry, feedManager, sen
39
41
  header.push(`[Enclosure URL: ${feedItem.enclosureUrl}]`);
40
42
  }
41
43
  const contentText = feedItem.content || feedItem.description;
42
- const processedContent = processTextContent(contentText, { showLineNumber, startLine, limit });
44
+ const processedContent = processTextContent(contentText, {
45
+ showLineNumber,
46
+ startLine,
47
+ limit: limit ?? DEFAULT_FEED_ITEM_CONTENT_LINE_LIMIT,
48
+ });
43
49
  return textToolResult(header.concat(processedContent.headerParts).concat(["", processedContent.text]));
44
50
  }
45
51
  catch (error) {
@@ -8,9 +8,9 @@ export function getFeedItemsToolConfig(agentId, registry, feedManager, sensorMan
8
8
  const inputSchema = {
9
9
  feedId: z.string().describe("The news/RSS feed ID from which to get items."),
10
10
  limit: z.number().optional().describe(`Number of items to return per page.`),
11
- skip: z.number().optional().describe("Number of items to skip for pagination."),
11
+ offset: z.number().optional().describe("Number of items to skip before the page starts (0-based offset)."),
12
12
  };
13
- const handler = async ({ feedId, limit, skip }) => {
13
+ const handler = async ({ feedId, limit, offset }) => {
14
14
  try {
15
15
  const visibleFeedIds = await resolveVisibleFeedIds(agentId, registry, systemSettingsManager);
16
16
  if (!visibleFeedIds.has(feedId)) {
@@ -22,7 +22,7 @@ export function getFeedItemsToolConfig(agentId, registry, feedManager, sensorMan
22
22
  }
23
23
  const timezone = await sensorManager.getUserTimezone();
24
24
  const allItems = await feedManager.getItemsFromFeed(feedId);
25
- const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, skip);
25
+ const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, offset);
26
26
  const formattedItems = pagination.items.map((item) => formatFeedItemSummary(item, feed.title, timezone));
27
27
  const header = formatPaginationHeader(`Items from feed [${feed.title}]`, pagination, "Items show summary only — use get_feed_item_content with an item ID for full content");
28
28
  return textToolResult(header.concat("", formattedItems.flatMap((item) => [item, "---"])));
@@ -13,9 +13,9 @@ export function getRecentFeedItemsToolConfig(agentId, registry, feedManager, sen
13
13
  .optional()
14
14
  .describe(`Number of minutes to look back from now for recent items (default: ${DEFAULT_RECENCY_IN_MINUTES}).`),
15
15
  limit: z.number().optional().describe(`Number of items to return per page.`),
16
- skip: z.number().optional().describe("Number of items to skip for pagination."),
16
+ offset: z.number().optional().describe("Number of items to skip before the page starts (0-based offset)."),
17
17
  };
18
- const handler = async ({ recencyInMinutes, limit, skip }) => {
18
+ const handler = async ({ recencyInMinutes, limit, offset }) => {
19
19
  try {
20
20
  const timezone = await sensorManager.getUserTimezone();
21
21
  const visibleFeedIdSet = await resolveVisibleFeedIds(agentId, registry, systemSettingsManager);
@@ -27,7 +27,7 @@ export function getRecentFeedItemsToolConfig(agentId, registry, feedManager, sen
27
27
  if (!allItems.length) {
28
28
  return textToolResult([`No items found in the last ${effectiveRecency} minutes. Try increase the minutes.`]);
29
29
  }
30
- const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, skip);
30
+ const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, offset);
31
31
  const formattedItems = pagination.items.map((item) => formatFeedItemSummary(item, feedsMap.get(item.feedId), timezone));
32
32
  const header = formatPaginationHeader(`Recent items from all feeds (last ${effectiveRecency} minutes)`, pagination, "Items show summary only — use get_feed_item_content with an item ID for full content");
33
33
  return textToolResult(header.concat("", formattedItems.flatMap((item) => [item, "---"])));
@@ -7,14 +7,14 @@ export const LIST_FEEDS_TOOL_NAME = "list_feeds";
7
7
  export function getListFeedsToolConfig(agentId, registry, feedManager, sensorManager, systemSettingsManager) {
8
8
  const inputSchema = {
9
9
  limit: z.number().optional().describe(`Number of feeds to return per page.`),
10
- skip: z.number().optional().describe("Number of feeds to skip for pagination."),
10
+ offset: z.number().optional().describe("Number of feeds to skip before the page starts (0-based offset)."),
11
11
  };
12
- const handler = async ({ limit, skip }) => {
12
+ const handler = async ({ limit, offset }) => {
13
13
  try {
14
14
  const timezone = await sensorManager.getUserTimezone();
15
15
  const visibleFeedIdSet = await resolveVisibleFeedIds(agentId, registry, systemSettingsManager);
16
16
  const feeds = await feedManager.getFeeds(Array.from(visibleFeedIdSet));
17
- const pagination = applyPagination(feeds, limit || DEFAULT_FEEDS_LIMIT, skip);
17
+ const pagination = applyPagination(feeds, limit || DEFAULT_FEEDS_LIMIT, offset);
18
18
  const formattedFeeds = pagination.items.map((feed) => formatFeed(feed, timezone));
19
19
  const header = formatPaginationHeader("Available feeds", pagination);
20
20
  return textToolResult(header.concat("", formattedFeeds));
@@ -9,9 +9,9 @@ export function getSearchFeedItemsToolConfig(agentId, registry, feedManager, sen
9
9
  query: z.string().describe("Natural-language phrase or topic to match against feed items."),
10
10
  feedId: z.string().optional().describe("Optional feed ID to filter search results to a specific feed."),
11
11
  limit: z.number().optional().describe(`Number of items to return per page.`),
12
- skip: z.number().optional().describe("Number of items to skip for pagination."),
12
+ offset: z.number().optional().describe("Number of items to skip before the page starts (0-based offset)."),
13
13
  };
14
- const handler = async ({ query, feedId, limit, skip }) => {
14
+ const handler = async ({ query, feedId, limit, offset }) => {
15
15
  try {
16
16
  const timezone = await sensorManager.getUserTimezone();
17
17
  const visibleFeedIdSet = await resolveVisibleFeedIds(agentId, registry, systemSettingsManager);
@@ -25,7 +25,7 @@ export function getSearchFeedItemsToolConfig(agentId, registry, feedManager, sen
25
25
  if (!allItems.length) {
26
26
  return textToolResult([`No items found matching "${query}".`]);
27
27
  }
28
- const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, skip);
28
+ const pagination = applyPagination(allItems, limit || DEFAULT_FEED_ITEMS_LIMIT, offset);
29
29
  const formattedItems = pagination.items.map((item) => formatFeedItemSummary(item, feedsMap.get(item.feedId), timezone));
30
30
  const header = formatPaginationHeader(`Items matching "${query}"`, pagination, "Items show summary only — use get_feed_item_content with an item ID for full content");
31
31
  return textToolResult(header.concat("", formattedItems.flatMap((item) => [item, "---"])));
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { getErrorToolResult, processTextContent, textToolResult } from "../tool-utils.js";
3
3
  export const GET_GMAIL_MESSAGE_CONTENT_TOOL_NAME = "get_gmail_message_content";
4
+ /** Default cap on lines returned for a Gmail message body to avoid flooding the context with long emails. */
5
+ export const DEFAULT_GMAIL_MESSAGE_CONTENT_LINE_LIMIT = 100;
4
6
  export function getGetGmailMessageContentToolConfig(googleClient) {
5
7
  const inputSchema = {
6
8
  messageId: z.string().describe("Gmail message ID (from list_gmail_messages or get_gmail_thread results)."),
@@ -14,13 +16,17 @@ export function getGetGmailMessageContentToolConfig(googleClient) {
14
16
  .number()
15
17
  .min(1)
16
18
  .optional()
17
- .describe("Optional. Maximum number of lines to return starting from startLine."),
19
+ .describe(`Optional. Maximum number of lines to return starting from startLine (default: ${DEFAULT_GMAIL_MESSAGE_CONTENT_LINE_LIMIT}).`),
18
20
  };
19
21
  const handler = async ({ messageId, showLineNumber, startLine, limit }) => {
20
22
  try {
21
23
  const message = await googleClient.getGmailMessage(messageId);
22
24
  const rawContent = message.content ?? message.snippet ?? "(no body)";
23
- const processed = processTextContent(rawContent, { showLineNumber, startLine, limit });
25
+ const processed = processTextContent(rawContent, {
26
+ showLineNumber,
27
+ startLine,
28
+ limit: limit ?? DEFAULT_GMAIL_MESSAGE_CONTENT_LINE_LIMIT,
29
+ });
24
30
  return textToolResult([...processed.headerParts, "", processed.text]);
25
31
  }
26
32
  catch (error) {
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { PLACES_SOURCE } from "../../services/places/places-manager.types.js";
3
3
  import { getErrorToolResult, textToolResult } from "../tool-utils.js";
4
4
  import { formatPlaceSummary } from "./places-format-utils.js";
5
+ import { resolveRoutingOrigin, routingDescriptionLine, routingOriginInputSchema } from "./places-routing-input.js";
5
6
  const DEFAULT_GEOCODE_LIMIT = 10;
6
7
  const MAX_GEOCODE_LIMIT = 25;
7
8
  export const GEOCODE_PLACE_TOOL_NAME = "geocode_place";
@@ -25,7 +26,9 @@ function selectForSource(map, source, fallback) {
25
26
  }
26
27
  export function getGeocodePlaceToolConfig(placesManager) {
27
28
  const defaultSource = placesManager.getDefaultSource();
28
- const description = selectForSource(DESCRIPTION_BY_SOURCE, defaultSource, NEUTRAL_DESCRIPTION);
29
+ const baseDescription = selectForSource(DESCRIPTION_BY_SOURCE, defaultSource, NEUTRAL_DESCRIPTION);
30
+ const routingLine = routingDescriptionLine(defaultSource);
31
+ const description = routingLine ? `${baseDescription} ${routingLine}` : baseDescription;
29
32
  const resultGuidance = selectForSource(RESULT_GUIDANCE_BY_SOURCE, defaultSource, NEUTRAL_RESULT_GUIDANCE);
30
33
  const inputSchema = {
31
34
  query: z.string().min(1).describe("A place name, a full address, or a landmark (e.g. 'Eiffel Tower')."),
@@ -42,6 +45,7 @@ export function getGeocodePlaceToolConfig(placesManager) {
42
45
  .optional()
43
46
  .describe("Optional latitude for ranking bias - prefers candidates near this point."),
44
47
  nearLongitude: z.number().min(-180).max(180).optional().describe("Optional longitude paired with nearLatitude."),
48
+ ...routingOriginInputSchema,
45
49
  limit: z
46
50
  .number()
47
51
  .int()
@@ -54,6 +58,10 @@ export function getGeocodePlaceToolConfig(placesManager) {
54
58
  if ((args.nearLatitude === undefined) !== (args.nearLongitude === undefined)) {
55
59
  return textToolResult(["nearLatitude and nearLongitude must both be provided or both omitted."], true);
56
60
  }
61
+ const routing = resolveRoutingOrigin(args);
62
+ if ("error" in routing) {
63
+ return textToolResult([routing.error], true);
64
+ }
57
65
  const queryParts = [args.query, args.city, args.country]
58
66
  .map((part) => part?.trim())
59
67
  .filter((part) => part !== undefined && part.length > 0);
@@ -68,6 +76,7 @@ export function getGeocodePlaceToolConfig(placesManager) {
68
76
  const places = await placesManager.geocode({
69
77
  text: queryText,
70
78
  near,
79
+ routingOrigin: routing.routingOrigin,
71
80
  limit: args.limit ?? DEFAULT_GEOCODE_LIMIT,
72
81
  });
73
82
  if (places.length === 0) {
@@ -25,8 +25,32 @@ export function formatPlaceSummary(place) {
25
25
  if (place.mapsUrl !== undefined && place.mapsUrl.length > 0) {
26
26
  lines.push(` - Map: ${place.mapsUrl}`);
27
27
  }
28
+ if (place.routing !== undefined) {
29
+ const { durationSeconds, distanceMeters, travelMode, directionsUrl } = place.routing;
30
+ lines.push(` - Travel: ${humanizeDuration(durationSeconds)} · ${humanizeDistance(distanceMeters)} (${travelMode})`);
31
+ if (directionsUrl !== undefined && directionsUrl.length > 0) {
32
+ lines.push(` - Directions: ${directionsUrl}`);
33
+ }
34
+ }
28
35
  return lines.join("\n");
29
36
  }
37
+ /** "8 min" under an hour, "1 h 12 min" (or "2 h" on the hour) above. */
38
+ function humanizeDuration(totalSeconds) {
39
+ const totalMinutes = Math.round(totalSeconds / 60);
40
+ if (totalMinutes < 60) {
41
+ return `${totalMinutes} min`;
42
+ }
43
+ const hours = Math.floor(totalMinutes / 60);
44
+ const minutes = totalMinutes % 60;
45
+ return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`;
46
+ }
47
+ /** "850 m" under a kilometre, "3.2 km" (one decimal) above. */
48
+ function humanizeDistance(meters) {
49
+ if (meters < 1000) {
50
+ return `${Math.round(meters)} m`;
51
+ }
52
+ return `${(meters / 1000).toFixed(1)} km`;
53
+ }
30
54
  /** Full-detail block returned by `places_get_details`. Layers attributes on top of the summary. */
31
55
  export function formatPlaceDetails(details) {
32
56
  const lines = [formatPlaceSummary(details)];
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { PLACES_SOURCE, TRAVEL_MODE, } from "../../services/places/places-manager.types.js";
3
+ const TRAVEL_MODE_VALUES = Object.values(TRAVEL_MODE);
4
+ /** Optional routing-origin inputs shared by the place search and geocode tools. */
5
+ export const routingOriginInputSchema = {
6
+ originLatitude: z
7
+ .number()
8
+ .min(-90)
9
+ .max(90)
10
+ .optional()
11
+ .describe("Latitude of the travel origin; pair with originLongitude to attach travel time and distance."),
12
+ originLongitude: z
13
+ .number()
14
+ .min(-180)
15
+ .max(180)
16
+ .optional()
17
+ .describe("Longitude of the travel origin; must be paired with originLatitude."),
18
+ travelMode: z
19
+ .enum(TRAVEL_MODE_VALUES)
20
+ .optional()
21
+ .describe("Travel mode for the origin (default DRIVE). Ignored unless an origin is provided."),
22
+ };
23
+ /**
24
+ * Validate the both-or-neither origin pair and build a `RoutingOrigin`. A
25
+ * `travelMode` without an origin is a no-op rather than an error.
26
+ */
27
+ export function resolveRoutingOrigin(args) {
28
+ const { originLatitude, originLongitude, travelMode } = args;
29
+ if ((originLatitude === undefined) !== (originLongitude === undefined)) {
30
+ return { error: "originLatitude and originLongitude must both be provided or both omitted." };
31
+ }
32
+ if (originLatitude === undefined || originLongitude === undefined) {
33
+ return { routingOrigin: undefined };
34
+ }
35
+ const routingOrigin = { point: { latitude: originLatitude, longitude: originLongitude } };
36
+ if (travelMode) {
37
+ routingOrigin.travelMode = travelMode;
38
+ }
39
+ return { routingOrigin };
40
+ }
41
+ const ROUTING_DESCRIPTION_BY_SOURCE = {
42
+ [PLACES_SOURCE.GOOGLE]: "Provide originLatitude/originLongitude to attach travel time and distance to each result.",
43
+ [PLACES_SOURCE.OSM]: "Travel time and distance to results are not available on this source.",
44
+ };
45
+ /** One-line, source-aware note about routing support for a tool description. */
46
+ export function routingDescriptionLine(source) {
47
+ return ROUTING_DESCRIPTION_BY_SOURCE[source];
48
+ }
49
+ //# sourceMappingURL=places-routing-input.js.map
@@ -2,12 +2,17 @@ import { z } from "zod";
2
2
  import { PLACE_CATEGORY } from "../../services/places/places-manager.types.js";
3
3
  import { getErrorToolResult, textToolResult } from "../tool-utils.js";
4
4
  import { formatPlaceSummary } from "./places-format-utils.js";
5
+ import { resolveRoutingOrigin, routingDescriptionLine, routingOriginInputSchema } from "./places-routing-input.js";
5
6
  const DEFAULT_SEARCH_NEARBY_LIMIT = 20;
6
7
  const MAX_SEARCH_NEARBY_LIMIT = 50;
7
8
  const MAX_SEARCH_NEARBY_RADIUS_METERS = 50_000;
8
9
  const CATEGORY_VALUES = Object.values(PLACE_CATEGORY);
9
10
  export const SEARCH_NEARBY_PLACES_TOOL_NAME = "search_nearby_places";
11
+ const BASE_DESCRIPTION = "Find places of a given category within a radius around a point. Use get_place_details with a returned id " +
12
+ "for opening hours, contact info, and other attributes.";
10
13
  export function getSearchNearbyPlacesToolConfig(placesManager) {
14
+ const routingLine = routingDescriptionLine(placesManager.getDefaultSource());
15
+ const description = routingLine ? `${BASE_DESCRIPTION} ${routingLine}` : BASE_DESCRIPTION;
11
16
  const inputSchema = {
12
17
  latitude: z.number().min(-90).max(90).describe("Latitude of the search center."),
13
18
  longitude: z.number().min(-180).max(180).describe("Longitude of the search center."),
@@ -20,6 +25,7 @@ export function getSearchNearbyPlacesToolConfig(placesManager) {
20
25
  category: z
21
26
  .enum(CATEGORY_VALUES)
22
27
  .describe("Curated place category to filter by. Use OTHER only as a last resort - prefer a more specific value."),
28
+ ...routingOriginInputSchema,
23
29
  limit: z
24
30
  .number()
25
31
  .int()
@@ -29,6 +35,10 @@ export function getSearchNearbyPlacesToolConfig(placesManager) {
29
35
  .describe(`Max results to return (default ${DEFAULT_SEARCH_NEARBY_LIMIT}, max ${MAX_SEARCH_NEARBY_LIMIT}).`),
30
36
  };
31
37
  const handler = async (args) => {
38
+ const routing = resolveRoutingOrigin(args);
39
+ if ("error" in routing) {
40
+ return textToolResult([routing.error], true);
41
+ }
32
42
  try {
33
43
  const places = await placesManager.searchPlaces({
34
44
  area: {
@@ -37,6 +47,7 @@ export function getSearchNearbyPlacesToolConfig(placesManager) {
37
47
  radiusMeters: args.radiusMeters,
38
48
  },
39
49
  category: args.category,
50
+ routingOrigin: routing.routingOrigin,
40
51
  limit: args.limit ?? DEFAULT_SEARCH_NEARBY_LIMIT,
41
52
  });
42
53
  const areaLabel = `within ${args.radiusMeters}m of ${args.latitude.toFixed(6)}, ${args.longitude.toFixed(6)}`;
@@ -53,7 +64,7 @@ export function getSearchNearbyPlacesToolConfig(placesManager) {
53
64
  };
54
65
  const config = {
55
66
  name: SEARCH_NEARBY_PLACES_TOOL_NAME,
56
- description: "Find places of a given category within a radius around a point. Use get_place_details with a returned id for opening hours, contact info, and other attributes.",
67
+ description,
57
68
  inputSchema,
58
69
  handler,
59
70
  };
@@ -6,16 +6,16 @@ export const LIST_REMINDERS_TOOL_NAME = "list_reminders";
6
6
  export function getListRemindersToolConfig(agentId, scheduler, sensorManager) {
7
7
  const inputSchema = {
8
8
  limit: z.number().optional().describe("Number of reminders to return per page."),
9
- skip: z.number().optional().describe("Number of reminders to skip for pagination."),
9
+ offset: z.number().optional().describe("Number of reminders to skip before the page starts (0-based offset)."),
10
10
  };
11
- const handler = async ({ limit, skip }) => {
11
+ const handler = async ({ limit, offset }) => {
12
12
  try {
13
13
  const userTimezone = await sensorManager.getUserTimezone();
14
14
  const reminders = scheduler.listAgentReminders(agentId);
15
15
  if (reminders.length === 0) {
16
16
  return textToolResult(["You have no upcoming reminders."]);
17
17
  }
18
- const pagination = applyPagination(reminders, limit || DEFAULT_REMINDERS_LIMIT, skip);
18
+ const pagination = applyPagination(reminders, limit || DEFAULT_REMINDERS_LIMIT, offset);
19
19
  const formatted = pagination.items.map((reminder) => formatReminder(reminder, userTimezone)).join("\n---\n");
20
20
  const header = formatPaginationHeader("Your reminders", pagination);
21
21
  return textToolResult(header.concat("", formatted));