@geminixiang/mikan 0.4.8 → 0.5.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 +19 -0
- package/README.md +13 -13
- 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/discord/bot.d.ts +9 -6
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +22 -22
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +6 -6
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +3 -3
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/types.d.ts +2 -2
- package/dist/adapters/discord/types.d.ts.map +1 -1
- package/dist/adapters/discord/types.js.map +1 -1
- package/dist/adapters/intake.d.ts +2 -2
- package/dist/adapters/intake.d.ts.map +1 -1
- package/dist/adapters/intake.js +4 -4
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/shared.d.ts +3 -3
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +2 -2
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +10 -7
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +46 -46
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +6 -6
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +2 -2
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts +6 -6
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +3 -3
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/block-kit.d.ts.map +1 -1
- package/dist/adapters/slack/types.d.ts +1 -1
- package/dist/adapters/slack/types.d.ts.map +1 -1
- package/dist/adapters/slack/types.js.map +1 -1
- package/dist/adapters/streaming.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +10 -7
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +21 -21
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +6 -6
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +3 -3
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/html.d.ts.map +1 -1
- package/dist/adapters/telegram/types.d.ts +2 -2
- package/dist/adapters/telegram/types.d.ts.map +1 -1
- package/dist/adapters/telegram/types.js.map +1 -1
- package/dist/adapters/types.d.ts +6 -6
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent-events.d.ts +5 -0
- package/dist/agent-events.d.ts.map +1 -0
- package/dist/agent-events.js +26 -0
- package/dist/agent-events.js.map +1 -0
- package/dist/agent.d.ts +7 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +84 -41
- package/dist/agent.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +1 -1
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +3 -3
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +5 -5
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +2 -2
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/parse.d.ts.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +5 -5
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +1 -1
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +5 -5
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/utils.d.ts +3 -3
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +6 -6
- package/dist/commands/utils.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/download.d.ts.map +1 -1
- package/dist/events.d.ts +2 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +35 -27
- package/dist/main.js.map +1 -1
- package/dist/model-registry.d.ts.map +1 -1
- package/dist/observability/instrument.d.ts.map +1 -1
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +27 -9
- package/dist/observability/sentry.js.map +1 -1
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/platform-messages.d.ts +2 -2
- package/dist/platform-messages.d.ts.map +1 -1
- package/dist/platform-messages.js +2 -2
- package/dist/platform-messages.js.map +1 -1
- package/dist/portal-shell.d.ts.map +1 -1
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/runtime/{conversation-orchestrator.d.ts → agent-run-controller.d.ts} +5 -5
- package/dist/runtime/agent-run-controller.d.ts.map +1 -0
- package/dist/runtime/{conversation-orchestrator.js → agent-run-controller.js} +17 -17
- package/dist/runtime/agent-run-controller.js.map +1 -0
- package/dist/runtime/conversation-runtime.d.ts +4 -0
- package/dist/runtime/conversation-runtime.d.ts.map +1 -0
- package/dist/runtime/{session-runtime.js → conversation-runtime.js} +12 -12
- package/dist/runtime/conversation-runtime.js.map +1 -0
- package/dist/runtime/types.d.ts +9 -9
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/errors.d.ts.map +1 -1
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/image.d.ts.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/path-context.d.ts.map +1 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sessions/{chat-session-manager.d.ts → agent-memory-file-manager.d.ts} +6 -6
- package/dist/sessions/agent-memory-file-manager.d.ts.map +1 -0
- package/dist/sessions/{chat-session-manager.js → agent-memory-file-manager.js} +16 -16
- package/dist/sessions/agent-memory-file-manager.js.map +1 -0
- package/dist/sessions/metadata.d.ts.map +1 -1
- package/dist/sessions/policy.d.ts.map +1 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +4 -4
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +3 -3
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -1
- package/dist/store.js.map +1 -1
- package/dist/tool-diagnostics.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/truncate.d.ts.map +1 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/trigger.d.ts +2 -2
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js +1 -1
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +47 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/date.d.ts +1 -1
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +1 -1
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/file-guards.d.ts.map +1 -1
- package/dist/utils/fs-atomic.d.ts.map +1 -1
- package/dist/utils/html.d.ts.map +1 -1
- package/dist/utils/http-body.d.ts.map +1 -1
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/routing.d.ts.map +1 -1
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +1 -1
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/provider-models.d.ts.map +1 -1
- package/dist/web/admin/store.d.ts.map +1 -1
- package/dist/web/admin/types.d.ts +1 -1
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/agent-events/portal.d.ts +3 -0
- package/dist/web/agent-events/portal.d.ts.map +1 -0
- package/dist/web/agent-events/portal.js +18 -0
- package/dist/web/agent-events/portal.js.map +1 -0
- package/dist/web/login/oauth.d.ts.map +1 -1
- package/dist/web/login/oauth.js.map +1 -1
- package/dist/web/login/portal.d.ts +2 -14
- package/dist/web/login/portal.d.ts.map +1 -1
- package/dist/web/login/portal.js +53 -130
- package/dist/web/login/portal.js.map +1 -1
- package/dist/web/login/store.d.ts.map +1 -1
- package/dist/web/login/types.d.ts.map +1 -1
- package/dist/web/request.d.ts +3 -0
- package/dist/web/request.d.ts.map +1 -0
- package/dist/web/request.js +13 -0
- package/dist/web/request.js.map +1 -0
- package/dist/web/server.d.ts +28 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +66 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/session-view/command.d.ts.map +1 -1
- package/dist/web/session-view/portal.d.ts.map +1 -1
- package/dist/web/session-view/portal.js +8 -8
- package/dist/web/session-view/portal.js.map +1 -1
- package/dist/web/session-view/service.d.ts.map +1 -1
- package/dist/web/session-view/store.d.ts.map +1 -1
- package/dist/web/session-view/types.d.ts +4 -4
- package/dist/web/session-view/types.d.ts.map +1 -1
- package/dist/web/session-view/types.js.map +1 -1
- package/dist/web/token-store.d.ts.map +1 -1
- package/dist/web/types.d.ts.map +1 -1
- package/package.json +44 -20
- package/dist/runtime/conversation-orchestrator.d.ts.map +0 -1
- package/dist/runtime/conversation-orchestrator.js.map +0 -1
- package/dist/runtime/session-runtime.d.ts +0 -4
- package/dist/runtime/session-runtime.d.ts.map +0 -1
- package/dist/runtime/session-runtime.js.map +0 -1
- package/dist/sessions/chat-session-manager.d.ts.map +0 -1
- package/dist/sessions/chat-session-manager.js.map +0 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const clients = new Set();
|
|
2
|
+
let nextEventId = 1;
|
|
3
|
+
export function emitAgentEvent(event) {
|
|
4
|
+
const payload = `id: ${nextEventId++}\nevent: agent\ndata: ${JSON.stringify(event)}\n\n`;
|
|
5
|
+
for (const client of clients) {
|
|
6
|
+
client.write(payload);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function addAgentEventStreamClient(res) {
|
|
10
|
+
clients.add(res);
|
|
11
|
+
res.writeHead(200, {
|
|
12
|
+
"Content-Type": "text/event-stream",
|
|
13
|
+
"Cache-Control": "no-cache, no-transform",
|
|
14
|
+
Connection: "keep-alive",
|
|
15
|
+
"X-Accel-Buffering": "no",
|
|
16
|
+
});
|
|
17
|
+
res.write(`: connected\n\n`);
|
|
18
|
+
const keepAlive = setInterval(() => res.write(`: keepalive\n\n`), 25_000);
|
|
19
|
+
keepAlive.unref();
|
|
20
|
+
return () => {
|
|
21
|
+
clearInterval(keepAlive);
|
|
22
|
+
clients.delete(res);
|
|
23
|
+
res.end();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=agent-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-events.js","sourceRoot":"","sources":["../src/agent-events.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;AAC1C,IAAI,WAAW,GAAG,CAAC,CAAC;AAEpB,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,MAAM,OAAO,GAAG,OAAO,WAAW,EAAE,yBAAyB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;IACzF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,GAAmB;IAC3D,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,wBAAwB;QACzC,UAAU,EAAE,YAAY;QACxB,mBAAmB,EAAE,IAAI;KAC1B,CAAC,CAAC;IACH,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1E,SAAS,CAAC,KAAK,EAAE,CAAC;IAElB,OAAO,GAAG,EAAE;QACV,aAAa,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ServerResponse } from \"http\";\nimport type { AgentEventEnvelope } from \"./types.js\";\n\nconst clients = new Set<ServerResponse>();\nlet nextEventId = 1;\n\nexport function emitAgentEvent(event: AgentEventEnvelope): void {\n const payload = `id: ${nextEventId++}\\nevent: agent\\ndata: ${JSON.stringify(event)}\\n\\n`;\n for (const client of clients) {\n client.write(payload);\n }\n}\n\nexport function addAgentEventStreamClient(res: ServerResponse): () => void {\n clients.add(res);\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\",\n });\n res.write(`: connected\\n\\n`);\n\n const keepAlive = setInterval(() => res.write(`: keepalive\\n\\n`), 25_000);\n keepAlive.unref();\n\n return () => {\n clearInterval(keepAlive);\n clients.delete(res);\n res.end();\n };\n}\n"]}
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { type ImageContent } from "@earendil-works/pi-ai";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ConversationMessage } from "./adapter.js";
|
|
3
3
|
import type { SessionViewTokenStoreLike } from "./commands/types.js";
|
|
4
4
|
import type { DockerContainerManager } from "./provisioner.js";
|
|
5
5
|
import { type RuntimePathContext, type SandboxConfig } from "./sandbox/index.js";
|
|
6
6
|
import type { VaultManager } from "./vault/index.js";
|
|
7
7
|
import { type ResolvedSessionScope } from "./sessions/store.js";
|
|
8
|
-
export type {
|
|
9
|
-
import type {
|
|
10
|
-
export declare function resolveTriggerAttribution(message: Pick<
|
|
8
|
+
export type { PiAgentWrapper } from "./types.js";
|
|
9
|
+
import type { PiAgentWrapper } from "./types.js";
|
|
10
|
+
export declare function resolveTriggerAttribution(message: Pick<ConversationMessage, "id" | "text" | "userName">): string | undefined;
|
|
11
11
|
export declare function getUnresolvedSandboxPathContext(sandboxConfig: SandboxConfig, hostWorkspaceRoot: string): RuntimePathContext;
|
|
12
|
-
export declare function buildPromptPayload(message:
|
|
12
|
+
export declare function buildPromptPayload(message: ConversationMessage, workspacePath: string, pathContext?: RuntimePathContext): {
|
|
13
13
|
userMessage: string;
|
|
14
14
|
imageAttachments: ImageContent[];
|
|
15
15
|
};
|
|
16
16
|
export declare function appendTriggerAttribution(text: string, triggerAttribution: string | undefined): string;
|
|
17
17
|
/**
|
|
18
|
-
* Create a new
|
|
18
|
+
* Create a new PiAgentWrapper for a channel.
|
|
19
19
|
* Sets up the session and subscribes to events once.
|
|
20
20
|
*
|
|
21
21
|
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
@@ -24,7 +24,7 @@ export declare function appendTriggerAttribution(text: string, triggerAttributio
|
|
|
24
24
|
export declare function createRunner(sandboxConfig: SandboxConfig, sessionKey: string, conversationId: string, conversationDir: string, workspaceDir: string, sessionScope: ResolvedSessionScope, vaultManager?: VaultManager, provisioner?: DockerContainerManager, sessionView?: {
|
|
25
25
|
tokenStore: SessionViewTokenStoreLike;
|
|
26
26
|
portalBaseUrl?: string;
|
|
27
|
-
}): Promise<
|
|
27
|
+
}): Promise<PiAgentWrapper>;
|
|
28
28
|
export declare function translateRuntimePathToHost(runtimePath: string, pathContext: RuntimePathContext): string;
|
|
29
29
|
export declare function translateAttachPathToHost(filePath: string, pathContext: RuntimePathContext): string;
|
|
30
30
|
//# sourceMappingURL=agent.d.ts.map
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,YAAY,EAAc,MAAM,uBAAuB,CAAC;AAkBhF,OAAO,KAAK,EACV,WAAW,EAMZ,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAKrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,qBAAqB,CAAC;AAO7B,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyI9C,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GACrD,MAAM,GAAG,SAAS,CAOpB;AA0MD,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,aAAa,EAC5B,iBAAiB,EAAE,MAAM,GACxB,kBAAkB,CAMpB;AAkTD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,GAC/B;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAaA;AA+BD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,CAKR;AAkmBD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,oBAAoB,EAClC,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,sBAAsB,EACpC,WAAW,CAAC,EAAE;IACZ,UAAU,EAAE,yBAAyB,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,OAAO,CAAC,WAAW,CAAC,CAiNtB;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAER;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAKR","sourcesContent":["import { Agent, type ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, type ImageContent, type Model } from \"@earendil-works/pi-ai\";\nimport {\n AgentSession,\n AuthStorage,\n convertToLlm,\n DefaultResourceLoader,\n formatSkillsForPrompt,\n getAgentDir,\n loadSkillsFromDir,\n ModelRegistry,\n SessionManager,\n SettingsManager,\n type Skill,\n} from \"@earendil-works/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join, posix } from \"path\";\nimport type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n ConversationKind,\n PlatformInfo,\n PlatformName,\n} from \"./adapter.js\";\nimport type { SessionViewTokenStoreLike } from \"./commands/types.js\";\nimport { resolveConversationSettings } from \"./config.js\";\nimport { ActorExecutionResolver } from \"./execution-resolver.js\";\nimport * as log from \"./log.js\";\nimport { reportUserFacingError } from \"./observability/sentry.js\";\nimport type { DockerContainerManager } from \"./provisioner.js\";\nimport {\n createExecutor,\n type Executor,\n type RuntimePathContext,\n type SandboxConfig,\n} from \"./sandbox/index.js\";\nimport { createMountedRuntimePathContext } from \"./sandbox/path-context.js\";\nimport {\n addLifecycleBreadcrumb,\n metricAttributes,\n updateActiveSpanAttribution,\n} from \"./observability/sentry.js\";\nimport type { VaultManager } from \"./vault/index.js\";\nimport { ChatSessionManager } from \"./sessions/chat-session-manager.js\";\nimport {\n extractSessionUuid,\n openManagedSession,\n type ResolvedSessionScope,\n type ThreadRootMessage,\n} from \"./sessions/store.js\";\nimport { shouldSurfaceToolDiagnostic } from \"./tool-diagnostics.js\";\nimport { createMikanTools } from \"./tools/index.js\";\nimport * as Sentry from \"@sentry/node\";\nimport { formatLocalTimestamp } from \"./utils/date.js\";\nimport { resolveConfiguredModel } from \"./model-registry.js\";\n\nexport type { AgentRunner } from \"./types.js\";\nimport type { AgentRunner } from \"./types.js\";\n\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n};\n\nfunction getImageMimeType(filename: string): string | undefined {\n return IMAGE_MIME_TYPES[filename.toLowerCase().split(\".\").pop() || \"\"];\n}\n\nfunction buildThreadSessionName(message: ThreadRootMessage | null): string | undefined {\n const text = message?.text?.trim();\n if (!text) return undefined;\n const userLabel = message?.userName || message?.user || \"unknown\";\n return `[${userLabel}]: ${text}`;\n}\n\nasync function getMemory(conversationDir: string): Promise<string> {\n const parts: string[] = [];\n\n // Read workspace-level memory (shared across all conversations)\n const workspaceMemoryPath = join(conversationDir, \"..\", \"MEMORY.md\");\n if (existsSync(workspaceMemoryPath)) {\n try {\n const content = (await readFile(workspaceMemoryPath, \"utf-8\")).trim();\n if (content) {\n parts.push(`### Global Workspace Memory\\n${content}`);\n }\n } catch (error) {\n log.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n }\n }\n\n // Read conversation-specific memory\n const conversationMemoryPath = join(conversationDir, \"MEMORY.md\");\n if (existsSync(conversationMemoryPath)) {\n try {\n const content = (await readFile(conversationMemoryPath, \"utf-8\")).trim();\n if (content) {\n parts.push(`### Conversation-Specific Memory\\n${content}`);\n }\n } catch (error) {\n log.logWarning(\"Failed to read conversation memory\", `${conversationMemoryPath}: ${error}`);\n }\n }\n\n if (parts.length === 0) {\n return \"(no working memory yet)\";\n }\n\n return parts.join(\"\\n\\n\");\n}\n\nfunction loadMikanSkills(conversationDir: string, workspacePath: string): Skill[] {\n const skillMap = new Map<string, Skill>();\n\n // conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)\n // hostWorkspacePath is the parent directory on host\n // workspacePath is the container path (e.g., /workspace)\n const hostWorkspacePath = join(conversationDir, \"..\");\n\n // Helper to translate host paths to container paths\n const translatePath = (hostPath: string): string => {\n if (hostPath.startsWith(hostWorkspacePath)) {\n return workspacePath + hostPath.slice(hostWorkspacePath.length);\n }\n return hostPath;\n };\n\n // Load workspace-level skills (global)\n const workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n for (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n // Translate paths to container paths for system prompt\n skill.filePath = translatePath(skill.filePath);\n skill.baseDir = translatePath(skill.baseDir);\n skillMap.set(skill.name, skill);\n }\n\n // Load conversation-specific skills (override workspace skills on collision)\n const conversationSkillsDir = join(conversationDir, \"skills\");\n for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: \"channel\" }).skills) {\n skill.filePath = translatePath(skill.filePath);\n skill.baseDir = translatePath(skill.baseDir);\n skillMap.set(skill.name, skill);\n }\n\n return Array.from(skillMap.values());\n}\n\nfunction buildRuntimePaths(runtimeWorkspaceRoot: string, conversationId: string) {\n const workspaceRoot = runtimeWorkspaceRoot.replace(/\\/+$/, \"\") || \"/\";\n const conversationPath = posix.join(workspaceRoot, conversationId);\n return {\n workspaceRoot,\n conversationPath,\n scratchPath: posix.join(conversationPath, \"scratch\"),\n };\n}\n\nfunction buildEnvDescription(sandboxType: SandboxConfig[\"type\"], workspaceRoot: string): string {\n switch (sandboxType) {\n case \"image\":\n return `You are running inside a managed per-user container.\n- Runtime workspace root: ${workspaceRoot}\n- Bash commands start in: ${workspaceRoot}\n- Install tools with the image's package manager\n- Your changes persist for this user's container until it is recreated`;\n case \"container\":\n return `You are running inside a shared container.\n- Runtime workspace root: ${workspaceRoot}\n- Bash commands start in: ${workspaceRoot}\n- Install tools with the container's package manager\n- Your changes persist across sessions`;\n case \"firecracker\":\n return `You are running inside a Firecracker microVM.\n- Runtime workspace root: ${workspaceRoot}\n- Use cd or absolute paths; project files are under ${workspaceRoot}\n- Install tools with: apt-get install <package> (Debian-based)\n- Your changes persist across sessions`;\n case \"cloudflare\":\n return `You are running through a Cloudflare Sandbox bridge.\n- Runtime workspace root: ${workspaceRoot}\n- Bash commands start in: ${workspaceRoot}\n- Your commands run in a remote container managed by Cloudflare\n- Important: the remote filesystem is not automatically synced back to the host workspace`;\n default:\n return `You are running directly on the host machine.\n- Runtime workspace root: ${workspaceRoot}\n- Bash commands start in: ${process.cwd()}\n- Be careful with system modifications`;\n }\n}\n\nexport function resolveTriggerAttribution(\n message: Pick<ChatMessage, \"id\" | \"text\" | \"userName\">,\n): string | undefined {\n const eventTextMatch = message.text.match(/^\\[EVENT:([^:]+):/);\n if (eventTextMatch) return `[event: ${eventTextMatch[1]}]`;\n const eventIdMatch = message.id.match(/^event:([^:]+)/);\n if (eventIdMatch) return `[event: ${eventIdMatch[1]}]`;\n if (message.userName) return `@${message.userName}`;\n return undefined;\n}\n\nfunction buildSystemPrompt(\n workspacePath: string,\n conversationId: string,\n conversationKind: ConversationKind,\n currentUserId: string | undefined,\n memory: string,\n sandboxConfig: SandboxConfig,\n platform: PlatformInfo,\n skills: Skill[],\n isEventTrigger = false,\n triggerAttribution?: string,\n): string {\n const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(\n workspacePath,\n conversationId,\n );\n const sandboxType = sandboxConfig.type;\n const isContainerLike = sandboxType === \"container\" || sandboxType === \"image\";\n const isFirecracker = sandboxType === \"firecracker\";\n\n // Format channel mappings\n const channelMappings =\n platform.channels.length > 0\n ? platform.channels.map((c) => `${c.id}\\t#${c.name}`).join(\"\\n\")\n : \"(no channels loaded)\";\n\n // Format user mappings\n const userMappings =\n platform.users.length > 0\n ? platform.users.map((u) => `${u.id}\\t@${u.userName}\\t${u.displayName}`).join(\"\\n\")\n : \"(no users loaded)\";\n\n const envDescription = buildEnvDescription(sandboxType, workspaceRoot);\n const eventTriggerInstructions = isEventTrigger\n ? `\n## Event Trigger Mode\n- You are handling a scheduled/background event, not opening a brand new chat with a stranger.\n- Treat the incoming user message as a self-contained task prepared by an earlier run.\n- Complete the task directly. Avoid generic greetings, self-introductions, or boilerplate offers to help.\n- For reminders/follow-ups, prefer a short direct response that sounds like a continuation of prior intent.\n- If the event text includes tone, brevity, or language instructions, follow them literally.\n`\n : \"\";\n const attributionInstructions = triggerAttribution\n ? `\n## Attribution\nAlways end your final ${platform.name} response and any GitHub issue/PR comments or descriptions you write via tools with:\n_Triggered by ${triggerAttribution}_\n\nDo not add this to \\`[SILENT]\\` responses.\n`\n : \"\";\n const slackBlockKitInstructions =\n platform.name === \"slack\"\n ? `\n## Slack Block Kit\n- On Slack, use the \\`slack_blockkit\\` tool when a response benefits from interaction or structured presentation.\n- Good uses: choices, confirmations, clarifying questions, quizzes, status reports, comparisons, summaries, and step lists.\n- Do not use it for normal short replies or simple factual answers.\n- Supported blocks: section, context, divider, header, actions.\n- Put buttons in actions.elements. Put static_select and multi_static_select in section.accessory.\n- Every interactive element must include action_id. Buttons must include value.\n- Always provide a plain-text fallback in text that matches the visible Block Kit content.\n- When using \\`slack_blockkit\\`, do not also send a normal text response in the same assistant turn; put the fallback in the tool's text field.\n`\n : \"\";\n\n return `You are mikan, a ${platform.name} bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older human-readable history beyond your context, search \\`log.jsonl\\` (contains user messages and your final responses, but not tool results).\n- Structured session history with tool results lives in \\`${conversationPath}/sessions/\\`.\n- The active top-level session is selected by \\`${conversationPath}/sessions/current\\`, which points to a timestamped \\`.jsonl\\` file in the same directory.\n- Scoped/thread sessions use fixed files at \\`${conversationPath}/sessions/<scope_id>.jsonl\\` (for example \\`${conversationPath}/sessions/1777386320.800769.jsonl\\`).\n- 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.\n- 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.\n${eventTriggerInstructions}\n${platform.formattingGuide}${slackBlockKitInstructions}\n\n## Platform IDs\nChannels: ${channelMappings}\n\nUsers: ${userMappings}\n\nWhen mentioning users, use <@username> format (e.g., <@mario>).\n\n## Environment\n${envDescription}\n- Default place for clones, downloads, and experiments: ${scratchPath}\n- Do not use host-only paths unless you are running in host mode and verified they exist.\n\n## Workspace Layout\n${workspaceRoot}/\n├── MEMORY.md # Global memory (all conversations)\n├── skills/ # Global CLI tools you create\n└── ${conversationId}/ # This conversation\n ├── MEMORY.md # Conversation-specific memory\n ├── log.jsonl # Human-readable message history (no tool results)\n ├── sessions/ # Structured session history used for context reconstruction\n │ ├── current # Active top-level session pointer\n │ ├── <timestamp>_<id>.jsonl # Top-level session files\n │ └── <scope_id>.jsonl # Scoped thread/reply session files\n ├── attachments/ # User-shared files\n ├── scratch/ # Working directory for clones/downloads/experiments: ${scratchPath}\n └── skills/ # Conversation-specific tools\n\n## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspaceRoot}/skills/<name>/\\` (global) or \\`${conversationPath}/skills/<name>/\\` (conversation-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}\n\n## Events\nUse 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.\n\nWrite event \\`text\\` as a self-contained future task with needed context, tone, and constraints because events do not inherit normal conversation history.\n\nFor one-shot reminders, include a timezone offset in \\`at\\`. For periodic events, use a cron schedule plus IANA timezone; assume ${Intl.DateTimeFormat().resolvedOptions().timeZone} when users omit timezone.\n\nWhen events trigger, messages are prefixed like \\`[EVENT:filename:type:time]\\`. Immediate and one-shot events auto-delete after triggering; periodic events persist until deleted.\n\nFor periodic events where there's nothing to report, respond with exactly \\`[SILENT]\\`. Debounce external triggers; prefer one summarized event over many.\n\n## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspaceRoot}/MEMORY.md): skills, preferences, project info\n- Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work\nUpdate when you learn something important or when asked to remember something.\n\n### Current Memory\n${memory}\n\n## System Configuration Log\nMaintain ${workspaceRoot}/SYSTEM.md to log all environment modifications:\n- Installed packages (apt install, npm install, uv pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\nUse \\`log.jsonl\\` for quick grep-style history. Use \\`${conversationPath}/sessions/\\` when you need structured turns, tool outputs, or thread/session lineage.\n${isContainerLike || isFirecracker ? \"Install jq: apt-get install jq\" : \"\"}\n${attributionInstructions}\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\n\n# Inspect top-level session pointer and available session files\ncat sessions/current\nls -1 sessions/\n\\`\\`\\`\n\n## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- event: Schedule immediate, one-shot, or periodic follow-ups\n- sandbox: Inspect or temporarily adjust sandbox limits\n- attach: Share files to the platform\n\nEach tool requires a \"label\" parameter (shown to user).\n`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return `${text.substring(0, maxLen - 3)}...`;\n}\n\nexport function getUnresolvedSandboxPathContext(\n sandboxConfig: SandboxConfig,\n hostWorkspaceRoot: string,\n): RuntimePathContext {\n if (sandboxConfig.type === \"image\") {\n return createMountedRuntimePathContext(hostWorkspaceRoot, \"/workspace\");\n }\n\n return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);\n}\n\ninterface RunnerExecutionContext {\n executionResolver?: ActorExecutionResolver;\n executor: Executor;\n getPathContext: () => RuntimePathContext;\n resolveExecutorForRun(context: {\n platform: string;\n userId: string;\n conversationId: string;\n }): Promise<void>;\n}\n\ninterface RunnerSessionState {\n responseCtx: ChatResponseContext | null;\n logCtx: {\n conversationId: string;\n userName?: string;\n conversationName?: string;\n sessionId?: string;\n } | null;\n queue: {\n enqueue(fn: () => Promise<void>, errorContext: string): void;\n } | null;\n pendingTools: Map<string, { toolName: string; args: unknown; startTime: number }>;\n totalUsage: {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n };\n llmCallCount: number;\n stopReason: string;\n errorMessage: string | undefined;\n reportedLlmError: boolean;\n finalResponseHandledByTool: boolean;\n triggerAttribution?: string;\n}\n\ninterface PreparedRunContext {\n sessionConversation: string;\n runQueue: ReturnType<typeof createRunQueue>;\n userMessage: string;\n imageAttachments: ImageContent[];\n triggerAttribution?: string;\n}\n\ninterface ConfiguredAgentSession {\n agent: Agent;\n session: AgentSession;\n}\n\nfunction createRunnerExecutionContext(\n sandboxConfig: SandboxConfig,\n vaultManager: VaultManager | undefined,\n provisioner: DockerContainerManager | undefined,\n workspaceDir: string,\n hostWorkspacePath: string,\n): RunnerExecutionContext {\n const executionResolver =\n vaultManager &&\n sandboxConfig.type !== \"host\" &&\n (vaultManager.isEnabled() ||\n sandboxConfig.type === \"container\" ||\n sandboxConfig.type === \"image\" ||\n sandboxConfig.type === \"cloudflare\" ||\n sandboxConfig.type === \"firecracker\")\n ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)\n : undefined;\n\n // activeExecutor is replaced at the start of each run() call when executionResolver\n // is present, so the stable `executor` wrapper always delegates to the latest resolved value.\n let activeExecutor: Executor =\n executionResolver !== undefined\n ? createExecutor({ type: \"host\" })\n : createExecutor(sandboxConfig);\n const executor: Executor = {\n exec(command, options) {\n return activeExecutor.exec(command, options);\n },\n getWorkspacePath(hostPath) {\n return activeExecutor.getWorkspacePath(hostPath);\n },\n getSandboxConfig() {\n return activeExecutor.getSandboxConfig();\n },\n getPathContext(hostWorkspaceRoot) {\n return activeExecutor.getPathContext(hostWorkspaceRoot);\n },\n };\n\n return {\n executionResolver,\n executor,\n getPathContext: () => executor.getPathContext(hostWorkspacePath),\n async resolveExecutorForRun(context): Promise<void> {\n if (!executionResolver) return;\n activeExecutor = await executionResolver.resolve(context);\n },\n };\n}\n\nasync function createConfiguredAgentSession(params: {\n conversationId: string;\n workspaceDir: string;\n runtimeWorkspaceRoot: string;\n systemPrompt: string;\n model: Model<Api>;\n thinkingLevel: ThinkingLevel;\n tools: Awaited<ReturnType<typeof createMikanTools>>[\"tools\"];\n sessionManager: SessionManager;\n settingsManager: SettingsManager;\n modelRegistry: ModelRegistry;\n}): Promise<ConfiguredAgentSession> {\n const {\n conversationId,\n workspaceDir,\n runtimeWorkspaceRoot,\n systemPrompt,\n model,\n thinkingLevel,\n tools,\n sessionManager,\n settingsManager,\n modelRegistry,\n } = params;\n const agent = new Agent({\n initialState: {\n systemPrompt,\n model,\n thinkingLevel,\n tools,\n },\n convertToLlm,\n getApiKey: async (provider) => {\n const key = await modelRegistry.getApiKeyForProvider(provider);\n if (!key) {\n throw new Error(\n `No API key for provider \"${provider}\". Set the appropriate environment variable or configure via auth.json`,\n );\n }\n return key;\n },\n });\n\n const loadedSession = sessionManager.buildSessionContext();\n if (loadedSession.messages.length > 0) {\n agent.state.messages = loadedSession.messages;\n log.logInfo(\n `[${conversationId}] Reloaded ${loadedSession.messages.length} messages from session context`,\n );\n }\n\n const resourceLoader = new DefaultResourceLoader({\n cwd: workspaceDir,\n agentDir: getAgentDir(),\n systemPrompt,\n });\n try {\n await resourceLoader.reload();\n const extResult = resourceLoader.getExtensions();\n if (extResult.errors.length > 0) {\n for (const err of extResult.errors) {\n log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);\n }\n }\n log.logInfo(\n `[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((extension) => extension.path).join(\", \")}`,\n );\n } catch (error) {\n log.logWarning(`[${conversationId}] Failed to load resources`, String(error));\n }\n\n const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n const session = new AgentSession({\n agent,\n sessionManager,\n settingsManager,\n cwd: runtimeWorkspaceRoot,\n modelRegistry,\n resourceLoader,\n baseToolsOverride,\n });\n return { agent, session };\n}\n\nfunction createEmptyUsageTotals() {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction createRunState(): RunnerSessionState {\n return {\n responseCtx: null,\n logCtx: null,\n queue: null,\n pendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n totalUsage: createEmptyUsageTotals(),\n llmCallCount: 0,\n stopReason: \"stop\",\n errorMessage: undefined,\n reportedLlmError: false,\n finalResponseHandledByTool: false,\n triggerAttribution: undefined,\n };\n}\n\nfunction resetRunState(\n runState: RunnerSessionState,\n responseCtx: ChatResponseContext,\n sessionConversation: string,\n userName: string | undefined,\n sessionUuid: string,\n triggerAttribution: string | undefined,\n): void {\n runState.responseCtx = responseCtx;\n runState.logCtx = {\n conversationId: sessionConversation,\n userName,\n conversationName: undefined,\n sessionId: sessionUuid,\n };\n runState.pendingTools.clear();\n runState.totalUsage = createEmptyUsageTotals();\n runState.llmCallCount = 0;\n runState.stopReason = \"stop\";\n runState.errorMessage = undefined;\n runState.reportedLlmError = false;\n runState.finalResponseHandledByTool = false;\n runState.triggerAttribution = triggerAttribution;\n}\n\nfunction createRunQueue(\n responseCtx: ChatResponseContext,\n runState: RunnerSessionState,\n): {\n queue: { enqueue(fn: () => Promise<void>, errorContext: string): void };\n wait: () => Promise<void>;\n} {\n let queueChain = Promise.resolve();\n return {\n queue: {\n enqueue(fn: () => Promise<void>, errorContext: string): void {\n queueChain = queueChain.then(async () => {\n if (runState.finalResponseHandledByTool) return;\n try {\n await fn();\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`API error (${errorContext})`, errMsg);\n try {\n await responseCtx.respondDiagnostic(`Error: ${errMsg}`, { style: \"error\" });\n } catch {\n // Ignore\n }\n }\n });\n },\n },\n wait: () => queueChain,\n };\n}\n\nfunction formatTimestampedUserMessage(message: ChatMessage): string {\n const timestamp = formatLocalTimestamp(new Date())!;\n const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : \"\";\n return `[${timestamp}] [${message.userName || \"unknown\"}]${threadContext}: ${message.text}`;\n}\n\nfunction collectMessageAttachments(\n message: ChatMessage,\n workspacePath: string,\n pathContext?: RuntimePathContext,\n): { imageAttachments: ImageContent[]; nonImagePaths: string[] } {\n const imageAttachments: ImageContent[] = [];\n const nonImagePaths: string[] = [];\n\n for (const attachment of message.attachments || []) {\n const runtimePath = `${workspacePath}/${attachment.localPath}`;\n const hostPath = pathContext?.runtimeToHostPath?.(runtimePath) ?? runtimePath;\n const mimeType = getImageMimeType(attachment.localPath);\n\n if (mimeType && existsSync(hostPath)) {\n try {\n imageAttachments.push({\n type: \"image\",\n mimeType,\n data: readFileSync(hostPath).toString(\"base64\"),\n });\n } catch {\n nonImagePaths.push(runtimePath);\n }\n } else {\n nonImagePaths.push(runtimePath);\n }\n }\n\n return { imageAttachments, nonImagePaths };\n}\n\nexport function buildPromptPayload(\n message: ChatMessage,\n workspacePath: string,\n pathContext?: RuntimePathContext,\n): {\n userMessage: string;\n imageAttachments: ImageContent[];\n} {\n let userMessage = formatTimestampedUserMessage(message);\n const { imageAttachments, nonImagePaths } = collectMessageAttachments(\n message,\n workspacePath,\n pathContext,\n );\n\n if (nonImagePaths.length > 0) {\n userMessage += `\\n\\n<slack_attachments>\\n${nonImagePaths.join(\"\\n\")}\\n</slack_attachments>`;\n }\n\n return { userMessage, imageAttachments };\n}\n\nasync function writePromptDebugContext(\n conversationDir: string,\n systemPrompt: string,\n session: AgentSession,\n userMessage: string,\n imageAttachmentCount: number,\n): Promise<void> {\n const debugContext = {\n systemPrompt,\n messages: session.messages,\n newUserMessage: userMessage,\n imageAttachmentCount,\n };\n await writeFile(\n join(conversationDir, \"last_prompt.jsonl\"),\n JSON.stringify(debugContext, null, 2),\n );\n}\n\nfunction getFinalAssistantText(session: AgentSession): string {\n const lastAssistant = session.messages.findLast((message) => message.role === \"assistant\");\n return (\n lastAssistant?.content\n .filter((content): content is { type: \"text\"; text: string } => content.type === \"text\")\n .map((content) => content.text)\n .join(\"\\n\") || \"\"\n );\n}\n\nexport function appendTriggerAttribution(\n text: string,\n triggerAttribution: string | undefined,\n): string {\n if (!triggerAttribution) return text;\n const suffix = `_Triggered by ${triggerAttribution}_`;\n if (text.trimEnd().endsWith(suffix)) return text;\n return `${text.trimEnd()}\\n\\n${suffix}`;\n}\n\nasync function finalizeRunResponse(\n responseCtx: ChatResponseContext,\n session: AgentSession,\n runState: RunnerSessionState,\n options?: {\n triggerAttribution?: string;\n createOverflowLink?: () => string;\n platform?: string;\n model?: Model<Api>;\n sessionConversation?: string;\n sessionUuid?: string;\n },\n): Promise<void> {\n if (runState.stopReason === \"error\" && runState.errorMessage) {\n if (!runState.reportedLlmError) {\n runState.reportedLlmError = true;\n reportUserFacingError(new Error(\"LLM run completed with error stop reason\"), {\n domain: \"llm\",\n surface: \"assistant_response\",\n operation: \"llm_turn\",\n severity: \"error\",\n platform: options?.platform,\n provider: options?.model?.provider,\n model: options?.model?.name,\n stopReason: runState.stopReason,\n context: {\n sessionConversation: options?.sessionConversation,\n sessionUuid: options?.sessionUuid,\n hasErrorMessage: true,\n llmCallCount: runState.llmCallCount,\n },\n });\n }\n try {\n await responseCtx.replaceResponse(\"_Sorry, something went wrong_\");\n await responseCtx.respondDiagnostic(`Error: ${runState.errorMessage}`, {\n style: \"error\",\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(\"Failed to post error message\", errMsg);\n reportUserFacingError(err, {\n domain: \"chat_platform\",\n surface: \"final_response\",\n operation: \"finalize_error_response\",\n severity: \"error\",\n platform: options?.platform,\n context: {\n sessionConversation: options?.sessionConversation,\n sessionUuid: options?.sessionUuid,\n stopReason: runState.stopReason,\n },\n });\n }\n return;\n }\n\n const finalText = getFinalAssistantText(session);\n if (runState.finalResponseHandledByTool) {\n log.logInfo(\"Final response already handled by tool - skipping final replacement\");\n return;\n }\n if (finalText.trim() === \"[SILENT]\" || finalText.trim().startsWith(\"[SILENT]\")) {\n try {\n await responseCtx.deleteResponse();\n log.logInfo(\"Silent response - deleted message and thread\");\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(\"Failed to delete message for silent response\", errMsg);\n }\n return;\n }\n\n if (!finalText.trim()) return;\n\n try {\n await responseCtx.replaceResponse(\n appendTriggerAttribution(finalText, options?.triggerAttribution),\n { createOverflowLink: options?.createOverflowLink },\n );\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(\"Failed to replace message with final text\", errMsg);\n reportUserFacingError(err, {\n domain: \"chat_platform\",\n surface: \"final_response\",\n operation: \"replace_final_response\",\n severity: \"error\",\n platform: options?.platform,\n context: {\n sessionConversation: options?.sessionConversation,\n sessionUuid: options?.sessionUuid,\n finalTextLength: finalText.length,\n },\n });\n }\n}\n\ninterface UsageReportContext {\n session: AgentSession;\n runState: RunnerSessionState;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n model: Model<Api>;\n agentConfig: ReturnType<typeof resolveConversationSettings>;\n sessionConversation: string;\n sessionUuid: string;\n waitForQueue: () => Promise<void>;\n}\n\nasync function reportUsageSummary(ctx: UsageReportContext): Promise<void> {\n const {\n session,\n runState,\n responseCtx,\n platform,\n model,\n agentConfig,\n sessionConversation,\n sessionUuid,\n waitForQueue,\n } = ctx;\n const lastAssistantMessage = session.messages\n .slice()\n .toReversed()\n .find(\n (message): message is Extract<typeof message, { role: \"assistant\" }> =>\n message.role === \"assistant\" && message.stopReason !== \"aborted\",\n );\n\n const contextTokens = lastAssistantMessage\n ? lastAssistantMessage.usage.input +\n lastAssistantMessage.usage.output +\n lastAssistantMessage.usage.cacheRead +\n lastAssistantMessage.usage.cacheWrite\n : 0;\n const contextWindow = model.contextWindow || 200000;\n\n const { totalUsage } = runState;\n const runMetricAttributes = metricAttributes({\n provider: model.provider,\n model: agentConfig.model,\n channel_id: sessionConversation,\n session_id: sessionUuid,\n stop_reason: runState.stopReason,\n llm_calls: runState.llmCallCount,\n });\n Sentry.metrics.distribution(\"agent.run.tokens_in\", totalUsage.input, {\n attributes: runMetricAttributes,\n });\n Sentry.metrics.distribution(\"agent.run.tokens_out\", totalUsage.output, {\n attributes: runMetricAttributes,\n });\n Sentry.metrics.distribution(\"agent.run.cache_read\", totalUsage.cacheRead, {\n attributes: runMetricAttributes,\n });\n Sentry.metrics.distribution(\"agent.run.cache_write\", totalUsage.cacheWrite, {\n attributes: runMetricAttributes,\n });\n Sentry.metrics.distribution(\"agent.run.cost\", totalUsage.cost.total, {\n attributes: runMetricAttributes,\n });\n Sentry.metrics.gauge(\"agent.context.utilization\", contextTokens / contextWindow, {\n unit: \"ratio\",\n attributes: runMetricAttributes,\n });\n\n const summary = log.logUsageSummary(\n runState.logCtx!,\n runState.totalUsage,\n contextTokens,\n contextWindow,\n );\n if (platform.diagnostics?.showUsageSummary === true) {\n runState.queue!.enqueue(\n () => responseCtx.respondDiagnostic(summary, { style: \"muted\" }),\n \"usage summary\",\n );\n await waitForQueue();\n }\n}\n\nfunction reloadSessionMessages(\n sessionManager: SessionManager,\n conversationId: string,\n agent: Agent,\n): void {\n const messages = sessionManager.buildSessionContext().messages;\n if (messages.length > 0) {\n agent.state.messages = messages;\n log.logInfo(`[${conversationId}] Reloaded ${messages.length} messages from context`);\n }\n}\n\nasync function prepareRunContext(params: {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n conversationId: string;\n conversationDir: string;\n sessionUuid: string;\n runState: RunnerSessionState;\n executor: Executor;\n executionResolver?: ActorExecutionResolver;\n resolveExecutorForRun: RunnerExecutionContext[\"resolveExecutorForRun\"];\n getPathContext: () => RuntimePathContext;\n sessionManager: SessionManager;\n session: AgentSession;\n agent: Agent;\n setEventContext: (context: {\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n userId: string;\n }) => void;\n setSandboxContext: (context: { conversationId: string; userId: string }) => void;\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n setBlockKitResponseFunction: (\n fn: (response: import(\"./adapter.js\").ChatResponseBlockKit) => Promise<void>,\n ) => void;\n pathContext: RuntimePathContext;\n}): Promise<PreparedRunContext & { pathContext: RuntimePathContext }> {\n const {\n message,\n responseCtx,\n platform,\n conversationId,\n conversationDir,\n sessionUuid,\n runState,\n executor,\n executionResolver,\n resolveExecutorForRun,\n getPathContext,\n sessionManager,\n session,\n agent,\n setEventContext,\n setSandboxContext,\n setUploadFunction,\n setBlockKitResponseFunction,\n } = params;\n let pathContext = params.pathContext;\n const sessionConversation = message.sessionKey.split(\":\")[0];\n\n await mkdir(join(conversationDir, \"scratch\"), { recursive: true });\n\n if (executionResolver) {\n await resolveExecutorForRun({\n platform: platform.name,\n userId: message.userId,\n conversationId,\n });\n pathContext = getPathContext();\n }\n\n reloadSessionMessages(sessionManager, conversationId, agent);\n\n const memory = await getMemory(conversationDir);\n const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);\n const triggerAttribution = resolveTriggerAttribution(message);\n const systemPrompt = buildSystemPrompt(\n pathContext.runtimeWorkspaceRoot,\n conversationId,\n message.conversationKind,\n message.userId,\n memory,\n executor.getSandboxConfig(),\n platform,\n skills,\n message.id.startsWith(\"event:\"),\n triggerAttribution,\n );\n session.agent.state.systemPrompt = systemPrompt;\n\n setEventContext({\n platform: platform.name,\n conversationId,\n conversationKind: message.conversationKind,\n userId: message.userId,\n });\n setSandboxContext({ conversationId, userId: message.userId });\n\n setUploadFunction(async (filePath: string, title?: string) => {\n const hostPath = translateAttachPathToHost(filePath, pathContext);\n await responseCtx.uploadFile(hostPath, title);\n });\n setBlockKitResponseFunction(async (response) => {\n if (platform.name === \"slack\" && responseCtx.respondBlockKit) {\n await responseCtx.respondBlockKit(response);\n } else {\n await responseCtx.replaceResponse(response.text);\n }\n runState.finalResponseHandledByTool = true;\n });\n\n resetRunState(\n runState,\n responseCtx,\n sessionConversation,\n message.userName,\n sessionUuid,\n triggerAttribution,\n );\n const runQueue = createRunQueue(responseCtx, runState);\n runState.queue = runQueue.queue;\n\n log.logInfo(\n `Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`,\n );\n log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);\n\n const { userMessage, imageAttachments } = buildPromptPayload(\n message,\n pathContext.runtimeWorkspaceRoot,\n pathContext,\n );\n await writePromptDebugContext(\n conversationDir,\n systemPrompt,\n session,\n userMessage,\n imageAttachments.length,\n );\n\n return {\n sessionConversation,\n runQueue,\n userMessage,\n imageAttachments,\n triggerAttribution,\n pathContext,\n };\n}\n\nfunction attachSessionEventHandlers(params: {\n session: AgentSession;\n runState: RunnerSessionState;\n model: Model<Api>;\n agentConfig: ReturnType<typeof resolveConversationSettings>;\n}): void {\n const { session, runState, model, agentConfig } = params;\n session.subscribe(async (event) => {\n if (!runState.responseCtx || !runState.logCtx || !runState.queue) return;\n\n const { responseCtx, logCtx, queue, pendingTools } = runState;\n const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };\n\n if (event.type === \"tool_execution_start\") {\n const args = (event.args ?? {}) as { label?: string };\n const label = args.label || event.toolName;\n\n pendingTools.set(event.toolCallId, {\n toolName: event.toolName,\n args: event.args,\n startTime: Date.now(),\n });\n addLifecycleBreadcrumb(\"agent.tool.started\", {\n tool: event.toolName,\n ...baseAttrs,\n });\n\n log.logToolStart(logCtx, event.toolName, label, event.args as Record<string, unknown>);\n return;\n }\n\n if (event.type === \"tool_execution_end\") {\n const resultStr = extractToolResultText(event.result);\n const pending = pendingTools.get(event.toolCallId);\n pendingTools.delete(event.toolCallId);\n const durationMs = pending ? Date.now() - pending.startTime : 0;\n\n Sentry.metrics.count(\"agent.tool.calls\", 1, {\n attributes: metricAttributes({\n tool: event.toolName,\n error: String(event.isError),\n ...baseAttrs,\n }),\n });\n Sentry.metrics.distribution(\"agent.tool.duration\", durationMs, {\n unit: \"millisecond\",\n attributes: metricAttributes({\n tool: event.toolName,\n ...baseAttrs,\n }),\n });\n addLifecycleBreadcrumb(\"agent.tool.completed\", {\n tool: event.toolName,\n error: event.isError,\n duration_ms: durationMs,\n ...baseAttrs,\n });\n\n if (event.isError) {\n log.logToolError(logCtx, event.toolName, durationMs, resultStr);\n } else {\n log.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);\n }\n\n if (shouldSurfaceToolDiagnostic(event.toolName)) {\n const toolResult: ChatToolResult = {\n toolName: event.toolName,\n label: pending?.args ? (pending.args as { label?: string }).label : undefined,\n args: pending?.args as Record<string, unknown> | undefined,\n result: truncate(resultStr, TOOL_RESULT_DIAGNOSTIC_CAP),\n isError: event.isError,\n durationMs,\n };\n queue.enqueue(() => responseCtx.respondToolResult(toolResult), \"tool result diagnostic\");\n }\n\n if (event.isError && shouldSurfaceToolDiagnostic(event.toolName)) {\n queue.enqueue(\n () => responseCtx.respond(`_Error: ${truncate(resultStr, 200)}_`),\n \"tool error\",\n );\n }\n return;\n }\n\n if (event.type === \"message_start\") {\n if (event.message.role === \"assistant\") {\n runState.llmCallCount += 1;\n addLifecycleBreadcrumb(\"agent.llm.call.started\", {\n call_index: runState.llmCallCount,\n provider: model.provider,\n model: agentConfig.model,\n ...baseAttrs,\n });\n log.logResponseStart(logCtx);\n }\n return;\n }\n\n if (event.type === \"message_update\") {\n const assistantMessageEvent = (\n event as {\n assistantMessageEvent?: { type?: string; delta?: string };\n }\n ).assistantMessageEvent;\n if (\n assistantMessageEvent?.type === \"text_delta\" &&\n assistantMessageEvent.delta &&\n responseCtx.appendResponseDelta\n ) {\n queue.enqueue(async () => {\n await responseCtx.appendResponseDelta?.(assistantMessageEvent.delta ?? \"\");\n }, \"response delta\");\n }\n return;\n }\n\n if (event.type === \"message_end\") {\n if (event.message.role === \"assistant\") {\n const assistantMsg = event.message;\n\n if (assistantMsg.stopReason) {\n runState.stopReason = assistantMsg.stopReason;\n }\n if (assistantMsg.errorMessage) {\n runState.errorMessage = assistantMsg.errorMessage;\n }\n\n if (assistantMsg.usage) {\n runState.totalUsage.input += assistantMsg.usage.input;\n runState.totalUsage.output += assistantMsg.usage.output;\n runState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n runState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n runState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n runState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n runState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n runState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n runState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\n const llmAttributes = metricAttributes({\n provider: model.provider,\n model: agentConfig.model,\n ...baseAttrs,\n stop_reason: assistantMsg.stopReason,\n error: Boolean(assistantMsg.errorMessage),\n });\n Sentry.metrics.count(\"agent.llm.calls\", 1, { attributes: llmAttributes });\n Sentry.metrics.distribution(\"agent.llm.tokens_in\", assistantMsg.usage.input, {\n attributes: llmAttributes,\n });\n Sentry.metrics.distribution(\"agent.llm.tokens_out\", assistantMsg.usage.output, {\n attributes: llmAttributes,\n });\n if (assistantMsg.usage.cacheRead > 0) {\n Sentry.metrics.distribution(\"agent.llm.cache_read\", assistantMsg.usage.cacheRead, {\n attributes: llmAttributes,\n });\n }\n if (assistantMsg.usage.cacheWrite > 0) {\n Sentry.metrics.distribution(\"agent.llm.cache_write\", assistantMsg.usage.cacheWrite, {\n attributes: llmAttributes,\n });\n }\n Sentry.metrics.distribution(\"agent.llm.cost_per_turn\", assistantMsg.usage.cost.total, {\n attributes: llmAttributes,\n });\n addLifecycleBreadcrumb(\"agent.llm.call.completed\", {\n call_index: runState.llmCallCount,\n provider: model.provider,\n model: agentConfig.model,\n stop_reason: assistantMsg.stopReason,\n error: Boolean(assistantMsg.errorMessage),\n input_tokens: assistantMsg.usage.input,\n output_tokens: assistantMsg.usage.output,\n cost_total_usd: assistantMsg.usage.cost.total,\n });\n }\n\n const thinkingParts: string[] = [];\n const textParts: string[] = [];\n const hasToolCall = assistantMsg.content.some((part) =>\n [\"tool_use\", \"toolCall\", \"tool-call\"].includes((part as { type?: string }).type ?? \"\"),\n );\n for (const part of assistantMsg.content) {\n if (part.type === \"thinking\") {\n thinkingParts.push(part.thinking);\n } else if (part.type === \"text\") {\n textParts.push(part.text);\n }\n }\n\n const text = textParts.join(\"\\n\");\n\n for (const thinking of thinkingParts) {\n log.logThinking(logCtx, thinking);\n queue.enqueue(() => responseCtx.respond(`_${thinking}_`), \"thinking main\");\n queue.enqueue(\n () => responseCtx.respondDiagnostic(`_${thinking}_`),\n \"thinking diagnostic\",\n );\n }\n\n if (text.trim() && !hasToolCall) {\n if (runState.finalResponseHandledByTool) return;\n const finalText = appendTriggerAttribution(text, runState.triggerAttribution);\n log.logResponse(logCtx, text);\n if (responseCtx.finishResponse) {\n queue.enqueue(async () => {\n await responseCtx.finishResponse?.(finalText);\n }, \"response finish\");\n } else {\n queue.enqueue(() => responseCtx.respond(finalText), \"response main\");\n }\n }\n }\n return;\n }\n\n if (event.type === \"compaction_start\") {\n log.logInfo(`Auto-compaction started (reason: ${event.reason})`);\n queue.enqueue(() => responseCtx.respond(\"_Compacting context..._\"), \"compaction start\");\n return;\n }\n\n if (event.type === \"compaction_end\") {\n if (event.result) {\n log.logInfo(`Auto-compaction complete: ${event.result.tokensBefore} tokens compacted`);\n } else if (event.aborted) {\n log.logInfo(\"Auto-compaction aborted\");\n }\n return;\n }\n\n if (event.type === \"auto_retry_start\") {\n log.logWarning(`Retrying (${event.attempt}/${event.maxAttempts})`, event.errorMessage);\n queue.enqueue(\n () => responseCtx.respond(`_Retrying (${event.attempt}/${event.maxAttempts})..._`),\n \"retry\",\n );\n }\n });\n}\n\n// Cap raw tool output before handing it to adapters. Bash output can be MB; without\n// this each adapter's splitter would fan it out into many sequential platform posts.\nconst TOOL_RESULT_DIAGNOSTIC_CAP = 8000;\n\nfunction extractToolResultText(result: unknown): string {\n if (typeof result === \"string\") {\n return result;\n }\n\n if (\n result &&\n typeof result === \"object\" &&\n \"content\" in result &&\n Array.isArray((result as { content: unknown }).content)\n ) {\n const content = (result as { content: Array<{ type: string; text?: string }> }).content;\n const textParts: string[] = [];\n for (const part of content) {\n if (part.type === \"text\" && part.text) {\n textParts.push(part.text);\n }\n }\n if (textParts.length > 0) {\n return textParts.join(\"\\n\");\n }\n }\n\n return JSON.stringify(result);\n}\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n *\n * Runner caching is handled by the caller (channelStates in main.ts).\n * This is a stateless factory function.\n */\nexport async function createRunner(\n sandboxConfig: SandboxConfig,\n sessionKey: string,\n conversationId: string,\n conversationDir: string,\n workspaceDir: string,\n sessionScope: ResolvedSessionScope,\n vaultManager?: VaultManager,\n provisioner?: DockerContainerManager,\n sessionView?: {\n tokenStore: SessionViewTokenStoreLike;\n portalBaseUrl?: string;\n },\n): Promise<AgentRunner> {\n const agentConfig = resolveConversationSettings(conversationDir);\n\n const workspaceBase = join(conversationDir, \"..\");\n const { executionResolver, executor, getPathContext, resolveExecutorForRun } =\n createRunnerExecutionContext(\n sandboxConfig,\n vaultManager,\n provisioner,\n workspaceDir,\n workspaceBase,\n );\n let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);\n\n // Create tools (per-runner, with per-runner upload function setter)\n const {\n tools,\n setUploadFunction,\n setBlockKitResponseFunction,\n setEventContext,\n setSandboxContext,\n } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, provisioner });\n\n const authStorage = AuthStorage.create(join(homedir(), \".pi\", \"mikan\", \"auth.json\"));\n const modelRegistry = ModelRegistry.create(authStorage);\n const model = resolveConfiguredModel(modelRegistry, agentConfig.provider, agentConfig.model);\n\n // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)\n const memory = await getMemory(conversationDir);\n const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);\n const emptyPlatform: PlatformInfo = {\n name: \"chat\",\n formattingGuide: \"\",\n channels: [],\n users: [],\n };\n const systemPrompt = buildSystemPrompt(\n pathContext.runtimeWorkspaceRoot,\n conversationId,\n \"shared\",\n undefined,\n memory,\n sandboxConfig,\n emptyPlatform,\n skills,\n );\n\n // Create session manager and settings manager. Top-level/private sessions\n // use the conversation's current pointer; scoped sessions use fixed files.\n // Platform-specific scope behavior is resolved before runner creation.\n const isThread = sessionKey.includes(\":\");\n const { sessionDir, contextFile, threadRootMessage } = sessionScope;\n const sessionManager = openManagedSession(\n contextFile,\n sessionDir,\n pathContext.runtimeWorkspaceRoot,\n );\n const threadSessionName = buildThreadSessionName(threadRootMessage);\n if (isThread && threadSessionName && sessionManager.getSessionName() !== threadSessionName) {\n sessionManager.appendSessionInfo(threadSessionName);\n }\n\n const sessionUuid = extractSessionUuid(contextFile);\n const chatSessionManager = new ChatSessionManager();\n const settingsManager = SettingsManager.inMemory();\n const { agent, session } = await createConfiguredAgentSession({\n conversationId,\n workspaceDir,\n runtimeWorkspaceRoot: pathContext.runtimeWorkspaceRoot,\n systemPrompt,\n model,\n thinkingLevel: agentConfig.thinkingLevel,\n tools,\n sessionManager,\n settingsManager,\n modelRegistry,\n });\n\n // Mutable per-run state - event handler references this\n const runState = createRunState();\n attachSessionEventHandlers({ session, runState, model, agentConfig });\n\n return {\n syncChatHistory(currentMessageId?: string): void {\n chatSessionManager.syncSessionManager({\n conversationDir,\n sessionKey,\n sessionManager,\n currentMessageId,\n });\n },\n\n async run(\n message: ChatMessage,\n responseCtx: ChatResponseContext,\n platform: PlatformInfo,\n ): Promise<{ stopReason: string; errorMessage?: string }> {\n const prepared = await prepareRunContext({\n message,\n responseCtx,\n platform,\n conversationId,\n conversationDir,\n sessionUuid,\n runState,\n executor,\n executionResolver,\n resolveExecutorForRun,\n getPathContext,\n sessionManager,\n session,\n agent,\n setEventContext,\n setSandboxContext,\n setUploadFunction,\n setBlockKitResponseFunction,\n pathContext,\n });\n pathContext = prepared.pathContext;\n\n if (runState.logCtx) {\n log.logAgentRunStart(runState.logCtx, model.provider, model.id, model.name);\n }\n\n updateActiveSpanAttribution({\n provider: model.provider,\n model: agentConfig.model,\n channel_id: prepared.sessionConversation,\n session_id: sessionUuid,\n });\n addLifecycleBreadcrumb(\"agent.prompt.sent\", {\n provider: model.provider,\n model: agentConfig.model,\n channel_id: prepared.sessionConversation,\n session_id: sessionUuid,\n attachment_count: message.attachments?.length ?? 0,\n image_attachment_count: prepared.imageAttachments.length,\n });\n\n await session.prompt(\n prepared.userMessage,\n prepared.imageAttachments.length > 0 ? { images: prepared.imageAttachments } : undefined,\n );\n\n // Wait for queued messages\n await prepared.runQueue.wait();\n\n const sessionViewTokenStore = sessionView?.tokenStore;\n const sessionViewPortalBaseUrl = sessionView?.portalBaseUrl;\n const createOverflowLink =\n sessionViewTokenStore && sessionViewPortalBaseUrl\n ? () => {\n const token = sessionViewTokenStore.create(\n platform.name as PlatformName,\n message.userId,\n conversationId,\n message.sessionKey,\n contextFile,\n message.userName,\n );\n return `${sessionViewPortalBaseUrl}/session?token=${token.token}`;\n }\n : undefined;\n\n await finalizeRunResponse(responseCtx, session, runState, {\n triggerAttribution: prepared.triggerAttribution,\n createOverflowLink,\n platform: platform.name,\n model,\n sessionConversation: prepared.sessionConversation,\n sessionUuid,\n });\n\n await reportUsageSummary({\n session,\n runState,\n responseCtx,\n platform,\n model,\n agentConfig,\n sessionConversation: prepared.sessionConversation,\n sessionUuid,\n waitForQueue: () => prepared.runQueue.wait(),\n });\n\n // Clear run state\n runState.responseCtx = null;\n runState.logCtx = null;\n runState.queue = null;\n\n return { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n },\n\n abort(): void {\n session.abort();\n },\n\n getCurrentStep(): { toolName?: string; label?: string } | undefined {\n const pending = runState.pendingTools;\n if (pending.size === 0) return undefined;\n // Get the first pending tool\n const first = pending.values().next().value;\n if (!first) return undefined;\n return {\n toolName: first.toolName,\n label: (first.args as { label?: string })?.label,\n };\n },\n };\n}\n\nexport function translateRuntimePathToHost(\n runtimePath: string,\n pathContext: RuntimePathContext,\n): string {\n return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;\n}\n\nexport function translateAttachPathToHost(\n filePath: string,\n pathContext: RuntimePathContext,\n): string {\n const runtimePath = posix.isAbsolute(filePath)\n ? filePath\n : posix.join(pathContext.runtimeWorkspaceRoot, filePath);\n return translateRuntimePathToHost(runtimePath, pathContext);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,YAAY,EAAc,MAAM,uBAAuB,CAAC;AAkBhF,OAAO,KAAK,EACV,mBAAmB,EAMpB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAKrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,qBAAqB,CAAC;AAQ7B,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAyIjD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,GAC7D,MAAM,GAAG,SAAS,CAOpB;AA0MD,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,aAAa,EAC5B,iBAAiB,EAAE,MAAM,GACxB,kBAAkB,CAMpB;AAkTD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,kBAAkB,GAC/B;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,YAAY,EAAE,CAAC;CAClC,CAaA;AA+BD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,CAKR;AA0oBD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,oBAAoB,EAClC,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,sBAAsB,EACpC,WAAW,CAAC,EAAE;IACZ,UAAU,EAAE,yBAAyB,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,OAAO,CAAC,cAAc,CAAC,CAsNzB;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAER;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAKR"}
|
package/dist/agent.js
CHANGED
|
@@ -11,13 +11,14 @@ import { reportUserFacingError } from "./observability/sentry.js";
|
|
|
11
11
|
import { createExecutor, } from "./sandbox/index.js";
|
|
12
12
|
import { createMountedRuntimePathContext } from "./sandbox/path-context.js";
|
|
13
13
|
import { addLifecycleBreadcrumb, metricAttributes, updateActiveSpanAttribution, } from "./observability/sentry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { AgentMemoryFileManager } from "./sessions/agent-memory-file-manager.js";
|
|
15
15
|
import { extractSessionUuid, openManagedSession, } from "./sessions/store.js";
|
|
16
16
|
import { shouldSurfaceToolDiagnostic } from "./tool-diagnostics.js";
|
|
17
17
|
import { createMikanTools } from "./tools/index.js";
|
|
18
18
|
import * as Sentry from "@sentry/node";
|
|
19
19
|
import { formatLocalTimestamp } from "./utils/date.js";
|
|
20
20
|
import { resolveConfiguredModel } from "./model-registry.js";
|
|
21
|
+
import { emitAgentEvent } from "./agent-events.js";
|
|
21
22
|
const IMAGE_MIME_TYPES = {
|
|
22
23
|
jpg: "image/jpeg",
|
|
23
24
|
jpeg: "image/jpeg",
|
|
@@ -292,7 +293,7 @@ Maintain ${workspaceRoot}/SYSTEM.md to log all environment modifications:
|
|
|
292
293
|
Update this file whenever you modify the environment. On fresh container, read it first to restore your setup.
|
|
293
294
|
|
|
294
295
|
## Log Queries (for older history)
|
|
295
|
-
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","
|
|
296
|
+
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isMessagingBot":false}\`
|
|
296
297
|
The log contains user messages and your final responses (not tool calls/results).
|
|
297
298
|
Use \`log.jsonl\` for quick grep-style history. Use \`${conversationPath}/sessions/\` when you need structured turns, tool outputs, or thread/session lineage.
|
|
298
299
|
${isContainerLike || isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
@@ -439,7 +440,7 @@ function createEmptyUsageTotals() {
|
|
|
439
440
|
}
|
|
440
441
|
function createRunState() {
|
|
441
442
|
return {
|
|
442
|
-
|
|
443
|
+
responder: null,
|
|
443
444
|
logCtx: null,
|
|
444
445
|
queue: null,
|
|
445
446
|
pendingTools: new Map(),
|
|
@@ -452,8 +453,8 @@ function createRunState() {
|
|
|
452
453
|
triggerAttribution: undefined,
|
|
453
454
|
};
|
|
454
455
|
}
|
|
455
|
-
function resetRunState(runState,
|
|
456
|
-
runState.
|
|
456
|
+
function resetRunState(runState, responder, sessionConversation, userName, sessionUuid, triggerAttribution) {
|
|
457
|
+
runState.responder = responder;
|
|
457
458
|
runState.logCtx = {
|
|
458
459
|
conversationId: sessionConversation,
|
|
459
460
|
userName,
|
|
@@ -469,7 +470,7 @@ function resetRunState(runState, responseCtx, sessionConversation, userName, ses
|
|
|
469
470
|
runState.finalResponseHandledByTool = false;
|
|
470
471
|
runState.triggerAttribution = triggerAttribution;
|
|
471
472
|
}
|
|
472
|
-
function createRunQueue(
|
|
473
|
+
function createRunQueue(responder, runState) {
|
|
473
474
|
let queueChain = Promise.resolve();
|
|
474
475
|
return {
|
|
475
476
|
queue: {
|
|
@@ -484,7 +485,7 @@ function createRunQueue(responseCtx, runState) {
|
|
|
484
485
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
485
486
|
log.logWarning(`API error (${errorContext})`, errMsg);
|
|
486
487
|
try {
|
|
487
|
-
await
|
|
488
|
+
await responder.respondDiagnostic(`Error: ${errMsg}`, { style: "error" });
|
|
488
489
|
}
|
|
489
490
|
catch {
|
|
490
491
|
// Ignore
|
|
@@ -558,7 +559,7 @@ export function appendTriggerAttribution(text, triggerAttribution) {
|
|
|
558
559
|
return text;
|
|
559
560
|
return `${text.trimEnd()}\n\n${suffix}`;
|
|
560
561
|
}
|
|
561
|
-
async function finalizeRunResponse(
|
|
562
|
+
async function finalizeRunResponse(responder, session, runState, options) {
|
|
562
563
|
if (runState.stopReason === "error" && runState.errorMessage) {
|
|
563
564
|
if (!runState.reportedLlmError) {
|
|
564
565
|
runState.reportedLlmError = true;
|
|
@@ -580,8 +581,8 @@ async function finalizeRunResponse(responseCtx, session, runState, options) {
|
|
|
580
581
|
});
|
|
581
582
|
}
|
|
582
583
|
try {
|
|
583
|
-
await
|
|
584
|
-
await
|
|
584
|
+
await responder.replaceResponse("_Sorry, something went wrong_");
|
|
585
|
+
await responder.respondDiagnostic(`Error: ${runState.errorMessage}`, {
|
|
585
586
|
style: "error",
|
|
586
587
|
});
|
|
587
588
|
}
|
|
@@ -610,7 +611,7 @@ async function finalizeRunResponse(responseCtx, session, runState, options) {
|
|
|
610
611
|
}
|
|
611
612
|
if (finalText.trim() === "[SILENT]" || finalText.trim().startsWith("[SILENT]")) {
|
|
612
613
|
try {
|
|
613
|
-
await
|
|
614
|
+
await responder.deleteResponse();
|
|
614
615
|
log.logInfo("Silent response - deleted message and thread");
|
|
615
616
|
}
|
|
616
617
|
catch (err) {
|
|
@@ -622,7 +623,7 @@ async function finalizeRunResponse(responseCtx, session, runState, options) {
|
|
|
622
623
|
if (!finalText.trim())
|
|
623
624
|
return;
|
|
624
625
|
try {
|
|
625
|
-
await
|
|
626
|
+
await responder.replaceResponse(appendTriggerAttribution(finalText, options?.triggerAttribution), { createOverflowLink: options?.createOverflowLink });
|
|
626
627
|
}
|
|
627
628
|
catch (err) {
|
|
628
629
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -642,7 +643,7 @@ async function finalizeRunResponse(responseCtx, session, runState, options) {
|
|
|
642
643
|
}
|
|
643
644
|
}
|
|
644
645
|
async function reportUsageSummary(ctx) {
|
|
645
|
-
const { session, runState,
|
|
646
|
+
const { session, runState, responder, platform, model, agentConfig, sessionConversation, sessionUuid, waitForQueue, } = ctx;
|
|
646
647
|
const lastAssistantMessage = session.messages
|
|
647
648
|
.slice()
|
|
648
649
|
.toReversed()
|
|
@@ -684,7 +685,7 @@ async function reportUsageSummary(ctx) {
|
|
|
684
685
|
});
|
|
685
686
|
const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
|
|
686
687
|
if (platform.diagnostics?.showUsageSummary === true) {
|
|
687
|
-
runState.queue.enqueue(() =>
|
|
688
|
+
runState.queue.enqueue(() => responder.respondDiagnostic(summary, { style: "muted" }), "usage summary");
|
|
688
689
|
await waitForQueue();
|
|
689
690
|
}
|
|
690
691
|
}
|
|
@@ -696,7 +697,7 @@ function reloadSessionMessages(sessionManager, conversationId, agent) {
|
|
|
696
697
|
}
|
|
697
698
|
}
|
|
698
699
|
async function prepareRunContext(params) {
|
|
699
|
-
const { message,
|
|
700
|
+
const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, sessionManager, session, agent, setEventContext, setSandboxContext, setUploadFunction, setBlockKitResponseFunction, } = params;
|
|
700
701
|
let pathContext = params.pathContext;
|
|
701
702
|
const sessionConversation = message.sessionKey.split(":")[0];
|
|
702
703
|
await mkdir(join(conversationDir, "scratch"), { recursive: true });
|
|
@@ -723,19 +724,19 @@ async function prepareRunContext(params) {
|
|
|
723
724
|
setSandboxContext({ conversationId, userId: message.userId });
|
|
724
725
|
setUploadFunction(async (filePath, title) => {
|
|
725
726
|
const hostPath = translateAttachPathToHost(filePath, pathContext);
|
|
726
|
-
await
|
|
727
|
+
await responder.uploadFile(hostPath, title);
|
|
727
728
|
});
|
|
728
729
|
setBlockKitResponseFunction(async (response) => {
|
|
729
|
-
if (platform.name === "slack" &&
|
|
730
|
-
await
|
|
730
|
+
if (platform.name === "slack" && responder.respondBlockKit) {
|
|
731
|
+
await responder.respondBlockKit(response);
|
|
731
732
|
}
|
|
732
733
|
else {
|
|
733
|
-
await
|
|
734
|
+
await responder.replaceResponse(response.text);
|
|
734
735
|
}
|
|
735
736
|
runState.finalResponseHandledByTool = true;
|
|
736
737
|
});
|
|
737
|
-
resetRunState(runState,
|
|
738
|
-
const runQueue = createRunQueue(
|
|
738
|
+
resetRunState(runState, responder, sessionConversation, message.userName, sessionUuid, triggerAttribution);
|
|
739
|
+
const runQueue = createRunQueue(responder, runState);
|
|
739
740
|
runState.queue = runQueue.queue;
|
|
740
741
|
log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
|
|
741
742
|
log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);
|
|
@@ -750,16 +751,33 @@ async function prepareRunContext(params) {
|
|
|
750
751
|
pathContext,
|
|
751
752
|
};
|
|
752
753
|
}
|
|
754
|
+
function formatAgentActorName(userName, fallback) {
|
|
755
|
+
return userName ? `DM:${userName}` : fallback;
|
|
756
|
+
}
|
|
757
|
+
function sendAgentEvent(payload) {
|
|
758
|
+
emitAgentEvent({ source: "mikan", ...payload });
|
|
759
|
+
}
|
|
753
760
|
function attachSessionEventHandlers(params) {
|
|
754
761
|
const { session, runState, model, agentConfig } = params;
|
|
755
762
|
session.subscribe(async (event) => {
|
|
756
|
-
if (!runState.
|
|
763
|
+
if (!runState.responder || !runState.logCtx || !runState.queue)
|
|
757
764
|
return;
|
|
758
|
-
const {
|
|
765
|
+
const { responder, logCtx, queue, pendingTools } = runState;
|
|
759
766
|
const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };
|
|
767
|
+
const agentEventSessionId = logCtx.sessionId ?? logCtx.conversationId;
|
|
760
768
|
if (event.type === "tool_execution_start") {
|
|
761
769
|
const args = (event.args ?? {});
|
|
762
770
|
const label = args.label || event.toolName;
|
|
771
|
+
sendAgentEvent({
|
|
772
|
+
sessionId: agentEventSessionId,
|
|
773
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
774
|
+
event: {
|
|
775
|
+
kind: "toolStart",
|
|
776
|
+
toolId: event.toolCallId,
|
|
777
|
+
toolName: event.toolName,
|
|
778
|
+
input: { label },
|
|
779
|
+
},
|
|
780
|
+
});
|
|
763
781
|
pendingTools.set(event.toolCallId, {
|
|
764
782
|
toolName: event.toolName,
|
|
765
783
|
args: event.args,
|
|
@@ -775,6 +793,11 @@ function attachSessionEventHandlers(params) {
|
|
|
775
793
|
if (event.type === "tool_execution_end") {
|
|
776
794
|
const resultStr = extractToolResultText(event.result);
|
|
777
795
|
const pending = pendingTools.get(event.toolCallId);
|
|
796
|
+
sendAgentEvent({
|
|
797
|
+
sessionId: agentEventSessionId,
|
|
798
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
799
|
+
event: { kind: "toolEnd", toolId: event.toolCallId },
|
|
800
|
+
});
|
|
778
801
|
pendingTools.delete(event.toolCallId);
|
|
779
802
|
const durationMs = pending ? Date.now() - pending.startTime : 0;
|
|
780
803
|
Sentry.metrics.count("agent.tool.calls", 1, {
|
|
@@ -812,16 +835,21 @@ function attachSessionEventHandlers(params) {
|
|
|
812
835
|
isError: event.isError,
|
|
813
836
|
durationMs,
|
|
814
837
|
};
|
|
815
|
-
queue.enqueue(() =>
|
|
838
|
+
queue.enqueue(() => responder.respondToolResult(toolResult), "tool result diagnostic");
|
|
816
839
|
}
|
|
817
840
|
if (event.isError && shouldSurfaceToolDiagnostic(event.toolName)) {
|
|
818
|
-
queue.enqueue(() =>
|
|
841
|
+
queue.enqueue(() => responder.respond(`_Error: ${truncate(resultStr, 200)}_`), "tool error");
|
|
819
842
|
}
|
|
820
843
|
return;
|
|
821
844
|
}
|
|
822
845
|
if (event.type === "message_start") {
|
|
823
846
|
if (event.message.role === "assistant") {
|
|
824
847
|
runState.llmCallCount += 1;
|
|
848
|
+
sendAgentEvent({
|
|
849
|
+
sessionId: agentEventSessionId,
|
|
850
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
851
|
+
event: { kind: "sessionStart" },
|
|
852
|
+
});
|
|
825
853
|
addLifecycleBreadcrumb("agent.llm.call.started", {
|
|
826
854
|
call_index: runState.llmCallCount,
|
|
827
855
|
provider: model.provider,
|
|
@@ -836,9 +864,9 @@ function attachSessionEventHandlers(params) {
|
|
|
836
864
|
const assistantMessageEvent = event.assistantMessageEvent;
|
|
837
865
|
if (assistantMessageEvent?.type === "text_delta" &&
|
|
838
866
|
assistantMessageEvent.delta &&
|
|
839
|
-
|
|
867
|
+
responder.appendResponseDelta) {
|
|
840
868
|
queue.enqueue(async () => {
|
|
841
|
-
await
|
|
869
|
+
await responder.appendResponseDelta?.(assistantMessageEvent.delta ?? "");
|
|
842
870
|
}, "response delta");
|
|
843
871
|
}
|
|
844
872
|
return;
|
|
@@ -914,21 +942,26 @@ function attachSessionEventHandlers(params) {
|
|
|
914
942
|
const text = textParts.join("\n");
|
|
915
943
|
for (const thinking of thinkingParts) {
|
|
916
944
|
log.logThinking(logCtx, thinking);
|
|
917
|
-
queue.enqueue(() =>
|
|
918
|
-
queue.enqueue(() =>
|
|
945
|
+
queue.enqueue(() => responder.respond(`_${thinking}_`), "thinking main");
|
|
946
|
+
queue.enqueue(() => responder.respondDiagnostic(`_${thinking}_`), "thinking diagnostic");
|
|
919
947
|
}
|
|
920
948
|
if (text.trim() && !hasToolCall) {
|
|
921
949
|
if (runState.finalResponseHandledByTool)
|
|
922
950
|
return;
|
|
923
951
|
const finalText = appendTriggerAttribution(text, runState.triggerAttribution);
|
|
924
952
|
log.logResponse(logCtx, text);
|
|
925
|
-
|
|
953
|
+
sendAgentEvent({
|
|
954
|
+
sessionId: agentEventSessionId,
|
|
955
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
956
|
+
event: { kind: "turnEnd" },
|
|
957
|
+
});
|
|
958
|
+
if (responder.finishResponse) {
|
|
926
959
|
queue.enqueue(async () => {
|
|
927
|
-
await
|
|
960
|
+
await responder.finishResponse?.(finalText);
|
|
928
961
|
}, "response finish");
|
|
929
962
|
}
|
|
930
963
|
else {
|
|
931
|
-
queue.enqueue(() =>
|
|
964
|
+
queue.enqueue(() => responder.respond(finalText), "response main");
|
|
932
965
|
}
|
|
933
966
|
}
|
|
934
967
|
}
|
|
@@ -936,7 +969,7 @@ function attachSessionEventHandlers(params) {
|
|
|
936
969
|
}
|
|
937
970
|
if (event.type === "compaction_start") {
|
|
938
971
|
log.logInfo(`Auto-compaction started (reason: ${event.reason})`);
|
|
939
|
-
queue.enqueue(() =>
|
|
972
|
+
queue.enqueue(() => responder.respond("_Compacting context..._"), "compaction start");
|
|
940
973
|
return;
|
|
941
974
|
}
|
|
942
975
|
if (event.type === "compaction_end") {
|
|
@@ -950,7 +983,12 @@ function attachSessionEventHandlers(params) {
|
|
|
950
983
|
}
|
|
951
984
|
if (event.type === "auto_retry_start") {
|
|
952
985
|
log.logWarning(`Retrying (${event.attempt}/${event.maxAttempts})`, event.errorMessage);
|
|
953
|
-
|
|
986
|
+
sendAgentEvent({
|
|
987
|
+
sessionId: agentEventSessionId,
|
|
988
|
+
actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
|
|
989
|
+
event: { kind: "sessionStart" },
|
|
990
|
+
});
|
|
991
|
+
queue.enqueue(() => responder.respond(`_Retrying (${event.attempt}/${event.maxAttempts})..._`), "retry");
|
|
954
992
|
}
|
|
955
993
|
});
|
|
956
994
|
}
|
|
@@ -979,7 +1017,7 @@ function extractToolResultText(result) {
|
|
|
979
1017
|
return JSON.stringify(result);
|
|
980
1018
|
}
|
|
981
1019
|
/**
|
|
982
|
-
* Create a new
|
|
1020
|
+
* Create a new PiAgentWrapper for a channel.
|
|
983
1021
|
* Sets up the session and subscribes to events once.
|
|
984
1022
|
*
|
|
985
1023
|
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
@@ -1016,7 +1054,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1016
1054
|
sessionManager.appendSessionInfo(threadSessionName);
|
|
1017
1055
|
}
|
|
1018
1056
|
const sessionUuid = extractSessionUuid(contextFile);
|
|
1019
|
-
const chatSessionManager = new
|
|
1057
|
+
const chatSessionManager = new AgentMemoryFileManager();
|
|
1020
1058
|
const settingsManager = SettingsManager.inMemory();
|
|
1021
1059
|
const { agent, session } = await createConfiguredAgentSession({
|
|
1022
1060
|
conversationId,
|
|
@@ -1042,10 +1080,10 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1042
1080
|
currentMessageId,
|
|
1043
1081
|
});
|
|
1044
1082
|
},
|
|
1045
|
-
async run(message,
|
|
1083
|
+
async run(message, responder, platform) {
|
|
1046
1084
|
const prepared = await prepareRunContext({
|
|
1047
1085
|
message,
|
|
1048
|
-
|
|
1086
|
+
responder,
|
|
1049
1087
|
platform,
|
|
1050
1088
|
conversationId,
|
|
1051
1089
|
conversationDir,
|
|
@@ -1082,6 +1120,11 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1082
1120
|
attachment_count: message.attachments?.length ?? 0,
|
|
1083
1121
|
image_attachment_count: prepared.imageAttachments.length,
|
|
1084
1122
|
});
|
|
1123
|
+
sendAgentEvent({
|
|
1124
|
+
sessionId: sessionUuid,
|
|
1125
|
+
actorName: formatAgentActorName(message.userName, prepared.sessionConversation),
|
|
1126
|
+
event: { kind: "sessionStart" },
|
|
1127
|
+
});
|
|
1085
1128
|
await session.prompt(prepared.userMessage, prepared.imageAttachments.length > 0 ? { images: prepared.imageAttachments } : undefined);
|
|
1086
1129
|
// Wait for queued messages
|
|
1087
1130
|
await prepared.runQueue.wait();
|
|
@@ -1093,7 +1136,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1093
1136
|
return `${sessionViewPortalBaseUrl}/session?token=${token.token}`;
|
|
1094
1137
|
}
|
|
1095
1138
|
: undefined;
|
|
1096
|
-
await finalizeRunResponse(
|
|
1139
|
+
await finalizeRunResponse(responder, session, runState, {
|
|
1097
1140
|
triggerAttribution: prepared.triggerAttribution,
|
|
1098
1141
|
createOverflowLink,
|
|
1099
1142
|
platform: platform.name,
|
|
@@ -1104,7 +1147,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1104
1147
|
await reportUsageSummary({
|
|
1105
1148
|
session,
|
|
1106
1149
|
runState,
|
|
1107
|
-
|
|
1150
|
+
responder,
|
|
1108
1151
|
platform,
|
|
1109
1152
|
model,
|
|
1110
1153
|
agentConfig,
|
|
@@ -1113,7 +1156,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
1113
1156
|
waitForQueue: () => prepared.runQueue.wait(),
|
|
1114
1157
|
});
|
|
1115
1158
|
// Clear run state
|
|
1116
|
-
runState.
|
|
1159
|
+
runState.responder = null;
|
|
1117
1160
|
runState.logCtx = null;
|
|
1118
1161
|
runState.queue = null;
|
|
1119
1162
|
return { stopReason: runState.stopReason, errorMessage: runState.errorMessage };
|