@geminixiang/mikan 1.0.0-beta.12 → 1.0.0-beta.13

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 (108) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/adapter.d.ts +1 -1
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts.map +1 -1
  6. package/dist/adapters/discord/bot.js +1 -0
  7. package/dist/adapters/discord/bot.js.map +1 -1
  8. package/dist/adapters/discord/context.d.ts.map +1 -1
  9. package/dist/adapters/discord/context.js +1 -0
  10. package/dist/adapters/discord/context.js.map +1 -1
  11. package/dist/adapters/github/bot.d.ts +6 -2
  12. package/dist/adapters/github/bot.d.ts.map +1 -1
  13. package/dist/adapters/github/bot.js +23 -2
  14. package/dist/adapters/github/bot.js.map +1 -1
  15. package/dist/adapters/github/context.d.ts.map +1 -1
  16. package/dist/adapters/github/context.js +1 -0
  17. package/dist/adapters/github/context.js.map +1 -1
  18. package/dist/adapters/github/tool-pack.d.ts +12 -0
  19. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  20. package/dist/adapters/github/tool-pack.js +30 -0
  21. package/dist/adapters/github/tool-pack.js.map +1 -0
  22. package/dist/adapters/github/types.d.ts +52 -0
  23. package/dist/adapters/github/types.d.ts.map +1 -1
  24. package/dist/adapters/github/types.js.map +1 -1
  25. package/dist/adapters/slack/bot.d.ts.map +1 -1
  26. package/dist/adapters/slack/bot.js +1 -0
  27. package/dist/adapters/slack/bot.js.map +1 -1
  28. package/dist/adapters/slack/context.d.ts.map +1 -1
  29. package/dist/adapters/slack/context.js +1 -0
  30. package/dist/adapters/slack/context.js.map +1 -1
  31. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  32. package/dist/adapters/telegram/bot.js +1 -0
  33. package/dist/adapters/telegram/bot.js.map +1 -1
  34. package/dist/adapters/telegram/context.d.ts.map +1 -1
  35. package/dist/adapters/telegram/context.js +1 -0
  36. package/dist/adapters/telegram/context.js.map +1 -1
  37. package/dist/agent.d.ts +16 -11
  38. package/dist/agent.d.ts.map +1 -1
  39. package/dist/agent.js +220 -230
  40. package/dist/agent.js.map +1 -1
  41. package/dist/cli/download.d.ts.map +1 -0
  42. package/dist/cli/download.js.map +1 -0
  43. package/dist/commands/model.d.ts.map +1 -1
  44. package/dist/commands/model.js +1 -0
  45. package/dist/commands/model.js.map +1 -1
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +1 -0
  48. package/dist/config.js.map +1 -1
  49. package/dist/execution-resolver.d.ts.map +1 -1
  50. package/dist/execution-resolver.js +8 -12
  51. package/dist/execution-resolver.js.map +1 -1
  52. package/dist/harness/models.d.ts +5 -0
  53. package/dist/harness/models.d.ts.map +1 -1
  54. package/dist/harness/models.js +10 -0
  55. package/dist/harness/models.js.map +1 -1
  56. package/dist/main.js +18 -7
  57. package/dist/main.js.map +1 -1
  58. package/dist/runtime/conversation-runtime.js +1 -1
  59. package/dist/runtime/conversation-runtime.js.map +1 -1
  60. package/dist/runtime/types.d.ts +9 -3
  61. package/dist/runtime/types.d.ts.map +1 -1
  62. package/dist/runtime/types.js.map +1 -1
  63. package/dist/tools/github-checks.d.ts +1 -1
  64. package/dist/tools/github-checks.d.ts.map +1 -1
  65. package/dist/tools/github-checks.js +15 -4
  66. package/dist/tools/github-checks.js.map +1 -1
  67. package/dist/tools/github-pr.d.ts +1 -1
  68. package/dist/tools/github-pr.d.ts.map +1 -1
  69. package/dist/tools/github-pr.js.map +1 -1
  70. package/dist/tools/index.d.ts +10 -5
  71. package/dist/tools/index.d.ts.map +1 -1
  72. package/dist/tools/index.js +12 -9
  73. package/dist/tools/index.js.map +1 -1
  74. package/dist/tools/types.d.ts +28 -0
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/types.js.map +1 -1
  77. package/dist/trigger.d.ts.map +1 -1
  78. package/dist/trigger.js +1 -2
  79. package/dist/trigger.js.map +1 -1
  80. package/dist/types.d.ts +18 -39
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js.map +1 -1
  83. package/dist/vault/policy.d.ts +20 -0
  84. package/dist/vault/policy.d.ts.map +1 -0
  85. package/dist/vault/policy.js +20 -0
  86. package/dist/vault/policy.js.map +1 -0
  87. package/dist/web/admin/portal.js +4 -4
  88. package/dist/web/admin/portal.js.map +1 -1
  89. package/dist/web/login/portal.js +1 -1
  90. package/dist/web/login/portal.js.map +1 -1
  91. package/dist/web/portal-shell.d.ts +4 -0
  92. package/dist/web/portal-shell.d.ts.map +1 -0
  93. package/dist/{portal-shell.js → web/portal-shell.js} +2 -2
  94. package/dist/web/portal-shell.js.map +1 -0
  95. package/dist/web/session-view/portal.js +1 -1
  96. package/dist/web/session-view/portal.js.map +1 -1
  97. package/package.json +4 -4
  98. package/dist/download.d.ts.map +0 -1
  99. package/dist/download.js.map +0 -1
  100. package/dist/model-registry.d.ts +0 -4
  101. package/dist/model-registry.d.ts.map +0 -1
  102. package/dist/model-registry.js +0 -7
  103. package/dist/model-registry.js.map +0 -1
  104. package/dist/portal-shell.d.ts +0 -4
  105. package/dist/portal-shell.d.ts.map +0 -1
  106. package/dist/portal-shell.js.map +0 -1
  107. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
  108. /package/dist/{download.js → cli/download.js} +0 -0
package/dist/agent.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Agent runner: prompt construction, path translation, run lifecycle, and createRunner.
3
+ * Single module — do not split helpers that only this file uses.
4
+ */
1
5
  import { DEFAULT_EVENT_BUDGET, defaultExtensionDirs, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, } from "./harness/index.js";
2
6
  import { createHash } from "crypto";
3
7
  import { existsSync, readFileSync } from "fs";
@@ -7,18 +11,31 @@ import { resolveConversationSettings } from "./config.js";
7
11
  import { readEnv } from "./utils/env.js";
8
12
  import { ActorExecutionResolver } from "./execution-resolver.js";
9
13
  import * as log from "./log.js";
10
- import { reportUserFacingError } from "./observability/sentry.js";
11
14
  import { createExecutor, } from "./sandbox/index.js";
12
15
  import { createMountedRuntimePathContext } from "./sandbox/path-context.js";
13
- import { addLifecycleBreadcrumb, metricAttributes, updateActiveSpanAttribution, } from "./observability/sentry.js";
16
+ import { addLifecycleBreadcrumb, metricAttributes, reportUserFacingError, updateActiveSpanAttribution, } from "./observability/sentry.js";
14
17
  import { AgentMemoryFileManager } from "./sessions/agent-memory-file-manager.js";
15
18
  import { extractSessionUuid, openManagedSession, } from "./sessions/store.js";
16
19
  import { HostEventStore } from "./tools/event.js";
17
20
  import { createMikanTools } from "./tools/index.js";
18
21
  import * as Sentry from "@sentry/node";
19
22
  import { formatLocalTimestamp } from "./utils/date.js";
20
- import { resolveConfiguredModel } from "./model-registry.js";
21
23
  import { emitAgentEvent } from "./agent-events.js";
24
+ export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot) {
25
+ if (sandboxConfig.type === "image") {
26
+ return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
27
+ }
28
+ return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);
29
+ }
30
+ export function translateRuntimePathToHost(runtimePath, pathContext) {
31
+ return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;
32
+ }
33
+ export function translateAttachPathToHost(filePath, pathContext) {
34
+ const runtimePath = posix.isAbsolute(filePath)
35
+ ? filePath
36
+ : posix.join(pathContext.runtimeWorkspaceRoot, filePath);
37
+ return translateRuntimePathToHost(runtimePath, pathContext);
38
+ }
22
39
  const IMAGE_MIME_TYPES = {
23
40
  jpg: "image/jpeg",
24
41
  jpeg: "image/jpeg",
@@ -29,12 +46,52 @@ const IMAGE_MIME_TYPES = {
29
46
  function getImageMimeType(filename) {
30
47
  return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
31
48
  }
32
- function buildThreadSessionName(message) {
33
- const text = message?.text?.trim();
34
- if (!text)
35
- return undefined;
36
- const userLabel = message?.userName || message?.user || "unknown";
37
- return `[${userLabel}]: ${text}`;
49
+ function formatTimestampedUserMessage(message) {
50
+ const timestamp = formatLocalTimestamp(new Date());
51
+ const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : "";
52
+ return `[${timestamp}] [${message.userName || "unknown"}]${threadContext}: ${message.text}`;
53
+ }
54
+ function collectMessageAttachments(message, workspacePath, pathContext) {
55
+ const imageAttachments = [];
56
+ const nonImagePaths = [];
57
+ for (const attachment of message.attachments || []) {
58
+ const runtimePath = `${workspacePath}/${attachment.localPath}`;
59
+ const hostPath = pathContext?.runtimeToHostPath?.(runtimePath) ?? runtimePath;
60
+ const mimeType = getImageMimeType(attachment.localPath);
61
+ if (mimeType && existsSync(hostPath)) {
62
+ try {
63
+ imageAttachments.push({
64
+ type: "image",
65
+ mimeType,
66
+ data: readFileSync(hostPath).toString("base64"),
67
+ });
68
+ }
69
+ catch {
70
+ nonImagePaths.push(runtimePath);
71
+ }
72
+ }
73
+ else {
74
+ nonImagePaths.push(runtimePath);
75
+ }
76
+ }
77
+ return { imageAttachments, nonImagePaths };
78
+ }
79
+ export function buildPromptPayload(message, workspacePath, pathContext) {
80
+ let userMessage = formatTimestampedUserMessage(message);
81
+ const { imageAttachments, nonImagePaths } = collectMessageAttachments(message, workspacePath, pathContext);
82
+ if (nonImagePaths.length > 0) {
83
+ userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
84
+ }
85
+ return { userMessage, imageAttachments };
86
+ }
87
+ async function writePromptDebugContext(conversationDir, systemPrompt, session, userMessage, imageAttachmentCount) {
88
+ const debugContext = {
89
+ systemPrompt,
90
+ messages: session.messages,
91
+ newUserMessage: userMessage,
92
+ imageAttachmentCount,
93
+ };
94
+ await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
38
95
  }
39
96
  async function getMemory(conversationDir) {
40
97
  const parts = [];
@@ -329,82 +386,21 @@ Do not add this to \`[SILENT]\` responses.`);
329
386
  }
330
387
  return parts.join("\n\n");
331
388
  }
332
- export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot) {
333
- if (sandboxConfig.type === "image") {
334
- return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
335
- }
336
- return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);
337
- }
338
- function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
339
- const executionResolver = vaultManager && sandboxConfig.type !== "host"
340
- ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)
341
- : undefined;
342
- // activeExecutor is replaced at the start of each run() call when executionResolver
343
- // is present, so the stable `executor` wrapper always delegates to the latest resolved value.
344
- let activeExecutor = executionResolver !== undefined
345
- ? createExecutor({ type: "host" })
346
- : createExecutor(sandboxConfig);
347
- const executor = {
348
- exec(command, options) {
349
- return activeExecutor.exec(command, options);
350
- },
351
- getWorkspacePath(hostPath) {
352
- return activeExecutor.getWorkspacePath(hostPath);
353
- },
354
- getSandboxConfig() {
355
- return activeExecutor.getSandboxConfig();
356
- },
357
- getPathContext(hostWorkspaceRoot) {
358
- return activeExecutor.getPathContext(hostWorkspaceRoot);
359
- },
360
- };
361
- return {
362
- executionResolver,
363
- executor,
364
- getPathContext: () => executor.getPathContext(hostWorkspacePath),
365
- async resolveExecutorForRun(context) {
366
- if (!executionResolver)
367
- return;
368
- activeExecutor = await executionResolver.resolve(context);
369
- },
370
- };
371
- }
372
- /**
373
- * Extension host services over mikan's runtime infrastructure: schedules
374
- * become event files under `<workspaceDir>/events` (picked up live by
375
- * EventsWatcher), secrets come from `vaults/extensions/<slug>/env`, and
376
- * notify posts through the platform bots when main.ts provides a notifier.
377
- */
378
- function buildExtensionHostServices(params) {
379
- const { workspaceDir, vaultManager, platformNotifier, platformReactor } = params;
380
- const eventStore = HostEventStore.fromWorkspaceDir(workspaceDir);
381
- return {
382
- stateDir: readEnv("STATE_DIR"),
383
- scheduleStore: {
384
- write: async (filename, payload) => {
385
- // Event files tolerate a missing platform (single-platform default),
386
- // so the harness payload is a valid event payload as written.
387
- await eventStore.write(filename, payload);
388
- },
389
- delete: async (filename) => (await eventStore.delete(filename)).deleted,
390
- list: async () => (await eventStore.list()).map((entry) => ({
391
- filename: entry.filename,
392
- payload: entry.payload,
393
- })),
394
- },
395
- ...(platformNotifier ? { postMessage: platformNotifier } : {}),
396
- ...(platformReactor ? { addReaction: platformReactor } : {}),
397
- ...(vaultManager
398
- ? {
399
- resolveSecrets: (slug) => vaultManager.resolve(`extensions/${slug}`)?.env ?? {},
400
- }
401
- : {}),
402
- };
389
+ export function appendTriggerAttribution(text, triggerAttribution, sessionLink) {
390
+ if (!triggerAttribution)
391
+ return text;
392
+ const trimmed = text.trimEnd();
393
+ const legacySuffix = `_Triggered by ${triggerAttribution}_`;
394
+ // Slack mrkdwn italics cannot span a URL — a `_..._` wrapping a link renders
395
+ // the underscores literally. Keep the session link outside the italic span.
396
+ const suffix = sessionLink ? `${legacySuffix} · session: ${sessionLink}` : legacySuffix;
397
+ if (trimmed.endsWith(suffix))
398
+ return text;
399
+ const body = trimmed.endsWith(legacySuffix)
400
+ ? trimmed.slice(0, -legacySuffix.length).trimEnd()
401
+ : trimmed;
402
+ return `${body}\n\n${suffix}`;
403
403
  }
404
- /**
405
- * Local (workspace/conversation) skills override extension skills on name
406
- * collision: an admin's on-disk skill always beats an extension's.
407
- */
408
404
  function mergeExtensionSkills(local, extension) {
409
405
  if (extension.length === 0)
410
406
  return local;
@@ -415,42 +411,6 @@ function mergeExtensionSkills(local, extension) {
415
411
  byName.set(skill.name, skill);
416
412
  return [...byName.values()];
417
413
  }
418
- async function createConfiguredAgentSession(params) {
419
- const { conversationId, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, } = params;
420
- // Host-only dirs under the state dir: extension code runs in the mikan
421
- // process, so it must never load from workspace paths — those are mounted
422
- // into sandbox containers and agent-writable (sandbox escape otherwise).
423
- const extensionsResult = await loadExtensions({
424
- dirs: defaultExtensionDirs(conversationId, readEnv("STATE_DIR")),
425
- context: { conversationId, workspaceDir, model, thinkingLevel },
426
- services: buildExtensionHostServices({
427
- workspaceDir,
428
- vaultManager,
429
- platformNotifier,
430
- platformReactor,
431
- }),
432
- });
433
- for (const err of extensionsResult.errors) {
434
- log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
435
- }
436
- if (extensionsResult.extensions.length > 0) {
437
- log.logInfo(`[${conversationId}] Loaded ${extensionsResult.extensions.length} extension(s): ${extensionsResult.extensions.map((extension) => extension.name).join(", ")}`);
438
- }
439
- const session = new MikanAgentSession({
440
- systemPrompt,
441
- model,
442
- thinkingLevel,
443
- tools,
444
- models,
445
- sessionStore,
446
- extensions: extensionsResult.registry,
447
- });
448
- const reloaded = session.reloadFromSession();
449
- if (reloaded > 0) {
450
- log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from session context`);
451
- }
452
- return { session, extensionSkills: extensionsResult.skills };
453
- }
454
414
  function createEmptyUsageTotals() {
455
415
  return {
456
416
  input: 0,
@@ -521,53 +481,6 @@ function createRunQueue(responder, runState) {
521
481
  wait: () => queueChain,
522
482
  };
523
483
  }
524
- function formatTimestampedUserMessage(message) {
525
- const timestamp = formatLocalTimestamp(new Date());
526
- const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : "";
527
- return `[${timestamp}] [${message.userName || "unknown"}]${threadContext}: ${message.text}`;
528
- }
529
- function collectMessageAttachments(message, workspacePath, pathContext) {
530
- const imageAttachments = [];
531
- const nonImagePaths = [];
532
- for (const attachment of message.attachments || []) {
533
- const runtimePath = `${workspacePath}/${attachment.localPath}`;
534
- const hostPath = pathContext?.runtimeToHostPath?.(runtimePath) ?? runtimePath;
535
- const mimeType = getImageMimeType(attachment.localPath);
536
- if (mimeType && existsSync(hostPath)) {
537
- try {
538
- imageAttachments.push({
539
- type: "image",
540
- mimeType,
541
- data: readFileSync(hostPath).toString("base64"),
542
- });
543
- }
544
- catch {
545
- nonImagePaths.push(runtimePath);
546
- }
547
- }
548
- else {
549
- nonImagePaths.push(runtimePath);
550
- }
551
- }
552
- return { imageAttachments, nonImagePaths };
553
- }
554
- export function buildPromptPayload(message, workspacePath, pathContext) {
555
- let userMessage = formatTimestampedUserMessage(message);
556
- const { imageAttachments, nonImagePaths } = collectMessageAttachments(message, workspacePath, pathContext);
557
- if (nonImagePaths.length > 0) {
558
- userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
559
- }
560
- return { userMessage, imageAttachments };
561
- }
562
- async function writePromptDebugContext(conversationDir, systemPrompt, session, userMessage, imageAttachmentCount) {
563
- const debugContext = {
564
- systemPrompt,
565
- messages: session.messages,
566
- newUserMessage: userMessage,
567
- imageAttachmentCount,
568
- };
569
- await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
570
- }
571
484
  function getFinalAssistantText(session) {
572
485
  const lastAssistant = session.messages.findLast((message) => message.role === "assistant");
573
486
  return (lastAssistant?.content
@@ -575,21 +488,6 @@ function getFinalAssistantText(session) {
575
488
  .map((content) => content.text)
576
489
  .join("\n") || "");
577
490
  }
578
- export function appendTriggerAttribution(text, triggerAttribution, sessionLink) {
579
- if (!triggerAttribution)
580
- return text;
581
- const trimmed = text.trimEnd();
582
- const legacySuffix = `_Triggered by ${triggerAttribution}_`;
583
- // Slack mrkdwn italics cannot span a URL — a `_..._` wrapping a link renders
584
- // the underscores literally. Keep the session link outside the italic span.
585
- const suffix = sessionLink ? `${legacySuffix} · session: ${sessionLink}` : legacySuffix;
586
- if (trimmed.endsWith(suffix))
587
- return text;
588
- const body = trimmed.endsWith(legacySuffix)
589
- ? trimmed.slice(0, -legacySuffix.length).trimEnd()
590
- : trimmed;
591
- return `${body}\n\n${suffix}`;
592
- }
593
491
  function isEventTriggerAttribution(triggerAttribution) {
594
492
  return triggerAttribution?.startsWith("[event:") === true;
595
493
  }
@@ -745,6 +643,136 @@ async function reportUsageSummary(ctx) {
745
643
  await waitForQueue();
746
644
  }
747
645
  }
646
+ function extractToolResultText(result) {
647
+ if (typeof result === "string") {
648
+ return result;
649
+ }
650
+ if (result &&
651
+ typeof result === "object" &&
652
+ "content" in result &&
653
+ Array.isArray(result.content)) {
654
+ const content = result.content;
655
+ const textParts = [];
656
+ for (const part of content) {
657
+ if (part.type === "text" && part.text) {
658
+ textParts.push(part.text);
659
+ }
660
+ }
661
+ if (textParts.length > 0) {
662
+ return textParts.join("\n");
663
+ }
664
+ }
665
+ return JSON.stringify(result);
666
+ }
667
+ function buildThreadSessionName(message) {
668
+ const text = message?.text?.trim();
669
+ if (!text)
670
+ return undefined;
671
+ const userLabel = message?.userName || message?.user || "unknown";
672
+ return `[${userLabel}]: ${text}`;
673
+ }
674
+ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
675
+ const executionResolver = vaultManager && sandboxConfig.type !== "host"
676
+ ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)
677
+ : undefined;
678
+ // activeExecutor is replaced at the start of each run() call when executionResolver
679
+ // is present, so the stable `executor` wrapper always delegates to the latest resolved value.
680
+ let activeExecutor = executionResolver !== undefined
681
+ ? createExecutor({ type: "host" })
682
+ : createExecutor(sandboxConfig);
683
+ const executor = {
684
+ exec(command, options) {
685
+ return activeExecutor.exec(command, options);
686
+ },
687
+ getWorkspacePath(hostPath) {
688
+ return activeExecutor.getWorkspacePath(hostPath);
689
+ },
690
+ getSandboxConfig() {
691
+ return activeExecutor.getSandboxConfig();
692
+ },
693
+ getPathContext(hostWorkspaceRoot) {
694
+ return activeExecutor.getPathContext(hostWorkspaceRoot);
695
+ },
696
+ };
697
+ return {
698
+ executionResolver,
699
+ executor,
700
+ getPathContext: () => executor.getPathContext(hostWorkspacePath),
701
+ async resolveExecutorForRun(context) {
702
+ if (!executionResolver)
703
+ return;
704
+ activeExecutor = await executionResolver.resolve(context);
705
+ },
706
+ };
707
+ }
708
+ /**
709
+ * Extension host services over mikan's runtime infrastructure: schedules
710
+ * become event files under `<workspaceDir>/events` (picked up live by
711
+ * EventsWatcher), secrets come from `vaults/extensions/<slug>/env`, and
712
+ * notify posts through the platform bots when main.ts provides a notifier.
713
+ */
714
+ function buildExtensionHostServices(params) {
715
+ const { workspaceDir, vaultManager, platformNotifier, platformReactor } = params;
716
+ const eventStore = HostEventStore.fromWorkspaceDir(workspaceDir);
717
+ return {
718
+ stateDir: readEnv("STATE_DIR"),
719
+ scheduleStore: {
720
+ write: async (filename, payload) => {
721
+ // Event files tolerate a missing platform (single-platform default),
722
+ // so the harness payload is a valid event payload as written.
723
+ await eventStore.write(filename, payload);
724
+ },
725
+ delete: async (filename) => (await eventStore.delete(filename)).deleted,
726
+ list: async () => (await eventStore.list()).map((entry) => ({
727
+ filename: entry.filename,
728
+ payload: entry.payload,
729
+ })),
730
+ },
731
+ ...(platformNotifier ? { postMessage: platformNotifier } : {}),
732
+ ...(platformReactor ? { addReaction: platformReactor } : {}),
733
+ ...(vaultManager
734
+ ? {
735
+ resolveSecrets: (slug) => vaultManager.resolve(`extensions/${slug}`)?.env ?? {},
736
+ }
737
+ : {}),
738
+ };
739
+ }
740
+ async function createConfiguredAgentSession(params) {
741
+ const { conversationId, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, } = params;
742
+ // Host-only dirs under the state dir: extension code runs in the mikan
743
+ // process, so it must never load from workspace paths — those are mounted
744
+ // into sandbox containers and agent-writable (sandbox escape otherwise).
745
+ const extensionsResult = await loadExtensions({
746
+ dirs: defaultExtensionDirs(conversationId, readEnv("STATE_DIR")),
747
+ context: { conversationId, workspaceDir, model, thinkingLevel },
748
+ services: buildExtensionHostServices({
749
+ workspaceDir,
750
+ vaultManager,
751
+ platformNotifier,
752
+ platformReactor,
753
+ }),
754
+ });
755
+ for (const err of extensionsResult.errors) {
756
+ log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
757
+ }
758
+ if (extensionsResult.extensions.length > 0) {
759
+ log.logInfo(`[${conversationId}] Loaded ${extensionsResult.extensions.length} extension(s): ${extensionsResult.extensions.map((extension) => extension.name).join(", ")}`);
760
+ }
761
+ const session = new MikanAgentSession({
762
+ systemPrompt,
763
+ model,
764
+ thinkingLevel,
765
+ tools,
766
+ models,
767
+ sessionStore,
768
+ extensions: extensionsResult.registry,
769
+ });
770
+ const reloaded = session.reloadFromSession();
771
+ if (reloaded > 0) {
772
+ log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from session context`);
773
+ }
774
+ return { session, extensionSkills: extensionsResult.skills };
775
+ }
748
776
  function reloadSessionMessages(session, conversationId) {
749
777
  const reloaded = session.reloadFromSession();
750
778
  if (reloaded > 0) {
@@ -752,7 +780,7 @@ function reloadSessionMessages(session, conversationId) {
752
780
  }
753
781
  }
754
782
  async function prepareRunContext(params) {
755
- const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, setReactFunction, } = params;
783
+ const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, setReactFunction, bindPlatformToolPacks, } = params;
756
784
  let pathContext = params.pathContext;
757
785
  const sessionConversation = message.sessionKey.split(":")[0];
758
786
  await mkdir(join(conversationDir, "scratch"), { recursive: true });
@@ -761,6 +789,7 @@ async function prepareRunContext(params) {
761
789
  platform: platform.name,
762
790
  userId: message.userId,
763
791
  conversationId,
792
+ trustModel: platform.trustModel,
764
793
  });
765
794
  pathContext = getPathContext();
766
795
  }
@@ -794,17 +823,9 @@ async function prepareRunContext(params) {
794
823
  // (interactive turns on platforms with reaction support); otherwise unset
795
824
  // so the tool reports it is unavailable rather than silently no-op'ing.
796
825
  setReactFunction(responder.react ? async (emoji) => responder.react(emoji) : null);
797
- // github_* tools are available only in GitHub conversations with wired
798
- // ops; everywhere else they report themselves unavailable.
799
- const { platformGithubOps, setGithubPrFunction, setGithubChecksFunction } = params;
800
- const githubOps = platform.name === "github" ? platformGithubOps : undefined;
801
- setGithubPrFunction(githubOps ? (request) => githubOps.pushAndCreatePr(conversationId, request) : null);
802
- setGithubChecksFunction(githubOps
803
- ? {
804
- getChecks: (branch) => githubOps.getChecks(conversationId, branch),
805
- getJobLog: (jobId) => githubOps.getJobLog(conversationId, jobId),
806
- }
807
- : null);
826
+ // Platform capability packs (e.g. GitHub PR/CI) enable themselves per run;
827
+ // core does not branch on platform name here.
828
+ bindPlatformToolPacks({ conversationId, platformName: platform.name });
808
829
  resetRunState(runState, responder, sessionConversation, message.userName, sessionUuid, triggerAttribution);
809
830
  const runQueue = createRunQueue(responder, runState);
810
831
  runState.queue = runQueue.queue;
@@ -1092,27 +1113,6 @@ function attachSessionEventHandlers(params) {
1092
1113
  }
1093
1114
  });
1094
1115
  }
1095
- function extractToolResultText(result) {
1096
- if (typeof result === "string") {
1097
- return result;
1098
- }
1099
- if (result &&
1100
- typeof result === "object" &&
1101
- "content" in result &&
1102
- Array.isArray(result.content)) {
1103
- const content = result.content;
1104
- const textParts = [];
1105
- for (const part of content) {
1106
- if (part.type === "text" && part.text) {
1107
- textParts.push(part.text);
1108
- }
1109
- }
1110
- if (textParts.length > 0) {
1111
- return textParts.join("\n");
1112
- }
1113
- }
1114
- return JSON.stringify(result);
1115
- }
1116
1116
  /**
1117
1117
  * Create a new PiAgentWrapper for a channel.
1118
1118
  * Sets up the session and subscribes to events once.
@@ -1120,18 +1120,18 @@ function extractToolResultText(result) {
1120
1120
  * Runner caching is handled by the caller (channelStates in main.ts).
1121
1121
  * This is a stateless factory function.
1122
1122
  */
1123
- export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, sessionView, platformNotifier, platformReactor, platformGithubOps) {
1123
+ export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, sessionView, platformNotifier, platformReactor, platformToolPackFactories) {
1124
1124
  const agentConfig = resolveConversationSettings(conversationDir);
1125
1125
  const workspaceBase = join(conversationDir, "..");
1126
1126
  const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
1127
1127
  let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
1128
1128
  // Create tools (per-runner, with per-runner upload function setter)
1129
- const { tools, setUploadFunction, setReactFunction, setGithubPrFunction, setGithubChecksFunction, setEventContext, setSandboxContext, } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, provisioner });
1129
+ const { tools, setUploadFunction, setReactFunction, bindPlatformToolPacks, setEventContext, setSandboxContext, } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, provisioner }, platformToolPackFactories ?? []);
1130
1130
  const modelRegistry = MikanModels.create();
1131
1131
  if (modelRegistry.getError()) {
1132
1132
  log.logWarning("models.json load error", modelRegistry.getError());
1133
1133
  }
1134
- const model = resolveConfiguredModel(modelRegistry, agentConfig.provider, agentConfig.model);
1134
+ const model = modelRegistry.resolve(agentConfig.provider, agentConfig.model);
1135
1135
  // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
1136
1136
  const memory = await getMemory(conversationDir);
1137
1137
  const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
@@ -1140,6 +1140,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1140
1140
  formattingGuide: "",
1141
1141
  channels: [],
1142
1142
  users: [],
1143
+ trustModel: "membership",
1143
1144
  };
1144
1145
  const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
1145
1146
  // Create session manager and settings manager. Top-level/private sessions
@@ -1198,9 +1199,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1198
1199
  setSandboxContext,
1199
1200
  setUploadFunction,
1200
1201
  setReactFunction,
1201
- setGithubPrFunction,
1202
- setGithubChecksFunction,
1203
- platformGithubOps,
1202
+ bindPlatformToolPacks,
1204
1203
  pathContext,
1205
1204
  });
1206
1205
  pathContext = prepared.pathContext;
@@ -1293,13 +1292,4 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1293
1292
  },
1294
1293
  };
1295
1294
  }
1296
- export function translateRuntimePathToHost(runtimePath, pathContext) {
1297
- return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;
1298
- }
1299
- export function translateAttachPathToHost(filePath, pathContext) {
1300
- const runtimePath = posix.isAbsolute(filePath)
1301
- ? filePath
1302
- : posix.join(pathContext.runtimeWorkspaceRoot, filePath);
1303
- return translateRuntimePathToHost(runtimePath, pathContext);
1304
- }
1305
1295
  //# sourceMappingURL=agent.js.map