@geminixiang/mikan 0.5.2 → 1.0.0-beta.0
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 +38 -0
- package/README.md +3 -3
- package/dist/adapter.d.ts +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/shared.js +2 -2
- package/dist/adapters/shared.js.map +1 -1
- package/dist/agent.d.ts +11 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +211 -119
- package/dist/agent.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +1 -5
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auto-reply.d.ts +3 -0
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +3 -0
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/model.d.ts +2 -3
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +1 -4
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +1 -5
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -5
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/types.d.ts +6 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +42 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +93 -7
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -6
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +9 -6
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/auth.d.ts +16 -0
- package/dist/harness/auth.d.ts.map +1 -0
- package/dist/harness/auth.js +86 -0
- package/dist/harness/auth.js.map +1 -0
- package/dist/harness/extensions/loader.d.ts +43 -0
- package/dist/harness/extensions/loader.d.ts.map +1 -0
- package/dist/harness/extensions/loader.js +325 -0
- package/dist/harness/extensions/loader.js.map +1 -0
- package/dist/harness/extensions/registry.d.ts +23 -0
- package/dist/harness/extensions/registry.d.ts.map +1 -0
- package/dist/harness/extensions/registry.js +45 -0
- package/dist/harness/extensions/registry.js.map +1 -0
- package/dist/harness/extensions/types.d.ts +225 -0
- package/dist/harness/extensions/types.d.ts.map +1 -0
- package/dist/harness/extensions/types.js +2 -0
- package/dist/harness/extensions/types.js.map +1 -0
- package/dist/harness/http.d.ts +16 -0
- package/dist/harness/http.d.ts.map +1 -0
- package/dist/harness/http.js +71 -0
- package/dist/harness/http.js.map +1 -0
- package/dist/harness/index.d.ts +19 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +18 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/models.d.ts +34 -0
- package/dist/harness/models.d.ts.map +1 -0
- package/dist/harness/models.js +248 -0
- package/dist/harness/models.js.map +1 -0
- package/dist/harness/runner.d.ts +124 -0
- package/dist/harness/runner.d.ts.map +1 -0
- package/dist/harness/runner.js +436 -0
- package/dist/harness/runner.js.map +1 -0
- package/dist/harness/session-store.d.ts +77 -0
- package/dist/harness/session-store.d.ts.map +1 -0
- package/dist/harness/session-store.js +248 -0
- package/dist/harness/session-store.js.map +1 -0
- package/dist/harness/settings.d.ts +47 -0
- package/dist/harness/settings.d.ts.map +1 -0
- package/dist/harness/settings.js +29 -0
- package/dist/harness/settings.js.map +1 -0
- package/dist/harness/skills.d.ts +54 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +203 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/types.d.ts +32 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +11 -14
- package/dist/log.js.map +1 -1
- package/dist/main.js +67 -8
- package/dist/main.js.map +1 -1
- package/dist/model-registry.d.ts +2 -2
- package/dist/model-registry.d.ts.map +1 -1
- package/dist/model-registry.js.map +1 -1
- package/dist/provisioner.d.ts +1 -4
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +3 -8
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/agent-run-controller.d.ts.map +1 -1
- package/dist/runtime/agent-run-controller.js +6 -3
- package/dist/runtime/agent-run-controller.js.map +1 -1
- package/dist/runtime/conversation-runtime.js +1 -1
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +4 -3
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/container.d.ts +1 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +2 -2
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +0 -4
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/image.d.ts.map +1 -1
- package/dist/sandbox/image.js +0 -3
- package/dist/sandbox/image.js.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +4 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sessions/agent-memory-file-manager.d.ts.map +1 -1
- package/dist/sessions/agent-memory-file-manager.js +8 -8
- package/dist/sessions/agent-memory-file-manager.js.map +1 -1
- package/dist/sessions/metadata.js +2 -2
- package/dist/sessions/metadata.js.map +1 -1
- package/dist/sessions/store.d.ts +3 -3
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +17 -43
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +1 -1
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +18 -11
- package/dist/store.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +1 -3
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +7 -74
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +1 -3
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +3 -7
- package/dist/tools/write.js.map +1 -1
- package/dist/trigger.d.ts +2 -0
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js +7 -13
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +7 -1
- package/dist/vault/index.js.map +1 -1
- package/dist/web/admin/portal.d.ts +1 -1
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +394 -73
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/provider-models.d.ts +2 -2
- package/dist/web/admin/provider-models.d.ts.map +1 -1
- package/dist/web/admin/provider-models.js +4 -2
- package/dist/web/admin/provider-models.js.map +1 -1
- package/dist/web/admin/store.d.ts.map +1 -1
- package/dist/web/admin/store.js +3 -12
- package/dist/web/admin/store.js.map +1 -1
- package/dist/web/admin/types.d.ts +7 -5
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/login/oauth.d.ts.map +1 -1
- package/dist/web/login/oauth.js +41 -23
- package/dist/web/login/oauth.js.map +1 -1
- package/dist/web/login/store.d.ts +0 -4
- package/dist/web/login/store.d.ts.map +1 -1
- package/dist/web/login/store.js +3 -16
- package/dist/web/login/store.js.map +1 -1
- package/dist/web/server.js +2 -2
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-view/portal.d.ts.map +1 -1
- package/dist/web/session-view/portal.js +5 -16
- package/dist/web/session-view/portal.js.map +1 -1
- package/dist/web/session-view/service.js +7 -13
- package/dist/web/session-view/service.js.map +1 -1
- package/dist/web/session-view/store.d.ts.map +1 -1
- package/dist/web/session-view/store.js +2 -7
- package/dist/web/session-view/store.js.map +1 -1
- package/dist/web/token-store.d.ts +2 -8
- package/dist/web/token-store.d.ts.map +1 -1
- package/dist/web/token-store.js +11 -12
- package/dist/web/token-store.js.map +1 -1
- package/package.json +6 -3
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -2
- package/dist/context.js.map +0 -1
package/dist/agent.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { DEFAULT_EVENT_BUDGET, defaultExtensionDirs, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, } from "./harness/index.js";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
3
|
import { existsSync, readFileSync } from "fs";
|
|
4
4
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5
|
-
import { homedir } from "os";
|
|
6
5
|
import { join, posix } from "path";
|
|
7
6
|
import { resolveConversationSettings } from "./config.js";
|
|
7
|
+
import { readEnv } from "./utils/env.js";
|
|
8
8
|
import { ActorExecutionResolver } from "./execution-resolver.js";
|
|
9
9
|
import * as log from "./log.js";
|
|
10
10
|
import { reportUserFacingError } from "./observability/sentry.js";
|
|
@@ -13,6 +13,7 @@ import { createMountedRuntimePathContext } from "./sandbox/path-context.js";
|
|
|
13
13
|
import { addLifecycleBreadcrumb, metricAttributes, updateActiveSpanAttribution, } from "./observability/sentry.js";
|
|
14
14
|
import { AgentMemoryFileManager } from "./sessions/agent-memory-file-manager.js";
|
|
15
15
|
import { extractSessionUuid, openManagedSession, } from "./sessions/store.js";
|
|
16
|
+
import { HostEventStore } from "./tools/event.js";
|
|
16
17
|
import { createMikanTools } from "./tools/index.js";
|
|
17
18
|
import * as Sentry from "@sentry/node";
|
|
18
19
|
import { formatLocalTimestamp } from "./utils/date.js";
|
|
@@ -151,7 +152,7 @@ export function resolveTriggerAttribution(message) {
|
|
|
151
152
|
return `@${message.userName}`;
|
|
152
153
|
return undefined;
|
|
153
154
|
}
|
|
154
|
-
function buildSystemPrompt(workspacePath, conversationId, conversationKind, currentUserId, memory, sandboxConfig, platform, skills
|
|
155
|
+
function buildSystemPrompt(workspacePath, conversationId, conversationKind, currentUserId, memory, sandboxConfig, platform, skills) {
|
|
155
156
|
const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, conversationId);
|
|
156
157
|
const sandboxType = sandboxConfig.type;
|
|
157
158
|
const isContainerLike = sandboxType === "container" || sandboxType === "image";
|
|
@@ -165,25 +166,11 @@ function buildSystemPrompt(workspacePath, conversationId, conversationKind, curr
|
|
|
165
166
|
? platform.users.map((u) => `${u.id}\t@${u.userName}\t${u.displayName}`).join("\n")
|
|
166
167
|
: "(no users loaded)";
|
|
167
168
|
const envDescription = buildEnvDescription(sandboxType, workspaceRoot);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- Complete the task directly. Avoid generic greetings, self-introductions, or boilerplate offers to help.
|
|
174
|
-
- For reminders/follow-ups, prefer a short direct response that sounds like a continuation of prior intent.
|
|
175
|
-
- If the event text includes tone, brevity, or language instructions, follow them literally.
|
|
176
|
-
`
|
|
177
|
-
: "";
|
|
178
|
-
const attributionInstructions = triggerAttribution
|
|
179
|
-
? `
|
|
180
|
-
## Attribution
|
|
181
|
-
Always end your final ${platform.name} response and any GitHub issue/PR comments or descriptions you write via tools with:
|
|
182
|
-
_Triggered by ${triggerAttribution}_
|
|
183
|
-
|
|
184
|
-
Do not add this to \`[SILENT]\` responses.
|
|
185
|
-
`
|
|
186
|
-
: "";
|
|
169
|
+
// Per-turn instructions (event-trigger mode, attribution) are delivered with
|
|
170
|
+
// the user message via buildTurnInstructions(), not baked in here, so this
|
|
171
|
+
// system prompt stays byte-stable across a conversation's turns and keeps the
|
|
172
|
+
// provider prompt cache warm (a changing system prefix invalidates the cache
|
|
173
|
+
// for the whole request, including the far larger conversation history).
|
|
187
174
|
const slackBlockKitInstructions = platform.name === "slack"
|
|
188
175
|
? `
|
|
189
176
|
## Slack Rendering
|
|
@@ -202,7 +189,6 @@ Do not add this to \`[SILENT]\` responses.
|
|
|
202
189
|
- Scoped/thread sessions use fixed files at \`${conversationPath}/sessions/<scope_id>.jsonl\` (for example \`${conversationPath}/sessions/1777386320.800769.jsonl\`).
|
|
203
190
|
- If a user asks about something that should exist in conversation history but is not found in the current context window, do not answer "I don't know" or "I don't have that". Instead, search the thread session, top-level session, and \`log.jsonl\` before responding.
|
|
204
191
|
- User messages include a \`[in-thread:TS]\` marker when sent from within a platform thread/reply (TS is the thread or parent message identifier). Without this marker, the message is a top-level conversation message.
|
|
205
|
-
${eventTriggerInstructions}
|
|
206
192
|
${platform.formattingGuide}${slackBlockKitInstructions}
|
|
207
193
|
|
|
208
194
|
## Platform IDs
|
|
@@ -290,7 +276,6 @@ Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","i
|
|
|
290
276
|
The log contains user messages and your final responses (not tool calls/results).
|
|
291
277
|
Use \`log.jsonl\` for quick grep-style history. Use \`${conversationPath}/sessions/\` when you need structured turns, tool outputs, or thread/session lineage.
|
|
292
278
|
${isContainerLike || isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
293
|
-
${attributionInstructions}
|
|
294
279
|
\`\`\`bash
|
|
295
280
|
# Recent messages
|
|
296
281
|
tail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'
|
|
@@ -318,6 +303,32 @@ ls -1 sessions/
|
|
|
318
303
|
Each tool requires a "label" parameter (shown to user).
|
|
319
304
|
`;
|
|
320
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Instructions that vary per turn (event-trigger mode, response attribution).
|
|
308
|
+
* These are delivered with the user message rather than baked into the system
|
|
309
|
+
* prompt, so the system prompt stays cache-stable across a conversation's turns
|
|
310
|
+
* (in a multi-user channel the attribution line alone changes every turn).
|
|
311
|
+
* Returns an empty string when the turn needs no special framing.
|
|
312
|
+
*/
|
|
313
|
+
export function buildTurnInstructions(isEventTrigger, triggerAttribution, platformName) {
|
|
314
|
+
const parts = [];
|
|
315
|
+
if (isEventTrigger) {
|
|
316
|
+
parts.push(`## Event Trigger Mode
|
|
317
|
+
- You are handling a scheduled/background event, not opening a brand new chat with a stranger.
|
|
318
|
+
- Treat the incoming user message as a self-contained task prepared by an earlier run.
|
|
319
|
+
- Complete the task directly. Avoid generic greetings, self-introductions, or boilerplate offers to help.
|
|
320
|
+
- For reminders/follow-ups, prefer a short direct response that sounds like a continuation of prior intent.
|
|
321
|
+
- If the event text includes tone, brevity, or language instructions, follow them literally.`);
|
|
322
|
+
}
|
|
323
|
+
if (triggerAttribution) {
|
|
324
|
+
parts.push(`## Attribution
|
|
325
|
+
Always end your final ${platformName} response and any GitHub issue/PR comments or descriptions you write via tools with:
|
|
326
|
+
_Triggered by ${triggerAttribution}_
|
|
327
|
+
|
|
328
|
+
Do not add this to \`[SILENT]\` responses.`);
|
|
329
|
+
}
|
|
330
|
+
return parts.join("\n\n");
|
|
331
|
+
}
|
|
321
332
|
export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot) {
|
|
322
333
|
if (sandboxConfig.type === "image") {
|
|
323
334
|
return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
|
|
@@ -325,13 +336,7 @@ export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot
|
|
|
325
336
|
return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);
|
|
326
337
|
}
|
|
327
338
|
function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
|
|
328
|
-
const executionResolver = vaultManager &&
|
|
329
|
-
sandboxConfig.type !== "host" &&
|
|
330
|
-
(vaultManager.isEnabled() ||
|
|
331
|
-
sandboxConfig.type === "container" ||
|
|
332
|
-
sandboxConfig.type === "image" ||
|
|
333
|
-
sandboxConfig.type === "cloudflare" ||
|
|
334
|
-
sandboxConfig.type === "firecracker")
|
|
339
|
+
const executionResolver = vaultManager && sandboxConfig.type !== "host"
|
|
335
340
|
? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)
|
|
336
341
|
: undefined;
|
|
337
342
|
// activeExecutor is replaced at the start of each run() call when executionResolver
|
|
@@ -364,58 +369,81 @@ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner,
|
|
|
364
369
|
},
|
|
365
370
|
};
|
|
366
371
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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 } = 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
|
+
})),
|
|
375
394
|
},
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
throw new Error(`No API key for provider "${provider}". Set the appropriate environment variable or configure via auth.json`);
|
|
395
|
+
...(platformNotifier ? { postMessage: platformNotifier } : {}),
|
|
396
|
+
...(vaultManager
|
|
397
|
+
? {
|
|
398
|
+
resolveSecrets: (slug) => vaultManager.resolve(`extensions/${slug}`)?.env ?? {},
|
|
381
399
|
}
|
|
382
|
-
|
|
383
|
-
|
|
400
|
+
: {}),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Local (workspace/conversation) skills override extension skills on name
|
|
405
|
+
* collision: an admin's on-disk skill always beats an extension's.
|
|
406
|
+
*/
|
|
407
|
+
function mergeExtensionSkills(local, extension) {
|
|
408
|
+
if (extension.length === 0)
|
|
409
|
+
return local;
|
|
410
|
+
const byName = new Map();
|
|
411
|
+
for (const skill of extension)
|
|
412
|
+
byName.set(skill.name, skill);
|
|
413
|
+
for (const skill of local)
|
|
414
|
+
byName.set(skill.name, skill);
|
|
415
|
+
return [...byName.values()];
|
|
416
|
+
}
|
|
417
|
+
async function createConfiguredAgentSession(params) {
|
|
418
|
+
const { conversationId, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, } = params;
|
|
419
|
+
// Host-only dirs under the state dir: extension code runs in the mikan
|
|
420
|
+
// process, so it must never load from workspace paths — those are mounted
|
|
421
|
+
// into sandbox containers and agent-writable (sandbox escape otherwise).
|
|
422
|
+
const extensionsResult = await loadExtensions({
|
|
423
|
+
dirs: defaultExtensionDirs(conversationId, readEnv("STATE_DIR")),
|
|
424
|
+
context: { conversationId, workspaceDir, model, thinkingLevel },
|
|
425
|
+
services: buildExtensionHostServices({ workspaceDir, vaultManager, platformNotifier }),
|
|
384
426
|
});
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
agent.state.messages = loadedSession.messages;
|
|
388
|
-
log.logInfo(`[${conversationId}] Reloaded ${loadedSession.messages.length} messages from session context`);
|
|
427
|
+
for (const err of extensionsResult.errors) {
|
|
428
|
+
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
389
429
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
if (extensionsResult.extensions.length > 0) {
|
|
431
|
+
log.logInfo(`[${conversationId}] Loaded ${extensionsResult.extensions.length} extension(s): ${extensionsResult.extensions.map((extension) => extension.name).join(", ")}`);
|
|
432
|
+
}
|
|
433
|
+
const session = new MikanAgentSession({
|
|
393
434
|
systemPrompt,
|
|
435
|
+
model,
|
|
436
|
+
thinkingLevel,
|
|
437
|
+
tools,
|
|
438
|
+
models,
|
|
439
|
+
sessionStore,
|
|
440
|
+
extensions: extensionsResult.registry,
|
|
394
441
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (extResult.errors.length > 0) {
|
|
399
|
-
for (const err of extResult.errors) {
|
|
400
|
-
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
log.logInfo(`[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((extension) => extension.path).join(", ")}`);
|
|
404
|
-
}
|
|
405
|
-
catch (error) {
|
|
406
|
-
log.logWarning(`[${conversationId}] Failed to load resources`, String(error));
|
|
442
|
+
const reloaded = session.reloadFromSession();
|
|
443
|
+
if (reloaded > 0) {
|
|
444
|
+
log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from session context`);
|
|
407
445
|
}
|
|
408
|
-
|
|
409
|
-
const session = new AgentSession({
|
|
410
|
-
agent,
|
|
411
|
-
sessionManager,
|
|
412
|
-
settingsManager,
|
|
413
|
-
cwd: runtimeWorkspaceRoot,
|
|
414
|
-
modelRegistry,
|
|
415
|
-
resourceLoader,
|
|
416
|
-
baseToolsOverride,
|
|
417
|
-
});
|
|
418
|
-
return { agent, session };
|
|
446
|
+
return { session, extensionSkills: extensionsResult.skills };
|
|
419
447
|
}
|
|
420
448
|
function createEmptyUsageTotals() {
|
|
421
449
|
return {
|
|
@@ -541,13 +569,23 @@ function getFinalAssistantText(session) {
|
|
|
541
569
|
.map((content) => content.text)
|
|
542
570
|
.join("\n") || "");
|
|
543
571
|
}
|
|
544
|
-
export function appendTriggerAttribution(text, triggerAttribution) {
|
|
572
|
+
export function appendTriggerAttribution(text, triggerAttribution, sessionLink) {
|
|
545
573
|
if (!triggerAttribution)
|
|
546
574
|
return text;
|
|
547
|
-
const
|
|
548
|
-
|
|
575
|
+
const trimmed = text.trimEnd();
|
|
576
|
+
const legacySuffix = `_Triggered by ${triggerAttribution}_`;
|
|
577
|
+
const suffix = sessionLink
|
|
578
|
+
? `_Triggered by ${triggerAttribution} · session: ${sessionLink}_`
|
|
579
|
+
: legacySuffix;
|
|
580
|
+
if (trimmed.endsWith(suffix))
|
|
549
581
|
return text;
|
|
550
|
-
|
|
582
|
+
const body = trimmed.endsWith(legacySuffix)
|
|
583
|
+
? trimmed.slice(0, -legacySuffix.length).trimEnd()
|
|
584
|
+
: trimmed;
|
|
585
|
+
return `${body}\n\n${suffix}`;
|
|
586
|
+
}
|
|
587
|
+
function isEventTriggerAttribution(triggerAttribution) {
|
|
588
|
+
return triggerAttribution?.startsWith("[event:") === true;
|
|
551
589
|
}
|
|
552
590
|
function extractToolLabel(toolName, args) {
|
|
553
591
|
const label = args?.label;
|
|
@@ -635,7 +673,7 @@ async function finalizeRunResponse(responder, session, runState, options) {
|
|
|
635
673
|
if (!finalText.trim())
|
|
636
674
|
return;
|
|
637
675
|
try {
|
|
638
|
-
await responder.replaceResponse(appendTriggerAttribution(formatResponseWithToolProgress(finalText, runState), options?.triggerAttribution), { createOverflowLink: options?.createOverflowLink });
|
|
676
|
+
await responder.replaceResponse(appendTriggerAttribution(formatResponseWithToolProgress(finalText, runState), options?.triggerAttribution, options?.triggerSessionLink), { createOverflowLink: options?.createOverflowLink });
|
|
639
677
|
}
|
|
640
678
|
catch (err) {
|
|
641
679
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -701,15 +739,14 @@ async function reportUsageSummary(ctx) {
|
|
|
701
739
|
await waitForQueue();
|
|
702
740
|
}
|
|
703
741
|
}
|
|
704
|
-
function reloadSessionMessages(
|
|
705
|
-
const
|
|
706
|
-
if (
|
|
707
|
-
|
|
708
|
-
log.logInfo(`[${conversationId}] Reloaded ${messages.length} messages from context`);
|
|
742
|
+
function reloadSessionMessages(session, conversationId) {
|
|
743
|
+
const reloaded = session.reloadFromSession();
|
|
744
|
+
if (reloaded > 0) {
|
|
745
|
+
log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from context`);
|
|
709
746
|
}
|
|
710
747
|
}
|
|
711
748
|
async function prepareRunContext(params) {
|
|
712
|
-
const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext,
|
|
749
|
+
const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, } = params;
|
|
713
750
|
let pathContext = params.pathContext;
|
|
714
751
|
const sessionConversation = message.sessionKey.split(":")[0];
|
|
715
752
|
await mkdir(join(conversationDir, "scratch"), { recursive: true });
|
|
@@ -721,12 +758,19 @@ async function prepareRunContext(params) {
|
|
|
721
758
|
});
|
|
722
759
|
pathContext = getPathContext();
|
|
723
760
|
}
|
|
724
|
-
reloadSessionMessages(
|
|
761
|
+
reloadSessionMessages(session, conversationId);
|
|
725
762
|
const memory = await getMemory(conversationDir);
|
|
726
|
-
const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
|
|
763
|
+
const skills = mergeExtensionSkills(loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot), params.extensionSkills ?? []);
|
|
727
764
|
const triggerAttribution = resolveTriggerAttribution(message);
|
|
728
|
-
const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills
|
|
765
|
+
const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills);
|
|
729
766
|
session.agent.state.systemPrompt = systemPrompt;
|
|
767
|
+
// Cache diagnosis: a byte-stable system prompt is the precondition for
|
|
768
|
+
// provider prompt caching. If this hash changes between turns of one
|
|
769
|
+
// conversation, something turn-varying leaked into the prompt; if it is
|
|
770
|
+
// stable and cacheRead stays 0, the miss is provider-side (e.g. OpenRouter
|
|
771
|
+
// routing the model across upstream hosts).
|
|
772
|
+
const promptHash = createHash("sha256").update(systemPrompt).digest("hex").slice(0, 8);
|
|
773
|
+
log.logInfo(`[${conversationId}] System prompt: ${systemPrompt.length} chars, sha ${promptHash}`);
|
|
730
774
|
setEventContext({
|
|
731
775
|
platform: platform.name,
|
|
732
776
|
conversationId,
|
|
@@ -744,11 +788,13 @@ async function prepareRunContext(params) {
|
|
|
744
788
|
log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
|
|
745
789
|
log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);
|
|
746
790
|
const { userMessage, imageAttachments } = buildPromptPayload(message, pathContext.runtimeWorkspaceRoot, pathContext);
|
|
747
|
-
|
|
791
|
+
const turnInstructions = buildTurnInstructions(message.id.startsWith("event:"), triggerAttribution, platform.name);
|
|
792
|
+
const finalUserMessage = turnInstructions ? `${turnInstructions}\n\n${userMessage}` : userMessage;
|
|
793
|
+
await writePromptDebugContext(conversationDir, systemPrompt, session, finalUserMessage, imageAttachments.length);
|
|
748
794
|
return {
|
|
749
795
|
sessionConversation,
|
|
750
796
|
runQueue,
|
|
751
|
-
userMessage,
|
|
797
|
+
userMessage: finalUserMessage,
|
|
752
798
|
imageAttachments,
|
|
753
799
|
triggerAttribution,
|
|
754
800
|
pathContext,
|
|
@@ -760,6 +806,7 @@ function formatAgentActorName(userName, fallback) {
|
|
|
760
806
|
function sendAgentEvent(payload) {
|
|
761
807
|
emitAgentEvent({ source: "mikan", ...payload });
|
|
762
808
|
}
|
|
809
|
+
// ponytail: additive SSE mirror only; keep responder rendering here until another frontend needs the same stream.
|
|
763
810
|
function attachSessionEventHandlers(params) {
|
|
764
811
|
const { session, runState, model, agentConfig } = params;
|
|
765
812
|
session.subscribe(async (event) => {
|
|
@@ -860,12 +907,17 @@ function attachSessionEventHandlers(params) {
|
|
|
860
907
|
}
|
|
861
908
|
if (event.type === "message_update") {
|
|
862
909
|
const assistantMessageEvent = event.assistantMessageEvent;
|
|
863
|
-
if (assistantMessageEvent?.type === "text_delta" &&
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
}
|
|
910
|
+
if (assistantMessageEvent?.type === "text_delta" && assistantMessageEvent.delta) {
|
|
911
|
+
sendAgentEvent({
|
|
912
|
+
sessionId: agentEventSessionId,
|
|
913
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
914
|
+
event: { kind: "responseDelta", delta: assistantMessageEvent.delta },
|
|
915
|
+
});
|
|
916
|
+
if (responder.appendResponseDelta) {
|
|
917
|
+
queue.enqueue(async () => {
|
|
918
|
+
await responder.appendResponseDelta?.(assistantMessageEvent.delta ?? "");
|
|
919
|
+
}, "response delta");
|
|
920
|
+
}
|
|
869
921
|
}
|
|
870
922
|
return;
|
|
871
923
|
}
|
|
@@ -948,6 +1000,11 @@ function attachSessionEventHandlers(params) {
|
|
|
948
1000
|
return;
|
|
949
1001
|
const finalText = appendTriggerAttribution(formatResponseWithToolProgress(text, runState), runState.triggerAttribution);
|
|
950
1002
|
log.logResponse(logCtx, text);
|
|
1003
|
+
sendAgentEvent({
|
|
1004
|
+
sessionId: agentEventSessionId,
|
|
1005
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
1006
|
+
event: { kind: "responseFinal", text: finalText },
|
|
1007
|
+
});
|
|
951
1008
|
sendAgentEvent({
|
|
952
1009
|
sessionId: agentEventSessionId,
|
|
953
1010
|
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
@@ -966,8 +1023,14 @@ function attachSessionEventHandlers(params) {
|
|
|
966
1023
|
return;
|
|
967
1024
|
}
|
|
968
1025
|
if (event.type === "compaction_start") {
|
|
1026
|
+
const text = "_Compacting context..._";
|
|
969
1027
|
log.logInfo(`Auto-compaction started (reason: ${event.reason})`);
|
|
970
|
-
|
|
1028
|
+
sendAgentEvent({
|
|
1029
|
+
sessionId: agentEventSessionId,
|
|
1030
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
1031
|
+
event: { kind: "diagnostic", text },
|
|
1032
|
+
});
|
|
1033
|
+
queue.enqueue(() => responder.respond(text), "compaction start");
|
|
971
1034
|
return;
|
|
972
1035
|
}
|
|
973
1036
|
if (event.type === "compaction_end") {
|
|
@@ -986,7 +1049,23 @@ function attachSessionEventHandlers(params) {
|
|
|
986
1049
|
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
987
1050
|
event: { kind: "sessionStart" },
|
|
988
1051
|
});
|
|
989
|
-
|
|
1052
|
+
const text = `_Retrying (${event.attempt}/${event.maxAttempts})..._`;
|
|
1053
|
+
sendAgentEvent({
|
|
1054
|
+
sessionId: agentEventSessionId,
|
|
1055
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
1056
|
+
event: { kind: "diagnostic", text },
|
|
1057
|
+
});
|
|
1058
|
+
queue.enqueue(() => responder.respond(text), "retry");
|
|
1059
|
+
}
|
|
1060
|
+
if (event.type === "budget_exceeded") {
|
|
1061
|
+
log.logWarning("Run stopped by budget circuit breaker", `${event.reason} (tokens=${event.tokens}, cost=${event.costUsd.toFixed(2)}, calls=${event.llmCalls}, ${event.durationMs}ms)`);
|
|
1062
|
+
const text = `_Stopped: run budget exceeded (${event.reason})_`;
|
|
1063
|
+
sendAgentEvent({
|
|
1064
|
+
sessionId: agentEventSessionId,
|
|
1065
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
1066
|
+
event: { kind: "diagnostic", text },
|
|
1067
|
+
});
|
|
1068
|
+
queue.enqueue(() => responder.respondDiagnostic(text, { style: "error" }), "budget exceeded");
|
|
990
1069
|
}
|
|
991
1070
|
});
|
|
992
1071
|
}
|
|
@@ -1018,15 +1097,17 @@ function extractToolResultText(result) {
|
|
|
1018
1097
|
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
1019
1098
|
* This is a stateless factory function.
|
|
1020
1099
|
*/
|
|
1021
|
-
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, sessionView) {
|
|
1100
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, sessionView, platformNotifier) {
|
|
1022
1101
|
const agentConfig = resolveConversationSettings(conversationDir);
|
|
1023
1102
|
const workspaceBase = join(conversationDir, "..");
|
|
1024
1103
|
const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
|
|
1025
1104
|
let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
|
|
1026
1105
|
// Create tools (per-runner, with per-runner upload function setter)
|
|
1027
1106
|
const { tools, setUploadFunction, setEventContext, setSandboxContext } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, provisioner });
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1107
|
+
const modelRegistry = MikanModels.create();
|
|
1108
|
+
if (modelRegistry.getError()) {
|
|
1109
|
+
log.logWarning("models.json load error", modelRegistry.getError());
|
|
1110
|
+
}
|
|
1030
1111
|
const model = resolveConfiguredModel(modelRegistry, agentConfig.provider, agentConfig.model);
|
|
1031
1112
|
// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
|
|
1032
1113
|
const memory = await getMemory(conversationDir);
|
|
@@ -1042,26 +1123,25 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1042
1123
|
// use the conversation's current pointer; scoped sessions use fixed files.
|
|
1043
1124
|
// Platform-specific scope behavior is resolved before runner creation.
|
|
1044
1125
|
const isThread = sessionKey.includes(":");
|
|
1045
|
-
const {
|
|
1046
|
-
const sessionManager = openManagedSession(contextFile,
|
|
1126
|
+
const { contextFile, threadRootMessage } = sessionScope;
|
|
1127
|
+
const sessionManager = openManagedSession(contextFile, pathContext.runtimeWorkspaceRoot);
|
|
1047
1128
|
const threadSessionName = buildThreadSessionName(threadRootMessage);
|
|
1048
1129
|
if (isThread && threadSessionName && sessionManager.getSessionName() !== threadSessionName) {
|
|
1049
1130
|
sessionManager.appendSessionInfo(threadSessionName);
|
|
1050
1131
|
}
|
|
1051
1132
|
const sessionUuid = extractSessionUuid(contextFile);
|
|
1052
1133
|
const chatSessionManager = new AgentMemoryFileManager();
|
|
1053
|
-
const
|
|
1054
|
-
const { agent, session } = await createConfiguredAgentSession({
|
|
1134
|
+
const { session, extensionSkills } = await createConfiguredAgentSession({
|
|
1055
1135
|
conversationId,
|
|
1056
1136
|
workspaceDir,
|
|
1057
|
-
runtimeWorkspaceRoot: pathContext.runtimeWorkspaceRoot,
|
|
1058
1137
|
systemPrompt,
|
|
1059
1138
|
model,
|
|
1060
1139
|
thinkingLevel: agentConfig.thinkingLevel,
|
|
1061
1140
|
tools,
|
|
1062
|
-
sessionManager,
|
|
1063
|
-
|
|
1064
|
-
|
|
1141
|
+
sessionStore: sessionManager,
|
|
1142
|
+
models: modelRegistry,
|
|
1143
|
+
vaultManager,
|
|
1144
|
+
platformNotifier,
|
|
1065
1145
|
});
|
|
1066
1146
|
// Mutable per-run state - event handler references this
|
|
1067
1147
|
const runState = createRunState();
|
|
@@ -1088,9 +1168,8 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1088
1168
|
executionResolver,
|
|
1089
1169
|
resolveExecutorForRun,
|
|
1090
1170
|
getPathContext,
|
|
1091
|
-
sessionManager,
|
|
1092
1171
|
session,
|
|
1093
|
-
|
|
1172
|
+
extensionSkills,
|
|
1094
1173
|
setEventContext,
|
|
1095
1174
|
setSandboxContext,
|
|
1096
1175
|
setUploadFunction,
|
|
@@ -1119,20 +1198,33 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1119
1198
|
actorName: formatAgentActorName(message.userName, prepared.sessionConversation),
|
|
1120
1199
|
event: { kind: "sessionStart" },
|
|
1121
1200
|
});
|
|
1122
|
-
|
|
1201
|
+
// Autonomous (event/trigger) runs get an explicit resource ceiling since
|
|
1202
|
+
// no human is watching the loop; interactive turns stay human-gated.
|
|
1203
|
+
const isEventRun = message.id.startsWith("event:");
|
|
1204
|
+
await session.prompt(prepared.userMessage, {
|
|
1205
|
+
...(prepared.imageAttachments.length > 0 ? { images: prepared.imageAttachments } : {}),
|
|
1206
|
+
...(isEventRun ? { budget: DEFAULT_EVENT_BUDGET } : {}),
|
|
1207
|
+
});
|
|
1123
1208
|
// Wait for queued messages
|
|
1124
1209
|
await prepared.runQueue.wait();
|
|
1125
1210
|
const sessionViewTokenStore = sessionView?.tokenStore;
|
|
1126
1211
|
const sessionViewPortalBaseUrl = sessionView?.portalBaseUrl;
|
|
1127
|
-
|
|
1212
|
+
let sessionViewLink;
|
|
1213
|
+
const createSessionViewLink = sessionViewTokenStore && sessionViewPortalBaseUrl
|
|
1128
1214
|
? () => {
|
|
1129
|
-
|
|
1130
|
-
|
|
1215
|
+
if (!sessionViewLink) {
|
|
1216
|
+
const token = sessionViewTokenStore.create(platform.name, message.userId, conversationId, message.sessionKey, contextFile, message.userName);
|
|
1217
|
+
sessionViewLink = `${sessionViewPortalBaseUrl}/session?token=${token.token}`;
|
|
1218
|
+
}
|
|
1219
|
+
return sessionViewLink;
|
|
1131
1220
|
}
|
|
1132
1221
|
: undefined;
|
|
1133
1222
|
await finalizeRunResponse(responder, session, runState, {
|
|
1134
1223
|
triggerAttribution: prepared.triggerAttribution,
|
|
1135
|
-
|
|
1224
|
+
triggerSessionLink: isEventTriggerAttribution(prepared.triggerAttribution)
|
|
1225
|
+
? createSessionViewLink?.()
|
|
1226
|
+
: undefined,
|
|
1227
|
+
createOverflowLink: createSessionViewLink,
|
|
1136
1228
|
platform: platform.name,
|
|
1137
1229
|
model,
|
|
1138
1230
|
sessionConversation: prepared.sessionConversation,
|