openclaw-server 0.1.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.
Files changed (54) hide show
  1. package/package.json +29 -0
  2. package/packs/default/faq.yaml +8 -0
  3. package/packs/default/intents.yaml +19 -0
  4. package/packs/default/pack.yaml +12 -0
  5. package/packs/default/policies.yaml +1 -0
  6. package/packs/default/scenarios.yaml +1 -0
  7. package/packs/default/synonyms.yaml +1 -0
  8. package/packs/default/templates.yaml +16 -0
  9. package/packs/default/tools.yaml +1 -0
  10. package/readme.md +1219 -0
  11. package/src/auth.ts +24 -0
  12. package/src/better-sqlite3.d.ts +17 -0
  13. package/src/config.ts +63 -0
  14. package/src/core/matcher.ts +214 -0
  15. package/src/core/normalizer.test.ts +37 -0
  16. package/src/core/normalizer.ts +183 -0
  17. package/src/core/pack-loader.ts +97 -0
  18. package/src/core/reply-engine.test.ts +76 -0
  19. package/src/core/reply-engine.ts +256 -0
  20. package/src/core/request-adapter.ts +65 -0
  21. package/src/core/session-store.ts +48 -0
  22. package/src/core/stream-renderer.ts +237 -0
  23. package/src/core/tool-engine.ts +60 -0
  24. package/src/debug-log.ts +211 -0
  25. package/src/index.ts +23 -0
  26. package/src/openai.ts +79 -0
  27. package/src/response-api.ts +107 -0
  28. package/src/routes/admin.ts +32 -0
  29. package/src/routes/chat-completions.ts +173 -0
  30. package/src/routes/health.ts +7 -0
  31. package/src/routes/models.ts +21 -0
  32. package/src/routes/request-validation.ts +33 -0
  33. package/src/routes/responses.ts +182 -0
  34. package/src/routes/tasks.ts +138 -0
  35. package/src/runtime-stats.ts +80 -0
  36. package/src/server.test.ts +776 -0
  37. package/src/server.ts +108 -0
  38. package/src/tasks/chat-integration.ts +70 -0
  39. package/src/tasks/service.ts +320 -0
  40. package/src/tasks/store.test.ts +183 -0
  41. package/src/tasks/store.ts +602 -0
  42. package/src/tasks/time-parser.test.ts +94 -0
  43. package/src/tasks/time-parser.ts +610 -0
  44. package/src/tasks/timezone.ts +171 -0
  45. package/src/tasks/types.ts +128 -0
  46. package/src/types.ts +202 -0
  47. package/src/weather/chat-integration.ts +56 -0
  48. package/src/weather/location-catalog.ts +166 -0
  49. package/src/weather/open-meteo-provider.ts +221 -0
  50. package/src/weather/parser.test.ts +23 -0
  51. package/src/weather/parser.ts +102 -0
  52. package/src/weather/service.test.ts +54 -0
  53. package/src/weather/service.ts +188 -0
  54. package/src/weather/types.ts +56 -0
@@ -0,0 +1,171 @@
1
+ export type TimeZoneDateParts = {
2
+ year: number;
3
+ month: number;
4
+ day: number;
5
+ hour: number;
6
+ minute: number;
7
+ second: number;
8
+ };
9
+
10
+ type TimeZoneDateInput = {
11
+ year: number;
12
+ month: number;
13
+ day: number;
14
+ hour?: number;
15
+ minute?: number;
16
+ second?: number;
17
+ };
18
+
19
+ type DateBounds = {
20
+ nowIso: string;
21
+ todayStartIso: string;
22
+ tomorrowStartIso: string;
23
+ dayAfterTomorrowIso: string;
24
+ nextWeekStartIso: string;
25
+ };
26
+
27
+ const formatterCache = new Map<string, Intl.DateTimeFormat>();
28
+
29
+ function getFormatter(timeZone: string): Intl.DateTimeFormat {
30
+ const cached = formatterCache.get(timeZone);
31
+ if (cached) {
32
+ return cached;
33
+ }
34
+
35
+ const formatter = new Intl.DateTimeFormat("en-CA", {
36
+ timeZone,
37
+ year: "numeric",
38
+ month: "2-digit",
39
+ day: "2-digit",
40
+ hour: "2-digit",
41
+ minute: "2-digit",
42
+ second: "2-digit",
43
+ hour12: false,
44
+ hourCycle: "h23",
45
+ });
46
+ formatterCache.set(timeZone, formatter);
47
+ return formatter;
48
+ }
49
+
50
+ function pad(value: number): string {
51
+ return `${value}`.padStart(2, "0");
52
+ }
53
+
54
+ function shiftCalendarDate(parts: Pick<TimeZoneDateParts, "year" | "month" | "day">, days: number) {
55
+ const shifted = new Date(Date.UTC(parts.year, parts.month - 1, parts.day + days));
56
+ return {
57
+ year: shifted.getUTCFullYear(),
58
+ month: shifted.getUTCMonth() + 1,
59
+ day: shifted.getUTCDate(),
60
+ };
61
+ }
62
+
63
+ export function getTimeZoneDateParts(date: Date, timeZone: string): TimeZoneDateParts {
64
+ const values: Partial<Record<keyof TimeZoneDateParts, number>> = {};
65
+
66
+ for (const part of getFormatter(timeZone).formatToParts(date)) {
67
+ if (part.type === "literal") {
68
+ continue;
69
+ }
70
+ if (
71
+ part.type === "year" ||
72
+ part.type === "month" ||
73
+ part.type === "day" ||
74
+ part.type === "hour" ||
75
+ part.type === "minute" ||
76
+ part.type === "second"
77
+ ) {
78
+ values[part.type] = Number(part.value);
79
+ }
80
+ }
81
+
82
+ return {
83
+ year: values.year ?? 0,
84
+ month: values.month ?? 0,
85
+ day: values.day ?? 0,
86
+ hour: values.hour === 24 ? 0 : (values.hour ?? 0),
87
+ minute: values.minute ?? 0,
88
+ second: values.second ?? 0,
89
+ };
90
+ }
91
+
92
+ function getTimeZoneOffsetMs(date: Date, timeZone: string): number {
93
+ const parts = getTimeZoneDateParts(date, timeZone);
94
+ const zonedAsUtc = Date.UTC(
95
+ parts.year,
96
+ parts.month - 1,
97
+ parts.day,
98
+ parts.hour,
99
+ parts.minute,
100
+ parts.second,
101
+ );
102
+ return zonedAsUtc - date.getTime();
103
+ }
104
+
105
+ export function zonedDateTimeToDate(input: TimeZoneDateInput, timeZone: string): Date {
106
+ const localAsUtc = Date.UTC(
107
+ input.year,
108
+ input.month - 1,
109
+ input.day,
110
+ input.hour ?? 0,
111
+ input.minute ?? 0,
112
+ input.second ?? 0,
113
+ );
114
+
115
+ let candidate = localAsUtc;
116
+ for (let index = 0; index < 3; index += 1) {
117
+ const next = localAsUtc - getTimeZoneOffsetMs(new Date(candidate), timeZone);
118
+ if (next === candidate) {
119
+ break;
120
+ }
121
+ candidate = next;
122
+ }
123
+
124
+ return new Date(candidate);
125
+ }
126
+
127
+ export function startOfDayInTimeZone(date: Date, timeZone: string): Date {
128
+ const parts = getTimeZoneDateParts(date, timeZone);
129
+ return zonedDateTimeToDate(
130
+ {
131
+ year: parts.year,
132
+ month: parts.month,
133
+ day: parts.day,
134
+ },
135
+ timeZone,
136
+ );
137
+ }
138
+
139
+ export function addDaysInTimeZone(date: Date, days: number, timeZone: string): Date {
140
+ const parts = getTimeZoneDateParts(date, timeZone);
141
+ const shifted = shiftCalendarDate(parts, days);
142
+ return zonedDateTimeToDate(
143
+ {
144
+ year: shifted.year,
145
+ month: shifted.month,
146
+ day: shifted.day,
147
+ hour: parts.hour,
148
+ minute: parts.minute,
149
+ second: parts.second,
150
+ },
151
+ timeZone,
152
+ );
153
+ }
154
+
155
+ export function buildDateBounds(now: Date, timeZone: string): DateBounds {
156
+ const todayStart = startOfDayInTimeZone(now, timeZone);
157
+ const tomorrowStart = addDaysInTimeZone(todayStart, 1, timeZone);
158
+
159
+ return {
160
+ nowIso: now.toISOString(),
161
+ todayStartIso: todayStart.toISOString(),
162
+ tomorrowStartIso: tomorrowStart.toISOString(),
163
+ dayAfterTomorrowIso: addDaysInTimeZone(todayStart, 2, timeZone).toISOString(),
164
+ nextWeekStartIso: addDaysInTimeZone(todayStart, 7, timeZone).toISOString(),
165
+ };
166
+ }
167
+
168
+ export function formatLocalDateTime(date: Date, timeZone: string): string {
169
+ const parts = getTimeZoneDateParts(date, timeZone);
170
+ return `${parts.year}-${pad(parts.month)}-${pad(parts.day)} ${pad(parts.hour)}:${pad(parts.minute)}`;
171
+ }
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+
3
+ export const TaskScopeSchema = z.enum(["today", "tomorrow", "week", "overdue", "all"]);
4
+ export type TaskScope = z.infer<typeof TaskScopeSchema>;
5
+
6
+ const IsoDateTimeSchema = z.string().datetime({ offset: true });
7
+
8
+ export const TaskChatRequestSchema = z.object({
9
+ user: z.string().min(1),
10
+ text: z.string().min(1),
11
+ now: IsoDateTimeSchema.optional(),
12
+ });
13
+
14
+ export const TaskListQuerySchema = z.object({
15
+ user: z.string().min(1),
16
+ scope: TaskScopeSchema.optional(),
17
+ now: IsoDateTimeSchema.optional(),
18
+ });
19
+
20
+ export const TaskReminderQuerySchema = z.object({
21
+ user: z.string().min(1),
22
+ now: IsoDateTimeSchema.optional(),
23
+ });
24
+
25
+ export const TaskStatsQuerySchema = z.object({
26
+ user: z.string().min(1),
27
+ now: IsoDateTimeSchema.optional(),
28
+ });
29
+
30
+ export type TaskChatRequest = z.infer<typeof TaskChatRequestSchema>;
31
+ export type TaskListQuery = z.infer<typeof TaskListQuerySchema>;
32
+ export type TaskReminderQuery = z.infer<typeof TaskReminderQuerySchema>;
33
+ export type TaskStatsQuery = z.infer<typeof TaskStatsQuerySchema>;
34
+
35
+ export type ConversationStatus = "awaiting_reminder";
36
+ export type TaskStatus = "scheduled" | "done" | "cancelled";
37
+ export type ReminderStatus = "scheduled" | "sent" | "acked" | "cancelled";
38
+
39
+ export type ReminderPreference = {
40
+ offsetMinutes: number | null;
41
+ matchedText: string;
42
+ };
43
+
44
+ export type ParsedTaskDraft =
45
+ | {
46
+ kind: "ready";
47
+ title: string;
48
+ dueAt: Date;
49
+ reminderOffsetMinutes?: number | null;
50
+ }
51
+ | {
52
+ kind: "missing_time";
53
+ title: string;
54
+ }
55
+ | {
56
+ kind: "missing_title";
57
+ };
58
+
59
+ export type ParsedTaskAction =
60
+ | { kind: "done" }
61
+ | { kind: "cancel" }
62
+ | { kind: "snooze"; minutes: number };
63
+
64
+ export type TaskSummary = {
65
+ id: string;
66
+ userId: string;
67
+ title: string;
68
+ sourceText: string;
69
+ dueAt: string;
70
+ dueAtText: string;
71
+ timezone: string;
72
+ status: TaskStatus;
73
+ reminderOffsetMinutes: number | null;
74
+ createdAt: string;
75
+ updatedAt: string;
76
+ completedAt: string | null;
77
+ cancelledAt: string | null;
78
+ };
79
+
80
+ export type ReminderSummary = {
81
+ id: string;
82
+ taskId: string;
83
+ userId: string;
84
+ title: string;
85
+ dueAt: string;
86
+ dueAtText: string;
87
+ remindAt: string;
88
+ remindAtText: string;
89
+ message: string;
90
+ status: ReminderStatus;
91
+ };
92
+
93
+ export type TaskStats = {
94
+ totalTasks: number;
95
+ scheduledTasks: number;
96
+ doneTasks: number;
97
+ cancelledTasks: number;
98
+ overdueTasks: number;
99
+ todayTasks: number;
100
+ todayDoneTasks: number;
101
+ pendingReminders: number;
102
+ };
103
+
104
+ export type ConversationDraft = {
105
+ userId: string;
106
+ status: ConversationStatus;
107
+ sourceText: string;
108
+ title: string;
109
+ dueAt: string;
110
+ reminderOffsetMinutes: number | null;
111
+ createdAt: string;
112
+ updatedAt: string;
113
+ };
114
+
115
+ export type TaskChatResult = {
116
+ reply: string;
117
+ intent:
118
+ | "draft_created"
119
+ | "task_created"
120
+ | "task_query"
121
+ | "task_action"
122
+ | "clarify"
123
+ | "help";
124
+ task?: TaskSummary;
125
+ tasks?: TaskSummary[];
126
+ reminders?: ReminderSummary[];
127
+ stats?: TaskStats;
128
+ };
package/src/types.ts ADDED
@@ -0,0 +1,202 @@
1
+ import { z } from "zod";
2
+
3
+ export const ChatMessageSchema = z
4
+ .object({
5
+ role: z.string().optional(),
6
+ content: z.unknown().optional(),
7
+ name: z.string().optional(),
8
+ })
9
+ .passthrough();
10
+
11
+ export const ToolDefinitionSchema = z
12
+ .object({
13
+ type: z.literal("function"),
14
+ function: z
15
+ .object({
16
+ name: z.string(),
17
+ description: z.string().optional(),
18
+ parameters: z.record(z.string(), z.unknown()).optional(),
19
+ })
20
+ .passthrough(),
21
+ })
22
+ .passthrough();
23
+
24
+ export const NamedToolChoiceSchema = z
25
+ .object({
26
+ type: z.literal("function"),
27
+ function: z.object({ name: z.string() }).passthrough(),
28
+ })
29
+ .passthrough();
30
+
31
+ export const ToolChoiceSchema = z.union([
32
+ z.literal("auto"),
33
+ z.literal("none"),
34
+ z.literal("required"),
35
+ NamedToolChoiceSchema,
36
+ ]);
37
+
38
+ export const ChatCompletionsRequestSchema = z
39
+ .object({
40
+ model: z.string().optional(),
41
+ stream: z.boolean().optional(),
42
+ user: z.string().optional(),
43
+ messages: z.array(ChatMessageSchema).min(1),
44
+ tools: z.array(ToolDefinitionSchema).optional(),
45
+ tool_choice: ToolChoiceSchema.optional(),
46
+ temperature: z.number().optional(),
47
+ max_tokens: z.number().int().positive().optional(),
48
+ })
49
+ .passthrough();
50
+
51
+ export const ResponseInputTextItemSchema = z
52
+ .object({
53
+ type: z.literal("input_text"),
54
+ text: z.string(),
55
+ })
56
+ .passthrough();
57
+
58
+ export const ResponseInputMessageSchema = z
59
+ .object({
60
+ type: z.literal("message").optional(),
61
+ role: z.string().optional(),
62
+ content: z.unknown().optional(),
63
+ text: z.string().optional(),
64
+ })
65
+ .passthrough();
66
+
67
+ export const ResponseInputItemSchema = z.union([
68
+ z.string(),
69
+ ResponseInputTextItemSchema,
70
+ ResponseInputMessageSchema,
71
+ ]);
72
+
73
+ export const ResponsesRequestSchema = z
74
+ .object({
75
+ model: z.string().optional(),
76
+ stream: z.boolean().optional(),
77
+ user: z.string().optional(),
78
+ instructions: z.string().optional(),
79
+ input: z.union([ResponseInputItemSchema, z.array(ResponseInputItemSchema).min(1)]),
80
+ tools: z.array(ToolDefinitionSchema).optional(),
81
+ tool_choice: ToolChoiceSchema.optional(),
82
+ max_output_tokens: z.number().int().positive().optional(),
83
+ })
84
+ .passthrough();
85
+
86
+ export type ChatMessage = z.infer<typeof ChatMessageSchema>;
87
+ export type ToolDefinition = z.infer<typeof ToolDefinitionSchema>;
88
+ export type ToolChoice = z.infer<typeof ToolChoiceSchema>;
89
+ export type ChatCompletionsRequest = z.infer<typeof ChatCompletionsRequestSchema>;
90
+ export type ResponseInputTextItem = z.infer<typeof ResponseInputTextItemSchema>;
91
+ export type ResponseInputMessage = z.infer<typeof ResponseInputMessageSchema>;
92
+ export type ResponsesInputItem = z.infer<typeof ResponseInputItemSchema>;
93
+ export type ResponsesRequest = z.infer<typeof ResponsesRequestSchema>;
94
+
95
+ export const PackManifestSchema = z.object({
96
+ id: z.string(),
97
+ name: z.string(),
98
+ models: z.array(z.string()).min(1),
99
+ fallbackTemplateId: z.string(),
100
+ continueKeywords: z.array(z.string()).default([]),
101
+ });
102
+
103
+ export const TemplateDefinitionSchema = z.object({
104
+ id: z.string(),
105
+ variants: z.array(z.string()).min(1),
106
+ });
107
+
108
+ export const IntentDefinitionSchema = z.object({
109
+ id: z.string(),
110
+ priority: z.number().default(0),
111
+ matchAny: z.array(z.string()).default([]),
112
+ regex: z.array(z.string()).default([]),
113
+ responseTemplateId: z.string().optional(),
114
+ scenarioId: z.string().optional(),
115
+ toolStrategyId: z.string().optional(),
116
+ });
117
+
118
+ export const FaqDefinitionSchema = z.object({
119
+ id: z.string(),
120
+ priority: z.number().default(0),
121
+ questionPatterns: z.array(z.string()).min(1),
122
+ responseTemplateId: z.string(),
123
+ });
124
+
125
+ export const ScenarioNodeDefinitionSchema = z.object({
126
+ id: z.string(),
127
+ responseTemplateId: z.string(),
128
+ nextId: z.string().optional(),
129
+ continueKeywords: z.array(z.string()).default([]),
130
+ end: z.boolean().optional(),
131
+ });
132
+
133
+ export const ScenarioDefinitionSchema = z.object({
134
+ id: z.string(),
135
+ entryIntentId: z.string(),
136
+ nodes: z.array(ScenarioNodeDefinitionSchema).min(1),
137
+ });
138
+
139
+ export const ToolStrategyDefinitionSchema = z.object({
140
+ id: z.string(),
141
+ preferredNames: z.array(z.string()).min(1),
142
+ arguments: z.record(z.string(), z.unknown()).default({}),
143
+ });
144
+
145
+ export type PackManifest = z.infer<typeof PackManifestSchema>;
146
+ export type TemplateDefinition = z.infer<typeof TemplateDefinitionSchema>;
147
+ export type IntentDefinition = z.infer<typeof IntentDefinitionSchema>;
148
+ export type FaqDefinition = z.infer<typeof FaqDefinitionSchema>;
149
+ export type ScenarioNodeDefinition = z.infer<typeof ScenarioNodeDefinitionSchema>;
150
+ export type ScenarioDefinition = z.infer<typeof ScenarioDefinitionSchema>;
151
+ export type ToolStrategyDefinition = z.infer<typeof ToolStrategyDefinitionSchema>;
152
+
153
+ export type LoadedPack = {
154
+ manifest: PackManifest;
155
+ templatesById: Map<string, TemplateDefinition>;
156
+ intents: IntentDefinition[];
157
+ faqs: FaqDefinition[];
158
+ scenariosById: Map<string, ScenarioDefinition>;
159
+ toolStrategiesById: Map<string, ToolStrategyDefinition>;
160
+ };
161
+
162
+ export type NormalizedTurn = {
163
+ sessionId: string;
164
+ locale: "zh-CN" | "en-US" | "mixed";
165
+ model: string;
166
+ stream: boolean;
167
+ systemPrompts: string[];
168
+ history: Array<{ role: string; text: string }>;
169
+ userText: string;
170
+ normalizedUserText: string;
171
+ tools: ToolDefinition[];
172
+ toolChoice?: ToolChoice;
173
+ };
174
+
175
+ export type SessionState = {
176
+ sessionId: string;
177
+ locale: string;
178
+ activeScenarioId?: string;
179
+ activeNodeId?: string;
180
+ lastIntentId?: string;
181
+ lastTemplateId?: string;
182
+ turnCount: number;
183
+ };
184
+
185
+ export type ChatToolCall = {
186
+ id: string;
187
+ type: "function";
188
+ function: {
189
+ name: string;
190
+ arguments: string;
191
+ };
192
+ };
193
+
194
+ export type EngineResult = {
195
+ model: string;
196
+ sessionId: string;
197
+ text: string | null;
198
+ finishReason: "stop" | "tool_calls";
199
+ matchedIntentId?: string;
200
+ templateId?: string;
201
+ toolCalls?: ChatToolCall[];
202
+ };
@@ -0,0 +1,56 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { EngineResult, NormalizedTurn } from "../types.js";
3
+ import type { WeatherMessageInspection } from "./types.js";
4
+ import type { WeatherService } from "./service.js";
5
+
6
+ function firstUserText(turn: NormalizedTurn): string {
7
+ return turn.history.find((message) => message.role === "user")?.text ?? turn.userText;
8
+ }
9
+
10
+ export function resolveWeatherUserId(turn: NormalizedTurn, explicitUser?: string): string {
11
+ if (explicitUser?.trim()) {
12
+ return explicitUser.trim();
13
+ }
14
+
15
+ const seed = `${turn.model}\n${firstUserText(turn)}`;
16
+ const digest = createHash("sha1").update(seed).digest("hex").slice(0, 16);
17
+ return `weather:${digest}`;
18
+ }
19
+
20
+ export function inspectWeatherMessage(params: {
21
+ weatherService: WeatherService;
22
+ turn: NormalizedTurn;
23
+ explicitUser?: string;
24
+ }): { userId: string; inspection: WeatherMessageInspection } {
25
+ const userId = resolveWeatherUserId(params.turn, params.explicitUser);
26
+ const inspection = params.weatherService.inspectMessage({
27
+ userId,
28
+ text: params.turn.userText,
29
+ });
30
+ return { userId, inspection };
31
+ }
32
+
33
+ export async function respondToWeatherMessage(params: {
34
+ weatherService: WeatherService;
35
+ turn: NormalizedTurn;
36
+ explicitUser?: string;
37
+ }): Promise<EngineResult | undefined> {
38
+ const { userId, inspection } = inspectWeatherMessage(params);
39
+ if (!inspection.shouldHandle) {
40
+ return undefined;
41
+ }
42
+
43
+ const weatherResult = await params.weatherService.processMessage({
44
+ userId,
45
+ text: params.turn.userText,
46
+ });
47
+
48
+ return {
49
+ model: params.turn.model,
50
+ sessionId: params.turn.sessionId,
51
+ text: weatherResult.reply,
52
+ finishReason: "stop",
53
+ matchedIntentId: `weather.${weatherResult.intent}`,
54
+ templateId: `weather.${weatherResult.intent}`,
55
+ };
56
+ }