@geminixiang/mikan 0.2.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 +324 -0
- package/LICENSE +22 -0
- package/README.md +297 -0
- package/dist/adapter.d.ts +134 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +2 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/discord/bot.d.ts +63 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -0
- package/dist/adapters/discord/bot.js +577 -0
- package/dist/adapters/discord/bot.js.map +1 -0
- package/dist/adapters/discord/context.d.ts +9 -0
- package/dist/adapters/discord/context.d.ts.map +1 -0
- package/dist/adapters/discord/context.js +245 -0
- package/dist/adapters/discord/context.js.map +1 -0
- package/dist/adapters/discord/index.d.ts +3 -0
- package/dist/adapters/discord/index.d.ts.map +1 -0
- package/dist/adapters/discord/index.js +3 -0
- package/dist/adapters/discord/index.js.map +1 -0
- package/dist/adapters/shared.d.ts +91 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +191 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +139 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -0
- package/dist/adapters/slack/bot.js +1272 -0
- package/dist/adapters/slack/bot.js.map +1 -0
- package/dist/adapters/slack/branch-manager.d.ts +28 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +117 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +12 -0
- package/dist/adapters/slack/context.d.ts.map +1 -0
- package/dist/adapters/slack/context.js +327 -0
- package/dist/adapters/slack/context.js.map +1 -0
- package/dist/adapters/slack/index.d.ts +3 -0
- package/dist/adapters/slack/index.d.ts.map +1 -0
- package/dist/adapters/slack/index.js +3 -0
- package/dist/adapters/slack/index.js.map +1 -0
- package/dist/adapters/slack/session.d.ts +38 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +66 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts +12 -0
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -0
- package/dist/adapters/slack/tools/attach.js +40 -0
- package/dist/adapters/slack/tools/attach.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts +51 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -0
- package/dist/adapters/telegram/bot.js +430 -0
- package/dist/adapters/telegram/bot.js.map +1 -0
- package/dist/adapters/telegram/context.d.ts +9 -0
- package/dist/adapters/telegram/context.d.ts.map +1 -0
- package/dist/adapters/telegram/context.js +190 -0
- package/dist/adapters/telegram/context.js.map +1 -0
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/adapters/telegram/index.d.ts +3 -0
- package/dist/adapters/telegram/index.d.ts.map +1 -0
- package/dist/adapters/telegram/index.js +3 -0
- package/dist/adapters/telegram/index.js.map +1 -0
- package/dist/agent.d.ts +36 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +1147 -0
- package/dist/agent.js.map +1 -0
- package/dist/commands/auto-reply.d.ts +5 -0
- package/dist/commands/auto-reply.d.ts.map +1 -0
- package/dist/commands/auto-reply.js +79 -0
- package/dist/commands/auto-reply.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +18 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +91 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +110 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +24 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/parse.d.ts +7 -0
- package/dist/commands/parse.d.ts.map +1 -0
- package/dist/commands/parse.js +17 -0
- package/dist/commands/parse.js.map +1 -0
- package/dist/commands/registry.d.ts +4 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +9 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +83 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts +5 -0
- package/dist/commands/session-view.d.ts.map +1 -0
- package/dist/commands/session-view.js +62 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +41 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +8 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +14 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +370 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -0
- package/dist/context.js.map +1 -0
- package/dist/conversation-history.d.ts +16 -0
- package/dist/conversation-history.d.ts.map +1 -0
- package/dist/conversation-history.js +144 -0
- package/dist/conversation-history.js.map +1 -0
- package/dist/download.d.ts +2 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +89 -0
- package/dist/download.js.map +1 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -0
- package/dist/events.d.ts +85 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +483 -0
- package/dist/events.js.map +1 -0
- package/dist/execution-resolver.d.ts +25 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +167 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/file-guards.d.ts +9 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +56 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts +2 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +10 -0
- package/dist/instrument.js.map +1 -0
- package/dist/log.d.ts +36 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +206 -0
- package/dist/log.js.map +1 -0
- package/dist/login/index.d.ts +42 -0
- package/dist/login/index.d.ts.map +1 -0
- package/dist/login/index.js +239 -0
- package/dist/login/index.js.map +1 -0
- package/dist/login/portal.d.ts +19 -0
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1544 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/login/session.d.ts +26 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/login/session.js +56 -0
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +366 -0
- package/dist/main.js.map +1 -0
- package/dist/provisioner.d.ts +83 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +500 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/runtime/conversation-orchestrator.d.ts +40 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +183 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +26 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +221 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +15 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +138 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/container.d.ts +16 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +138 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +17 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +212 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +11 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +89 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +22 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +54 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/path-context.d.ts +4 -0
- package/dist/sandbox/path-context.d.ts.map +1 -0
- package/dist/sandbox/path-context.js +20 -0
- package/dist/sandbox/path-context.js.map +1 -0
- package/dist/sandbox/types.d.ts +67 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sentry.d.ts +50 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +257 -0
- package/dist/sentry.js.map +1 -0
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +7 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +16 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +1822 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +434 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +18 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +36 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/sessions/metadata.d.ts +15 -0
- package/dist/sessions/metadata.d.ts.map +1 -0
- package/dist/sessions/metadata.js +11 -0
- package/dist/sessions/metadata.js.map +1 -0
- package/dist/sessions/policy.d.ts +13 -0
- package/dist/sessions/policy.d.ts.map +1 -0
- package/dist/sessions/policy.js +23 -0
- package/dist/sessions/policy.js.map +1 -0
- package/dist/sessions/store.d.ts +103 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +349 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/store.d.ts +58 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +152 -0
- package/dist/store.js.map +1 -0
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +10 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +80 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +11 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +133 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/event.d.ts +62 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +138 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.d.ts +11 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +136 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/truncate.d.ts +57 -0
- package/dist/tools/truncate.d.ts.map +1 -0
- package/dist/tools/truncate.js +184 -0
- package/dist/tools/truncate.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +33 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/trigger.d.ts +31 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +98 -0
- package/dist/trigger.js.map +1 -0
- package/dist/ui-copy.d.ts +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +4 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +16 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +281 -0
- package/dist/vault.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { loadAgentConfig, loadAgentConfigForConversation } from "./config.js";
|
|
4
|
+
import { ensureDirExists, isRecord, readJsonFileIfExists } from "./file-guards.js";
|
|
5
|
+
import { DockerContainerManager } from "./provisioner.js";
|
|
6
|
+
import { createExecutor } from "./sandbox/index.js";
|
|
7
|
+
import { reportUserFacingError } from "./sentry.js";
|
|
8
|
+
import { normalizeSharedVaultName } from "./vault.js";
|
|
9
|
+
import { resolveActorVaultKey } from "./vault-routing.js";
|
|
10
|
+
export function readConversationWorkspaceMountMode(workspaceDir, conversationId) {
|
|
11
|
+
const globalDefault = readGlobalWorkspaceMountMode();
|
|
12
|
+
if (!workspaceDir) {
|
|
13
|
+
return globalDefault;
|
|
14
|
+
}
|
|
15
|
+
const conversationDir = join(workspaceDir, conversationId);
|
|
16
|
+
try {
|
|
17
|
+
return (loadAgentConfigForConversation(conversationDir).sandboxImageWorkspaceMount ?? globalDefault);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
const conversationSettingsPath = join(conversationDir, "settings.json");
|
|
21
|
+
const raw = readConversationSettingsFallback(conversationSettingsPath);
|
|
22
|
+
return raw?.sandbox?.image?.workspaceMount ?? globalDefault;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function readGlobalWorkspaceMountMode() {
|
|
26
|
+
try {
|
|
27
|
+
return loadAgentConfig().sandboxImageWorkspaceMount ?? "private";
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "private";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readConversationSettingsFallback(settingsPath) {
|
|
34
|
+
try {
|
|
35
|
+
return readJsonFileIfExists(settingsPath, (value) => isRecord(value), () => "Ignoring malformed conversation settings file while resolving workspace mount");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class ActorExecutionResolver {
|
|
42
|
+
constructor(baseConfig, vaultManager, provisioner, workspaceDir) {
|
|
43
|
+
this.baseConfig = baseConfig;
|
|
44
|
+
this.vaultManager = vaultManager;
|
|
45
|
+
this.provisioner = provisioner;
|
|
46
|
+
this.workspaceDir = workspaceDir;
|
|
47
|
+
this.ensuredConversationDirs = new Set();
|
|
48
|
+
}
|
|
49
|
+
async resolve(context) {
|
|
50
|
+
const vaultKey = resolveActorVaultKey(this.baseConfig, context.userId, context.conversationId);
|
|
51
|
+
this.ensureDefaultSharedVault(vaultKey);
|
|
52
|
+
const vault = this.vaultManager.resolve(vaultKey);
|
|
53
|
+
const config = this.resolveSandboxConfig(vaultKey);
|
|
54
|
+
const env = config.type !== "host" && vault && Object.keys(vault.env).length > 0 ? vault.env : undefined;
|
|
55
|
+
return createExecutor(config, env, this.buildEnsureReadyCallback(vaultKey, context.conversationId, config, vault));
|
|
56
|
+
}
|
|
57
|
+
ensureDefaultSharedVault(vaultKey) {
|
|
58
|
+
if (this.baseConfig.type !== "image" && this.baseConfig.type !== "cloudflare")
|
|
59
|
+
return;
|
|
60
|
+
if (this.vaultManager.hasEntry(vaultKey))
|
|
61
|
+
return;
|
|
62
|
+
let profile;
|
|
63
|
+
try {
|
|
64
|
+
profile = loadAgentConfig().defaultSharedVault;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!profile || normalizeSharedVaultName(profile) !== profile)
|
|
70
|
+
return;
|
|
71
|
+
this.vaultManager.copySharedVaultTo(profile, vaultKey);
|
|
72
|
+
}
|
|
73
|
+
resolveSandboxConfig(vaultKey) {
|
|
74
|
+
const config = this.vaultManager.getSandboxConfig(vaultKey, this.baseConfig);
|
|
75
|
+
if (this.baseConfig.type !== "image") {
|
|
76
|
+
return config;
|
|
77
|
+
}
|
|
78
|
+
if (config.type === "container") {
|
|
79
|
+
return config;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
type: "container",
|
|
83
|
+
container: DockerContainerManager.containerName(vaultKey),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
buildEnsureReadyCallback(vaultKey, conversationId, config, vault) {
|
|
87
|
+
if (this.baseConfig.type !== "image" || config.type !== "container") {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return async () => {
|
|
91
|
+
const expected = config.container || DockerContainerManager.containerName(vaultKey);
|
|
92
|
+
let actual;
|
|
93
|
+
try {
|
|
94
|
+
actual = await this.provisioner?.provision(vaultKey, {
|
|
95
|
+
containerName: expected,
|
|
96
|
+
mounts: this.resolveMounts(conversationId, vault),
|
|
97
|
+
conversationId,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
reportUserFacingError(err, {
|
|
102
|
+
domain: "sandbox",
|
|
103
|
+
surface: "sandbox_provision",
|
|
104
|
+
operation: "ensure_image_container_ready",
|
|
105
|
+
severity: "error",
|
|
106
|
+
context: {
|
|
107
|
+
sandboxType: "image",
|
|
108
|
+
resolvedSandboxType: config.type,
|
|
109
|
+
conversationId,
|
|
110
|
+
vaultKey,
|
|
111
|
+
expectedContainer: expected,
|
|
112
|
+
hasVault: Boolean(vault),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
if (actual && actual !== expected) {
|
|
118
|
+
throw new Error(`Provisioner returned container "${actual}" for container key "${vaultKey}", expected "${expected}"`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
resolveMounts(conversationId, vault) {
|
|
123
|
+
const mountsByTarget = new Map();
|
|
124
|
+
for (const mount of this.buildImageSandboxMounts(conversationId)) {
|
|
125
|
+
mountsByTarget.set(mount.target, mount);
|
|
126
|
+
}
|
|
127
|
+
for (const mount of vault?.mounts ?? []) {
|
|
128
|
+
if (!existsSync(mount.source)) {
|
|
129
|
+
reportUserFacingError(new Error("Vault mount source is missing"), {
|
|
130
|
+
domain: "sandbox",
|
|
131
|
+
surface: "vault_injection",
|
|
132
|
+
operation: "resolve_mounts",
|
|
133
|
+
severity: "warning",
|
|
134
|
+
context: {
|
|
135
|
+
sandboxType: "image",
|
|
136
|
+
conversationId,
|
|
137
|
+
target: mount.target,
|
|
138
|
+
hasVault: Boolean(vault),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
mountsByTarget.set(mount.target, { source: mount.source, target: mount.target });
|
|
144
|
+
}
|
|
145
|
+
return [...mountsByTarget.values()];
|
|
146
|
+
}
|
|
147
|
+
buildImageSandboxMounts(conversationId) {
|
|
148
|
+
if (!this.workspaceDir) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
if (readConversationWorkspaceMountMode(this.workspaceDir, conversationId) === "full") {
|
|
152
|
+
return [{ source: this.workspaceDir, target: "/workspace" }];
|
|
153
|
+
}
|
|
154
|
+
const conversationDir = join(this.workspaceDir, conversationId);
|
|
155
|
+
if (!this.ensuredConversationDirs.has(conversationId)) {
|
|
156
|
+
ensureDirExists(conversationDir);
|
|
157
|
+
this.ensuredConversationDirs.add(conversationId);
|
|
158
|
+
}
|
|
159
|
+
return [
|
|
160
|
+
{ source: join(this.workspaceDir, "MEMORY.md"), target: "/workspace/MEMORY.md" },
|
|
161
|
+
{ source: join(this.workspaceDir, "skills"), target: "/workspace/skills" },
|
|
162
|
+
{ source: join(this.workspaceDir, "events"), target: "/workspace/events" },
|
|
163
|
+
{ source: conversationDir, target: `/workspace/${conversationId}` },
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=execution-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-resolver.js","sourceRoot":"","sources":["../src/execution-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAuB,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAqC,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAyC,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAU1D,MAAM,UAAU,kCAAkC,CAChD,YAAgC,EAChC,cAAsB;IAEtB,MAAM,aAAa,GAAG,4BAA4B,EAAE,CAAC;IACrD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,OAAO,CACL,8BAA8B,CAAC,eAAe,CAAC,CAAC,0BAA0B,IAAI,aAAa,CAC5F,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,wBAAwB,GAAG,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,gCAAgC,CAAC,wBAAwB,CAAC,CAAC;QACvE,OAAO,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,IAAI,aAAa,CAAC;IAC9D,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B;IACnC,IAAI,CAAC;QACH,OAAO,eAAe,EAAE,CAAC,0BAA0B,IAAI,SAAS,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,gCAAgC,CACvC,YAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,oBAAoB,CACzB,YAAY,EACZ,CAAC,KAAK,EAAmF,EAAE,CACzF,QAAQ,CAAC,KAAK,CAAC,EACjB,GAAG,EAAE,CAAC,+EAA+E,CACtF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,sBAAsB;IAGjC,YACU,UAAyB,EACzB,YAA0B,EAC1B,WAAoC,EACpC,YAAqB;QAHrB,eAAU,GAAV,UAAU,CAAe;QACzB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,gBAAW,GAAX,WAAW,CAAyB;QACpC,iBAAY,GAAZ,YAAY,CAAS;QANd,4BAAuB,GAAG,IAAI,GAAG,EAAU,CAAC;IAO1D,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,OAAqB;QACjC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QAC/F,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC;QAExC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,GAAG,GACP,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/F,OAAO,cAAc,CACnB,MAAM,EACN,GAAG,EACH,IAAI,CAAC,wBAAwB,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,CAC/E,CAAC;IACJ,CAAC;IAEO,wBAAwB,CAAC,QAAgB;QAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,YAAY;YAAE,OAAO;QACtF,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO;QAEjD,IAAI,OAA2B,CAAC;QAChC,IAAI,CAAC;YACH,OAAO,GAAG,eAAe,EAAE,CAAC,kBAAkB,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,wBAAwB,CAAC,OAAO,CAAC,KAAK,OAAO;YAAE,OAAO;QAEtE,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IAEO,oBAAoB,CAAC,QAAgB;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACrC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,sBAAsB,CAAC,aAAa,CAAC,QAAQ,CAAC;SAC1D,CAAC;IACJ,CAAC;IAEO,wBAAwB,CAC9B,QAAgB,EAChB,cAAsB,EACtB,MAAqB,EACrB,KAAqB;QAErB,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACpE,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,KAAK,IAAI,EAAE;YAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,IAAI,sBAAsB,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YACpF,IAAI,MAA0B,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,QAAQ,EAAE;oBACnD,aAAa,EAAE,QAAQ;oBACvB,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,KAAK,CAAC;oBACjD,cAAc;iBACf,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,qBAAqB,CAAC,GAAG,EAAE;oBACzB,MAAM,EAAE,SAAS;oBACjB,OAAO,EAAE,mBAAmB;oBAC5B,SAAS,EAAE,8BAA8B;oBACzC,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE;wBACP,WAAW,EAAE,OAAO;wBACpB,mBAAmB,EAAE,MAAM,CAAC,IAAI;wBAChC,cAAc;wBACd,QAAQ;wBACR,iBAAiB,EAAE,QAAQ;wBAC3B,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC;qBACzB;iBACF,CAAC,CAAC;gBACH,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,IAAI,MAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACb,mCAAmC,MAAM,wBAAwB,QAAQ,gBAAgB,QAAQ,GAAG,CACrG,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAEO,aAAa,CAAC,cAAsB,EAAE,KAAqB;QACjE,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0B,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,EAAE,CAAC;YACjE,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,qBAAqB,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,EAAE;oBAChE,MAAM,EAAE,SAAS;oBACjB,OAAO,EAAE,iBAAiB;oBAC1B,SAAS,EAAE,gBAAgB;oBAC3B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE;wBACP,WAAW,EAAE,OAAO;wBACpB,cAAc;wBACd,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC;qBACzB;iBACF,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IAEO,uBAAuB,CAAC,cAAsB;QACpD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,kCAAkC,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,KAAK,MAAM,EAAE,CAAC;YACrF,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YACtD,eAAe,CAAC,eAAe,CAAC,CAAC;YACjC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACnD,CAAC;QAED,OAAO;YACL,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,sBAAsB,EAAE;YAChF,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE;YAC1E,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE;YAC1E,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,cAAc,EAAE,EAAE;SACpE,CAAC;IACJ,CAAC;CACF","sourcesContent":["import { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { loadAgentConfig, loadAgentConfigForConversation } from \"./config.js\";\nimport { ensureDirExists, isRecord, readJsonFileIfExists } from \"./file-guards.js\";\nimport { DockerContainerManager, type ContainerMount } from \"./provisioner.js\";\nimport { createExecutor, type Executor, type SandboxConfig } from \"./sandbox/index.js\";\nimport { reportUserFacingError } from \"./sentry.js\";\nimport { normalizeSharedVaultName, type ResolvedVault, type VaultManager } from \"./vault.js\";\nimport { resolveActorVaultKey } from \"./vault-routing.js\";\n\nexport interface ActorContext {\n platform: string;\n userId: string;\n conversationId: string;\n}\n\nexport type ImageWorkspaceMountMode = \"private\" | \"full\";\n\nexport function readConversationWorkspaceMountMode(\n workspaceDir: string | undefined,\n conversationId: string,\n): ImageWorkspaceMountMode {\n const globalDefault = readGlobalWorkspaceMountMode();\n if (!workspaceDir) {\n return globalDefault;\n }\n\n const conversationDir = join(workspaceDir, conversationId);\n try {\n return (\n loadAgentConfigForConversation(conversationDir).sandboxImageWorkspaceMount ?? globalDefault\n );\n } catch {\n const conversationSettingsPath = join(conversationDir, \"settings.json\");\n const raw = readConversationSettingsFallback(conversationSettingsPath);\n return raw?.sandbox?.image?.workspaceMount ?? globalDefault;\n }\n}\n\nfunction readGlobalWorkspaceMountMode(): ImageWorkspaceMountMode {\n try {\n return loadAgentConfig().sandboxImageWorkspaceMount ?? \"private\";\n } catch {\n return \"private\";\n }\n}\n\nfunction readConversationSettingsFallback(\n settingsPath: string,\n): { sandbox?: { image?: { workspaceMount?: ImageWorkspaceMountMode } } } | undefined {\n try {\n return readJsonFileIfExists(\n settingsPath,\n (value): value is { sandbox?: { image?: { workspaceMount?: ImageWorkspaceMountMode } } } =>\n isRecord(value),\n () => \"Ignoring malformed conversation settings file while resolving workspace mount\",\n );\n } catch {\n return undefined;\n }\n}\n\nexport class ActorExecutionResolver {\n private readonly ensuredConversationDirs = new Set<string>();\n\n constructor(\n private baseConfig: SandboxConfig,\n private vaultManager: VaultManager,\n private provisioner?: DockerContainerManager,\n private workspaceDir?: string,\n ) {}\n\n async resolve(context: ActorContext): Promise<Executor> {\n const vaultKey = resolveActorVaultKey(this.baseConfig, context.userId, context.conversationId);\n this.ensureDefaultSharedVault(vaultKey);\n\n const vault = this.vaultManager.resolve(vaultKey);\n const config = this.resolveSandboxConfig(vaultKey);\n const env =\n config.type !== \"host\" && vault && Object.keys(vault.env).length > 0 ? vault.env : undefined;\n return createExecutor(\n config,\n env,\n this.buildEnsureReadyCallback(vaultKey, context.conversationId, config, vault),\n );\n }\n\n private ensureDefaultSharedVault(vaultKey: string): void {\n if (this.baseConfig.type !== \"image\" && this.baseConfig.type !== \"cloudflare\") return;\n if (this.vaultManager.hasEntry(vaultKey)) return;\n\n let profile: string | undefined;\n try {\n profile = loadAgentConfig().defaultSharedVault;\n } catch {\n return;\n }\n if (!profile || normalizeSharedVaultName(profile) !== profile) return;\n\n this.vaultManager.copySharedVaultTo(profile, vaultKey);\n }\n\n private resolveSandboxConfig(vaultKey: string): SandboxConfig {\n const config = this.vaultManager.getSandboxConfig(vaultKey, this.baseConfig);\n if (this.baseConfig.type !== \"image\") {\n return config;\n }\n\n if (config.type === \"container\") {\n return config;\n }\n\n return {\n type: \"container\",\n container: DockerContainerManager.containerName(vaultKey),\n };\n }\n\n private buildEnsureReadyCallback(\n vaultKey: string,\n conversationId: string,\n config: SandboxConfig,\n vault?: ResolvedVault,\n ): (() => Promise<void>) | undefined {\n if (this.baseConfig.type !== \"image\" || config.type !== \"container\") {\n return undefined;\n }\n\n return async () => {\n const expected = config.container || DockerContainerManager.containerName(vaultKey);\n let actual: string | undefined;\n try {\n actual = await this.provisioner?.provision(vaultKey, {\n containerName: expected,\n mounts: this.resolveMounts(conversationId, vault),\n conversationId,\n });\n } catch (err) {\n reportUserFacingError(err, {\n domain: \"sandbox\",\n surface: \"sandbox_provision\",\n operation: \"ensure_image_container_ready\",\n severity: \"error\",\n context: {\n sandboxType: \"image\",\n resolvedSandboxType: config.type,\n conversationId,\n vaultKey,\n expectedContainer: expected,\n hasVault: Boolean(vault),\n },\n });\n throw err;\n }\n if (actual && actual !== expected) {\n throw new Error(\n `Provisioner returned container \"${actual}\" for container key \"${vaultKey}\", expected \"${expected}\"`,\n );\n }\n };\n }\n\n private resolveMounts(conversationId: string, vault?: ResolvedVault): ContainerMount[] {\n const mountsByTarget = new Map<string, ContainerMount>();\n for (const mount of this.buildImageSandboxMounts(conversationId)) {\n mountsByTarget.set(mount.target, mount);\n }\n for (const mount of vault?.mounts ?? []) {\n if (!existsSync(mount.source)) {\n reportUserFacingError(new Error(\"Vault mount source is missing\"), {\n domain: \"sandbox\",\n surface: \"vault_injection\",\n operation: \"resolve_mounts\",\n severity: \"warning\",\n context: {\n sandboxType: \"image\",\n conversationId,\n target: mount.target,\n hasVault: Boolean(vault),\n },\n });\n continue;\n }\n mountsByTarget.set(mount.target, { source: mount.source, target: mount.target });\n }\n return [...mountsByTarget.values()];\n }\n\n private buildImageSandboxMounts(conversationId: string): ContainerMount[] {\n if (!this.workspaceDir) {\n return [];\n }\n\n if (readConversationWorkspaceMountMode(this.workspaceDir, conversationId) === \"full\") {\n return [{ source: this.workspaceDir, target: \"/workspace\" }];\n }\n\n const conversationDir = join(this.workspaceDir, conversationId);\n if (!this.ensuredConversationDirs.has(conversationId)) {\n ensureDirExists(conversationDir);\n this.ensuredConversationDirs.add(conversationId);\n }\n\n return [\n { source: join(this.workspaceDir, \"MEMORY.md\"), target: \"/workspace/MEMORY.md\" },\n { source: join(this.workspaceDir, \"skills\"), target: \"/workspace/skills\" },\n { source: join(this.workspaceDir, \"events\"), target: \"/workspace/events\" },\n { source: conversationDir, target: `/workspace/${conversationId}` },\n ];\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Static, TSchema } from "@sinclair/typebox";
|
|
2
|
+
export declare function ensureDirExists(dir: string): void;
|
|
3
|
+
export declare function readTextFileIfExists(path: string): string | undefined;
|
|
4
|
+
export declare function readJsonFileIfExists<T>(path: string, validate: (value: unknown) => value is T, malformedMessage: (detail: string) => string): T | undefined;
|
|
5
|
+
export declare function readJsonSchemaFileIfExists<T extends TSchema>(path: string, schema: T, malformedMessage: (detail: string) => string): Static<T> | undefined;
|
|
6
|
+
export declare function parseJsonValue<T>(raw: string, validate: (value: unknown) => value is T, malformedMessage: (detail: string) => string): T;
|
|
7
|
+
export declare function parseJsonSchemaValue<T extends TSchema>(raw: string, schema: T, malformedMessage: (detail: string) => string): Static<T>;
|
|
8
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
9
|
+
//# sourceMappingURL=file-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-guards.d.ts","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIzD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAC3C,CAAC,GAAG,SAAS,CAGf;AAED,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,OAAO,EAC1D,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAC3C,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAGvB;AAWD,wBAAgB,cAAc,CAAC,CAAC,EAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAC3C,CAAC,CAMH;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EACpD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,EACT,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAC3C,MAAM,CAAC,CAAC,CAAC,CAeX;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE","sourcesContent":["import type { Static, TSchema } from \"@sinclair/typebox\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport { existsSync, mkdirSync, readFileSync } from \"fs\";\n\nexport function ensureDirExists(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nexport function readTextFileIfExists(path: string): string | undefined {\n if (!existsSync(path)) {\n return undefined;\n }\n\n return readFileSync(path, \"utf-8\");\n}\n\nexport function readJsonFileIfExists<T>(\n path: string,\n validate: (value: unknown) => value is T,\n malformedMessage: (detail: string) => string,\n): T | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonValue(raw, validate, malformedMessage);\n}\n\nexport function readJsonSchemaFileIfExists<T extends TSchema>(\n path: string,\n schema: T,\n malformedMessage: (detail: string) => string,\n): Static<T> | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonSchemaValue(raw, schema, malformedMessage);\n}\n\nfunction parseJson(raw: string, malformedMessage: (detail: string) => string): unknown {\n try {\n return JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(malformedMessage(detail), { cause: err });\n }\n}\n\nexport function parseJsonValue<T>(\n raw: string,\n validate: (value: unknown) => value is T,\n malformedMessage: (detail: string) => string,\n): T {\n const parsed = parseJson(raw, malformedMessage);\n if (!validate(parsed)) {\n throw new Error(malformedMessage(\"unexpected JSON shape\"));\n }\n return parsed;\n}\n\nexport function parseJsonSchemaValue<T extends TSchema>(\n raw: string,\n schema: T,\n malformedMessage: (detail: string) => string,\n): Static<T> {\n const parsed = parseJson(raw, malformedMessage);\n if (!Value.Check(schema, parsed)) {\n let firstError: { path: string; message: string } | undefined;\n for (const err of Value.Errors(schema, parsed)) {\n firstError = err;\n break;\n }\n const detail =\n !firstError || firstError.path === \"\" || firstError.path === \"/\"\n ? \"unexpected JSON shape\"\n : `${firstError.path}: ${firstError.message}`;\n throw new Error(malformedMessage(detail));\n }\n return parsed;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
3
|
+
export function ensureDirExists(dir) {
|
|
4
|
+
if (!existsSync(dir)) {
|
|
5
|
+
mkdirSync(dir, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function readTextFileIfExists(path) {
|
|
9
|
+
if (!existsSync(path)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return readFileSync(path, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
export function readJsonFileIfExists(path, validate, malformedMessage) {
|
|
15
|
+
const raw = readTextFileIfExists(path);
|
|
16
|
+
return raw === undefined ? undefined : parseJsonValue(raw, validate, malformedMessage);
|
|
17
|
+
}
|
|
18
|
+
export function readJsonSchemaFileIfExists(path, schema, malformedMessage) {
|
|
19
|
+
const raw = readTextFileIfExists(path);
|
|
20
|
+
return raw === undefined ? undefined : parseJsonSchemaValue(raw, schema, malformedMessage);
|
|
21
|
+
}
|
|
22
|
+
function parseJson(raw, malformedMessage) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
28
|
+
throw new Error(malformedMessage(detail), { cause: err });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function parseJsonValue(raw, validate, malformedMessage) {
|
|
32
|
+
const parsed = parseJson(raw, malformedMessage);
|
|
33
|
+
if (!validate(parsed)) {
|
|
34
|
+
throw new Error(malformedMessage("unexpected JSON shape"));
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
export function parseJsonSchemaValue(raw, schema, malformedMessage) {
|
|
39
|
+
const parsed = parseJson(raw, malformedMessage);
|
|
40
|
+
if (!Value.Check(schema, parsed)) {
|
|
41
|
+
let firstError;
|
|
42
|
+
for (const err of Value.Errors(schema, parsed)) {
|
|
43
|
+
firstError = err;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
const detail = !firstError || firstError.path === "" || firstError.path === "/"
|
|
47
|
+
? "unexpected JSON shape"
|
|
48
|
+
: `${firstError.path}: ${firstError.message}`;
|
|
49
|
+
throw new Error(malformedMessage(detail));
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
export function isRecord(value) {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=file-guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-guards.js","sourceRoot":"","sources":["../src/file-guards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAEzD,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,QAAwC,EACxC,gBAA4C;IAE5C,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,IAAY,EACZ,MAAS,EACT,gBAA4C;IAE5C,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAC7F,CAAC;AAED,SAAS,SAAS,CAAC,GAAW,EAAE,gBAA4C;IAC1E,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,QAAwC,EACxC,gBAA4C;IAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAChD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,GAAW,EACX,MAAS,EACT,gBAA4C;IAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QACjC,IAAI,UAAyD,CAAC;QAC9D,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YAC/C,UAAU,GAAG,GAAG,CAAC;YACjB,MAAM;QACR,CAAC;QACD,MAAM,MAAM,GACV,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,EAAE,IAAI,UAAU,CAAC,IAAI,KAAK,GAAG;YAC9D,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { Static, TSchema } from \"@sinclair/typebox\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport { existsSync, mkdirSync, readFileSync } from \"fs\";\n\nexport function ensureDirExists(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nexport function readTextFileIfExists(path: string): string | undefined {\n if (!existsSync(path)) {\n return undefined;\n }\n\n return readFileSync(path, \"utf-8\");\n}\n\nexport function readJsonFileIfExists<T>(\n path: string,\n validate: (value: unknown) => value is T,\n malformedMessage: (detail: string) => string,\n): T | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonValue(raw, validate, malformedMessage);\n}\n\nexport function readJsonSchemaFileIfExists<T extends TSchema>(\n path: string,\n schema: T,\n malformedMessage: (detail: string) => string,\n): Static<T> | undefined {\n const raw = readTextFileIfExists(path);\n return raw === undefined ? undefined : parseJsonSchemaValue(raw, schema, malformedMessage);\n}\n\nfunction parseJson(raw: string, malformedMessage: (detail: string) => string): unknown {\n try {\n return JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(malformedMessage(detail), { cause: err });\n }\n}\n\nexport function parseJsonValue<T>(\n raw: string,\n validate: (value: unknown) => value is T,\n malformedMessage: (detail: string) => string,\n): T {\n const parsed = parseJson(raw, malformedMessage);\n if (!validate(parsed)) {\n throw new Error(malformedMessage(\"unexpected JSON shape\"));\n }\n return parsed;\n}\n\nexport function parseJsonSchemaValue<T extends TSchema>(\n raw: string,\n schema: T,\n malformedMessage: (detail: string) => string,\n): Static<T> {\n const parsed = parseJson(raw, malformedMessage);\n if (!Value.Check(schema, parsed)) {\n let firstError: { path: string; message: string } | undefined;\n for (const err of Value.Errors(schema, parsed)) {\n firstError = err;\n break;\n }\n const detail =\n !firstError || firstError.path === \"\" || firstError.path === \"/\"\n ? \"unexpected JSON shape\"\n : `${firstError.path}: ${firstError.message}`;\n throw new Error(malformedMessage(detail));\n }\n return parsed;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `content` to `targetPath` with mode 0600, even when `targetPath`
|
|
3
|
+
* already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel
|
|
4
|
+
* guarantees permissions at creation, not after a racy chmod) and then
|
|
5
|
+
* rename(2) into place for atomicity. Readers never see a torn write,
|
|
6
|
+
* and a crash mid-write leaves either the old file or a stray .tmp
|
|
7
|
+
* (cleaned by the next attempt or manually) — never a half-written target.
|
|
8
|
+
*/
|
|
9
|
+
export declare function atomicWritePrivateFile(targetPath: string, content: string): void;
|
|
10
|
+
//# sourceMappingURL=fs-atomic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.d.ts","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAiChF","sourcesContent":["import {\n closeSync,\n constants as fsConstants,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"fs\";\nimport { randomBytes } from \"crypto\";\nimport { basename, dirname, join } from \"path\";\n\nconst PRIVATE_FILE_MODE = 0o600;\n\n/**\n * Write `content` to `targetPath` with mode 0600, even when `targetPath`\n * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel\n * guarantees permissions at creation, not after a racy chmod) and then\n * rename(2) into place for atomicity. Readers never see a torn write,\n * and a crash mid-write leaves either the old file or a stray .tmp\n * (cleaned by the next attempt or manually) — never a half-written target.\n */\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore — original error is more informative\n }\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { closeSync, constants as fsConstants, openSync, renameSync, unlinkSync, writeSync, } from "fs";
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
import { basename, dirname, join } from "path";
|
|
4
|
+
const PRIVATE_FILE_MODE = 0o600;
|
|
5
|
+
/**
|
|
6
|
+
* Write `content` to `targetPath` with mode 0600, even when `targetPath`
|
|
7
|
+
* already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel
|
|
8
|
+
* guarantees permissions at creation, not after a racy chmod) and then
|
|
9
|
+
* rename(2) into place for atomicity. Readers never see a torn write,
|
|
10
|
+
* and a crash mid-write leaves either the old file or a stray .tmp
|
|
11
|
+
* (cleaned by the next attempt or manually) — never a half-written target.
|
|
12
|
+
*/
|
|
13
|
+
export function atomicWritePrivateFile(targetPath, content) {
|
|
14
|
+
const dir = dirname(targetPath);
|
|
15
|
+
const tmpPath = join(dir, `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
|
|
16
|
+
const fd = openSync(tmpPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, PRIVATE_FILE_MODE);
|
|
17
|
+
try {
|
|
18
|
+
writeSync(fd, content);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
try {
|
|
22
|
+
unlinkSync(tmpPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore — original error is more informative
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
closeSync(fd);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
renameSync(tmpPath, targetPath);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(tmpPath);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=fs-atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.js","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,IAAI,WAAW,EACxB,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE/C,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB,EAAE,OAAe;IACxE,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAClB,GAAG,EACH,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAChF,CAAC;IACF,MAAM,EAAE,GAAG,QAAQ,CACjB,OAAO,EACP,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,MAAM,EAC/D,iBAAiB,CAClB,CAAC;IACF,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["import {\n closeSync,\n constants as fsConstants,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"fs\";\nimport { randomBytes } from \"crypto\";\nimport { basename, dirname, join } from \"path\";\n\nconst PRIVATE_FILE_MODE = 0o600;\n\n/**\n * Write `content` to `targetPath` with mode 0600, even when `targetPath`\n * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel\n * guarantees permissions at creation, not after a racy chmod) and then\n * rename(2) into place for atomicity. Readers never see a torn write,\n * and a crash mid-write leaves either the old file or a stray .tmp\n * (cleaned by the next attempt or manually) — never a half-written target.\n */\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore — original error is more informative\n }\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { defaultCommandHandlers, dispatchCommand } from "./commands/index.js";
|
|
2
|
+
export type { CommandContext, CommandHandler, CommandServices } from "./commands/index.js";
|
|
3
|
+
export * from "./sessions/metadata.js";
|
|
4
|
+
export * from "./sessions/policy.js";
|
|
5
|
+
export * from "./sessions/store.js";
|
|
6
|
+
export { createSessionRuntime, type CreateSessionSandboxOptions, type RunSessionOptions, type SessionRuntime, type SessionRuntimeOptions, } from "./runtime/index.js";
|
|
7
|
+
export type { Bot, BotAdapters, BotEvent, BotHandler, ChatAdapter, ChatMessage, ChatResponseContext, ChatToolResult, ConversationKind, PlatformInfo, RunningSession, } from "./adapter.js";
|
|
8
|
+
export { SandboxError, createExecutor, getSandboxAdapters, parseSandboxArg, validateSandbox, } from "./sandbox/index.js";
|
|
9
|
+
export type { CloudflareSandboxConfig, ExecOptions, ExecResult, Executor, SandboxAdapter, SandboxConfig, } from "./sandbox/index.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3F,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,GAAG,EACH,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,GACd,MAAM,oBAAoB,CAAC","sourcesContent":["export { defaultCommandHandlers, dispatchCommand } from \"./commands/index.js\";\nexport type { CommandContext, CommandHandler, CommandServices } from \"./commands/index.js\";\nexport * from \"./sessions/metadata.js\";\nexport * from \"./sessions/policy.js\";\nexport * from \"./sessions/store.js\";\nexport {\n createSessionRuntime,\n type CreateSessionSandboxOptions,\n type RunSessionOptions,\n type SessionRuntime,\n type SessionRuntimeOptions,\n} from \"./runtime/index.js\";\nexport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatAdapter,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n ConversationKind,\n PlatformInfo,\n RunningSession,\n} from \"./adapter.js\";\nexport {\n SandboxError,\n createExecutor,\n getSandboxAdapters,\n parseSandboxArg,\n validateSandbox,\n} from \"./sandbox/index.js\";\nexport type {\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n SandboxAdapter,\n SandboxConfig,\n} from \"./sandbox/index.js\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defaultCommandHandlers, dispatchCommand } from "./commands/index.js";
|
|
2
|
+
export * from "./sessions/metadata.js";
|
|
3
|
+
export * from "./sessions/policy.js";
|
|
4
|
+
export * from "./sessions/store.js";
|
|
5
|
+
export { createSessionRuntime, } from "./runtime/index.js";
|
|
6
|
+
export { SandboxError, createExecutor, getSandboxAdapters, parseSandboxArg, validateSandbox, } from "./sandbox/index.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE9E,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EACL,oBAAoB,GAKrB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,oBAAoB,CAAC","sourcesContent":["export { defaultCommandHandlers, dispatchCommand } from \"./commands/index.js\";\nexport type { CommandContext, CommandHandler, CommandServices } from \"./commands/index.js\";\nexport * from \"./sessions/metadata.js\";\nexport * from \"./sessions/policy.js\";\nexport * from \"./sessions/store.js\";\nexport {\n createSessionRuntime,\n type CreateSessionSandboxOptions,\n type RunSessionOptions,\n type SessionRuntime,\n type SessionRuntimeOptions,\n} from \"./runtime/index.js\";\nexport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatAdapter,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n ConversationKind,\n PlatformInfo,\n RunningSession,\n} from \"./adapter.js\";\nexport {\n SandboxError,\n createExecutor,\n getSandboxAdapters,\n parseSandboxArg,\n validateSandbox,\n} from \"./sandbox/index.js\";\nexport type {\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n SandboxAdapter,\n SandboxConfig,\n} from \"./sandbox/index.js\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"","sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport { resolveSentryDsn, resolveStateDirFromArgv } from \"./config.js\";\nimport { setEnvAliases } from \"./env.js\";\nimport { createSentryInitOptions } from \"./sentry.js\";\n\nif (!process.env.STATE_DIR && !process.env.MIKAN_STATE_DIR) {\n setEnvAliases(\"STATE_DIR\", resolveStateDirFromArgv());\n}\nconst sentryDsn = resolveSentryDsn();\n\nSentry.init(createSentryInitOptions(sentryDsn));\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/node";
|
|
2
|
+
import { resolveSentryDsn, resolveStateDirFromArgv } from "./config.js";
|
|
3
|
+
import { setEnvAliases } from "./env.js";
|
|
4
|
+
import { createSentryInitOptions } from "./sentry.js";
|
|
5
|
+
if (!process.env.STATE_DIR && !process.env.MIKAN_STATE_DIR) {
|
|
6
|
+
setEnvAliases("STATE_DIR", resolveStateDirFromArgv());
|
|
7
|
+
}
|
|
8
|
+
const sentryDsn = resolveSentryDsn();
|
|
9
|
+
Sentry.init(createSentryInitOptions(sentryDsn));
|
|
10
|
+
//# sourceMappingURL=instrument.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument.js","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;IAC3D,aAAa,CAAC,WAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AACxD,CAAC;AACD,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;AAErC,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC","sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport { resolveSentryDsn, resolveStateDirFromArgv } from \"./config.js\";\nimport { setEnvAliases } from \"./env.js\";\nimport { createSentryInitOptions } from \"./sentry.js\";\n\nif (!process.env.STATE_DIR && !process.env.MIKAN_STATE_DIR) {\n setEnvAliases(\"STATE_DIR\", resolveStateDirFromArgv());\n}\nconst sentryDsn = resolveSentryDsn();\n\nSentry.init(createSentryInitOptions(sentryDsn));\n"]}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface LogContext {
|
|
2
|
+
conversationId: string;
|
|
3
|
+
userName?: string;
|
|
4
|
+
conversationName?: string;
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function logUserMessage(ctx: LogContext, text: string): void;
|
|
8
|
+
export declare function logToolStart(ctx: LogContext, toolName: string, label: string, args: Record<string, unknown>): void;
|
|
9
|
+
export declare function logToolSuccess(ctx: LogContext, toolName: string, durationMs: number, result: string): void;
|
|
10
|
+
export declare function logToolError(ctx: LogContext, toolName: string, durationMs: number, error: string): void;
|
|
11
|
+
export declare function logResponseStart(ctx: LogContext): void;
|
|
12
|
+
export declare function logThinking(ctx: LogContext, thinking: string): void;
|
|
13
|
+
export declare function logResponse(ctx: LogContext, text: string): void;
|
|
14
|
+
export declare function logInfo(message: string): void;
|
|
15
|
+
export declare function logWarning(message: string, details?: string): void;
|
|
16
|
+
export declare function logAgentError(ctx: LogContext | "system", error: string): void;
|
|
17
|
+
export declare function logUsageSummary(ctx: LogContext, usage: {
|
|
18
|
+
input: number;
|
|
19
|
+
output: number;
|
|
20
|
+
cacheRead: number;
|
|
21
|
+
cacheWrite: number;
|
|
22
|
+
cost: {
|
|
23
|
+
input: number;
|
|
24
|
+
output: number;
|
|
25
|
+
cacheRead: number;
|
|
26
|
+
cacheWrite: number;
|
|
27
|
+
total: number;
|
|
28
|
+
};
|
|
29
|
+
}, contextTokens?: number, contextWindow?: number): string;
|
|
30
|
+
export declare function logStartup(workingDir: string, sandbox: string): void;
|
|
31
|
+
export declare function logConnected(platform: string): void;
|
|
32
|
+
export declare function logDisconnected(): void;
|
|
33
|
+
export declare function logBackfillStart(channelCount: number): void;
|
|
34
|
+
export declare function logBackfillChannel(channelName: string, messageCount: number): void;
|
|
35
|
+
export declare function logBackfillComplete(totalMessages: number, durationMs: number): void;
|
|
36
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE;AAGD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,IAAI,CAWN;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,IAAI,CAYN;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,IAAI,CAUN;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAEtD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQnE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ/D;AAGD,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CASlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQ7E;AAUD,wBAAgB,eAAe,CAC7B,GAAG,EAAE,UAAU,EACf,KAAK,EAAE;IACL,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/F,EACD,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAqCR;AAGD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIpE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGnD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAGD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAElF;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAOnF","sourcesContent":["import chalk from \"chalk\";\n\nexport interface LogContext {\n conversationId: string;\n userName?: string;\n conversationName?: string; // For display like #dev-team vs C16HET4EQ\n sessionId?: string;\n}\n\nfunction timestamp(): string {\n const now = new Date();\n const hh = String(now.getHours()).padStart(2, \"0\");\n const mm = String(now.getMinutes()).padStart(2, \"0\");\n const ss = String(now.getSeconds()).padStart(2, \"0\");\n return `[${hh}:${mm}:${ss}]`;\n}\n\nfunction formatContext(ctx: LogContext): string {\n const session = ctx.sessionId ? `:${ctx.sessionId}` : \"\";\n if (ctx.conversationId.startsWith(\"D\")) {\n return `[DM:${ctx.userName || ctx.conversationId}${session}]`;\n }\n const conversation = ctx.conversationName || ctx.conversationId;\n const user = ctx.userName || \"unknown\";\n return `[${conversation.startsWith(\"#\") ? conversation : `#${conversation}`}:${user}${session}]`;\n}\n\n// Keep stdout/stderr lines manageable when echoing tool/agent output. Long bodies\n// flow through Sentry and session storage; the console stream just needs a preview.\nconst LOG_PREVIEW_MAX = 1000;\n\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return `${text.substring(0, maxLen)}\\n(truncated at ${maxLen} chars)`;\n}\n\nfunction formatToolArgs(args: Record<string, unknown>): string {\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(args)) {\n // Skip the label - it's already shown in the tool name\n if (key === \"label\") continue;\n\n // For read tool, format path with offset/limit\n if (key === \"path\" && typeof value === \"string\") {\n const offset = args.offset as number | undefined;\n const limit = args.limit as number | undefined;\n if (offset !== undefined && limit !== undefined) {\n lines.push(`${value}:${offset}-${offset + limit}`);\n } else {\n lines.push(value);\n }\n continue;\n }\n\n // Skip offset/limit since we already handled them\n if (key === \"offset\" || key === \"limit\") continue;\n\n // For other values, format them\n if (typeof value === \"string\") {\n // Multi-line strings get indented\n if (value.includes(\"\\n\")) {\n lines.push(value);\n } else {\n lines.push(value);\n }\n } else {\n lines.push(JSON.stringify(value));\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// User messages\nexport function logUserMessage(ctx: LogContext, text: string): void {\n console.log(chalk.green(`${timestamp()} ${formatContext(ctx)} ${text}`));\n}\n\n// Tool execution\nexport function logToolStart(\n ctx: LogContext,\n toolName: string,\n label: string,\n args: Record<string, unknown>,\n): void {\n const formattedArgs = formatToolArgs(args);\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↳ ${toolName}: ${label}`));\n if (formattedArgs) {\n // Indent the args\n const indented = formattedArgs\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n }\n}\n\nexport function logToolSuccess(\n ctx: LogContext,\n toolName: string,\n durationMs: number,\n result: string,\n): void {\n const duration = (durationMs / 1000).toFixed(1);\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ ${toolName} (${duration}s)`));\n\n const truncated = truncate(result, LOG_PREVIEW_MAX);\n if (truncated) {\n const indented = truncated\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n }\n}\n\nexport function logToolError(\n ctx: LogContext,\n toolName: string,\n durationMs: number,\n error: string,\n): void {\n const duration = (durationMs / 1000).toFixed(1);\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ ${toolName} (${duration}s)`));\n\n const truncated = truncate(error, LOG_PREVIEW_MAX);\n const indented = truncated\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n}\n\n// Response streaming\nexport function logResponseStart(ctx: LogContext): void {\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`));\n}\n\nexport function logThinking(ctx: LogContext, thinking: string): void {\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`));\n const truncated = truncate(thinking, LOG_PREVIEW_MAX);\n const indented = truncated\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n}\n\nexport function logResponse(ctx: LogContext, text: string): void {\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`));\n const truncated = truncate(text, LOG_PREVIEW_MAX);\n const indented = truncated\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n}\n\n// System\nexport function logInfo(message: string): void {\n console.log(chalk.blue(`${timestamp()} [system] ${message}`));\n}\n\nexport function logWarning(message: string, details?: string): void {\n console.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));\n if (details) {\n const indented = details\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n }\n}\n\nexport function logAgentError(ctx: LogContext | \"system\", error: string): void {\n const context = ctx === \"system\" ? \"[system]\" : formatContext(ctx);\n console.log(chalk.yellow(`${timestamp()} ${context} ✗ Agent error`));\n const indented = error\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n console.log(chalk.dim(indented));\n}\n\nfunction formatTokenCount(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n return `${(count / 1000000).toFixed(1)}M`;\n}\n\n// Usage summary\nexport function logUsageSummary(\n ctx: LogContext,\n usage: {\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 contextTokens?: number,\n contextWindow?: number,\n): string {\n const lines: string[] = [];\n lines.push(\"_Usage Summary_\");\n lines.push(`Tokens: ${usage.input.toLocaleString()} in, ${usage.output.toLocaleString()} out`);\n if (usage.cacheRead > 0 || usage.cacheWrite > 0) {\n lines.push(\n `Cache: ${usage.cacheRead.toLocaleString()} read, ${usage.cacheWrite.toLocaleString()} write`,\n );\n }\n if (contextTokens && contextWindow) {\n const contextPercent = ((contextTokens / contextWindow) * 100).toFixed(1);\n lines.push(\n `Context: ${formatTokenCount(contextTokens)} / ${formatTokenCount(contextWindow)} (${contextPercent}%)`,\n );\n }\n lines.push(\n `Cost: $${usage.cost.input.toFixed(4)} in, $${usage.cost.output.toFixed(4)} out` +\n (usage.cacheRead > 0 || usage.cacheWrite > 0\n ? `, $${usage.cost.cacheRead.toFixed(4)} cache read, $${usage.cost.cacheWrite.toFixed(4)} cache write`\n : \"\"),\n );\n lines.push(`*Total: $${usage.cost.total.toFixed(4)}*`);\n\n const summary = lines.join(\"\\n\");\n\n console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💰 Usage`));\n console.log(\n chalk.dim(\n ` ${usage.input.toLocaleString()} in + ${usage.output.toLocaleString()} out` +\n (usage.cacheRead > 0 || usage.cacheWrite > 0\n ? ` (${usage.cacheRead.toLocaleString()} cache read, ${usage.cacheWrite.toLocaleString()} cache write)`\n : \"\") +\n ` = $${usage.cost.total.toFixed(4)}`,\n ),\n );\n\n return summary;\n}\n\n// Startup (no context needed)\nexport function logStartup(workingDir: string, sandbox: string): void {\n console.log(\"Starting mikan...\");\n console.log(` Working directory: ${workingDir}`);\n console.log(` Sandbox: ${sandbox}`);\n}\n\nexport function logConnected(platform: string): void {\n console.log(`⚡️ Mikan connected to ${platform} and listening!`);\n console.log(\"\");\n}\n\nexport function logDisconnected(): void {\n console.log(\"Mikan disconnected.\");\n}\n\n// Backfill\nexport function logBackfillStart(channelCount: number): void {\n console.log(chalk.blue(`${timestamp()} [system] Backfilling ${channelCount} channels...`));\n}\n\nexport function logBackfillChannel(channelName: string, messageCount: number): void {\n console.log(chalk.blue(`${timestamp()} [system] #${channelName}: ${messageCount} messages`));\n}\n\nexport function logBackfillComplete(totalMessages: number, durationMs: number): void {\n const duration = (durationMs / 1000).toFixed(1);\n console.log(\n chalk.blue(\n `${timestamp()} [system] Backfill complete: ${totalMessages} messages in ${duration}s`,\n ),\n );\n}\n"]}
|