@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.
- package/CHANGELOG.md +21 -0
- package/dist/adapters/discord/bot.d.ts +3 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +27 -5
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/github/bot.d.ts +6 -1
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +56 -21
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/shared.d.ts +12 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +44 -1
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +3 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +75 -28
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +3 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +23 -7
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/agent/catalog.d.ts.map +1 -1
- package/dist/agent/catalog.js +5 -1
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +116 -46
- package/dist/agent/runner.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +17 -0
- package/dist/env-manifest.js.map +1 -1
- package/dist/main.js +74 -12
- package/dist/main.js.map +1 -1
- package/dist/mcp/catalog.d.ts.map +1 -1
- package/dist/mcp/catalog.js +21 -0
- package/dist/mcp/catalog.js.map +1 -1
- package/dist/mcp/loader.d.ts +3 -2
- package/dist/mcp/loader.d.ts.map +1 -1
- package/dist/mcp/loader.js +54 -20
- package/dist/mcp/loader.js.map +1 -1
- package/dist/mcp/open-connector.d.ts +14 -0
- package/dist/mcp/open-connector.d.ts.map +1 -0
- package/dist/mcp/open-connector.js +203 -0
- package/dist/mcp/open-connector.js.map +1 -0
- package/dist/mcp/types.d.ts +6 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/packages/index.d.ts.map +1 -1
- package/dist/packages/index.js +20 -49
- package/dist/packages/index.js.map +1 -1
- package/dist/process-lifecycle.d.ts +19 -0
- package/dist/process-lifecycle.d.ts.map +1 -0
- package/dist/process-lifecycle.js +60 -0
- package/dist/process-lifecycle.js.map +1 -0
- package/dist/runtime/conversation-runtime.js +11 -4
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +3 -1
- package/dist/runtime/session-lifecycle.d.ts.map +1 -1
- package/dist/runtime/session-lifecycle.js +22 -2
- package/dist/runtime/session-lifecycle.js.map +1 -1
- package/dist/runtime/types.d.ts +5 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/types.d.ts +16 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- 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"]}
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/mcp/types.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/mcp/types.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/packages/index.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
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.
|
|
565
|
-
source: dir,
|
|
566
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|