@knowsuchagency/fulcrum 3.6.11 → 3.7.1

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/bin/fulcrum.js CHANGED
@@ -46445,7 +46445,7 @@ async function runMcpServer(urlOverride, portOverride) {
46445
46445
  const client = new FulcrumClient(urlOverride, portOverride);
46446
46446
  const server = new McpServer({
46447
46447
  name: "fulcrum",
46448
- version: "3.6.11"
46448
+ version: "3.7.1"
46449
46449
  });
46450
46450
  registerTools(server, client);
46451
46451
  const transport = new StdioServerTransport;
@@ -48794,7 +48794,7 @@ var marketplace_default = `{
48794
48794
  "name": "fulcrum",
48795
48795
  "source": "./",
48796
48796
  "description": "Task orchestration for Claude Code",
48797
- "version": "3.6.11",
48797
+ "version": "3.7.1",
48798
48798
  "skills": [
48799
48799
  "./skills/fulcrum"
48800
48800
  ],
@@ -49998,7 +49998,7 @@ function compareVersions(v1, v2) {
49998
49998
  var package_default = {
49999
49999
  name: "@knowsuchagency/fulcrum",
50000
50000
  private: true,
50001
- version: "3.6.11",
50001
+ version: "3.7.1",
50002
50002
  description: "Harness Attention. Orchestrate Agents. Ship.",
50003
50003
  license: "PolyForm-Perimeter-1.0.0",
50004
50004
  type: "module",
@@ -0,0 +1 @@
1
+ ALTER TABLE `google_accounts` ADD `last_gmail_history_id` text;
@@ -463,6 +463,13 @@
463
463
  "when": 1771757600000,
464
464
  "tag": "0065_add_last_channel_sync_at",
465
465
  "breakpoints": true
466
+ },
467
+ {
468
+ "idx": 66,
469
+ "version": "6",
470
+ "when": 1771844000000,
471
+ "tag": "0066_persist_gmail_history_id",
472
+ "breakpoints": true
466
473
  }
467
474
  ]
468
475
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowsuchagency/fulcrum",
3
- "version": "3.6.11",
3
+ "version": "3.7.1",
4
4
  "description": "Harness Attention. Orchestrate Agents. Ship.",
5
5
  "license": "PolyForm-Perimeter-1.0.0",
6
6
  "repository": {
package/server/index.js CHANGED
@@ -4724,6 +4724,7 @@ var init_schema = __esm(() => {
4724
4724
  lastCalendarSyncError: text("last_calendar_sync_error"),
4725
4725
  lastGmailSyncAt: text("last_gmail_sync_at"),
4726
4726
  lastGmailSyncError: text("last_gmail_sync_error"),
4727
+ lastGmailHistoryId: text("last_gmail_history_id"),
4727
4728
  sendAsEmail: text("send_as_email"),
4728
4729
  needsReauth: integer("needs_reauth", { mode: "boolean" }).default(false),
4729
4730
  createdAt: text("created_at").notNull(),
@@ -336873,7 +336874,7 @@ function storeEmail(params) {
336873
336874
  connectionId: params.connectionId,
336874
336875
  messageId: params.messageId
336875
336876
  });
336876
- return;
336877
+ return false;
336877
336878
  }
336878
336879
  const snippet = params.textContent ? params.textContent.slice(0, 200).replace(/\s+/g, " ").trim() : undefined;
336879
336880
  const metadata = {
@@ -336910,6 +336911,7 @@ function storeEmail(params) {
336910
336911
  messageId: params.messageId,
336911
336912
  direction: params.direction
336912
336913
  });
336914
+ return true;
336913
336915
  }
336914
336916
  function getStoredEmails(options) {
336915
336917
  const conditions2 = [
@@ -337098,7 +337100,7 @@ class EmailChannel {
337098
337100
  continue;
337099
337101
  const authResult = await checkAuthorization(this.connectionId, headers, this.credentials?.allowedSenders || [], this.credentials?.imap.user.toLowerCase() ?? "");
337100
337102
  if (headers.messageId) {
337101
- storeEmail({
337103
+ const isNew = storeEmail({
337102
337104
  connectionId: this.connectionId,
337103
337105
  messageId: headers.messageId,
337104
337106
  threadId: authResult.threadId,
@@ -337114,6 +337116,8 @@ class EmailChannel {
337114
337116
  emailDate: headers.date ?? undefined,
337115
337117
  imapUid: message.uid
337116
337118
  });
337119
+ if (!isNew)
337120
+ continue;
337117
337121
  }
337118
337122
  const incomingMessage = {
337119
337123
  channelType: "email",
@@ -1135469,6 +1135473,14 @@ class GmailBackend {
1135469
1135473
  this.isShuttingDown = false;
1135470
1135474
  try {
1135471
1135475
  this.updateStatus("connecting");
1135476
+ const account = db2.select().from(googleAccounts).where(eq(googleAccounts.id, this.googleAccountId)).get();
1135477
+ if (account?.lastGmailHistoryId) {
1135478
+ this.lastHistoryId = account.lastGmailHistoryId;
1135479
+ log2.messaging.info("Restored Gmail historyId from DB", {
1135480
+ connectionId: this.connectionId,
1135481
+ lastHistoryId: this.lastHistoryId
1135482
+ });
1135483
+ }
1135472
1135484
  await listMessages(this.googleAccountId, { maxResults: 1 });
1135473
1135485
  this.updateStatus("connected");
1135474
1135486
  this.startPolling();
@@ -1135503,6 +1135515,7 @@ class GmailBackend {
1135503
1135515
  maxResults: 20
1135504
1135516
  });
1135505
1135517
  this.lastHistoryId = result.latestHistoryId;
1135518
+ db2.update(googleAccounts).set({ lastGmailHistoryId: result.latestHistoryId }).where(eq(googleAccounts.id, this.googleAccountId)).run();
1135506
1135519
  const account = db2.select().from(googleAccounts).where(eq(googleAccounts.id, this.googleAccountId)).get();
1135507
1135520
  const selfEmail = account?.email?.toLowerCase() ?? "";
1135508
1135521
  const settings = getSettings();
@@ -1135545,7 +1135558,7 @@ class GmailBackend {
1135545
1135558
  contentType: null
1135546
1135559
  }, allowedSenders, selfEmail);
1135547
1135560
  if (msg.messageId) {
1135548
- storeEmail({
1135561
+ const isNew = storeEmail({
1135549
1135562
  connectionId: this.connectionId,
1135550
1135563
  messageId: msg.messageId,
1135551
1135564
  threadId: authResult.threadId,
@@ -1135558,6 +1135571,8 @@ class GmailBackend {
1135558
1135571
  subject: msg.subject ?? undefined,
1135559
1135572
  textContent: msg.body ?? undefined
1135560
1135573
  });
1135574
+ if (!isNew)
1135575
+ continue;
1135561
1135576
  }
1135562
1135577
  const incomingMessage = {
1135563
1135578
  channelType: "email",
@@ -1135672,6 +1135687,22 @@ function getChannelMessages(options = {}) {
1135672
1135687
  function getChannelMessageById(id) {
1135673
1135688
  return db2.select().from(channelMessages).where(eq(channelMessages.id, id)).get();
1135674
1135689
  }
1135690
+ function getRecentChannelMessages(connectionId, options = {}) {
1135691
+ const { before, limit = 5 } = options;
1135692
+ const conditions2 = [
1135693
+ eq(channelMessages.connectionId, connectionId)
1135694
+ ];
1135695
+ if (before) {
1135696
+ conditions2.push(lt(channelMessages.messageTimestamp, before));
1135697
+ }
1135698
+ const results = db2.select({
1135699
+ direction: channelMessages.direction,
1135700
+ senderName: channelMessages.senderName,
1135701
+ content: channelMessages.content,
1135702
+ messageTimestamp: channelMessages.messageTimestamp
1135703
+ }).from(channelMessages).where(and(...conditions2)).orderBy(desc(channelMessages.messageTimestamp)).limit(limit).all();
1135704
+ return results.reverse();
1135705
+ }
1135675
1135706
  function getRecentOutgoingMessages(connectionId, options = {}) {
1135676
1135707
  const { since, limit = 20 } = options;
1135677
1135708
  const conditions2 = [
@@ -1150349,7 +1150380,25 @@ async function* streamOpencodeObserverMessage(sessionId, userMessage, options) {
1150349
1150380
  const client3 = await getClient();
1150350
1150381
  const settings = getSettings();
1150351
1150382
  const model = options.model || settings.assistant.observerOpencodeModel || settings.agent.opencodeModel;
1150352
- const contextualMessage = `[${options.channelType.toUpperCase()} message from ${options.senderName || options.senderId}]
1150383
+ let contextualMessage = "";
1150384
+ if (options.channelHistory && options.channelHistory.length > 0) {
1150385
+ const historyLines = options.channelHistory.map((msg) => {
1150386
+ const time2 = new Date(msg.messageTimestamp).toLocaleTimeString("en-US", {
1150387
+ hour: "2-digit",
1150388
+ minute: "2-digit",
1150389
+ hour12: false
1150390
+ });
1150391
+ const label = msg.direction === "outgoing" ? "You" : msg.senderName || "Unknown";
1150392
+ const truncated = msg.content.length > 500 ? msg.content.slice(0, 500) + "..." : msg.content;
1150393
+ return `[${time2}] ${label}: ${truncated}`;
1150394
+ });
1150395
+ contextualMessage += `[Recent messages on this channel:
1150396
+ ${historyLines.join(`
1150397
+ `)}]
1150398
+
1150399
+ `;
1150400
+ }
1150401
+ contextualMessage += `[${options.channelType.toUpperCase()} message from ${options.senderName || options.senderId}]
1150353
1150402
 
1150354
1150403
  ${userMessage}`;
1150355
1150404
  const fullPrompt = `${getObserverSystemPrompt()}
@@ -1150869,6 +1150918,10 @@ async function processObserveOnlyMessage(msg) {
1150869
1150918
  });
1150870
1150919
  return;
1150871
1150920
  }
1150921
+ const channelHistory = msg.channelType !== "email" ? getRecentChannelMessages(msg.connectionId, {
1150922
+ before: (msg.timestamp ?? new Date).toISOString(),
1150923
+ limit: 5
1150924
+ }) : [];
1150872
1150925
  const observeSessionKey = `observe-${msg.connectionId}`;
1150873
1150926
  const { session: session3 } = getOrCreateSession(msg.connectionId, observeSessionKey, "Observer", undefined, msg.channelType);
1150874
1150927
  const settings = getSettings();
@@ -1150879,7 +1150932,8 @@ async function processObserveOnlyMessage(msg) {
1150879
1150932
  const stream2 = _deps.streamOpencodeObserverMessage(session3.id, msg.content, {
1150880
1150933
  channelType: msg.channelType,
1150881
1150934
  senderId: msg.senderId,
1150882
- senderName: msg.senderName
1150935
+ senderName: msg.senderName,
1150936
+ channelHistory
1150883
1150937
  });
1150884
1150938
  for await (const event of stream2) {
1150885
1150939
  if (event.type === "error") {
@@ -1150919,7 +1150973,8 @@ async function processObserveOnlyMessage(msg) {
1150919
1150973
  systemPromptAdditions: systemPrompt,
1150920
1150974
  modelId: observerModelId,
1150921
1150975
  securityTier: "observer",
1150922
- ephemeral: true
1150976
+ ephemeral: true,
1150977
+ ...channelHistory.length > 0 && { channelHistory }
1150923
1150978
  });
1150924
1150979
  for await (const event of stream2) {
1150925
1150980
  if (event.type === "error") {
@@ -1255379,7 +1255434,7 @@ mcpRoutes.all("/", async (c) => {
1255379
1255434
  });
1255380
1255435
  const server2 = new McpServer({
1255381
1255436
  name: "fulcrum",
1255382
- version: "3.6.11"
1255437
+ version: "3.7.1"
1255383
1255438
  });
1255384
1255439
  const client3 = new FulcrumClient(`http://localhost:${port}`);
1255385
1255440
  registerTools(server2, client3);