mioku-plugin-agent 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ import type { Bot } from "mioku";
2
+
3
+ /** 需要按平台换取下载地址的附件引用 */
4
+ export interface PlatformFileRef {
5
+ fileId: string;
6
+ groupId?: string;
7
+ userId?: string;
8
+ }
9
+
10
+ export interface PlatformFileLookup {
11
+ sources: string[];
12
+ names: string[];
13
+ }
14
+
15
+ export const EMPTY_FILE_LOOKUP: PlatformFileLookup = { sources: [], names: [] };
16
+
17
+ /** 把平台返回的原始结果合并成下载来源(各平台实现共用) */
18
+ export const mergeFileLookup = (
19
+ target: PlatformFileLookup,
20
+ result: unknown,
21
+ ): void => {
22
+ if (!result || typeof result !== "object") return;
23
+ const record = result as Record<string, unknown>;
24
+ for (const key of ["url", "file", "path"]) {
25
+ const value = record[key];
26
+ if (typeof value === "string" && value.trim()) {
27
+ target.sources.push(value.trim());
28
+ }
29
+ }
30
+ const base64 = record.base64 ?? record.data;
31
+ if (typeof base64 === "string" && base64.trim()) {
32
+ target.sources.push(`base64://${base64.trim()}`);
33
+ }
34
+ for (const key of ["file_name", "name"]) {
35
+ const value = record[key];
36
+ if (typeof value === "string" && value.trim()) {
37
+ target.names.push(value.trim());
38
+ }
39
+ }
40
+ };
41
+
42
+ /**
43
+ * 一个平台分支:自己订阅 `adapter:message` 路由,自己处理平台专有差异。
44
+ * 新增平台只要加一个文件并登记到 `platforms/index.ts`。
45
+ */
46
+ export interface AgentPlatform {
47
+ /** 适配器名,与 `bot.adapter` 对应 */
48
+ readonly adapter: string;
49
+ /** 该分支订阅的事件路由,如 `onebotv11:message` */
50
+ readonly route: string;
51
+ /** 平台专有的 file_id → 下载来源;没有该能力时返回空 */
52
+ resolveFile(bot: Bot, ref: PlatformFileRef): Promise<PlatformFileLookup>;
53
+ }
package/tools/approval.ts CHANGED
@@ -2,7 +2,7 @@ import type { AgentPermissionLevel } from "../types";
2
2
 
3
3
  export interface PendingApproval {
4
4
  id: string;
5
- userId: number;
5
+ userId: string;
6
6
  command: string;
7
7
  cwd: string;
8
8
  level: AgentPermissionLevel;
@@ -18,7 +18,7 @@ interface PendingEntry extends PendingApproval {
18
18
 
19
19
  export class ApprovalManager {
20
20
  private pending = new Map<string, PendingEntry>();
21
- private latestByUser = new Map<number, string>();
21
+ private latestByUser = new Map<string, string>();
22
22
  private seq = 0;
23
23
 
24
24
  create(
@@ -55,7 +55,7 @@ export class ApprovalManager {
55
55
  return true;
56
56
  }
57
57
 
58
- resolveLatest(userId: number, approved: boolean): PendingApproval | null {
58
+ resolveLatest(userId: string, approved: boolean): PendingApproval | null {
59
59
  const id = this.latestByUser.get(userId) ?? this.latestFor(userId);
60
60
  if (!id) return null;
61
61
  const entry = this.pending.get(id);
@@ -65,13 +65,13 @@ export class ApprovalManager {
65
65
  return approval;
66
66
  }
67
67
 
68
- latestByUserId(userId: number): PendingApproval | null {
68
+ latestByUserId(userId: string): PendingApproval | null {
69
69
  const id = this.latestByUser.get(userId) ?? this.latestFor(userId);
70
70
  const entry = id ? this.pending.get(id) : undefined;
71
71
  return entry ? this.toPending(entry) : null;
72
72
  }
73
73
 
74
- cancelByUser(userId: number): number {
74
+ cancelByUser(userId: string): number {
75
75
  const ids = [...this.pending.values()]
76
76
  .filter((entry) => entry.userId === userId)
77
77
  .map((entry) => entry.id);
@@ -79,7 +79,7 @@ export class ApprovalManager {
79
79
  return ids.length;
80
80
  }
81
81
 
82
- listByUser(userId: number): PendingApproval[] {
82
+ listByUser(userId: string): PendingApproval[] {
83
83
  return [...this.pending.values()]
84
84
  .filter((entry) => entry.userId === userId)
85
85
  .sort((a, b) => a.createdAt - b.createdAt)
@@ -95,7 +95,7 @@ export class ApprovalManager {
95
95
  this.latestByUser.clear();
96
96
  }
97
97
 
98
- private latestFor(userId: number): string | undefined {
98
+ private latestFor(userId: string): string | undefined {
99
99
  let latest: PendingEntry | undefined;
100
100
  for (const entry of this.pending.values()) {
101
101
  if (entry.userId !== userId) continue;
package/tools/bash.ts CHANGED
@@ -47,7 +47,7 @@ export interface BashReporter {
47
47
  }
48
48
 
49
49
  interface BashToolDeps {
50
- userId: number;
50
+ userId: string;
51
51
  policy: FsPolicy;
52
52
  config: BashConfig;
53
53
  approvals: ApprovalManager;
package/tools/deliver.ts CHANGED
@@ -8,7 +8,7 @@ import { sendImageSource, sendLocalFile } from "../core/attachment";
8
8
  interface DeliverToolDeps {
9
9
  ctx: MiokuContext;
10
10
  bot: Bot | undefined;
11
- userId: number;
11
+ userId: string;
12
12
  policy: FsPolicy;
13
13
  }
14
14
 
package/tools/index.ts CHANGED
@@ -25,7 +25,10 @@ import { assessCommandRisk } from "../core/risk";
25
25
  import type { TurnActivity } from "../core/activity";
26
26
 
27
27
  export interface TurnToolOptions {
28
- userId: number;
28
+ /** 会话/工作区隔离键(适配器 + 平台用户 id) */
29
+ userId: string;
30
+ /** 平台原始用户 id,用于发送消息 */
31
+ sendUserId: string;
29
32
  bot: Bot | undefined;
30
33
  runId: number;
31
34
  reporter: BashReporter;
@@ -120,13 +123,13 @@ export function buildTurnTools(
120
123
  createSendFileTool({
121
124
  ctx: host.ctx,
122
125
  bot: options.bot,
123
- userId: options.userId,
126
+ userId: options.sendUserId,
124
127
  policy,
125
128
  }),
126
129
  createSendImageTool({
127
130
  ctx: host.ctx,
128
131
  bot: options.bot,
129
- userId: options.userId,
132
+ userId: options.sendUserId,
130
133
  policy,
131
134
  }),
132
135
  ];
@@ -175,6 +178,7 @@ export function buildTurnTools(
175
178
  createTodoTool({
176
179
  host,
177
180
  userId: options.userId,
181
+ sendUserId: options.sendUserId,
178
182
  bot: options.bot,
179
183
  quiet: isQuietMode(policy.level),
180
184
  }),
package/tools/perm.ts CHANGED
@@ -19,12 +19,16 @@ export function normalizePermissionLevel(value: unknown): AgentPermissionLevel {
19
19
  return "workspace-write";
20
20
  }
21
21
 
22
+ /** 作用域键里可能含 `:`(适配器前缀),统一替换成目录安全字符 */
23
+ const sanitizeSegment = (value: string): string =>
24
+ String(value ?? "").replace(/[^A-Za-z0-9._-]+/g, "_") || "unknown";
25
+
22
26
  export interface FsPolicy {
23
27
  level: AgentPermissionLevel;
24
28
  workspaceRoot: string;
25
29
  }
26
30
 
27
- export function workspaceRootFor(baseDir: string, userId: number): string {
31
+ export function workspaceRootFor(baseDir: string, userId: string): string {
28
32
  const raw = String(baseDir ?? "").trim();
29
33
  const base =
30
34
  raw && path.isAbsolute(raw)
@@ -33,7 +37,7 @@ export function workspaceRootFor(baseDir: string, userId: number): string {
33
37
  process.cwd(),
34
38
  raw || path.join("data", "agent", "workspace"),
35
39
  );
36
- return path.resolve(base, String(userId));
40
+ return path.resolve(base, sanitizeSegment(userId));
37
41
  }
38
42
 
39
43
  export function resolveWorkspacePath(policy: FsPolicy, target: string): string {
package/tools/todo.ts CHANGED
@@ -38,12 +38,15 @@ export function formatPlanText(items: SessionPlanItem[]): string {
38
38
 
39
39
  export function createTodoTool(options: {
40
40
  host: AgentHost;
41
- userId: number;
41
+ /** 会话隔离键 */
42
+ userId: string;
43
+ /** 平台原始用户 id,用于推送 */
44
+ sendUserId: string;
42
45
  bot: Bot | undefined;
43
46
  /** yolo 模式:不推送清单,用户只看最终回复 */
44
47
  quiet?: boolean;
45
48
  }): AITool {
46
- const { host, userId, bot, quiet } = options;
49
+ const { host, userId, sendUserId, bot, quiet } = options;
47
50
  return {
48
51
  name: "todo_write",
49
52
  description: DESCRIPTION,
@@ -114,7 +117,7 @@ export function createTodoTool(options: {
114
117
  if (bot && !quiet) {
115
118
  await bot
116
119
  .sendMessage(
117
- { type: "private", user_id: userId },
120
+ { type: "private", user_id: sendUserId },
118
121
  [host.ctx.segment.text(formatPlanText(items))],
119
122
  )
120
123
  .catch((err) =>
package/types.ts CHANGED
@@ -19,7 +19,7 @@ export type AgentPermissionLevel =
19
19
 
20
20
  export interface AgentAccessConfig {
21
21
  allowAdmins: boolean;
22
- users: number[];
22
+ users: string[];
23
23
  }
24
24
 
25
25
  export interface AgentBaseConfig {
@@ -104,8 +104,8 @@ export interface AgentHost {
104
104
  getSettings(): AgentSettingsConfig;
105
105
  getChatShared(): Promise<ChatSharedConfig>;
106
106
  resolveModel(): ResolvedModel | null;
107
- workspaceRoot(userId: number): string;
108
- isAllowed(userId: number): Promise<boolean>;
107
+ workspaceRoot(userId: string): string;
108
+ isAllowed(userId: string): Promise<boolean>;
109
109
  updateBase(patch: Partial<AgentBaseConfig>): Promise<void>;
110
110
  logger: MiokuContext["logger"];
111
111
  }