assistme 0.2.8 → 0.2.9

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/dist/index.js CHANGED
@@ -1,35 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  JobRunner,
4
- claimTask,
5
- clearConfig,
6
- completeTask,
7
- createSession,
8
- createTask,
9
- emitEvent,
10
- endSession,
11
- failTask,
12
- getConfig,
13
- getConfigPath,
14
- getConversationHistory,
15
- getCurrentUserId,
16
- getOrCreateCliConversation,
17
- getSupabase,
4
+ callMcpHandler,
18
5
  log,
19
- loginWithToken,
20
- logout,
21
6
  newCorrelationId,
22
- pollActionResponse,
23
- pollAndClaimJobRun,
24
- pollAndClaimTask,
25
- resetEventSequence,
26
- setActionRequest,
27
- setConfig,
28
7
  setCorrelationId,
29
- setLogLevel,
30
- setSessionBusy,
31
- updateHeartbeat
32
- } from "./chunk-XY3LGAOY.js";
8
+ setLogLevel
9
+ } from "./chunk-UWE5WVQI.js";
10
+ import {
11
+ clearConfig,
12
+ getConfig,
13
+ getConfigPath,
14
+ setConfig
15
+ } from "./chunk-TTEGHE2E.js";
33
16
 
34
17
  // src/index.ts
35
18
  import { Command } from "commander";
@@ -40,6 +23,217 @@ import { createRequire } from "module";
40
23
  import chalk from "chalk";
41
24
  import ora from "ora";
42
25
  import { createInterface } from "readline";
26
+
27
+ // src/db/supabase.ts
28
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
29
+ import { join } from "path";
30
+ import { homedir } from "os";
31
+ var AUTH_DIR = join(homedir(), ".config", "assistme");
32
+ var AUTH_FILE = join(AUTH_DIR, "auth.json");
33
+ function ensureAuthDir() {
34
+ if (!existsSync(AUTH_DIR)) {
35
+ mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
36
+ }
37
+ }
38
+ function readAuthStore() {
39
+ try {
40
+ if (existsSync(AUTH_FILE)) {
41
+ return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
42
+ }
43
+ } catch {
44
+ }
45
+ return {};
46
+ }
47
+ function writeAuthStore(data) {
48
+ ensureAuthDir();
49
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
50
+ }
51
+ async function loginWithToken(mcpToken) {
52
+ if (!mcpToken.startsWith("am_")) {
53
+ throw new Error(
54
+ "Invalid token format. Use an am_ token from the web page."
55
+ );
56
+ }
57
+ const result = await callMcpHandler(
58
+ "auth.validate_token",
59
+ {},
60
+ mcpToken
61
+ );
62
+ const store = readAuthStore();
63
+ store["mcp_token"] = mcpToken;
64
+ writeAuthStore(store);
65
+ return result.user_id;
66
+ }
67
+ async function getCurrentUserId() {
68
+ const result = await callMcpHandler(
69
+ "auth.validate_token"
70
+ );
71
+ return result.user_id;
72
+ }
73
+ async function logout() {
74
+ try {
75
+ writeAuthStore({});
76
+ } catch {
77
+ }
78
+ }
79
+ async function createSession(_userId, sessionName, workspacePath, version2) {
80
+ const { getConfig: getConfig2 } = await import("./config-PUIS2TQL.js");
81
+ const data = await callMcpHandler("session.create", {
82
+ session_name: sessionName,
83
+ workspace_path: workspacePath,
84
+ version: version2,
85
+ model: getConfig2().model || null
86
+ });
87
+ return data;
88
+ }
89
+ async function updateHeartbeat(sessionId) {
90
+ try {
91
+ await callMcpHandler("session.heartbeat", { session_id: sessionId });
92
+ } catch (err) {
93
+ log.warn(`Heartbeat update failed: ${err instanceof Error ? err.message : err}`);
94
+ }
95
+ }
96
+ async function endSession(sessionId) {
97
+ try {
98
+ await callMcpHandler("session.end", { session_id: sessionId });
99
+ } catch (err) {
100
+ log.error(`Failed to end session: ${err instanceof Error ? err.message : err}`);
101
+ }
102
+ }
103
+ async function setSessionBusy(sessionId, busy) {
104
+ await callMcpHandler("session.set_busy", {
105
+ session_id: sessionId,
106
+ busy
107
+ });
108
+ }
109
+ async function cleanupStaleSessions(currentSessionId, thresholdMs = 12e4) {
110
+ try {
111
+ const result = await callMcpHandler(
112
+ "session.cleanup_stale",
113
+ { current_session_id: currentSessionId, threshold_ms: thresholdMs }
114
+ );
115
+ return result.cleaned;
116
+ } catch {
117
+ return 0;
118
+ }
119
+ }
120
+ async function getActiveSessions(limit = 5) {
121
+ return callMcpHandler("session.get_active", { limit });
122
+ }
123
+ async function getOrCreateCliConversation(_userId, _sessionId) {
124
+ const data = await callMcpHandler("conversation.get_or_create");
125
+ return data;
126
+ }
127
+ async function createTask(conversationId, _userId, sessionId, prompt) {
128
+ const data = await callMcpHandler("task.create", {
129
+ conversation_id: conversationId,
130
+ session_id: sessionId,
131
+ prompt
132
+ });
133
+ return { ...data, prompt };
134
+ }
135
+ async function pollAndClaimTask(sessionId) {
136
+ try {
137
+ const data = await callMcpHandler(
138
+ "task.poll_and_claim",
139
+ { session_id: sessionId }
140
+ );
141
+ if (!data) return null;
142
+ return {
143
+ ...data,
144
+ prompt: data.metadata?.prompt || data.content || ""
145
+ };
146
+ } catch (err) {
147
+ log.warn(`Poll-and-claim failed: ${err instanceof Error ? err.message : err}`);
148
+ return null;
149
+ }
150
+ }
151
+ async function claimTask(messageId) {
152
+ const data = await callMcpHandler("task.claim", {
153
+ message_id: messageId
154
+ });
155
+ return data;
156
+ }
157
+ async function completeTask(messageId, resultSummary, tokenUsage) {
158
+ await callMcpHandler("task.complete", {
159
+ message_id: messageId,
160
+ result: resultSummary,
161
+ token_usage: tokenUsage || null
162
+ });
163
+ }
164
+ async function failTask(messageId, errorMessage) {
165
+ try {
166
+ await callMcpHandler("task.fail", {
167
+ message_id: messageId,
168
+ error: errorMessage
169
+ });
170
+ } catch (err) {
171
+ log.error(`Failed to update task status: ${err instanceof Error ? err.message : err}`);
172
+ }
173
+ }
174
+ async function pollAndClaimJobRun(_userId) {
175
+ try {
176
+ const data = await callMcpHandler(
177
+ "job.claim_pending_run"
178
+ );
179
+ return data;
180
+ } catch (err) {
181
+ log.debug(`Job run poll failed: ${err instanceof Error ? err.message : err}`);
182
+ return null;
183
+ }
184
+ }
185
+ async function getConversationHistory(conversationId, excludeMessageId, limit = 20) {
186
+ try {
187
+ const rows = await callMcpHandler(
188
+ "conversation.get_history",
189
+ {
190
+ conversation_id: conversationId,
191
+ exclude_message_id: excludeMessageId,
192
+ limit
193
+ }
194
+ );
195
+ return (rows || []).reverse().map((row) => {
196
+ const prompt = row.metadata?.prompt || "";
197
+ const content = row.content || "";
198
+ const response = row.status === "failed" ? `[Task failed] ${content}` : content;
199
+ return { prompt, response };
200
+ }).filter((entry) => entry.prompt && entry.response);
201
+ } catch (err) {
202
+ log.debug(`Failed to fetch conversation history: ${err instanceof Error ? err.message : err}`);
203
+ return [];
204
+ }
205
+ }
206
+ var eventSequence = 0;
207
+ function resetEventSequence() {
208
+ eventSequence = 0;
209
+ }
210
+ async function emitEvent(messageId, eventType, eventData) {
211
+ eventSequence++;
212
+ try {
213
+ await callMcpHandler("event.emit", {
214
+ message_id: messageId,
215
+ event_type: eventType,
216
+ event_data: eventData,
217
+ seq: eventSequence
218
+ });
219
+ } catch (err) {
220
+ log.warn(`Failed to emit event: ${err instanceof Error ? err.message : err}`);
221
+ }
222
+ }
223
+ async function setActionRequest(messageId, actionData) {
224
+ await callMcpHandler("action.set_request", {
225
+ message_id: messageId,
226
+ action_data: actionData
227
+ });
228
+ }
229
+ async function pollActionResponse(messageId) {
230
+ return callMcpHandler(
231
+ "action.poll_response",
232
+ { message_id: messageId }
233
+ );
234
+ }
235
+
236
+ // src/commands/auth.ts
43
237
  function registerAuthCommands(program2) {
44
238
  program2.command("login").description("Authenticate with your AssistMe account via browser token").action(async () => {
45
239
  const tokenPageUrl = process.env.ASSISTME_WEB_URL || "https://assistme.co.nz/agent/token";
@@ -164,9 +358,9 @@ import ora2 from "ora";
164
358
  // src/tools/browser.ts
165
359
  import { WebSocket } from "ws";
166
360
  import { execSync, spawn } from "child_process";
167
- import { platform, homedir } from "os";
168
- import { existsSync, unlinkSync, mkdirSync, cpSync } from "fs";
169
- import { join } from "path";
361
+ import { platform, homedir as homedir2 } from "os";
362
+ import { existsSync as existsSync2, unlinkSync, mkdirSync as mkdirSync2, cpSync } from "fs";
363
+ import { join as join2 } from "path";
170
364
  var BrowserController = class {
171
365
  ws = null;
172
366
  debugPort;
@@ -701,7 +895,7 @@ function findChromePath() {
701
895
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
702
896
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
703
897
  ];
704
- return paths.find((p) => existsSync(p)) ?? null;
898
+ return paths.find((p) => existsSync2(p)) ?? null;
705
899
  }
706
900
  if (os === "linux") {
707
901
  const names = [
@@ -738,7 +932,7 @@ function findChromePath() {
738
932
  for (const prefix of prefixes) {
739
933
  for (const sub of subPaths) {
740
934
  const p = `${prefix}\\${sub}`;
741
- if (existsSync(p)) return p;
935
+ if (existsSync2(p)) return p;
742
936
  }
743
937
  }
744
938
  return null;
@@ -746,39 +940,39 @@ function findChromePath() {
746
940
  return null;
747
941
  }
748
942
  function getDefaultProfileDir(chromePath) {
749
- const home = homedir();
943
+ const home = homedir2();
750
944
  const os = platform();
751
945
  if (os === "darwin") {
752
946
  if (chromePath.includes("Brave Browser"))
753
- return join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser");
947
+ return join2(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser");
754
948
  if (chromePath.includes("Microsoft Edge"))
755
- return join(home, "Library", "Application Support", "Microsoft Edge");
949
+ return join2(home, "Library", "Application Support", "Microsoft Edge");
756
950
  if (chromePath.includes("Chromium"))
757
- return join(home, "Library", "Application Support", "Chromium");
951
+ return join2(home, "Library", "Application Support", "Chromium");
758
952
  if (chromePath.includes("Canary"))
759
- return join(home, "Library", "Application Support", "Google", "Chrome Canary");
760
- return join(home, "Library", "Application Support", "Google", "Chrome");
953
+ return join2(home, "Library", "Application Support", "Google", "Chrome Canary");
954
+ return join2(home, "Library", "Application Support", "Google", "Chrome");
761
955
  }
762
956
  if (os === "win32") {
763
- const appData = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
957
+ const appData = process.env.LOCALAPPDATA || join2(home, "AppData", "Local");
764
958
  if (chromePath.includes("brave"))
765
- return join(appData, "BraveSoftware", "Brave-Browser", "User Data");
766
- if (chromePath.includes("msedge")) return join(appData, "Microsoft", "Edge", "User Data");
767
- return join(appData, "Google", "Chrome", "User Data");
768
- }
769
- if (chromePath.includes("brave")) return join(home, ".config", "BraveSoftware", "Brave-Browser");
770
- if (chromePath.includes("microsoft-edge")) return join(home, ".config", "microsoft-edge");
771
- if (chromePath.includes("chromium")) return join(home, ".config", "chromium");
772
- return join(home, ".config", "google-chrome");
959
+ return join2(appData, "BraveSoftware", "Brave-Browser", "User Data");
960
+ if (chromePath.includes("msedge")) return join2(appData, "Microsoft", "Edge", "User Data");
961
+ return join2(appData, "Google", "Chrome", "User Data");
962
+ }
963
+ if (chromePath.includes("brave")) return join2(home, ".config", "BraveSoftware", "Brave-Browser");
964
+ if (chromePath.includes("microsoft-edge")) return join2(home, ".config", "microsoft-edge");
965
+ if (chromePath.includes("chromium")) return join2(home, ".config", "chromium");
966
+ return join2(home, ".config", "google-chrome");
773
967
  }
774
968
  function getDebugProfileDir(chromePath) {
775
- const home = homedir();
776
- const debugDir = join(home, ".assistme", "browser-profile");
777
- if (!existsSync(debugDir)) {
778
- mkdirSync(debugDir, { recursive: true });
969
+ const home = homedir2();
970
+ const debugDir = join2(home, ".assistme", "browser-profile");
971
+ if (!existsSync2(debugDir)) {
972
+ mkdirSync2(debugDir, { recursive: true });
779
973
  log.debug(`Created debug profile directory: ${debugDir}`);
780
974
  const realDir = getDefaultProfileDir(chromePath);
781
- if (existsSync(realDir)) {
975
+ if (existsSync2(realDir)) {
782
976
  seedDebugProfile(realDir, debugDir);
783
977
  }
784
978
  }
@@ -788,35 +982,35 @@ function seedDebugProfile(realDir, debugDir) {
788
982
  const rootFiles = ["Local State"];
789
983
  const profileFiles = ["Bookmarks", "Preferences", "Favicons", "Top Sites", "Shortcuts"];
790
984
  for (const file of rootFiles) {
791
- const src = join(realDir, file);
792
- const dest = join(debugDir, file);
985
+ const src = join2(realDir, file);
986
+ const dest = join2(debugDir, file);
793
987
  try {
794
- if (existsSync(src)) {
988
+ if (existsSync2(src)) {
795
989
  cpSync(src, dest, { force: true });
796
990
  log.debug(`Seeded: ${file}`);
797
991
  }
798
992
  } catch {
799
993
  }
800
994
  }
801
- const srcProfile = join(realDir, "Default");
802
- const destProfile = join(debugDir, "Default");
803
- if (existsSync(srcProfile)) {
804
- mkdirSync(destProfile, { recursive: true });
995
+ const srcProfile = join2(realDir, "Default");
996
+ const destProfile = join2(debugDir, "Default");
997
+ if (existsSync2(srcProfile)) {
998
+ mkdirSync2(destProfile, { recursive: true });
805
999
  for (const file of profileFiles) {
806
- const src = join(srcProfile, file);
807
- const dest = join(destProfile, file);
1000
+ const src = join2(srcProfile, file);
1001
+ const dest = join2(destProfile, file);
808
1002
  try {
809
- if (existsSync(src)) {
1003
+ if (existsSync2(src)) {
810
1004
  cpSync(src, dest, { force: true });
811
1005
  log.debug(`Seeded: Default/${file}`);
812
1006
  }
813
1007
  } catch {
814
1008
  }
815
1009
  }
816
- const srcExt = join(srcProfile, "Extensions");
817
- const destExt = join(destProfile, "Extensions");
1010
+ const srcExt = join2(srcProfile, "Extensions");
1011
+ const destExt = join2(destProfile, "Extensions");
818
1012
  try {
819
- if (existsSync(srcExt)) {
1013
+ if (existsSync2(srcExt)) {
820
1014
  cpSync(srcExt, destExt, { recursive: true, force: true });
821
1015
  log.debug("Seeded: Default/Extensions");
822
1016
  }
@@ -907,14 +1101,14 @@ async function ensureBrowserAvailable(port = 9222) {
907
1101
  return { success: true, action: "launched", chromePath };
908
1102
  }
909
1103
  const debugDir = getDebugProfileDir(chromePath);
910
- const lockPath = join(debugDir, "SingletonLock");
911
- if (existsSync(lockPath)) {
1104
+ const lockPath = join2(debugDir, "SingletonLock");
1105
+ if (existsSync2(lockPath)) {
912
1106
  log.debug("Found stale SingletonLock in debug profile \u2014 removing and retrying");
913
1107
  try {
914
1108
  unlinkSync(lockPath);
915
1109
  for (const f of ["SingletonSocket", "SingletonCookie"]) {
916
1110
  try {
917
- unlinkSync(join(debugDir, f));
1111
+ unlinkSync(join2(debugDir, f));
918
1112
  } catch {
919
1113
  }
920
1114
  }
@@ -1124,13 +1318,14 @@ var Scheduler = class {
1124
1318
  }
1125
1319
  async initializeNextRuns() {
1126
1320
  try {
1127
- const userId = await getCurrentUserId();
1128
- const sb = getSupabase();
1129
- const { data } = await sb.from("agent_scheduled_tasks").select("*").eq("user_id", userId).eq("enabled", true).is("next_run_at", null);
1321
+ const data = await callMcpHandler("schedule.get_uninitialized");
1130
1322
  if (data) {
1131
1323
  for (const task of data) {
1132
1324
  const nextRun = getNextRunTime(task.cron_expression, task.timezone);
1133
- await sb.from("agent_scheduled_tasks").update({ next_run_at: nextRun.toISOString() }).eq("id", task.id);
1325
+ await callMcpHandler("schedule.update", {
1326
+ task_id: task.id,
1327
+ next_run_at: nextRun.toISOString()
1328
+ });
1134
1329
  }
1135
1330
  }
1136
1331
  } catch (err) {
@@ -1140,24 +1335,29 @@ var Scheduler = class {
1140
1335
  async checkDueTasks() {
1141
1336
  if (!this.running || !this.onScheduledTask) return;
1142
1337
  try {
1143
- const userId = await getCurrentUserId();
1144
- const sb = getSupabase();
1145
- const { data: dueTasks } = await sb.from("agent_scheduled_tasks").select("*").eq("user_id", userId).eq("enabled", true).lte("next_run_at", (/* @__PURE__ */ new Date()).toISOString()).order("next_run_at", { ascending: true }).limit(1);
1338
+ const dueTasks = await callMcpHandler("schedule.check_due");
1146
1339
  if (!dueTasks || dueTasks.length === 0) return;
1147
1340
  const task = dueTasks[0];
1148
1341
  log.info(`Scheduled task due: "${task.name}"`);
1149
1342
  const nextRun = getNextRunTime(task.cron_expression, task.timezone);
1150
- await sb.from("agent_scheduled_tasks").update({
1343
+ await callMcpHandler("schedule.update", {
1344
+ task_id: task.id,
1151
1345
  last_run_at: (/* @__PURE__ */ new Date()).toISOString(),
1152
1346
  next_run_at: nextRun.toISOString(),
1153
1347
  run_count: task.run_count + 1
1154
- }).eq("id", task.id);
1348
+ });
1155
1349
  try {
1156
1350
  await this.onScheduledTask(task);
1157
- await sb.from("agent_scheduled_tasks").update({ last_error: null }).eq("id", task.id);
1351
+ await callMcpHandler("schedule.update", {
1352
+ task_id: task.id,
1353
+ last_error: null
1354
+ });
1158
1355
  } catch (err) {
1159
1356
  const errMsg = err instanceof Error ? err.message : String(err);
1160
- await sb.from("agent_scheduled_tasks").update({ last_error: errMsg }).eq("id", task.id);
1357
+ await callMcpHandler("schedule.update", {
1358
+ task_id: task.id,
1359
+ last_error: errMsg
1360
+ });
1161
1361
  log.error(`Scheduled task "${task.name}" failed: ${errMsg}`);
1162
1362
  }
1163
1363
  } catch (err) {
@@ -1165,50 +1365,41 @@ var Scheduler = class {
1165
1365
  }
1166
1366
  }
1167
1367
  };
1168
- async function createScheduledTask(userId, name, prompt, cronExpression, timezone = "UTC") {
1169
- const sb = getSupabase();
1368
+ async function createScheduledTask(_userId, name, prompt, cronExpression, timezone = "UTC") {
1170
1369
  const nextRun = getNextRunTime(cronExpression, timezone);
1171
- const { data, error } = await sb.from("agent_scheduled_tasks").insert({
1172
- user_id: userId,
1370
+ return callMcpHandler("schedule.create", {
1173
1371
  name,
1174
1372
  prompt,
1175
1373
  cron_expression: cronExpression,
1176
1374
  timezone,
1177
1375
  next_run_at: nextRun.toISOString()
1178
- }).select().single();
1179
- if (error) throw new Error(`Failed to create schedule: ${error.message}`);
1180
- return data;
1376
+ });
1181
1377
  }
1182
- async function listScheduledTasks(userId) {
1183
- const sb = getSupabase();
1184
- const { data, error } = await sb.from("agent_scheduled_tasks").select("*").eq("user_id", userId).order("created_at", { ascending: false });
1185
- if (error) throw new Error(`Failed to list schedules: ${error.message}`);
1186
- return data || [];
1378
+ async function listScheduledTasks(_userId) {
1379
+ return callMcpHandler("schedule.list");
1187
1380
  }
1188
1381
  async function toggleScheduledTask(taskId, enabled) {
1189
- const sb = getSupabase();
1190
- const update = { enabled };
1382
+ const params = { task_id: taskId, enabled };
1191
1383
  if (enabled) {
1192
- const { data } = await sb.from("agent_scheduled_tasks").select("cron_expression, timezone").eq("id", taskId).single();
1193
- if (data) {
1194
- const nextRun = getNextRunTime(data.cron_expression, data.timezone);
1195
- update.next_run_at = nextRun.toISOString();
1384
+ const taskData = await callMcpHandler(
1385
+ "schedule.get_task",
1386
+ { task_id: taskId }
1387
+ );
1388
+ if (taskData) {
1389
+ const nextRun = getNextRunTime(taskData.cron_expression, taskData.timezone);
1390
+ params.next_run_at = nextRun.toISOString();
1196
1391
  }
1197
1392
  }
1198
- const { error } = await sb.from("agent_scheduled_tasks").update(update).eq("id", taskId);
1199
- if (error) throw new Error(`Failed to toggle schedule: ${error.message}`);
1393
+ await callMcpHandler("schedule.toggle", params);
1200
1394
  }
1201
1395
  async function deleteScheduledTask(taskId) {
1202
- const sb = getSupabase();
1203
- const { error } = await sb.from("agent_scheduled_tasks").delete().eq("id", taskId);
1204
- if (error) throw new Error(`Failed to delete schedule: ${error.message}`);
1396
+ await callMcpHandler("schedule.delete", { task_id: taskId });
1205
1397
  }
1206
1398
 
1207
1399
  // src/agent/session.ts
1208
1400
  var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
1209
1401
  var DEFAULT_POLL_INTERVAL = 2e3;
1210
1402
  var MAX_POLL_INTERVAL = 3e4;
1211
- var STALE_SESSION_THRESHOLD = 12e4;
1212
1403
  var SessionManager = class {
1213
1404
  session = null;
1214
1405
  heartbeatTimer = null;
@@ -1261,9 +1452,6 @@ var SessionManager = class {
1261
1452
  });
1262
1453
  return this.session;
1263
1454
  }
1264
- /**
1265
- * Schedule the next poll with exponential backoff on failures.
1266
- */
1267
1455
  schedulePoll() {
1268
1456
  if (!this.running) return;
1269
1457
  const delay = this.consecutivePollFailures === 0 ? this.basePollInterval : Math.min(
@@ -1283,10 +1471,6 @@ var SessionManager = class {
1283
1471
  log.error(`Scheduled task error: ${err}`);
1284
1472
  }
1285
1473
  }
1286
- /**
1287
- * Execute a pending job run triggered from the web UI.
1288
- * Loads the job, builds the agentic prompt, and processes it as a chat task.
1289
- */
1290
1474
  async executeJobRun(jobRun) {
1291
1475
  if (!this.session || !this.userId || !this.conversationId || !this.onTask)
1292
1476
  return;
@@ -1312,8 +1496,10 @@ var SessionManager = class {
1312
1496
  return;
1313
1497
  }
1314
1498
  try {
1315
- const sb = getSupabase();
1316
- await sb.from("agent_job_runs").update({ session_id: this.session.id }).eq("id", jobRun.id);
1499
+ await callMcpHandler("job.link_run_session", {
1500
+ run_id: jobRun.id,
1501
+ session_id: this.session.id
1502
+ });
1317
1503
  } catch (linkErr) {
1318
1504
  log.debug(`Failed to link session to job run: ${linkErr}`);
1319
1505
  }
@@ -1389,37 +1575,17 @@ var SessionManager = class {
1389
1575
  }
1390
1576
  this.schedulePoll();
1391
1577
  }
1392
- /**
1393
- * Mark sessions as offline if they haven't sent a heartbeat recently.
1394
- * Runs once on startup to clean up orphaned sessions from crashed processes.
1395
- */
1396
1578
  async cleanupStaleSessions() {
1397
- if (!this.userId) return;
1579
+ if (!this.userId || !this.session) return;
1398
1580
  try {
1399
- const sb = getSupabase();
1400
- const threshold = new Date(
1401
- Date.now() - STALE_SESSION_THRESHOLD
1402
- ).toISOString();
1403
- const { data: stale } = await sb.from("agent_sessions").select("id").eq("user_id", this.userId).in("status", ["online", "busy"]).lt("last_heartbeat_at", threshold).neq("id", this.session?.id || "");
1404
- if (stale && stale.length > 0) {
1405
- for (const s of stale) {
1406
- await sb.from("agent_sessions").update({
1407
- status: "offline",
1408
- ended_at: (/* @__PURE__ */ new Date()).toISOString(),
1409
- metadata: { ended_reason: "stale_session_cleanup" }
1410
- }).eq("id", s.id);
1411
- }
1412
- log.info(`Cleaned up ${stale.length} stale session(s)`);
1581
+ const cleaned = await cleanupStaleSessions(this.session.id);
1582
+ if (cleaned > 0) {
1583
+ log.info(`Cleaned up ${cleaned} stale session(s)`);
1413
1584
  }
1414
1585
  } catch (err) {
1415
1586
  log.debug(`Stale session cleanup error: ${err}`);
1416
1587
  }
1417
1588
  }
1418
- /**
1419
- * Submit a task from the interactive prompt or scheduled job.
1420
- * Sets processing=true BEFORE creating the task so the poll loop
1421
- * never races to pick it up.
1422
- */
1423
1589
  async submitTask(prompt) {
1424
1590
  if (!this.session || !this.userId || !this.conversationId || !this.onTask) {
1425
1591
  throw new Error("Session not started");
@@ -1445,9 +1611,6 @@ var SessionManager = class {
1445
1611
  }
1446
1612
  }
1447
1613
  }
1448
- /**
1449
- * Stop the session with a safety timeout to prevent hanging on shutdown.
1450
- */
1451
1614
  async stop(timeoutMs = 5e3) {
1452
1615
  this.running = false;
1453
1616
  this.scheduler.stop();
@@ -1493,29 +1656,24 @@ import {
1493
1656
 
1494
1657
  // src/agent/memory.ts
1495
1658
  var MemoryManager = class {
1496
- userId;
1497
- constructor(userId) {
1498
- this.userId = userId;
1659
+ constructor(_userId) {
1499
1660
  }
1500
1661
  /**
1501
1662
  * Store a new memory. Called by the agent after completing tasks
1502
1663
  * to remember important facts about the user.
1503
1664
  */
1504
1665
  async remember(content, category = "general", options) {
1505
- const sb = getSupabase();
1506
1666
  const expiresAt = options?.expiresInDays ? new Date(
1507
1667
  Date.now() + options.expiresInDays * 864e5
1508
1668
  ).toISOString() : null;
1509
- const { data, error } = await sb.from("agent_memories").insert({
1510
- user_id: this.userId,
1669
+ const data = await callMcpHandler("memory.store", {
1511
1670
  category,
1512
1671
  content,
1513
1672
  importance: options?.importance ?? 5,
1514
1673
  tags: options?.tags ?? [],
1515
1674
  source_message_id: options?.sourceMessageId ?? null,
1516
1675
  expires_at: expiresAt
1517
- }).select().single();
1518
- if (error) throw new Error(`Failed to store memory: ${error.message}`);
1676
+ });
1519
1677
  log.debug(`Memory stored: [${category}] ${content.slice(0, 80)}...`);
1520
1678
  return data;
1521
1679
  }
@@ -1523,27 +1681,15 @@ var MemoryManager = class {
1523
1681
  * Search memories by query text. Uses ILIKE + tag containment.
1524
1682
  */
1525
1683
  async search(query2, limit = 10) {
1526
- const sb = getSupabase();
1527
- const sanitized = query2.replace(/[%_]/g, "\\$&");
1528
- const { data, error } = await sb.from("agent_memories").select("*").eq("user_id", this.userId).or(
1529
- `content.ilike.%${sanitized}%,tags.cs.{${sanitized}}`
1530
- ).order("importance", { ascending: false }).limit(limit);
1531
- if (error) {
1532
- log.warn(`Memory search failed: ${error.message}`);
1684
+ try {
1685
+ return await callMcpHandler("memory.search", {
1686
+ query: query2,
1687
+ limit
1688
+ });
1689
+ } catch (err) {
1690
+ log.warn(`Memory search failed: ${err instanceof Error ? err.message : err}`);
1533
1691
  return [];
1534
1692
  }
1535
- if (data && data.length > 0) {
1536
- const now = (/* @__PURE__ */ new Date()).toISOString();
1537
- await Promise.all(
1538
- data.map(
1539
- (m) => sb.from("agent_memories").update({
1540
- access_count: m.access_count + 1,
1541
- last_accessed_at: now
1542
- }).eq("id", m.id)
1543
- )
1544
- );
1545
- }
1546
- return data || [];
1547
1693
  }
1548
1694
  /**
1549
1695
  * Get the most important/recent memories to include in context.
@@ -1551,18 +1697,11 @@ var MemoryManager = class {
1551
1697
  * Automatically filters out expired memories.
1552
1698
  */
1553
1699
  async getContext(maxItems = 20) {
1554
- const sb = getSupabase();
1555
- const now = (/* @__PURE__ */ new Date()).toISOString();
1556
- const { data: instructions } = await sb.from("agent_memories").select("*").eq("user_id", this.userId).eq("category", "instruction").or(`expires_at.is.null,expires_at.gt.${now}`).order("importance", { ascending: false }).limit(5);
1557
- const { data: preferences } = await sb.from("agent_memories").select("*").eq("user_id", this.userId).eq("category", "preference").or(`expires_at.is.null,expires_at.gt.${now}`).order("importance", { ascending: false }).limit(5);
1558
- const { data: general } = await sb.from("agent_memories").select("*").eq("user_id", this.userId).not("category", "in", '("instruction","preference")').or(`expires_at.is.null,expires_at.gt.${now}`).order("importance", { ascending: false }).order("updated_at", { ascending: false }).limit(maxItems - 10);
1559
- const all = [
1560
- ...instructions || [],
1561
- ...preferences || [],
1562
- ...general || []
1563
- ];
1700
+ const all = await callMcpHandler("memory.get_context", {
1701
+ max_items: maxItems
1702
+ });
1564
1703
  const seen = /* @__PURE__ */ new Set();
1565
- return all.filter((m) => {
1704
+ return (all || []).filter((m) => {
1566
1705
  if (seen.has(m.id)) return false;
1567
1706
  seen.add(m.id);
1568
1707
  return true;
@@ -1599,32 +1738,23 @@ var MemoryManager = class {
1599
1738
  }
1600
1739
  // ── CRUD for CLI ────────────────────────────────────────────
1601
1740
  async list(category, limit = 20) {
1602
- const sb = getSupabase();
1603
- let query2 = sb.from("agent_memories").select("*").eq("user_id", this.userId).order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
1604
- if (category) {
1605
- query2 = query2.eq("category", category);
1606
- }
1607
- const { data, error } = await query2;
1608
- if (error) throw new Error(`Failed to list memories: ${error.message}`);
1741
+ const data = await callMcpHandler("memory.list", {
1742
+ category: category || null,
1743
+ limit
1744
+ });
1609
1745
  return data || [];
1610
1746
  }
1611
1747
  async add(content, category = "general", importance = 5, tags = []) {
1612
1748
  return this.remember(content, category, { importance, tags });
1613
1749
  }
1614
1750
  async remove(memoryId) {
1615
- const sb = getSupabase();
1616
- const { error } = await sb.from("agent_memories").delete().eq("id", memoryId).eq("user_id", this.userId);
1617
- if (error) throw new Error(`Failed to delete memory: ${error.message}`);
1751
+ await callMcpHandler("memory.remove", { memory_id: memoryId });
1618
1752
  }
1619
1753
  async clear(category) {
1620
- const sb = getSupabase();
1621
- let query2 = sb.from("agent_memories").delete().eq("user_id", this.userId);
1622
- if (category) {
1623
- query2 = query2.eq("category", category);
1624
- }
1625
- const { error, count } = await query2.select("id");
1626
- if (error) throw new Error(`Failed to clear memories: ${error.message}`);
1627
- return count || 0;
1754
+ const result = await callMcpHandler("memory.clear", {
1755
+ category: category || null
1756
+ });
1757
+ return result.count;
1628
1758
  }
1629
1759
  };
1630
1760
 
@@ -1772,31 +1902,16 @@ var SkillManager = class {
1772
1902
  skills = /* @__PURE__ */ new Map();
1773
1903
  idfCache = /* @__PURE__ */ new Map();
1774
1904
  userId = null;
1775
- /** Budget for skill descriptions in system prompt (characters). */
1776
1905
  DESCRIPTION_BUDGET_CHARS = 16e3;
1777
- /**
1778
- * Set the user ID for DB operations.
1779
- * Called after authentication; enables DB-backed skill storage.
1780
- */
1781
1906
  setUserId(userId) {
1782
1907
  this.userId = userId;
1783
1908
  }
1784
- /**
1785
- * Load skills from DB (user's agent_skills collection).
1786
- * This is the only loading mechanism — no file-based loading.
1787
- */
1788
1909
  async loadFromDb() {
1789
1910
  if (!this.userId) return;
1790
1911
  try {
1791
- const sb = getSupabase();
1792
- const { data, error } = await sb.from("agent_skills").select("*").eq("user_id", this.userId).eq("is_active", true);
1793
- if (error) {
1794
- log.debug(`DB skill load failed: ${error.message}`);
1795
- return;
1796
- }
1912
+ const data = await callMcpHandler("skill.load");
1797
1913
  this.skills.clear();
1798
- const dbRows = data || [];
1799
- for (const row of dbRows) {
1914
+ for (const row of data || []) {
1800
1915
  const skill = this.rowToSkill(row);
1801
1916
  this.skills.set(skill.name, skill);
1802
1917
  }
@@ -1808,15 +1923,7 @@ var SkillManager = class {
1808
1923
  log.debug(`DB skill load error: ${err}`);
1809
1924
  }
1810
1925
  }
1811
- /**
1812
- * Convert a DB row to a Skill object.
1813
- */
1814
1926
  rowToSkill(row) {
1815
- const meta = row.metadata || {};
1816
- const rawVars = row.variables || meta.variables;
1817
- const variables = Array.isArray(rawVars) ? rawVars : void 0;
1818
- const rawConfig = row.config;
1819
- const config = rawConfig && typeof rawConfig === "object" ? rawConfig : void 0;
1820
1927
  return {
1821
1928
  name: row.name,
1822
1929
  description: row.description || "",
@@ -1832,15 +1939,9 @@ var SkillManager = class {
1832
1939
  filePath: "",
1833
1940
  source: row.source || "manual",
1834
1941
  dbId: row.id,
1835
- sourceSkillId: row.source_skill_id || void 0,
1836
- variables,
1837
- config
1942
+ sourceSkillId: row.source_skill_id || void 0
1838
1943
  };
1839
1944
  }
1840
- /**
1841
- * Pre-compute IDF (Inverse Document Frequency) for all tokens across skills.
1842
- * Called once after loadFromDb(), avoids recomputing on every findRelevant() call.
1843
- */
1844
1945
  buildIdfCache() {
1845
1946
  this.idfCache.clear();
1846
1947
  const docFreq = /* @__PURE__ */ new Map();
@@ -1856,22 +1957,12 @@ var SkillManager = class {
1856
1957
  this.idfCache.set(word, Math.log(totalSkills / df) + 1);
1857
1958
  }
1858
1959
  }
1859
- /**
1860
- * Get all loaded skills (from user's agent_skills collection).
1861
- */
1862
1960
  getAll() {
1863
1961
  return Array.from(this.skills.values());
1864
1962
  }
1865
- /**
1866
- * Get a skill by name.
1867
- */
1868
1963
  get(name) {
1869
1964
  return this.skills.get(name);
1870
1965
  }
1871
- /**
1872
- * Find skills relevant to a given task prompt.
1873
- * Uses a TF-IDF-inspired scoring approach with pre-computed IDF cache.
1874
- */
1875
1966
  findRelevant(prompt, maxResults = 3) {
1876
1967
  const lower = prompt.toLowerCase();
1877
1968
  const promptTokens = tokenize(lower);
@@ -1883,37 +1974,25 @@ var SkillManager = class {
1883
1974
  let score = 0;
1884
1975
  if (lower.includes(skill.name.toLowerCase())) score += 10;
1885
1976
  for (const kw of skill.keywords) {
1886
- if (lower.includes(kw.toLowerCase())) {
1887
- score += 8;
1888
- }
1977
+ if (lower.includes(kw.toLowerCase())) score += 8;
1889
1978
  }
1890
1979
  const descTokens = tokenize(skill.description.toLowerCase());
1891
1980
  for (const word of descTokens) {
1892
- if (promptTokenSet.has(word)) {
1893
- score += 3 * idf(word);
1894
- }
1981
+ if (promptTokenSet.has(word)) score += 3 * idf(word);
1895
1982
  }
1896
1983
  const contentTokens = tokenize(skill.content.toLowerCase());
1897
1984
  for (const word of contentTokens) {
1898
- if (promptTokenSet.has(word)) {
1899
- score += 0.5 * idf(word);
1900
- }
1985
+ if (promptTokenSet.has(word)) score += 0.5 * idf(word);
1901
1986
  }
1902
1987
  const promptBigrams = bigrams(promptTokens);
1903
1988
  const descBigrams = bigrams(descTokens);
1904
1989
  for (const bg of descBigrams) {
1905
1990
  if (promptBigrams.has(bg)) score += 5;
1906
1991
  }
1907
- if (score > 0) {
1908
- scored.push({ skill, score });
1909
- }
1992
+ if (score > 0) scored.push({ skill, score });
1910
1993
  }
1911
1994
  return scored.sort((a, b) => b.score - a.score).slice(0, maxResults).map((s) => s.skill);
1912
1995
  }
1913
- /**
1914
- * Build a lightweight prompt section with skill descriptions only.
1915
- * Full skill content is loaded on-demand via the skill_invoke tool.
1916
- */
1917
1996
  buildSkillDescriptions() {
1918
1997
  const skills = this.getAll().filter((s) => !s.disableModelInvocation).sort((a, b) => {
1919
1998
  if (a.metadata.always && !b.metadata.always) return -1;
@@ -1942,9 +2021,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
1942
2021
  }
1943
2022
  return prompt;
1944
2023
  }
1945
- /**
1946
- * @deprecated Use buildSkillDescriptions() + skill_invoke tool instead.
1947
- */
2024
+ /** @deprecated Use buildSkillDescriptions() + skill_invoke tool instead. */
1948
2025
  buildSkillPrompt(taskPrompt) {
1949
2026
  const relevant = this.findRelevant(taskPrompt);
1950
2027
  if (relevant.length === 0) return "";
@@ -1961,40 +2038,23 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
1961
2038
  }
1962
2039
  return prompt;
1963
2040
  }
1964
- /**
1965
- * Create a new skill. Saves to `skills` table only (as private draft).
1966
- * The skill is NOT added to user's active collection (agent_skills) until
1967
- * the user approves it via addSkill().
1968
- *
1969
- * Returns { id, name } of the created skill, or null on failure.
1970
- */
1971
2041
  async create(name, description, content, options) {
1972
2042
  if (!this.userId) return null;
1973
2043
  try {
1974
- const sb = getSupabase();
1975
- const source = options?.source || "manual";
1976
- const metadata = {};
1977
- if (options?.emoji) {
1978
- metadata.openclaw = { emoji: options.emoji };
1979
- }
1980
- if (options?.variables && options.variables.length > 0) {
1981
- metadata.variables = options.variables;
1982
- }
1983
- const { data, error } = await sb.rpc("create_skill", {
1984
- p_user_id: this.userId,
1985
- p_name: name,
1986
- p_description: description,
1987
- p_content: content,
1988
- p_version: "1.0.0",
1989
- p_source: source,
1990
- p_emoji: options?.emoji || null,
1991
- p_keywords: options?.keywords || [],
1992
- p_metadata: metadata
1993
- });
1994
- if (error) {
1995
- log.debug(`Skill create failed for "${name}": ${error.message}`);
1996
- return null;
1997
- }
2044
+ const metadata = options?.emoji ? { openclaw: { emoji: options.emoji } } : {};
2045
+ const data = await callMcpHandler(
2046
+ "skill.create",
2047
+ {
2048
+ name,
2049
+ description,
2050
+ content,
2051
+ version: "1.0.0",
2052
+ source: options?.source || "manual",
2053
+ emoji: options?.emoji || null,
2054
+ keywords: options?.keywords || [],
2055
+ metadata
2056
+ }
2057
+ );
1998
2058
  const row = Array.isArray(data) ? data[0] : data;
1999
2059
  if (!row) {
2000
2060
  log.debug(`Skill create returned no data for "${name}"`);
@@ -2009,45 +2069,15 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2009
2069
  return null;
2010
2070
  }
2011
2071
  }
2012
- /**
2013
- * Add a skill to the user's active collection (agent_skills).
2014
- * Copies content from the skills table.
2015
- * This is the "approval" step — after this, the skill is usable.
2016
- */
2017
2072
  async addSkill(skillId) {
2018
2073
  if (!this.userId) return null;
2019
2074
  try {
2020
- const sb = getSupabase();
2021
- const { data: pub, error: fetchErr } = await sb.from("skills").select("*").eq("id", skillId).eq("is_active", true).single();
2022
- if (fetchErr || !pub) {
2023
- log.debug(`Skill not found: ${skillId}`);
2024
- return null;
2025
- }
2026
- const row = pub;
2027
- const { data, error } = await sb.rpc("upsert_agent_skill", {
2028
- p_user_id: this.userId,
2029
- p_name: row.name,
2030
- p_description: row.description || "",
2031
- p_content: row.content,
2032
- p_version: row.version || "1.0.0",
2033
- p_source: row.source || "manual",
2034
- p_emoji: row.emoji || null,
2035
- p_keywords: row.keywords || [],
2036
- p_source_skill_id: skillId
2037
- });
2038
- if (error) {
2039
- log.debug(`addSkill failed: ${error.message}`);
2040
- return null;
2041
- }
2042
- try {
2043
- await sb.rpc("increment_install_count", { p_skill_id: skillId });
2044
- } catch {
2045
- try {
2046
- await sb.from("skills").update({ install_count: (row.install_count || 0) + 1 }).eq("id", skillId);
2047
- } catch {
2048
- }
2049
- }
2050
- const agentSkillRow = data && typeof data === "object" ? data : row;
2075
+ const result = await callMcpHandler(
2076
+ "skill.fetch_and_add",
2077
+ { skill_id: skillId }
2078
+ );
2079
+ const row = result.skill;
2080
+ const agentSkillRow = result.agent_skill && typeof result.agent_skill === "object" ? result.agent_skill : row;
2051
2081
  const skill = this.rowToSkill({
2052
2082
  ...agentSkillRow,
2053
2083
  name: row.name,
@@ -2064,9 +2094,6 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2064
2094
  return null;
2065
2095
  }
2066
2096
  }
2067
- /**
2068
- * Remove a skill from user's collection (soft-delete in agent_skills).
2069
- */
2070
2097
  remove(name) {
2071
2098
  const skill = this.skills.get(name);
2072
2099
  if (!skill) return false;
@@ -2075,23 +2102,15 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2075
2102
  });
2076
2103
  return true;
2077
2104
  }
2078
- /**
2079
- * List skills with summary info.
2080
- */
2081
2105
  listFormatted() {
2082
2106
  const skills = this.getAll();
2083
- if (skills.length === 0) {
2084
- return "No skills in your collection.";
2085
- }
2107
+ if (skills.length === 0) return "No skills in your collection.";
2086
2108
  return skills.map((s) => {
2087
2109
  const emoji = s.metadata.emoji || "";
2088
2110
  return ` ${emoji ? emoji + " " : ""}${s.name} (${s.version}) [${s.source}]
2089
2111
  ${s.description}`;
2090
2112
  }).join("\n\n");
2091
2113
  }
2092
- /**
2093
- * Check if a skill with a similar name already exists in user's collection.
2094
- */
2095
2114
  findSimilar(name) {
2096
2115
  if (this.skills.has(name)) return this.skills.get(name);
2097
2116
  const normalizedName = name.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -2112,10 +2131,6 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2112
2131
  }
2113
2132
  return null;
2114
2133
  }
2115
- /**
2116
- * Update an existing skill in user's collection.
2117
- * Bumps the patch version automatically. Also updates skills table if user is author.
2118
- */
2119
2134
  update(name, newContent, description) {
2120
2135
  const skill = this.skills.get(name);
2121
2136
  if (!skill) return false;
@@ -2131,63 +2146,41 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2131
2146
  }).catch(() => {
2132
2147
  });
2133
2148
  if (skill.sourceSkillId && this.userId) {
2134
- const sb = getSupabase();
2135
- void (async () => {
2136
- try {
2137
- await sb.from("skills").update({ content: newContent, description: newDescription, version: newVersion }).eq("id", skill.sourceSkillId).eq("author_id", this.userId);
2138
- } catch {
2139
- }
2140
- })();
2149
+ callMcpHandler("skill.update_source", {
2150
+ source_skill_id: skill.sourceSkillId,
2151
+ content: newContent,
2152
+ description: newDescription,
2153
+ version: newVersion
2154
+ }).catch(() => {
2155
+ });
2141
2156
  }
2142
2157
  log.info(`Skill "${name}" updated to v${newVersion}`);
2143
2158
  return true;
2144
2159
  }
2145
2160
  // ── DB Integration ─────────────────────────────────────────────────
2146
- /**
2147
- * Persist a skill to agent_skills via upsert_agent_skill().
2148
- * Fire-and-forget: failures are logged but don't block.
2149
- */
2150
2161
  async syncToAgentSkills(name, description, content, version2, options) {
2151
2162
  if (!this.userId) return;
2152
2163
  try {
2153
- const sb = getSupabase();
2154
- const metadata = {};
2155
- if (options?.variables && options.variables.length > 0) {
2156
- metadata.variables = options.variables;
2157
- }
2158
- const { data, error } = await sb.rpc("upsert_agent_skill", {
2159
- p_user_id: this.userId,
2160
- p_name: name,
2161
- p_description: description,
2162
- p_content: content,
2163
- p_version: version2,
2164
- p_source: options?.source || "manual",
2165
- p_emoji: options?.emoji || null,
2166
- p_keywords: options?.keywords || [],
2167
- p_change_summary: options?.changeSummary || null,
2168
- p_source_skill_id: options?.sourceSkillId || null,
2169
- p_metadata: Object.keys(metadata).length > 0 ? metadata : null
2164
+ const data = await callMcpHandler("skill.upsert", {
2165
+ name,
2166
+ description,
2167
+ content,
2168
+ version: version2,
2169
+ source: options?.source || "manual",
2170
+ emoji: options?.emoji || null,
2171
+ keywords: options?.keywords || [],
2172
+ change_summary: options?.changeSummary || null,
2173
+ source_skill_id: options?.sourceSkillId || null
2170
2174
  });
2171
- if (error) {
2172
- log.debug(`DB skill sync failed for "${name}": ${error.message}`);
2173
- return;
2174
- }
2175
2175
  const skill = this.skills.get(name);
2176
2176
  if (skill && data && typeof data === "object" && "id" in data) {
2177
2177
  skill.dbId = data.id;
2178
2178
  }
2179
- if (skill && options?.variables) {
2180
- skill.variables = options.variables;
2181
- }
2182
2179
  log.debug(`Skill "${name}" synced to agent_skills`);
2183
2180
  } catch (err) {
2184
2181
  log.debug(`DB skill sync error for "${name}": ${err}`);
2185
2182
  }
2186
2183
  }
2187
- /**
2188
- * Log a skill invocation to agent_skill_invocations.
2189
- * Fire-and-forget: failures don't block task execution.
2190
- */
2191
2184
  async logInvocation(skillName, options) {
2192
2185
  if (!this.userId) return;
2193
2186
  const skill = this.skills.get(skillName);
@@ -2197,39 +2190,24 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2197
2190
  return;
2198
2191
  }
2199
2192
  try {
2200
- const sb = getSupabase();
2201
- const { error } = await sb.from("agent_skill_invocations").insert({
2193
+ await callMcpHandler("skill.log_invocation", {
2202
2194
  skill_id: skillDbId,
2203
- user_id: this.userId,
2204
2195
  message_id: options?.messageId || null,
2205
2196
  session_id: options?.sessionId || null,
2206
2197
  task_prompt: options?.taskPrompt?.slice(0, 500) || null,
2207
2198
  arguments: options?.arguments || null,
2208
2199
  success: options?.success ?? null
2209
2200
  });
2210
- if (error) {
2211
- log.debug(`Invocation log failed: ${error.message}`);
2212
- }
2213
2201
  } catch (err) {
2214
2202
  log.debug(`Invocation log error: ${err}`);
2215
2203
  }
2216
2204
  }
2217
- /**
2218
- * Search user's skills in DB using full-text search.
2219
- * Falls back to in-memory findRelevant() if DB is unavailable.
2220
- */
2221
2205
  async searchDb(query2, limit = 10) {
2222
2206
  if (this.userId) {
2223
2207
  try {
2224
- const sb = getSupabase();
2225
- const { data, error } = await sb.rpc("search_agent_skills", {
2226
- p_user_id: this.userId,
2227
- p_query: query2,
2228
- p_limit: limit
2229
- });
2230
- if (!error && data) {
2231
- const rows = data;
2232
- return rows.map((row) => ({
2208
+ const data = await callMcpHandler("skill.search", { query: query2, limit });
2209
+ if (data) {
2210
+ return data.map((row) => ({
2233
2211
  name: row.name,
2234
2212
  description: row.description || "",
2235
2213
  emoji: row.emoji || "",
@@ -2249,21 +2227,14 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2249
2227
  invocationCount: 0
2250
2228
  }));
2251
2229
  }
2252
- /**
2253
- * Remove a skill from DB (soft-delete).
2254
- */
2255
2230
  async removeFromDb(name) {
2256
2231
  if (!this.userId) return;
2257
2232
  try {
2258
- const sb = getSupabase();
2259
- await sb.from("agent_skills").update({ is_active: false }).eq("user_id", this.userId).eq("name", name);
2233
+ await callMcpHandler("skill.remove", { name });
2260
2234
  } catch {
2261
2235
  }
2262
2236
  }
2263
2237
  // ── Marketplace ────────────────────────────────────────────────────
2264
- /**
2265
- * Publish a skill to the marketplace (sets is_public = true in skills table).
2266
- */
2267
2238
  async publish(name, options) {
2268
2239
  if (!this.userId) return null;
2269
2240
  const skill = this.skills.get(name);
@@ -2273,31 +2244,21 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2273
2244
  return null;
2274
2245
  }
2275
2246
  try {
2276
- const sb = getSupabase();
2277
- const { data, error } = await sb.from("skills").upsert(
2278
- {
2279
- name: skill.name,
2280
- description: skill.description,
2281
- version: skill.version,
2282
- emoji: skill.metadata.emoji || null,
2283
- content: skill.content,
2284
- argument_hint: skill.argumentHint || null,
2285
- keywords: skill.keywords,
2286
- allowed_tools: skill.allowedTools,
2287
- author_id: this.userId,
2288
- author_name: options?.authorName || null,
2289
- metadata: skill.metadata,
2290
- homepage: skill.homepage || null,
2291
- category: options?.category || null,
2292
- source: skill.source,
2293
- is_public: true
2294
- },
2295
- { onConflict: "author_id,name" }
2296
- ).select("id").single();
2297
- if (error) {
2298
- log.debug(`Publish failed for "${name}": ${error.message}`);
2299
- return null;
2300
- }
2247
+ const data = await callMcpHandler("skill.publish", {
2248
+ name: skill.name,
2249
+ description: skill.description,
2250
+ version: skill.version,
2251
+ emoji: skill.metadata.emoji || null,
2252
+ content: skill.content,
2253
+ argument_hint: skill.argumentHint || null,
2254
+ keywords: skill.keywords,
2255
+ allowed_tools: skill.allowedTools,
2256
+ author_name: options?.authorName || null,
2257
+ metadata: skill.metadata,
2258
+ homepage: skill.homepage || null,
2259
+ category: options?.category || null,
2260
+ source: skill.source
2261
+ });
2301
2262
  log.info(`Skill "${name}" published to marketplace`);
2302
2263
  return data;
2303
2264
  } catch (err) {
@@ -2305,21 +2266,16 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2305
2266
  return null;
2306
2267
  }
2307
2268
  }
2308
- /**
2309
- * Browse marketplace skills (public + active).
2310
- */
2311
2269
  async browse(options) {
2312
2270
  try {
2313
- const sb = getSupabase();
2314
- const { data, error } = await sb.rpc("browse_skills", {
2315
- p_query: options?.query || null,
2316
- p_category: options?.category || null,
2317
- p_sort: options?.sort || "popular",
2318
- p_limit: options?.limit || 20,
2319
- p_offset: options?.offset || 0
2271
+ const data = await callMcpHandler("skill.browse", {
2272
+ query: options?.query || null,
2273
+ category: options?.category || null,
2274
+ sort: options?.sort || "popular",
2275
+ limit: options?.limit || 20,
2276
+ offset: options?.offset || 0
2320
2277
  });
2321
- if (error || !data) return [];
2322
- return data.map((r) => ({
2278
+ return (data || []).map((r) => ({
2323
2279
  id: r.id,
2324
2280
  name: r.name,
2325
2281
  description: r.description || "",
@@ -2335,54 +2291,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
2335
2291
  return [];
2336
2292
  }
2337
2293
  }
2338
- // ── Variable Configuration ──────────────────────────────────────────
2339
- /**
2340
- * Update user-specific variable values (config) for a skill.
2341
- * Persists to agent_skills.config in DB and updates in-memory.
2342
- */
2343
- async updateConfig(skillName, config) {
2344
- const skill = this.skills.get(skillName);
2345
- if (!skill) return false;
2346
- const merged = { ...skill.config || {}, ...config };
2347
- skill.config = merged;
2348
- if (this.userId && skill.dbId) {
2349
- try {
2350
- const sb = getSupabase();
2351
- await sb.from("agent_skills").update({ config: merged }).eq("id", skill.dbId).eq("user_id", this.userId);
2352
- log.debug(`Config updated for skill "${skillName}"`);
2353
- } catch (err) {
2354
- log.debug(`Config update failed for "${skillName}": ${err}`);
2355
- }
2356
- }
2357
- return true;
2358
- }
2359
- /**
2360
- * Get the unresolved (unconfigured) variables for a skill.
2361
- * Returns variables that are required but have no value in config.
2362
- */
2363
- getUnconfiguredVariables(skillName) {
2364
- const skill = this.skills.get(skillName);
2365
- if (!skill?.variables) return [];
2366
- return skill.variables.filter((v) => {
2367
- if (!v.required) return false;
2368
- const value = skill.config?.[v.name];
2369
- return value === void 0 || value === null || value === "";
2370
- });
2371
- }
2372
2294
  };
2373
- function substituteVariables(content, config, variables) {
2374
- if (!content.includes("{{")) return content;
2375
- return content.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
2376
- const value = config?.[varName];
2377
- if (value !== void 0 && value !== null) {
2378
- if (Array.isArray(value)) return value.join(", ");
2379
- return String(value);
2380
- }
2381
- const varDef = variables?.find((v) => v.name === varName);
2382
- if (varDef?.default !== void 0) return varDef.default;
2383
- return `{{${varName}: [NOT CONFIGURED]}}`;
2384
- });
2385
- }
2386
2295
  function substituteArguments(content, args) {
2387
2296
  const parts = args.split(/\s+/);
2388
2297
  content = content.replace(/\$ARGUMENTS/g, args);
@@ -2443,7 +2352,7 @@ import { z } from "zod/v4";
2443
2352
 
2444
2353
  // src/tools/filesystem.ts
2445
2354
  import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
2446
- import { resolve, relative, join as join2 } from "path";
2355
+ import { resolve, relative, join as join3 } from "path";
2447
2356
  import { glob } from "glob";
2448
2357
  function assertWithinWorkspace(filePath) {
2449
2358
  const config = getConfig();
@@ -2480,7 +2389,7 @@ async function searchFiles(pattern, directory) {
2480
2389
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
2481
2390
  });
2482
2391
  if (matches.length === 0) return "No files found matching the pattern.";
2483
- return matches.slice(0, 50).map((m) => relative(config.workspacePath, join2(cwd, m))).join("\n");
2392
+ return matches.slice(0, 50).map((m) => relative(config.workspacePath, join3(cwd, m))).join("\n");
2484
2393
  }
2485
2394
  async function listDirectory(path) {
2486
2395
  const config = getConfig();
@@ -2490,7 +2399,7 @@ async function listDirectory(path) {
2490
2399
  for (const entry of entries) {
2491
2400
  if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
2492
2401
  const icon = entry.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
2493
- const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then(
2402
+ const info = entry.isFile() ? await stat(join3(resolved, entry.name)).then(
2494
2403
  (s) => ` (${formatSize(s.size)})`
2495
2404
  ) : "";
2496
2405
  results.push(`${icon} ${entry.name}${info}`);
@@ -2514,11 +2423,11 @@ async function searchContent(pattern, fileGlob, directory) {
2514
2423
  const results = [];
2515
2424
  for (const file of files.slice(0, 200)) {
2516
2425
  try {
2517
- const content = await readFile(join2(cwd, file), "utf-8");
2426
+ const content = await readFile(join3(cwd, file), "utf-8");
2518
2427
  const lines = content.split("\n");
2519
2428
  for (let i = 0; i < lines.length; i++) {
2520
2429
  if (regex.test(lines[i])) {
2521
- const relPath = relative(config.workspacePath, join2(cwd, file));
2430
+ const relPath = relative(config.workspacePath, join3(cwd, file));
2522
2431
  results.push(`${relPath}:${i + 1}: ${lines[i].trim()}`);
2523
2432
  regex.lastIndex = 0;
2524
2433
  if (results.length >= 30) break;
@@ -3044,24 +2953,12 @@ function createAgentToolsServer(deps) {
3044
2953
  ),
3045
2954
  tool(
3046
2955
  "skill_create",
3047
- "Create a new skill and add it to the user's collection. Returns the skill ID on success. Use {{variable_name}} syntax in instructions for user-specific data (e.g. {{github_repos}}, {{slack_channel}}).",
2956
+ "Create a new skill and add it to the user's collection. Returns the skill ID on success.",
3048
2957
  {
3049
2958
  name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
3050
2959
  description: z.string().describe("One-line description of what this skill does"),
3051
- instructions: z.string().describe(
3052
- "Markdown step-by-step instructions. Use {{variable_name}} placeholders for user-specific data (e.g. {{github_repos}}, {{trello_board}}, {{slack_channel}}). These will be resolved with user's config at invoke time."
3053
- ),
3054
- emoji: z.string().optional().describe("Single emoji representing this skill"),
3055
- variables: z.array(z.object({
3056
- name: z.string().describe("Variable name matching {{name}} in instructions"),
3057
- description: z.string().describe("Human-readable description of what this variable is for"),
3058
- type: z.enum(["string", "string[]", "number", "boolean"]).describe("Data type"),
3059
- required: z.boolean().describe("Whether the skill needs this to function"),
3060
- default: z.string().optional().describe("Default value if user doesn't configure"),
3061
- example: z.string().optional().describe("Example value to guide the user")
3062
- })).optional().describe(
3063
- "Variables that need user-specific configuration. Define one for each {{variable}} used in instructions."
3064
- )
2960
+ instructions: z.string().describe("Markdown step-by-step instructions"),
2961
+ emoji: z.string().optional().describe("Single emoji representing this skill")
3065
2962
  },
3066
2963
  async (args) => {
3067
2964
  const existing = skillManager.findSimilar(args.name);
@@ -3079,11 +2976,7 @@ function createAgentToolsServer(deps) {
3079
2976
  args.name,
3080
2977
  args.description,
3081
2978
  args.instructions,
3082
- {
3083
- source: "manual",
3084
- emoji: args.emoji,
3085
- variables: args.variables
3086
- }
2979
+ { source: "manual", emoji: args.emoji }
3087
2980
  );
3088
2981
  if (!result) {
3089
2982
  return {
@@ -3098,30 +2991,17 @@ function createAgentToolsServer(deps) {
3098
2991
  {
3099
2992
  source: "manual",
3100
2993
  emoji: args.emoji,
3101
- sourceSkillId: result.id,
3102
- variables: args.variables
2994
+ sourceSkillId: result.id
3103
2995
  }
3104
2996
  );
3105
- let responseText = `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`;
3106
- if (args.variables && args.variables.length > 0) {
3107
- const requiredVars = args.variables.filter((v) => v.required);
3108
- if (requiredVars.length > 0) {
3109
- responseText += `
3110
-
3111
- **Variables that need configuration:**
3112
- `;
3113
- for (const v of requiredVars) {
3114
- const example = v.example ? ` (e.g. ${v.example})` : "";
3115
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}
3116
- `;
3117
- }
3118
- responseText += `
3119
- Use \`skill_configure\` to set these values for the user, or ask the user to provide them.`;
3120
- }
3121
- }
3122
2997
  log.success(`Skill "${args.name}" created and added to collection`);
3123
2998
  return {
3124
- content: [{ type: "text", text: responseText }]
2999
+ content: [
3000
+ {
3001
+ type: "text",
3002
+ text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`
3003
+ }
3004
+ ]
3125
3005
  };
3126
3006
  }
3127
3007
  ),
@@ -3193,7 +3073,6 @@ Use \`skill_configure\` to set these values for the user, or ask the user to pro
3193
3073
  };
3194
3074
  }
3195
3075
  let content = skill.content;
3196
- content = substituteVariables(content, skill.config, skill.variables);
3197
3076
  if (args.arguments) {
3198
3077
  content = substituteArguments(content, args.arguments);
3199
3078
  }
@@ -3212,18 +3091,6 @@ ${content}`;
3212
3091
  **Allowed tools for this skill:** ${skill.allowedTools.join(", ")}
3213
3092
  `;
3214
3093
  }
3215
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
3216
- if (unconfigured.length > 0) {
3217
- response += `
3218
-
3219
- **\u26A0 Unconfigured variables \u2014 use \`skill_configure\` to set these:**
3220
- `;
3221
- for (const v of unconfigured) {
3222
- const example = v.example ? ` (e.g. ${v.example})` : "";
3223
- response += `- \`{{${v.name}}}\`: ${v.description}${example}
3224
- `;
3225
- }
3226
- }
3227
3094
  log.info(`Skill invoked: "${args.name}"`);
3228
3095
  skillManager.logInvocation(args.name, {
3229
3096
  messageId: taskId,
@@ -3263,53 +3130,6 @@ ${content}`;
3263
3130
  return { content: [{ type: "text", text: response }] };
3264
3131
  }
3265
3132
  ),
3266
- tool(
3267
- "skill_configure",
3268
- "Set user-specific variable values for a skill. Variables are defined in the skill template (e.g. {{github_repos}}) and need to be configured with the user's actual data before the skill can work properly.",
3269
- {
3270
- name: z.string().describe("Name of the skill to configure"),
3271
- config: z.record(z.unknown()).describe(
3272
- 'Key-value pairs of variable values. Keys must match variable names defined in the skill. Example: {"github_repos": ["octocat/hello-world", "myorg/myrepo"], "github_username": "octocat"}'
3273
- )
3274
- },
3275
- async (args) => {
3276
- const skill = skillManager.get(args.name);
3277
- if (!skill) {
3278
- const available = skillManager.getAll().map((s) => s.name).join(", ");
3279
- return {
3280
- content: [{
3281
- type: "text",
3282
- text: `Skill "${args.name}" not found. Available: ${available}`
3283
- }]
3284
- };
3285
- }
3286
- const updated = await skillManager.updateConfig(args.name, args.config);
3287
- if (!updated) {
3288
- return {
3289
- content: [{ type: "text", text: `Failed to update config for "${args.name}".` }]
3290
- };
3291
- }
3292
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
3293
- let responseText = `Configuration updated for skill "${args.name}".`;
3294
- if (unconfigured.length > 0) {
3295
- responseText += `
3296
-
3297
- **Still needs configuration:**
3298
- `;
3299
- for (const v of unconfigured) {
3300
- const example = v.example ? ` (e.g. ${v.example})` : "";
3301
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}
3302
- `;
3303
- }
3304
- } else {
3305
- responseText += ` All required variables are configured.`;
3306
- }
3307
- log.info(`Config updated for skill "${args.name}": ${Object.keys(args.config).join(", ")}`);
3308
- return {
3309
- content: [{ type: "text", text: responseText }]
3310
- };
3311
- }
3312
- ),
3313
3133
  tool(
3314
3134
  "skill_generate",
3315
3135
  "Prepare context for generating skills from a job description. Returns existing skills and job info so you can analyze the job and create skills using skill_create (which auto-adds to user's collection). After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
@@ -3358,8 +3178,6 @@ ${content}`;
3358
3178
  response += `- instructions: detailed step-by-step markdown instructions the agent can follow
3359
3179
  `;
3360
3180
  response += `- emoji: a single emoji representing the skill
3361
- `;
3362
- response += `- variables: define user-specific data needed by the skill (see below)
3363
3181
 
3364
3182
  `;
3365
3183
  response += `skill_create automatically adds the skill to the user's collection \u2014 no need to call skill_add.
@@ -3376,59 +3194,9 @@ ${content}`;
3376
3194
  `;
3377
3195
  response += `- Include error handling steps
3378
3196
  `;
3379
- response += `- Each skill should be a single, well-defined workflow (10-25 steps)
3380
-
3381
- `;
3382
- response += `**IMPORTANT \u2014 Use {{variables}} for user-specific data:**
3383
- `;
3384
- response += `Skills often need user-specific information (accounts, repos, boards, channels, etc.).
3385
- `;
3386
- response += `Instead of hardcoding or asking at runtime, use \`{{variable_name}}\` placeholders in instructions.
3387
-
3388
- `;
3389
- response += `Example: A GitHub PR review skill should use \`{{github_repos}}\` in its instructions:
3390
- `;
3391
- response += ` "Navigate to each repository in {{github_repos}} and check for open PRs..."
3392
-
3393
- `;
3394
- response += `For each \`{{variable}}\` used in instructions, add a matching entry in the \`variables\` array:
3395
- `;
3396
- response += `\`\`\`json
3397
- `;
3398
- response += `{
3399
- `;
3400
- response += ` "name": "github_repos",
3401
- `;
3402
- response += ` "description": "GitHub repositories to monitor (owner/repo format)",
3197
+ response += `- Use placeholders like {query}, {date} for variable inputs
3403
3198
  `;
3404
- response += ` "type": "string[]",
3405
- `;
3406
- response += ` "required": true,
3407
- `;
3408
- response += ` "example": "octocat/hello-world, myorg/myrepo"
3409
- `;
3410
- response += `}
3411
- `;
3412
- response += `\`\`\`
3413
-
3414
- `;
3415
- response += `Common variables by platform:
3416
- `;
3417
- response += `- GitHub: github_repos, github_username, github_org
3418
- `;
3419
- response += `- Trello: trello_board_url, trello_list_names
3420
- `;
3421
- response += `- Slack: slack_channels, slack_workspace_url
3422
- `;
3423
- response += `- Email: email_labels, email_filters
3424
- `;
3425
- response += `- E-commerce: store_url, competitor_urls, product_categories
3426
- `;
3427
- response += `- General: timezone, language, notification_channel
3428
-
3429
- `;
3430
- response += `After creating skills, if any have required variables, call \`skill_configure\` to set initial values `;
3431
- response += `(ask the user for the values first).
3199
+ response += `- Each skill should be a single, well-defined workflow (10-25 steps)
3432
3200
  `;
3433
3201
  return { content: [{ type: "text", text: response }] };
3434
3202
  }
@@ -3721,8 +3489,10 @@ ${content}`;
3721
3489
  const tz = args.timezone || "UTC";
3722
3490
  try {
3723
3491
  const task = await createScheduledTask(userId, name, prompt, args.cron, tz);
3724
- const sb = getSupabase();
3725
- await sb.from("agent_scheduled_tasks").update({ job_id: job.jobId }).eq("id", task.id);
3492
+ await callMcpHandler("schedule.link_job", {
3493
+ task_id: task.id,
3494
+ job_id: job.jobId
3495
+ });
3726
3496
  const nextRun = task.next_run_at ? new Date(task.next_run_at).toLocaleString() : "calculating...";
3727
3497
  let response = `## Job Scheduled: ${args.job_name}
3728
3498
 
@@ -3825,20 +3595,14 @@ ${content}`;
3825
3595
  ]
3826
3596
  });
3827
3597
  }
3828
- async function saveJobToDb(userId, jobName, jobDescription, createdSkillNames) {
3598
+ async function saveJobToDb(_userId, jobName, jobDescription, createdSkillNames) {
3829
3599
  try {
3830
- const sb = getSupabase();
3831
- const { data, error } = await sb.rpc("save_job_with_skills", {
3832
- p_user_id: userId,
3833
- p_job_name: jobName,
3834
- p_job_description: jobDescription,
3835
- p_skill_names: createdSkillNames
3600
+ const data = await callMcpHandler("job.save_with_skills", {
3601
+ job_name: jobName,
3602
+ job_description: jobDescription,
3603
+ skill_names: createdSkillNames
3836
3604
  });
3837
- if (error) {
3838
- log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
3839
- return;
3840
- }
3841
- log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
3605
+ log.debug(`Job "${jobName}" saved via edge function (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
3842
3606
  } catch (err) {
3843
3607
  log.debug(`saveJobToDb error: ${err}`);
3844
3608
  }
@@ -3930,8 +3694,6 @@ Available capabilities:
3930
3694
  - If the user approves, use skill_add to add it to their collection, then proceed with the task
3931
3695
  - If a skill's instructions could be improved based on your experience, use skill_improve
3932
3696
  - Use skill_search to find relevant skills when the task doesn't obviously match the listed skills
3933
- - Skills use {{variable_name}} placeholders for user-specific data (repos, channels, boards, etc.)
3934
- - Use skill_configure to set variable values after creating skills or when the user provides their data
3935
3697
 
3936
3698
  5. JOB AUTOMATION:
3937
3699
  - When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
@@ -4065,7 +3827,6 @@ var TaskProcessor = class {
4065
3827
  "mcp__assistme-agent__skill_improve",
4066
3828
  "mcp__assistme-agent__skill_invoke",
4067
3829
  "mcp__assistme-agent__skill_search",
4068
- "mcp__assistme-agent__skill_configure",
4069
3830
  "mcp__assistme-agent__skill_generate",
4070
3831
  "mcp__assistme-agent__skill_link_job",
4071
3832
  "mcp__assistme-agent__skill_browse",
@@ -4362,9 +4123,8 @@ import chalk5 from "chalk";
4362
4123
  function registerStatusCommand(program2) {
4363
4124
  program2.command("status").description("Check the status of the current agent session").action(async () => {
4364
4125
  try {
4365
- const userId = await getCurrentUserId();
4366
- const sb = getSupabase();
4367
- const { data: sessions } = await sb.from("agent_sessions").select("*").eq("user_id", userId).in("status", ["online", "busy"]).order("started_at", { ascending: false }).limit(5);
4126
+ await getCurrentUserId();
4127
+ const sessions = await getActiveSessions(5);
4368
4128
  if (!sessions || sessions.length === 0) {
4369
4129
  console.log(chalk5.yellow("No active sessions found."));
4370
4130
  console.log('Run "assistme" to begin a new session.');
@@ -4745,7 +4505,7 @@ function registerJobCommands(program2) {
4745
4505
  jobCmd.command("list").description("List your defined jobs").action(async () => {
4746
4506
  try {
4747
4507
  const userId = await getCurrentUserId();
4748
- const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
4508
+ const { JobRunner: JobRunner2 } = await import("./job-runner-N4XAAWLJ.js");
4749
4509
  const runner = new JobRunner2(userId);
4750
4510
  const jobs = await runner.listJobs();
4751
4511
  if (jobs.length === 0) {
@@ -4773,7 +4533,7 @@ function registerJobCommands(program2) {
4773
4533
  jobCmd.command("status [name]").description("Show run history for a job (or all jobs)").option("-l, --limit <number>", "Max runs to show (default: 5)").action(async (name, opts) => {
4774
4534
  try {
4775
4535
  const userId = await getCurrentUserId();
4776
- const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
4536
+ const { JobRunner: JobRunner2 } = await import("./job-runner-N4XAAWLJ.js");
4777
4537
  const runner = new JobRunner2(userId);
4778
4538
  const runs = await runner.getRunHistory(
4779
4539
  name,
@@ -4827,7 +4587,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`
4827
4587
  process.exit(1);
4828
4588
  }
4829
4589
  const userId = await getCurrentUserId();
4830
- const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
4590
+ const { JobRunner: JobRunner2 } = await import("./job-runner-N4XAAWLJ.js");
4831
4591
  const runner = new JobRunner2(userId);
4832
4592
  const job = await runner.loadJob(name);
4833
4593
  if (!job) {
@@ -4849,8 +4609,10 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`
4849
4609
  opts.cron,
4850
4610
  tz
4851
4611
  );
4852
- const sb = getSupabase();
4853
- await sb.from("agent_scheduled_tasks").update({ job_id: job.jobId }).eq("id", task.id);
4612
+ await callMcpHandler("schedule.link_job", {
4613
+ task_id: task.id,
4614
+ job_id: job.jobId
4615
+ });
4854
4616
  log.success(`Job "${name}" scheduled: ${opts.cron} (${tz})`);
4855
4617
  console.log(` Skills: ${job.skills.length}`);
4856
4618
  console.log(