@knowsuchagency/fulcrum 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server/index.js
CHANGED
|
@@ -4611,6 +4611,7 @@ var init_schema = __esm(() => {
|
|
|
4611
4611
|
isFavorite: integer("is_favorite", { mode: "boolean" }).default(false),
|
|
4612
4612
|
messageCount: integer("message_count").default(0),
|
|
4613
4613
|
lastMessageAt: text("last_message_at"),
|
|
4614
|
+
claudeSessionId: text("claude_session_id"),
|
|
4614
4615
|
createdAt: text("created_at").notNull(),
|
|
4615
4616
|
updatedAt: text("updated_at").notNull()
|
|
4616
4617
|
});
|
|
@@ -296775,13 +296776,26 @@ var require_dist18 = __commonJS((exports) => {
|
|
|
296775
296776
|
});
|
|
296776
296777
|
|
|
296777
296778
|
// server/services/channels/slack-channel.ts
|
|
296778
|
-
|
|
296779
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
296780
|
+
import { basename as basename2 } from "path";
|
|
296781
|
+
var import_bolt, SUPPORTED_MIME_TYPES, MAX_FILE_SIZE, SlackChannel;
|
|
296779
296782
|
var init_slack_channel = __esm(() => {
|
|
296780
296783
|
init_drizzle_orm();
|
|
296781
296784
|
init_db2();
|
|
296782
296785
|
init_logger3();
|
|
296783
296786
|
init_settings();
|
|
296784
296787
|
import_bolt = __toESM(require_dist18(), 1);
|
|
296788
|
+
SUPPORTED_MIME_TYPES = new Set([
|
|
296789
|
+
"image/png",
|
|
296790
|
+
"image/jpeg",
|
|
296791
|
+
"image/gif",
|
|
296792
|
+
"image/webp",
|
|
296793
|
+
"application/pdf",
|
|
296794
|
+
"text/plain",
|
|
296795
|
+
"text/csv",
|
|
296796
|
+
"text/markdown"
|
|
296797
|
+
]);
|
|
296798
|
+
MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
296785
296799
|
SlackChannel = class SlackChannel {
|
|
296786
296800
|
type = "slack";
|
|
296787
296801
|
connectionId;
|
|
@@ -296925,8 +296939,9 @@ var init_slack_channel = __esm(() => {
|
|
|
296925
296939
|
async handleMessage(message) {
|
|
296926
296940
|
if (message.bot_id || message.subtype === "bot_message")
|
|
296927
296941
|
return;
|
|
296928
|
-
const content = message.text;
|
|
296929
|
-
|
|
296942
|
+
const content = message.text || "";
|
|
296943
|
+
const files = message.files;
|
|
296944
|
+
if (!content && (!files || files.length === 0))
|
|
296930
296945
|
return;
|
|
296931
296946
|
const userId = message.user;
|
|
296932
296947
|
if (!userId)
|
|
@@ -296938,18 +296953,31 @@ var init_slack_channel = __esm(() => {
|
|
|
296938
296953
|
senderName = userInfo.user?.real_name || userInfo.user?.name;
|
|
296939
296954
|
}
|
|
296940
296955
|
} catch {}
|
|
296956
|
+
let attachments;
|
|
296957
|
+
if (files && files.length > 0) {
|
|
296958
|
+
const downloaded = await this.downloadSlackFiles(files);
|
|
296959
|
+
if (downloaded.length > 0) {
|
|
296960
|
+
attachments = downloaded;
|
|
296961
|
+
}
|
|
296962
|
+
}
|
|
296941
296963
|
const incomingMessage = {
|
|
296942
296964
|
channelType: "slack",
|
|
296943
296965
|
connectionId: this.connectionId,
|
|
296944
296966
|
senderId: userId,
|
|
296945
296967
|
senderName,
|
|
296946
296968
|
content,
|
|
296947
|
-
|
|
296969
|
+
attachments,
|
|
296970
|
+
timestamp: new Date(parseFloat(message.ts) * 1000),
|
|
296971
|
+
metadata: files?.length ? {
|
|
296972
|
+
files: files.map((f) => ({ name: f.name, type: f.mimetype, size: f.size }))
|
|
296973
|
+
} : undefined
|
|
296948
296974
|
};
|
|
296949
296975
|
log2.messaging.info("Slack message received", {
|
|
296950
296976
|
connectionId: this.connectionId,
|
|
296951
296977
|
from: userId,
|
|
296952
|
-
contentLength: content.length
|
|
296978
|
+
contentLength: content.length,
|
|
296979
|
+
fileCount: files?.length ?? 0,
|
|
296980
|
+
attachmentCount: attachments?.length ?? 0
|
|
296953
296981
|
});
|
|
296954
296982
|
try {
|
|
296955
296983
|
await this.events?.onMessage(incomingMessage);
|
|
@@ -296960,6 +296988,110 @@ var init_slack_channel = __esm(() => {
|
|
|
296960
296988
|
});
|
|
296961
296989
|
}
|
|
296962
296990
|
}
|
|
296991
|
+
async downloadSlackFiles(files) {
|
|
296992
|
+
const results = [];
|
|
296993
|
+
for (const file of files) {
|
|
296994
|
+
try {
|
|
296995
|
+
const attachment = await this.downloadSlackFile(file);
|
|
296996
|
+
if (attachment) {
|
|
296997
|
+
results.push(attachment);
|
|
296998
|
+
}
|
|
296999
|
+
} catch (err) {
|
|
297000
|
+
log2.messaging.warn("Failed to download Slack file", {
|
|
297001
|
+
connectionId: this.connectionId,
|
|
297002
|
+
fileName: file.name,
|
|
297003
|
+
error: String(err)
|
|
297004
|
+
});
|
|
297005
|
+
}
|
|
297006
|
+
}
|
|
297007
|
+
return results;
|
|
297008
|
+
}
|
|
297009
|
+
async downloadSlackFile(file) {
|
|
297010
|
+
if (!file.url_private_download) {
|
|
297011
|
+
log2.messaging.warn("Slack file has no download URL", {
|
|
297012
|
+
fileName: file.name,
|
|
297013
|
+
fileId: file.id
|
|
297014
|
+
});
|
|
297015
|
+
return null;
|
|
297016
|
+
}
|
|
297017
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
297018
|
+
log2.messaging.warn("Slack file too large, skipping", {
|
|
297019
|
+
fileName: file.name,
|
|
297020
|
+
size: file.size,
|
|
297021
|
+
maxSize: MAX_FILE_SIZE
|
|
297022
|
+
});
|
|
297023
|
+
return null;
|
|
297024
|
+
}
|
|
297025
|
+
if (!SUPPORTED_MIME_TYPES.has(file.mimetype)) {
|
|
297026
|
+
log2.messaging.warn("Unsupported Slack file type, skipping", {
|
|
297027
|
+
fileName: file.name,
|
|
297028
|
+
mimeType: file.mimetype
|
|
297029
|
+
});
|
|
297030
|
+
return null;
|
|
297031
|
+
}
|
|
297032
|
+
let response = await fetch(file.url_private_download, {
|
|
297033
|
+
headers: {
|
|
297034
|
+
Authorization: `Bearer ${this.botToken}`
|
|
297035
|
+
},
|
|
297036
|
+
redirect: "manual"
|
|
297037
|
+
});
|
|
297038
|
+
if (response.status >= 300 && response.status < 400) {
|
|
297039
|
+
const location = response.headers.get("location");
|
|
297040
|
+
if (location) {
|
|
297041
|
+
response = await fetch(location);
|
|
297042
|
+
}
|
|
297043
|
+
}
|
|
297044
|
+
if (!response.ok) {
|
|
297045
|
+
log2.messaging.warn("Failed to download Slack file", {
|
|
297046
|
+
fileName: file.name,
|
|
297047
|
+
status: response.status
|
|
297048
|
+
});
|
|
297049
|
+
return null;
|
|
297050
|
+
}
|
|
297051
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim();
|
|
297052
|
+
if (contentType && contentType !== file.mimetype) {
|
|
297053
|
+
log2.messaging.warn("Slack file content-type mismatch", {
|
|
297054
|
+
fileName: file.name,
|
|
297055
|
+
expected: file.mimetype,
|
|
297056
|
+
actual: contentType
|
|
297057
|
+
});
|
|
297058
|
+
}
|
|
297059
|
+
const isText = file.mimetype.startsWith("text/");
|
|
297060
|
+
const type = file.mimetype.startsWith("image/") ? "image" : isText ? "text" : "document";
|
|
297061
|
+
if (isText) {
|
|
297062
|
+
const text3 = await response.text();
|
|
297063
|
+
return {
|
|
297064
|
+
mediaType: file.mimetype,
|
|
297065
|
+
data: text3,
|
|
297066
|
+
filename: file.name,
|
|
297067
|
+
type
|
|
297068
|
+
};
|
|
297069
|
+
}
|
|
297070
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
297071
|
+
if (file.mimetype === "application/pdf" && buffer.length >= 4) {
|
|
297072
|
+
const header = buffer.subarray(0, 5).toString("ascii");
|
|
297073
|
+
if (!header.startsWith("%PDF-")) {
|
|
297074
|
+
log2.messaging.warn("Downloaded file is not a valid PDF", {
|
|
297075
|
+
fileName: file.name,
|
|
297076
|
+
header: buffer.subarray(0, 16).toString("hex"),
|
|
297077
|
+
size: buffer.length
|
|
297078
|
+
});
|
|
297079
|
+
return null;
|
|
297080
|
+
}
|
|
297081
|
+
}
|
|
297082
|
+
log2.messaging.info("Downloaded Slack file", {
|
|
297083
|
+
fileName: file.name,
|
|
297084
|
+
mimeType: file.mimetype,
|
|
297085
|
+
size: buffer.length,
|
|
297086
|
+
type
|
|
297087
|
+
});
|
|
297088
|
+
return {
|
|
297089
|
+
mediaType: file.mimetype,
|
|
297090
|
+
data: buffer.toString("base64"),
|
|
297091
|
+
filename: file.name,
|
|
297092
|
+
type
|
|
297093
|
+
};
|
|
297094
|
+
}
|
|
296963
297095
|
async handleSlashCommand(command, respond) {
|
|
296964
297096
|
log2.messaging.info("Slack slash command received", {
|
|
296965
297097
|
connectionId: this.connectionId,
|
|
@@ -297025,6 +297157,31 @@ var init_slack_channel = __esm(() => {
|
|
|
297025
297157
|
if (!channelId) {
|
|
297026
297158
|
throw new Error("Failed to open DM channel");
|
|
297027
297159
|
}
|
|
297160
|
+
if (metadata?.filePath) {
|
|
297161
|
+
const filePath = metadata.filePath;
|
|
297162
|
+
try {
|
|
297163
|
+
const fileData = readFileSync7(filePath);
|
|
297164
|
+
const filename = basename2(filePath);
|
|
297165
|
+
await this.app.client.files.uploadV2({
|
|
297166
|
+
channel_id: channelId,
|
|
297167
|
+
file: fileData,
|
|
297168
|
+
filename,
|
|
297169
|
+
initial_comment: content || undefined
|
|
297170
|
+
});
|
|
297171
|
+
log2.messaging.info("Slack file uploaded", {
|
|
297172
|
+
connectionId: this.connectionId,
|
|
297173
|
+
to: recipientId,
|
|
297174
|
+
filename
|
|
297175
|
+
});
|
|
297176
|
+
return true;
|
|
297177
|
+
} catch (fileErr) {
|
|
297178
|
+
log2.messaging.error("Failed to upload Slack file", {
|
|
297179
|
+
connectionId: this.connectionId,
|
|
297180
|
+
filePath,
|
|
297181
|
+
error: String(fileErr)
|
|
297182
|
+
});
|
|
297183
|
+
}
|
|
297184
|
+
}
|
|
297028
297185
|
if (content.length <= 4000) {
|
|
297029
297186
|
const messageOptions = {
|
|
297030
297187
|
channel: channelId,
|
|
@@ -1136411,6 +1136568,7 @@ A message has arrived:
|
|
|
1136411
1136568
|
**Channel**: ${context.channel}
|
|
1136412
1136569
|
**From**: ${context.sender}${context.senderName ? ` (${context.senderName})` : ""}
|
|
1136413
1136570
|
**Content**: ${context.content}
|
|
1136571
|
+
${context.hasAttachments ? `**Attachments**: ${context.attachmentNames?.join(", ") || "file(s) attached"}` : ""}
|
|
1136414
1136572
|
${context.metadata?.subject ? `**Subject**: ${context.metadata.subject}` : ""}
|
|
1136415
1136573
|
${context.metadata?.threadId ? `**Thread ID**: ${context.metadata.threadId}` : ""}
|
|
1136416
1136574
|
|
|
@@ -1136441,7 +1136599,7 @@ ${formattingGuide}`;
|
|
|
1136441
1136599
|
function getObserveOnlySystemPrompt(channelType, context) {
|
|
1136442
1136600
|
return `## Observe-Only Mode
|
|
1136443
1136601
|
|
|
1136444
|
-
You are
|
|
1136602
|
+
You are the user's observer \u2014 your core job is to prevent things from falling through the cracks. When you see something actionable, make it visible by creating a Fulcrum task. Memories are secondary \u2014 useful for tracking context that doesn't warrant a task.
|
|
1136445
1136603
|
|
|
1136446
1136604
|
**Channel**: ${context.channel}
|
|
1136447
1136605
|
**From**: ${context.sender}${context.senderName ? ` (${context.senderName})` : ""}
|
|
@@ -1136455,20 +1136613,35 @@ ${context.metadata?.isGroup ? `**Group Chat**: yes` : ""}
|
|
|
1136455
1136613
|
|
|
1136456
1136614
|
## Available Tools
|
|
1136457
1136615
|
|
|
1136458
|
-
|
|
1136459
|
-
- \`
|
|
1136460
|
-
- \`
|
|
1136616
|
+
### Task tools
|
|
1136617
|
+
- \`list_tasks\` - Search existing tasks (use to check for duplicates before creating)
|
|
1136618
|
+
- \`create_task\` - Create a new task with title, description, tags, and dueDate
|
|
1136619
|
+
- \`update_task\` - Update an existing task's title or description
|
|
1136620
|
+
- \`add_task_link\` - Add a URL link to a task
|
|
1136621
|
+
- \`add_task_tag\` - Add a tag to an existing task
|
|
1136622
|
+
- \`set_task_due_date\` - Set or update a task's due date
|
|
1136623
|
+
|
|
1136624
|
+
### Memory tools
|
|
1136625
|
+
- \`memory_store\` - Store observations with tags
|
|
1136626
|
+
- \`memory_search\` - Search existing memories to avoid duplicates
|
|
1136627
|
+
- \`memory_list\` - List existing memories by tag
|
|
1136628
|
+
- \`memory_file_read\` - Read the master memory file (read-only in observer mode)
|
|
1136629
|
+
|
|
1136630
|
+
### Notification tools
|
|
1136631
|
+
- \`send_notification\` - Send a notification to the user (desktop, sound, Slack, etc.)
|
|
1136461
1136632
|
|
|
1136462
1136633
|
## Your Task
|
|
1136463
1136634
|
|
|
1136464
|
-
Silently analyze this message and
|
|
1136635
|
+
Silently analyze this message and take the appropriate action:
|
|
1136465
1136636
|
|
|
1136466
|
-
1. **Actionable requests directed at the user** (deadlines, meetings, tasks) \u2192
|
|
1136467
|
-
2. **
|
|
1136468
|
-
3. **Important persistent observations** (learning someone's name, recurring topics, key relationships) \u2192
|
|
1136637
|
+
1. **Actionable requests directed at the user** (deadlines, meetings, tasks, to-dos, follow-ups) \u2192 Use \`list_tasks\` to check for duplicates, then \`create_task\` with a clear title, description, relevant tags, and dueDate if mentioned. Tag with \`from:${context.channel}\`. After creating a task, use \`send_notification\` to alert the user (e.g., title: "New task from ${context.channel}", message: the task title).
|
|
1136638
|
+
2. **Updates about existing matters** (confirmations, status changes, new details on known topics) \u2192 Use \`list_tasks\` to find the related task, then \`update_task\` or \`add_task_link\` as appropriate.
|
|
1136639
|
+
3. **Important persistent observations** (learning someone's name, recurring topics, key relationships) \u2192 \`memory_store\` with tag \`persistent\`
|
|
1136469
1136640
|
4. **Casual messages, spam, or irrelevant content** \u2192 Do nothing
|
|
1136470
1136641
|
|
|
1136471
|
-
|
|
1136642
|
+
When creating tasks, write the title as a clear action item (e.g., "Arrange cow rental for parade" not "Message about cow rental"). Include the sender and channel context in the description.
|
|
1136643
|
+
|
|
1136644
|
+
**Always use \`memory_store\`** for non-task observations \u2014 never write to MEMORY.md directly. The hourly sweep reviews stored memories and promotes important patterns to the memory file.
|
|
1136472
1136645
|
Include the source channel as the \`source\` field (e.g., "channel:${context.channel}").
|
|
1136473
1136646
|
|
|
1136474
1136647
|
## Security Warning
|
|
@@ -1136498,7 +1136671,7 @@ You are performing your hourly sweep.
|
|
|
1136498
1136671
|
## Your Task
|
|
1136499
1136672
|
|
|
1136500
1136673
|
1. **Review actionable memories** - use \`memory_search\` to find memories tagged \`actionable\` or \`monitoring\` and check for:
|
|
1136501
|
-
- Items that have been resolved
|
|
1136674
|
+
- Items that have been resolved \u2192 delete with \`memory_delete\`
|
|
1136502
1136675
|
- Patterns or connections between tracked items
|
|
1136503
1136676
|
- Items that should be linked to tasks
|
|
1136504
1136677
|
|
|
@@ -1136511,14 +1136684,24 @@ You are performing your hourly sweep.
|
|
|
1136511
1136684
|
- Store memories with tag \`actionable\` for missed items
|
|
1136512
1136685
|
- Take action if still relevant
|
|
1136513
1136686
|
|
|
1136514
|
-
4. **
|
|
1136687
|
+
4. **Clean up memories** - use \`memory_list\` and \`memory_delete\` to:
|
|
1136688
|
+
- Delete resolved or outdated memories
|
|
1136689
|
+
- Remove duplicates
|
|
1136690
|
+
|
|
1136691
|
+
5. **Curate MEMORY.md** - read with \`memory_file_read\` and check if it needs updates:
|
|
1136692
|
+
- Remove genuinely stale, duplicate, or outdated entries
|
|
1136693
|
+
- Move ephemeral observations (one-time events, transient status) to \`memory_store\` with appropriate tags, then remove from the file
|
|
1136694
|
+
- **Promote recurring patterns**: search \`memory_store\` for memories tagged \`persistent\` \u2014 if a pattern appears consistently, add it to MEMORY.md and delete the individual memories
|
|
1136695
|
+
- Keep: user preferences, project conventions, recurring patterns, key relationships
|
|
1136696
|
+
- Rewrite the cleaned file with \`memory_file_update\` if changes are needed
|
|
1136697
|
+
- Do NOT remove content just to reduce size \u2014 only remove what is genuinely stale, duplicate, or ephemeral
|
|
1136515
1136698
|
|
|
1136516
1136699
|
## Output
|
|
1136517
1136700
|
|
|
1136518
1136701
|
After completing your sweep, provide a brief summary of:
|
|
1136519
1136702
|
- Memories reviewed and actions taken
|
|
1136520
1136703
|
- Tasks updated or created
|
|
1136521
|
-
-
|
|
1136704
|
+
- MEMORY.md changes (stale items removed, ephemeral items migrated to memory_store, patterns promoted from memory_store)
|
|
1136522
1136705
|
- Items requiring user attention`;
|
|
1136523
1136706
|
}
|
|
1136524
1136707
|
function getRitualSystemPrompt(type) {
|
|
@@ -1136529,6 +1136712,10 @@ function getRitualSystemPrompt(type) {
|
|
|
1136529
1136712
|
|
|
1136530
1136713
|
You are performing your morning ritual.
|
|
1136531
1136714
|
|
|
1136715
|
+
## Memory Check
|
|
1136716
|
+
|
|
1136717
|
+
Before composing your briefing, read MEMORY.md with \`memory_file_read\` for context about ongoing matters, preferences, and recent patterns.
|
|
1136718
|
+
|
|
1136532
1136719
|
## Output Channels
|
|
1136533
1136720
|
|
|
1136534
1136721
|
Use the \`list_messaging_channels\` tool to discover which messaging channels are available and connected.
|
|
@@ -1136540,6 +1136727,15 @@ Then use the \`message\` tool to send your briefing \u2014 just specify \`channe
|
|
|
1136540
1136727
|
|
|
1136541
1136728
|
You are performing your evening ritual.
|
|
1136542
1136729
|
|
|
1136730
|
+
## Memory Maintenance
|
|
1136731
|
+
|
|
1136732
|
+
Before composing your summary, curate MEMORY.md if changes are needed:
|
|
1136733
|
+
1. Read MEMORY.md with \`memory_file_read\`
|
|
1136734
|
+
2. Remove genuinely stale, duplicate, or outdated content
|
|
1136735
|
+
3. Move ephemeral items to \`memory_store\` with appropriate tags
|
|
1136736
|
+
4. Search \`memory_store\` for memories tagged \`persistent\` \u2014 promote recurring patterns into MEMORY.md and delete the individual memories
|
|
1136737
|
+
5. Rewrite the file with \`memory_file_update\` only if changes were made
|
|
1136738
|
+
|
|
1136543
1136739
|
## Output Channels
|
|
1136544
1136740
|
|
|
1136545
1136741
|
Use the \`list_messaging_channels\` tool to discover which messaging channels are available and connected.
|
|
@@ -1136557,40 +1136753,35 @@ WhatsApp does NOT render full Markdown. Keep formatting simple:
|
|
|
1136557
1136753
|
case "slack":
|
|
1136558
1136754
|
return `## Slack Formatting & Block Kit
|
|
1136559
1136755
|
|
|
1136560
|
-
|
|
1136756
|
+
Wrap your entire response in \`<slack-response>\` XML tags containing a JSON object with:
|
|
1136561
1136757
|
|
|
1136562
1136758
|
- **body** (required): Plain text message shown in notifications and as fallback
|
|
1136563
1136759
|
- **blocks** (optional): Array of Slack Block Kit blocks for rich formatting
|
|
1136760
|
+
- **filePath** (optional): Absolute path to a file on disk to upload as an attachment
|
|
1136761
|
+
|
|
1136762
|
+
Example:
|
|
1136763
|
+
<slack-response>
|
|
1136764
|
+
{"body": "Here are your open tasks", "blocks": [{"type": "section", "text": {"type": "mrkdwn", "text": "*Open Tasks:*\\n\u2022 Task 1\\n\u2022 Task 2"}}]}
|
|
1136765
|
+
</slack-response>
|
|
1136766
|
+
|
|
1136767
|
+
To send a file (image, document, etc.) you created:
|
|
1136768
|
+
<slack-response>
|
|
1136769
|
+
{"body": "Here's the generated image", "filePath": "/absolute/path/to/file.png"}
|
|
1136770
|
+
</slack-response>
|
|
1136564
1136771
|
|
|
1136565
1136772
|
### Block Kit Blocks
|
|
1136566
1136773
|
|
|
1136567
1136774
|
**Section Block** - Main content:
|
|
1136568
|
-
|
|
1136569
|
-
{ "type": "section", "text": { "type": "mrkdwn", "text": "*Bold* and _italic_" } }
|
|
1136570
|
-
\`\`\`
|
|
1136775
|
+
\`{"type": "section", "text": {"type": "mrkdwn", "text": "*Bold* and _italic_"}}\`
|
|
1136571
1136776
|
|
|
1136572
1136777
|
**Section with Fields** - Multi-column layout:
|
|
1136573
|
-
|
|
1136574
|
-
{ "type": "section", "fields": [
|
|
1136575
|
-
{ "type": "mrkdwn", "text": "*Status:*\\nIn Progress" },
|
|
1136576
|
-
{ "type": "mrkdwn", "text": "*Due:*\\nToday" }
|
|
1136577
|
-
] }
|
|
1136578
|
-
\`\`\`
|
|
1136778
|
+
\`{"type": "section", "fields": [{"type": "mrkdwn", "text": "*Status:*\\nIn Progress"}, {"type": "mrkdwn", "text": "*Due:*\\nToday"}]}\`
|
|
1136579
1136779
|
|
|
1136580
|
-
**Header Block
|
|
1136581
|
-
\`\`\`json
|
|
1136582
|
-
{ "type": "header", "text": { "type": "plain_text", "text": "Task Summary", "emoji": true } }
|
|
1136583
|
-
\`\`\`
|
|
1136780
|
+
**Header Block**: \`{"type": "header", "text": {"type": "plain_text", "text": "Title", "emoji": true}}\`
|
|
1136584
1136781
|
|
|
1136585
|
-
**Divider Block
|
|
1136586
|
-
\`\`\`json
|
|
1136587
|
-
{ "type": "divider" }
|
|
1136588
|
-
\`\`\`
|
|
1136782
|
+
**Divider Block**: \`{"type": "divider"}\`
|
|
1136589
1136783
|
|
|
1136590
|
-
**Context Block
|
|
1136591
|
-
\`\`\`json
|
|
1136592
|
-
{ "type": "context", "elements": [{ "type": "mrkdwn", "text": "Last updated 5 min ago" }] }
|
|
1136593
|
-
\`\`\`
|
|
1136784
|
+
**Context Block**: \`{"type": "context", "elements": [{"type": "mrkdwn", "text": "Small muted text"}]}\`
|
|
1136594
1136785
|
|
|
1136595
1136786
|
### mrkdwn Syntax
|
|
1136596
1136787
|
- *bold* with single asterisks
|
|
@@ -1136605,7 +1136796,9 @@ Your response will be structured as JSON with two fields:
|
|
|
1136605
1136796
|
- **Lists/Status**: Use section blocks with bullet points or fields
|
|
1136606
1136797
|
- **Structured Data**: Use fields for key-value pairs side by side
|
|
1136607
1136798
|
- **Headers**: Use header blocks for major sections
|
|
1136608
|
-
- **Simple Responses**: Just set body to your plain text
|
|
1136799
|
+
- **Simple Responses**: Just set body to your plain text, omit blocks
|
|
1136800
|
+
|
|
1136801
|
+
**IMPORTANT**: Always wrap your response in \`<slack-response>\` tags with valid JSON inside.`;
|
|
1136609
1136802
|
case "discord":
|
|
1136610
1136803
|
return `## Discord Formatting
|
|
1136611
1136804
|
|
|
@@ -1138553,7 +1138746,7 @@ function s_({ prompt: X, options: Q }) {
|
|
|
1138553
1138746
|
let L6 = TI(import.meta.url), q6 = tz(L6, "..");
|
|
1138554
1138747
|
B = tz(q6, "cli.js");
|
|
1138555
1138748
|
}
|
|
1138556
|
-
process.env.CLAUDE_AGENT_SDK_VERSION = "0.2.
|
|
1138749
|
+
process.env.CLAUDE_AGENT_SDK_VERSION = "0.2.37";
|
|
1138557
1138750
|
let { abortController: z = F6(), additionalDirectories: K = [], agent: V, agents: L, allowedTools: U = [], betas: F, canUseTool: q2, continue: N, cwd: w, debug: M, debugFile: R, disallowedTools: S = [], tools: C, env: K0, executable: U0 = M6() ? "bun" : "node", executableArgs: s = [], extraArgs: D0 = {}, fallbackModel: q0, enableFileCheckpointing: L1, forkSession: P1, hooks: o1, includePartialMessages: c, persistSession: Q9, maxThinkingTokens: r6, maxTurns: o6, maxBudgetUsd: t6, mcpServers: a6, model: B4, outputFormat: E0, permissionMode: S1 = "default", allowDangerouslySkipPermissions: s6 = false, permissionPromptToolName: az, plugins: sz, resume: ez, resumeSessionAt: XK, sessionId: QK, stderr: $K, strictMcpConfig: YK } = J, G7 = E0?.type === "json_schema" ? E0.schema : undefined, V6 = K0;
|
|
1138558
1138751
|
if (!V6)
|
|
1138559
1138752
|
V6 = { ...process.env };
|
|
@@ -1147067,8 +1147260,11 @@ You have access to Fulcrum's MCP tools. Use them proactively to help users.
|
|
|
1147067
1147260
|
- \`memory_file_read\` - Read the master memory file (MEMORY.md)
|
|
1147068
1147261
|
- \`memory_file_update\` - Update the file (whole or by section heading)
|
|
1147069
1147262
|
|
|
1147070
|
-
**
|
|
1147263
|
+
**Memory Management:**
|
|
1147071
1147264
|
- \`memory_store\` - Store individual knowledge snippets with optional tags
|
|
1147265
|
+
- \`memory_search\` - Full-text search across memories (FTS5: AND, OR, NOT, "phrases", prefix*)
|
|
1147266
|
+
- \`memory_list\` - List memories with optional tag filter and pagination
|
|
1147267
|
+
- \`memory_delete\` - Delete a memory by ID (for cleanup of resolved/stale items)
|
|
1147072
1147268
|
|
|
1147073
1147269
|
**Google Account & Gmail Tools:**
|
|
1147074
1147270
|
- \`list_google_accounts\` - List all Google accounts with calendar/Gmail status
|
|
@@ -1147402,7 +1147598,7 @@ Fulcrum is your digital concierge - a personal command center where you track ev
|
|
|
1147402
1147598
|
- send_notification
|
|
1147403
1147599
|
- search (unified FTS5 search across tasks, projects, messages, events, memories, conversations; gmail is opt-in via \`entities: ["gmail"]\`)
|
|
1147404
1147600
|
- memory_file_read, memory_file_update (master memory file - always in prompt)
|
|
1147405
|
-
- memory_store (
|
|
1147601
|
+
- memory_store, memory_search, memory_list, memory_delete (persistent knowledge with tags)
|
|
1147406
1147602
|
- message (send to WhatsApp/Discord/Telegram/Slack/Gmail - user-only, concierge mode)
|
|
1147407
1147603
|
- memory_store with tag \`actionable\` (track things needing attention - concierge mode)
|
|
1147408
1147604
|
- search with memoryTags filter (find tracked items by tag)
|
|
@@ -1147411,7 +1147607,7 @@ Fulcrum is your digital concierge - a personal command center where you track ev
|
|
|
1147411
1147607
|
}
|
|
1147412
1147608
|
|
|
1147413
1147609
|
// server/services/memory-file-service.ts
|
|
1147414
|
-
import { readFileSync as
|
|
1147610
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync14 } from "fs";
|
|
1147415
1147611
|
import { join as join13 } from "path";
|
|
1147416
1147612
|
function getMemoryFilePath() {
|
|
1147417
1147613
|
return join13(getFulcrumDir(), MEMORY_FILENAME);
|
|
@@ -1147420,7 +1147616,7 @@ function readMemoryFile() {
|
|
|
1147420
1147616
|
const filePath = getMemoryFilePath();
|
|
1147421
1147617
|
if (!existsSync14(filePath))
|
|
1147422
1147618
|
return "";
|
|
1147423
|
-
return
|
|
1147619
|
+
return readFileSync9(filePath, "utf-8");
|
|
1147424
1147620
|
}
|
|
1147425
1147621
|
function writeMemoryFile(content) {
|
|
1147426
1147622
|
const filePath = getMemoryFilePath();
|
|
@@ -1147614,7 +1147810,12 @@ ${knowledge}`;
|
|
|
1147614
1147810
|
|
|
1147615
1147811
|
## Master Memory File
|
|
1147616
1147812
|
|
|
1147617
|
-
This is your persistent memory.
|
|
1147813
|
+
This is your persistent memory (MEMORY.md), injected into every conversation.
|
|
1147814
|
+
|
|
1147815
|
+
**What belongs here:** user preferences, project conventions, recurring patterns, key relationships, important decisions.
|
|
1147816
|
+
**What does NOT belong:** ephemeral observations, one-time events, transient status \u2014 use \`memory_store\` with appropriate tags instead.
|
|
1147817
|
+
|
|
1147818
|
+
Update with \`memory_file_update\` only for broadly useful, long-term knowledge. The hourly sweep automatically curates this file.
|
|
1147618
1147819
|
|
|
1147619
1147820
|
${memoryFileContent}`;
|
|
1147620
1147821
|
}
|
|
@@ -1147869,19 +1148070,22 @@ async function* streamMessage(sessionId, userMessage, modelIdOrOptions, editorCo
|
|
|
1147869
1148070
|
yield { type: "error", data: { message: "Session not found" } };
|
|
1147870
1148071
|
return;
|
|
1147871
1148072
|
}
|
|
1147872
|
-
|
|
1147873
|
-
|
|
1147874
|
-
|
|
1147875
|
-
|
|
1147876
|
-
|
|
1148073
|
+
if (!options.ephemeral) {
|
|
1148074
|
+
addMessage(sessionId, {
|
|
1148075
|
+
role: "user",
|
|
1148076
|
+
content: userMessage,
|
|
1148077
|
+
sessionId
|
|
1148078
|
+
});
|
|
1148079
|
+
}
|
|
1147877
1148080
|
const settings = getSettings();
|
|
1147878
1148081
|
const port = settings.server.port;
|
|
1147879
1148082
|
const effectiveModelId = options.modelId ?? settings.assistant.model;
|
|
1147880
1148083
|
let state = sessionState.get(sessionId);
|
|
1147881
1148084
|
if (!state) {
|
|
1147882
|
-
state = {};
|
|
1148085
|
+
state = { claudeSessionId: options.ephemeral ? undefined : session3.claudeSessionId ?? undefined };
|
|
1147883
1148086
|
sessionState.set(sessionId, state);
|
|
1147884
1148087
|
}
|
|
1148088
|
+
const resumeSessionId = options.ephemeral ? undefined : state.claudeSessionId;
|
|
1147885
1148089
|
const tempFiles = [];
|
|
1147886
1148090
|
try {
|
|
1147887
1148091
|
log2.assistant.debug("Starting assistant query", {
|
|
@@ -1147929,8 +1148133,15 @@ User message: ${userMessage}`;
|
|
|
1147929
1148133
|
{
|
|
1147930
1148134
|
const tempDir = await mkdtemp(join14(tmpdir3(), "fulcrum-doc-"));
|
|
1147931
1148135
|
const tempPath = join14(tempDir, attachment.filename || "document.pdf");
|
|
1147932
|
-
|
|
1148136
|
+
const docBuffer = Buffer.from(attachment.data, "base64");
|
|
1148137
|
+
await writeFile3(tempPath, docBuffer);
|
|
1147933
1148138
|
tempFiles.push(tempPath);
|
|
1148139
|
+
log2.assistant.info("Wrote document attachment to temp file", {
|
|
1148140
|
+
sessionId,
|
|
1148141
|
+
path: tempPath,
|
|
1148142
|
+
size: docBuffer.length,
|
|
1148143
|
+
header: docBuffer.subarray(0, 8).toString("ascii")
|
|
1148144
|
+
});
|
|
1147934
1148145
|
parts.push(`[Attached document: ${tempPath}]`);
|
|
1147935
1148146
|
}
|
|
1147936
1148147
|
break;
|
|
@@ -1147953,13 +1148164,14 @@ ${attachment.data}`);
|
|
|
1147953
1148164
|
sessionId,
|
|
1147954
1148165
|
requestedModelId: effectiveModelId,
|
|
1147955
1148166
|
resolvedModel: MODEL_MAP[effectiveModelId],
|
|
1147956
|
-
resumeSessionId:
|
|
1148167
|
+
resumeSessionId: resumeSessionId ?? null,
|
|
1148168
|
+
ephemeral: options.ephemeral ?? false
|
|
1147957
1148169
|
});
|
|
1147958
1148170
|
const result = s_({
|
|
1147959
1148171
|
prompt: fullPrompt,
|
|
1147960
1148172
|
options: {
|
|
1147961
1148173
|
model: MODEL_MAP[effectiveModelId],
|
|
1147962
|
-
resume:
|
|
1148174
|
+
resume: resumeSessionId,
|
|
1147963
1148175
|
includePartialMessages: true,
|
|
1147964
1148176
|
pathToClaudeCodeExecutable: getClaudeCodePathForSdk(),
|
|
1147965
1148177
|
mcpServers: {
|
|
@@ -1147973,6 +1148185,7 @@ ${attachment.data}`);
|
|
|
1147973
1148185
|
permissionMode: "bypassPermissions",
|
|
1147974
1148186
|
allowDangerouslySkipPermissions: true,
|
|
1147975
1148187
|
settingSources: ["user"],
|
|
1148188
|
+
...options.ephemeral && { persistSession: false, maxTurns: 3 },
|
|
1147976
1148189
|
...options.outputFormat && { outputFormat: options.outputFormat }
|
|
1147977
1148190
|
}
|
|
1147978
1148191
|
});
|
|
@@ -1147998,7 +1148211,10 @@ ${attachment.data}`);
|
|
|
1147998
1148211
|
}
|
|
1147999
1148212
|
} else if (message.type === "assistant") {
|
|
1148000
1148213
|
const assistantMsg = message;
|
|
1148001
|
-
|
|
1148214
|
+
if (!options.ephemeral) {
|
|
1148215
|
+
state.claudeSessionId = assistantMsg.session_id;
|
|
1148216
|
+
db2.update(chatSessions).set({ claudeSessionId: assistantMsg.session_id }).where(eq(chatSessions.id, sessionId)).run();
|
|
1148217
|
+
}
|
|
1148002
1148218
|
const textContent = assistantMsg.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
1148003
1148219
|
if (textContent) {
|
|
1148004
1148220
|
if (textContent.length > currentText.length) {
|
|
@@ -1148032,7 +1148248,10 @@ ${attachment.data}`);
|
|
|
1148032
1148248
|
const resultMsg = message;
|
|
1148033
1148249
|
if (resultMsg.subtype?.startsWith("error_")) {
|
|
1148034
1148250
|
const errors2 = resultMsg.errors || ["Unknown error"];
|
|
1148035
|
-
|
|
1148251
|
+
if (!options.ephemeral) {
|
|
1148252
|
+
state.claudeSessionId = undefined;
|
|
1148253
|
+
db2.update(chatSessions).set({ claudeSessionId: null }).where(eq(chatSessions.id, sessionId)).run();
|
|
1148254
|
+
}
|
|
1148036
1148255
|
yield { type: "error", data: { message: errors2.join(", ") } };
|
|
1148037
1148256
|
}
|
|
1148038
1148257
|
if (resultMsg.structured_output) {
|
|
@@ -1148045,7 +1148264,7 @@ ${attachment.data}`);
|
|
|
1148045
1148264
|
});
|
|
1148046
1148265
|
}
|
|
1148047
1148266
|
}
|
|
1148048
|
-
if (currentText.trim()) {
|
|
1148267
|
+
if (!options.ephemeral && currentText.trim()) {
|
|
1148049
1148268
|
addMessage(sessionId, {
|
|
1148050
1148269
|
role: "assistant",
|
|
1148051
1148270
|
content: currentText,
|
|
@@ -1148059,7 +1148278,10 @@ ${attachment.data}`);
|
|
|
1148059
1148278
|
} catch (err) {
|
|
1148060
1148279
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1148061
1148280
|
log2.assistant.error("Assistant stream error", { sessionId, error: errorMsg });
|
|
1148062
|
-
|
|
1148281
|
+
if (!options.ephemeral) {
|
|
1148282
|
+
state.claudeSessionId = undefined;
|
|
1148283
|
+
db2.update(chatSessions).set({ claudeSessionId: null }).where(eq(chatSessions.id, sessionId)).run();
|
|
1148284
|
+
}
|
|
1148063
1148285
|
yield { type: "error", data: { message: errorMsg } };
|
|
1148064
1148286
|
} finally {
|
|
1148065
1148287
|
for (const tempPath of tempFiles) {
|
|
@@ -1149957,8 +1150179,51 @@ ${contextualMessage}`;
|
|
|
1149957
1150179
|
}
|
|
1149958
1150180
|
const parsed = JSON.parse(jsonText);
|
|
1149959
1150181
|
if (parsed.actions && Array.isArray(parsed.actions)) {
|
|
1150182
|
+
const settings2 = getSettings();
|
|
1150183
|
+
const fulcrumPort = settings2.server?.port ?? 7777;
|
|
1149960
1150184
|
for (const action of parsed.actions) {
|
|
1149961
|
-
if (action.type === "
|
|
1150185
|
+
if (action.type === "create_task" && action.title) {
|
|
1150186
|
+
try {
|
|
1150187
|
+
const resp = await fetch(`http://localhost:${fulcrumPort}/api/tasks`, {
|
|
1150188
|
+
method: "POST",
|
|
1150189
|
+
headers: { "Content-Type": "application/json" },
|
|
1150190
|
+
body: JSON.stringify({
|
|
1150191
|
+
title: action.title,
|
|
1150192
|
+
description: action.description || null,
|
|
1150193
|
+
status: "TO_DO",
|
|
1150194
|
+
tags: action.tags,
|
|
1150195
|
+
dueDate: action.dueDate || null
|
|
1150196
|
+
})
|
|
1150197
|
+
});
|
|
1150198
|
+
if (!resp.ok) {
|
|
1150199
|
+
log2.messaging.warn("Observer failed to create task via OpenCode", {
|
|
1150200
|
+
sessionId,
|
|
1150201
|
+
status: resp.status,
|
|
1150202
|
+
title: action.title
|
|
1150203
|
+
});
|
|
1150204
|
+
} else {
|
|
1150205
|
+
log2.messaging.info("Observer created task via OpenCode", {
|
|
1150206
|
+
sessionId,
|
|
1150207
|
+
title: action.title
|
|
1150208
|
+
});
|
|
1150209
|
+
try {
|
|
1150210
|
+
await fetch(`http://localhost:${fulcrumPort}/api/config/notifications/send`, {
|
|
1150211
|
+
method: "POST",
|
|
1150212
|
+
headers: { "Content-Type": "application/json" },
|
|
1150213
|
+
body: JSON.stringify({
|
|
1150214
|
+
title: `New task from ${options.channelType}`,
|
|
1150215
|
+
message: action.title
|
|
1150216
|
+
})
|
|
1150217
|
+
});
|
|
1150218
|
+
} catch {}
|
|
1150219
|
+
}
|
|
1150220
|
+
} catch (err) {
|
|
1150221
|
+
log2.messaging.warn("Observer task creation error via OpenCode", {
|
|
1150222
|
+
sessionId,
|
|
1150223
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1150224
|
+
});
|
|
1150225
|
+
}
|
|
1150226
|
+
} else if (action.type === "store_memory" && action.content) {
|
|
1149962
1150227
|
const source = action.source || `channel:${options.channelType}`;
|
|
1149963
1150228
|
await storeMemory({
|
|
1149964
1150229
|
content: action.content,
|
|
@@ -1149990,38 +1150255,59 @@ ${contextualMessage}`;
|
|
|
1149990
1150255
|
yield { type: "error", data: { message: errorMsg } };
|
|
1149991
1150256
|
}
|
|
1149992
1150257
|
}
|
|
1149993
|
-
var OPENCODE_DEFAULT_PORT = 4096, opencodeClient = null, OBSERVER_SYSTEM_PROMPT = `You are
|
|
1150258
|
+
var OPENCODE_DEFAULT_PORT = 4096, opencodeClient = null, OBSERVER_SYSTEM_PROMPT = `You are the user's observer \u2014 your core job is to prevent things from falling through the cracks. When you see something actionable, create a task. Memories are secondary \u2014 useful for context that doesn't warrant a task.
|
|
1149994
1150259
|
|
|
1149995
1150260
|
IMPORTANT: You have NO tools. Instead, respond with a JSON object describing what actions to take.
|
|
1149996
1150261
|
|
|
1149997
1150262
|
Response format (respond with ONLY this JSON, no other text):
|
|
1149998
1150263
|
{
|
|
1149999
1150264
|
"actions": [
|
|
1150265
|
+
{
|
|
1150266
|
+
"type": "create_task",
|
|
1150267
|
+
"title": "Clear action item title",
|
|
1150268
|
+
"description": "Details including sender and context",
|
|
1150269
|
+
"tags": ["from:whatsapp", "errand"],
|
|
1150270
|
+
"dueDate": "2025-02-18"
|
|
1150271
|
+
},
|
|
1150000
1150272
|
{
|
|
1150001
1150273
|
"type": "store_memory",
|
|
1150002
1150274
|
"content": "The fact or information to store",
|
|
1150003
|
-
"tags": ["
|
|
1150275
|
+
"tags": ["persistent"],
|
|
1150004
1150276
|
"source": "channel:whatsapp"
|
|
1150005
1150277
|
}
|
|
1150006
1150278
|
]
|
|
1150007
1150279
|
}
|
|
1150008
1150280
|
|
|
1150009
|
-
If the message contains nothing worth
|
|
1150281
|
+
If the message contains nothing worth tracking (casual chat, greetings, spam, etc.), respond with:
|
|
1150010
1150282
|
{"actions": []}
|
|
1150011
1150283
|
|
|
1150012
|
-
|
|
1150013
|
-
|
|
1150014
|
-
|
|
1150015
|
-
-
|
|
1150016
|
-
|
|
1150017
|
-
|
|
1150018
|
-
|
|
1150284
|
+
## Action types
|
|
1150285
|
+
|
|
1150286
|
+
### create_task (preferred for actionable items)
|
|
1150287
|
+
Use for: deadlines, meetings, to-dos, follow-ups, requests directed at the user.
|
|
1150288
|
+
Fields: title (required, imperative action item), description, tags (array), dueDate (YYYY-MM-DD if mentioned).
|
|
1150289
|
+
Write titles as clear action items (e.g., "Arrange cow rental for parade" not "Message about cow rental").
|
|
1150290
|
+
|
|
1150291
|
+
### store_memory (for non-task observations)
|
|
1150292
|
+
Use for: learning someone's name, recurring patterns, key relationships, context updates.
|
|
1150293
|
+
Fields: content (required), tags (array), source (e.g., "channel:whatsapp").
|
|
1150019
1150294
|
|
|
1150020
|
-
|
|
1150295
|
+
## Guidelines
|
|
1150296
|
+
|
|
1150297
|
+
Create a task for:
|
|
1150298
|
+
- Deadlines, appointments, meetings
|
|
1150299
|
+
- Action items or requests directed at the user
|
|
1150300
|
+
- Follow-ups or reminders
|
|
1150301
|
+
|
|
1150302
|
+
Store a memory for:
|
|
1150303
|
+
- Contact details, names, relationships
|
|
1150304
|
+
- Project context or status updates
|
|
1150305
|
+
- Patterns worth remembering
|
|
1150306
|
+
|
|
1150307
|
+
Do nothing for:
|
|
1150021
1150308
|
- Casual greetings or small talk
|
|
1150022
1150309
|
- Spam or promotional content
|
|
1150023
|
-
- Messages you don't understand
|
|
1150024
|
-
- Trivially obvious information`;
|
|
1150310
|
+
- Messages you don't understand`;
|
|
1150025
1150311
|
var init_opencode_channel_service = __esm(() => {
|
|
1150026
1150312
|
init_dist10();
|
|
1150027
1150313
|
init_logger3();
|
|
@@ -1150104,6 +1150390,8 @@ async function handleIncomingMessage(msg) {
|
|
|
1150104
1150390
|
sender: msg.senderId,
|
|
1150105
1150391
|
senderName: msg.senderName,
|
|
1150106
1150392
|
content,
|
|
1150393
|
+
hasAttachments: (msg.attachments?.length ?? 0) > 0,
|
|
1150394
|
+
attachmentNames: msg.attachments?.map((a) => a.filename),
|
|
1150107
1150395
|
metadata: {
|
|
1150108
1150396
|
subject: msg.metadata?.subject,
|
|
1150109
1150397
|
threadId: msg.metadata?.threadId,
|
|
@@ -1150112,24 +1150400,46 @@ async function handleIncomingMessage(msg) {
|
|
|
1150112
1150400
|
};
|
|
1150113
1150401
|
const systemPrompt = getMessagingSystemPrompt(msg.channelType, context);
|
|
1150114
1150402
|
const isSlack = msg.channelType === "slack";
|
|
1150115
|
-
const stream2 = _deps.streamMessage(session3.id, content, {
|
|
1150403
|
+
const stream2 = _deps.streamMessage(session3.id, content || "(file attached)", {
|
|
1150116
1150404
|
systemPromptAdditions: systemPrompt,
|
|
1150117
|
-
...
|
|
1150405
|
+
...msg.attachments?.length && { attachments: msg.attachments }
|
|
1150118
1150406
|
});
|
|
1150119
1150407
|
let responseText = "";
|
|
1150120
|
-
let
|
|
1150408
|
+
let hasError = false;
|
|
1150121
1150409
|
for await (const event of stream2) {
|
|
1150122
1150410
|
if (event.type === "error") {
|
|
1150123
1150411
|
const errorMsg = event.data.message;
|
|
1150124
1150412
|
log2.messaging.error("Assistant error handling message", { error: errorMsg });
|
|
1150413
|
+
hasError = true;
|
|
1150125
1150414
|
} else if (event.type === "message:complete") {
|
|
1150126
|
-
|
|
1150127
|
-
|
|
1150128
|
-
|
|
1150415
|
+
const text3 = event.data.content;
|
|
1150416
|
+
if (!text3.startsWith("API Error:") && !text3.startsWith("Error:")) {
|
|
1150417
|
+
responseText = text3;
|
|
1150418
|
+
} else {
|
|
1150419
|
+
log2.messaging.warn("Suppressed API error from assistant response", {
|
|
1150420
|
+
errorPreview: text3.slice(0, 200)
|
|
1150421
|
+
});
|
|
1150422
|
+
hasError = true;
|
|
1150423
|
+
}
|
|
1150129
1150424
|
}
|
|
1150130
1150425
|
}
|
|
1150131
|
-
if (
|
|
1150132
|
-
|
|
1150426
|
+
if (hasError && !responseText.trim()) {
|
|
1150427
|
+
responseText = "Sorry, I ran into an issue processing that. Could you try again or rephrase your message?";
|
|
1150428
|
+
}
|
|
1150429
|
+
if (isSlack && responseText.trim()) {
|
|
1150430
|
+
const parsed = parseSlackResponse(responseText);
|
|
1150431
|
+
if (parsed) {
|
|
1150432
|
+
const metadata = {};
|
|
1150433
|
+
if (parsed.blocks)
|
|
1150434
|
+
metadata.blocks = parsed.blocks;
|
|
1150435
|
+
if (parsed.filePath)
|
|
1150436
|
+
metadata.filePath = parsed.filePath;
|
|
1150437
|
+
await sendResponse(msg, parsed.body, Object.keys(metadata).length > 0 ? metadata : undefined);
|
|
1150438
|
+
} else {
|
|
1150439
|
+
await sendResponse(msg, responseText, {
|
|
1150440
|
+
blocks: [{ type: "section", text: { type: "mrkdwn", text: responseText } }]
|
|
1150441
|
+
});
|
|
1150442
|
+
}
|
|
1150133
1150443
|
} else if (responseText.trim()) {
|
|
1150134
1150444
|
await sendResponse(msg, responseText);
|
|
1150135
1150445
|
}
|
|
@@ -1150255,6 +1150565,24 @@ Started: ${new Date(mapping.createdAt).toLocaleString()}
|
|
|
1150255
1150565
|
Last active: ${new Date(mapping.lastMessageAt).toLocaleString()}`;
|
|
1150256
1150566
|
await sendResponse(msg, statusText);
|
|
1150257
1150567
|
}
|
|
1150568
|
+
function parseSlackResponse(text3) {
|
|
1150569
|
+
const match3 = text3.match(/<slack-response>([\s\S]*?)<\/slack-response>/);
|
|
1150570
|
+
if (!match3)
|
|
1150571
|
+
return null;
|
|
1150572
|
+
try {
|
|
1150573
|
+
const parsed = JSON.parse(match3[1]);
|
|
1150574
|
+
if (typeof parsed.body === "string" && parsed.body.trim()) {
|
|
1150575
|
+
return {
|
|
1150576
|
+
body: parsed.body,
|
|
1150577
|
+
...Array.isArray(parsed.blocks) && { blocks: parsed.blocks },
|
|
1150578
|
+
...typeof parsed.filePath === "string" && parsed.filePath.trim() && { filePath: parsed.filePath }
|
|
1150579
|
+
};
|
|
1150580
|
+
}
|
|
1150581
|
+
return null;
|
|
1150582
|
+
} catch {
|
|
1150583
|
+
return null;
|
|
1150584
|
+
}
|
|
1150585
|
+
}
|
|
1150258
1150586
|
async function sendResponse(originalMsg, content, metadata) {
|
|
1150259
1150587
|
if (originalMsg.channelType === "email") {
|
|
1150260
1150588
|
log2.messaging.info("Skipping email response \u2014 sending disabled, use Gmail drafts", {
|
|
@@ -1150361,7 +1150689,8 @@ async function processObserveOnlyMessage(msg) {
|
|
|
1150361
1150689
|
const stream2 = _deps.streamMessage(session3.id, msg.content, {
|
|
1150362
1150690
|
systemPromptAdditions: systemPrompt,
|
|
1150363
1150691
|
modelId: observerModelId,
|
|
1150364
|
-
securityTier: "observer"
|
|
1150692
|
+
securityTier: "observer",
|
|
1150693
|
+
ephemeral: true
|
|
1150365
1150694
|
});
|
|
1150366
1150695
|
for await (const event of stream2) {
|
|
1150367
1150696
|
if (event.type === "error") {
|
|
@@ -1150382,7 +1150711,7 @@ async function processObserveOnlyMessage(msg) {
|
|
|
1150382
1150711
|
recordObserverFailure();
|
|
1150383
1150712
|
}
|
|
1150384
1150713
|
}
|
|
1150385
|
-
var _deps,
|
|
1150714
|
+
var _deps, OBSERVER_CIRCUIT_BREAKER, COMMANDS;
|
|
1150386
1150715
|
var init_message_handler = __esm(() => {
|
|
1150387
1150716
|
init_logger3();
|
|
1150388
1150717
|
init_channel_manager();
|
|
@@ -1150394,21 +1150723,6 @@ var init_message_handler = __esm(() => {
|
|
|
1150394
1150723
|
streamMessage: (...args) => streamMessage(...args),
|
|
1150395
1150724
|
streamOpencodeObserverMessage: (...args) => streamOpencodeObserverMessage(...args)
|
|
1150396
1150725
|
};
|
|
1150397
|
-
SLACK_RESPONSE_SCHEMA = {
|
|
1150398
|
-
type: "object",
|
|
1150399
|
-
properties: {
|
|
1150400
|
-
body: {
|
|
1150401
|
-
type: "string",
|
|
1150402
|
-
description: "Plain text message (shown in notifications and as fallback)"
|
|
1150403
|
-
},
|
|
1150404
|
-
blocks: {
|
|
1150405
|
-
type: "array",
|
|
1150406
|
-
description: "Slack Block Kit blocks for rich formatting",
|
|
1150407
|
-
items: { type: "object", additionalProperties: true }
|
|
1150408
|
-
}
|
|
1150409
|
-
},
|
|
1150410
|
-
required: ["body"]
|
|
1150411
|
-
};
|
|
1150412
1150726
|
OBSERVER_CIRCUIT_BREAKER = {
|
|
1150413
1150727
|
failureCount: 0,
|
|
1150414
1150728
|
failureThreshold: 3,
|
|
@@ -1150689,7 +1151003,10 @@ async function sendMessageToChannel(channel, body, options) {
|
|
|
1150689
1151003
|
return { success: false, error: "Slack channel not active" };
|
|
1150690
1151004
|
}
|
|
1150691
1151005
|
try {
|
|
1150692
|
-
const msgMetadata = options?.slackBlocks
|
|
1151006
|
+
const msgMetadata = options?.slackBlocks || options?.filePath ? {
|
|
1151007
|
+
...options.slackBlocks && { blocks: options.slackBlocks },
|
|
1151008
|
+
...options.filePath && { filePath: options.filePath }
|
|
1151009
|
+
} : undefined;
|
|
1150693
1151010
|
const success = await slackChannel.sendMessage(resolvedTo, body, msgMetadata);
|
|
1150694
1151011
|
if (success) {
|
|
1150695
1151012
|
log2.messaging.info("Sent Slack message", { to: resolvedTo, hasBlocks: !!options?.slackBlocks });
|
|
@@ -1208698,7 +1209015,7 @@ var ALLOWED_MIME_TYPES = [
|
|
|
1208698
1209015
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1208699
1209016
|
"text/csv"
|
|
1208700
1209017
|
];
|
|
1208701
|
-
var
|
|
1209018
|
+
var MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
1208702
1209019
|
function getTaskUploadsDir(taskId) {
|
|
1208703
1209020
|
return path8.join(getFulcrumDir(), "uploads", "tasks", taskId);
|
|
1208704
1209021
|
}
|
|
@@ -1209355,8 +1209672,8 @@ app2.post("/:id/attachments", async (c) => {
|
|
|
1209355
1209672
|
if (!ALLOWED_MIME_TYPES.includes(file.type)) {
|
|
1209356
1209673
|
return c.json({ error: `File type not allowed: ${file.type}` }, 400);
|
|
1209357
1209674
|
}
|
|
1209358
|
-
if (file.size >
|
|
1209359
|
-
return c.json({ error: `File too large. Maximum size is ${
|
|
1209675
|
+
if (file.size > MAX_FILE_SIZE2) {
|
|
1209676
|
+
return c.json({ error: `File too large. Maximum size is ${MAX_FILE_SIZE2 / 1024 / 1024}MB` }, 400);
|
|
1209360
1209677
|
}
|
|
1209361
1209678
|
const uploadDir = getTaskUploadsDir(taskId);
|
|
1209362
1209679
|
if (!fs11.existsSync(uploadDir)) {
|
|
@@ -1211946,7 +1212263,7 @@ init_drizzle_orm();
|
|
|
1211946
1212263
|
init_nanoid();
|
|
1211947
1212264
|
init_logger3();
|
|
1211948
1212265
|
init_settings();
|
|
1211949
|
-
import { existsSync as existsSync22, readFileSync as
|
|
1212266
|
+
import { existsSync as existsSync22, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync7, mkdtempSync, rmSync as rmSync6 } from "fs";
|
|
1211950
1212267
|
import { join as join22 } from "path";
|
|
1211951
1212268
|
import { tmpdir as tmpdir4 } from "os";
|
|
1211952
1212269
|
import { execSync as execSync6 } from "child_process";
|
|
@@ -1212023,9 +1212340,9 @@ function fetchCopierYamlFromGit(gitUrl) {
|
|
|
1212023
1212340
|
const yamlAltPath = join22(tempDir, "copier.yaml");
|
|
1212024
1212341
|
let content = null;
|
|
1212025
1212342
|
if (existsSync22(yamlPath)) {
|
|
1212026
|
-
content =
|
|
1212343
|
+
content = readFileSync13(yamlPath, "utf-8");
|
|
1212027
1212344
|
} else if (existsSync22(yamlAltPath)) {
|
|
1212028
|
-
content =
|
|
1212345
|
+
content = readFileSync13(yamlAltPath, "utf-8");
|
|
1212029
1212346
|
}
|
|
1212030
1212347
|
if (!content) {
|
|
1212031
1212348
|
rmSync6(tempDir, { recursive: true, force: true });
|
|
@@ -1212047,10 +1212364,10 @@ async function fetchCopierYaml(source) {
|
|
|
1212047
1212364
|
const yamlPath = join22(templatePath, "copier.yml");
|
|
1212048
1212365
|
const yamlAltPath = join22(templatePath, "copier.yaml");
|
|
1212049
1212366
|
if (existsSync22(yamlPath)) {
|
|
1212050
|
-
return { content:
|
|
1212367
|
+
return { content: readFileSync13(yamlPath, "utf-8"), templatePath };
|
|
1212051
1212368
|
}
|
|
1212052
1212369
|
if (existsSync22(yamlAltPath)) {
|
|
1212053
|
-
return { content:
|
|
1212370
|
+
return { content: readFileSync13(yamlAltPath, "utf-8"), templatePath };
|
|
1212054
1212371
|
}
|
|
1212055
1212372
|
throw new Error("copier.yml not found in template directory");
|
|
1212056
1212373
|
}
|
|
@@ -1215930,7 +1216247,7 @@ var github_default = app11;
|
|
|
1215930
1216247
|
init_db2();
|
|
1215931
1216248
|
init_pty_instance();
|
|
1215932
1216249
|
init_dtach_service();
|
|
1215933
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
1216250
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync14, readlinkSync as readlinkSync3, existsSync as existsSync23 } from "fs";
|
|
1215934
1216251
|
import { execSync as execSync8 } from "child_process";
|
|
1215935
1216252
|
import { homedir as homedir7 } from "os";
|
|
1215936
1216253
|
import { join as join23 } from "path";
|
|
@@ -1216215,7 +1216532,7 @@ function findAllAgentProcesses(agentFilter) {
|
|
|
1216215
1216532
|
for (const pidStr of procDirs) {
|
|
1216216
1216533
|
const pid = parseInt(pidStr, 10);
|
|
1216217
1216534
|
try {
|
|
1216218
|
-
const cmdline =
|
|
1216535
|
+
const cmdline = readFileSync14(`/proc/${pid}/cmdline`, "utf-8");
|
|
1216219
1216536
|
if (combinedPattern.test(cmdline)) {
|
|
1216220
1216537
|
const agent = detectAgentType(cmdline);
|
|
1216221
1216538
|
if (agent && (!agentFilter || agentFilter.includes(agent))) {
|
|
@@ -1216271,7 +1216588,7 @@ function getProcessCwd(pid) {
|
|
|
1216271
1216588
|
}
|
|
1216272
1216589
|
function getProcessMemoryMB(pid) {
|
|
1216273
1216590
|
try {
|
|
1216274
|
-
const status =
|
|
1216591
|
+
const status = readFileSync14(`/proc/${pid}/status`, "utf-8");
|
|
1216275
1216592
|
const match3 = status.match(/VmRSS:\s+(\d+)\s+kB/);
|
|
1216276
1216593
|
return match3 ? parseInt(match3[1], 10) / 1024 : 0;
|
|
1216277
1216594
|
} catch {
|
|
@@ -1216286,10 +1216603,10 @@ function getProcessMemoryMB(pid) {
|
|
|
1216286
1216603
|
function getProcessStartTime(pid) {
|
|
1216287
1216604
|
if (!isMacOS2) {
|
|
1216288
1216605
|
try {
|
|
1216289
|
-
const stat2 =
|
|
1216606
|
+
const stat2 = readFileSync14(`/proc/${pid}/stat`, "utf-8");
|
|
1216290
1216607
|
const fields = stat2.split(" ");
|
|
1216291
1216608
|
const starttime = parseInt(fields[21], 10);
|
|
1216292
|
-
const uptime = parseFloat(
|
|
1216609
|
+
const uptime = parseFloat(readFileSync14("/proc/uptime", "utf-8").split(" ")[0]);
|
|
1216293
1216610
|
const clockTicks = 100;
|
|
1216294
1216611
|
const bootTime = Math.floor(Date.now() / 1000) - uptime;
|
|
1216295
1216612
|
return Math.floor(bootTime + starttime / clockTicks);
|
|
@@ -1216364,7 +1216681,7 @@ monitoringRoutes.get("/claude-instances", (c) => {
|
|
|
1216364
1216681
|
for (const pidStr of procDirs) {
|
|
1216365
1216682
|
const pid = parseInt(pidStr, 10);
|
|
1216366
1216683
|
try {
|
|
1216367
|
-
const cmdline =
|
|
1216684
|
+
const cmdline = readFileSync14(`/proc/${pid}/cmdline`, "utf-8");
|
|
1216368
1216685
|
if (cmdline.includes(socketPath)) {
|
|
1216369
1216686
|
foundPids.push(pid);
|
|
1216370
1216687
|
}
|
|
@@ -1216458,7 +1216775,7 @@ monitoringRoutes.post("/claude-instances/:pid/kill-pid", (c) => {
|
|
|
1216458
1216775
|
return c.json({ error: "Invalid PID" }, 400);
|
|
1216459
1216776
|
}
|
|
1216460
1216777
|
try {
|
|
1216461
|
-
const cmdline =
|
|
1216778
|
+
const cmdline = readFileSync14(`/proc/${pid}/cmdline`, "utf-8");
|
|
1216462
1216779
|
const agent = detectAgentType(cmdline);
|
|
1216463
1216780
|
if (!agent) {
|
|
1216464
1216781
|
return c.json({ error: "Process is not a recognized AI agent" }, 400);
|
|
@@ -1216484,7 +1216801,7 @@ function getTotalMemory() {
|
|
|
1216484
1216801
|
}
|
|
1216485
1216802
|
}
|
|
1216486
1216803
|
try {
|
|
1216487
|
-
const meminfo =
|
|
1216804
|
+
const meminfo = readFileSync14("/proc/meminfo", "utf-8");
|
|
1216488
1216805
|
const match3 = meminfo.match(/MemTotal:\s+(\d+)/);
|
|
1216489
1216806
|
return match3 ? parseInt(match3[1], 10) * 1024 : 0;
|
|
1216490
1216807
|
} catch {
|
|
@@ -1216700,7 +1217017,7 @@ monitoringRoutes.get("/docker-stats", async (c) => {
|
|
|
1216700
1217017
|
});
|
|
1216701
1217018
|
function getProcessEnv(pid) {
|
|
1216702
1217019
|
try {
|
|
1216703
|
-
const environ =
|
|
1217020
|
+
const environ = readFileSync14(`/proc/${pid}/environ`, "utf-8");
|
|
1216704
1217021
|
const env = {};
|
|
1216705
1217022
|
for (const entry of environ.split("\x00")) {
|
|
1216706
1217023
|
const idx = entry.indexOf("=");
|
|
@@ -1216715,7 +1217032,7 @@ function getProcessEnv(pid) {
|
|
|
1216715
1217032
|
}
|
|
1216716
1217033
|
function getParentPid(pid) {
|
|
1216717
1217034
|
try {
|
|
1216718
|
-
const stat2 =
|
|
1217035
|
+
const stat2 = readFileSync14(`/proc/${pid}/stat`, "utf-8");
|
|
1216719
1217036
|
const match3 = stat2.match(/^\d+ \([^)]+\) \S+ (\d+)/);
|
|
1216720
1217037
|
return match3 ? parseInt(match3[1], 10) : null;
|
|
1216721
1217038
|
} catch {
|
|
@@ -1216730,7 +1217047,7 @@ function findFulcrumInstances() {
|
|
|
1216730
1217047
|
for (const pidStr of procDirs) {
|
|
1216731
1217048
|
const pid = parseInt(pidStr, 10);
|
|
1216732
1217049
|
try {
|
|
1216733
|
-
const cmdline =
|
|
1217050
|
+
const cmdline = readFileSync14(`/proc/${pid}/cmdline`, "utf-8").replace(/\0/g, " ");
|
|
1216734
1217051
|
const env = getProcessEnv(pid);
|
|
1216735
1217052
|
const parentPid = getParentPid(pid);
|
|
1216736
1217053
|
const cmdParts = cmdline.trim().split(/\s+/);
|
|
@@ -1216846,7 +1217163,7 @@ async function getClaudeOAuthToken() {
|
|
|
1216846
1217163
|
const primaryPath = join23(homedir7(), ".claude", ".credentials.json");
|
|
1216847
1217164
|
try {
|
|
1216848
1217165
|
if (existsSync23(primaryPath)) {
|
|
1216849
|
-
const content =
|
|
1217166
|
+
const content = readFileSync14(primaryPath, "utf-8");
|
|
1216850
1217167
|
const config = JSON.parse(content);
|
|
1216851
1217168
|
if (config.claudeAiOauth && typeof config.claudeAiOauth === "object") {
|
|
1216852
1217169
|
const token = config.claudeAiOauth.accessToken;
|
|
@@ -1218440,7 +1218757,7 @@ init_logger3();
|
|
|
1218440
1218757
|
import { execSync as execSync10 } from "child_process";
|
|
1218441
1218758
|
import { homedir as homedir10, platform as platform2 } from "os";
|
|
1218442
1218759
|
import { join as join33 } from "path";
|
|
1218443
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as
|
|
1218760
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync16, unlinkSync as unlinkSync8 } from "fs";
|
|
1218444
1218761
|
var USER_UNIT_DIR = join33(homedir10(), ".config/systemd/user");
|
|
1218445
1218762
|
var systemdAvailable = null;
|
|
1218446
1218763
|
function isSystemdAvailable() {
|
|
@@ -1218668,11 +1218985,11 @@ function getTimer(name, scope) {
|
|
|
1218668
1218985
|
let workingDirectory = null;
|
|
1218669
1218986
|
if (scope === "user" && unitPath) {
|
|
1218670
1218987
|
try {
|
|
1218671
|
-
timerContent =
|
|
1218988
|
+
timerContent = readFileSync16(unitPath, "utf-8");
|
|
1218672
1218989
|
} catch {}
|
|
1218673
1218990
|
const servicePath = unitPath.replace(".timer", ".service");
|
|
1218674
1218991
|
try {
|
|
1218675
|
-
serviceContent =
|
|
1218992
|
+
serviceContent = readFileSync16(servicePath, "utf-8");
|
|
1218676
1218993
|
const execMatch = serviceContent.match(/^ExecStart=(.+)$/m);
|
|
1218677
1218994
|
if (execMatch) {
|
|
1218678
1218995
|
command = execMatch[1];
|
|
@@ -1218877,8 +1219194,8 @@ function updateTimer(name, updates) {
|
|
|
1218877
1219194
|
if (!existsSync27(timerPath)) {
|
|
1218878
1219195
|
throw new Error(`Timer ${timerName} not found`);
|
|
1218879
1219196
|
}
|
|
1218880
|
-
let timerContent =
|
|
1218881
|
-
let serviceContent = existsSync27(servicePath) ?
|
|
1219197
|
+
let timerContent = readFileSync16(timerPath, "utf-8");
|
|
1219198
|
+
let serviceContent = existsSync27(servicePath) ? readFileSync16(servicePath, "utf-8") : "";
|
|
1218882
1219199
|
if (updates.description !== undefined) {
|
|
1218883
1219200
|
timerContent = timerContent.replace(/^Description=.*$/m, `Description=${updates.description}`);
|
|
1218884
1219201
|
serviceContent = serviceContent.replace(/^Description=.*$/m, `Description=${updates.description}`);
|
|
@@ -1219852,7 +1220169,7 @@ var ALLOWED_MIME_TYPES2 = [
|
|
|
1219852
1220169
|
"application/gzip",
|
|
1219853
1220170
|
"application/x-tar"
|
|
1219854
1220171
|
];
|
|
1219855
|
-
var
|
|
1220172
|
+
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
1219856
1220173
|
function sanitizeFilename2(filename) {
|
|
1219857
1220174
|
return filename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1219858
1220175
|
}
|
|
@@ -1220839,8 +1221156,8 @@ app19.post("/:id/attachments", async (c) => {
|
|
|
1220839
1221156
|
if (!ALLOWED_MIME_TYPES2.includes(file.type)) {
|
|
1220840
1221157
|
return c.json({ error: `File type not allowed: ${file.type}` }, 400);
|
|
1220841
1221158
|
}
|
|
1220842
|
-
if (file.size >
|
|
1220843
|
-
return c.json({ error: `File too large. Maximum size is ${
|
|
1221159
|
+
if (file.size > MAX_FILE_SIZE3) {
|
|
1221160
|
+
return c.json({ error: `File too large. Maximum size is ${MAX_FILE_SIZE3 / 1024 / 1024}MB` }, 400);
|
|
1220844
1221161
|
}
|
|
1220845
1221162
|
const uploadDir = getProjectUploadsDir(projectId);
|
|
1220846
1221163
|
if (!fs17.existsSync(uploadDir)) {
|
|
@@ -1251893,6 +1252210,27 @@ var toolRegistry = [
|
|
|
1251893
1252210
|
keywords: ["memory", "store", "save", "remember", "knowledge", "persist", "fact"],
|
|
1251894
1252211
|
defer_loading: false
|
|
1251895
1252212
|
},
|
|
1252213
|
+
{
|
|
1252214
|
+
name: "memory_search",
|
|
1252215
|
+
description: "Search persistent memories using full-text search (FTS5)",
|
|
1252216
|
+
category: "memory",
|
|
1252217
|
+
keywords: ["memory", "search", "find", "query", "fts", "recall", "knowledge"],
|
|
1252218
|
+
defer_loading: false
|
|
1252219
|
+
},
|
|
1252220
|
+
{
|
|
1252221
|
+
name: "memory_list",
|
|
1252222
|
+
description: "List persistent memories with optional tag filter",
|
|
1252223
|
+
category: "memory",
|
|
1252224
|
+
keywords: ["memory", "list", "browse", "tags", "filter", "all"],
|
|
1252225
|
+
defer_loading: false
|
|
1252226
|
+
},
|
|
1252227
|
+
{
|
|
1252228
|
+
name: "memory_delete",
|
|
1252229
|
+
description: "Delete a persistent memory by ID",
|
|
1252230
|
+
category: "memory",
|
|
1252231
|
+
keywords: ["memory", "delete", "remove", "clean", "resolved", "outdated"],
|
|
1252232
|
+
defer_loading: false
|
|
1252233
|
+
},
|
|
1251896
1252234
|
{
|
|
1251897
1252235
|
name: "memory_file_read",
|
|
1251898
1252236
|
description: "Read the master memory file (MEMORY.md)",
|
|
@@ -1251993,7 +1252331,7 @@ var registerCoreTools = (server2, _client) => {
|
|
|
1251993
1252331
|
};
|
|
1251994
1252332
|
|
|
1251995
1252333
|
// cli/src/mcp/tools/tasks.ts
|
|
1251996
|
-
import { basename as
|
|
1252334
|
+
import { basename as basename4 } from "path";
|
|
1251997
1252335
|
|
|
1251998
1252336
|
// shared/date-utils.ts
|
|
1251999
1252337
|
function getTodayInTimezone(timezone) {
|
|
@@ -1252014,7 +1252352,7 @@ function getTodayInTimezone(timezone) {
|
|
|
1252014
1252352
|
}
|
|
1252015
1252353
|
|
|
1252016
1252354
|
// cli/src/mcp/tools/tasks.ts
|
|
1252017
|
-
|
|
1252355
|
+
function registerListTasks(server2, client3) {
|
|
1252018
1252356
|
server2.tool("list_tasks", "List all Fulcrum tasks with flexible filtering. Supports text search across title/tags/project, multi-tag filtering (OR logic), multi-status filtering, date range, and overdue detection.", {
|
|
1252019
1252357
|
status: exports_external.optional(TaskStatusSchema2).describe("Filter by single task status (use statuses for multiple)"),
|
|
1252020
1252358
|
statuses: exports_external.optional(exports_external.array(TaskStatusSchema2)).describe("Filter by multiple statuses (OR logic)"),
|
|
@@ -1252106,20 +1252444,8 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252106
1252444
|
return handleToolError(err);
|
|
1252107
1252445
|
}
|
|
1252108
1252446
|
});
|
|
1252109
|
-
|
|
1252110
|
-
|
|
1252111
|
-
}, async ({ id }) => {
|
|
1252112
|
-
try {
|
|
1252113
|
-
const [task, dependencies, attachments] = await Promise.all([
|
|
1252114
|
-
client3.getTask(id),
|
|
1252115
|
-
client3.getTaskDependencies(id),
|
|
1252116
|
-
client3.listTaskAttachments(id)
|
|
1252117
|
-
]);
|
|
1252118
|
-
return formatSuccess({ ...task, dependencies, attachments });
|
|
1252119
|
-
} catch (err) {
|
|
1252120
|
-
return handleToolError(err);
|
|
1252121
|
-
}
|
|
1252122
|
-
});
|
|
1252447
|
+
}
|
|
1252448
|
+
function registerCreateTask(server2, client3) {
|
|
1252123
1252449
|
server2.tool("create_task", "Create a new task. For worktree tasks, provide repoPath to create a git worktree. For non-worktree tasks, omit repoPath. When tags are provided, returns all existing tags for reference.", {
|
|
1252124
1252450
|
title: exports_external.string().describe("Task title"),
|
|
1252125
1252451
|
repoPath: exports_external.optional(exports_external.string()).describe("Absolute path to the git repository (optional for non-worktree tasks)"),
|
|
@@ -1252144,7 +1252470,7 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252144
1252470
|
dueDate
|
|
1252145
1252471
|
}) => {
|
|
1252146
1252472
|
try {
|
|
1252147
|
-
const repoName = repoPath ?
|
|
1252473
|
+
const repoName = repoPath ? basename4(repoPath) : null;
|
|
1252148
1252474
|
const effectiveBaseBranch = baseBranch ?? "main";
|
|
1252149
1252475
|
const task = await client3.createTask({
|
|
1252150
1252476
|
title,
|
|
@@ -1252180,6 +1252506,8 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252180
1252506
|
return handleToolError(err);
|
|
1252181
1252507
|
}
|
|
1252182
1252508
|
});
|
|
1252509
|
+
}
|
|
1252510
|
+
function registerUpdateTask(server2, client3) {
|
|
1252183
1252511
|
server2.tool("update_task", "Update task metadata (title or description)", {
|
|
1252184
1252512
|
id: exports_external.string().describe("Task ID"),
|
|
1252185
1252513
|
title: exports_external.optional(exports_external.string()).describe("New title"),
|
|
@@ -1252197,6 +1252525,82 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252197
1252525
|
return handleToolError(err);
|
|
1252198
1252526
|
}
|
|
1252199
1252527
|
});
|
|
1252528
|
+
}
|
|
1252529
|
+
function registerAddTaskLink(server2, client3) {
|
|
1252530
|
+
server2.tool("add_task_link", "Add a URL link to a task (for documentation, related PRs, design files, etc.)", {
|
|
1252531
|
+
taskId: exports_external.string().describe("Task ID"),
|
|
1252532
|
+
url: exports_external.string().url().describe("URL to add"),
|
|
1252533
|
+
label: exports_external.optional(exports_external.string()).describe("Display label (auto-detected if not provided)")
|
|
1252534
|
+
}, async ({ taskId, url: url2, label }) => {
|
|
1252535
|
+
try {
|
|
1252536
|
+
const link = await client3.addTaskLink(taskId, url2, label);
|
|
1252537
|
+
return formatSuccess(link);
|
|
1252538
|
+
} catch (err) {
|
|
1252539
|
+
return handleToolError(err);
|
|
1252540
|
+
}
|
|
1252541
|
+
});
|
|
1252542
|
+
}
|
|
1252543
|
+
function registerAddTaskTag(server2, client3) {
|
|
1252544
|
+
server2.tool("add_task_tag", "Add a tag to a task for categorization. Returns similar existing tags to help catch typos.", {
|
|
1252545
|
+
taskId: exports_external.string().describe("Task ID"),
|
|
1252546
|
+
tag: exports_external.string().describe("Tag to add")
|
|
1252547
|
+
}, async ({ taskId, tag }) => {
|
|
1252548
|
+
try {
|
|
1252549
|
+
const result = await client3.addTaskTag(taskId, tag);
|
|
1252550
|
+
const allTasks = await client3.listTasks();
|
|
1252551
|
+
const existingTags = new Set;
|
|
1252552
|
+
for (const t of allTasks) {
|
|
1252553
|
+
if (t.tags) {
|
|
1252554
|
+
for (const tg of t.tags) {
|
|
1252555
|
+
existingTags.add(tg);
|
|
1252556
|
+
}
|
|
1252557
|
+
}
|
|
1252558
|
+
}
|
|
1252559
|
+
const tagLower = tag.toLowerCase();
|
|
1252560
|
+
const similarTags = Array.from(existingTags).filter((tg) => tg !== tag && (tg.toLowerCase().includes(tagLower) || tagLower.includes(tg.toLowerCase())));
|
|
1252561
|
+
return formatSuccess({
|
|
1252562
|
+
...result,
|
|
1252563
|
+
similarTags: similarTags.length > 0 ? similarTags : undefined
|
|
1252564
|
+
});
|
|
1252565
|
+
} catch (err) {
|
|
1252566
|
+
return handleToolError(err);
|
|
1252567
|
+
}
|
|
1252568
|
+
});
|
|
1252569
|
+
}
|
|
1252570
|
+
function registerSetTaskDueDate(server2, client3) {
|
|
1252571
|
+
server2.tool("set_task_due_date", "Set or clear the due date for a task", {
|
|
1252572
|
+
taskId: exports_external.string().describe("Task ID"),
|
|
1252573
|
+
dueDate: exports_external.nullable(exports_external.string()).describe("Due date in YYYY-MM-DD format, or null to clear")
|
|
1252574
|
+
}, async ({ taskId, dueDate }) => {
|
|
1252575
|
+
try {
|
|
1252576
|
+
const result = await client3.setTaskDueDate(taskId, dueDate);
|
|
1252577
|
+
return formatSuccess(result);
|
|
1252578
|
+
} catch (err) {
|
|
1252579
|
+
return handleToolError(err);
|
|
1252580
|
+
}
|
|
1252581
|
+
});
|
|
1252582
|
+
}
|
|
1252583
|
+
var registerTaskTools = (server2, client3) => {
|
|
1252584
|
+
registerListTasks(server2, client3);
|
|
1252585
|
+
registerCreateTask(server2, client3);
|
|
1252586
|
+
registerUpdateTask(server2, client3);
|
|
1252587
|
+
registerAddTaskLink(server2, client3);
|
|
1252588
|
+
registerAddTaskTag(server2, client3);
|
|
1252589
|
+
registerSetTaskDueDate(server2, client3);
|
|
1252590
|
+
server2.tool("get_task", "Get details of a specific task by ID, including dependencies and attachments", {
|
|
1252591
|
+
id: exports_external.string().describe("Task ID (UUID)")
|
|
1252592
|
+
}, async ({ id }) => {
|
|
1252593
|
+
try {
|
|
1252594
|
+
const [task, dependencies, attachments] = await Promise.all([
|
|
1252595
|
+
client3.getTask(id),
|
|
1252596
|
+
client3.getTaskDependencies(id),
|
|
1252597
|
+
client3.listTaskAttachments(id)
|
|
1252598
|
+
]);
|
|
1252599
|
+
return formatSuccess({ ...task, dependencies, attachments });
|
|
1252600
|
+
} catch (err) {
|
|
1252601
|
+
return handleToolError(err);
|
|
1252602
|
+
}
|
|
1252603
|
+
});
|
|
1252200
1252604
|
server2.tool("delete_task", "Delete a task and optionally its linked git worktree", {
|
|
1252201
1252605
|
id: exports_external.string().describe("Task ID"),
|
|
1252202
1252606
|
deleteWorktree: exports_external.optional(exports_external.boolean()).describe("Also delete the linked git worktree (default: false)")
|
|
@@ -1252221,18 +1252625,6 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252221
1252625
|
return handleToolError(err);
|
|
1252222
1252626
|
}
|
|
1252223
1252627
|
});
|
|
1252224
|
-
server2.tool("add_task_link", "Add a URL link to a task (for documentation, related PRs, design files, etc.)", {
|
|
1252225
|
-
taskId: exports_external.string().describe("Task ID"),
|
|
1252226
|
-
url: exports_external.string().url().describe("URL to add"),
|
|
1252227
|
-
label: exports_external.optional(exports_external.string()).describe("Display label (auto-detected if not provided)")
|
|
1252228
|
-
}, async ({ taskId, url: url2, label }) => {
|
|
1252229
|
-
try {
|
|
1252230
|
-
const link = await client3.addTaskLink(taskId, url2, label);
|
|
1252231
|
-
return formatSuccess(link);
|
|
1252232
|
-
} catch (err) {
|
|
1252233
|
-
return handleToolError(err);
|
|
1252234
|
-
}
|
|
1252235
|
-
});
|
|
1252236
1252628
|
server2.tool("remove_task_link", "Remove a URL link from a task", {
|
|
1252237
1252629
|
taskId: exports_external.string().describe("Task ID"),
|
|
1252238
1252630
|
linkId: exports_external.string().describe("Link ID to remove")
|
|
@@ -1252254,31 +1252646,6 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252254
1252646
|
return handleToolError(err);
|
|
1252255
1252647
|
}
|
|
1252256
1252648
|
});
|
|
1252257
|
-
server2.tool("add_task_tag", "Add a tag to a task for categorization. Returns similar existing tags to help catch typos.", {
|
|
1252258
|
-
taskId: exports_external.string().describe("Task ID"),
|
|
1252259
|
-
tag: exports_external.string().describe("Tag to add")
|
|
1252260
|
-
}, async ({ taskId, tag }) => {
|
|
1252261
|
-
try {
|
|
1252262
|
-
const result = await client3.addTaskTag(taskId, tag);
|
|
1252263
|
-
const allTasks = await client3.listTasks();
|
|
1252264
|
-
const existingTags = new Set;
|
|
1252265
|
-
for (const t of allTasks) {
|
|
1252266
|
-
if (t.tags) {
|
|
1252267
|
-
for (const tg of t.tags) {
|
|
1252268
|
-
existingTags.add(tg);
|
|
1252269
|
-
}
|
|
1252270
|
-
}
|
|
1252271
|
-
}
|
|
1252272
|
-
const tagLower = tag.toLowerCase();
|
|
1252273
|
-
const similarTags = Array.from(existingTags).filter((tg) => tg !== tag && (tg.toLowerCase().includes(tagLower) || tagLower.includes(tg.toLowerCase())));
|
|
1252274
|
-
return formatSuccess({
|
|
1252275
|
-
...result,
|
|
1252276
|
-
similarTags: similarTags.length > 0 ? similarTags : undefined
|
|
1252277
|
-
});
|
|
1252278
|
-
} catch (err) {
|
|
1252279
|
-
return handleToolError(err);
|
|
1252280
|
-
}
|
|
1252281
|
-
});
|
|
1252282
1252649
|
server2.tool("remove_task_tag", "Remove a tag from a task", {
|
|
1252283
1252650
|
taskId: exports_external.string().describe("Task ID"),
|
|
1252284
1252651
|
tag: exports_external.string().describe("Tag to remove")
|
|
@@ -1252290,17 +1252657,6 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252290
1252657
|
return handleToolError(err);
|
|
1252291
1252658
|
}
|
|
1252292
1252659
|
});
|
|
1252293
|
-
server2.tool("set_task_due_date", "Set or clear the due date for a task", {
|
|
1252294
|
-
taskId: exports_external.string().describe("Task ID"),
|
|
1252295
|
-
dueDate: exports_external.nullable(exports_external.string()).describe("Due date in YYYY-MM-DD format, or null to clear")
|
|
1252296
|
-
}, async ({ taskId, dueDate }) => {
|
|
1252297
|
-
try {
|
|
1252298
|
-
const result = await client3.setTaskDueDate(taskId, dueDate);
|
|
1252299
|
-
return formatSuccess(result);
|
|
1252300
|
-
} catch (err) {
|
|
1252301
|
-
return handleToolError(err);
|
|
1252302
|
-
}
|
|
1252303
|
-
});
|
|
1252304
1252660
|
server2.tool("get_task_dependencies", "Get the dependencies and dependents of a task, and whether it is blocked", {
|
|
1252305
1252661
|
taskId: exports_external.string().describe("Task ID")
|
|
1252306
1252662
|
}, async ({ taskId }) => {
|
|
@@ -1252459,6 +1252815,14 @@ var registerTaskTools = (server2, client3) => {
|
|
|
1252459
1252815
|
}
|
|
1252460
1252816
|
});
|
|
1252461
1252817
|
};
|
|
1252818
|
+
var registerTaskObserverTools = (server2, client3) => {
|
|
1252819
|
+
registerListTasks(server2, client3);
|
|
1252820
|
+
registerCreateTask(server2, client3);
|
|
1252821
|
+
registerUpdateTask(server2, client3);
|
|
1252822
|
+
registerAddTaskLink(server2, client3);
|
|
1252823
|
+
registerAddTaskTag(server2, client3);
|
|
1252824
|
+
registerSetTaskDueDate(server2, client3);
|
|
1252825
|
+
};
|
|
1252462
1252826
|
|
|
1252463
1252827
|
// cli/src/mcp/tools/repositories.ts
|
|
1252464
1252828
|
var registerRepositoryTools = (server2, client3) => {
|
|
@@ -1253348,8 +1253712,9 @@ var registerAssistantTools = (server2, client3) => {
|
|
|
1253348
1253712
|
subject: exports_external.optional(exports_external.string()).describe("Email subject (for Gmail channel only)"),
|
|
1253349
1253713
|
replyToMessageId: exports_external.optional(exports_external.string()).describe("Message ID to reply to (for threading)"),
|
|
1253350
1253714
|
slack_blocks: exports_external.optional(exports_external.array(exports_external.record(exports_external.string(), exports_external.any()))).describe("Slack Block Kit blocks for rich formatting (Slack channel only). Array of block objects."),
|
|
1253715
|
+
filePath: exports_external.optional(exports_external.string()).describe("Absolute path to a local file to upload alongside the message (Slack only). Use for sending images, documents, etc."),
|
|
1253351
1253716
|
googleAccountId: exports_external.optional(exports_external.string()).describe("Google account ID for Gmail channel. If omitted, auto-resolves when exactly one Gmail-enabled account exists.")
|
|
1253352
|
-
}, async ({ channel, body, subject, replyToMessageId, slack_blocks, googleAccountId }) => {
|
|
1253717
|
+
}, async ({ channel, body, subject, replyToMessageId, slack_blocks, filePath, googleAccountId }) => {
|
|
1253353
1253718
|
try {
|
|
1253354
1253719
|
if (channel === "gmail") {
|
|
1253355
1253720
|
let accountId = googleAccountId;
|
|
@@ -1253372,7 +1253737,8 @@ var registerAssistantTools = (server2, client3) => {
|
|
|
1253372
1253737
|
body,
|
|
1253373
1253738
|
subject,
|
|
1253374
1253739
|
replyToMessageId,
|
|
1253375
|
-
slackBlocks: slack_blocks
|
|
1253740
|
+
slackBlocks: slack_blocks,
|
|
1253741
|
+
filePath
|
|
1253376
1253742
|
});
|
|
1253377
1253743
|
return formatSuccess(result);
|
|
1253378
1253744
|
} catch (err) {
|
|
@@ -1253643,7 +1254009,7 @@ var MEMORY_SOURCES = [
|
|
|
1253643
1254009
|
];
|
|
1253644
1254010
|
|
|
1253645
1254011
|
// cli/src/mcp/tools/memory.ts
|
|
1253646
|
-
|
|
1254012
|
+
function registerMemoryStoreTool(server2, client3) {
|
|
1253647
1254013
|
server2.tool("memory_store", "Store a piece of knowledge in persistent memory. Use this to remember facts, preferences, decisions, patterns, or any information that should persist across conversations.", {
|
|
1253648
1254014
|
content: exports_external.string().describe("The memory content to store. Be specific and self-contained."),
|
|
1253649
1254015
|
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Optional tags for categorization (e.g., ["preference", "architecture", "decision"])'),
|
|
@@ -1253656,10 +1254022,61 @@ var registerMemoryTools = (server2, client3) => {
|
|
|
1253656
1254022
|
return handleToolError(err);
|
|
1253657
1254023
|
}
|
|
1253658
1254024
|
});
|
|
1254025
|
+
}
|
|
1254026
|
+
function registerMemorySearchTool(server2, client3) {
|
|
1254027
|
+
server2.tool("memory_search", 'Search persistent memories using full-text search (FTS5). Supports boolean operators (AND, OR, NOT), phrase matching ("quoted"), and prefix matching (term*).', {
|
|
1254028
|
+
query: exports_external.string().describe('Search query. Supports FTS5 syntax: AND, OR, NOT, "phrases", prefix*'),
|
|
1254029
|
+
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Filter by tags (e.g., ["actionable", "preference"])'),
|
|
1254030
|
+
limit: exports_external.optional(exports_external.number()).describe("Max results to return (default: 20)")
|
|
1254031
|
+
}, async ({ query, tags: tags2, limit: limit2 }) => {
|
|
1254032
|
+
try {
|
|
1254033
|
+
const result = await client3.searchMemories({ query, tags: tags2, limit: limit2 });
|
|
1254034
|
+
return formatSuccess(result);
|
|
1254035
|
+
} catch (err) {
|
|
1254036
|
+
return handleToolError(err);
|
|
1254037
|
+
}
|
|
1254038
|
+
});
|
|
1254039
|
+
}
|
|
1254040
|
+
function registerMemoryListTool(server2, client3) {
|
|
1254041
|
+
server2.tool("memory_list", "List all persistent memories, optionally filtered by tags. Returns memories sorted by creation date (newest first).", {
|
|
1254042
|
+
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Filter by tags (e.g., ["actionable"])'),
|
|
1254043
|
+
limit: exports_external.optional(exports_external.number()).describe("Max results to return (default: 50)"),
|
|
1254044
|
+
offset: exports_external.optional(exports_external.number()).describe("Offset for pagination")
|
|
1254045
|
+
}, async ({ tags: tags2, limit: limit2, offset }) => {
|
|
1254046
|
+
try {
|
|
1254047
|
+
const result = await client3.listMemories({ tags: tags2, limit: limit2, offset });
|
|
1254048
|
+
return formatSuccess(result);
|
|
1254049
|
+
} catch (err) {
|
|
1254050
|
+
return handleToolError(err);
|
|
1254051
|
+
}
|
|
1254052
|
+
});
|
|
1254053
|
+
}
|
|
1254054
|
+
function registerMemoryDeleteTool(server2, client3) {
|
|
1254055
|
+
server2.tool("memory_delete", "Delete a persistent memory by ID. Use this to clean up resolved or outdated memories.", {
|
|
1254056
|
+
id: exports_external.string().describe("The ID of the memory to delete")
|
|
1254057
|
+
}, async ({ id }) => {
|
|
1254058
|
+
try {
|
|
1254059
|
+
const result = await client3.deleteMemory(id);
|
|
1254060
|
+
return formatSuccess(result);
|
|
1254061
|
+
} catch (err) {
|
|
1254062
|
+
return handleToolError(err);
|
|
1254063
|
+
}
|
|
1254064
|
+
});
|
|
1254065
|
+
}
|
|
1254066
|
+
var registerMemoryTools = (server2, client3) => {
|
|
1254067
|
+
registerMemoryStoreTool(server2, client3);
|
|
1254068
|
+
registerMemorySearchTool(server2, client3);
|
|
1254069
|
+
registerMemoryListTool(server2, client3);
|
|
1254070
|
+
registerMemoryDeleteTool(server2, client3);
|
|
1254071
|
+
};
|
|
1254072
|
+
var registerMemoryObserverTools = (server2, client3) => {
|
|
1254073
|
+
registerMemoryStoreTool(server2, client3);
|
|
1254074
|
+
registerMemorySearchTool(server2, client3);
|
|
1254075
|
+
registerMemoryListTool(server2, client3);
|
|
1253659
1254076
|
};
|
|
1253660
1254077
|
|
|
1253661
1254078
|
// cli/src/mcp/tools/memory-file.ts
|
|
1253662
|
-
var
|
|
1254079
|
+
var registerMemoryFileReadTool = (server2, client3) => {
|
|
1253663
1254080
|
server2.tool("memory_file_read", "Read the master memory file (MEMORY.md). This file contains persistent knowledge, user preferences, and instructions that are included in every conversation.", {}, async () => {
|
|
1253664
1254081
|
try {
|
|
1253665
1254082
|
const result = await client3.readMemoryFile();
|
|
@@ -1253668,6 +1254085,9 @@ var registerMemoryFileTools = (server2, client3) => {
|
|
|
1253668
1254085
|
return handleToolError(err);
|
|
1253669
1254086
|
}
|
|
1253670
1254087
|
});
|
|
1254088
|
+
};
|
|
1254089
|
+
var registerMemoryFileTools = (server2, client3) => {
|
|
1254090
|
+
registerMemoryFileReadTool(server2, client3);
|
|
1253671
1254091
|
server2.tool("memory_file_update", "Update the master memory file. Provide full content to replace the entire file, or specify a section heading to update just that section. The memory file is included in every conversation, so keep it organized and concise.", {
|
|
1253672
1254092
|
content: exports_external.string().describe("The content to write. If section is specified, this replaces only that section body."),
|
|
1253673
1254093
|
section: exports_external.optional(exports_external.string()).describe('Optional markdown heading (e.g., "## Preferences") to update a specific section. If omitted, replaces the entire file.')
|
|
@@ -1253770,7 +1254190,7 @@ function registerTools(server2, client3) {
|
|
|
1253770
1254190
|
registerSearchTools(server2, client3);
|
|
1253771
1254191
|
}
|
|
1253772
1254192
|
// cli/src/utils/server.ts
|
|
1253773
|
-
import { existsSync as existsSync31, readFileSync as
|
|
1254193
|
+
import { existsSync as existsSync31, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12, cpSync } from "fs";
|
|
1253774
1254194
|
import { join as join37 } from "path";
|
|
1253775
1254195
|
import { homedir as homedir13 } from "os";
|
|
1253776
1254196
|
var DEFAULT_PORT = 7777;
|
|
@@ -1253794,7 +1254214,7 @@ function expandPath2(p3) {
|
|
|
1253794
1254214
|
function readSettingsFile(path14) {
|
|
1253795
1254215
|
try {
|
|
1253796
1254216
|
if (existsSync31(path14)) {
|
|
1253797
|
-
const content =
|
|
1254217
|
+
const content = readFileSync18(path14, "utf-8");
|
|
1253798
1254218
|
return JSON.parse(content);
|
|
1253799
1254219
|
}
|
|
1253800
1254220
|
} catch {}
|
|
@@ -1253834,8 +1254254,8 @@ function discoverServerUrl(urlOverride, portOverride) {
|
|
|
1253834
1254254
|
}
|
|
1253835
1254255
|
|
|
1253836
1254256
|
// cli/src/client.ts
|
|
1253837
|
-
import { readFileSync as
|
|
1253838
|
-
import { basename as
|
|
1254257
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
1254258
|
+
import { basename as basename5 } from "path";
|
|
1253839
1254259
|
|
|
1253840
1254260
|
class FulcrumClient {
|
|
1253841
1254261
|
baseUrl;
|
|
@@ -1254097,8 +1254517,8 @@ class FulcrumClient {
|
|
|
1254097
1254517
|
return this.fetch(`/api/tasks/${taskId}/attachments`);
|
|
1254098
1254518
|
}
|
|
1254099
1254519
|
async uploadTaskAttachment(taskId, filePath) {
|
|
1254100
|
-
const fileContent =
|
|
1254101
|
-
const filename =
|
|
1254520
|
+
const fileContent = readFileSync19(filePath);
|
|
1254521
|
+
const filename = basename5(filePath);
|
|
1254102
1254522
|
const formData = new FormData;
|
|
1254103
1254523
|
const blob3 = new Blob([fileContent]);
|
|
1254104
1254524
|
formData.append("file", blob3, filename);
|
|
@@ -1254184,8 +1254604,8 @@ class FulcrumClient {
|
|
|
1254184
1254604
|
return this.fetch(`/api/projects/${projectId}/attachments`);
|
|
1254185
1254605
|
}
|
|
1254186
1254606
|
async uploadProjectAttachment(projectId, filePath) {
|
|
1254187
|
-
const fileContent =
|
|
1254188
|
-
const filename =
|
|
1254607
|
+
const fileContent = readFileSync19(filePath);
|
|
1254608
|
+
const filename = basename5(filePath);
|
|
1254189
1254609
|
const formData = new FormData;
|
|
1254190
1254610
|
const blob3 = new Blob([fileContent]);
|
|
1254191
1254611
|
formData.append("file", blob3, filename);
|
|
@@ -1254602,7 +1255022,7 @@ mcpRoutes.all("/", async (c) => {
|
|
|
1254602
1255022
|
});
|
|
1254603
1255023
|
const server2 = new McpServer({
|
|
1254604
1255024
|
name: "fulcrum",
|
|
1254605
|
-
version: "3.
|
|
1255025
|
+
version: "3.4.0"
|
|
1254606
1255026
|
});
|
|
1254607
1255027
|
const client3 = new FulcrumClient(`http://localhost:${port}`);
|
|
1254608
1255028
|
registerTools(server2, client3);
|
|
@@ -1254625,8 +1255045,10 @@ mcpObserverRoutes.all("/", async (c) => {
|
|
|
1254625
1255045
|
version: "2.12.0"
|
|
1254626
1255046
|
});
|
|
1254627
1255047
|
const client3 = new FulcrumClient(`http://localhost:${port}`);
|
|
1254628
|
-
|
|
1254629
|
-
|
|
1255048
|
+
registerMemoryObserverTools(server2, client3);
|
|
1255049
|
+
registerMemoryFileReadTool(server2, client3);
|
|
1255050
|
+
registerTaskObserverTools(server2, client3);
|
|
1255051
|
+
registerNotificationTools(server2, client3);
|
|
1254630
1255052
|
await server2.connect(transport);
|
|
1254631
1255053
|
return transport.handleRequest(c.req.raw);
|
|
1254632
1255054
|
});
|
|
@@ -1255687,7 +1256109,8 @@ app23.post("/send", async (c) => {
|
|
|
1255687
1256109
|
const result = await sendMessageToChannel(body.channel, body.body, {
|
|
1255688
1256110
|
subject: body.subject,
|
|
1255689
1256111
|
replyToMessageId: body.replyToMessageId,
|
|
1255690
|
-
slackBlocks: body.slackBlocks
|
|
1256112
|
+
slackBlocks: body.slackBlocks,
|
|
1256113
|
+
filePath: body.filePath
|
|
1255691
1256114
|
});
|
|
1255692
1256115
|
if (result.success) {
|
|
1255693
1256116
|
log2.messaging.info("Message sent via API", {
|
|
@@ -1257559,6 +1257982,7 @@ init_logger3();
|
|
|
1257559
1257982
|
init_settings();
|
|
1257560
1257983
|
init_assistant_service();
|
|
1257561
1257984
|
init_session_mapper();
|
|
1257985
|
+
init_memory_file_service();
|
|
1257562
1257986
|
var HOURLY_INTERVAL = 60 * 60 * 1000;
|
|
1257563
1257987
|
var hourlyIntervalId = null;
|
|
1257564
1257988
|
var morningTimeoutId = null;
|
|
@@ -1257598,10 +1258022,14 @@ async function runHourlySweep() {
|
|
|
1257598
1258022
|
const lastSweep = getLastSweepRun("hourly");
|
|
1257599
1258023
|
const actionableMemoryCount = countActionableMemories();
|
|
1257600
1258024
|
const openTaskCount = countOpenTasks();
|
|
1258025
|
+
const memoryFileContent = readMemoryFile();
|
|
1258026
|
+
const memoryFileLineCount = memoryFileContent.trim() ? memoryFileContent.split(`
|
|
1258027
|
+
`).length : 0;
|
|
1257601
1258028
|
log2.assistant.info("Running hourly sweep", {
|
|
1257602
1258029
|
runId: run2.id,
|
|
1257603
1258030
|
actionableMemories: actionableMemoryCount,
|
|
1257604
|
-
openTasks: openTaskCount
|
|
1258031
|
+
openTasks: openTaskCount,
|
|
1258032
|
+
memoryFileLines: memoryFileLineCount
|
|
1257605
1258033
|
});
|
|
1257606
1258034
|
const { session: session3 } = getOrCreateSession(SWEEP_SESSION_PREFIX, "sweep-agent", "Assistant Sweep");
|
|
1257607
1258035
|
const prompt = `Perform your hourly sweep. Last sweep: ${lastSweep?.completedAt ?? "never"}`;
|