@geminixiang/mama 0.2.0-beta.4 → 0.2.0-beta.5
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 +100 -421
- 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 +1 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +4 -1
- 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 +1 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +9 -3
- 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/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 +13 -5
- 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 +8 -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/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 +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 +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 +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -40
- 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 +62 -386
- 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 +26 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +285 -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,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,41 @@
|
|
|
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 { SandboxConfig } from "../sandbox.js";
|
|
5
|
+
import type { VaultManager } from "../vault.js";
|
|
6
|
+
export interface LinkTokenStoreLike {
|
|
7
|
+
create(platform: PlatformName, platformUserId: string, conversationId: string, vaultId: string, providerId: string): {
|
|
8
|
+
token: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface SessionViewTokenStoreLike {
|
|
12
|
+
create(platform: PlatformName, platformUserId: string, conversationId: string, sessionKey: string, sessionFile: string): {
|
|
13
|
+
token: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface CommandServices {
|
|
17
|
+
workingDir: string;
|
|
18
|
+
sandbox: SandboxConfig;
|
|
19
|
+
vaultManager: VaultManager;
|
|
20
|
+
bindingStore?: UserBindingStore;
|
|
21
|
+
provisioner?: DockerContainerManager;
|
|
22
|
+
linkTokenStore: LinkTokenStoreLike;
|
|
23
|
+
sessionViewTokenStore: SessionViewTokenStoreLike;
|
|
24
|
+
portalBaseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface CommandContext {
|
|
27
|
+
bot: Bot;
|
|
28
|
+
responseCtx: BotAdapters["responseCtx"];
|
|
29
|
+
platform: PlatformName;
|
|
30
|
+
platformUserId: string;
|
|
31
|
+
conversationId: string;
|
|
32
|
+
vaultConversationId?: string;
|
|
33
|
+
sessionKey: string;
|
|
34
|
+
commandText: string;
|
|
35
|
+
privateConversation: boolean;
|
|
36
|
+
services: CommandServices;
|
|
37
|
+
}
|
|
38
|
+
export interface CommandHandler {
|
|
39
|
+
tryHandle(context: CommandContext): Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
//# 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,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,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 { 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 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 { 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 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
|
@@ -9,15 +9,15 @@ export interface AgentConfig {
|
|
|
9
9
|
sandboxCpus?: string;
|
|
10
10
|
sandboxMemory?: string;
|
|
11
11
|
}
|
|
12
|
-
export declare function loadAgentConfig(
|
|
12
|
+
export declare function loadAgentConfig(): AgentConfig;
|
|
13
13
|
export declare function resolveWorkspaceDirFromArgv(args?: string[]): string | undefined;
|
|
14
14
|
export declare function resolveStateDirFromArgv(args?: string[]): string;
|
|
15
|
-
export declare function resolveSentryDsn(
|
|
15
|
+
export declare function resolveSentryDsn(): string | undefined;
|
|
16
16
|
/**
|
|
17
17
|
* Externally-visible base URL of the link/OAuth server, e.g.
|
|
18
|
-
* `https://mama.example.com` (no trailing slash). Read from `
|
|
18
|
+
* `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,
|
|
19
19
|
* the same env var the bot uses to build credential onboarding links.
|
|
20
20
|
*/
|
|
21
21
|
export declare function resolveLinkBaseUrl(): string | undefined;
|
|
22
|
-
export declare function saveAgentConfig(
|
|
22
|
+
export declare function saveAgentConfig(config: Partial<AgentConfig>): void;
|
|
23
23
|
//# 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;
|
|
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;AAyCD,wBAAgB,eAAe,IAAI,WAAW,CAwB7C;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;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAIvD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CA+BlE","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 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 Partial<AgentConfig>;\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 loadRawAgentConfig(): Partial<AgentConfig> {\n return loadConfigFile(join(getStateDir(), \"settings.json\")) ?? {};\n}\n\nexport function loadAgentConfig(): AgentConfig {\n const fromFile = loadRawAgentConfig();\n\n const provider = fromFile.provider || process.env.MAMA_AI_PROVIDER || DEFAULTS.provider;\n const model = fromFile.model || process.env.MAMA_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(): string | undefined {\n const fromFile = loadRawAgentConfig();\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 `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\nexport function saveAgentConfig(config: Partial<AgentConfig>): void {\n const settingsPath = join(getStateDir(), \"settings.json\");\n\n let existing: Partial<AgentConfig> = {};\n if (existsSync(settingsPath)) {\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(\n `Refusing to overwrite malformed settings file at ${settingsPath}: ${detail}`,\n );\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\n `Refusing to overwrite malformed settings file at ${settingsPath}: expected a JSON object at the top level`,\n );\n }\n existing = parsed as Partial<AgentConfig>;\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"]}
|
package/dist/config.js
CHANGED
|
@@ -14,40 +14,31 @@ function loadConfigFile(settingsPath) {
|
|
|
14
14
|
if (!existsSync(settingsPath)) {
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
18
|
+
let parsed;
|
|
17
19
|
try {
|
|
18
|
-
|
|
19
|
-
const parsed = JSON.parse(raw);
|
|
20
|
-
if (parsed && typeof parsed === "object") {
|
|
21
|
-
return parsed;
|
|
22
|
-
}
|
|
20
|
+
parsed = JSON.parse(raw);
|
|
23
21
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
22
|
+
catch (err) {
|
|
23
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
24
|
+
throw new Error(`Malformed settings file at ${settingsPath}: ${detail}`);
|
|
26
25
|
}
|
|
27
|
-
|
|
26
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
27
|
+
throw new Error(`Malformed settings file at ${settingsPath}: expected a JSON object at the top level`);
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
28
30
|
}
|
|
29
|
-
function
|
|
31
|
+
function getStateDir() {
|
|
30
32
|
const raw = process.env.MAMA_STATE_DIR?.trim();
|
|
31
|
-
return raw ? resolve(raw) :
|
|
33
|
+
return raw ? resolve(raw) : join(homedir(), ".mama");
|
|
32
34
|
}
|
|
33
|
-
function loadRawAgentConfig(
|
|
34
|
-
|
|
35
|
-
const candidates = [
|
|
36
|
-
...(stateDir ? [join(stateDir, "settings.json")] : []),
|
|
37
|
-
...(workspaceDir ? [join(workspaceDir, "settings.json")] : []),
|
|
38
|
-
];
|
|
39
|
-
for (const settingsPath of candidates) {
|
|
40
|
-
const config = loadConfigFile(settingsPath);
|
|
41
|
-
if (config) {
|
|
42
|
-
return config;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return {};
|
|
35
|
+
function loadRawAgentConfig() {
|
|
36
|
+
return loadConfigFile(join(getStateDir(), "settings.json")) ?? {};
|
|
46
37
|
}
|
|
47
|
-
export function loadAgentConfig(
|
|
48
|
-
const fromFile = loadRawAgentConfig(
|
|
49
|
-
const provider = fromFile.provider || process.env.
|
|
50
|
-
const model = fromFile.model || process.env.
|
|
38
|
+
export function loadAgentConfig() {
|
|
39
|
+
const fromFile = loadRawAgentConfig();
|
|
40
|
+
const provider = fromFile.provider || process.env.MAMA_AI_PROVIDER || DEFAULTS.provider;
|
|
41
|
+
const model = fromFile.model || process.env.MAMA_AI_MODEL || DEFAULTS.model;
|
|
51
42
|
const thinkingLevel = fromFile.thinkingLevel ?? DEFAULTS.thinkingLevel;
|
|
52
43
|
const sessionScope = fromFile.sessionScope ?? DEFAULTS.sessionScope;
|
|
53
44
|
const logFormat = fromFile.logFormat ?? DEFAULTS.logFormat;
|
|
@@ -100,8 +91,8 @@ export function resolveStateDirFromArgv(args = process.argv.slice(2)) {
|
|
|
100
91
|
}
|
|
101
92
|
return join(homedir(), ".mama");
|
|
102
93
|
}
|
|
103
|
-
export function resolveSentryDsn(
|
|
104
|
-
const fromFile = loadRawAgentConfig(
|
|
94
|
+
export function resolveSentryDsn() {
|
|
95
|
+
const fromFile = loadRawAgentConfig();
|
|
105
96
|
if (fromFile.sentryDsn) {
|
|
106
97
|
return fromFile.sentryDsn;
|
|
107
98
|
}
|
|
@@ -109,29 +100,32 @@ export function resolveSentryDsn(workspaceDir) {
|
|
|
109
100
|
}
|
|
110
101
|
/**
|
|
111
102
|
* Externally-visible base URL of the link/OAuth server, e.g.
|
|
112
|
-
* `https://mama.example.com` (no trailing slash). Read from `
|
|
103
|
+
* `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,
|
|
113
104
|
* the same env var the bot uses to build credential onboarding links.
|
|
114
105
|
*/
|
|
115
106
|
export function resolveLinkBaseUrl() {
|
|
116
|
-
const raw = process.env.
|
|
107
|
+
const raw = process.env.MAMA_LINK_URL?.trim();
|
|
117
108
|
if (!raw)
|
|
118
109
|
return undefined;
|
|
119
110
|
return raw.replace(/\/+$/, "");
|
|
120
111
|
}
|
|
121
|
-
export function saveAgentConfig(
|
|
122
|
-
const settingsPath = join(
|
|
112
|
+
export function saveAgentConfig(config) {
|
|
113
|
+
const settingsPath = join(getStateDir(), "settings.json");
|
|
123
114
|
let existing = {};
|
|
124
115
|
if (existsSync(settingsPath)) {
|
|
116
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
117
|
+
let parsed;
|
|
125
118
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
123
|
+
throw new Error(`Refusing to overwrite malformed settings file at ${settingsPath}: ${detail}`);
|
|
131
124
|
}
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
126
|
+
throw new Error(`Refusing to overwrite malformed settings file at ${settingsPath}: expected a JSON object at the top level`);
|
|
134
127
|
}
|
|
128
|
+
existing = parsed;
|
|
135
129
|
}
|
|
136
130
|
const merged = { ...existing, ...config };
|
|
137
131
|
const dir = dirname(settingsPath);
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","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.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","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 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 Partial<AgentConfig>;\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 loadRawAgentConfig(): Partial<AgentConfig> {\n return loadConfigFile(join(getStateDir(), \"settings.json\")) ?? {};\n}\n\nexport function loadAgentConfig(): AgentConfig {\n const fromFile = loadRawAgentConfig();\n\n const provider = fromFile.provider || process.env.MAMA_AI_PROVIDER || DEFAULTS.provider;\n const model = fromFile.model || process.env.MAMA_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(): string | undefined {\n const fromFile = loadRawAgentConfig();\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 `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\nexport function saveAgentConfig(config: Partial<AgentConfig>): void {\n const settingsPath = join(getStateDir(), \"settings.json\");\n\n let existing: Partial<AgentConfig> = {};\n if (existsSync(settingsPath)) {\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(\n `Refusing to overwrite malformed settings file at ${settingsPath}: ${detail}`,\n );\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\n `Refusing to overwrite malformed settings file at ${settingsPath}: expected a JSON object at the top level`,\n );\n }\n existing = parsed as Partial<AgentConfig>;\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"]}
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,KAAK,cAAc,EAEnB,eAAe,EAChB,MAAM,+BAA+B,CAAC;AASvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAOD,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAQD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sGAAsG;IACtG,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,cAAc,EAC9B,eAAe,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,SAAS,EACrB,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,MAAM,CAAC,CAiIjB;AASD,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAEhF;AAED,wBAAsB,kBAAkB,CACtC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAmBxC","sourcesContent":["/**\n * Context management for mama.\n *\n * Mama uses two data sources per conversation:\n * - sessions/*.jsonl: Structured session history for agent context\n * - log.jsonl: Human-readable conversation history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - createMamaSettingsManager: Creates an in-memory SettingsManager for AgentSession\n */\n\nimport type { Message, UserMessage } from \"@mariozechner/pi-ai\";\nimport {\n type SessionManager,\n type SessionMessageEntry,\n SettingsManager,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\n/**\n * Time range for filtering log messages\n */\nexport interface TimeRange {\n start: number; // Unix timestamp in ms\n end: number;\n}\n\n/**\n * Default number of days to sync when no time range is specified\n */\nconst DEFAULT_SYNC_DAYS = 10;\n\nexport interface ConversationLogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isBot?: boolean;\n}\n\ninterface ExistingSessionMessage {\n timestamp?: number;\n rawText: string;\n normalizedText: string;\n}\n\n/**\n * Thread filter for scoping log sync to a specific thread session.\n * When provided, only messages belonging to this thread are synced,\n * preventing cross-thread context contamination.\n */\nexport interface ThreadFilter {\n /** Filter mode: a specific thread, or top-level messages only for persistent channel/chat sessions */\n scope?: \"thread\" | \"top-level\";\n /** The root message timestamp (user's original message ts, derived from sessionKey) */\n rootTs: string;\n /** The thread anchor timestamp (bot's first reply ts, used as thread_ts by Slack replies) */\n threadTs?: string;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * This ensures that messages logged while mama wasn't running (conversation chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param conversationDir - Path to the conversation directory containing log.jsonl\n * @param excludeSlackTs - Current platform message ID/timestamp (will be added via prompt(), not sync)\n * @param timeRange - Optional time range to filter log entries (defaults to last 10 days)\n * @param threadFilter - Optional thread filter to scope sync to a specific thread\n * @returns Number of messages synced\n */\nexport async function syncLogToSessionManager(\n sessionManager: SessionManager,\n conversationDir: string,\n excludeSlackTs?: string,\n timeRange?: TimeRange,\n threadFilter?: ThreadFilter,\n): Promise<number> {\n // Calculate default time range (last 10 days) if not provided\n const now = Date.now();\n const defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;\n const range = timeRange ?? { start: defaultStart, end: now };\n const logFile = join(conversationDir, \"log.jsonl\");\n\n if (!existsSync(logFile)) return 0;\n\n // Build a list of existing session messages for dedupe.\n // Live user prompts carry a formatted timestamp in the text and use Date.now(),\n // while log.jsonl uses the platform event timestamp. We therefore need a small\n // fuzzy match window in addition to the exact timestamp/content match used for\n // already-synced log entries.\n const existingMessages: ExistingSessionMessage[] = [];\n const existingMessageKeys = new Set<string>();\n for (const entry of sessionManager.getEntries()) {\n if (entry.type !== \"message\") continue;\n const msgEntry = entry as SessionMessageEntry;\n const message = msgEntry.message as Message;\n const contentText = Array.isArray(message.content)\n ? message.content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\\n\\n\")\n : typeof message.content === \"string\"\n ? message.content\n : \"\";\n existingMessages.push({\n timestamp: typeof message.timestamp === \"number\" ? message.timestamp : undefined,\n rawText: contentText,\n normalizedText: normalizeComparableUserText(contentText),\n });\n if (typeof message.timestamp === \"number\") {\n existingMessageKeys.add(`${message.timestamp}:${contentText}`);\n }\n }\n\n // Read log.jsonl and find user messages not in context\n const logContent = await readFile(logFile, \"utf-8\");\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n const newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n for (const line of logLines) {\n try {\n const logMsg: ConversationLogMessage = JSON.parse(line);\n\n const slackTs = logMsg.ts;\n const date = logMsg.date;\n if (!slackTs || !date) continue;\n\n // Skip the current message being processed (will be added via prompt())\n if (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n // While queued messages are being processed, newer messages may already be present\n // in log.jsonl. Do not look ahead into those future messages when building the\n // current turn's context.\n if (!isMessageAtOrBeforeCurrent(slackTs, excludeSlackTs)) continue;\n\n // Skip bot messages - added through agent flow\n if (logMsg.isBot) continue;\n\n // Thread filtering: only sync messages belonging to this session's thread\n if (threadFilter) {\n if (threadFilter.scope === \"top-level\") {\n // Persistent top-level sessions should only ingest top-level messages.\n // This avoids pulling in unrelated replies from other threads.\n if (logMsg.threadTs) {\n continue;\n }\n } else {\n if (logMsg.threadTs) {\n // Thread reply: only include if threadTs matches our thread anchor or rootTs\n if (\n logMsg.threadTs !== threadFilter.threadTs &&\n logMsg.threadTs !== threadFilter.rootTs\n ) {\n continue;\n }\n } else {\n // Top-level message: only include if it's this session's root message\n if (slackTs !== threadFilter.rootTs) {\n continue;\n }\n }\n }\n }\n\n // Build the message text as it would appear in context\n const threadContext = logMsg.threadTs ? ` [in-thread:${logMsg.threadTs}]` : \"\";\n const messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]${threadContext}: ${logMsg.text || \"\"}`;\n\n const msgTime = new Date(date).getTime() || Date.now();\n const messageKey = `${msgTime}:${messageText}`;\n if (existingMessageKeys.has(messageKey)) continue;\n if (hasExistingSessionMessage(existingMessages, msgTime, messageText)) continue;\n\n // Skip messages outside the time range\n if (msgTime < range.start || msgTime > range.end) continue;\n\n const userMessage: UserMessage = {\n role: \"user\",\n content: [{ type: \"text\", text: messageText }],\n timestamp: msgTime,\n };\n\n newMessages.push({ timestamp: msgTime, message: userMessage });\n existingMessages.push({\n timestamp: msgTime,\n rawText: messageText,\n normalizedText: normalizeComparableUserText(messageText),\n });\n existingMessageKeys.add(messageKey); // Track to avoid duplicates within this sync\n } catch {\n // Skip malformed lines\n }\n }\n\n if (newMessages.length === 0) return 0;\n\n // Sort by timestamp and add to session\n newMessages.sort((a, b) => a.timestamp - b.timestamp);\n\n for (const { message } of newMessages) {\n sessionManager.appendMessage(message);\n }\n\n return newMessages.length;\n}\n\n// ============================================================================\n// Settings manager for mama\n// ============================================================================\n\n// Mama manages model/provider config through its own config.ts / settings.json.\n// We use an in-memory SettingsManager so AgentSession has valid defaults\n// without interfering with coding-agent's global settings files.\nexport function createMamaSettingsManager(_workspaceDir: string): SettingsManager {\n return SettingsManager.inMemory();\n}\n\nexport async function findLogMessageById(\n conversationDir: string,\n messageId: string,\n): Promise<ConversationLogMessage | null> {\n const logFile = join(conversationDir, \"log.jsonl\");\n if (!existsSync(logFile)) return null;\n\n const logContent = await readFile(logFile, \"utf-8\");\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = logLines.length - 1; i >= 0; i--) {\n try {\n const entry = JSON.parse(logLines[i]) as ConversationLogMessage;\n if (entry.ts === messageId) {\n return entry;\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return null;\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction hasExistingSessionMessage(\n existingMessages: ExistingSessionMessage[],\n timestamp: number,\n text: string,\n): boolean {\n const normalizedText = normalizeComparableUserText(text);\n return existingMessages.some((existing) => {\n if (existing.timestamp === timestamp && existing.rawText === text) {\n return true;\n }\n if (existing.normalizedText !== normalizedText || existing.timestamp === undefined) {\n return false;\n }\n return existing.timestamp >= timestamp;\n });\n}\n\nfunction isMessageAtOrBeforeCurrent(messageId: string, currentMessageId?: string): boolean {\n if (!currentMessageId) return true;\n const comparison = compareMessageIds(messageId, currentMessageId);\n return comparison === null || comparison <= 0;\n}\n\nfunction compareMessageIds(a: string, b: string): number | null {\n if (/^\\d+$/.test(a) && /^\\d+$/.test(b)) {\n const left = BigInt(a);\n const right = BigInt(b);\n return left < right ? -1 : left > right ? 1 : 0;\n }\n\n const left = Number(a);\n const right = Number(b);\n if (Number.isFinite(left) && Number.isFinite(right)) {\n return left < right ? -1 : left > right ? 1 : 0;\n }\n\n return null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,KAAK,cAAc,EAEnB,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAUvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAOD,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAQD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sGAAsG;IACtG,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,cAAc,EAC9B,eAAe,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,SAAS,EACrB,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,MAAM,CAAC,CAuIjB;AASD,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAEhF;AAED,wBAAsB,kBAAkB,CACtC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAwBxC","sourcesContent":["/**\n * Context management for mama.\n *\n * Mama uses two data sources per conversation:\n * - sessions/*.jsonl: Structured session history for agent context\n * - log.jsonl: Human-readable conversation history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - createMamaSettingsManager: Creates an in-memory SettingsManager for AgentSession\n */\n\nimport type { Message, UserMessage } from \"@mariozechner/pi-ai\";\nimport {\n type SessionManager,\n type SessionMessageEntry,\n SettingsManager,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\n/**\n * Time range for filtering log messages\n */\nexport interface TimeRange {\n start: number; // Unix timestamp in ms\n end: number;\n}\n\n/**\n * Default number of days to sync when no time range is specified\n */\nconst DEFAULT_SYNC_DAYS = 10;\n\nexport interface ConversationLogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isBot?: boolean;\n}\n\ninterface ExistingSessionMessage {\n timestamp?: number;\n rawText: string;\n normalizedText: string;\n}\n\n/**\n * Thread filter for scoping log sync to a specific thread session.\n * When provided, only messages belonging to this thread are synced,\n * preventing cross-thread context contamination.\n */\nexport interface ThreadFilter {\n /** Filter mode: a specific thread, or top-level messages only for persistent channel/chat sessions */\n scope?: \"thread\" | \"top-level\";\n /** The root message timestamp (user's original message ts, derived from sessionKey) */\n rootTs: string;\n /** The thread anchor timestamp (bot's first reply ts, used as thread_ts by Slack replies) */\n threadTs?: string;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * This ensures that messages logged while mama wasn't running (conversation chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param conversationDir - Path to the conversation directory containing log.jsonl\n * @param excludeSlackTs - Current platform message ID/timestamp (will be added via prompt(), not sync)\n * @param timeRange - Optional time range to filter log entries (defaults to last 10 days)\n * @param threadFilter - Optional thread filter to scope sync to a specific thread\n * @returns Number of messages synced\n */\nexport async function syncLogToSessionManager(\n sessionManager: SessionManager,\n conversationDir: string,\n excludeSlackTs?: string,\n timeRange?: TimeRange,\n threadFilter?: ThreadFilter,\n): Promise<number> {\n // Calculate default time range (last 10 days) if not provided\n const now = Date.now();\n const defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;\n const range = timeRange ?? { start: defaultStart, end: now };\n const logFile = join(conversationDir, \"log.jsonl\");\n\n if (!existsSync(logFile)) return 0;\n\n // Build a list of existing session messages for dedupe.\n // Live user prompts carry a formatted timestamp in the text and use Date.now(),\n // while log.jsonl uses the platform event timestamp. We therefore need a small\n // fuzzy match window in addition to the exact timestamp/content match used for\n // already-synced log entries.\n const existingMessages: ExistingSessionMessage[] = [];\n const existingMessageKeys = new Set<string>();\n for (const entry of sessionManager.getEntries()) {\n if (entry.type !== \"message\") continue;\n const msgEntry = entry as SessionMessageEntry;\n const message = msgEntry.message as Message;\n const contentText = Array.isArray(message.content)\n ? message.content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\\n\\n\")\n : typeof message.content === \"string\"\n ? message.content\n : \"\";\n existingMessages.push({\n timestamp: typeof message.timestamp === \"number\" ? message.timestamp : undefined,\n rawText: contentText,\n normalizedText: normalizeComparableUserText(contentText),\n });\n if (typeof message.timestamp === \"number\") {\n existingMessageKeys.add(`${message.timestamp}:${contentText}`);\n }\n }\n\n // Read log.jsonl and find user messages not in context\n const logContent = await readFile(logFile, \"utf-8\");\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n const newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n for (let lineIdx = 0; lineIdx < logLines.length; lineIdx++) {\n const line = logLines[lineIdx];\n let logMsg: ConversationLogMessage;\n try {\n logMsg = JSON.parse(line) as ConversationLogMessage;\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${lineIdx + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n continue;\n }\n\n const slackTs = logMsg.ts;\n const date = logMsg.date;\n if (!slackTs || !date) continue;\n\n // Skip the current message being processed (will be added via prompt())\n if (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n // While queued messages are being processed, newer messages may already be present\n // in log.jsonl. Do not look ahead into those future messages when building the\n // current turn's context.\n if (!isMessageAtOrBeforeCurrent(slackTs, excludeSlackTs)) continue;\n\n // Skip bot messages - added through agent flow\n if (logMsg.isBot) continue;\n\n // Thread filtering: only sync messages belonging to this session's thread\n if (threadFilter) {\n if (threadFilter.scope === \"top-level\") {\n // Persistent top-level sessions should only ingest top-level messages.\n // This avoids pulling in unrelated replies from other threads.\n if (logMsg.threadTs) {\n continue;\n }\n } else {\n if (logMsg.threadTs) {\n // Thread reply: only include if threadTs matches our thread anchor or rootTs\n if (\n logMsg.threadTs !== threadFilter.threadTs &&\n logMsg.threadTs !== threadFilter.rootTs\n ) {\n continue;\n }\n } else {\n // Top-level message: only include if it's this session's root message\n if (slackTs !== threadFilter.rootTs) {\n continue;\n }\n }\n }\n }\n\n // Build the message text as it would appear in context\n const threadContext = logMsg.threadTs ? ` [in-thread:${logMsg.threadTs}]` : \"\";\n const messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]${threadContext}: ${logMsg.text || \"\"}`;\n\n const msgTime = new Date(date).getTime() || Date.now();\n const messageKey = `${msgTime}:${messageText}`;\n if (existingMessageKeys.has(messageKey)) continue;\n if (hasExistingSessionMessage(existingMessages, msgTime, messageText)) continue;\n\n // Skip messages outside the time range\n if (msgTime < range.start || msgTime > range.end) continue;\n\n const userMessage: UserMessage = {\n role: \"user\",\n content: [{ type: \"text\", text: messageText }],\n timestamp: msgTime,\n };\n\n newMessages.push({ timestamp: msgTime, message: userMessage });\n existingMessages.push({\n timestamp: msgTime,\n rawText: messageText,\n normalizedText: normalizeComparableUserText(messageText),\n });\n existingMessageKeys.add(messageKey); // Track to avoid duplicates within this sync\n }\n\n if (newMessages.length === 0) return 0;\n\n // Sort by timestamp and add to session\n newMessages.sort((a, b) => a.timestamp - b.timestamp);\n\n for (const { message } of newMessages) {\n sessionManager.appendMessage(message);\n }\n\n return newMessages.length;\n}\n\n// ============================================================================\n// Settings manager for mama\n// ============================================================================\n\n// Mama manages model/provider config through its own config.ts / settings.json.\n// We use an in-memory SettingsManager so AgentSession has valid defaults\n// without interfering with coding-agent's global settings files.\nexport function createMamaSettingsManager(_workspaceDir: string): SettingsManager {\n return SettingsManager.inMemory();\n}\n\nexport async function findLogMessageById(\n conversationDir: string,\n messageId: string,\n): Promise<ConversationLogMessage | null> {\n const logFile = join(conversationDir, \"log.jsonl\");\n if (!existsSync(logFile)) return null;\n\n const logContent = await readFile(logFile, \"utf-8\");\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = logLines.length - 1; i >= 0; i--) {\n let entry: ConversationLogMessage;\n try {\n entry = JSON.parse(logLines[i]) as ConversationLogMessage;\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n continue;\n }\n if (entry.ts === messageId) {\n return entry;\n }\n }\n\n return null;\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction hasExistingSessionMessage(\n existingMessages: ExistingSessionMessage[],\n timestamp: number,\n text: string,\n): boolean {\n const normalizedText = normalizeComparableUserText(text);\n return existingMessages.some((existing) => {\n if (existing.timestamp === timestamp && existing.rawText === text) {\n return true;\n }\n if (existing.normalizedText !== normalizedText || existing.timestamp === undefined) {\n return false;\n }\n return existing.timestamp >= timestamp;\n });\n}\n\nfunction isMessageAtOrBeforeCurrent(messageId: string, currentMessageId?: string): boolean {\n if (!currentMessageId) return true;\n const comparison = compareMessageIds(messageId, currentMessageId);\n return comparison === null || comparison <= 0;\n}\n\nfunction compareMessageIds(a: string, b: string): number | null {\n if (/^\\d+$/.test(a) && /^\\d+$/.test(b)) {\n const left = BigInt(a);\n const right = BigInt(b);\n return left < right ? -1 : left > right ? 1 : 0;\n }\n\n const left = Number(a);\n const right = Number(b);\n if (Number.isFinite(left) && Number.isFinite(right)) {\n return left < right ? -1 : left > right ? 1 : 0;\n }\n\n return null;\n}\n"]}
|
package/dist/context.js
CHANGED
|
@@ -13,6 +13,7 @@ import { SettingsManager, } from "@mariozechner/pi-coding-agent";
|
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
14
|
import { readFile } from "fs/promises";
|
|
15
15
|
import { join } from "path";
|
|
16
|
+
import * as log from "./log.js";
|
|
16
17
|
/**
|
|
17
18
|
* Default number of days to sync when no time range is specified
|
|
18
19
|
*/
|
|
@@ -71,77 +72,80 @@ export async function syncLogToSessionManager(sessionManager, conversationDir, e
|
|
|
71
72
|
const logContent = await readFile(logFile, "utf-8");
|
|
72
73
|
const logLines = logContent.trim().split("\n").filter(Boolean);
|
|
73
74
|
const newMessages = [];
|
|
74
|
-
for (
|
|
75
|
+
for (let lineIdx = 0; lineIdx < logLines.length; lineIdx++) {
|
|
76
|
+
const line = logLines[lineIdx];
|
|
77
|
+
let logMsg;
|
|
75
78
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
logMsg = JSON.parse(line);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
log.logWarning(`Skipping malformed log entry at ${logFile}:${lineIdx + 1}`, err instanceof Error ? err.message : String(err));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const slackTs = logMsg.ts;
|
|
86
|
+
const date = logMsg.date;
|
|
87
|
+
if (!slackTs || !date)
|
|
88
|
+
continue;
|
|
89
|
+
// Skip the current message being processed (will be added via prompt())
|
|
90
|
+
if (excludeSlackTs && slackTs === excludeSlackTs)
|
|
91
|
+
continue;
|
|
92
|
+
// While queued messages are being processed, newer messages may already be present
|
|
93
|
+
// in log.jsonl. Do not look ahead into those future messages when building the
|
|
94
|
+
// current turn's context.
|
|
95
|
+
if (!isMessageAtOrBeforeCurrent(slackTs, excludeSlackTs))
|
|
96
|
+
continue;
|
|
97
|
+
// Skip bot messages - added through agent flow
|
|
98
|
+
if (logMsg.isBot)
|
|
99
|
+
continue;
|
|
100
|
+
// Thread filtering: only sync messages belonging to this session's thread
|
|
101
|
+
if (threadFilter) {
|
|
102
|
+
if (threadFilter.scope === "top-level") {
|
|
103
|
+
// Persistent top-level sessions should only ingest top-level messages.
|
|
104
|
+
// This avoids pulling in unrelated replies from other threads.
|
|
105
|
+
if (logMsg.threadTs) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (logMsg.threadTs) {
|
|
111
|
+
// Thread reply: only include if threadTs matches our thread anchor or rootTs
|
|
112
|
+
if (logMsg.threadTs !== threadFilter.threadTs &&
|
|
113
|
+
logMsg.threadTs !== threadFilter.rootTs) {
|
|
98
114
|
continue;
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
117
|
else {
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
logMsg.threadTs !== threadFilter.rootTs) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
// Top-level message: only include if it's this session's root message
|
|
111
|
-
if (slackTs !== threadFilter.rootTs) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
118
|
+
// Top-level message: only include if it's this session's root message
|
|
119
|
+
if (slackTs !== threadFilter.rootTs) {
|
|
120
|
+
continue;
|
|
114
121
|
}
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
|
-
// Build the message text as it would appear in context
|
|
118
|
-
const threadContext = logMsg.threadTs ? ` [in-thread:${logMsg.threadTs}]` : "";
|
|
119
|
-
const messageText = `[${logMsg.userName || logMsg.user || "unknown"}]${threadContext}: ${logMsg.text || ""}`;
|
|
120
|
-
const msgTime = new Date(date).getTime() || Date.now();
|
|
121
|
-
const messageKey = `${msgTime}:${messageText}`;
|
|
122
|
-
if (existingMessageKeys.has(messageKey))
|
|
123
|
-
continue;
|
|
124
|
-
if (hasExistingSessionMessage(existingMessages, msgTime, messageText))
|
|
125
|
-
continue;
|
|
126
|
-
// Skip messages outside the time range
|
|
127
|
-
if (msgTime < range.start || msgTime > range.end)
|
|
128
|
-
continue;
|
|
129
|
-
const userMessage = {
|
|
130
|
-
role: "user",
|
|
131
|
-
content: [{ type: "text", text: messageText }],
|
|
132
|
-
timestamp: msgTime,
|
|
133
|
-
};
|
|
134
|
-
newMessages.push({ timestamp: msgTime, message: userMessage });
|
|
135
|
-
existingMessages.push({
|
|
136
|
-
timestamp: msgTime,
|
|
137
|
-
rawText: messageText,
|
|
138
|
-
normalizedText: normalizeComparableUserText(messageText),
|
|
139
|
-
});
|
|
140
|
-
existingMessageKeys.add(messageKey); // Track to avoid duplicates within this sync
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
// Skip malformed lines
|
|
144
124
|
}
|
|
125
|
+
// Build the message text as it would appear in context
|
|
126
|
+
const threadContext = logMsg.threadTs ? ` [in-thread:${logMsg.threadTs}]` : "";
|
|
127
|
+
const messageText = `[${logMsg.userName || logMsg.user || "unknown"}]${threadContext}: ${logMsg.text || ""}`;
|
|
128
|
+
const msgTime = new Date(date).getTime() || Date.now();
|
|
129
|
+
const messageKey = `${msgTime}:${messageText}`;
|
|
130
|
+
if (existingMessageKeys.has(messageKey))
|
|
131
|
+
continue;
|
|
132
|
+
if (hasExistingSessionMessage(existingMessages, msgTime, messageText))
|
|
133
|
+
continue;
|
|
134
|
+
// Skip messages outside the time range
|
|
135
|
+
if (msgTime < range.start || msgTime > range.end)
|
|
136
|
+
continue;
|
|
137
|
+
const userMessage = {
|
|
138
|
+
role: "user",
|
|
139
|
+
content: [{ type: "text", text: messageText }],
|
|
140
|
+
timestamp: msgTime,
|
|
141
|
+
};
|
|
142
|
+
newMessages.push({ timestamp: msgTime, message: userMessage });
|
|
143
|
+
existingMessages.push({
|
|
144
|
+
timestamp: msgTime,
|
|
145
|
+
rawText: messageText,
|
|
146
|
+
normalizedText: normalizeComparableUserText(messageText),
|
|
147
|
+
});
|
|
148
|
+
existingMessageKeys.add(messageKey); // Track to avoid duplicates within this sync
|
|
145
149
|
}
|
|
146
150
|
if (newMessages.length === 0)
|
|
147
151
|
return 0;
|
|
@@ -168,14 +172,16 @@ export async function findLogMessageById(conversationDir, messageId) {
|
|
|
168
172
|
const logContent = await readFile(logFile, "utf-8");
|
|
169
173
|
const logLines = logContent.trim().split("\n").filter(Boolean);
|
|
170
174
|
for (let i = logLines.length - 1; i >= 0; i--) {
|
|
175
|
+
let entry;
|
|
171
176
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
177
|
+
entry = JSON.parse(logLines[i]);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
log.logWarning(`Skipping malformed log entry at ${logFile}:${i + 1}`, err instanceof Error ? err.message : String(err));
|
|
181
|
+
continue;
|
|
176
182
|
}
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
if (entry.ts === messageId) {
|
|
184
|
+
return entry;
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
return null;
|