@geminixiang/mikan 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +54 -181
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +5 -4
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/shared.d.ts +3 -2
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +11 -11
- package/dist/adapters/shared.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 +43 -1
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +2 -3
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/admin/portal.d.ts +27 -0
- package/dist/admin/portal.d.ts.map +1 -0
- package/dist/admin/portal.js +2029 -0
- package/dist/admin/portal.js.map +1 -0
- package/dist/admin/store.d.ts +22 -0
- package/dist/admin/store.d.ts.map +1 -0
- package/dist/admin/store.js +39 -0
- package/dist/admin/store.js.map +1 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -9
- package/dist/agent.js.map +1 -1
- package/dist/commands/admin.d.ts +8 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/admin.js +59 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +8 -7
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +38 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +2 -2
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +13 -3
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +2 -2
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +3 -6
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +11 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/utils.d.ts +1 -0
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +5 -0
- package/dist/commands/utils.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -13
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -5
- package/dist/events.js.map +1 -1
- package/dist/file-guards.d.ts.map +1 -1
- package/dist/file-guards.js +10 -7
- package/dist/file-guards.js.map +1 -1
- package/dist/login/portal.d.ts +11 -1
- package/dist/login/portal.d.ts.map +1 -1
- package/dist/login/portal.js +57 -175
- package/dist/login/portal.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -1
- package/dist/main.js.map +1 -1
- package/dist/portal-shell.d.ts +30 -0
- package/dist/portal-shell.d.ts.map +1 -0
- package/dist/portal-shell.js +371 -0
- package/dist/portal-shell.js.map +1 -0
- package/dist/session-view/portal.d.ts.map +1 -1
- package/dist/session-view/portal.js +88 -242
- package/dist/session-view/portal.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +30 -12
- package/dist/store.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +2 -8
- package/dist/vault.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
2
|
import { loadConversationAutoReplyConfig, saveConversationAutoReplyConfig, } from "../config.js";
|
|
3
3
|
import { matchCommand } from "./parse.js";
|
|
4
|
-
import { replyDiagnosticWithContext } from "./utils.js";
|
|
4
|
+
import { formatCommandSummary, replyDiagnosticWithContext } from "./utils.js";
|
|
5
5
|
const AUTO_REPLY_COMMANDS = [
|
|
6
6
|
"auto-reply",
|
|
7
7
|
"autoreply",
|
|
@@ -26,13 +26,14 @@ function parseAutoReplyCommand(text) {
|
|
|
26
26
|
return { type: "invalid" };
|
|
27
27
|
}
|
|
28
28
|
function formatAutoReplyStatus(config) {
|
|
29
|
-
const
|
|
30
|
-
if (config.rules.length
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const lines = [`Auto-reply is ${config.enabled ? "enabled" : "disabled"} for this channel.`];
|
|
30
|
+
if (config.rules.length > 0) {
|
|
31
|
+
lines.push(`Current rules: ${config.rules.join("; ")}`);
|
|
32
|
+
}
|
|
33
|
+
return formatCommandSummary("Auto Reply", lines);
|
|
33
34
|
}
|
|
34
35
|
function formatAutoReplyUsage() {
|
|
35
|
-
return "
|
|
36
|
+
return formatCommandSummary("Auto Reply", ["Usage: `/pi-auto-reply on|off|status`"]);
|
|
36
37
|
}
|
|
37
38
|
function applyAction(current, action) {
|
|
38
39
|
switch (action.type) {
|
|
@@ -51,7 +52,7 @@ export class AutoReplyCommandHandler {
|
|
|
51
52
|
if (!action)
|
|
52
53
|
return false;
|
|
53
54
|
if (context.privateConversation) {
|
|
54
|
-
await replyDiagnosticWithContext(context.responseCtx, "
|
|
55
|
+
await replyDiagnosticWithContext(context.responseCtx, formatCommandSummary("Auto Reply", ["只能在 group/channel 裡設定。"]), { style: "muted" });
|
|
55
56
|
return true;
|
|
56
57
|
}
|
|
57
58
|
if (action.type === "invalid") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-reply.js","sourceRoot":"","sources":["../../src/commands/auto-reply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAEL,+BAA+B,EAC/B,+BAA+B,GAChC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"auto-reply.js","sourceRoot":"","sources":["../../src/commands/auto-reply.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"path\";\nimport {\n type AutoReplyConfig,\n loadConversationAutoReplyConfig,\n saveConversationAutoReplyConfig,\n} from \"../config.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\ntype AutoReplyAction = { type: \"status\" } | { type: \"on\" } | { type: \"off\" } | { type: \"invalid\" };\n\nconst AUTO_REPLY_COMMANDS = [\n \"auto-reply\",\n \"autoreply\",\n \"/auto-reply\",\n \"/autoreply\",\n \"/pi-auto-reply\",\n \"/pi-autoreply\",\n] as const;\n\nfunction parseAutoReplyCommand(text: string): AutoReplyAction | null {\n const matched = matchCommand(text, AUTO_REPLY_COMMANDS, { stripMention: true });\n if (!matched) return null;\n if (matched.args.length === 0) return { type: \"status\" };\n\n const lower = matched.args.join(\" \").toLowerCase();\n if (lower === \"status\") return { type: \"status\" };\n if (lower === \"on\" || lower === \"enable\" || lower === \"enabled\") return { type: \"on\" };\n if (lower === \"off\" || lower === \"disable\" || lower === \"disabled\") return { type: \"off\" };\n\n return { type: \"invalid\" };\n}\n\nfunction formatAutoReplyStatus(config: AutoReplyConfig): string {\n const lines = [`Auto-reply is ${config.enabled ? \"enabled\" : \"disabled\"} for this channel.`];\n if (config.rules.length > 0) {\n lines.push(`Current rules: ${config.rules.join(\"; \")}`);\n }\n return formatCommandSummary(\"Auto Reply\", lines);\n}\n\nfunction formatAutoReplyUsage(): string {\n return formatCommandSummary(\"Auto Reply\", [\"Usage: `/pi-auto-reply on|off|status`\"]);\n}\n\nfunction applyAction(current: AutoReplyConfig, action: AutoReplyAction): AutoReplyConfig {\n switch (action.type) {\n case \"status\":\n case \"invalid\":\n return current;\n case \"on\":\n return { ...current, enabled: true };\n case \"off\":\n return { ...current, enabled: false };\n }\n}\n\nexport class AutoReplyCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const action = parseAutoReplyCommand(context.commandText);\n if (!action) return false;\n\n if (context.privateConversation) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatCommandSummary(\"Auto Reply\", [\"只能在 group/channel 裡設定。\"]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (action.type === \"invalid\") {\n await replyDiagnosticWithContext(context.responseCtx, formatAutoReplyUsage(), {\n style: \"muted\",\n });\n return true;\n }\n\n const conversationDir = join(context.services.workingDir, context.conversationId);\n const current = loadConversationAutoReplyConfig(conversationDir);\n const next = applyAction(current, action);\n if (action.type === \"on\" || (action.type === \"off\" && current.enabled)) {\n saveConversationAutoReplyConfig(conversationDir, next);\n }\n\n const status = formatAutoReplyStatus(next);\n const text =\n action.type === \"on\"\n ? `${status}\\nEdit rules at: \\`${join(conversationDir, \"auto-reply\")}\\``\n : status;\n await replyDiagnosticWithContext(context.responseCtx, text, {\n style: \"muted\",\n });\n return true;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElF,wBAAgB,sBAAsB,IAAI,cAAc,EAAE,CAUzD","sourcesContent":["import { AdminCommandHandler } from \"./admin.js\";\nimport { AutoReplyCommandHandler } from \"./auto-reply.js\";\nimport { LoginCommandHandler } from \"./login.js\";\nimport { ModelCommandHandler } from \"./model.js\";\nimport { NewCommandHandler } from \"./new.js\";\nimport { SandboxCommandHandler } from \"./sandbox.js\";\nimport { SessionViewCommandHandler } from \"./session-view.js\";\nimport type { CommandHandler } from \"./types.js\";\n\nexport { dispatchCommand } from \"./registry.js\";\nexport type { CommandContext, CommandHandler, CommandServices } from \"./types.js\";\n\nexport function defaultCommandHandlers(): CommandHandler[] {\n return [\n new AdminCommandHandler(),\n new LoginCommandHandler(),\n new SessionViewCommandHandler(),\n new AutoReplyCommandHandler(),\n new ModelCommandHandler(),\n new SandboxCommandHandler(),\n new NewCommandHandler(),\n ];\n}\n"]}
|
package/dist/commands/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AdminCommandHandler } from "./admin.js";
|
|
1
2
|
import { AutoReplyCommandHandler } from "./auto-reply.js";
|
|
2
3
|
import { LoginCommandHandler } from "./login.js";
|
|
3
4
|
import { ModelCommandHandler } from "./model.js";
|
|
@@ -7,6 +8,7 @@ import { SessionViewCommandHandler } from "./session-view.js";
|
|
|
7
8
|
export { dispatchCommand } from "./registry.js";
|
|
8
9
|
export function defaultCommandHandlers() {
|
|
9
10
|
return [
|
|
11
|
+
new AdminCommandHandler(),
|
|
10
12
|
new LoginCommandHandler(),
|
|
11
13
|
new SessionViewCommandHandler(),
|
|
12
14
|
new AutoReplyCommandHandler(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,IAAI,mBAAmB,EAAE;QACzB,IAAI,yBAAyB,EAAE;QAC/B,IAAI,uBAAuB,EAAE;QAC7B,IAAI,mBAAmB,EAAE;QACzB,IAAI,qBAAqB,EAAE;QAC3B,IAAI,iBAAiB,EAAE;KACxB,CAAC;AACJ,CAAC","sourcesContent":["import { AutoReplyCommandHandler } from \"./auto-reply.js\";\nimport { LoginCommandHandler } from \"./login.js\";\nimport { ModelCommandHandler } from \"./model.js\";\nimport { NewCommandHandler } from \"./new.js\";\nimport { SandboxCommandHandler } from \"./sandbox.js\";\nimport { SessionViewCommandHandler } from \"./session-view.js\";\nimport type { CommandHandler } from \"./types.js\";\n\nexport { dispatchCommand } from \"./registry.js\";\nexport type { CommandContext, CommandHandler, CommandServices } from \"./types.js\";\n\nexport function defaultCommandHandlers(): CommandHandler[] {\n return [\n new LoginCommandHandler(),\n new SessionViewCommandHandler(),\n new AutoReplyCommandHandler(),\n new ModelCommandHandler(),\n new SandboxCommandHandler(),\n new NewCommandHandler(),\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,IAAI,mBAAmB,EAAE;QACzB,IAAI,mBAAmB,EAAE;QACzB,IAAI,yBAAyB,EAAE;QAC/B,IAAI,uBAAuB,EAAE;QAC7B,IAAI,mBAAmB,EAAE;QACzB,IAAI,qBAAqB,EAAE;QAC3B,IAAI,iBAAiB,EAAE;KACxB,CAAC;AACJ,CAAC","sourcesContent":["import { AdminCommandHandler } from \"./admin.js\";\nimport { AutoReplyCommandHandler } from \"./auto-reply.js\";\nimport { LoginCommandHandler } from \"./login.js\";\nimport { ModelCommandHandler } from \"./model.js\";\nimport { NewCommandHandler } from \"./new.js\";\nimport { SandboxCommandHandler } from \"./sandbox.js\";\nimport { SessionViewCommandHandler } from \"./session-view.js\";\nimport type { CommandHandler } from \"./types.js\";\n\nexport { dispatchCommand } from \"./registry.js\";\nexport type { CommandContext, CommandHandler, CommandServices } from \"./types.js\";\n\nexport function defaultCommandHandlers(): CommandHandler[] {\n return [\n new AdminCommandHandler(),\n new LoginCommandHandler(),\n new SessionViewCommandHandler(),\n new AutoReplyCommandHandler(),\n new ModelCommandHandler(),\n new SandboxCommandHandler(),\n new NewCommandHandler(),\n ];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAsCjE,qBAAa,mBAAoB,YAAW,cAAc;IAClD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAoGzD;CACF","sourcesContent":["import * as log from \"../log.js\";\nimport { parseLoginCommand } from \"../login/index.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { sharedVaultKey } from \"../vault.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\nfunction ensureLoginVault(context: CommandContext): string {\n const { services, platformUserId, conversationId, vaultConversationId } = context;\n return resolveActorVaultKey(\n services.sandbox,\n platformUserId,\n vaultConversationId ?? conversationId,\n );\n}\n\nasync function replyVault(context: CommandContext, lines: string[]): Promise<void> {\n await replyDiagnosticWithContext(context.responseCtx, formatCommandSummary(\"Vault\", lines), {\n style: \"muted\",\n });\n}\n\nasync function refreshCopiedVaultRuntime(\n context: CommandContext,\n vaultId: string,\n): Promise<string | undefined> {\n if (context.services.sandbox.type !== \"image\") return undefined;\n\n const targetConversationId = context.vaultConversationId ?? context.conversationId;\n const cleared = context.services.runtime?.refreshConversationEnvironment(targetConversationId);\n if (cleared === false) {\n return \"A session is currently running, so the sandbox was not restarted. The copied credentials will be applied after the run finishes and the sandbox is recreated.\";\n }\n\n if (!context.services.provisioner) {\n return \"The cached session was refreshed. The sandbox will pick up copied credentials on the next provision.\";\n }\n\n await context.services.provisioner.remove(vaultId);\n return \"The sandbox container was removed and will be recreated with the copied env and file mounts on the next message.\";\n}\n\nexport class LoginCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseLoginCommand(context.commandText);\n if (!parsed) return false;\n\n if (!context.privateConversation) {\n await replyVault(context, [\n \"為了保護你的憑證,`/login` 只能在與機器人的私訊中使用。\",\n \"請先私訊機器人,再重新執行 `/login`。\",\n ]);\n return true;\n }\n\n if (parsed.action === \"shared_list\") {\n const profiles = context.services.vaultManager.listSharedVaults();\n await replyVault(\n context,\n profiles.length > 0\n ? [\"Shared login profiles:\", ...profiles.map((name) => `- ${name}`)]\n : [\"No shared login profiles found.\"],\n );\n return true;\n }\n\n if (parsed.action === \"shared_delete\") {\n try {\n const deleted = context.services.vaultManager.deleteSharedVault(parsed.name);\n await replyVault(context, [\n deleted\n ? `Deleted shared login profile \\`${parsed.name}\\`.`\n : `Shared login profile \\`${parsed.name}\\` does not exist.`,\n ]);\n } catch (error) {\n await replyVault(context, [error instanceof Error ? error.message : String(error)]);\n }\n return true;\n }\n\n if (parsed.action === \"copy_shared\") {\n try {\n const vaultId = ensureLoginVault(context);\n const result = context.services.vaultManager.copySharedVaultTo(parsed.name, vaultId);\n const refreshNote = await refreshCopiedVaultRuntime(context, vaultId);\n await replyVault(context, [\n `Copied shared login profile \\`${parsed.name}\\` into this conversation.`,\n \"Shared values overwrite matching conversation values; conversation-only values are kept.\",\n `Copied: ${result.envKeysCopied} env key(s), ${result.filesCopied} file(s).`,\n ...(refreshNote ? [refreshNote] : []),\n ]);\n } catch (error) {\n await replyVault(context, [error instanceof Error ? error.message : String(error)]);\n }\n return true;\n }\n\n if (!context.services.portalBaseUrl) {\n await replyVault(context, [\n \"Login is not configured.\",\n \"Set `MIKAN_LINK_URL` or `MIKAN_LINK_PORT` on the server.\",\n ]);\n return true;\n }\n\n const isSharedSetup = parsed.action === \"shared_create\" || parsed.action === \"shared_update\";\n let vaultId: string;\n try {\n vaultId = isSharedSetup ? (sharedVaultKey(parsed.name) ?? \"\") : ensureLoginVault(context);\n if (!vaultId) {\n throw new Error(\n isSharedSetup ? `Invalid shared login profile name: ${parsed.name}` : \"Invalid vault id\",\n );\n }\n } catch (error) {\n log.logWarning(\n `[${context.conversationId}] Failed to prepare login vault for ${context.platform}/${context.platformUserId}`,\n error instanceof Error ? error.message : String(error),\n );\n await replyVault(context, [\n \"Login setup failed on the server.\",\n \"請稍後重試,或聯絡管理員檢查 vault 儲存權限。\",\n ]);\n return true;\n }\n\n const token = context.services.linkTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n vaultId,\n \"\",\n );\n const vaultLabel = isSharedSetup\n ? `shared login profile (${parsed.name})`\n : context.services.sandbox.type === \"container\"\n ? `container vault (${vaultId})`\n : \"your vault\";\n await replyVault(context, [\n `${context.services.portalBaseUrl}/link?token=${token.token}`,\n `Target: ${vaultLabel} · Expires: 15 minutes`,\n ]);\n return true;\n }\n}\n"]}
|
package/dist/commands/login.js
CHANGED
|
@@ -2,11 +2,16 @@ import * as log from "../log.js";
|
|
|
2
2
|
import { parseLoginCommand } from "../login/index.js";
|
|
3
3
|
import { resolveActorVaultKey } from "../vault-routing.js";
|
|
4
4
|
import { sharedVaultKey } from "../vault.js";
|
|
5
|
-
import {
|
|
5
|
+
import { formatCommandSummary, replyDiagnosticWithContext } from "./utils.js";
|
|
6
6
|
function ensureLoginVault(context) {
|
|
7
7
|
const { services, platformUserId, conversationId, vaultConversationId } = context;
|
|
8
8
|
return resolveActorVaultKey(services.sandbox, platformUserId, vaultConversationId ?? conversationId);
|
|
9
9
|
}
|
|
10
|
+
async function replyVault(context, lines) {
|
|
11
|
+
await replyDiagnosticWithContext(context.responseCtx, formatCommandSummary("Vault", lines), {
|
|
12
|
+
style: "muted",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
10
15
|
async function refreshCopiedVaultRuntime(context, vaultId) {
|
|
11
16
|
if (context.services.sandbox.type !== "image")
|
|
12
17
|
return undefined;
|
|
@@ -27,25 +32,30 @@ export class LoginCommandHandler {
|
|
|
27
32
|
if (!parsed)
|
|
28
33
|
return false;
|
|
29
34
|
if (!context.privateConversation) {
|
|
30
|
-
await
|
|
35
|
+
await replyVault(context, [
|
|
36
|
+
"為了保護你的憑證,`/login` 只能在與機器人的私訊中使用。",
|
|
37
|
+
"請先私訊機器人,再重新執行 `/login`。",
|
|
38
|
+
]);
|
|
31
39
|
return true;
|
|
32
40
|
}
|
|
33
41
|
if (parsed.action === "shared_list") {
|
|
34
42
|
const profiles = context.services.vaultManager.listSharedVaults();
|
|
35
|
-
await
|
|
36
|
-
?
|
|
37
|
-
: "No shared login profiles found.");
|
|
43
|
+
await replyVault(context, profiles.length > 0
|
|
44
|
+
? ["Shared login profiles:", ...profiles.map((name) => `- ${name}`)]
|
|
45
|
+
: ["No shared login profiles found."]);
|
|
38
46
|
return true;
|
|
39
47
|
}
|
|
40
48
|
if (parsed.action === "shared_delete") {
|
|
41
49
|
try {
|
|
42
50
|
const deleted = context.services.vaultManager.deleteSharedVault(parsed.name);
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
await replyVault(context, [
|
|
52
|
+
deleted
|
|
53
|
+
? `Deleted shared login profile \`${parsed.name}\`.`
|
|
54
|
+
: `Shared login profile \`${parsed.name}\` does not exist.`,
|
|
55
|
+
]);
|
|
46
56
|
}
|
|
47
57
|
catch (error) {
|
|
48
|
-
await
|
|
58
|
+
await replyVault(context, [error instanceof Error ? error.message : String(error)]);
|
|
49
59
|
}
|
|
50
60
|
return true;
|
|
51
61
|
}
|
|
@@ -54,15 +64,23 @@ export class LoginCommandHandler {
|
|
|
54
64
|
const vaultId = ensureLoginVault(context);
|
|
55
65
|
const result = context.services.vaultManager.copySharedVaultTo(parsed.name, vaultId);
|
|
56
66
|
const refreshNote = await refreshCopiedVaultRuntime(context, vaultId);
|
|
57
|
-
await
|
|
67
|
+
await replyVault(context, [
|
|
68
|
+
`Copied shared login profile \`${parsed.name}\` into this conversation.`,
|
|
69
|
+
"Shared values overwrite matching conversation values; conversation-only values are kept.",
|
|
70
|
+
`Copied: ${result.envKeysCopied} env key(s), ${result.filesCopied} file(s).`,
|
|
71
|
+
...(refreshNote ? [refreshNote] : []),
|
|
72
|
+
]);
|
|
58
73
|
}
|
|
59
74
|
catch (error) {
|
|
60
|
-
await
|
|
75
|
+
await replyVault(context, [error instanceof Error ? error.message : String(error)]);
|
|
61
76
|
}
|
|
62
77
|
return true;
|
|
63
78
|
}
|
|
64
79
|
if (!context.services.portalBaseUrl) {
|
|
65
|
-
await
|
|
80
|
+
await replyVault(context, [
|
|
81
|
+
"Login is not configured.",
|
|
82
|
+
"Set `MIKAN_LINK_URL` or `MIKAN_LINK_PORT` on the server.",
|
|
83
|
+
]);
|
|
66
84
|
return true;
|
|
67
85
|
}
|
|
68
86
|
const isSharedSetup = parsed.action === "shared_create" || parsed.action === "shared_update";
|
|
@@ -75,7 +93,10 @@ export class LoginCommandHandler {
|
|
|
75
93
|
}
|
|
76
94
|
catch (error) {
|
|
77
95
|
log.logWarning(`[${context.conversationId}] Failed to prepare login vault for ${context.platform}/${context.platformUserId}`, error instanceof Error ? error.message : String(error));
|
|
78
|
-
await
|
|
96
|
+
await replyVault(context, [
|
|
97
|
+
"Login setup failed on the server.",
|
|
98
|
+
"請稍後重試,或聯絡管理員檢查 vault 儲存權限。",
|
|
99
|
+
]);
|
|
79
100
|
return true;
|
|
80
101
|
}
|
|
81
102
|
const token = context.services.linkTokenStore.create(context.platform, context.platformUserId, context.conversationId, vaultId, "");
|
|
@@ -84,7 +105,10 @@ export class LoginCommandHandler {
|
|
|
84
105
|
: context.services.sandbox.type === "container"
|
|
85
106
|
? `container vault (${vaultId})`
|
|
86
107
|
: "your vault";
|
|
87
|
-
await
|
|
108
|
+
await replyVault(context, [
|
|
109
|
+
`${context.services.portalBaseUrl}/link?token=${token.token}`,
|
|
110
|
+
`Target: ${vaultLabel} · Expires: 15 minutes`,
|
|
111
|
+
]);
|
|
88
112
|
return true;
|
|
89
113
|
}
|
|
90
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import * as log from \"../log.js\";\nimport { parseLoginCommand } from \"../login/index.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { sharedVaultKey } from \"../vault.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyWithContext } from \"./utils.js\";\n\nfunction ensureLoginVault(context: CommandContext): string {\n const { services, platformUserId, conversationId, vaultConversationId } = context;\n return resolveActorVaultKey(\n services.sandbox,\n platformUserId,\n vaultConversationId ?? conversationId,\n );\n}\n\nasync function refreshCopiedVaultRuntime(\n context: CommandContext,\n vaultId: string,\n): Promise<string | undefined> {\n if (context.services.sandbox.type !== \"image\") return undefined;\n\n const targetConversationId = context.vaultConversationId ?? context.conversationId;\n const cleared = context.services.runtime?.refreshConversationEnvironment(targetConversationId);\n if (cleared === false) {\n return \"A session is currently running, so the sandbox was not restarted. The copied credentials will be applied after the run finishes and the sandbox is recreated.\";\n }\n\n if (!context.services.provisioner) {\n return \"The cached session was refreshed. The sandbox will pick up copied credentials on the next provision.\";\n }\n\n await context.services.provisioner.remove(vaultId);\n return \"The sandbox container was removed and will be recreated with the copied env and file mounts on the next message.\";\n}\n\nexport class LoginCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseLoginCommand(context.commandText);\n if (!parsed) return false;\n\n if (!context.privateConversation) {\n await replyWithContext(\n context.responseCtx,\n \"為了保護你的憑證,`/login` 只能在與機器人的私訊中使用。請先私訊機器人,再重新執行 `/login`。\",\n );\n return true;\n }\n\n if (parsed.action === \"shared_list\") {\n const profiles = context.services.vaultManager.listSharedVaults();\n await replyWithContext(\n context.responseCtx,\n profiles.length > 0\n ? `Shared login profiles:\\n${profiles.map((name) => `- ${name}`).join(\"\\n\")}`\n : \"No shared login profiles found.\",\n );\n return true;\n }\n\n if (parsed.action === \"shared_delete\") {\n try {\n const deleted = context.services.vaultManager.deleteSharedVault(parsed.name);\n await replyWithContext(\n context.responseCtx,\n deleted\n ? `Deleted shared login profile \\`${parsed.name}\\`.`\n : `Shared login profile \\`${parsed.name}\\` does not exist.`,\n );\n } catch (error) {\n await replyWithContext(\n context.responseCtx,\n error instanceof Error ? error.message : String(error),\n );\n }\n return true;\n }\n\n if (parsed.action === \"copy_shared\") {\n try {\n const vaultId = ensureLoginVault(context);\n const result = context.services.vaultManager.copySharedVaultTo(parsed.name, vaultId);\n const refreshNote = await refreshCopiedVaultRuntime(context, vaultId);\n await replyWithContext(\n context.responseCtx,\n `Copied shared login profile \\`${parsed.name}\\` into this conversation. Shared values overwrite matching conversation values; conversation-only values are kept. (${result.envKeysCopied} env key(s), ${result.filesCopied} file(s))${refreshNote ? ` ${refreshNote}` : \"\"}`,\n );\n } catch (error) {\n await replyWithContext(\n context.responseCtx,\n error instanceof Error ? error.message : String(error),\n );\n }\n return true;\n }\n\n if (!context.services.portalBaseUrl) {\n await replyWithContext(\n context.responseCtx,\n \"Login is not configured. Set `MIKAN_LINK_URL` or `MIKAN_LINK_PORT` on the server.\",\n );\n return true;\n }\n\n const isSharedSetup = parsed.action === \"shared_create\" || parsed.action === \"shared_update\";\n let vaultId: string;\n try {\n vaultId = isSharedSetup ? (sharedVaultKey(parsed.name) ?? \"\") : ensureLoginVault(context);\n if (!vaultId) {\n throw new Error(\n isSharedSetup ? `Invalid shared login profile name: ${parsed.name}` : \"Invalid vault id\",\n );\n }\n } catch (error) {\n log.logWarning(\n `[${context.conversationId}] Failed to prepare login vault for ${context.platform}/${context.platformUserId}`,\n error instanceof Error ? error.message : String(error),\n );\n await replyWithContext(\n context.responseCtx,\n \"Login setup failed on the server. 請稍後重試,或聯絡管理員檢查 vault 儲存權限。\",\n );\n return true;\n }\n\n const token = context.services.linkTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n vaultId,\n \"\",\n );\n const vaultLabel = isSharedSetup\n ? `shared login profile (${parsed.name})`\n : context.services.sandbox.type === \"container\"\n ? `container vault (${vaultId})`\n : \"your vault\";\n await replyWithContext(\n context.responseCtx,\n `Open this link to store credentials in ${vaultLabel} (expires in 15 minutes):\\n${context.services.portalBaseUrl}/link?token=${token.token}`,\n );\n return true;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import * as log from \"../log.js\";\nimport { parseLoginCommand } from \"../login/index.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { sharedVaultKey } from \"../vault.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\nfunction ensureLoginVault(context: CommandContext): string {\n const { services, platformUserId, conversationId, vaultConversationId } = context;\n return resolveActorVaultKey(\n services.sandbox,\n platformUserId,\n vaultConversationId ?? conversationId,\n );\n}\n\nasync function replyVault(context: CommandContext, lines: string[]): Promise<void> {\n await replyDiagnosticWithContext(context.responseCtx, formatCommandSummary(\"Vault\", lines), {\n style: \"muted\",\n });\n}\n\nasync function refreshCopiedVaultRuntime(\n context: CommandContext,\n vaultId: string,\n): Promise<string | undefined> {\n if (context.services.sandbox.type !== \"image\") return undefined;\n\n const targetConversationId = context.vaultConversationId ?? context.conversationId;\n const cleared = context.services.runtime?.refreshConversationEnvironment(targetConversationId);\n if (cleared === false) {\n return \"A session is currently running, so the sandbox was not restarted. The copied credentials will be applied after the run finishes and the sandbox is recreated.\";\n }\n\n if (!context.services.provisioner) {\n return \"The cached session was refreshed. The sandbox will pick up copied credentials on the next provision.\";\n }\n\n await context.services.provisioner.remove(vaultId);\n return \"The sandbox container was removed and will be recreated with the copied env and file mounts on the next message.\";\n}\n\nexport class LoginCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseLoginCommand(context.commandText);\n if (!parsed) return false;\n\n if (!context.privateConversation) {\n await replyVault(context, [\n \"為了保護你的憑證,`/login` 只能在與機器人的私訊中使用。\",\n \"請先私訊機器人,再重新執行 `/login`。\",\n ]);\n return true;\n }\n\n if (parsed.action === \"shared_list\") {\n const profiles = context.services.vaultManager.listSharedVaults();\n await replyVault(\n context,\n profiles.length > 0\n ? [\"Shared login profiles:\", ...profiles.map((name) => `- ${name}`)]\n : [\"No shared login profiles found.\"],\n );\n return true;\n }\n\n if (parsed.action === \"shared_delete\") {\n try {\n const deleted = context.services.vaultManager.deleteSharedVault(parsed.name);\n await replyVault(context, [\n deleted\n ? `Deleted shared login profile \\`${parsed.name}\\`.`\n : `Shared login profile \\`${parsed.name}\\` does not exist.`,\n ]);\n } catch (error) {\n await replyVault(context, [error instanceof Error ? error.message : String(error)]);\n }\n return true;\n }\n\n if (parsed.action === \"copy_shared\") {\n try {\n const vaultId = ensureLoginVault(context);\n const result = context.services.vaultManager.copySharedVaultTo(parsed.name, vaultId);\n const refreshNote = await refreshCopiedVaultRuntime(context, vaultId);\n await replyVault(context, [\n `Copied shared login profile \\`${parsed.name}\\` into this conversation.`,\n \"Shared values overwrite matching conversation values; conversation-only values are kept.\",\n `Copied: ${result.envKeysCopied} env key(s), ${result.filesCopied} file(s).`,\n ...(refreshNote ? [refreshNote] : []),\n ]);\n } catch (error) {\n await replyVault(context, [error instanceof Error ? error.message : String(error)]);\n }\n return true;\n }\n\n if (!context.services.portalBaseUrl) {\n await replyVault(context, [\n \"Login is not configured.\",\n \"Set `MIKAN_LINK_URL` or `MIKAN_LINK_PORT` on the server.\",\n ]);\n return true;\n }\n\n const isSharedSetup = parsed.action === \"shared_create\" || parsed.action === \"shared_update\";\n let vaultId: string;\n try {\n vaultId = isSharedSetup ? (sharedVaultKey(parsed.name) ?? \"\") : ensureLoginVault(context);\n if (!vaultId) {\n throw new Error(\n isSharedSetup ? `Invalid shared login profile name: ${parsed.name}` : \"Invalid vault id\",\n );\n }\n } catch (error) {\n log.logWarning(\n `[${context.conversationId}] Failed to prepare login vault for ${context.platform}/${context.platformUserId}`,\n error instanceof Error ? error.message : String(error),\n );\n await replyVault(context, [\n \"Login setup failed on the server.\",\n \"請稍後重試,或聯絡管理員檢查 vault 儲存權限。\",\n ]);\n return true;\n }\n\n const token = context.services.linkTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n vaultId,\n \"\",\n );\n const vaultLabel = isSharedSetup\n ? `shared login profile (${parsed.name})`\n : context.services.sandbox.type === \"container\"\n ? `container vault (${vaultId})`\n : \"your vault\";\n await replyVault(context, [\n `${context.services.portalBaseUrl}/link?token=${token.token}`,\n `Target: ${vaultLabel} · Expires: 15 minutes`,\n ]);\n return true;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAOnE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAuBzE;AA8BD,qBAAa,mBAAoB,YAAW,cAAc;IAClD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA0EzD;IAED,OAAO,CAAC,YAAY;CAKrB","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ThinkingLevel as PiAiThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { AuthStorage, ModelRegistry } from \"@earendil-works/pi-coding-agent\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { loadAgentConfigForConversation, saveConversationModelConfig } from \"../config.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyDiagnosticWithContext } 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\nconst MODEL_COMMANDS = [\"model\", \"/model\", \"/pi-model\"] as const;\n\nexport function parseModelCommand(text: string): ParsedModelCommand | null {\n const matched = matchCommand(text, MODEL_COMMANDS);\n if (!matched) return null;\n\n if (matched.args.length === 0) {\n return { command: matched.command };\n }\n\n const spec = matched.args[0];\n const slash = spec.indexOf(\"/\");\n if (slash <= 0 || slash === spec.length - 1) {\n return { command: matched.command };\n }\n\n const modelSpec = spec.slice(slash + 1);\n const parsedModel = parseModelThinkingLevel(modelSpec);\n\n return {\n command: matched.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\nfunction formatModelCommandSummary(lines: string[]): string {\n return
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAOnE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAuBzE;AA8BD,qBAAa,mBAAoB,YAAW,cAAc;IAClD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA0EzD;IAED,OAAO,CAAC,YAAY;CAKrB","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ThinkingLevel as PiAiThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { AuthStorage, ModelRegistry } from \"@earendil-works/pi-coding-agent\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { loadAgentConfigForConversation, saveConversationModelConfig } from \"../config.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } 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\nconst MODEL_COMMANDS = [\"model\", \"/model\", \"/pi-model\"] as const;\n\nexport function parseModelCommand(text: string): ParsedModelCommand | null {\n const matched = matchCommand(text, MODEL_COMMANDS);\n if (!matched) return null;\n\n if (matched.args.length === 0) {\n return { command: matched.command };\n }\n\n const spec = matched.args[0];\n const slash = spec.indexOf(\"/\");\n if (slash <= 0 || slash === spec.length - 1) {\n return { command: matched.command };\n }\n\n const modelSpec = spec.slice(slash + 1);\n const parsedModel = parseModelThinkingLevel(modelSpec);\n\n return {\n command: matched.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\nfunction formatModelCommandSummary(lines: string[]): string {\n return formatCommandSummary(\"Model\", lines);\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `Current: \\`${formatModelSpec(current.provider, current.model, current.thinkingLevel)}\\``,\n \"\",\n \"Usage: `/pi-model provider/model[:thinking]`\",\n \"Example: `/pi-model anthropic/claude-sonnet-4-6:off`\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (!this.isKnownModel(parsed.provider, parsed.model)) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `找不到模型:\\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\``,\n \"請確認 provider/model 名稱,或先在 pi models.json 註冊自訂模型。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (!context.services.runtime) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n \"Model command is not configured correctly on the server. Please try again later.\",\n ]),\n { style: \"muted\" },\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n \"目前這個 conversation 有執行中的工作,請等它完成或先 `/stop` 後再切換模型。\",\n ]),\n { style: \"muted\" },\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `Switched: \\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\``,\n \"下一則訊息會使用新模型。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n private isKnownModel(provider: string, model: string): boolean {\n const authStorage = AuthStorage.create(join(homedir(), \".pi\", \"mikan\", \"auth.json\"));\n const registry = ModelRegistry.create(authStorage);\n return registry.find(provider, model) !== undefined;\n }\n}\n"]}
|
package/dist/commands/model.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from "os";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { loadAgentConfigForConversation, saveConversationModelConfig } from "../config.js";
|
|
5
5
|
import { matchCommand } from "./parse.js";
|
|
6
|
-
import { replyDiagnosticWithContext } from "./utils.js";
|
|
6
|
+
import { formatCommandSummary, replyDiagnosticWithContext } from "./utils.js";
|
|
7
7
|
const PI_AI_THINKING_LEVELS = [
|
|
8
8
|
"minimal",
|
|
9
9
|
"low",
|
|
@@ -52,7 +52,7 @@ function formatModelSpec(provider, model, thinkingLevel) {
|
|
|
52
52
|
return `${provider}/${model}${thinkingLevel ? `:${thinkingLevel}` : ""}`;
|
|
53
53
|
}
|
|
54
54
|
function formatModelCommandSummary(lines) {
|
|
55
|
-
return
|
|
55
|
+
return formatCommandSummary("Model", lines);
|
|
56
56
|
}
|
|
57
57
|
export class ModelCommandHandler {
|
|
58
58
|
async tryHandle(context) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ThinkingLevel as PiAiThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { AuthStorage, ModelRegistry } from \"@earendil-works/pi-coding-agent\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { loadAgentConfigForConversation, saveConversationModelConfig } from \"../config.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } 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\nconst MODEL_COMMANDS = [\"model\", \"/model\", \"/pi-model\"] as const;\n\nexport function parseModelCommand(text: string): ParsedModelCommand | null {\n const matched = matchCommand(text, MODEL_COMMANDS);\n if (!matched) return null;\n\n if (matched.args.length === 0) {\n return { command: matched.command };\n }\n\n const spec = matched.args[0];\n const slash = spec.indexOf(\"/\");\n if (slash <= 0 || slash === spec.length - 1) {\n return { command: matched.command };\n }\n\n const modelSpec = spec.slice(slash + 1);\n const parsedModel = parseModelThinkingLevel(modelSpec);\n\n return {\n command: matched.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\nfunction formatModelCommandSummary(lines: string[]): string {\n return formatCommandSummary(\"Model\", lines);\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `Current: \\`${formatModelSpec(current.provider, current.model, current.thinkingLevel)}\\``,\n \"\",\n \"Usage: `/pi-model provider/model[:thinking]`\",\n \"Example: `/pi-model anthropic/claude-sonnet-4-6:off`\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (!this.isKnownModel(parsed.provider, parsed.model)) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `找不到模型:\\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\``,\n \"請確認 provider/model 名稱,或先在 pi models.json 註冊自訂模型。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (!context.services.runtime) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n \"Model command is not configured correctly on the server. Please try again later.\",\n ]),\n { style: \"muted\" },\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n \"目前這個 conversation 有執行中的工作,請等它完成或先 `/stop` 後再切換模型。\",\n ]),\n { style: \"muted\" },\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 replyDiagnosticWithContext(\n context.responseCtx,\n formatModelCommandSummary([\n `Switched: \\`${formatModelSpec(parsed.provider, parsed.model, parsed.thinkingLevel)}\\``,\n \"下一則訊息會使用新模型。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n private isKnownModel(provider: string, model: string): boolean {\n const authStorage = AuthStorage.create(join(homedir(), \".pi\", \"mikan\", \"auth.json\"));\n const registry = ModelRegistry.create(authStorage);\n return registry.find(provider, model) !== undefined;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwBjE,qBAAa,iBAAkB,YAAW,cAAc;IAChD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBzD;CACF","sourcesContent":["import { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\ntype ParsedNewCommand = {\n command: \"new\" | \"/new\" | \"/pi-new\";\n};\n\nconst NEW_COMMANDS = [\"new\", \"/new\", \"/pi-new\"] as const;\n\nfunction parseNewCommand(text: string): ParsedNewCommand | null {\n const matched = matchCommand(text, NEW_COMMANDS);\n return matched ? { command: matched.command } : null;\n}\n\nasync function replyNewSession(context: CommandContext, lines: string[]): Promise<void> {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatCommandSummary(\"New Session\", lines),\n {\n style: \"muted\",\n },\n );\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 replyNewSession(context, [\n \"為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。\",\n ]);\n return true;\n }\n\n if (!context.services.runtime) {\n await replyNewSession(context, [\n \"New command is not configured correctly on the server.\",\n \"Please try again later.\",\n ]);\n return true;\n }\n\n await context.services.runtime.handleNewCommand(\n context.sessionKey,\n context.conversationId,\n context.bot,\n );\n return true;\n }\n}\n"]}
|
package/dist/commands/new.js
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { matchCommand } from "./parse.js";
|
|
2
|
-
import {
|
|
2
|
+
import { formatCommandSummary, replyDiagnosticWithContext } from "./utils.js";
|
|
3
3
|
const NEW_COMMANDS = ["new", "/new", "/pi-new"];
|
|
4
4
|
function parseNewCommand(text) {
|
|
5
5
|
const matched = matchCommand(text, NEW_COMMANDS);
|
|
6
6
|
return matched ? { command: matched.command } : null;
|
|
7
7
|
}
|
|
8
|
+
async function replyNewSession(context, lines) {
|
|
9
|
+
await replyDiagnosticWithContext(context.responseCtx, formatCommandSummary("New Session", lines), {
|
|
10
|
+
style: "muted",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
8
13
|
export class NewCommandHandler {
|
|
9
14
|
async tryHandle(context) {
|
|
10
15
|
if (!parseNewCommand(context.commandText))
|
|
11
16
|
return false;
|
|
12
17
|
if (!context.privateConversation) {
|
|
13
|
-
await
|
|
18
|
+
await replyNewSession(context, [
|
|
19
|
+
"為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。",
|
|
20
|
+
]);
|
|
14
21
|
return true;
|
|
15
22
|
}
|
|
16
23
|
if (!context.services.runtime) {
|
|
17
|
-
await
|
|
24
|
+
await replyNewSession(context, [
|
|
25
|
+
"New command is not configured correctly on the server.",
|
|
26
|
+
"Please try again later.",
|
|
27
|
+
]);
|
|
18
28
|
return true;
|
|
19
29
|
}
|
|
20
30
|
await context.services.runtime.handleNewCommand(context.sessionKey, context.conversationId, context.bot);
|
package/dist/commands/new.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAM9E,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAEzD,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,KAAe;IACrE,MAAM,0BAA0B,CAC9B,OAAO,CAAC,WAAW,EACnB,oBAAoB,CAAC,aAAa,EAAE,KAAK,CAAC,EAC1C;QACE,KAAK,EAAE,OAAO;KACf,CACF,CAAC;AACJ,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,eAAe,CAAC,OAAO,EAAE;gBAC7B,4CAA4C;aAC7C,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,eAAe,CAAC,OAAO,EAAE;gBAC7B,wDAAwD;gBACxD,yBAAyB;aAC1B,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAC7C,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["import { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\ntype ParsedNewCommand = {\n command: \"new\" | \"/new\" | \"/pi-new\";\n};\n\nconst NEW_COMMANDS = [\"new\", \"/new\", \"/pi-new\"] as const;\n\nfunction parseNewCommand(text: string): ParsedNewCommand | null {\n const matched = matchCommand(text, NEW_COMMANDS);\n return matched ? { command: matched.command } : null;\n}\n\nasync function replyNewSession(context: CommandContext, lines: string[]): Promise<void> {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatCommandSummary(\"New Session\", lines),\n {\n style: \"muted\",\n },\n );\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 replyNewSession(context, [\n \"為了避免誤清除共享上下文,`/new` 目前只能在與機器人的私訊 / DM 中使用。\",\n ]);\n return true;\n }\n\n if (!context.services.runtime) {\n await replyNewSession(context, [\n \"New command is not configured correctly on the server.\",\n \"Please try again later.\",\n ]);\n return true;\n }\n\n await context.services.runtime.handleNewCommand(\n context.sessionKey,\n context.conversationId,\n context.bot,\n );\n return true;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/commands/sandbox.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjE,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,GAAG,UAAU,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;CACvC;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAS7E;AAMD,qBAAa,qBAAsB,YAAW,cAAc;IACpD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA2FzD;CACF","sourcesContent":["import { join } from \"node:path\";\nimport { saveConversationSandboxConfig } from \"../config.js\";\nimport { readConversationWorkspaceMountMode } from \"../execution-resolver.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyDiagnosticWithContext } from \"./utils.js\";\n\nexport interface ParsedSandboxCommand {\n command: \"/pi-sandbox\" | \"/sandbox\";\n action?: \"boost\" | \"private\" | \"full\";\n}\n\nconst SANDBOX_COMMANDS = [\"/pi-sandbox\", \"/sandbox\"] as const;\n\nexport function parseSandboxCommand(text: string): ParsedSandboxCommand | null {\n const matched = matchCommand(text, SANDBOX_COMMANDS, { stripMention: true });\n if (!matched) return null;\n\n const action = matched.args.length === 1 ? matched.args[0].toLowerCase() : undefined;\n if (action === \"boost\" || action === \"private\" || action === \"full\") {\n return { command: matched.command, action };\n }\n return { command: matched.command };\n}\n\nfunction formatSandboxCommandSummary(title: string, lines: string[]): string {\n return
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/commands/sandbox.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjE,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,GAAG,UAAU,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;CACvC;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAS7E;AAMD,qBAAa,qBAAsB,YAAW,cAAc;IACpD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA2FzD;CACF","sourcesContent":["import { join } from \"node:path\";\nimport { saveConversationSandboxConfig } from \"../config.js\";\nimport { readConversationWorkspaceMountMode } from \"../execution-resolver.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\nexport interface ParsedSandboxCommand {\n command: \"/pi-sandbox\" | \"/sandbox\";\n action?: \"boost\" | \"private\" | \"full\";\n}\n\nconst SANDBOX_COMMANDS = [\"/pi-sandbox\", \"/sandbox\"] as const;\n\nexport function parseSandboxCommand(text: string): ParsedSandboxCommand | null {\n const matched = matchCommand(text, SANDBOX_COMMANDS, { stripMention: true });\n if (!matched) return null;\n\n const action = matched.args.length === 1 ? matched.args[0].toLowerCase() : undefined;\n if (action === \"boost\" || action === \"private\" || action === \"full\") {\n return { command: matched.command, action };\n }\n return { command: matched.command };\n}\n\nfunction formatSandboxCommandSummary(title: string, lines: string[]): string {\n return formatCommandSummary(title, lines);\n}\n\nexport class SandboxCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseSandboxCommand(context.commandText);\n if (!parsed) return false;\n\n if (context.services.sandbox.type !== \"image\" || !context.services.provisioner) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox\", [\n \"`/pi-sandbox` 目前只支援 `image:*` managed sandbox。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const containerKey = resolveActorVaultKey(\n context.services.sandbox,\n context.platformUserId,\n context.conversationId,\n );\n\n if (parsed.action === \"private\" || parsed.action === \"full\") {\n saveConversationSandboxConfig(join(context.services.workingDir, context.conversationId), {\n imageWorkspaceMount: parsed.action,\n });\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Workspace\", [\n parsed.action === \"full\"\n ? \"已將此 conversation 的 sandbox 設為 full workspace mode。\"\n : \"已將此 conversation 的 sandbox 設為 private workspace mode。\",\n `Workspace mount: ${parsed.action}`,\n parsed.action === \"full\"\n ? \"之後這個 container 會把整個 host workspace 掛到 /workspace。\"\n : \"之後這個 container 只會掛載 private workspace 檔案與當前 conversation 目錄。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (parsed.action === \"boost\") {\n const boostLimits = context.services.provisioner.getBoostLimits();\n if (!boostLimits?.cpus && !boostLimits?.memory) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Boost\", [\n \"此 mikan instance 尚未設定 sandbox boost 規格。\",\n \"請先在全域 settings.json 設定 `sandbox.boost`。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const status = await context.services.provisioner.boost(containerKey);\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Boost\", [\n \"已暫時提升此 conversation 的 sandbox 規格。\",\n `Current: ${formatLimits(status.limits)}`,\n \"boost 會在此 sandbox container 關閉後結束。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const status = context.services.provisioner.getLimitStatus(containerKey);\n const defaultLimits = context.services.provisioner.getDefaultLimits();\n const boostLimits = context.services.provisioner.getBoostLimits();\n const workspaceMount = readConversationWorkspaceMountMode(\n context.services.workingDir,\n context.conversationId,\n );\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\n \"Sandbox\",\n [\n `Current: ${formatLimits(status.limits)}`,\n `Status: ${status.boosted ? \"boosted\" : \"default\"}`,\n `Workspace mount: ${workspaceMount}`,\n \"\",\n `Default: ${formatLimits(defaultLimits)}`,\n boostLimits ? `Boost: ${formatLimits({ ...defaultLimits, ...boostLimits })}` : undefined,\n ].filter((line): line is string => line !== undefined),\n ),\n { style: \"muted\" },\n );\n return true;\n }\n}\n\nfunction formatLimits(limits: { cpus?: string; memory?: string } | undefined): string {\n return `CPU ${limits?.cpus ?? \"unlimited\"} / Memory ${limits?.memory ?? \"unlimited\"}`;\n}\n"]}
|
package/dist/commands/sandbox.js
CHANGED
|
@@ -3,7 +3,7 @@ import { saveConversationSandboxConfig } from "../config.js";
|
|
|
3
3
|
import { readConversationWorkspaceMountMode } from "../execution-resolver.js";
|
|
4
4
|
import { resolveActorVaultKey } from "../vault-routing.js";
|
|
5
5
|
import { matchCommand } from "./parse.js";
|
|
6
|
-
import { replyDiagnosticWithContext } from "./utils.js";
|
|
6
|
+
import { formatCommandSummary, replyDiagnosticWithContext } from "./utils.js";
|
|
7
7
|
const SANDBOX_COMMANDS = ["/pi-sandbox", "/sandbox"];
|
|
8
8
|
export function parseSandboxCommand(text) {
|
|
9
9
|
const matched = matchCommand(text, SANDBOX_COMMANDS, { stripMention: true });
|
|
@@ -16,7 +16,7 @@ export function parseSandboxCommand(text) {
|
|
|
16
16
|
return { command: matched.command };
|
|
17
17
|
}
|
|
18
18
|
function formatSandboxCommandSummary(title, lines) {
|
|
19
|
-
return
|
|
19
|
+
return formatCommandSummary(title, lines);
|
|
20
20
|
}
|
|
21
21
|
export class SandboxCommandHandler {
|
|
22
22
|
async tryHandle(context) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/commands/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,kCAAkC,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/commands/sandbox.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { saveConversationSandboxConfig } from \"../config.js\";\nimport { readConversationWorkspaceMountMode } from \"../execution-resolver.js\";\nimport { resolveActorVaultKey } from \"../vault-routing.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } from \"./utils.js\";\n\nexport interface ParsedSandboxCommand {\n command: \"/pi-sandbox\" | \"/sandbox\";\n action?: \"boost\" | \"private\" | \"full\";\n}\n\nconst SANDBOX_COMMANDS = [\"/pi-sandbox\", \"/sandbox\"] as const;\n\nexport function parseSandboxCommand(text: string): ParsedSandboxCommand | null {\n const matched = matchCommand(text, SANDBOX_COMMANDS, { stripMention: true });\n if (!matched) return null;\n\n const action = matched.args.length === 1 ? matched.args[0].toLowerCase() : undefined;\n if (action === \"boost\" || action === \"private\" || action === \"full\") {\n return { command: matched.command, action };\n }\n return { command: matched.command };\n}\n\nfunction formatSandboxCommandSummary(title: string, lines: string[]): string {\n return formatCommandSummary(title, lines);\n}\n\nexport class SandboxCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const parsed = parseSandboxCommand(context.commandText);\n if (!parsed) return false;\n\n if (context.services.sandbox.type !== \"image\" || !context.services.provisioner) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox\", [\n \"`/pi-sandbox` 目前只支援 `image:*` managed sandbox。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const containerKey = resolveActorVaultKey(\n context.services.sandbox,\n context.platformUserId,\n context.conversationId,\n );\n\n if (parsed.action === \"private\" || parsed.action === \"full\") {\n saveConversationSandboxConfig(join(context.services.workingDir, context.conversationId), {\n imageWorkspaceMount: parsed.action,\n });\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Workspace\", [\n parsed.action === \"full\"\n ? \"已將此 conversation 的 sandbox 設為 full workspace mode。\"\n : \"已將此 conversation 的 sandbox 設為 private workspace mode。\",\n `Workspace mount: ${parsed.action}`,\n parsed.action === \"full\"\n ? \"之後這個 container 會把整個 host workspace 掛到 /workspace。\"\n : \"之後這個 container 只會掛載 private workspace 檔案與當前 conversation 目錄。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n if (parsed.action === \"boost\") {\n const boostLimits = context.services.provisioner.getBoostLimits();\n if (!boostLimits?.cpus && !boostLimits?.memory) {\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Boost\", [\n \"此 mikan instance 尚未設定 sandbox boost 規格。\",\n \"請先在全域 settings.json 設定 `sandbox.boost`。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const status = await context.services.provisioner.boost(containerKey);\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\"Sandbox Boost\", [\n \"已暫時提升此 conversation 的 sandbox 規格。\",\n `Current: ${formatLimits(status.limits)}`,\n \"boost 會在此 sandbox container 關閉後結束。\",\n ]),\n { style: \"muted\" },\n );\n return true;\n }\n\n const status = context.services.provisioner.getLimitStatus(containerKey);\n const defaultLimits = context.services.provisioner.getDefaultLimits();\n const boostLimits = context.services.provisioner.getBoostLimits();\n const workspaceMount = readConversationWorkspaceMountMode(\n context.services.workingDir,\n context.conversationId,\n );\n await replyDiagnosticWithContext(\n context.responseCtx,\n formatSandboxCommandSummary(\n \"Sandbox\",\n [\n `Current: ${formatLimits(status.limits)}`,\n `Status: ${status.boosted ? \"boosted\" : \"default\"}`,\n `Workspace mount: ${workspaceMount}`,\n \"\",\n `Default: ${formatLimits(defaultLimits)}`,\n boostLimits ? `Boost: ${formatLimits({ ...defaultLimits, ...boostLimits })}` : undefined,\n ].filter((line): line is string => line !== undefined),\n ),\n { style: \"muted\" },\n );\n return true;\n }\n}\n\nfunction formatLimits(limits: { cpus?: string; memory?: string } | undefined): string {\n return `CPU ${limits?.cpus ?? \"unlimited\"} / Memory ${limits?.memory ?? \"unlimited\"}`;\n}\n"]}
|
|
@@ -1 +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;
|
|
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,CA6EzD;CACF","sourcesContent":["import { resolveExistingSessionFile } from \"../session-view/service.js\";\nimport { parseSessionViewCommand } from \"../session-view/command.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { formatCommandSummary, replyDiagnosticWithContext } 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 (lines: string[]): Promise<void> => {\n const text = formatCommandSummary(\"Session\", lines);\n if (context.privateConversation) {\n await replyDiagnosticWithContext(context.responseCtx, text, { style: \"muted\" });\n return;\n }\n\n if (context.bot.postPrivateDiagnostic) {\n await context.bot.postPrivateDiagnostic(\n context.conversationId,\n context.platformUserId,\n text,\n {\n style: \"muted\",\n },\n );\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 replyDiagnosticWithContext(context.responseCtx, text, { style: \"muted\" });\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.\",\n \"Set `MIKAN_LINK_URL` or `MIKAN_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。\",\n \"先和機器人對話一次,建立 session 後再試。\",\n ]);\n return true;\n }\n\n const platformUser = context.bot\n .getPlatformInfo()\n .users.find((user) => user.id === context.platformUserId);\n const platformUserName = platformUser?.userName || platformUser?.displayName;\n\n const token = context.services.sessionViewTokenStore.create(\n context.platform,\n context.platformUserId,\n context.conversationId,\n context.sessionKey,\n sessionFile,\n platformUserName,\n );\n\n await sendSessionViewReply([\n `${context.services.portalBaseUrl}/session?token=${token.token}`,\n \"Expires: 24 hours\",\n ]);\n return true;\n }\n}\n"]}
|