@geminixiang/mikan 1.0.0-beta.46 → 1.0.0-beta.47

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 (66) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/adapters/discord/bot.d.ts +3 -0
  3. package/dist/adapters/discord/bot.d.ts.map +1 -1
  4. package/dist/adapters/discord/bot.js +27 -5
  5. package/dist/adapters/discord/bot.js.map +1 -1
  6. package/dist/adapters/github/bot.d.ts +6 -1
  7. package/dist/adapters/github/bot.d.ts.map +1 -1
  8. package/dist/adapters/github/bot.js +56 -21
  9. package/dist/adapters/github/bot.js.map +1 -1
  10. package/dist/adapters/shared.d.ts +12 -1
  11. package/dist/adapters/shared.d.ts.map +1 -1
  12. package/dist/adapters/shared.js +44 -1
  13. package/dist/adapters/shared.js.map +1 -1
  14. package/dist/adapters/slack/bot.d.ts +3 -0
  15. package/dist/adapters/slack/bot.d.ts.map +1 -1
  16. package/dist/adapters/slack/bot.js +75 -28
  17. package/dist/adapters/slack/bot.js.map +1 -1
  18. package/dist/adapters/telegram/bot.d.ts +3 -0
  19. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  20. package/dist/adapters/telegram/bot.js +23 -7
  21. package/dist/adapters/telegram/bot.js.map +1 -1
  22. package/dist/agent/catalog.d.ts.map +1 -1
  23. package/dist/agent/catalog.js +5 -1
  24. package/dist/agent/catalog.js.map +1 -1
  25. package/dist/agent/runner.d.ts.map +1 -1
  26. package/dist/agent/runner.js +116 -46
  27. package/dist/agent/runner.js.map +1 -1
  28. package/dist/env-manifest.d.ts.map +1 -1
  29. package/dist/env-manifest.js +17 -0
  30. package/dist/env-manifest.js.map +1 -1
  31. package/dist/main.js +74 -12
  32. package/dist/main.js.map +1 -1
  33. package/dist/mcp/catalog.d.ts.map +1 -1
  34. package/dist/mcp/catalog.js +21 -0
  35. package/dist/mcp/catalog.js.map +1 -1
  36. package/dist/mcp/loader.d.ts +3 -2
  37. package/dist/mcp/loader.d.ts.map +1 -1
  38. package/dist/mcp/loader.js +54 -20
  39. package/dist/mcp/loader.js.map +1 -1
  40. package/dist/mcp/open-connector.d.ts +14 -0
  41. package/dist/mcp/open-connector.d.ts.map +1 -0
  42. package/dist/mcp/open-connector.js +203 -0
  43. package/dist/mcp/open-connector.js.map +1 -0
  44. package/dist/mcp/types.d.ts +6 -0
  45. package/dist/mcp/types.d.ts.map +1 -1
  46. package/dist/mcp/types.js.map +1 -1
  47. package/dist/packages/index.d.ts.map +1 -1
  48. package/dist/packages/index.js +20 -49
  49. package/dist/packages/index.js.map +1 -1
  50. package/dist/process-lifecycle.d.ts +19 -0
  51. package/dist/process-lifecycle.d.ts.map +1 -0
  52. package/dist/process-lifecycle.js +60 -0
  53. package/dist/process-lifecycle.js.map +1 -0
  54. package/dist/runtime/conversation-runtime.js +11 -4
  55. package/dist/runtime/conversation-runtime.js.map +1 -1
  56. package/dist/runtime/session-lifecycle.d.ts +3 -1
  57. package/dist/runtime/session-lifecycle.d.ts.map +1 -1
  58. package/dist/runtime/session-lifecycle.js +22 -2
  59. package/dist/runtime/session-lifecycle.js.map +1 -1
  60. package/dist/runtime/types.d.ts +5 -1
  61. package/dist/runtime/types.d.ts.map +1 -1
  62. package/dist/runtime/types.js.map +1 -1
  63. package/dist/types.d.ts +16 -3
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js.map +1 -1
  66. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { join } from "node:path";
3
+ import { readEnv } from "../env-manifest.js";
4
+ import * as log from "../log.js";
5
+ import { atomicWritePrivateFile, ensureDirExists, isRecord, readJsonSchemaFileIfExists, } from "../utils/file-guards.js";
6
+ const OPEN_CONNECTOR_SERVER = "open-connector";
7
+ const TOKEN_STATE_FILE = "open-connector-runtime-token.json";
8
+ const REQUEST_TIMEOUT_MS = 10_000;
9
+ const pendingTokens = new Map();
10
+ const CONNECTION_AWARE_TOOLS = new Set(["get_action_guide", "execute_action"]);
11
+ const RuntimeTokenStateSchema = Type.Object({
12
+ version: Type.Literal(1),
13
+ origin: Type.String(),
14
+ name: Type.String(),
15
+ id: Type.String(),
16
+ token: Type.String(),
17
+ }, { additionalProperties: false });
18
+ function readRuntimeTokenState(path) {
19
+ return readJsonSchemaFileIfExists(path, RuntimeTokenStateSchema, (detail) => `Malformed OpenConnector runtime token state at ${path}: ${detail}`);
20
+ }
21
+ function readUniqueConnectionName(result, service) {
22
+ if (!isRecord(result) || !Array.isArray(result.content))
23
+ return undefined;
24
+ const text = result.content.find((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")?.text;
25
+ if (!text)
26
+ return undefined;
27
+ try {
28
+ const payload = JSON.parse(text);
29
+ if (!isRecord(payload))
30
+ return undefined;
31
+ const connections = Array.isArray(payload.data)
32
+ ? payload.data
33
+ : Array.isArray(payload.connections)
34
+ ? payload.connections
35
+ : undefined;
36
+ if (!connections)
37
+ return undefined;
38
+ const names = new Set(connections.flatMap((connection) => isRecord(connection) &&
39
+ connection.service === service &&
40
+ typeof connection.connectionName === "string"
41
+ ? [connection.connectionName]
42
+ : []));
43
+ return names.size === 1 ? names.values().next().value : undefined;
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ /** Fill an omitted connection only when OpenConnector reports exactly one
50
+ * candidate for the action's service. Multiple-account selection remains an
51
+ * explicit agent decision. */
52
+ export async function prepareOpenConnectorToolArguments(client, serverName, toolName, params, signal) {
53
+ if (serverName !== OPEN_CONNECTOR_SERVER ||
54
+ !CONNECTION_AWARE_TOOLS.has(toolName) ||
55
+ typeof params.connectionName === "string" ||
56
+ typeof params.actionId !== "string") {
57
+ return params;
58
+ }
59
+ const service = params.actionId.split(".", 1)[0];
60
+ if (!service)
61
+ return params;
62
+ const result = await client.callTool({ name: "list_connections", arguments: { service } }, undefined, { timeout: REQUEST_TIMEOUT_MS, ...(signal ? { signal } : {}) });
63
+ const connectionName = readUniqueConnectionName(result, service);
64
+ return connectionName ? { ...params, connectionName } : params;
65
+ }
66
+ function stringArray(value, field) {
67
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
68
+ throw new Error(`OpenConnector returned an invalid ${field}`);
69
+ }
70
+ return value;
71
+ }
72
+ async function readJson(response, operation) {
73
+ if (!response.ok) {
74
+ throw new Error(`OpenConnector ${operation} failed with HTTP ${response.status}`);
75
+ }
76
+ try {
77
+ return await response.json();
78
+ }
79
+ catch (error) {
80
+ throw new Error(`OpenConnector ${operation} returned invalid JSON`, { cause: error });
81
+ }
82
+ }
83
+ function requestSignal(signal) {
84
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
85
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
86
+ }
87
+ async function createRuntimeToken(origin, adminToken, name, signal) {
88
+ const headers = { Authorization: `Bearer ${adminToken}` };
89
+ const policyValue = await readJson(await fetch(new URL("/api/runtime-policy", origin), {
90
+ headers,
91
+ signal: requestSignal(signal),
92
+ }), "runtime policy request");
93
+ if (!isRecord(policyValue) || !isRecord(policyValue.deployment)) {
94
+ throw new Error("OpenConnector returned an invalid runtime policy");
95
+ }
96
+ const deployment = policyValue.deployment;
97
+ const responseValue = await readJson(await fetch(new URL("/api/runtime-tokens", origin), {
98
+ method: "POST",
99
+ headers: { ...headers, "Content-Type": "application/json" },
100
+ body: JSON.stringify({
101
+ name,
102
+ allowedActions: stringArray(deployment.allowedActions, "allowedActions policy"),
103
+ blockedActions: stringArray(deployment.blockedActions, "blockedActions policy"),
104
+ allowedProxies: stringArray(deployment.allowedProxies, "allowedProxies policy"),
105
+ }),
106
+ signal: requestSignal(signal),
107
+ }), "runtime token creation");
108
+ if (!isRecord(responseValue) ||
109
+ typeof responseValue.token !== "string" ||
110
+ !responseValue.token.startsWith("oct_") ||
111
+ !isRecord(responseValue.record) ||
112
+ typeof responseValue.record.id !== "string" ||
113
+ responseValue.record.name !== name) {
114
+ throw new Error("OpenConnector returned an invalid runtime token");
115
+ }
116
+ return {
117
+ version: 1,
118
+ origin,
119
+ name,
120
+ id: responseValue.record.id,
121
+ token: responseValue.token,
122
+ };
123
+ }
124
+ function disabledServer(servers, config) {
125
+ return { ...servers, [OPEN_CONNECTOR_SERVER]: { ...config, disabled: true } };
126
+ }
127
+ async function loadOrCreateRuntimeToken(office, origin, adminToken, name, signal) {
128
+ const statePath = join(office.stateDir, TOKEN_STATE_FILE);
129
+ const state = readRuntimeTokenState(statePath);
130
+ if (state) {
131
+ if (state.origin !== origin || state.name !== name) {
132
+ throw new Error(`OpenConnector runtime token state does not match ${name}`);
133
+ }
134
+ return state;
135
+ }
136
+ const key = `${origin}:${office.key}`;
137
+ const pending = pendingTokens.get(key);
138
+ if (pending)
139
+ return pending;
140
+ const creation = (async () => {
141
+ const current = readRuntimeTokenState(statePath);
142
+ if (current)
143
+ return current;
144
+ const created = await createRuntimeToken(origin, adminToken, name, signal);
145
+ ensureDirExists(office.stateDir);
146
+ atomicWritePrivateFile(statePath, `${JSON.stringify(created, null, 2)}\n`);
147
+ log.logInfo(`[${office.address.conversationId}] Created OpenConnector runtime token ${created.id}`);
148
+ return created;
149
+ })();
150
+ pendingTokens.set(key, creation);
151
+ try {
152
+ return await creation;
153
+ }
154
+ finally {
155
+ pendingTokens.delete(key);
156
+ }
157
+ }
158
+ /**
159
+ * Replace the deployment-wide OpenConnector bearer token with one scoped to
160
+ * this Slack Conversation office. The privileged admin token is read only at
161
+ * this host boundary and is never written into settings, Vault, or a sandbox.
162
+ */
163
+ export async function provisionOfficeOpenConnectorToken(office, platformWorkspaceId, servers, signal) {
164
+ const config = servers?.[OPEN_CONNECTOR_SERVER];
165
+ const adminToken = readEnv("OPENCONNECTOR_ADMIN_TOKEN");
166
+ if (!config || config.disabled || !adminToken || office.address.platform !== "slack") {
167
+ return servers;
168
+ }
169
+ const configuredOrigin = readEnv("OPENCONNECTOR_ORIGIN");
170
+ if (!configuredOrigin || !URL.canParse(configuredOrigin)) {
171
+ log.logWarning(`[${office.address.conversationId}] OpenConnector token provisioning skipped`, "OPENCONNECTOR_ORIGIN is missing or invalid");
172
+ return disabledServer(servers, config);
173
+ }
174
+ if (!platformWorkspaceId) {
175
+ log.logWarning(`[${office.address.conversationId}] OpenConnector token provisioning skipped`, "Slack workspace ID is unavailable");
176
+ return disabledServer(servers, config);
177
+ }
178
+ if (!config.url) {
179
+ log.logWarning(`[${office.address.conversationId}] OpenConnector token provisioning skipped`, "the open-connector server is not configured for HTTP");
180
+ return disabledServer(servers, config);
181
+ }
182
+ try {
183
+ const endpoint = new URL(config.url);
184
+ const origin = new URL(configuredOrigin).origin;
185
+ if (endpoint.origin !== origin) {
186
+ throw new Error("the MCP endpoint does not match the deployment-owned OpenConnector origin");
187
+ }
188
+ const name = `mikan:slack:${platformWorkspaceId}:${office.address.conversationId}`;
189
+ const state = await loadOrCreateRuntimeToken(office, origin, adminToken, name, signal);
190
+ return {
191
+ ...servers,
192
+ [OPEN_CONNECTOR_SERVER]: {
193
+ ...config,
194
+ headers: { ...config.headers, Authorization: `Bearer ${state.token}` },
195
+ },
196
+ };
197
+ }
198
+ catch (error) {
199
+ log.logWarning(`[${office.address.conversationId}] OpenConnector token provisioning failed`, error instanceof Error ? error.message : String(error));
200
+ return disabledServer(servers, config);
201
+ }
202
+ }
203
+ //# sourceMappingURL=open-connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-connector.js","sourceRoot":"","sources":["../../src/mcp/open-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Type, type Static } from \"@sinclair/typebox\";\nimport { join } from \"node:path\";\nimport { readEnv } from \"../env-manifest.js\";\nimport * as log from \"../log.js\";\nimport type { Office } from \"../office/index.js\";\nimport {\n atomicWritePrivateFile,\n ensureDirExists,\n isRecord,\n readJsonSchemaFileIfExists,\n} from \"../utils/file-guards.js\";\nimport type { McpServerConfig } from \"./types.js\";\n\nconst OPEN_CONNECTOR_SERVER = \"open-connector\";\nconst TOKEN_STATE_FILE = \"open-connector-runtime-token.json\";\nconst REQUEST_TIMEOUT_MS = 10_000;\nconst pendingTokens = new Map<string, Promise<RuntimeTokenState>>();\nconst CONNECTION_AWARE_TOOLS = new Set([\"get_action_guide\", \"execute_action\"]);\n\nconst RuntimeTokenStateSchema = Type.Object(\n {\n version: Type.Literal(1),\n origin: Type.String(),\n name: Type.String(),\n id: Type.String(),\n token: Type.String(),\n },\n { additionalProperties: false },\n);\n\ntype RuntimeTokenState = Static<typeof RuntimeTokenStateSchema>;\n\nfunction readRuntimeTokenState(path: string) {\n return readJsonSchemaFileIfExists(\n path,\n RuntimeTokenStateSchema,\n (detail) => `Malformed OpenConnector runtime token state at ${path}: ${detail}`,\n );\n}\n\nfunction readUniqueConnectionName(result: unknown, service: string) {\n if (!isRecord(result) || !Array.isArray(result.content)) return undefined;\n const text = result.content.find(\n (part): part is { type: \"text\"; text: string } =>\n isRecord(part) && part.type === \"text\" && typeof part.text === \"string\",\n )?.text;\n if (!text) return undefined;\n try {\n const payload: unknown = JSON.parse(text);\n if (!isRecord(payload)) return undefined;\n const connections = Array.isArray(payload.data)\n ? payload.data\n : Array.isArray(payload.connections)\n ? payload.connections\n : undefined;\n if (!connections) return undefined;\n const names = new Set(\n connections.flatMap((connection) =>\n isRecord(connection) &&\n connection.service === service &&\n typeof connection.connectionName === \"string\"\n ? [connection.connectionName]\n : [],\n ),\n );\n return names.size === 1 ? names.values().next().value : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Fill an omitted connection only when OpenConnector reports exactly one\n * candidate for the action's service. Multiple-account selection remains an\n * explicit agent decision. */\nexport async function prepareOpenConnectorToolArguments(\n client: Pick<Client, \"callTool\">,\n serverName: string,\n toolName: string,\n params: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<Record<string, unknown>> {\n if (\n serverName !== OPEN_CONNECTOR_SERVER ||\n !CONNECTION_AWARE_TOOLS.has(toolName) ||\n typeof params.connectionName === \"string\" ||\n typeof params.actionId !== \"string\"\n ) {\n return params;\n }\n const service = params.actionId.split(\".\", 1)[0];\n if (!service) return params;\n const result = await client.callTool(\n { name: \"list_connections\", arguments: { service } },\n undefined,\n { timeout: REQUEST_TIMEOUT_MS, ...(signal ? { signal } : {}) },\n );\n const connectionName = readUniqueConnectionName(result, service);\n return connectionName ? { ...params, connectionName } : params;\n}\n\nfunction stringArray(value: unknown, field: string): string[] {\n if (!Array.isArray(value) || !value.every((item) => typeof item === \"string\")) {\n throw new Error(`OpenConnector returned an invalid ${field}`);\n }\n return value;\n}\n\nasync function readJson(response: Response, operation: string): Promise<unknown> {\n if (!response.ok) {\n throw new Error(`OpenConnector ${operation} failed with HTTP ${response.status}`);\n }\n try {\n return await response.json();\n } catch (error) {\n throw new Error(`OpenConnector ${operation} returned invalid JSON`, { cause: error });\n }\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);\n return signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nasync function createRuntimeToken(\n origin: string,\n adminToken: string,\n name: string,\n signal?: AbortSignal,\n): Promise<RuntimeTokenState> {\n const headers = { Authorization: `Bearer ${adminToken}` };\n const policyValue = await readJson(\n await fetch(new URL(\"/api/runtime-policy\", origin), {\n headers,\n signal: requestSignal(signal),\n }),\n \"runtime policy request\",\n );\n if (!isRecord(policyValue) || !isRecord(policyValue.deployment)) {\n throw new Error(\"OpenConnector returned an invalid runtime policy\");\n }\n const deployment = policyValue.deployment;\n const responseValue = await readJson(\n await fetch(new URL(\"/api/runtime-tokens\", origin), {\n method: \"POST\",\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name,\n allowedActions: stringArray(deployment.allowedActions, \"allowedActions policy\"),\n blockedActions: stringArray(deployment.blockedActions, \"blockedActions policy\"),\n allowedProxies: stringArray(deployment.allowedProxies, \"allowedProxies policy\"),\n }),\n signal: requestSignal(signal),\n }),\n \"runtime token creation\",\n );\n if (\n !isRecord(responseValue) ||\n typeof responseValue.token !== \"string\" ||\n !responseValue.token.startsWith(\"oct_\") ||\n !isRecord(responseValue.record) ||\n typeof responseValue.record.id !== \"string\" ||\n responseValue.record.name !== name\n ) {\n throw new Error(\"OpenConnector returned an invalid runtime token\");\n }\n return {\n version: 1,\n origin,\n name,\n id: responseValue.record.id,\n token: responseValue.token,\n };\n}\n\nfunction disabledServer(\n servers: Record<string, McpServerConfig>,\n config: McpServerConfig,\n): Record<string, McpServerConfig> {\n return { ...servers, [OPEN_CONNECTOR_SERVER]: { ...config, disabled: true } };\n}\n\nasync function loadOrCreateRuntimeToken(\n office: Office,\n origin: string,\n adminToken: string,\n name: string,\n signal?: AbortSignal,\n): Promise<RuntimeTokenState> {\n const statePath = join(office.stateDir, TOKEN_STATE_FILE);\n const state = readRuntimeTokenState(statePath);\n if (state) {\n if (state.origin !== origin || state.name !== name) {\n throw new Error(`OpenConnector runtime token state does not match ${name}`);\n }\n return state;\n }\n\n const key = `${origin}:${office.key}`;\n const pending = pendingTokens.get(key);\n if (pending) return pending;\n\n const creation = (async () => {\n const current = readRuntimeTokenState(statePath);\n if (current) return current;\n const created = await createRuntimeToken(origin, adminToken, name, signal);\n ensureDirExists(office.stateDir);\n atomicWritePrivateFile(statePath, `${JSON.stringify(created, null, 2)}\\n`);\n log.logInfo(\n `[${office.address.conversationId}] Created OpenConnector runtime token ${created.id}`,\n );\n return created;\n })();\n pendingTokens.set(key, creation);\n try {\n return await creation;\n } finally {\n pendingTokens.delete(key);\n }\n}\n\n/**\n * Replace the deployment-wide OpenConnector bearer token with one scoped to\n * this Slack Conversation office. The privileged admin token is read only at\n * this host boundary and is never written into settings, Vault, or a sandbox.\n */\nexport async function provisionOfficeOpenConnectorToken(\n office: Office,\n platformWorkspaceId: string | undefined,\n servers: Record<string, McpServerConfig> | undefined,\n signal?: AbortSignal,\n): Promise<Record<string, McpServerConfig> | undefined> {\n const config = servers?.[OPEN_CONNECTOR_SERVER];\n const adminToken = readEnv(\"OPENCONNECTOR_ADMIN_TOKEN\");\n if (!config || config.disabled || !adminToken || office.address.platform !== \"slack\") {\n return servers;\n }\n const configuredOrigin = readEnv(\"OPENCONNECTOR_ORIGIN\");\n if (!configuredOrigin || !URL.canParse(configuredOrigin)) {\n log.logWarning(\n `[${office.address.conversationId}] OpenConnector token provisioning skipped`,\n \"OPENCONNECTOR_ORIGIN is missing or invalid\",\n );\n return disabledServer(servers, config);\n }\n if (!platformWorkspaceId) {\n log.logWarning(\n `[${office.address.conversationId}] OpenConnector token provisioning skipped`,\n \"Slack workspace ID is unavailable\",\n );\n return disabledServer(servers, config);\n }\n if (!config.url) {\n log.logWarning(\n `[${office.address.conversationId}] OpenConnector token provisioning skipped`,\n \"the open-connector server is not configured for HTTP\",\n );\n return disabledServer(servers, config);\n }\n\n try {\n const endpoint = new URL(config.url);\n const origin = new URL(configuredOrigin).origin;\n if (endpoint.origin !== origin) {\n throw new Error(\"the MCP endpoint does not match the deployment-owned OpenConnector origin\");\n }\n const name = `mikan:slack:${platformWorkspaceId}:${office.address.conversationId}`;\n const state = await loadOrCreateRuntimeToken(office, origin, adminToken, name, signal);\n return {\n ...servers,\n [OPEN_CONNECTOR_SERVER]: {\n ...config,\n headers: { ...config.headers, Authorization: `Bearer ${state.token}` },\n },\n };\n } catch (error) {\n log.logWarning(\n `[${office.address.conversationId}] OpenConnector token provisioning failed`,\n error instanceof Error ? error.message : String(error),\n );\n return disabledServer(servers, config);\n }\n}\n"]}
@@ -49,10 +49,16 @@ export interface McpLoadError {
49
49
  server: string;
50
50
  error: string;
51
51
  }
52
+ export interface McpServerInstruction {
53
+ server: string;
54
+ text: string;
55
+ }
52
56
  export interface McpToolsResult {
53
57
  /** Tools namespaced `mcp__<server>__<tool>`, ready for the agent tool list. */
54
58
  tools: AgentTool<TSchema>[];
55
59
  errors: McpLoadError[];
60
+ /** Admin-approved server guidance for operating its tools. */
61
+ instructions: McpServerInstruction[];
56
62
  /** Close all server connections (and kill stdio child processes). */
57
63
  dispose: () => Promise<void>;
58
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,KAAK,yBAAyB,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE1D,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,yBAAyB,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,qEAAqE;IACrE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,KAAK,yBAAyB,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE1D,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,yBAAyB,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,8DAA8D;IAC9D,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,qEAAqE;IACrE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport type { TSchema } from \"@sinclair/typebox\";\n\n/**\n * MCP (Model Context Protocol) server configuration and load results.\n *\n * A server entry is either stdio (`command` + optional `args`/`env`) or\n * streamable HTTP (`url` + optional `headers`). Exactly one of `command`\n * or `url` must be set — the settings schema keeps both optional so the\n * file stays object-rooted and forgiving; `loadMcpTools` enforces the\n * exclusivity at runtime.\n *\n * Credentials (API keys in `env`/`headers`) stay in host-side settings and\n * the MCP server process; the model sees only tool names and schemas.\n */\nexport interface McpServerConfig {\n /** stdio transport: executable to spawn on the host. */\n command?: string;\n args?: string[];\n /** Extra environment for the spawned server, merged over a safe default. */\n env?: Record<string, string>;\n /** streamable-HTTP transport: server endpoint URL. */\n url?: string;\n headers?: Record<string, string>;\n /** Disable without deleting — lets a conversation turn off a global server. */\n disabled?: boolean;\n}\n\ntype McpPresetCredentialTarget = \"env\" | \"header\" | \"url\";\n\ninterface McpPresetCredential {\n key: string;\n label: string;\n description: string;\n target: McpPresetCredentialTarget;\n required: boolean;\n secret: boolean;\n valuePrefix?: string;\n}\n\nexport interface McpPreset {\n id: string;\n name: string;\n description: string;\n category: string;\n serverName: string;\n sourceUrl: string;\n setupUrl: string;\n server: McpServerConfig;\n credentials: McpPresetCredential[];\n}\n\nexport interface McpLoadError {\n server: string;\n error: string;\n}\n\nexport interface McpToolsResult {\n /** Tools namespaced `mcp__<server>__<tool>`, ready for the agent tool list. */\n tools: AgentTool<TSchema>[];\n errors: McpLoadError[];\n /** Close all server connections (and kill stdio child processes). */\n dispose: () => Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport type { TSchema } from \"@sinclair/typebox\";\n\n/**\n * MCP (Model Context Protocol) server configuration and load results.\n *\n * A server entry is either stdio (`command` + optional `args`/`env`) or\n * streamable HTTP (`url` + optional `headers`). Exactly one of `command`\n * or `url` must be set — the settings schema keeps both optional so the\n * file stays object-rooted and forgiving; `loadMcpTools` enforces the\n * exclusivity at runtime.\n *\n * Credentials (API keys in `env`/`headers`) stay in host-side settings and\n * the MCP server process; the model sees only tool names and schemas.\n */\nexport interface McpServerConfig {\n /** stdio transport: executable to spawn on the host. */\n command?: string;\n args?: string[];\n /** Extra environment for the spawned server, merged over a safe default. */\n env?: Record<string, string>;\n /** streamable-HTTP transport: server endpoint URL. */\n url?: string;\n headers?: Record<string, string>;\n /** Disable without deleting — lets a conversation turn off a global server. */\n disabled?: boolean;\n}\n\ntype McpPresetCredentialTarget = \"env\" | \"header\" | \"url\";\n\ninterface McpPresetCredential {\n key: string;\n label: string;\n description: string;\n target: McpPresetCredentialTarget;\n required: boolean;\n secret: boolean;\n valuePrefix?: string;\n}\n\nexport interface McpPreset {\n id: string;\n name: string;\n description: string;\n category: string;\n serverName: string;\n sourceUrl: string;\n setupUrl: string;\n server: McpServerConfig;\n credentials: McpPresetCredential[];\n}\n\nexport interface McpLoadError {\n server: string;\n error: string;\n}\n\nexport interface McpServerInstruction {\n server: string;\n text: string;\n}\n\nexport interface McpToolsResult {\n /** Tools namespaced `mcp__<server>__<tool>`, ready for the agent tool list. */\n tools: AgentTool<TSchema>[];\n errors: McpLoadError[];\n /** Admin-approved server guidance for operating its tools. */\n instructions: McpServerInstruction[];\n /** Close all server connections (and kill stdio child processes). */\n dispose: () => Promise<void>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/packages/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EAEnB,eAAe,EACf,gBAAgB,EAChB,YAAY,EAEZ,mBAAmB,EAEnB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuCjD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAsCxD;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,CAIpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc/C;AAgBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAKzD;AAID;;;;;;;;;;;;GAYG;AAEH,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAOrD;;;;;;;;;;;;;;GAcG;AACH;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAS/E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,kBAAkB,GAC1B,mBAAmB,CAerB;AAgOD;;;;;;;;;GASG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAiB3B;AAgED;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuB3E,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,gBAAgB,GACzB,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC,CAM3D;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,sBAAsB,GAAG,gBAAgB,CA+B7F;AAiFD;;;;;;;;;GASG;AAEH,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAepB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAMT;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAWpB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/packages/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EAEnB,eAAe,EACf,gBAAgB,EAChB,YAAY,EAEZ,mBAAmB,EAEnB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAepB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuCjD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAsCxD;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,CAIpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc/C;AAgBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAKzD;AAID;;;;;;;;;;;;GAYG;AAEH,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMrD;;;;;;;;;;;;;;GAcG;AACH;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAS/E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,kBAAkB,GAC1B,mBAAmB,CAerB;AA8LD;;;;;;;;;GASG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAiB3B;AAkED;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuB3E,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,gBAAgB,GACzB,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC,CAM3D;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,sBAAsB,GAAG,gBAAgB,CA+B7F;AA2FD;;;;;;;;;GASG;AAEH,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAepB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAMT;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAWpB"}
@@ -7,7 +7,7 @@
7
7
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
8
  import { execFileSync } from "node:child_process";
9
9
  import { createHash } from "node:crypto";
10
- import { cpSync, existsSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
10
+ import { cpSync, existsSync, lstatSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
11
11
  import { ensureDirExists, readTextFileIfExists } from "../utils/file-guards.js";
12
12
  import * as log from "../log.js";
13
13
  import { officeStateDir } from "../office/index.js";
@@ -192,7 +192,6 @@ export function formatSource(parsed) {
192
192
  }
193
193
  /** Git operations get a bounded wall clock so a hung remote cannot wedge a save. */
194
194
  const GIT_TIMEOUT_MS = 120_000;
195
- const NPM_TIMEOUT_MS = 300_000;
196
195
  const MATERIALIZED_REF_FILE = "mikan-ref";
197
196
  /**
198
197
  * How far materialization may go to produce the package's files.
@@ -285,7 +284,7 @@ function materializeGit(parsed, scopeDir, mode) {
285
284
  throw new Error(`Not fetched on this host yet: ${describeRef(parsed)}`);
286
285
  }
287
286
  // An incomplete checkout must not retain a marker from an earlier attempt:
288
- // otherwise a retry could mistake a failed dependency install for success.
287
+ // otherwise a retry could mistake an interrupted fetch or checkout for success.
289
288
  if (alreadyCloned)
290
289
  rmSync(markerPath, { force: true });
291
290
  if (!alreadyCloned) {
@@ -315,9 +314,8 @@ function materializeGit(parsed, scopeDir, mode) {
315
314
  }
316
315
  git(cloneDir, ["checkout", "-q", "--detach", "FETCH_HEAD"]);
317
316
  const dir = resolveSubpath(cloneDir, parsed);
318
- installDependencies(dir);
319
317
  // This is the completion marker, not merely a fetched-ref marker. Write it
320
- // last so a failed npm install is retried by the next materialization.
318
+ // only after checkout and subpath validation have both succeeded.
321
319
  writeFileSync(markerPath, `${requestedRef}\n`);
322
320
  return dir;
323
321
  }
@@ -402,41 +400,6 @@ function resolveSubpath(cloneDir, parsed) {
402
400
  }
403
401
  return dir;
404
402
  }
405
- /**
406
- * Install runtime dependencies when the package declares any. This runs npm
407
- * on the host against code that was just fetched from a remote, which is the
408
- * same trust level as importing the package itself — but it is worth being
409
- * explicit that it happens, because it also runs the dependencies' install
410
- * scripts.
411
- */
412
- function installDependencies(dir) {
413
- if (!hasDependencies(dir))
414
- return;
415
- log.logInfo(`Installing package dependencies in ${dir}`);
416
- try {
417
- execFileSync("npm", ["install", "--omit=dev", "--no-audit", "--no-fund"], {
418
- cwd: dir,
419
- timeout: NPM_TIMEOUT_MS,
420
- encoding: "utf-8",
421
- stdio: ["ignore", "pipe", "pipe"],
422
- });
423
- }
424
- catch (err) {
425
- throw new Error(`npm install failed for the package: ${gitErrorText(err)}`, { cause: err });
426
- }
427
- }
428
- function hasDependencies(dir) {
429
- const raw = readTextFileIfExists(join(dir, "package.json"));
430
- if (raw === undefined)
431
- return false;
432
- try {
433
- const pkg = JSON.parse(raw);
434
- return Boolean(pkg.dependencies && Object.keys(pkg.dependencies).length > 0);
435
- }
436
- catch {
437
- return false;
438
- }
439
- }
440
403
  function git(cwd, args) {
441
404
  return execFileSync("git", args, {
442
405
  cwd,
@@ -525,7 +488,7 @@ async function describe(source, scope, options, shadowedBy) {
525
488
  }
526
489
  function contributedSkills(dir) {
527
490
  const skillsDir = join(dir, "skills");
528
- return loadSkillsFromDir({ dir: skillsDir, source: "package" }).skills.map((skill) => skill.name);
491
+ return loadSkillsFromDir({ dir: skillsDir, source: "package", rejectSymlinks: true }).skills.map((skill) => skill.name);
529
492
  }
530
493
  /** Conventional resource directory inside a package, mirroring pi's layout. */
531
494
  const SKILLS_SUBDIR = "skills";
@@ -561,11 +524,9 @@ export function packageSkillRuntimeDir(slug) {
561
524
  * This pure step consumes an already resolved package set and never reaches the network.
562
525
  */
563
526
  export function conversationPackageSkillMounts(packages) {
564
- return packages.skillDirs.map(({ slug, dir }) => ({
565
- source: dir,
566
- target: packageSkillRuntimeDir(slug),
567
- readOnly: true,
568
- }));
527
+ return packages.skillDirs.flatMap(({ slug, dir }) => isNonSymlinkDirectory(dir)
528
+ ? [{ source: dir, target: packageSkillRuntimeDir(slug), readOnly: true }]
529
+ : []);
569
530
  }
570
531
  /**
571
532
  * Resolve the package set for one conversation. Reads both scopes' settings,
@@ -633,9 +594,19 @@ function materialize(declaration, options, errors) {
633
594
  }
634
595
  function packageSkillDir(pkg) {
635
596
  const dir = join(pkg.dir, SKILLS_SUBDIR);
636
- if (!existsSync(dir) || !statSync(dir).isDirectory())
637
- return [];
638
- return [{ slug: packageSlug(pkg), dir }];
597
+ return isNonSymlinkDirectory(dir) ? [{ slug: packageSlug(pkg), dir }] : [];
598
+ }
599
+ function isNonSymlinkDirectory(path) {
600
+ if (!existsSync(path))
601
+ return false;
602
+ try {
603
+ const stats = lstatSync(path);
604
+ return !stats.isSymbolicLink() && stats.isDirectory();
605
+ }
606
+ catch (err) {
607
+ log.logWarning(`Could not inspect package skills directory ${path}`, messageOf(err));
608
+ return false;
609
+ }
639
610
  }
640
611
  /**
641
612
  * Filesystem-safe, collision-resistant name for a package, used as the mount