@geminixiang/mikan 1.0.0-beta.37 → 1.0.0-beta.38

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 CHANGED
@@ -9,6 +9,12 @@ any release.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.0.0-beta.38]
13
+
14
+ ### Fixed
15
+
16
+ - Align the conversation-skill symlink guard with what the host actually reads: symlinked entries are skipped per skill — with the skipped paths named in the host log and surfaced to the agent in its prompt — instead of one symlink anywhere disqualifying the whole tree, and symlinks inside vendored `node_modules` or dot directories (which the loader never reads) no longer disqualify anything. Offices whose skills vanished because a skill vendored npm dependencies (`.bin` symlinks) get them back.
17
+
12
18
  ## [1.0.0-beta.37]
13
19
 
14
20
  ### Fixed
package/dist/agent.d.ts CHANGED
@@ -13,7 +13,6 @@ export declare function buildPromptPayload(message: ConversationMessage, workspa
13
13
  userMessage: string;
14
14
  imageAttachments: ImageContent[];
15
15
  }>;
16
- export declare function isSafePromptSkillTree(dir: string, conversationDir: string): boolean;
17
16
  export declare function resolveTriggerAttribution(message: Pick<ConversationMessage, "id" | "text" | "userName">): string | undefined;
18
17
  /**
19
18
  * Instructions that vary per turn (event-trigger mode, response attribution).
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAC;AAIrF,OAAO,EAAY,KAAK,YAAY,EAAc,MAAM,uBAAuB,CAAC;AA6BhF,OAAO,KAAK,EAEV,mBAAmB,EAKpB,MAAM,cAAc,CAAC;AAiBtB,OAAO,EAKL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAiC5B,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAER;AAcD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CA2BR;AAgGD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,EAChC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACxD,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAAC,CAcD;AA0HD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CA0BnF;AA8CD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GAC7D,MAAM,GAAG,SAAS,CAOpB;AAoMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,OAAO,EACvB,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,YAAY,EAAE,MAAM,GACnB,MAAM,CAkBR;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAYR;AA00CD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqYxF"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAC;AAIrF,OAAO,EAAY,KAAK,YAAY,EAAc,MAAM,uBAAuB,CAAC;AA6BhF,OAAO,KAAK,EAEV,mBAAmB,EAKpB,MAAM,cAAc,CAAC;AAiBtB,OAAO,EAKL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAiC5B,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAER;AAcD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CA2BR;AAgGD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,EAChC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACxD,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAAC,CAcD;AA8KD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GAC7D,MAAM,GAAG,SAAS,CAOpB;AAyMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,OAAO,EACvB,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,YAAY,EAAE,MAAM,GACnB,MAAM,CAkBR;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAYR;AA80CD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAsYxF"}
package/dist/agent.js CHANGED
@@ -8,7 +8,7 @@ import { runSubagent } from "./harness/subagent-runner.js";
8
8
  import { loadSubagentProfiles } from "./harness/subagent-profiles.js";
9
9
  import { mergeSubagentProgress, parseSubagentProgressSnapshot, renderSubagentDashboard, settleSubagentProgress, } from "./subagent-progress.js";
10
10
  import { createHash } from "crypto";
11
- import { existsSync, lstatSync, readdirSync } from "fs";
11
+ import { existsSync, lstatSync } from "fs";
12
12
  import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "fs/promises";
13
13
  import { tmpdir } from "os";
14
14
  import { basename, isAbsolute, join, posix, relative, resolve, sep } from "path";
@@ -242,54 +242,28 @@ function loadMikanSkills(address, conversationDir, workspacePath, projection) {
242
242
  }
243
243
  // Load conversation-specific skills (override workspace skills on collision).
244
244
  // Host prompt construction must never follow an agent-created symlink out of
245
- // the conversation office.
245
+ // the conversation office, so this load rejects symlinks per entry — on
246
+ // exactly the paths it reads. Vendored node_modules and dot directories are
247
+ // never read and never disqualify anything.
246
248
  const conversationSkillsDir = projection.promptSources.conversationSkillsDir;
247
- if (isSafePromptSkillTree(conversationSkillsDir, projection.promptSources.conversationDir)) {
248
- for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" })
249
- .skills) {
250
- skill.filePath = translatePath(skill.filePath);
251
- skill.baseDir = translatePath(skill.baseDir);
252
- skillMap.set(skill.name, skill);
253
- }
254
- }
255
- else if (existsSync(conversationSkillsDir)) {
256
- log.logWarning("Ignoring unsafe conversation skill tree", conversationSkillsDir);
257
- }
258
- return Array.from(skillMap.values());
259
- }
260
- export function isSafePromptSkillTree(dir, conversationDir) {
261
- if (!existsSync(dir))
262
- return true;
263
- if (dir !== conversationDir && !dir.startsWith(`${conversationDir}${sep}`))
264
- return false;
265
- const stack = [dir];
266
- while (stack.length > 0) {
267
- const current = stack.pop();
268
- let stats;
269
- try {
270
- stats = lstatSync(current);
271
- }
272
- catch {
273
- return false;
274
- }
275
- if (stats.isSymbolicLink())
276
- return false;
277
- if (!stats.isDirectory())
249
+ const conversationSkills = loadSkillsFromDir({
250
+ dir: conversationSkillsDir,
251
+ source: "channel",
252
+ rejectSymlinks: true,
253
+ });
254
+ const skippedSkillLinks = [];
255
+ for (const diagnostic of conversationSkills.diagnostics) {
256
+ if (diagnostic.code !== "symlink")
278
257
  continue;
279
- let entries;
280
- try {
281
- entries = readdirSync(current, { withFileTypes: true });
282
- }
283
- catch {
284
- return false;
285
- }
286
- for (const entry of entries) {
287
- if (entry.isSymbolicLink())
288
- return false;
289
- stack.push(join(current, entry.name));
290
- }
258
+ log.logWarning("Skipping conversation skill entry (symlink)", diagnostic.path);
259
+ skippedSkillLinks.push(translatePath(diagnostic.path));
260
+ }
261
+ for (const skill of conversationSkills.skills) {
262
+ skill.filePath = translatePath(skill.filePath);
263
+ skill.baseDir = translatePath(skill.baseDir);
264
+ skillMap.set(skill.name, skill);
291
265
  }
292
- return true;
266
+ return { skills: Array.from(skillMap.values()), skippedSkillLinks };
293
267
  }
294
268
  function buildRuntimePaths(runtimeWorkspaceRoot, address) {
295
269
  const workspaceRoot = runtimeWorkspaceRoot.replace(/\/+$/, "") || "/";
@@ -344,7 +318,7 @@ export function resolveTriggerAttribution(message) {
344
318
  return `@${message.userName}`;
345
319
  return undefined;
346
320
  }
347
- function buildSystemPrompt(workspacePath, address, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection) {
321
+ function buildSystemPrompt(workspacePath, address, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection, skippedSkillLinks = []) {
348
322
  const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, address);
349
323
  const sandboxType = sandboxConfig.type;
350
324
  const isContainerLike = sandboxType === "container" || sandboxType === "image";
@@ -450,7 +424,9 @@ Scripts are in: {baseDir}/
450
424
  \`name\` and \`description\` are required. Use \`{baseDir}\` as placeholder for the skill's directory path.
451
425
 
452
426
  ### Available Skills
453
- ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}
427
+ ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}${skippedSkillLinks.length > 0
428
+ ? `\n\nNote: these skill entries were skipped because they are symlinks, which the host never follows when reading this conversation's skills: ${skippedSkillLinks.join(", ")}. Replace each with a real file or directory (for example \`cp -rL\`) to load it.`
429
+ : ""}
454
430
 
455
431
  ## Events
456
432
  Use the \`event\` tool to schedule immediate, one-shot, or periodic follow-ups. It writes to the host-side mikan control plane and fills routing fields for the current conversation automatically.
@@ -1109,9 +1085,10 @@ async function prepareRunContext(params) {
1109
1085
  reloadSessionMessages(session, conversationId);
1110
1086
  const projection = resolveWorkspaceProjection(join(conversationDir, ".."), message.address);
1111
1087
  const memory = await getMemory(projection);
1112
- const skills = mergeExtensionSkills(loadMikanSkills(message.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection), params.extensionSkills ?? []);
1088
+ const conversationSkillLoad = loadMikanSkills(message.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection);
1089
+ const skills = mergeExtensionSkills(conversationSkillLoad.skills, params.extensionSkills ?? []);
1113
1090
  const triggerAttribution = resolveTriggerAttribution(message);
1114
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, message.address, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection);
1091
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, message.address, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection, conversationSkillLoad.skippedSkillLinks);
1115
1092
  session.agent.state.systemPrompt = systemPrompt;
1116
1093
  // Cache diagnosis: a byte-stable system prompt is the precondition for
1117
1094
  // provider prompt caching. If this hash changes between turns of one
@@ -1545,7 +1522,7 @@ export async function createRunner(options) {
1545
1522
  });
1546
1523
  // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
1547
1524
  const memory = await getMemory(projection);
1548
- const skills = loadMikanSkills(options.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection);
1525
+ const { skills, skippedSkillLinks } = loadMikanSkills(options.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection);
1549
1526
  const emptyPlatform = {
1550
1527
  name: "chat",
1551
1528
  formattingGuide: "",
@@ -1553,7 +1530,7 @@ export async function createRunner(options) {
1553
1530
  users: [],
1554
1531
  trustModel: "membership",
1555
1532
  };
1556
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, options.address, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection);
1533
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, options.address, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection, skippedSkillLinks);
1557
1534
  // Create session manager and settings manager. Top-level/private sessions
1558
1535
  // use the conversation's current pointer; scoped sessions use fixed files.
1559
1536
  // Platform-specific scope behavior is resolved before runner creation.