startx 0.9.2 → 0.9.8

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 (65) hide show
  1. package/apps/core-server/package.json +4 -3
  2. package/apps/core-server/src/events/index.ts +19 -19
  3. package/apps/core-server/src/index.ts +1 -3
  4. package/apps/startx-cli/dist/index.mjs +52 -52
  5. package/configs/eslint-config/src/configs/base.ts +26 -11
  6. package/package.json +2 -2
  7. package/packages/{@repo/db → @db/drizzle}/package.json +1 -1
  8. package/packages/@db/drizzle/tsconfig.json +7 -0
  9. package/packages/@db/sqlite/package.json +47 -0
  10. package/packages/@db/sqlite/src/index.ts +2 -0
  11. package/packages/@db/sqlite/src/lib/sqlite-client.ts +146 -0
  12. package/packages/@db/sqlite/src/lib/sqlite-convertor.ts +235 -0
  13. package/packages/@db/sqlite/tsconfig.json +7 -0
  14. package/packages/@repo/env/package.json +2 -1
  15. package/packages/@repo/env/src/utils.ts +7 -6
  16. package/packages/@repo/lib/src/events/i-event.ts +59 -0
  17. package/packages/@repo/lib/src/events/index.ts +1 -0
  18. package/packages/aix/eslint.config.ts +4 -0
  19. package/packages/aix/package.json +53 -0
  20. package/packages/aix/src/aix.ts +519 -0
  21. package/packages/aix/src/index.ts +3 -0
  22. package/packages/aix/src/lib/convertor/schema-convertor.ts +108 -0
  23. package/packages/aix/src/lib/convertor/variable-resolver.ts +161 -0
  24. package/packages/aix/src/lib/tokenizer/index.ts +1 -0
  25. package/packages/aix/src/lib/tokenizer/tokenizer.ts +42 -0
  26. package/packages/aix/src/providers/ai-chat.ts +25 -0
  27. package/packages/aix/src/providers/ai-event.ts +21 -0
  28. package/packages/aix/src/providers/ai-interface.ts +236 -0
  29. package/packages/aix/src/providers/ai-prompt.ts +14 -0
  30. package/packages/aix/src/providers/default-models.ts +471 -0
  31. package/packages/aix/src/providers/index.ts +1 -0
  32. package/packages/aix/src/providers/openai/openai.ts +139 -0
  33. package/packages/aix/src/providers/providers.ts +39 -0
  34. package/packages/aix/src/providers/types.ts +54 -0
  35. package/packages/aix/src/tools/generic/database.ts +290 -0
  36. package/packages/aix/src/tools/generic/forecast.ts +216 -0
  37. package/packages/aix/src/tools/generic/index.ts +4 -0
  38. package/packages/aix/src/tools/generic/planner.ts +114 -0
  39. package/packages/aix/src/tools/generic/summarizer.ts +101 -0
  40. package/packages/aix/src/tools/i-tool.ts +33 -0
  41. package/packages/aix/src/tools/index.ts +2 -0
  42. package/packages/aix/src/tools/system/index.ts +297 -0
  43. package/packages/aix/src/tools/tool-manager.ts +241 -0
  44. package/packages/aix/src/tools/types.ts +109 -0
  45. package/packages/aix/tsconfig.json +7 -0
  46. package/packages/aix/vitest.config.ts +3 -0
  47. package/packages/constants/eslint.config.ts +4 -0
  48. package/packages/{@repo/constants → constants}/package.json +1 -1
  49. package/packages/constants/vitest.config.ts +3 -0
  50. package/pnpm-workspace.yaml +12 -1
  51. package/turbo.json +0 -1
  52. package/packages/@repo/db/tsconfig.json +0 -13
  53. /package/packages/{@repo/db → @db/drizzle}/drizzle.config.ts +0 -0
  54. /package/packages/{@repo/constants → @db/drizzle}/eslint.config.ts +0 -0
  55. /package/packages/{@repo/db → @db/drizzle}/src/functions.ts +0 -0
  56. /package/packages/{@repo/db → @db/drizzle}/src/index.ts +0 -0
  57. /package/packages/{@repo/db → @db/drizzle}/src/schema/common.ts +0 -0
  58. /package/packages/{@repo/db → @db/drizzle}/src/schema/index.ts +0 -0
  59. /package/packages/{@repo/constants → @db/drizzle}/vitest.config.ts +0 -0
  60. /package/packages/{@repo/db → @db/sqlite}/eslint.config.ts +0 -0
  61. /package/packages/{@repo/db → @db/sqlite}/vitest.config.ts +0 -0
  62. /package/packages/{@repo/constants → constants}/src/api.ts +0 -0
  63. /package/packages/{@repo/constants → constants}/src/index.ts +0 -0
  64. /package/packages/{@repo/constants → constants}/src/time.ts +0 -0
  65. /package/packages/{@repo/constants → constants}/tsconfig.json +0 -0
@@ -0,0 +1,290 @@
1
+ import * as vm from "node:vm";
2
+ import pg from "pg";
3
+ import { z } from "zod";
4
+ import { ITool } from "../i-tool.js";
5
+
6
+ /**
7
+ * Pool cache
8
+ */
9
+ const poolCache: Map<string, pg.Pool> = new Map();
10
+
11
+ function getPool(url: string) {
12
+ if (!poolCache.has(url)) {
13
+ poolCache.set(url, new pg.Pool({ connectionString: url }));
14
+ }
15
+ return poolCache.get(url)!;
16
+ }
17
+
18
+ export const DatabaseTools: ITool[] = [
19
+ /**
20
+ * GET TABLE LIST (replaces get_database_list)
21
+ */
22
+ new ITool({
23
+ title: "get_table_list",
24
+ description: "Returns list of tables from the connected Postgres database including schema and table name.",
25
+ schema: z.object({}),
26
+ run: async (_: {}, internal) => {
27
+ try {
28
+ const url = internal?.system?.DATABASE_URL as string | undefined;
29
+ if (!url) {
30
+ return [
31
+ { type: "text", text: "Missing connection URL in internal." },
32
+ {
33
+ type: "resource_link",
34
+ uri: "return",
35
+ name: "Missing connection",
36
+ _meta: { return: { isCompleted: false, isError: true } },
37
+ },
38
+ ];
39
+ }
40
+
41
+ const pool = getPool(url);
42
+
43
+ const result = (await pool.query(`
44
+ SELECT
45
+ table_schema,
46
+ table_name
47
+ FROM information_schema.tables
48
+ WHERE table_type = 'BASE TABLE'
49
+ AND table_schema NOT IN ('pg_catalog', 'information_schema')
50
+ ORDER BY table_schema, table_name;
51
+ `)) as { rows: Array<{ table_schema: string; table_name: string }> };
52
+
53
+ const tables = result.rows.map(row => ({
54
+ schema: row.table_schema,
55
+ table_name: row.table_name,
56
+ }));
57
+
58
+ if (!tables.length) {
59
+ return [
60
+ { type: "text", text: "No tables found." },
61
+ {
62
+ type: "resource_link",
63
+ uri: "return",
64
+ name: "No tables",
65
+ _meta: { return: { isCompleted: true, isError: false } },
66
+ },
67
+ ];
68
+ }
69
+
70
+ return [
71
+ {
72
+ type: "text",
73
+ text: JSON.stringify(tables, null, 2),
74
+ },
75
+ ];
76
+ } catch (err: unknown) {
77
+ const message = err instanceof Error ? err.message : String(err);
78
+
79
+ return [
80
+ { type: "text", text: `Failed to fetch tables: ${message}` },
81
+ {
82
+ type: "resource_link",
83
+ uri: "return",
84
+ name: "Table fetch error",
85
+ _meta: { return: { isCompleted: false, isError: true } },
86
+ },
87
+ ];
88
+ }
89
+ },
90
+ }),
91
+
92
+ /**
93
+ * EXECUTE SQL
94
+ */
95
+ new ITool({
96
+ title: "execute_postgres_sql",
97
+ description: `
98
+ Execute SQL on the connected Postgres database using connectionUrl from internal.
99
+ Supports evaluating JavaScript expressions inside {{...}} blocks to dynamically inject values.
100
+ The variables are accessible via the 'vars' object and are safely parameterized to prevent SQL injection.
101
+
102
+ EXAMPLE 1 (Basic Variable):
103
+ SELECT * FROM users WHERE id = {{vars.USER_ID}};
104
+
105
+ EXAMPLE 2 (Math/Logic Operation):
106
+ SELECT * FROM orders WHERE amount > {{vars.MIN_AMOUNT * 1.5}} AND status = {{(vars.IS_ACTIVE ? 'ACTIVE' : 'INACTIVE')}};
107
+
108
+ EXAMPLE 3 (Array/String Manipulation):
109
+ SELECT * FROM products WHERE category = {{vars.CATEGORIES[0].toLowerCase()}};
110
+
111
+ Mark isCompleted as true if this operation fully resolves the user's request.
112
+ `.trim(),
113
+ schema: z.object({
114
+ sql: z
115
+ .string()
116
+ .describe(
117
+ "The SQL query. Use {{expression}} to evaluate JS and safely inject values via parameterized queries."
118
+ ),
119
+ isCompleted: z
120
+ .boolean()
121
+ .describe(
122
+ "Set to true if this operation fully resolves the user's request (must return formatted Markdown). Set to false if you just need to extract data for your own further reasoning."
123
+ ),
124
+ }),
125
+ run: async (props, internal) => {
126
+ try {
127
+ const url = internal?.system?.DATABASE_URL as string | undefined;
128
+
129
+ if (!url) {
130
+ return [
131
+ { type: "text", text: "Missing connection URL." },
132
+ {
133
+ type: "resource_link",
134
+ uri: "return",
135
+ name: "Missing connection",
136
+ _meta: { return: { isCompleted: true, isError: true } },
137
+ },
138
+ ];
139
+ }
140
+
141
+ const rawSql = String(props.sql || "").trim();
142
+
143
+ if (!rawSql) {
144
+ return [
145
+ { type: "text", text: "Missing SQL query." },
146
+ {
147
+ type: "resource_link",
148
+ uri: "return",
149
+ name: "Missing SQL",
150
+ _meta: { return: { isCompleted: true, isError: true } },
151
+ },
152
+ ];
153
+ }
154
+
155
+ const contextObj: Record<string, unknown> = {};
156
+ for (const [key, value] of internal?.vars?.entries() || []) {
157
+ let parsedData = value.data;
158
+ // Attempt to parse stringified JSON outputs back into objects
159
+ if (typeof parsedData === "string") {
160
+ try {
161
+ parsedData = JSON.parse(parsedData);
162
+ } catch (e) {
163
+ // If it's not valid JSON, leave it as a string
164
+ }
165
+ }
166
+ contextObj[key] = parsedData;
167
+ }
168
+
169
+ const context = vm.createContext({ vars: contextObj });
170
+
171
+ const values: any[] = [];
172
+ let paramIndex = 1;
173
+ let resolvedSql = rawSql;
174
+
175
+ try {
176
+ resolvedSql = rawSql.replace(/\{\{(.+?)\}\}/g, (_match, expression: string) => {
177
+ const script = new vm.Script(expression.trim());
178
+ const evaluatedValue = script.runInContext(context);
179
+
180
+ // Optional: Add safety check for undefined evaluations
181
+ if (evaluatedValue === undefined) {
182
+ throw new Error(`Expression '${expression}' evaluated to undefined.`);
183
+ }
184
+
185
+ values.push(evaluatedValue);
186
+ return `$${paramIndex++}`;
187
+ });
188
+ } catch (evalError: any) {
189
+ return [
190
+ { type: "text", text: `Expression evaluation failed: ${evalError.message}` },
191
+ {
192
+ type: "resource_link",
193
+ uri: "return",
194
+ name: "Evaluation error",
195
+ _meta: { return: { isCompleted: false, isError: true } },
196
+ },
197
+ ];
198
+ }
199
+
200
+ const firstWord = resolvedSql.match(/^\s*(\w+)/i)?.[1]?.toUpperCase() || "";
201
+ const destructiveWords = ["INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "TRUNCATE"];
202
+ const isDestructive = destructiveWords.includes(firstWord);
203
+ const isAdmin = Boolean(internal?.system?.IS_ADMIN ?? true);
204
+
205
+ if (isDestructive && !isAdmin) {
206
+ return [
207
+ {
208
+ type: "text",
209
+ text: `Destructive query (${firstWord}) blocked. Not admin.`,
210
+ },
211
+ {
212
+ type: "resource_link",
213
+ uri: "return",
214
+ name: "Permission denied",
215
+ _meta: { return: { isCompleted: false, isError: true } },
216
+ },
217
+ ];
218
+ }
219
+
220
+ const pool = getPool(url);
221
+ const result = await pool.query({ text: resolvedSql, values });
222
+ const rows = Array.isArray(result.rows) ? result.rows : [];
223
+
224
+ const columns = (result.fields || []).map((f: any) => ({
225
+ name: f.name,
226
+ dataTypeID: f.dataTypeID,
227
+ }));
228
+
229
+ if (props.isCompleted) {
230
+ return [
231
+ {
232
+ type: "text",
233
+ text: JSON.stringify(
234
+ {
235
+ rowCount: result.rowCount,
236
+ returnedRows: rows.length,
237
+ rows,
238
+ columns,
239
+ },
240
+ null,
241
+ 2
242
+ ),
243
+ },
244
+ {
245
+ type: "resource_link",
246
+ uri: "return",
247
+ name: "Execution completed",
248
+ _meta: {
249
+ return: {
250
+ isCompleted: true,
251
+ isError: false,
252
+ },
253
+ },
254
+ },
255
+ ];
256
+ }
257
+
258
+ return [
259
+ {
260
+ type: "text",
261
+ text: JSON.stringify(
262
+ {
263
+ rowCount: result.rowCount,
264
+ returnedRows: rows.length,
265
+ rows,
266
+ columns,
267
+ },
268
+ null,
269
+ 2
270
+ ),
271
+ },
272
+ ];
273
+ } catch (err: unknown) {
274
+ const message = err instanceof Error ? err.message : String(err);
275
+
276
+ return [
277
+ { type: "text", text: `SQL execution failed: ${message}` },
278
+ {
279
+ type: "resource_link",
280
+ uri: "return",
281
+ name: "SQL error",
282
+ _meta: {
283
+ return: { isCompleted: false, isError: true },
284
+ },
285
+ },
286
+ ];
287
+ }
288
+ },
289
+ }),
290
+ ];
@@ -0,0 +1,216 @@
1
+ import axios from "axios";
2
+ import { z } from "zod";
3
+ import { ITool } from "../i-tool.js";
4
+ import type { ToolReturn } from "../types.js";
5
+
6
+ async function findCoordinates(location: string) {
7
+ const geoRes = await axios.get("https://geocoding-api.open-meteo.com/v1/search", {
8
+ params: { name: location, count: 1 },
9
+ });
10
+
11
+ const geoData = geoRes.data;
12
+ if (!geoData.results?.length) {
13
+ return null;
14
+ }
15
+
16
+ const { latitude, longitude } = geoData.results[0];
17
+ return { latitude, longitude };
18
+ }
19
+
20
+ export const WeatherTools: ITool[] = [
21
+ new ITool({
22
+ title: "get_forecast",
23
+ description: "Returns the 1-day weather forecast for a given location.",
24
+ schema: z.object({
25
+ location: z.string().describe("Location name, city, or place."),
26
+ }),
27
+ run: async props => {
28
+ try {
29
+ const coords = await findCoordinates(props.location);
30
+
31
+ if (!coords) {
32
+ return [
33
+ {
34
+ type: "text",
35
+ text: `Could not find location: ${props.location}`,
36
+ },
37
+ {
38
+ type: "resource_link",
39
+ uri: "return",
40
+ name: "Location not found",
41
+ _meta: {
42
+ return: {
43
+ isCompleted: false,
44
+ isError: true,
45
+ },
46
+ },
47
+ },
48
+ ];
49
+ }
50
+
51
+ const weatherRes = await axios.get("https://api.open-meteo.com/v1/forecast", {
52
+ params: {
53
+ latitude: coords.latitude,
54
+ longitude: coords.longitude,
55
+ daily: "weathercode,temperature_2m_max,temperature_2m_min",
56
+ timezone: "auto",
57
+ },
58
+ });
59
+
60
+ const forecast = weatherRes.data.daily;
61
+
62
+ const text = `Forecast for ${props.location}:
63
+ - Max Temp: ${forecast.temperature_2m_max[0]}°C
64
+ - Min Temp: ${forecast.temperature_2m_min[0]}°C
65
+ - Weather Code: ${forecast.weathercode[0]}`;
66
+ const response: ToolReturn = [
67
+ {
68
+ type: "text",
69
+ text,
70
+ },
71
+ ];
72
+
73
+ return response;
74
+ } catch (err: unknown) {
75
+ const message = err instanceof Error ? err.message : String(err);
76
+ return [
77
+ { type: "text", text: `Error fetching forecast: ${message}` },
78
+ {
79
+ type: "resource_link",
80
+ uri: "return",
81
+ name: "Forecast error",
82
+ _meta: {
83
+ return: {
84
+ isCompleted: true,
85
+ isError: true,
86
+ },
87
+ },
88
+ },
89
+ ];
90
+ }
91
+ },
92
+ }),
93
+
94
+ new ITool({
95
+ title: "current_temperature",
96
+ description: "Returns current temperature in Celsius.",
97
+ schema: z.object({
98
+ city: z.string().describe("City name."),
99
+ }),
100
+ run: async ({ city }) => {
101
+ try {
102
+ const coords = await findCoordinates(city);
103
+
104
+ if (!coords) {
105
+ return [
106
+ {
107
+ type: "text",
108
+ text: `Could not find city: ${city}`,
109
+ },
110
+ {
111
+ type: "resource_link",
112
+ uri: "return",
113
+ name: "City not found",
114
+ _meta: {
115
+ return: {
116
+ isCompleted: true,
117
+ isError: true,
118
+ },
119
+ },
120
+ },
121
+ ];
122
+ }
123
+
124
+ const weatherRes = await axios.get("https://api.open-meteo.com/v1/forecast", {
125
+ params: {
126
+ latitude: coords.latitude,
127
+ longitude: coords.longitude,
128
+ current_weather: true,
129
+ timezone: "auto",
130
+ },
131
+ });
132
+
133
+ const temperature = weatherRes.data.current_weather?.temperature;
134
+
135
+ const text = `The current temperature in ${city} is ${temperature}°C.`;
136
+ const response: ToolReturn = [
137
+ {
138
+ type: "text",
139
+ text,
140
+ },
141
+ ];
142
+
143
+ return response;
144
+ } catch (err: unknown) {
145
+ const message = err instanceof Error ? err.message : String(err);
146
+ return [
147
+ { type: "text", text: `Error fetching temperature: ${message}` },
148
+ {
149
+ type: "resource_link",
150
+ uri: "return",
151
+ name: "Temperature error",
152
+ _meta: {
153
+ return: {
154
+ isCompleted: false,
155
+ isError: true,
156
+ },
157
+ },
158
+ },
159
+ ];
160
+ }
161
+ },
162
+ }),
163
+
164
+ new ITool({
165
+ title: "convert_temperature",
166
+ description: "Converts a temperature between Celsius and Fahrenheit.",
167
+ schema: z.object({
168
+ value: z.number().describe("Temperature value."),
169
+ to: z.enum(["celsius", "fahrenheit"]).describe("Target unit."),
170
+ }),
171
+ run: ({ value, to }: { value: number; to: "celsius" | "fahrenheit" }) => {
172
+ try {
173
+ let result: string;
174
+
175
+ if (to === "celsius") {
176
+ const c = ((value - 32) * 5) / 9;
177
+ result = `${value}°F is ${c.toFixed(2)}°C`;
178
+ } else {
179
+ const f = (value * 9) / 5 + 32;
180
+ result = `${value}°C is ${f.toFixed(2)}°F`;
181
+ }
182
+
183
+ return [
184
+ { type: "text", text: result },
185
+ {
186
+ type: "resource_link",
187
+ uri: "return",
188
+ name: "Conversion completed",
189
+ _meta: {
190
+ return: {
191
+ isCompleted: true,
192
+ isError: false,
193
+ },
194
+ },
195
+ },
196
+ ];
197
+ } catch (err: unknown) {
198
+ const message = err instanceof Error ? err.message : String(err);
199
+ return [
200
+ { type: "text", text: `Error converting temperature: ${message}` },
201
+ {
202
+ type: "resource_link",
203
+ uri: "return",
204
+ name: "Conversion error",
205
+ _meta: {
206
+ return: {
207
+ isCompleted: true,
208
+ isError: true,
209
+ },
210
+ },
211
+ },
212
+ ];
213
+ }
214
+ },
215
+ }),
216
+ ];
@@ -0,0 +1,4 @@
1
+ export * from "./forecast.js";
2
+ export * from "./planner.js";
3
+ export * from "./database.js";
4
+ export * from "./summarizer.js";
@@ -0,0 +1,114 @@
1
+ import { z } from "zod";
2
+ import { ITool } from "../i-tool.js";
3
+
4
+ const ExecutionNodeSchema = z.object({
5
+ nodeId: z.string().describe("Unique identifier (e.g., 'fetch_user_data')"),
6
+ instruction: z
7
+ .string()
8
+ .describe(
9
+ "Strict instruction. DO NOT hardcode/mock data. Reference injected dynamic variables (e.g., `vars.xyz`) directly in scripts/queries."
10
+ ),
11
+ schemaOnly: z
12
+ .boolean()
13
+ .default(true)
14
+ .describe("TRUE for large datasets (SQL, APIs). FALSE for final markdown responses or strictly small payloads."),
15
+ tools: z.array(z.string()).describe("List of required tool names"),
16
+ dependsOn: z.array(z.string()).optional().describe("Array of nodeIds that must resolve before execution"),
17
+ });
18
+
19
+ const ExecutionPhaseSchema = z.object({
20
+ phaseId: z.string().describe("Sequential phase identifier (e.g., 'phase_1')"),
21
+ parallelNodes: z.array(ExecutionNodeSchema).describe("Nodes executing concurrently in this phase"),
22
+ });
23
+
24
+ const PlannerSchema = z.object({
25
+ isFeasible: z.boolean(),
26
+ queryClassification: z
27
+ .enum(["direct_answer", "requires_tools", "unclear"])
28
+ .describe(
29
+ "Classify intent. Use 'direct_answer' for abstract/general queries. Use 'requires_tools' for actionable workflows."
30
+ ),
31
+ intentSummary: z.string(),
32
+ directResponse: z
33
+ .string()
34
+ .optional()
35
+ .describe("Populate ONLY if queryClassification is 'direct_answer'. Provides the final answer immediately."),
36
+ executionPhases: z
37
+ .array(ExecutionPhaseSchema)
38
+ .optional()
39
+ .describe("Populate ONLY if queryClassification is 'requires_tools'."),
40
+ abortReason: z
41
+ .string()
42
+ .optional()
43
+ .describe("Populate ONLY if isFeasible is false or queryClassification is 'unclear'."),
44
+ });
45
+
46
+ type PlannerInput = z.infer<typeof PlannerSchema>;
47
+
48
+ export const PlannerTool: ITool[] = [
49
+ new ITool({
50
+ title: "planner",
51
+ description:
52
+ "Evaluates queries and architects execution plans. Can bypass planning for abstract queries to provide direct answers. RULES: 1. Ensure dependent nodes use dynamic variable injection. 2. Prevent data hallucination in dependent nodes.",
53
+ schema: PlannerSchema,
54
+ run: (input: PlannerInput) => {
55
+ try {
56
+ const { isFeasible, queryClassification, intentSummary, directResponse, executionPhases, abortReason } = input;
57
+
58
+ let payload;
59
+ let eventName = "";
60
+ let isError = false;
61
+
62
+ if (!isFeasible || queryClassification === "unclear") {
63
+ payload = { isFeasible, queryClassification, intentSummary, abortReason };
64
+ eventName = "ExecutionAborted";
65
+ isError = true;
66
+ } else if (queryClassification === "direct_answer") {
67
+ payload = { queryClassification, intentSummary, directResponse };
68
+ eventName = "DirectAnswerResolved";
69
+ } else {
70
+ payload = { isFeasible, queryClassification, intentSummary, executionPhases };
71
+ eventName = "PlanArchitected";
72
+ }
73
+
74
+ return [
75
+ {
76
+ type: "text",
77
+ text: JSON.stringify(payload, null, 2),
78
+ },
79
+ {
80
+ type: "resource_link",
81
+ uri: "return",
82
+ name: eventName,
83
+ _meta: {
84
+ return: {
85
+ isCompleted: true,
86
+ isError,
87
+ },
88
+ },
89
+ },
90
+ ];
91
+ } catch (err: unknown) {
92
+ const message = err instanceof Error ? err.message : String(err);
93
+
94
+ return [
95
+ {
96
+ type: "text",
97
+ text: `Error processing plan: ${message}`,
98
+ },
99
+ {
100
+ type: "resource_link",
101
+ uri: "return",
102
+ name: "PlanFailed",
103
+ _meta: {
104
+ return: {
105
+ isCompleted: true,
106
+ isError: true,
107
+ },
108
+ },
109
+ },
110
+ ];
111
+ }
112
+ },
113
+ }),
114
+ ];