@knowsuchagency/fulcrum 3.2.0 → 3.2.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
@@ -44317,7 +44317,7 @@ var init_registry = __esm(() => {
44317
44317
  },
44318
44318
  {
44319
44319
  name: "create_calendar_event",
44320
- description: "Create a new calendar event on a CalDAV calendar",
44320
+ description: "Create a new calendar event on a CalDAV or Google calendar.",
44321
44321
  category: "caldav",
44322
44322
  keywords: ["calendar", "event", "create", "new", "schedule", "appointment", "meeting"],
44323
44323
  defer_loading: true
@@ -46316,7 +46316,7 @@ async function runMcpServer(urlOverride, portOverride) {
46316
46316
  const client = new FulcrumClient(urlOverride, portOverride);
46317
46317
  const server = new McpServer({
46318
46318
  name: "fulcrum",
46319
- version: "3.2.0"
46319
+ version: "3.2.1"
46320
46320
  });
46321
46321
  registerTools(server, client);
46322
46322
  const transport = new StdioServerTransport;
@@ -48665,7 +48665,7 @@ var marketplace_default = `{
48665
48665
  "name": "fulcrum",
48666
48666
  "source": "./",
48667
48667
  "description": "Task orchestration for Claude Code",
48668
- "version": "3.2.0",
48668
+ "version": "3.2.1",
48669
48669
  "skills": [
48670
48670
  "./skills/fulcrum"
48671
48671
  ],
@@ -49869,7 +49869,7 @@ function compareVersions(v1, v2) {
49869
49869
  var package_default = {
49870
49870
  name: "@knowsuchagency/fulcrum",
49871
49871
  private: true,
49872
- version: "3.2.0",
49872
+ version: "3.2.1",
49873
49873
  description: "Harness Attention. Orchestrate Agents. Ship.",
49874
49874
  license: "PolyForm-Perimeter-1.0.0",
49875
49875
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowsuchagency/fulcrum",
3
- "version": "3.2.0",
3
+ "version": "3.2.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
@@ -1148030,6 +1148030,7 @@ ${attachment.data}`);
1148030
1148030
  const resultMsg = message;
1148031
1148031
  if (resultMsg.subtype?.startsWith("error_")) {
1148032
1148032
  const errors2 = resultMsg.errors || ["Unknown error"];
1148033
+ state.claudeSessionId = undefined;
1148033
1148034
  yield { type: "error", data: { message: errors2.join(", ") } };
1148034
1148035
  }
1148035
1148036
  if (resultMsg.structured_output) {
@@ -1148042,18 +1148043,21 @@ ${attachment.data}`);
1148042
1148043
  });
1148043
1148044
  }
1148044
1148045
  }
1148045
- addMessage(sessionId, {
1148046
- role: "assistant",
1148047
- content: currentText,
1148048
- model: MODEL_MAP[effectiveModelId],
1148049
- tokensIn,
1148050
- tokensOut,
1148051
- sessionId
1148052
- });
1148046
+ if (currentText.trim()) {
1148047
+ addMessage(sessionId, {
1148048
+ role: "assistant",
1148049
+ content: currentText,
1148050
+ model: MODEL_MAP[effectiveModelId],
1148051
+ tokensIn,
1148052
+ tokensOut,
1148053
+ sessionId
1148054
+ });
1148055
+ }
1148053
1148056
  yield { type: "done", data: {} };
1148054
1148057
  } catch (err) {
1148055
1148058
  const errorMsg = err instanceof Error ? err.message : String(err);
1148056
1148059
  log2.assistant.error("Assistant stream error", { sessionId, error: errorMsg });
1148060
+ state.claudeSessionId = undefined;
1148057
1148061
  yield { type: "error", data: { message: errorMsg } };
1148058
1148062
  } finally {
1148059
1148063
  for (const tempPath of tempFiles) {
@@ -1150024,6 +1150028,38 @@ var init_opencode_channel_service = __esm(() => {
1150024
1150028
  });
1150025
1150029
 
1150026
1150030
  // server/services/channels/message-handler.ts
1150031
+ function recordObserverFailure() {
1150032
+ OBSERVER_CIRCUIT_BREAKER.failureCount++;
1150033
+ if (OBSERVER_CIRCUIT_BREAKER.failureCount >= OBSERVER_CIRCUIT_BREAKER.failureThreshold) {
1150034
+ if (OBSERVER_CIRCUIT_BREAKER.state !== "open") {
1150035
+ log2.messaging.warn("Observer circuit breaker OPEN \u2014 pausing observe-only processing", {
1150036
+ failures: OBSERVER_CIRCUIT_BREAKER.failureCount,
1150037
+ cooldownMs: OBSERVER_CIRCUIT_BREAKER.cooldownMs
1150038
+ });
1150039
+ }
1150040
+ OBSERVER_CIRCUIT_BREAKER.state = "open";
1150041
+ OBSERVER_CIRCUIT_BREAKER.nextProbeAt = Date.now() + OBSERVER_CIRCUIT_BREAKER.cooldownMs;
1150042
+ OBSERVER_CIRCUIT_BREAKER.cooldownMs = Math.min(OBSERVER_CIRCUIT_BREAKER.cooldownMs * 2, OBSERVER_CIRCUIT_BREAKER.maxCooldownMs);
1150043
+ }
1150044
+ }
1150045
+ function recordObserverSuccess() {
1150046
+ if (OBSERVER_CIRCUIT_BREAKER.state === "open") {
1150047
+ log2.messaging.info("Observer circuit breaker CLOSED \u2014 resuming normal processing", {
1150048
+ previousFailures: OBSERVER_CIRCUIT_BREAKER.failureCount
1150049
+ });
1150050
+ }
1150051
+ OBSERVER_CIRCUIT_BREAKER.failureCount = 0;
1150052
+ OBSERVER_CIRCUIT_BREAKER.state = "closed";
1150053
+ OBSERVER_CIRCUIT_BREAKER.nextProbeAt = 0;
1150054
+ OBSERVER_CIRCUIT_BREAKER.cooldownMs = 60000;
1150055
+ }
1150056
+ function isObserverCircuitOpen() {
1150057
+ if (OBSERVER_CIRCUIT_BREAKER.state !== "open")
1150058
+ return false;
1150059
+ if (Date.now() >= OBSERVER_CIRCUIT_BREAKER.nextProbeAt)
1150060
+ return false;
1150061
+ return true;
1150062
+ }
1150027
1150063
  async function handleIncomingMessage(msg) {
1150028
1150064
  const content = msg.content.trim();
1150029
1150065
  const isObserveOnly = msg.metadata?.observeOnly === true;
@@ -1150271,12 +1150307,16 @@ function splitMessage(content, maxLength) {
1150271
1150307
  return parts;
1150272
1150308
  }
1150273
1150309
  async function processObserveOnlyMessage(msg) {
1150310
+ if (isObserverCircuitOpen()) {
1150311
+ return;
1150312
+ }
1150274
1150313
  const observeSessionKey = `observe-${msg.connectionId}`;
1150275
1150314
  const { session: session3 } = getOrCreateSession(msg.connectionId, observeSessionKey, "Observer", undefined, msg.channelType);
1150276
1150315
  const settings = getSettings();
1150277
1150316
  const observerProvider = settings.assistant.observerProvider ?? settings.assistant.provider;
1150278
1150317
  if (observerProvider === "opencode") {
1150279
1150318
  try {
1150319
+ let hadError = false;
1150280
1150320
  const stream2 = _deps.streamOpencodeObserverMessage(session3.id, msg.content, {
1150281
1150321
  channelType: msg.channelType,
1150282
1150322
  senderId: msg.senderId,
@@ -1150284,15 +1150324,21 @@ async function processObserveOnlyMessage(msg) {
1150284
1150324
  });
1150285
1150325
  for await (const event of stream2) {
1150286
1150326
  if (event.type === "error") {
1150327
+ hadError = true;
1150287
1150328
  const errorMsg = event.data.message;
1150288
1150329
  log2.messaging.error("Error in OpenCode observe-only processing", { error: errorMsg });
1150289
1150330
  }
1150290
1150331
  }
1150332
+ if (hadError)
1150333
+ recordObserverFailure();
1150334
+ else
1150335
+ recordObserverSuccess();
1150291
1150336
  } catch (err) {
1150292
1150337
  log2.messaging.error("Error processing observe-only message via OpenCode", {
1150293
1150338
  connectionId: msg.connectionId,
1150294
1150339
  error: String(err)
1150295
1150340
  });
1150341
+ recordObserverFailure();
1150296
1150342
  }
1150297
1150343
  return;
1150298
1150344
  }
@@ -1150308,6 +1150354,7 @@ async function processObserveOnlyMessage(msg) {
1150308
1150354
  };
1150309
1150355
  const systemPrompt = getObserveOnlySystemPrompt(msg.channelType, context);
1150310
1150356
  try {
1150357
+ let hadError = false;
1150311
1150358
  const observerModelId = settings.assistant.observerModel;
1150312
1150359
  const stream2 = _deps.streamMessage(session3.id, msg.content, {
1150313
1150360
  systemPromptAdditions: systemPrompt,
@@ -1150316,18 +1150363,24 @@ async function processObserveOnlyMessage(msg) {
1150316
1150363
  });
1150317
1150364
  for await (const event of stream2) {
1150318
1150365
  if (event.type === "error") {
1150366
+ hadError = true;
1150319
1150367
  const errorMsg = event.data.message;
1150320
1150368
  log2.messaging.error("Error in observe-only message processing", { error: errorMsg });
1150321
1150369
  }
1150322
1150370
  }
1150371
+ if (hadError)
1150372
+ recordObserverFailure();
1150373
+ else
1150374
+ recordObserverSuccess();
1150323
1150375
  } catch (err) {
1150324
1150376
  log2.messaging.error("Error processing observe-only message", {
1150325
1150377
  connectionId: msg.connectionId,
1150326
1150378
  error: String(err)
1150327
1150379
  });
1150380
+ recordObserverFailure();
1150328
1150381
  }
1150329
1150382
  }
1150330
- var _deps, SLACK_RESPONSE_SCHEMA, COMMANDS;
1150383
+ var _deps, SLACK_RESPONSE_SCHEMA, OBSERVER_CIRCUIT_BREAKER, COMMANDS;
1150331
1150384
  var init_message_handler = __esm(() => {
1150332
1150385
  init_logger3();
1150333
1150386
  init_channel_manager();
@@ -1150354,6 +1150407,14 @@ var init_message_handler = __esm(() => {
1150354
1150407
  },
1150355
1150408
  required: ["body"]
1150356
1150409
  };
1150410
+ OBSERVER_CIRCUIT_BREAKER = {
1150411
+ failureCount: 0,
1150412
+ failureThreshold: 3,
1150413
+ state: "closed",
1150414
+ nextProbeAt: 0,
1150415
+ cooldownMs: 60000,
1150416
+ maxCooldownMs: 600000
1150417
+ };
1150357
1150418
  COMMANDS = {
1150358
1150419
  RESET: ["/reset", "/new", "/clear"],
1150359
1150420
  HELP: ["/help", "/?"],
@@ -1251751,7 +1251812,7 @@ var toolRegistry = [
1251751
1251812
  },
1251752
1251813
  {
1251753
1251814
  name: "create_calendar_event",
1251754
- description: "Create a new calendar event on a CalDAV calendar",
1251815
+ description: "Create a new calendar event on a CalDAV or Google calendar.",
1251755
1251816
  category: "caldav",
1251756
1251817
  keywords: ["calendar", "event", "create", "new", "schedule", "appointment", "meeting"],
1251757
1251818
  defer_loading: true
@@ -1254470,7 +1254531,7 @@ mcpRoutes.all("/", async (c) => {
1254470
1254531
  });
1254471
1254532
  const server2 = new McpServer({
1254472
1254533
  name: "fulcrum",
1254473
- version: "3.2.0"
1254534
+ version: "3.2.1"
1254474
1254535
  });
1254475
1254536
  const client3 = new FulcrumClient(`http://localhost:${port}`);
1254476
1254537
  registerTools(server2, client3);
@@ -1255793,6 +1255854,7 @@ init_settings();
1255793
1255854
  init_logger3();
1255794
1255855
  init_ical_helpers();
1255795
1255856
  init_caldav_account_manager();
1255857
+ init_google_calendar_manager();
1255796
1255858
  var logger9 = createLogger("CalDAV");
1255797
1255859
  function migrateFromSettings() {
1255798
1255860
  const settings = getSettings();
@@ -1256098,6 +1256160,20 @@ async function createEvent(input) {
1256098
1256160
  if (!calendar2) {
1256099
1256161
  throw new Error(`Calendar not found: ${input.calendarId}`);
1256100
1256162
  }
1256163
+ if (calendar2.googleAccountId) {
1256164
+ await googleCalendarManager.createEvent(calendar2.id, {
1256165
+ summary: input.summary,
1256166
+ dtstart: input.dtstart,
1256167
+ dtend: input.dtend,
1256168
+ description: input.description,
1256169
+ location: input.location,
1256170
+ allDay: input.allDay
1256171
+ });
1256172
+ const created = db2.select().from(caldavEvents).where(eq(caldavEvents.calendarId, input.calendarId)).orderBy(desc(caldavEvents.createdAt)).limit(1).get();
1256173
+ if (!created)
1256174
+ throw new Error("Failed to retrieve created Google Calendar event");
1256175
+ return created;
1256176
+ }
1256101
1256177
  const client4 = calendar2.accountId ? accountManager.getClient(calendar2.accountId) : null;
1256102
1256178
  if (!client4) {
1256103
1256179
  throw new Error("CalDAV account not connected for this calendar");
@@ -1256154,6 +1256230,10 @@ async function updateEvent(id, updates) {
1256154
1256230
  throw new Error(`Event not found: ${id}`);
1256155
1256231
  }
1256156
1256232
  const calendar2 = db2.select().from(caldavCalendars).where(eq(caldavCalendars.id, event.calendarId)).get();
1256233
+ if (calendar2?.googleAccountId) {
1256234
+ await googleCalendarManager.updateEvent(id, updates);
1256235
+ return db2.select().from(caldavEvents).where(eq(caldavEvents.id, id)).get();
1256236
+ }
1256157
1256237
  const client4 = calendar2?.accountId ? accountManager.getClient(calendar2.accountId) : null;
1256158
1256238
  if (!client4) {
1256159
1256239
  throw new Error("CalDAV account not connected for this calendar");
@@ -1256200,6 +1256280,10 @@ async function deleteEvent(id) {
1256200
1256280
  throw new Error(`Event not found: ${id}`);
1256201
1256281
  }
1256202
1256282
  const calendar2 = db2.select().from(caldavCalendars).where(eq(caldavCalendars.id, event.calendarId)).get();
1256283
+ if (calendar2?.googleAccountId) {
1256284
+ await googleCalendarManager.deleteEvent(id);
1256285
+ return;
1256286
+ }
1256203
1256287
  const client4 = calendar2?.accountId ? accountManager.getClient(calendar2.accountId) : null;
1256204
1256288
  if (!client4) {
1256205
1256289
  throw new Error("CalDAV account not connected for this calendar");