@geminixiang/mama 0.2.0-beta.4 → 0.2.0-beta.6
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/README.md +151 -417
- package/dist/adapter.d.ts +9 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +2 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +71 -18
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +9 -2
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +5 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +69 -11
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +13 -3
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +1 -35
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +9 -2
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -7
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts +2 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js.map +1 -1
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +15 -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 +37 -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 +94 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +14 -0
- package/dist/commands/registry.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 +38 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +43 -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 +5 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +9 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +15 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +167 -56
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +74 -68
- package/dist/context.js.map +1 -1
- package/dist/execution-resolver.d.ts +6 -3
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +47 -14
- package/dist/execution-resolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +2 -3
- package/dist/instrument.js.map +1 -1
- package/dist/login/index.d.ts.map +1 -1
- package/dist/login/index.js +19 -8
- package/dist/login/index.js.map +1 -1
- package/dist/login/portal.d.ts.map +1 -1
- package/dist/login/portal.js +7 -7
- package/dist/login/portal.js.map +1 -1
- package/dist/login/session.d.ts +3 -2
- package/dist/login/session.d.ts.map +1 -1
- package/dist/login/session.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +107 -388
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +11 -9
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +125 -87
- package/dist/provisioner.js.map +1 -1
- 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 +27 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +303 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +14 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +131 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/index.d.ts +6 -4
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +6 -3
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +5 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/session-view/portal.d.ts.map +1 -1
- package/dist/session-view/portal.js +10 -1
- package/dist/session-view/portal.js.map +1 -1
- package/dist/session-view/service.d.ts.map +1 -1
- package/dist/session-view/service.js +36 -26
- package/dist/session-view/service.js.map +1 -1
- package/dist/session-view/store.d.ts +3 -2
- package/dist/session-view/store.d.ts.map +1 -1
- package/dist/session-view/store.js.map +1 -1
- package/dist/vault-routing.d.ts +3 -5
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +8 -20
- package/dist/vault-routing.js.map +1 -1
- package/dist/vault.d.ts +7 -5
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +101 -50
- package/dist/vault.js.map +1 -1
- package/package.json +1 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { loadAgentConfigForConversation, saveConversationModelConfig } from "../config.js";
|
|
5
|
+
import { replyWithContext } from "./utils.js";
|
|
6
|
+
const PI_AI_THINKING_LEVELS = [
|
|
7
|
+
"minimal",
|
|
8
|
+
"low",
|
|
9
|
+
"medium",
|
|
10
|
+
"high",
|
|
11
|
+
"xhigh",
|
|
12
|
+
];
|
|
13
|
+
const THINKING_LEVELS = new Set(["off", ...PI_AI_THINKING_LEVELS]);
|
|
14
|
+
export function parseModelCommand(text) {
|
|
15
|
+
const tokens = text.trim().split(/\s+/).filter(Boolean);
|
|
16
|
+
if (tokens.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
const command = tokens[0].toLowerCase();
|
|
19
|
+
if (command !== "model" && command !== "/model" && command !== "/pi-model") {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (tokens.length === 1) {
|
|
23
|
+
return { command: command };
|
|
24
|
+
}
|
|
25
|
+
const spec = tokens[1];
|
|
26
|
+
const slash = spec.indexOf("/");
|
|
27
|
+
if (slash <= 0 || slash === spec.length - 1) {
|
|
28
|
+
return { command: command };
|
|
29
|
+
}
|
|
30
|
+
const modelSpec = spec.slice(slash + 1);
|
|
31
|
+
const parsedModel = parseModelThinkingLevel(modelSpec);
|
|
32
|
+
return {
|
|
33
|
+
command: command,
|
|
34
|
+
provider: spec.slice(0, slash),
|
|
35
|
+
model: parsedModel.model,
|
|
36
|
+
thinkingLevel: parsedModel.thinkingLevel,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseModelThinkingLevel(modelSpec) {
|
|
40
|
+
const colon = modelSpec.lastIndexOf(":");
|
|
41
|
+
if (colon <= 0 || colon === modelSpec.length - 1) {
|
|
42
|
+
return { model: modelSpec };
|
|
43
|
+
}
|
|
44
|
+
const suffix = modelSpec.slice(colon + 1);
|
|
45
|
+
if (!THINKING_LEVELS.has(suffix)) {
|
|
46
|
+
return { model: modelSpec };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
model: modelSpec.slice(0, colon),
|
|
50
|
+
thinkingLevel: suffix,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function formatModelSpec(provider, model, thinkingLevel) {
|
|
54
|
+
return `${provider}/${model}${thinkingLevel ? `:${thinkingLevel}` : ""}`;
|
|
55
|
+
}
|
|
56
|
+
export class ModelCommandHandler {
|
|
57
|
+
async tryHandle(context) {
|
|
58
|
+
const parsed = parseModelCommand(context.commandText);
|
|
59
|
+
if (!parsed)
|
|
60
|
+
return false;
|
|
61
|
+
const conversationDir = join(context.services.workingDir, context.conversationId);
|
|
62
|
+
if (!parsed.provider || !parsed.model) {
|
|
63
|
+
const current = loadAgentConfigForConversation(conversationDir);
|
|
64
|
+
await replyWithContext(context.responseCtx, `目前模型:\`${formatModelSpec(current.provider, current.model, current.thinkingLevel)}\`\n用法:\`/pi-model provider/model[:thinking]\`,例如 \`/pi-model anthropic/claude-sonnet-4-5:off\`。`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (!this.isKnownModel(parsed.provider, parsed.model)) {
|
|
68
|
+
await replyWithContext(context.responseCtx, `找不到模型 \`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\`。請確認 provider/model 名稱,或先在 pi models.json 註冊自訂模型。`);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (!context.services.runtime) {
|
|
72
|
+
await replyWithContext(context.responseCtx, "Model command is not configured correctly on the server. Please try again later.");
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const switched = context.services.runtime.switchConversationModel(context.conversationId, parsed.provider, parsed.model);
|
|
76
|
+
if (!switched) {
|
|
77
|
+
await replyWithContext(context.responseCtx, "目前這個 conversation 有執行中的工作,請等它完成或先 `/stop` 後再切換模型。");
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
saveConversationModelConfig(conversationDir, {
|
|
81
|
+
provider: parsed.provider,
|
|
82
|
+
model: parsed.model,
|
|
83
|
+
...(parsed.thinkingLevel ? { thinkingLevel: parsed.thinkingLevel } : {}),
|
|
84
|
+
});
|
|
85
|
+
await replyWithContext(context.responseCtx, `已切換這個 conversation 的模型為 \`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\`。下一則訊息會使用新模型。`);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
isKnownModel(provider, model) {
|
|
89
|
+
const authStorage = AuthStorage.create(join(homedir(), ".pi", "mama", "auth.json"));
|
|
90
|
+
const registry = ModelRegistry.create(authStorage);
|
|
91
|
+
return registry.find(provider, model) !== undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { ThinkingLevel as PiAiThinkingLevel } from \"@mariozechner/pi-ai\";\nimport { AuthStorage, ModelRegistry } from \"@mariozechner/pi-coding-agent\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { loadAgentConfigForConversation, saveConversationModelConfig } from \"../config.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nconst PI_AI_THINKING_LEVELS = [\n \"minimal\",\n \"low\",\n \"medium\",\n \"high\",\n \"xhigh\",\n] satisfies PiAiThinkingLevel[];\nconst THINKING_LEVELS = new Set<ThinkingLevel>([\"off\", ...PI_AI_THINKING_LEVELS]);\n\nexport interface ParsedModelCommand {\n command: \"model\" | \"/model\" | \"/pi-model\";\n provider?: string;\n model?: string;\n thinkingLevel?: ThinkingLevel;\n}\n\nexport function parseModelCommand(text: string): ParsedModelCommand | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const command = tokens[0].toLowerCase();\n if (command !== \"model\" && command !== \"/model\" && command !== \"/pi-model\") {\n return null;\n }\n\n if (tokens.length === 1) {\n return { command: command as ParsedModelCommand[\"command\"] };\n }\n\n const spec = tokens[1];\n const slash = spec.indexOf(\"/\");\n if (slash <= 0 || slash === spec.length - 1) {\n return { command: command as ParsedModelCommand[\"command\"] };\n }\n\n const modelSpec = spec.slice(slash + 1);\n const parsedModel = parseModelThinkingLevel(modelSpec);\n\n return {\n command: command as ParsedModelCommand[\"command\"],\n provider: spec.slice(0, slash),\n model: parsedModel.model,\n thinkingLevel: parsedModel.thinkingLevel,\n };\n}\n\nfunction parseModelThinkingLevel(modelSpec: string): {\n model: string;\n thinkingLevel?: ThinkingLevel;\n} {\n const colon = modelSpec.lastIndexOf(\":\");\n if (colon <= 0 || colon === modelSpec.length - 1) {\n return { model: modelSpec };\n }\n\n const suffix = modelSpec.slice(colon + 1);\n if (!THINKING_LEVELS.has(suffix as ThinkingLevel)) {\n return { model: modelSpec };\n }\n\n return {\n model: modelSpec.slice(0, colon),\n thinkingLevel: suffix as ThinkingLevel,\n };\n}\n\nfunction formatModelSpec(provider: string, model: string, thinkingLevel?: ThinkingLevel): string {\n return `${provider}/${model}${thinkingLevel ? `:${thinkingLevel}` : \"\"}`;\n}\n\nexport class ModelCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseModelCommand(context.commandText);\n if (!parsed) return false;\n\n const conversationDir = join(context.services.workingDir, context.conversationId);\n if (!parsed.provider || !parsed.model) {\n const current = loadAgentConfigForConversation(conversationDir);\n await replyWithContext(\n context.responseCtx,\n `目前模型:\\`${formatModelSpec(current.provider, current.model, current.thinkingLevel)}\\`\\n用法:\\`/pi-model provider/model[:thinking]\\`,例如 \\`/pi-model anthropic/claude-sonnet-4-5:off\\`。`,\n );\n return true;\n }\n\n if (!this.isKnownModel(parsed.provider, parsed.model)) {\n await replyWithContext(\n context.responseCtx,\n `找不到模型 \\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\`。請確認 provider/model 名稱,或先在 pi models.json 註冊自訂模型。`,\n );\n return true;\n }\n\n if (!context.services.runtime) {\n await replyWithContext(\n context.responseCtx,\n \"Model command is not configured correctly on the server. Please try again later.\",\n );\n return true;\n }\n\n const switched = context.services.runtime.switchConversationModel(\n context.conversationId,\n parsed.provider,\n parsed.model,\n );\n if (!switched) {\n await replyWithContext(\n context.responseCtx,\n \"目前這個 conversation 有執行中的工作,請等它完成或先 `/stop` 後再切換模型。\",\n );\n return true;\n }\n\n saveConversationModelConfig(conversationDir, {\n provider: parsed.provider,\n model: parsed.model,\n ...(parsed.thinkingLevel ? { thinkingLevel: parsed.thinkingLevel } : {}),\n });\n\n await replyWithContext(\n context.responseCtx,\n `已切換這個 conversation 的模型為 \\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\`。下一則訊息會使用新模型。`,\n );\n return true;\n }\n\n private isKnownModel(provider: string, model: string): boolean {\n const authStorage = AuthStorage.create(join(homedir(), \".pi\", \"mama\", \"auth.json\"));\n const registry = ModelRegistry.create(authStorage);\n return registry.find(provider, model) !== undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CommandContext, CommandHandler } from "./types.js";
|
|
2
|
+
export interface ParsedNewCommand {
|
|
3
|
+
command: "new" | "/new" | "/pi-new";
|
|
4
|
+
}
|
|
5
|
+
export declare function parseNewCommand(text: string): ParsedNewCommand | null;
|
|
6
|
+
export declare class NewCommandHandler implements CommandHandler {
|
|
7
|
+
tryHandle(context: CommandContext): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=new.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAUrE;AAED,qBAAa,iBAAkB,YAAW,cAAc;IAChD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBzD;CACF","sourcesContent":["import type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nexport interface ParsedNewCommand {\n command: \"new\" | \"/new\" | \"/pi-new\";\n}\n\nexport function parseNewCommand(text: string): ParsedNewCommand | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const command = tokens[0].toLowerCase();\n if (command !== \"new\" && command !== \"/new\" && command !== \"/pi-new\") {\n return null;\n }\n\n return { command: command as ParsedNewCommand[\"command\"] };\n}\n\nexport class NewCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n if (!parseNewCommand(context.commandText)) return false;\n\n if (!context.privateConversation) {\n await replyWithContext(\n context.responseCtx,\n \"為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。\",\n );\n return true;\n }\n\n if (!context.services.runtime) {\n await replyWithContext(\n context.responseCtx,\n \"New command is not configured correctly on the server. Please try again later.\",\n );\n return true;\n }\n\n await context.services.runtime.handleNew(\n context.sessionKey,\n context.conversationId,\n context.bot,\n );\n return true;\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { replyWithContext } from "./utils.js";
|
|
2
|
+
export function parseNewCommand(text) {
|
|
3
|
+
const tokens = text.trim().split(/\s+/).filter(Boolean);
|
|
4
|
+
if (tokens.length === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const command = tokens[0].toLowerCase();
|
|
7
|
+
if (command !== "new" && command !== "/new" && command !== "/pi-new") {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return { command: command };
|
|
11
|
+
}
|
|
12
|
+
export class NewCommandHandler {
|
|
13
|
+
async tryHandle(context) {
|
|
14
|
+
if (!parseNewCommand(context.commandText))
|
|
15
|
+
return false;
|
|
16
|
+
if (!context.privateConversation) {
|
|
17
|
+
await replyWithContext(context.responseCtx, "為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。");
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (!context.services.runtime) {
|
|
21
|
+
await replyWithContext(context.responseCtx, "New command is not configured correctly on the server. Please try again later.");
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
await context.services.runtime.handleNew(context.sessionKey, context.conversationId, context.bot);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=new.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM9C,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAsC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,SAAS,CAAC,OAAuB;QACrC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO,KAAK,CAAC;QAExD,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;YACjC,MAAM,gBAAgB,CACpB,OAAO,CAAC,WAAW,EACnB,4CAA4C,CAC7C,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,gBAAgB,CACpB,OAAO,CAAC,WAAW,EACnB,gFAAgF,CACjF,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CACtC,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["import type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nexport interface ParsedNewCommand {\n command: \"new\" | \"/new\" | \"/pi-new\";\n}\n\nexport function parseNewCommand(text: string): ParsedNewCommand | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const command = tokens[0].toLowerCase();\n if (command !== \"new\" && command !== \"/new\" && command !== \"/pi-new\") {\n return null;\n }\n\n return { command: command as ParsedNewCommand[\"command\"] };\n}\n\nexport class NewCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n if (!parseNewCommand(context.commandText)) return false;\n\n if (!context.privateConversation) {\n await replyWithContext(\n context.responseCtx,\n \"為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。\",\n );\n return true;\n }\n\n if (!context.services.runtime) {\n await replyWithContext(\n context.responseCtx,\n \"New command is not configured correctly on the server. Please try again later.\",\n );\n return true;\n }\n\n await context.services.runtime.handleNew(\n context.sessionKey,\n context.conversationId,\n context.bot,\n );\n return true;\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CommandContext, CommandHandler } from "./types.js";
|
|
2
|
+
export declare class CommandRegistry {
|
|
3
|
+
private readonly handlers;
|
|
4
|
+
constructor(handlers: readonly CommandHandler[]);
|
|
5
|
+
handle(context: CommandContext): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjE,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAI;IAE9D,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAOtD;CACF","sourcesContent":["import type { CommandContext, CommandHandler } from \"./types.js\";\n\nexport class CommandRegistry {\n constructor(private readonly handlers: readonly CommandHandler[]) {}\n\n async handle(context: CommandContext): Promise<boolean> {\n for (const handler of this.handlers) {\n if (await handler.tryHandle(context)) {\n return true;\n }\n }\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class CommandRegistry {
|
|
2
|
+
constructor(handlers) {
|
|
3
|
+
this.handlers = handlers;
|
|
4
|
+
}
|
|
5
|
+
async handle(context) {
|
|
6
|
+
for (const handler of this.handlers) {
|
|
7
|
+
if (await handler.tryHandle(context)) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAe;IAC1B,YAA6B,QAAmC;wBAAnC,QAAQ;IAA8B,CAAC;IAEpE,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF","sourcesContent":["import type { CommandContext, CommandHandler } from \"./types.js\";\n\nexport class CommandRegistry {\n constructor(private readonly handlers: readonly CommandHandler[]) {}\n\n async handle(context: CommandContext): Promise<boolean> {\n for (const handler of this.handlers) {\n if (await handler.tryHandle(context)) {\n return true;\n }\n }\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-view.d.ts","sourceRoot":"","sources":["../../src/commands/session-view.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjE,qBAAa,yBAA0B,YAAW,cAAc;IACxD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAsDzD;CACF","sourcesContent":["import { resolveExistingSessionFile } from \"../session-view/service.js\";\nimport { parseSessionViewCommand } from \"../session-view/command.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nexport class SessionViewCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n if (!parseSessionViewCommand(context.commandText)) return false;\n\n const sendSessionViewReply = async (text: string): Promise<void> => {\n if (context.privateConversation) {\n await replyWithContext(context.responseCtx, text);\n return;\n }\n\n if (context.bot.postPrivate) {\n await context.bot.postPrivate(context.conversationId, context.platformUserId, text);\n return;\n }\n\n await replyWithContext(context.responseCtx, text);\n };\n\n if (!context.privateConversation && !context.bot.postPrivate) {\n await sendSessionViewReply(\n \"為了保護對話內容,`/session` 目前只能在與機器人的私訊 / DM 中使用。\",\n );\n return true;\n }\n\n if (!context.services.portalBaseUrl) {\n await sendSessionViewReply(\n \"Session viewer is not configured. Set `MAMA_LINK_URL` or `MAMA_LINK_PORT` on the server.\",\n );\n return true;\n }\n\n const sessionFile = resolveExistingSessionFile(\n context.services.workingDir,\n context.conversationId,\n context.sessionKey,\n );\n if (!sessionFile) {\n await sendSessionViewReply(\n \"目前還沒有可查看的 session。先和機器人對話一次,建立 session 後再試。\",\n );\n return true;\n }\n\n const token = context.services.sessionViewTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n context.sessionKey,\n sessionFile,\n );\n\n const linkText = `Open this read-only session link (expires in 24 hours):\\n${context.services.portalBaseUrl}/session?token=${token.token}`;\n await sendSessionViewReply(linkText);\n return true;\n }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveExistingSessionFile } from "../session-view/service.js";
|
|
2
|
+
import { parseSessionViewCommand } from "../session-view/command.js";
|
|
3
|
+
import { replyWithContext } from "./utils.js";
|
|
4
|
+
export class SessionViewCommandHandler {
|
|
5
|
+
async tryHandle(context) {
|
|
6
|
+
if (!parseSessionViewCommand(context.commandText))
|
|
7
|
+
return false;
|
|
8
|
+
const sendSessionViewReply = async (text) => {
|
|
9
|
+
if (context.privateConversation) {
|
|
10
|
+
await replyWithContext(context.responseCtx, text);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (context.bot.postPrivate) {
|
|
14
|
+
await context.bot.postPrivate(context.conversationId, context.platformUserId, text);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await replyWithContext(context.responseCtx, text);
|
|
18
|
+
};
|
|
19
|
+
if (!context.privateConversation && !context.bot.postPrivate) {
|
|
20
|
+
await sendSessionViewReply("為了保護對話內容,`/session` 目前只能在與機器人的私訊 / DM 中使用。");
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (!context.services.portalBaseUrl) {
|
|
24
|
+
await sendSessionViewReply("Session viewer is not configured. Set `MAMA_LINK_URL` or `MAMA_LINK_PORT` on the server.");
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const sessionFile = resolveExistingSessionFile(context.services.workingDir, context.conversationId, context.sessionKey);
|
|
28
|
+
if (!sessionFile) {
|
|
29
|
+
await sendSessionViewReply("目前還沒有可查看的 session。先和機器人對話一次,建立 session 後再試。");
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const token = context.services.sessionViewTokenStore.create(context.platform, context.platformUserId, context.conversationId, context.sessionKey, sessionFile);
|
|
33
|
+
const linkText = `Open this read-only session link (expires in 24 hours):\n${context.services.portalBaseUrl}/session?token=${token.token}`;
|
|
34
|
+
await sendSessionViewReply(linkText);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=session-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-view.js","sourceRoot":"","sources":["../../src/commands/session-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,OAAO,yBAAyB;IACpC,KAAK,CAAC,SAAS,CAAC,OAAuB;QACrC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO,KAAK,CAAC;QAEhE,MAAM,oBAAoB,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;YACjE,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;gBAChC,MAAM,gBAAgB,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YAED,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBAC5B,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;gBACpF,OAAO;YACT,CAAC;YAED,MAAM,gBAAgB,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACpD,CAAC,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,mBAAmB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAC7D,MAAM,oBAAoB,CACxB,4CAA4C,CAC7C,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YACpC,MAAM,oBAAoB,CACxB,0FAA0F,CAC3F,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,WAAW,GAAG,0BAA0B,CAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,EAC3B,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,UAAU,CACnB,CAAC;QACF,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,oBAAoB,CACxB,6CAA6C,CAC9C,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC,MAAM,CACzD,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,UAAU,EAClB,WAAW,CACZ,CAAC;QAEF,MAAM,QAAQ,GAAG,4DAA4D,OAAO,CAAC,QAAQ,CAAC,aAAa,kBAAkB,KAAK,CAAC,KAAK,EAAE,CAAC;QAC3I,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["import { resolveExistingSessionFile } from \"../session-view/service.js\";\nimport { parseSessionViewCommand } from \"../session-view/command.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nexport class SessionViewCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n if (!parseSessionViewCommand(context.commandText)) return false;\n\n const sendSessionViewReply = async (text: string): Promise<void> => {\n if (context.privateConversation) {\n await replyWithContext(context.responseCtx, text);\n return;\n }\n\n if (context.bot.postPrivate) {\n await context.bot.postPrivate(context.conversationId, context.platformUserId, text);\n return;\n }\n\n await replyWithContext(context.responseCtx, text);\n };\n\n if (!context.privateConversation && !context.bot.postPrivate) {\n await sendSessionViewReply(\n \"為了保護對話內容,`/session` 目前只能在與機器人的私訊 / DM 中使用。\",\n );\n return true;\n }\n\n if (!context.services.portalBaseUrl) {\n await sendSessionViewReply(\n \"Session viewer is not configured. Set `MAMA_LINK_URL` or `MAMA_LINK_PORT` on the server.\",\n );\n return true;\n }\n\n const sessionFile = resolveExistingSessionFile(\n context.services.workingDir,\n context.conversationId,\n context.sessionKey,\n );\n if (!sessionFile) {\n await sendSessionViewReply(\n \"目前還沒有可查看的 session。先和機器人對話一次,建立 session 後再試。\",\n );\n return true;\n }\n\n const token = context.services.sessionViewTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n context.sessionKey,\n sessionFile,\n );\n\n const linkText = `Open this read-only session link (expires in 24 hours):\\n${context.services.portalBaseUrl}/session?token=${token.token}`;\n await sendSessionViewReply(linkText);\n return true;\n }\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Bot, BotAdapters, PlatformName } from "../adapter.js";
|
|
2
|
+
import type { UserBindingStore } from "../bindings.js";
|
|
3
|
+
import type { DockerContainerManager } from "../provisioner.js";
|
|
4
|
+
import type { SessionRuntime } from "../runtime/session-runtime.js";
|
|
5
|
+
import type { SandboxConfig } from "../sandbox.js";
|
|
6
|
+
import type { VaultManager } from "../vault.js";
|
|
7
|
+
export interface LinkTokenStoreLike {
|
|
8
|
+
create(platform: PlatformName, platformUserId: string, conversationId: string, vaultId: string, providerId: string): {
|
|
9
|
+
token: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface SessionViewTokenStoreLike {
|
|
13
|
+
create(platform: PlatformName, platformUserId: string, conversationId: string, sessionKey: string, sessionFile: string): {
|
|
14
|
+
token: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface CommandServices {
|
|
18
|
+
workingDir: string;
|
|
19
|
+
runtime?: SessionRuntime;
|
|
20
|
+
sandbox: SandboxConfig;
|
|
21
|
+
vaultManager: VaultManager;
|
|
22
|
+
bindingStore?: UserBindingStore;
|
|
23
|
+
provisioner?: DockerContainerManager;
|
|
24
|
+
linkTokenStore: LinkTokenStoreLike;
|
|
25
|
+
sessionViewTokenStore: SessionViewTokenStoreLike;
|
|
26
|
+
portalBaseUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CommandContext {
|
|
29
|
+
bot: Bot;
|
|
30
|
+
responseCtx: BotAdapters["responseCtx"];
|
|
31
|
+
platform: PlatformName;
|
|
32
|
+
platformUserId: string;
|
|
33
|
+
conversationId: string;
|
|
34
|
+
vaultConversationId?: string;
|
|
35
|
+
sessionKey: string;
|
|
36
|
+
commandText: string;
|
|
37
|
+
privateConversation: boolean;
|
|
38
|
+
services: CommandServices;
|
|
39
|
+
}
|
|
40
|
+
export interface CommandHandler {
|
|
41
|
+
tryHandle(context: CommandContext): Promise<boolean>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,MAAM,CACJ,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,cAAc,EAAE,kBAAkB,CAAC;IACnC,qBAAqB,EAAE,yBAAyB,CAAC;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,GAAG,CAAC;IACT,WAAW,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;IACxC,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtD","sourcesContent":["import type { Bot, BotAdapters, PlatformName } from \"../adapter.js\";\nimport type { UserBindingStore } from \"../bindings.js\";\nimport type { DockerContainerManager } from \"../provisioner.js\";\nimport type { SessionRuntime } from \"../runtime/session-runtime.js\";\nimport type { SandboxConfig } from \"../sandbox.js\";\nimport type { VaultManager } from \"../vault.js\";\n\nexport interface LinkTokenStoreLike {\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): { token: string };\n}\n\nexport interface SessionViewTokenStoreLike {\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n sessionKey: string,\n sessionFile: string,\n ): { token: string };\n}\n\nexport interface CommandServices {\n workingDir: string;\n runtime?: SessionRuntime;\n sandbox: SandboxConfig;\n vaultManager: VaultManager;\n bindingStore?: UserBindingStore;\n provisioner?: DockerContainerManager;\n linkTokenStore: LinkTokenStoreLike;\n sessionViewTokenStore: SessionViewTokenStoreLike;\n portalBaseUrl?: string;\n}\n\nexport interface CommandContext {\n bot: Bot;\n responseCtx: BotAdapters[\"responseCtx\"];\n platform: PlatformName;\n platformUserId: string;\n conversationId: string;\n vaultConversationId?: string;\n sessionKey: string;\n commandText: string;\n privateConversation: boolean;\n services: CommandServices;\n}\n\nexport interface CommandHandler {\n tryHandle(context: CommandContext): Promise<boolean>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Bot, BotAdapters, PlatformName } from \"../adapter.js\";\nimport type { UserBindingStore } from \"../bindings.js\";\nimport type { DockerContainerManager } from \"../provisioner.js\";\nimport type { SessionRuntime } from \"../runtime/session-runtime.js\";\nimport type { SandboxConfig } from \"../sandbox.js\";\nimport type { VaultManager } from \"../vault.js\";\n\nexport interface LinkTokenStoreLike {\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): { token: string };\n}\n\nexport interface SessionViewTokenStoreLike {\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n sessionKey: string,\n sessionFile: string,\n ): { token: string };\n}\n\nexport interface CommandServices {\n workingDir: string;\n runtime?: SessionRuntime;\n sandbox: SandboxConfig;\n vaultManager: VaultManager;\n bindingStore?: UserBindingStore;\n provisioner?: DockerContainerManager;\n linkTokenStore: LinkTokenStoreLike;\n sessionViewTokenStore: SessionViewTokenStoreLike;\n portalBaseUrl?: string;\n}\n\nexport interface CommandContext {\n bot: Bot;\n responseCtx: BotAdapters[\"responseCtx\"];\n platform: PlatformName;\n platformUserId: string;\n conversationId: string;\n vaultConversationId?: string;\n sessionKey: string;\n commandText: string;\n privateConversation: boolean;\n services: CommandServices;\n}\n\nexport interface CommandHandler {\n tryHandle(context: CommandContext): Promise<boolean>;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BotEvent } from "../adapter.js";
|
|
2
|
+
import type { CommandContext } from "./types.js";
|
|
3
|
+
export declare function replyWithContext(responseCtx: CommandContext["responseCtx"], text: string): Promise<void>;
|
|
4
|
+
export declare function isPrivateConversation(event: BotEvent): boolean;
|
|
5
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/commands/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,cAAc,CAAC,aAAa,CAAC,EAC1C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAE9D","sourcesContent":["import type { BotEvent } from \"../adapter.js\";\nimport type { CommandContext } from \"./types.js\";\n\nexport async function replyWithContext(\n responseCtx: CommandContext[\"responseCtx\"],\n text: string,\n): Promise<void> {\n await responseCtx.setTyping(false);\n await responseCtx.setWorking(false);\n await responseCtx.respond(text);\n}\n\nexport function isPrivateConversation(event: BotEvent): boolean {\n return event.conversationKind === \"direct\" || event.type === \"dm\";\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export async function replyWithContext(responseCtx, text) {
|
|
2
|
+
await responseCtx.setTyping(false);
|
|
3
|
+
await responseCtx.setWorking(false);
|
|
4
|
+
await responseCtx.respond(text);
|
|
5
|
+
}
|
|
6
|
+
export function isPrivateConversation(event) {
|
|
7
|
+
return event.conversationKind === "direct" || event.type === "dm";
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/commands/utils.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,WAA0C,EAC1C,IAAY;IAEZ,MAAM,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAe;IACnD,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACpE,CAAC","sourcesContent":["import type { BotEvent } from \"../adapter.js\";\nimport type { CommandContext } from \"./types.js\";\n\nexport async function replyWithContext(\n responseCtx: CommandContext[\"responseCtx\"],\n text: string,\n): Promise<void> {\n await responseCtx.setTyping(false);\n await responseCtx.setWorking(false);\n await responseCtx.respond(text);\n}\n\nexport function isPrivateConversation(event: BotEvent): boolean {\n return event.conversationKind === \"direct\" || event.type === \"dm\";\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
|
|
2
|
+
export declare class MissingGlobalSettingsError extends Error {
|
|
3
|
+
readonly settingsPath: string;
|
|
4
|
+
constructor(settingsPath: string);
|
|
5
|
+
}
|
|
1
6
|
export interface AgentConfig {
|
|
2
7
|
provider: string;
|
|
3
8
|
model: string;
|
|
4
|
-
thinkingLevel
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
logLevel?: "trace" | "debug" | "info" | "warn" | "error";
|
|
9
|
+
thinkingLevel: ThinkingLevel;
|
|
10
|
+
logFormat: "console" | "json";
|
|
11
|
+
logLevel: "trace" | "debug" | "info" | "warn" | "error";
|
|
8
12
|
sentryDsn?: string;
|
|
9
13
|
sandboxCpus?: string;
|
|
10
14
|
sandboxMemory?: string;
|
|
11
15
|
}
|
|
12
|
-
export declare function loadAgentConfig(
|
|
16
|
+
export declare function loadAgentConfig(): AgentConfig;
|
|
17
|
+
export declare function loadAgentConfigForConversation(conversationDir: string): AgentConfig;
|
|
18
|
+
export declare function saveConversationModelConfig(conversationDir: string, config: Pick<AgentConfig, "provider" | "model"> & Partial<Pick<AgentConfig, "thinkingLevel">>): void;
|
|
13
19
|
export declare function resolveWorkspaceDirFromArgv(args?: string[]): string | undefined;
|
|
14
20
|
export declare function resolveStateDirFromArgv(args?: string[]): string;
|
|
15
|
-
export declare function resolveSentryDsn(
|
|
21
|
+
export declare function resolveSentryDsn(): string | undefined;
|
|
22
|
+
export declare function createGlobalSettingsFile(stateDir: string): string;
|
|
16
23
|
/**
|
|
17
24
|
* Externally-visible base URL of the link/OAuth server, e.g.
|
|
18
|
-
* `https://mama.example.com` (no trailing slash). Read from `
|
|
25
|
+
* `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,
|
|
19
26
|
* the same env var the bot uses to build credential onboarding links.
|
|
20
27
|
*/
|
|
21
28
|
export declare function resolveLinkBaseUrl(): string | undefined;
|
|
22
|
-
export declare function saveAgentConfig(
|
|
29
|
+
export declare function saveAgentConfig(config: Partial<AgentConfig>): void;
|
|
23
30
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACpC,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmDD,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAwBjE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,WAAwB,GAAG,MAAM,GAAG,SAAS,CA2B5F;AAED,wBAAgB,uBAAuB,CAAC,IAAI,WAAwB,GAAG,MAAM,CAY5E;AAED,wBAAgB,gBAAgB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO1E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAIvD;AAED,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAwBxF","sourcesContent":["import { existsSync, mkdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport interface AgentConfig {\n provider: string;\n model: string;\n thinkingLevel?: string;\n sessionScope?: \"thread\" | \"channel\";\n logFormat?: \"console\" | \"json\";\n logLevel?: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\";\n sentryDsn?: string;\n sandboxCpus?: string;\n sandboxMemory?: string;\n}\n\nconst DEFAULTS: AgentConfig = {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-5\",\n thinkingLevel: \"off\",\n sessionScope: \"thread\",\n logFormat: \"console\",\n logLevel: \"info\",\n};\n\nfunction loadConfigFile(settingsPath: string): Partial<AgentConfig> | undefined {\n if (!existsSync(settingsPath)) {\n return undefined;\n }\n\n try {\n const raw = readFileSync(settingsPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as Partial<AgentConfig>;\n }\n } catch {\n // Ignore parse errors, fall through to next candidate\n }\n\n return undefined;\n}\n\nfunction getConfiguredStateDir(): string | undefined {\n const raw = process.env.MAMA_STATE_DIR?.trim();\n return raw ? resolve(raw) : undefined;\n}\n\nfunction loadRawAgentConfig(workspaceDir?: string): Partial<AgentConfig> {\n const stateDir = getConfiguredStateDir();\n const candidates = [\n ...(stateDir ? [join(stateDir, \"settings.json\")] : []),\n ...(workspaceDir ? [join(workspaceDir, \"settings.json\")] : []),\n ];\n\n for (const settingsPath of candidates) {\n const config = loadConfigFile(settingsPath);\n if (config) {\n return config;\n }\n }\n\n return {};\n}\n\nexport function loadAgentConfig(workspaceDir: string): AgentConfig {\n const fromFile = loadRawAgentConfig(workspaceDir);\n\n const provider = fromFile.provider || process.env.MOM_AI_PROVIDER || DEFAULTS.provider;\n const model = fromFile.model || process.env.MOM_AI_MODEL || DEFAULTS.model;\n const thinkingLevel = fromFile.thinkingLevel ?? DEFAULTS.thinkingLevel;\n const sessionScope = fromFile.sessionScope ?? DEFAULTS.sessionScope;\n const logFormat = fromFile.logFormat ?? DEFAULTS.logFormat;\n const logLevel = fromFile.logLevel ?? DEFAULTS.logLevel;\n const sentryDsn = fromFile.sentryDsn ?? process.env.SENTRY_DSN;\n const sandboxCpus = fromFile.sandboxCpus;\n const sandboxMemory = fromFile.sandboxMemory;\n\n return {\n provider,\n model,\n thinkingLevel,\n sessionScope,\n logFormat,\n logLevel,\n sentryDsn,\n sandboxCpus,\n sandboxMemory,\n };\n}\n\nexport function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)): string | undefined {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === \"--sandbox\" || arg === \"--download\" || arg === \"--state-dir\") {\n i += 1;\n continue;\n }\n\n if (arg === \"--version\" || arg === \"-v\" || arg === \"-V\") {\n continue;\n }\n\n if (\n arg.startsWith(\"--sandbox=\") ||\n arg.startsWith(\"--download=\") ||\n arg.startsWith(\"--state-dir=\")\n ) {\n continue;\n }\n\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n\n return undefined;\n}\n\nexport function resolveStateDirFromArgv(args = process.argv.slice(2)): string {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--state-dir=\")) {\n return resolve(arg.slice(\"--state-dir=\".length));\n }\n if (arg === \"--state-dir\") {\n return resolve(args[++i] || \"\");\n }\n }\n\n return join(homedir(), \".mama\");\n}\n\nexport function resolveSentryDsn(workspaceDir?: string): string | undefined {\n const fromFile = loadRawAgentConfig(workspaceDir);\n if (fromFile.sentryDsn) {\n return fromFile.sentryDsn;\n }\n\n return process.env.SENTRY_DSN;\n}\n\n/**\n * Externally-visible base URL of the link/OAuth server, e.g.\n * `https://mama.example.com` (no trailing slash). Read from `MOM_LINK_URL`,\n * the same env var the bot uses to build credential onboarding links.\n */\nexport function resolveLinkBaseUrl(): string | undefined {\n const raw = process.env.MOM_LINK_URL?.trim();\n if (!raw) return undefined;\n return raw.replace(/\\/+$/, \"\");\n}\n\nexport function saveAgentConfig(workspaceDir: string, config: Partial<AgentConfig>): void {\n const settingsPath = join(workspaceDir, \"settings.json\");\n\n let existing: Partial<AgentConfig> = {};\n if (existsSync(settingsPath)) {\n try {\n const raw = readFileSync(settingsPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n existing = parsed as Partial<AgentConfig>;\n }\n } catch {\n // Start fresh if file is malformed\n }\n }\n\n const merged = { ...existing, ...config };\n\n const dir = dirname(settingsPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n atomicWritePrivateFile(settingsPath, JSON.stringify(merged, null, 2));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,qBAAa,0BAA2B,SAAQ,KAAK;aACvB,YAAY,EAAE,MAAM;IAAhD,YAA4B,YAAY,EAAE,MAAM,EAG/C;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+HD,wBAAgB,eAAe,IAAI,WAAW,CAE7C;AAED,wBAAgB,8BAA8B,CAAC,eAAe,EAAE,MAAM,GAAG,WAAW,CAMnF;AAED,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC,GAC5F,IAAI,CAWN;AAED,wBAAgB,2BAA2B,CAAC,IAAI,WAAwB,GAAG,MAAM,GAAG,SAAS,CA2B5F;AAED,wBAAgB,uBAAuB,CAAC,IAAI,WAAwB,GAAG,MAAM,CAY5E;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAOrD;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUjE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAIvD;AA6CD,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAwBlE","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport { existsSync, mkdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class MissingGlobalSettingsError extends Error {\n constructor(public readonly settingsPath: string) {\n super(`Missing global settings file at ${settingsPath}`);\n this.name = \"MissingGlobalSettingsError\";\n }\n}\n\nexport interface AgentConfig {\n provider: string;\n model: string;\n thinkingLevel: ThinkingLevel;\n logFormat: \"console\" | \"json\";\n logLevel: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\";\n sentryDsn?: string;\n sandboxCpus?: string;\n sandboxMemory?: string;\n}\n\nconst ONBOARD_SETTINGS: SettingsFileConfig = {\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-5\",\n thinkingLevel: \"off\",\n },\n log: {\n format: \"console\",\n level: \"info\",\n },\n};\n\ninterface SettingsFileConfig {\n llm?: Partial<Pick<AgentConfig, \"provider\" | \"model\" | \"thinkingLevel\">>;\n log?: { format?: AgentConfig[\"logFormat\"]; level?: AgentConfig[\"logLevel\"] };\n sentry?: { dsn?: string };\n sandbox?: { cpus?: string; memory?: string };\n}\n\nfunction loadSettingsFile(settingsPath: string): SettingsFileConfig | undefined {\n if (!existsSync(settingsPath)) {\n return undefined;\n }\n\n const raw = readFileSync(settingsPath, \"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Malformed settings file at ${settingsPath}: ${detail}`);\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\n `Malformed settings file at ${settingsPath}: expected a JSON object at the top level`,\n );\n }\n return parsed as SettingsFileConfig;\n}\n\nfunction getStateDir(): string {\n const raw = process.env.MAMA_STATE_DIR?.trim();\n return raw ? resolve(raw) : join(homedir(), \".mama\");\n}\n\nfunction normalizeSettingsConfig(config: SettingsFileConfig): Partial<AgentConfig> {\n return {\n ...(config.llm?.provider !== undefined ? { provider: config.llm.provider } : {}),\n ...(config.llm?.model !== undefined ? { model: config.llm.model } : {}),\n ...(config.llm?.thinkingLevel !== undefined ? { thinkingLevel: config.llm.thinkingLevel } : {}),\n ...(config.log?.format !== undefined ? { logFormat: config.log.format } : {}),\n ...(config.log?.level !== undefined ? { logLevel: config.log.level } : {}),\n ...(config.sentry?.dsn !== undefined ? { sentryDsn: config.sentry.dsn } : {}),\n ...(config.sandbox?.cpus !== undefined ? { sandboxCpus: config.sandbox.cpus } : {}),\n ...(config.sandbox?.memory !== undefined ? { sandboxMemory: config.sandbox.memory } : {}),\n };\n}\n\nfunction getSettingsPath(): string {\n return join(getStateDir(), \"settings.json\");\n}\n\nfunction requireGlobalSettings(): SettingsFileConfig {\n const settingsPath = getSettingsPath();\n const config = loadSettingsFile(settingsPath);\n if (!config) {\n throw new MissingGlobalSettingsError(settingsPath);\n }\n return config;\n}\n\nfunction requireString(value: string | undefined, path: string): string {\n if (!value) {\n throw new Error(\n `Missing required global setting: ${path}. Run \\`mama --onboard\\` to create settings.json.`,\n );\n }\n return value;\n}\n\nfunction requireThinkingLevel(value: ThinkingLevel | undefined): ThinkingLevel {\n return requireString(value, \"llm.thinkingLevel\") as ThinkingLevel;\n}\n\nfunction requireLogFormat(value: AgentConfig[\"logFormat\"] | undefined): AgentConfig[\"logFormat\"] {\n if (value !== \"console\" && value !== \"json\") {\n throw new Error(\"Missing or invalid required global setting: log.format\");\n }\n return value;\n}\n\nfunction requireLogLevel(value: AgentConfig[\"logLevel\"] | undefined): AgentConfig[\"logLevel\"] {\n const allowed = [\"trace\", \"debug\", \"info\", \"warn\", \"error\"];\n if (!value || !allowed.includes(value)) {\n throw new Error(\"Missing or invalid required global setting: log.level\");\n }\n return value;\n}\n\nfunction toAgentConfig(fromFile: Partial<AgentConfig>): AgentConfig {\n const provider = requireString(fromFile.provider, \"llm.provider\");\n const model = requireString(fromFile.model, \"llm.model\");\n const thinkingLevel = requireThinkingLevel(fromFile.thinkingLevel);\n const logFormat = requireLogFormat(fromFile.logFormat);\n const logLevel = requireLogLevel(fromFile.logLevel);\n const sentryDsn = fromFile.sentryDsn ?? process.env.SENTRY_DSN;\n const sandboxCpus = fromFile.sandboxCpus;\n const sandboxMemory = fromFile.sandboxMemory;\n\n return {\n provider,\n model,\n thinkingLevel,\n logFormat,\n logLevel,\n sentryDsn,\n sandboxCpus,\n sandboxMemory,\n };\n}\n\nfunction loadRawAgentConfig(): Partial<AgentConfig> {\n return normalizeSettingsConfig(requireGlobalSettings());\n}\n\nexport function loadAgentConfig(): AgentConfig {\n return toAgentConfig(loadRawAgentConfig());\n}\n\nexport function loadAgentConfigForConversation(conversationDir: string): AgentConfig {\n const globalConfig = loadRawAgentConfig();\n const conversationConfig = normalizeSettingsConfig(\n loadSettingsFile(join(conversationDir, \"settings.json\")) ?? {},\n );\n return toAgentConfig({ ...globalConfig, ...conversationConfig });\n}\n\nexport function saveConversationModelConfig(\n conversationDir: string,\n config: Pick<AgentConfig, \"provider\" | \"model\"> & Partial<Pick<AgentConfig, \"thinkingLevel\">>,\n): void {\n if (!existsSync(conversationDir)) {\n mkdirSync(conversationDir, { recursive: true });\n }\n const settingsPath = join(conversationDir, \"settings.json\");\n const existing = loadSettingsFile(settingsPath) ?? {};\n const scopedConfig: SettingsFileConfig = {\n ...existing,\n llm: { ...existing.llm, ...config },\n };\n atomicWritePrivateFile(settingsPath, JSON.stringify(scopedConfig, null, 2));\n}\n\nexport function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)): string | undefined {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === \"--sandbox\" || arg === \"--download\" || arg === \"--state-dir\") {\n i += 1;\n continue;\n }\n\n if (arg === \"--version\" || arg === \"-v\" || arg === \"-V\" || arg === \"--onboard\") {\n continue;\n }\n\n if (\n arg.startsWith(\"--sandbox=\") ||\n arg.startsWith(\"--download=\") ||\n arg.startsWith(\"--state-dir=\")\n ) {\n continue;\n }\n\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n\n return undefined;\n}\n\nexport function resolveStateDirFromArgv(args = process.argv.slice(2)): string {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--state-dir=\")) {\n return resolve(arg.slice(\"--state-dir=\".length));\n }\n if (arg === \"--state-dir\") {\n return resolve(args[++i] || \"\");\n }\n }\n\n return join(homedir(), \".mama\");\n}\n\nexport function resolveSentryDsn(): string | undefined {\n const fromFile = normalizeSettingsConfig(loadSettingsFile(getSettingsPath()) ?? {});\n if (fromFile.sentryDsn) {\n return fromFile.sentryDsn;\n }\n\n return process.env.SENTRY_DSN;\n}\n\nexport function createGlobalSettingsFile(stateDir: string): string {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n throw new Error(`Global settings already exists at ${settingsPath}`);\n }\n if (!existsSync(stateDir)) {\n mkdirSync(stateDir, { recursive: true });\n }\n atomicWritePrivateFile(settingsPath, JSON.stringify(ONBOARD_SETTINGS, null, 2));\n return settingsPath;\n}\n\n/**\n * Externally-visible base URL of the link/OAuth server, e.g.\n * `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,\n * the same env var the bot uses to build credential onboarding links.\n */\nexport function resolveLinkBaseUrl(): string | undefined {\n const raw = process.env.MAMA_LINK_URL?.trim();\n if (!raw) return undefined;\n return raw.replace(/\\/+$/, \"\");\n}\n\nfunction hasDefinedValue(values: Record<string, unknown> | undefined): boolean {\n return values !== undefined && Object.values(values).some((value) => value !== undefined);\n}\n\nfunction compactSettingsConfig(config: SettingsFileConfig): SettingsFileConfig {\n return {\n ...(hasDefinedValue(config.llm) ? { llm: config.llm } : {}),\n ...(hasDefinedValue(config.log) ? { log: config.log } : {}),\n ...(hasDefinedValue(config.sentry) ? { sentry: config.sentry } : {}),\n ...(hasDefinedValue(config.sandbox) ? { sandbox: config.sandbox } : {}),\n };\n}\n\nfunction patchSettingsConfig(\n existing: SettingsFileConfig,\n config: Partial<AgentConfig>,\n): SettingsFileConfig {\n const patched: SettingsFileConfig = {\n ...existing,\n llm: {\n ...existing.llm,\n ...(config.provider !== undefined ? { provider: config.provider } : {}),\n ...(config.model !== undefined ? { model: config.model } : {}),\n ...(config.thinkingLevel !== undefined ? { thinkingLevel: config.thinkingLevel } : {}),\n },\n log: {\n ...existing.log,\n ...(config.logFormat !== undefined ? { format: config.logFormat } : {}),\n ...(config.logLevel !== undefined ? { level: config.logLevel } : {}),\n },\n sentry: {\n ...existing.sentry,\n ...(config.sentryDsn !== undefined ? { dsn: config.sentryDsn } : {}),\n },\n sandbox: {\n ...existing.sandbox,\n ...(config.sandboxCpus !== undefined ? { cpus: config.sandboxCpus } : {}),\n ...(config.sandboxMemory !== undefined ? { memory: config.sandboxMemory } : {}),\n },\n };\n return compactSettingsConfig(patched);\n}\n\nexport function saveAgentConfig(config: Partial<AgentConfig>): void {\n const settingsPath = join(getStateDir(), \"settings.json\");\n\n let existing: SettingsFileConfig = ONBOARD_SETTINGS;\n if (existsSync(settingsPath)) {\n try {\n existing = loadSettingsFile(settingsPath) ?? {};\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n const message = detail.startsWith(\"Malformed settings file\")\n ? detail.replace(\"Malformed settings file\", \"Refusing to overwrite malformed settings file\")\n : detail;\n throw new Error(message);\n }\n }\n\n const merged = patchSettingsConfig(existing, config);\n\n const dir = dirname(settingsPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n atomicWritePrivateFile(settingsPath, JSON.stringify(merged, null, 2));\n}\n"]}
|