@zhin.js/plugin-calendar 0.1.15 → 0.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-calendar",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "CalDAV / Google / Microsoft calendar tools for Zhin Agent",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -40,34 +40,29 @@
40
40
  "url": "https://github.com/lc-cn"
41
41
  },
42
42
  "dependencies": {
43
- "@zhin.js/console-contract": "1.1.5",
44
- "@zhin.js/host-http": "1.1.17",
45
- "@zhin.js/im-contract": "1.1.13",
46
- "@zhin.js/page": "1.1.17",
47
- "@zhin.js/skill": "1.1.16",
48
- "@zhin.js/tool": "1.1.18"
43
+ "zod": "^4.6.7",
44
+ "@zhin.js/console-contract": "1.1.6",
45
+ "@zhin.js/host-http": "1.2.0",
46
+ "@zhin.js/page": "1.1.18",
47
+ "@zhin.js/skill": "1.1.17",
48
+ "@zhin.js/tool": "1.1.19"
49
49
  },
50
50
  "devDependencies": {
51
51
  "typescript": "^6.0.3",
52
52
  "vitest": "^4.1.10",
53
- "zod": "^4.4.3",
54
- "@zhin.js/command": "1.1.17",
55
- "zhin.js": "1.1.24"
53
+ "@zhin.js/command": "1.2.0",
54
+ "zhin.js": "1.1.26"
56
55
  },
57
56
  "peerDependencies": {
58
- "zod": "^4.0.0",
59
- "@zhin.js/command": "^1.1.17",
60
- "@zhin.js/page": "^1.1.17",
61
- "@zhin.js/skill": "^1.1.16",
62
- "@zhin.js/tool": "^1.1.18",
63
- "zhin.js": "^1.1.24"
57
+ "@zhin.js/command": "^1.2.0",
58
+ "@zhin.js/page": "^1.1.18",
59
+ "@zhin.js/skill": "^1.1.17",
60
+ "@zhin.js/tool": "^1.1.19",
61
+ "zhin.js": "^1.1.26"
64
62
  },
65
63
  "peerDependenciesMeta": {
66
64
  "@zhin.js/command": {
67
65
  "optional": true
68
- },
69
- "zod": {
70
- "optional": true
71
66
  }
72
67
  },
73
68
  "repository": {
@@ -110,6 +105,7 @@
110
105
  },
111
106
  "scripts": {
112
107
  "build": "tsc",
108
+ "clean": "rimraf lib",
113
109
  "check": "tsc -p tsconfig.check.json",
114
110
  "test": "vitest run --root ../../.. plugins/utils/calendar/tests",
115
111
  "auth": "tsx src/cli/auth.ts"
@@ -1,5 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { definePage, createDeclarativePage } from '@zhin.js/console-contract';
2
+ import { definePage, definePageBlueprint } from '@zhin.js/console-contract';
3
3
  export const meta = definePage({
4
4
  title: '日历日程',
5
5
  titleI18n: { en: 'Calendar' },
@@ -7,11 +7,11 @@ export const meta = definePage({
7
7
  order: 53,
8
8
  requiredPermissions: ['calendar:read'],
9
9
  });
10
- export default createDeclarativePage({
10
+ export default definePageBlueprint({
11
11
  title: { zh: '日历日程', en: 'Calendar' },
12
12
  description: {
13
- zh: '查看日历后端连接状态、时区与工作时间范围',
14
- en: 'View calendar provider status, timezone, and working hours configuration',
13
+ zh: '查看日历后端连接状态、时区与工作时间范围,并在线新建或管理日程',
14
+ en: 'View calendar provider status, timezone, and working hours configuration, and create or manage events',
15
15
  },
16
16
  dictionary: {
17
17
  '已就绪': 'Ready',
@@ -59,7 +59,75 @@ export default createDeclarativePage({
59
59
  value: 'workingHours',
60
60
  },
61
61
  ],
62
+ actions: [
63
+ {
64
+ name: 'create',
65
+ label: { zh: '新建日程', en: 'Create Event' },
66
+ variant: 'primary',
67
+ fields: [
68
+ {
69
+ key: 'title',
70
+ label: { zh: '日程标题', en: 'Event Title' },
71
+ type: 'text',
72
+ required: true,
73
+ },
74
+ {
75
+ key: 'date',
76
+ label: { zh: '日期 YYYY-MM-DD', en: 'Date (YYYY-MM-DD)' },
77
+ type: 'text',
78
+ required: true,
79
+ placeholder: '2026-11-08',
80
+ },
81
+ {
82
+ key: 'time',
83
+ label: { zh: '时间 HH:mm', en: 'Time (HH:mm)' },
84
+ type: 'text',
85
+ defaultValue: '09:00',
86
+ },
87
+ {
88
+ key: 'repeat',
89
+ label: { zh: '重复周期', en: 'Repeat' },
90
+ type: 'select',
91
+ defaultValue: 'none',
92
+ options: [
93
+ { value: 'none', label: { zh: '不重复 (none)', en: 'None' } },
94
+ { value: 'daily', label: { zh: '每天 (daily)', en: 'Daily' } },
95
+ { value: 'weekly', label: { zh: '每周 (weekly)', en: 'Weekly' } },
96
+ { value: 'monthly', label: { zh: '每月 (monthly)', en: 'Monthly' } },
97
+ { value: 'yearly', label: { zh: '每年 (yearly)', en: 'Yearly' } },
98
+ ],
99
+ },
100
+ ],
101
+ },
102
+ {
103
+ name: 'delete',
104
+ label: { zh: '删除日程', en: 'Delete Event' },
105
+ variant: 'danger',
106
+ fields: [
107
+ {
108
+ key: 'id',
109
+ label: { zh: '日程 ID', en: 'Event ID' },
110
+ type: 'text',
111
+ required: true,
112
+ },
113
+ ],
114
+ },
115
+ ],
62
116
  sections: [
117
+ {
118
+ id: 'events',
119
+ layout: 'table',
120
+ title: { zh: '日程列表', en: 'Calendar Events' },
121
+ emptyText: { zh: '暂无日程记录', en: 'No calendar events yet' },
122
+ items: 'events',
123
+ columns: [
124
+ { key: 'id', label: { zh: 'ID', en: 'ID' } },
125
+ { key: 'title', label: { zh: '日程标题', en: 'Title' } },
126
+ { key: 'date', label: { zh: '日期', en: 'Date' } },
127
+ { key: 'time', label: { zh: '时间', en: 'Time' } },
128
+ { key: 'repeat', label: { zh: '重复', en: 'Repeat' } },
129
+ ],
130
+ },
63
131
  {
64
132
  id: 'overview',
65
133
  title: { zh: '配置概览', en: 'Configuration Overview' },
@@ -1,4 +1,5 @@
1
- import { definePage, createDeclarativePage } from '@zhin.js/console-contract';
1
+ import { definePage, definePageBlueprint } from '@zhin.js/console-contract';
2
+ import type { CalendarConsoleEventItem } from '../../plugin.js';
2
3
 
3
4
  export interface CalendarConsoleStatus {
4
5
  readonly provider: 'none' | 'google' | 'microsoft' | 'caldav';
@@ -6,6 +7,8 @@ export interface CalendarConsoleStatus {
6
7
  readonly ready: boolean;
7
8
  readonly oauthCallbackPath: string;
8
9
  readonly workingHours: string;
10
+ readonly eventCount?: number;
11
+ readonly events?: readonly CalendarConsoleEventItem[];
9
12
  }
10
13
 
11
14
  export const meta = definePage({
@@ -16,11 +19,11 @@ export const meta = definePage({
16
19
  requiredPermissions: ['calendar:read'],
17
20
  });
18
21
 
19
- export default createDeclarativePage<CalendarConsoleStatus>({
22
+ export default definePageBlueprint<CalendarConsoleStatus>({
20
23
  title: { zh: '日历日程', en: 'Calendar' },
21
24
  description: {
22
- zh: '查看日历后端连接状态、时区与工作时间范围',
23
- en: 'View calendar provider status, timezone, and working hours configuration',
25
+ zh: '查看日历后端连接状态、时区与工作时间范围,并在线新建或管理日程',
26
+ en: 'View calendar provider status, timezone, and working hours configuration, and create or manage events',
24
27
  },
25
28
  dictionary: {
26
29
  '已就绪': 'Ready',
@@ -68,7 +71,75 @@ export default createDeclarativePage<CalendarConsoleStatus>({
68
71
  value: 'workingHours',
69
72
  },
70
73
  ],
74
+ actions: [
75
+ {
76
+ name: 'create',
77
+ label: { zh: '新建日程', en: 'Create Event' },
78
+ variant: 'primary',
79
+ fields: [
80
+ {
81
+ key: 'title',
82
+ label: { zh: '日程标题', en: 'Event Title' },
83
+ type: 'text',
84
+ required: true,
85
+ },
86
+ {
87
+ key: 'date',
88
+ label: { zh: '日期 YYYY-MM-DD', en: 'Date (YYYY-MM-DD)' },
89
+ type: 'text',
90
+ required: true,
91
+ placeholder: '2026-11-08',
92
+ },
93
+ {
94
+ key: 'time',
95
+ label: { zh: '时间 HH:mm', en: 'Time (HH:mm)' },
96
+ type: 'text',
97
+ defaultValue: '09:00',
98
+ },
99
+ {
100
+ key: 'repeat',
101
+ label: { zh: '重复周期', en: 'Repeat' },
102
+ type: 'select',
103
+ defaultValue: 'none',
104
+ options: [
105
+ { value: 'none', label: { zh: '不重复 (none)', en: 'None' } },
106
+ { value: 'daily', label: { zh: '每天 (daily)', en: 'Daily' } },
107
+ { value: 'weekly', label: { zh: '每周 (weekly)', en: 'Weekly' } },
108
+ { value: 'monthly', label: { zh: '每月 (monthly)', en: 'Monthly' } },
109
+ { value: 'yearly', label: { zh: '每年 (yearly)', en: 'Yearly' } },
110
+ ],
111
+ },
112
+ ],
113
+ },
114
+ {
115
+ name: 'delete',
116
+ label: { zh: '删除日程', en: 'Delete Event' },
117
+ variant: 'danger',
118
+ fields: [
119
+ {
120
+ key: 'id',
121
+ label: { zh: '日程 ID', en: 'Event ID' },
122
+ type: 'text',
123
+ required: true,
124
+ },
125
+ ],
126
+ },
127
+ ],
71
128
  sections: [
129
+ {
130
+ id: 'events',
131
+ layout: 'table',
132
+ title: { zh: '日程列表', en: 'Calendar Events' },
133
+ emptyText: { zh: '暂无日程记录', en: 'No calendar events yet' },
134
+ items: 'events',
135
+ columns: [
136
+ { key: 'id', label: { zh: 'ID', en: 'ID' } },
137
+ { key: 'title', label: { zh: '日程标题', en: 'Title' } },
138
+ { key: 'date', label: { zh: '日期', en: 'Date' } },
139
+ { key: 'time', label: { zh: '时间', en: 'Time' } },
140
+ { key: 'repeat', label: { zh: '重复', en: 'Repeat' } },
141
+ ],
142
+ },
72
143
  {
73
144
  id: 'overview',
74
145
  title: { zh: '配置概览', en: 'Configuration Overview' },
package/plugin.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { projectLocaleToken } from 'zhin.js/i18n';
3
3
  import { databaseHostToken, definePlugin, definePluginCapabilityGraph, outboundHostToken, pluginConsoleRpcToken, } from "zhin.js";
4
4
  import { httpHostToken } from "@zhin.js/host-http";
5
+ import { createCalendarNotifier } from "./lib/notify.js";
5
6
  import { assertCalendarTimezone, createCalendarConsoleRpc, createCalendarProvider, createOAuthTokenManagers, } from "./lib/index.js";
6
7
  import { buildRedirectUri, CalendarAuthService, DEFAULT_OAUTH_CALLBACK_PATH, localOriginFromAddress, } from "./lib/chat-auth.js";
7
8
  import { CalendarCredentialStore, } from "./lib/credential-store.js";
@@ -38,11 +39,7 @@ export default definePlugin({
38
39
  const http = resources.has(httpHostToken)
39
40
  ? resources.use(httpHostToken)
40
41
  : undefined;
41
- const notify = outbound?.sendTarget
42
- ? async (target, text, locale) => {
43
- await outbound.sendTarget(target, text, { locale });
44
- }
45
- : undefined;
42
+ const notify = createCalendarNotifier(outbound, (target) => context.isEnabledFor(target));
46
43
  const callbackPath = config.oauth_callback_path?.trim() || DEFAULT_OAUTH_CALLBACK_PATH;
47
44
  const resolveRedirectUri = () => {
48
45
  const configuredBase = config.oauth_redirect_base_url?.trim() ?? "";
package/schema.json CHANGED
@@ -29,7 +29,10 @@
29
29
  },
30
30
  "default": "none",
31
31
  "x-zhin-config": {
32
- "effect": "reload"
32
+ "effect": "reload",
33
+ "levels": [
34
+ "global"
35
+ ]
33
36
  }
34
37
  },
35
38
  "timezone": {
@@ -42,21 +45,30 @@
42
45
  },
43
46
  "default": "Asia/Manila",
44
47
  "x-zhin-config": {
45
- "effect": "reload"
48
+ "effect": "reload",
49
+ "levels": [
50
+ "global"
51
+ ]
46
52
  }
47
53
  },
48
54
  "work_day_start": {
49
55
  "type": "string",
50
56
  "default": "09:00",
51
57
  "x-zhin-config": {
52
- "effect": "reload"
58
+ "effect": "reload",
59
+ "levels": [
60
+ "global"
61
+ ]
53
62
  }
54
63
  },
55
64
  "work_day_end": {
56
65
  "type": "string",
57
66
  "default": "18:00",
58
67
  "x-zhin-config": {
59
- "effect": "reload"
68
+ "effect": "reload",
69
+ "levels": [
70
+ "global"
71
+ ]
60
72
  }
61
73
  },
62
74
  "oauth_redirect_base_url": {
@@ -64,7 +76,10 @@
64
76
  "description": "聊天授权回调的外部基础地址(如 https://bot.example.com);留空时使用 zhin HTTP Host 的本机地址",
65
77
  "default": "",
66
78
  "x-zhin-config": {
67
- "effect": "reload"
79
+ "effect": "reload",
80
+ "levels": [
81
+ "global"
82
+ ]
68
83
  }
69
84
  },
70
85
  "oauth_callback_path": {
@@ -72,7 +87,10 @@
72
87
  "description": "聊天授权回调路由,挂在 zhin HTTP Host 上;反向代理/隧道只转发部分路径时可调整",
73
88
  "default": "/calendar/oauth/callback",
74
89
  "x-zhin-config": {
75
- "effect": "reload"
90
+ "effect": "reload",
91
+ "levels": [
92
+ "global"
93
+ ]
76
94
  }
77
95
  },
78
96
  "caldav": {
@@ -110,6 +128,11 @@
110
128
  "effect": "reload"
111
129
  }
112
130
  }
131
+ },
132
+ "x-zhin-config": {
133
+ "levels": [
134
+ "global"
135
+ ]
113
136
  }
114
137
  },
115
138
  "google": {
@@ -170,6 +193,11 @@
170
193
  "effect": "reload"
171
194
  }
172
195
  }
196
+ },
197
+ "x-zhin-config": {
198
+ "levels": [
199
+ "global"
200
+ ]
173
201
  }
174
202
  },
175
203
  "microsoft": {
@@ -248,7 +276,20 @@
248
276
  "effect": "reload"
249
277
  }
250
278
  }
279
+ },
280
+ "x-zhin-config": {
281
+ "levels": [
282
+ "global"
283
+ ]
251
284
  }
252
285
  }
286
+ },
287
+ "x-zhin-config": {
288
+ "levels": [
289
+ "global",
290
+ "adapter",
291
+ "endpoint",
292
+ "conversation"
293
+ ]
253
294
  }
254
295
  }
@@ -8,7 +8,7 @@ import { parseDateTimeInZone } from "../../../../lib/time.js";
8
8
  export default defineAgentTool({
9
9
  description: "Create a calendar event. Always confirm title, start, end and timezone with the user first.",
10
10
  requiresApproval: "always",
11
- permissions: ["role(master)"],
11
+ permissions: ["owner"],
12
12
  tags: ["calendar"],
13
13
  keywords: ["创建日程", "约时间", "create event"],
14
14
  inputSchema: z.object({
@@ -9,7 +9,7 @@ export default defineAgentTool({
9
9
  description:
10
10
  "Create a calendar event. Always confirm title, start, end and timezone with the user first.",
11
11
  requiresApproval: "always",
12
- permissions: ["role(master)"],
12
+ permissions: ["owner"],
13
13
  tags: ["calendar"],
14
14
  keywords: ["创建日程", "约时间", "create event"],
15
15
  inputSchema: z.object({
package/src/config.ts CHANGED
@@ -9,25 +9,29 @@ export const calendarConfig = Schema.object({
9
9
  ])
10
10
  .default("none")
11
11
  .description("日历后端;none 时工具返回未启用提示").i18n({"en": {"description": "Calendar backend. Tools report that the calendar is disabled when set to none."}})
12
- .effect("reload"),
12
+ .effect("reload")
13
+ .fieldLevels(["global"]),
13
14
  timezone: Schema.string()
14
15
  .default("Asia/Manila")
15
16
  .description("解释无偏移时间与展示用的 IANA 时区").i18n({"en": {"description": "IANA timezone for interpreting times without offsets and for display."}})
16
- .effect("reload"),
17
- work_day_start: Schema.string().default("09:00").effect("reload"),
18
- work_day_end: Schema.string().default("18:00").effect("reload"),
17
+ .effect("reload")
18
+ .fieldLevels(["global"]),
19
+ work_day_start: Schema.string().default("09:00").effect("reload").fieldLevels(["global"]),
20
+ work_day_end: Schema.string().default("18:00").effect("reload").fieldLevels(["global"]),
19
21
  oauth_redirect_base_url: Schema.string()
20
22
  .default("")
21
23
  .description(
22
24
  "聊天授权回调的外部基础地址(如 https://bot.example.com);留空时使用 zhin HTTP Host 的本机地址"
23
25
  )
24
- .effect("reload"),
26
+ .effect("reload")
27
+ .fieldLevels(["global"]),
25
28
  oauth_callback_path: Schema.string()
26
29
  .default("/calendar/oauth/callback")
27
30
  .description(
28
31
  "聊天授权回调路由,挂在 zhin HTTP Host 上;反向代理/隧道只转发部分路径时可调整"
29
32
  )
30
- .effect("reload"),
33
+ .effect("reload")
34
+ .fieldLevels(["global"]),
31
35
  caldav: Schema.object({
32
36
  enabled: Schema.boolean().default(false),
33
37
  calendar_url: Schema.string()
@@ -36,7 +40,7 @@ export const calendarConfig = Schema.object({
36
40
  .effect("reload"),
37
41
  username: Schema.string().default("").effect("reload"),
38
42
  password: Schema.string().default("").effect("reload"),
39
- }),
43
+ }).fieldLevels(["global"]),
40
44
  google: Schema.object({
41
45
  enabled: Schema.boolean().default(false),
42
46
  client_id: Schema.string()
@@ -61,7 +65,7 @@ export const calendarConfig = Schema.object({
61
65
  "可选:持久化轮换后的 refresh_token 的 JSON 路径(如 data/calendar-google-token.json)"
62
66
  )
63
67
  .effect("reload"),
64
- }),
68
+ }).fieldLevels(["global"]),
65
69
  microsoft: Schema.object({
66
70
  enabled: Schema.boolean().default(false),
67
71
  client_id: Schema.string()
@@ -90,8 +94,9 @@ export const calendarConfig = Schema.object({
90
94
  "可选:持久化轮换后的 refresh_token 的 JSON 路径(如 data/calendar-ms-token.json)"
91
95
  )
92
96
  .effect("reload"),
93
- }),
94
- });
97
+ }).fieldLevels(["global"]),
98
+ // ADR 0050 / 0055: one shared calendar backend, so every field stays global; the switch works at every level.
99
+ }).featureLevels(["global", "adapter", "endpoint", "conversation"]);
95
100
 
96
101
  export type CalendarConfig = Schema.Output<typeof calendarConfig>;
97
102
 
package/src/console.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import {
2
3
  definePluginConsoleRpc,
3
4
  PluginConsoleRpcError,
@@ -10,6 +11,16 @@ import { DEFAULT_OAUTH_CALLBACK_PATH } from './chat-auth.js';
10
11
  import type { CalendarConfig } from './config.js';
11
12
  import { calendarRuntimeToken, type CalendarRuntime } from './runtime.js';
12
13
 
14
+ export interface CalendarConsoleEventItem {
15
+ readonly id: string;
16
+ readonly title: string;
17
+ readonly date: string;
18
+ readonly time: string;
19
+ readonly repeat: string;
20
+ readonly createdAt: string;
21
+ readonly [key: string]: string;
22
+ }
23
+
13
24
  type CalendarRuntimeLookup = {
14
25
  readonly use?: (token: typeof calendarRuntimeToken) => CalendarRuntime | undefined;
15
26
  };
@@ -28,7 +39,11 @@ function resolveCalendarRuntime(call: PluginConsoleRpcCall, fallback?: CalendarR
28
39
  }
29
40
 
30
41
  export function createCalendarConsoleRpc(defaultRuntime?: CalendarRuntime): PluginConsoleRpc {
42
+ const localEvents: CalendarConsoleEventItem[] = [];
43
+
31
44
  return definePluginConsoleRpc({
45
+ // Day-to-day edits belong to operators; `:write` would otherwise infer `manage` (ADR 0049).
46
+ permissions: [{ id: 'calendar:write', level: 'operate', description: '编辑日程' }],
32
47
  status: {
33
48
  page: 'index',
34
49
  requiredPermissions: ['calendar:read'],
@@ -49,8 +64,97 @@ export function createCalendarConsoleRpc(defaultRuntime?: CalendarRuntime): Plug
49
64
  ready: Boolean(runtime.provider),
50
65
  oauthCallbackPath: cfg.oauth_callback_path?.trim() || DEFAULT_OAUTH_CALLBACK_PATH,
51
66
  workingHours: `${start} - ${end}`,
67
+ ...(localEvents.length > 0
68
+ ? {
69
+ eventCount: localEvents.length,
70
+ events: [...localEvents],
71
+ }
72
+ : {}),
52
73
  };
53
74
  },
54
75
  },
76
+ actions: {
77
+ create: {
78
+ page: 'index',
79
+ requiredPermissions: ['calendar:write'],
80
+ async handler(input: PluginConsoleRpcInput, call: PluginConsoleRpcCall): Promise<PluginConsoleRpcJson> {
81
+ const runtime = resolveCalendarRuntime(call, defaultRuntime);
82
+ const title = typeof input.title === 'string' ? input.title.trim() : '';
83
+ if (!title) {
84
+ throw new PluginConsoleRpcError('invalid_input', '请输入日程标题');
85
+ }
86
+ const date = typeof input.date === 'string' ? input.date.trim() : '';
87
+ if (!/^\d{4}-\d{2}-\d{2}$/u.test(date)) {
88
+ throw new PluginConsoleRpcError('invalid_input', '请提供 YYYY-MM-DD 格式的日期');
89
+ }
90
+ const time =
91
+ typeof input.time === 'string' && input.time.trim()
92
+ ? input.time.trim()
93
+ : '09:00';
94
+ const repeat =
95
+ typeof input.repeat === 'string' &&
96
+ ['none', 'daily', 'weekly', 'monthly', 'yearly'].includes(input.repeat)
97
+ ? input.repeat
98
+ : 'none';
99
+
100
+ let id = randomUUID().slice(0, 8);
101
+ if (runtime.provider) {
102
+ try {
103
+ const start = new Date(`${date}T${time}:00`);
104
+ const end = new Date(start.getTime() + 3600_000);
105
+ if (!Number.isNaN(start.getTime())) {
106
+ const created = await runtime.provider.createEvent(
107
+ { title, start, end },
108
+ call.signal,
109
+ );
110
+ if (created?.id) id = created.id;
111
+ }
112
+ } catch {
113
+ // Keep console event creation working even when external provider is unreachable
114
+ }
115
+ }
116
+
117
+ const eventItem: CalendarConsoleEventItem = {
118
+ id,
119
+ title,
120
+ date,
121
+ time,
122
+ repeat,
123
+ createdAt: new Date().toISOString(),
124
+ };
125
+ localEvents.unshift(eventItem);
126
+ return {
127
+ ok: true,
128
+ id,
129
+ title,
130
+ date,
131
+ time,
132
+ repeat,
133
+ message: `已添加日程:${title} (${date} ${time})`,
134
+ };
135
+ },
136
+ },
137
+ delete: {
138
+ page: 'index',
139
+ requiredPermissions: ['calendar:write'],
140
+ handler(input: PluginConsoleRpcInput, call: PluginConsoleRpcCall): PluginConsoleRpcJson {
141
+ resolveCalendarRuntime(call, defaultRuntime);
142
+ const id = typeof input.id === 'string' ? input.id.trim() : '';
143
+ if (!id) {
144
+ throw new PluginConsoleRpcError('invalid_input', '请提供要删除的日程 ID');
145
+ }
146
+ const idx = localEvents.findIndex((item) => item.id === id);
147
+ const removed = idx >= 0;
148
+ if (removed) {
149
+ localEvents.splice(idx, 1);
150
+ }
151
+ return {
152
+ ok: removed,
153
+ id,
154
+ message: removed ? `已删除日程 ${id}` : `未找到日程 ${id}`,
155
+ };
156
+ },
157
+ },
158
+ },
55
159
  });
56
160
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * OAuth redirect endpoint mounted on zhin's shared HTTP host.
3
3
  */
4
- import type { IncomingMessage, ServerResponse } from "node:http";
5
4
  import type { CallbackResult } from "./chat-auth.js";
6
5
 
7
6
  export interface CalendarRouteHost {
@@ -9,10 +8,9 @@ export interface CalendarRouteHost {
9
8
  method: string,
10
9
  path: string,
11
10
  handler: (
12
- request: IncomingMessage,
13
- response: ServerResponse,
14
- url: URL
15
- ) => void | Promise<void>,
11
+ request: Request,
12
+ context: { readonly url: URL }
13
+ ) => Response | Promise<Response>,
16
14
  meta?: { readonly summary?: string; readonly tags?: readonly string[] }
17
15
  ): () => void;
18
16
  }
@@ -42,13 +40,12 @@ export function registerOAuthCallbackRoute(
42
40
  return http.route(
43
41
  "GET",
44
42
  callbackPath,
45
- async (_request, response, url) => {
43
+ async (_request, { url }) => {
46
44
  const result = await handleCallback(url.searchParams);
47
- response.writeHead(result.statusCode, {
45
+ return new Response(renderPage(result), { status: result.statusCode, headers: {
48
46
  "content-type": "text/html; charset=utf-8",
49
47
  "cache-control": "no-store",
50
- });
51
- response.end(renderPage(result));
48
+ } });
52
49
  },
53
50
  { summary: "Calendar OAuth callback", tags: ["calendar"] }
54
51
  );
package/src/index.ts CHANGED
@@ -28,7 +28,10 @@ export {
28
28
  } from "./config.js";
29
29
  export { cleanSecret } from "./secrets.js";
30
30
  export { calendarRuntimeToken, type CalendarRuntime } from "./runtime.js";
31
- export { createCalendarConsoleRpc } from "./console.js";
31
+ export {
32
+ createCalendarConsoleRpc,
33
+ type CalendarConsoleEventItem,
34
+ } from "./console.js";
32
35
  export {
33
36
  OAuthTokenManager,
34
37
  OAuthTokenError,