@fdcn/sourcefed 0.2.8 → 0.3.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.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-DK3LZ5RU.js → chunk-QPDTUNFA.js} +45 -19
  3. package/dist/cli.js +72 -38
  4. package/dist/daemon.js +45 -19
  5. package/dist/data/core/SKILL.md +4 -2
  6. package/dist/data/core/references/commands.md +2 -2
  7. package/dist/data/sourcefed/SKILL.md +4 -4
  8. package/dist/mcp.js +2 -2
  9. package/dist/opencode.js +139 -12545
  10. package/dist/pi.js +2 -2
  11. package/dist/tui-compiled.mjs +144 -126
  12. package/dist.dts/adapters/opencode/bridge.d.ts +4 -5
  13. package/dist.dts/adapters/opencode/index.d.ts +4 -3
  14. package/dist.dts/adapters/opencode/tools/monitor_create.d.ts +52 -2
  15. package/dist.dts/adapters/opencode/tools/monitor_list.d.ts +13 -2
  16. package/dist.dts/adapters/opencode/tools/monitor_start.d.ts +19 -2
  17. package/dist.dts/adapters/opencode/tools/monitor_status.d.ts +19 -2
  18. package/dist.dts/adapters/opencode/tools/monitor_stop.d.ts +19 -2
  19. package/dist.dts/adapters/opencode/tui/plugin.d.ts +2 -2
  20. package/dist.dts/adapters/opencode/tui/sidebar.d.ts +8 -3
  21. package/dist.dts/adapters/opencode/tui.d.ts +3 -2
  22. package/dist.dts/index.d.ts +1 -1
  23. package/dist.dts/packages/daemon/src/daemon.d.ts +2 -0
  24. package/dist.dts/packages/sourcefed/src/server.d.ts +2 -6
  25. package/dist.dts/providers/slack/src/monitor.d.ts +1 -1
  26. package/dist.dts/providers/slack/src/poll/utils/parse-slack-read-result.d.ts +1 -1
  27. package/dist.dts/providers/slack/src/poll/utils/slack-api.d.ts +4 -0
  28. package/dist.dts/providers/slack/src/schema.d.ts +1 -1
  29. package/dist.dts/providers/slack/src/types/source-record.d.ts +1 -1
  30. package/package.json +2 -2
package/README.md CHANGED
@@ -33,12 +33,12 @@ OpenCode loads this package as both a server and a TUI plugin:
33
33
 
34
34
  ```json
35
35
  // opencode.json
36
- { "plugin": ["@fdcn/sourcefed@0.2.8"] }
36
+ { "plugin": ["@fdcn/sourcefed@0.3.0"] }
37
37
  ```
38
38
 
39
39
  ```json
40
40
  // tui.json
41
- { "plugin": ["@fdcn/sourcefed@0.2.8"] }
41
+ { "plugin": ["@fdcn/sourcefed@0.3.0"] }
42
42
  ```
43
43
 
44
44
  The package exposes `./server`, `./tui`, `./opencode`, `./opencode/tui`, and `./pi` entrypoints for hosts that load them directly.
@@ -11008,7 +11008,7 @@ function safeParse24(schema, input, config$1) {
11008
11008
  var SlackSourceSchema = /* @__PURE__ */ object23({
11009
11009
  type: /* @__PURE__ */ literal3("slack"),
11010
11010
  channelId: /* @__PURE__ */ string24(),
11011
- threadTs: /* @__PURE__ */ string24()
11011
+ threadTs: /* @__PURE__ */ optional22(/* @__PURE__ */ string24())
11012
11012
  });
11013
11013
  var SlackCursorSchema = /* @__PURE__ */ object23({
11014
11014
  primed: /* @__PURE__ */ boolean23(),
@@ -11025,7 +11025,7 @@ function messageAt(ts) {
11025
11025
  if (value > 0) return new Date(value * 1e3).toISOString();
11026
11026
  return (/* @__PURE__ */ new Date()).toISOString();
11027
11027
  }
11028
- function parseSlackReadResult(result, cursorRaw) {
11028
+ function parseSlackReadResult(result, cursorRaw, location = "thread") {
11029
11029
  const parsedCursor = /* @__PURE__ */ safeParse24(SlackCursorSchema, cursorRaw);
11030
11030
  let previous = {};
11031
11031
  if (parsedCursor.success) previous = parsedCursor.output;
@@ -11053,7 +11053,7 @@ function parseSlackReadResult(result, cursorRaw) {
11053
11053
  kind: "message",
11054
11054
  id: `message:${message.ts}`,
11055
11055
  at: messageAt(message.ts),
11056
- summary: `Slack thread message by ${author}`,
11056
+ summary: `Slack ${location} message by ${author}`,
11057
11057
  body: text,
11058
11058
  actionable: true
11059
11059
  };
@@ -11074,15 +11074,19 @@ function slackToken() {
11074
11074
  return process.env.SOURCEFED_SLACK_TOKEN;
11075
11075
  }
11076
11076
  async function fetchSlackThread(channelId, threadTs) {
11077
+ return fetchSlackMessages("conversations.replies", { channel: channelId, ts: threadTs, inclusive: "false" });
11078
+ }
11079
+ async function fetchSlackDm(channelId) {
11080
+ return fetchSlackMessages("conversations.history", { channel: channelId });
11081
+ }
11082
+ async function fetchSlackMessages(method, params) {
11077
11083
  const messages = [];
11078
11084
  let cursor = "";
11079
11085
  let received = false;
11080
11086
  do {
11081
- const page = await slackApi("conversations.replies", {
11082
- channel: channelId,
11083
- ts: threadTs,
11087
+ const page = await slackApi(method, {
11088
+ ...params,
11084
11089
  limit: "1000",
11085
- inclusive: "false",
11086
11090
  ...cursor ? { cursor } : {}
11087
11091
  });
11088
11092
  if (!page) break;
@@ -11106,8 +11110,7 @@ async function fetchSlackThread(channelId, threadTs) {
11106
11110
  }
11107
11111
  usersCursor = page.response_metadata?.next_cursor ?? "";
11108
11112
  } while (usersCursor);
11109
- const result = { messages, users };
11110
- return result;
11113
+ return { messages, users };
11111
11114
  }
11112
11115
  async function slackApi(method, params) {
11113
11116
  const token = slackToken();
@@ -11132,9 +11135,9 @@ async function slackApi(method, params) {
11132
11135
  }
11133
11136
  }
11134
11137
  async function pollSlack(source, cursorRaw) {
11135
- const result = await fetchSlackThread(source.channelId, source.threadTs);
11138
+ const result = source.threadTs ? await fetchSlackThread(source.channelId, source.threadTs) : await fetchSlackDm(source.channelId);
11136
11139
  if (!result) throw new Error("slack API request failed; check SOURCEFED_SLACK_TOKEN");
11137
- const parsed = parseSlackReadResult(result, cursorRaw);
11140
+ const parsed = parseSlackReadResult(result, cursorRaw, source.threadTs ? "thread" : "DM");
11138
11141
  return { ...parsed, terminal: false };
11139
11142
  }
11140
11143
  var slackPollMonitor = new PollMonitor3({
@@ -11151,7 +11154,8 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11151
11154
  const event = payload.event;
11152
11155
  const channelId = event?.channel;
11153
11156
  const threadTs = event?.thread_ts;
11154
- if (payload.type !== "event_callback" || event?.type !== "message" || !channelId || !threadTs) return void 0;
11157
+ if (payload.type !== "event_callback" || event?.type !== "message" || !channelId) return void 0;
11158
+ if (!threadTs && !channelId.startsWith("D")) return void 0;
11155
11159
  let text = "[message without text]";
11156
11160
  if (event.text?.trim()) text = event.text.trim();
11157
11161
  let author = event.username;
@@ -11160,12 +11164,14 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11160
11164
  if (!author) author = "someone";
11161
11165
  let at = messageAt(event.event_ts);
11162
11166
  if (!event.event_ts) at = messageAt(event.ts);
11167
+ const source = threadTs ? { type: "slack", channelId, threadTs } : { type: "slack", channelId };
11168
+ const location = threadTs ? "thread" : "DM";
11163
11169
  return {
11164
- source: { type: "slack", channelId, threadTs },
11170
+ source,
11165
11171
  kind: "message",
11166
11172
  id: event.ts === void 0 ? void 0 : `message:${event.ts}`,
11167
11173
  at,
11168
- summary: `Slack thread message by ${author}`,
11174
+ summary: `Slack ${location} message by ${author}`,
11169
11175
  body: `${text}
11170
11176
 
11171
11177
  Slack event: ${deliveryId}`,
@@ -11218,14 +11224,16 @@ var SlackMonitor = class extends Monitor3 {
11218
11224
  constructor() {
11219
11225
  super({ type: "slack", schema: SlackSourceSchema });
11220
11226
  }
11221
- key = (source) => `slack:${source.channelId}#${source.threadTs}`;
11227
+ key = (source) => source.threadTs ? `slack:${source.channelId}#${source.threadTs}` : `slack:${source.channelId}`;
11222
11228
  icon = "\u{F04B1}";
11223
- label = () => "thread";
11229
+ label = (source) => source.threadTs ? "thread" : "DM";
11224
11230
  detail = (source) => ` ${source.channelId}`;
11225
- describe = (source) => `Slack ${source.channelId} thread ${source.threadTs}`;
11231
+ describe = (source) => source.threadTs ? `Slack ${source.channelId} thread ${source.threadTs}` : `Slack DM ${source.channelId}`;
11226
11232
  build(input) {
11227
11233
  if (input.channelId && input.threadTs) return { type: "slack", channelId: input.channelId, threadTs: input.threadTs };
11228
- if (!input.threadUrl) return { error: "channelId + threadTs or threadUrl is required for a slack monitor" };
11234
+ if (input.channelId?.startsWith("D")) return { type: "slack", channelId: input.channelId };
11235
+ if (input.channelId) return { error: "threadTs is required unless channelId is a Slack DM ID" };
11236
+ if (!input.threadUrl) return { error: "channelId or threadUrl is required for a slack monitor" };
11229
11237
  let url;
11230
11238
  try {
11231
11239
  url = new URL(input.threadUrl);
@@ -11597,6 +11605,7 @@ var SourcefedDaemon = class {
11597
11605
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11598
11606
  const stopped = await this.service.stop(id);
11599
11607
  if ("error" in stopped) return { ok: false, error: stopped.error };
11608
+ await this.purgeMonitorEvents(monitor.target, id);
11600
11609
  return { ok: true, monitor: monitorView(stopped) };
11601
11610
  }
11602
11611
  async startMonitor(target, id) {
@@ -11610,6 +11619,7 @@ var SourcefedDaemon = class {
11610
11619
  const monitor = await ownedMonitor(this.service, id, target);
11611
11620
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11612
11621
  await this.service.remove(id);
11622
+ await this.purgeMonitorEvents(monitor.target, id);
11613
11623
  return { ok: true, removed: true };
11614
11624
  }
11615
11625
  async readEvents(target) {
@@ -11683,6 +11693,8 @@ var SourcefedDaemon = class {
11683
11693
  async deliver(key, target, listeners) {
11684
11694
  const queued = await this.queue.read(target);
11685
11695
  if (queued.length === 0) return;
11696
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11697
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11686
11698
  let receivedByListener = this.received.get(key);
11687
11699
  if (!receivedByListener) {
11688
11700
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11692,7 +11704,7 @@ var SourcefedDaemon = class {
11692
11704
  for (const received of receivedByListener.values()) {
11693
11705
  for (const id of received) claimed.add(id);
11694
11706
  }
11695
- const fresh = queued.filter((event) => !claimed.has(event.id));
11707
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11696
11708
  if (fresh.length === 0) return;
11697
11709
  for (const listener of [...listeners]) {
11698
11710
  let received = receivedByListener.get(listener);
@@ -11711,6 +11723,20 @@ var SourcefedDaemon = class {
11711
11723
  }
11712
11724
  }
11713
11725
  }
11726
+ async dropQueued(target, eventIDs) {
11727
+ if (eventIDs.length === 0) return;
11728
+ await this.queue.acknowledge(target, eventIDs);
11729
+ const receivedByListener = this.received.get(targetKey(target));
11730
+ if (!receivedByListener) return;
11731
+ const ids = new Set(eventIDs);
11732
+ for (const received of receivedByListener.values()) {
11733
+ for (const id of ids) received.delete(id);
11734
+ }
11735
+ }
11736
+ async purgeMonitorEvents(target, monitorID) {
11737
+ const queued = await this.queue.read(target);
11738
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11739
+ }
11714
11740
  };
11715
11741
  function monitorView(monitor) {
11716
11742
  const definition = sourceDefinition(monitor.source);
package/dist/cli.js CHANGED
@@ -11014,7 +11014,7 @@ function safeParse24(schema, input, config$1) {
11014
11014
  var SlackSourceSchema = /* @__PURE__ */ object23({
11015
11015
  type: /* @__PURE__ */ literal3("slack"),
11016
11016
  channelId: /* @__PURE__ */ string24(),
11017
- threadTs: /* @__PURE__ */ string24()
11017
+ threadTs: /* @__PURE__ */ optional22(/* @__PURE__ */ string24())
11018
11018
  });
11019
11019
  var SlackCursorSchema = /* @__PURE__ */ object23({
11020
11020
  primed: /* @__PURE__ */ boolean23(),
@@ -11031,7 +11031,7 @@ function messageAt(ts) {
11031
11031
  if (value > 0) return new Date(value * 1e3).toISOString();
11032
11032
  return (/* @__PURE__ */ new Date()).toISOString();
11033
11033
  }
11034
- function parseSlackReadResult(result, cursorRaw) {
11034
+ function parseSlackReadResult(result, cursorRaw, location = "thread") {
11035
11035
  const parsedCursor = /* @__PURE__ */ safeParse24(SlackCursorSchema, cursorRaw);
11036
11036
  let previous = {};
11037
11037
  if (parsedCursor.success) previous = parsedCursor.output;
@@ -11059,7 +11059,7 @@ function parseSlackReadResult(result, cursorRaw) {
11059
11059
  kind: "message",
11060
11060
  id: `message:${message.ts}`,
11061
11061
  at: messageAt(message.ts),
11062
- summary: `Slack thread message by ${author}`,
11062
+ summary: `Slack ${location} message by ${author}`,
11063
11063
  body: text,
11064
11064
  actionable: true
11065
11065
  };
@@ -11080,15 +11080,19 @@ function slackToken() {
11080
11080
  return process.env.SOURCEFED_SLACK_TOKEN;
11081
11081
  }
11082
11082
  async function fetchSlackThread(channelId, threadTs) {
11083
+ return fetchSlackMessages("conversations.replies", { channel: channelId, ts: threadTs, inclusive: "false" });
11084
+ }
11085
+ async function fetchSlackDm(channelId) {
11086
+ return fetchSlackMessages("conversations.history", { channel: channelId });
11087
+ }
11088
+ async function fetchSlackMessages(method, params) {
11083
11089
  const messages = [];
11084
11090
  let cursor = "";
11085
11091
  let received = false;
11086
11092
  do {
11087
- const page = await slackApi("conversations.replies", {
11088
- channel: channelId,
11089
- ts: threadTs,
11093
+ const page = await slackApi(method, {
11094
+ ...params,
11090
11095
  limit: "1000",
11091
- inclusive: "false",
11092
11096
  ...cursor ? { cursor } : {}
11093
11097
  });
11094
11098
  if (!page) break;
@@ -11112,8 +11116,7 @@ async function fetchSlackThread(channelId, threadTs) {
11112
11116
  }
11113
11117
  usersCursor = page.response_metadata?.next_cursor ?? "";
11114
11118
  } while (usersCursor);
11115
- const result = { messages, users };
11116
- return result;
11119
+ return { messages, users };
11117
11120
  }
11118
11121
  async function slackApi(method, params) {
11119
11122
  const token = slackToken();
@@ -11138,9 +11141,9 @@ async function slackApi(method, params) {
11138
11141
  }
11139
11142
  }
11140
11143
  async function pollSlack(source, cursorRaw) {
11141
- const result = await fetchSlackThread(source.channelId, source.threadTs);
11144
+ const result = source.threadTs ? await fetchSlackThread(source.channelId, source.threadTs) : await fetchSlackDm(source.channelId);
11142
11145
  if (!result) throw new Error("slack API request failed; check SOURCEFED_SLACK_TOKEN");
11143
- const parsed = parseSlackReadResult(result, cursorRaw);
11146
+ const parsed = parseSlackReadResult(result, cursorRaw, source.threadTs ? "thread" : "DM");
11144
11147
  return { ...parsed, terminal: false };
11145
11148
  }
11146
11149
  var slackPollMonitor = new PollMonitor3({
@@ -11157,7 +11160,8 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11157
11160
  const event = payload.event;
11158
11161
  const channelId = event?.channel;
11159
11162
  const threadTs = event?.thread_ts;
11160
- if (payload.type !== "event_callback" || event?.type !== "message" || !channelId || !threadTs) return void 0;
11163
+ if (payload.type !== "event_callback" || event?.type !== "message" || !channelId) return void 0;
11164
+ if (!threadTs && !channelId.startsWith("D")) return void 0;
11161
11165
  let text = "[message without text]";
11162
11166
  if (event.text?.trim()) text = event.text.trim();
11163
11167
  let author = event.username;
@@ -11166,12 +11170,14 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11166
11170
  if (!author) author = "someone";
11167
11171
  let at = messageAt(event.event_ts);
11168
11172
  if (!event.event_ts) at = messageAt(event.ts);
11173
+ const source = threadTs ? { type: "slack", channelId, threadTs } : { type: "slack", channelId };
11174
+ const location = threadTs ? "thread" : "DM";
11169
11175
  return {
11170
- source: { type: "slack", channelId, threadTs },
11176
+ source,
11171
11177
  kind: "message",
11172
11178
  id: event.ts === void 0 ? void 0 : `message:${event.ts}`,
11173
11179
  at,
11174
- summary: `Slack thread message by ${author}`,
11180
+ summary: `Slack ${location} message by ${author}`,
11175
11181
  body: `${text}
11176
11182
 
11177
11183
  Slack event: ${deliveryId}`,
@@ -11224,14 +11230,16 @@ var SlackMonitor = class extends Monitor3 {
11224
11230
  constructor() {
11225
11231
  super({ type: "slack", schema: SlackSourceSchema });
11226
11232
  }
11227
- key = (source) => `slack:${source.channelId}#${source.threadTs}`;
11233
+ key = (source) => source.threadTs ? `slack:${source.channelId}#${source.threadTs}` : `slack:${source.channelId}`;
11228
11234
  icon = "\u{F04B1}";
11229
- label = () => "thread";
11235
+ label = (source) => source.threadTs ? "thread" : "DM";
11230
11236
  detail = (source) => ` ${source.channelId}`;
11231
- describe = (source) => `Slack ${source.channelId} thread ${source.threadTs}`;
11237
+ describe = (source) => source.threadTs ? `Slack ${source.channelId} thread ${source.threadTs}` : `Slack DM ${source.channelId}`;
11232
11238
  build(input) {
11233
11239
  if (input.channelId && input.threadTs) return { type: "slack", channelId: input.channelId, threadTs: input.threadTs };
11234
- if (!input.threadUrl) return { error: "channelId + threadTs or threadUrl is required for a slack monitor" };
11240
+ if (input.channelId?.startsWith("D")) return { type: "slack", channelId: input.channelId };
11241
+ if (input.channelId) return { error: "threadTs is required unless channelId is a Slack DM ID" };
11242
+ if (!input.threadUrl) return { error: "channelId or threadUrl is required for a slack monitor" };
11235
11243
  let url2;
11236
11244
  try {
11237
11245
  url2 = new URL(input.threadUrl);
@@ -11520,6 +11528,7 @@ var SourcefedDaemon = class {
11520
11528
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11521
11529
  const stopped = await this.service.stop(id);
11522
11530
  if ("error" in stopped) return { ok: false, error: stopped.error };
11531
+ await this.purgeMonitorEvents(monitor.target, id);
11523
11532
  return { ok: true, monitor: monitorView(stopped) };
11524
11533
  }
11525
11534
  async startMonitor(target, id) {
@@ -11533,6 +11542,7 @@ var SourcefedDaemon = class {
11533
11542
  const monitor = await ownedMonitor(this.service, id, target);
11534
11543
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11535
11544
  await this.service.remove(id);
11545
+ await this.purgeMonitorEvents(monitor.target, id);
11536
11546
  return { ok: true, removed: true };
11537
11547
  }
11538
11548
  async readEvents(target) {
@@ -11606,6 +11616,8 @@ var SourcefedDaemon = class {
11606
11616
  async deliver(key, target, listeners) {
11607
11617
  const queued = await this.queue.read(target);
11608
11618
  if (queued.length === 0) return;
11619
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11620
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11609
11621
  let receivedByListener = this.received.get(key);
11610
11622
  if (!receivedByListener) {
11611
11623
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11615,7 +11627,7 @@ var SourcefedDaemon = class {
11615
11627
  for (const received of receivedByListener.values()) {
11616
11628
  for (const id of received) claimed.add(id);
11617
11629
  }
11618
- const fresh = queued.filter((event) => !claimed.has(event.id));
11630
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11619
11631
  if (fresh.length === 0) return;
11620
11632
  for (const listener of [...listeners]) {
11621
11633
  let received = receivedByListener.get(listener);
@@ -11634,6 +11646,20 @@ var SourcefedDaemon = class {
11634
11646
  }
11635
11647
  }
11636
11648
  }
11649
+ async dropQueued(target, eventIDs) {
11650
+ if (eventIDs.length === 0) return;
11651
+ await this.queue.acknowledge(target, eventIDs);
11652
+ const receivedByListener = this.received.get(targetKey(target));
11653
+ if (!receivedByListener) return;
11654
+ const ids = new Set(eventIDs);
11655
+ for (const received of receivedByListener.values()) {
11656
+ for (const id of ids) received.delete(id);
11657
+ }
11658
+ }
11659
+ async purgeMonitorEvents(target, monitorID) {
11660
+ const queued = await this.queue.read(target);
11661
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11662
+ }
11637
11663
  };
11638
11664
  function monitorView(monitor) {
11639
11665
  const definition = sourceDefinition(monitor.source);
@@ -43501,7 +43527,7 @@ function safeParse242(schema, input, config$1) {
43501
43527
  var SlackSourceSchema2 = /* @__PURE__ */ object232({
43502
43528
  type: /* @__PURE__ */ literal32("slack"),
43503
43529
  channelId: /* @__PURE__ */ string242(),
43504
- threadTs: /* @__PURE__ */ string242()
43530
+ threadTs: /* @__PURE__ */ optional2222(/* @__PURE__ */ string242())
43505
43531
  });
43506
43532
  var SlackCursorSchema2 = /* @__PURE__ */ object232({
43507
43533
  primed: /* @__PURE__ */ boolean232(),
@@ -43518,7 +43544,7 @@ function messageAt2(ts) {
43518
43544
  if (value > 0) return new Date(value * 1e3).toISOString();
43519
43545
  return (/* @__PURE__ */ new Date()).toISOString();
43520
43546
  }
43521
- function parseSlackReadResult2(result, cursorRaw) {
43547
+ function parseSlackReadResult2(result, cursorRaw, location = "thread") {
43522
43548
  const parsedCursor = /* @__PURE__ */ safeParse242(SlackCursorSchema2, cursorRaw);
43523
43549
  let previous = {};
43524
43550
  if (parsedCursor.success) previous = parsedCursor.output;
@@ -43546,7 +43572,7 @@ function parseSlackReadResult2(result, cursorRaw) {
43546
43572
  kind: "message",
43547
43573
  id: `message:${message.ts}`,
43548
43574
  at: messageAt2(message.ts),
43549
- summary: `Slack thread message by ${author}`,
43575
+ summary: `Slack ${location} message by ${author}`,
43550
43576
  body: text,
43551
43577
  actionable: true
43552
43578
  };
@@ -43567,15 +43593,19 @@ function slackToken2() {
43567
43593
  return process.env.SOURCEFED_SLACK_TOKEN;
43568
43594
  }
43569
43595
  async function fetchSlackThread2(channelId, threadTs) {
43596
+ return fetchSlackMessages2("conversations.replies", { channel: channelId, ts: threadTs, inclusive: "false" });
43597
+ }
43598
+ async function fetchSlackDm2(channelId) {
43599
+ return fetchSlackMessages2("conversations.history", { channel: channelId });
43600
+ }
43601
+ async function fetchSlackMessages2(method, params) {
43570
43602
  const messages = [];
43571
43603
  let cursor = "";
43572
43604
  let received = false;
43573
43605
  do {
43574
- const page = await slackApi2("conversations.replies", {
43575
- channel: channelId,
43576
- ts: threadTs,
43606
+ const page = await slackApi2(method, {
43607
+ ...params,
43577
43608
  limit: "1000",
43578
- inclusive: "false",
43579
43609
  ...cursor ? { cursor } : {}
43580
43610
  });
43581
43611
  if (!page) break;
@@ -43599,8 +43629,7 @@ async function fetchSlackThread2(channelId, threadTs) {
43599
43629
  }
43600
43630
  usersCursor = page.response_metadata?.next_cursor ?? "";
43601
43631
  } while (usersCursor);
43602
- const result = { messages, users };
43603
- return result;
43632
+ return { messages, users };
43604
43633
  }
43605
43634
  async function slackApi2(method, params) {
43606
43635
  const token = slackToken2();
@@ -43625,9 +43654,9 @@ async function slackApi2(method, params) {
43625
43654
  }
43626
43655
  }
43627
43656
  async function pollSlack2(source, cursorRaw) {
43628
- const result = await fetchSlackThread2(source.channelId, source.threadTs);
43657
+ const result = source.threadTs ? await fetchSlackThread2(source.channelId, source.threadTs) : await fetchSlackDm2(source.channelId);
43629
43658
  if (!result) throw new Error("slack API request failed; check SOURCEFED_SLACK_TOKEN");
43630
- const parsed = parseSlackReadResult2(result, cursorRaw);
43659
+ const parsed = parseSlackReadResult2(result, cursorRaw, source.threadTs ? "thread" : "DM");
43631
43660
  return { ...parsed, terminal: false };
43632
43661
  }
43633
43662
  var slackPollMonitor2 = new PollMonitor32({
@@ -43644,7 +43673,8 @@ function parseSlackWebhook2(payload, _eventName, deliveryId) {
43644
43673
  const event = payload.event;
43645
43674
  const channelId = event?.channel;
43646
43675
  const threadTs = event?.thread_ts;
43647
- if (payload.type !== "event_callback" || event?.type !== "message" || !channelId || !threadTs) return void 0;
43676
+ if (payload.type !== "event_callback" || event?.type !== "message" || !channelId) return void 0;
43677
+ if (!threadTs && !channelId.startsWith("D")) return void 0;
43648
43678
  let text = "[message without text]";
43649
43679
  if (event.text?.trim()) text = event.text.trim();
43650
43680
  let author = event.username;
@@ -43653,12 +43683,14 @@ function parseSlackWebhook2(payload, _eventName, deliveryId) {
43653
43683
  if (!author) author = "someone";
43654
43684
  let at = messageAt2(event.event_ts);
43655
43685
  if (!event.event_ts) at = messageAt2(event.ts);
43686
+ const source = threadTs ? { type: "slack", channelId, threadTs } : { type: "slack", channelId };
43687
+ const location = threadTs ? "thread" : "DM";
43656
43688
  return {
43657
- source: { type: "slack", channelId, threadTs },
43689
+ source,
43658
43690
  kind: "message",
43659
43691
  id: event.ts === void 0 ? void 0 : `message:${event.ts}`,
43660
43692
  at,
43661
- summary: `Slack thread message by ${author}`,
43693
+ summary: `Slack ${location} message by ${author}`,
43662
43694
  body: `${text}
43663
43695
 
43664
43696
  Slack event: ${deliveryId}`,
@@ -43711,14 +43743,16 @@ var SlackMonitor2 = class extends Monitor32 {
43711
43743
  constructor() {
43712
43744
  super({ type: "slack", schema: SlackSourceSchema2 });
43713
43745
  }
43714
- key = (source) => `slack:${source.channelId}#${source.threadTs}`;
43746
+ key = (source) => source.threadTs ? `slack:${source.channelId}#${source.threadTs}` : `slack:${source.channelId}`;
43715
43747
  icon = "\u{F04B1}";
43716
- label = () => "thread";
43748
+ label = (source) => source.threadTs ? "thread" : "DM";
43717
43749
  detail = (source) => ` ${source.channelId}`;
43718
- describe = (source) => `Slack ${source.channelId} thread ${source.threadTs}`;
43750
+ describe = (source) => source.threadTs ? `Slack ${source.channelId} thread ${source.threadTs}` : `Slack DM ${source.channelId}`;
43719
43751
  build(input) {
43720
43752
  if (input.channelId && input.threadTs) return { type: "slack", channelId: input.channelId, threadTs: input.threadTs };
43721
- if (!input.threadUrl) return { error: "channelId + threadTs or threadUrl is required for a slack monitor" };
43753
+ if (input.channelId?.startsWith("D")) return { type: "slack", channelId: input.channelId };
43754
+ if (input.channelId) return { error: "threadTs is required unless channelId is a Slack DM ID" };
43755
+ if (!input.threadUrl) return { error: "channelId or threadUrl is required for a slack monitor" };
43722
43756
  let url2;
43723
43757
  try {
43724
43758
  url2 = new URL(input.threadUrl);
@@ -43817,7 +43851,7 @@ function buildServer(daemon) {
43817
43851
  server.registerTool(
43818
43852
  "monitor_create",
43819
43853
  {
43820
- description: "Create or reuse a monitor for a Jira issue, GitHub pull request, or Slack thread.",
43854
+ description: "Create or reuse a monitor for a Jira issue, GitHub pull request, Slack thread, or Slack DM.",
43821
43855
  inputSchema: createSchema,
43822
43856
  annotations: { idempotentHint: true, readOnlyHint: false }
43823
43857
  },
package/dist/daemon.js CHANGED
@@ -11010,7 +11010,7 @@ function safeParse24(schema, input, config$1) {
11010
11010
  var SlackSourceSchema = /* @__PURE__ */ object23({
11011
11011
  type: /* @__PURE__ */ literal3("slack"),
11012
11012
  channelId: /* @__PURE__ */ string24(),
11013
- threadTs: /* @__PURE__ */ string24()
11013
+ threadTs: /* @__PURE__ */ optional22(/* @__PURE__ */ string24())
11014
11014
  });
11015
11015
  var SlackCursorSchema = /* @__PURE__ */ object23({
11016
11016
  primed: /* @__PURE__ */ boolean23(),
@@ -11027,7 +11027,7 @@ function messageAt(ts) {
11027
11027
  if (value > 0) return new Date(value * 1e3).toISOString();
11028
11028
  return (/* @__PURE__ */ new Date()).toISOString();
11029
11029
  }
11030
- function parseSlackReadResult(result, cursorRaw) {
11030
+ function parseSlackReadResult(result, cursorRaw, location = "thread") {
11031
11031
  const parsedCursor = /* @__PURE__ */ safeParse24(SlackCursorSchema, cursorRaw);
11032
11032
  let previous = {};
11033
11033
  if (parsedCursor.success) previous = parsedCursor.output;
@@ -11055,7 +11055,7 @@ function parseSlackReadResult(result, cursorRaw) {
11055
11055
  kind: "message",
11056
11056
  id: `message:${message.ts}`,
11057
11057
  at: messageAt(message.ts),
11058
- summary: `Slack thread message by ${author}`,
11058
+ summary: `Slack ${location} message by ${author}`,
11059
11059
  body: text,
11060
11060
  actionable: true
11061
11061
  };
@@ -11076,15 +11076,19 @@ function slackToken() {
11076
11076
  return process.env.SOURCEFED_SLACK_TOKEN;
11077
11077
  }
11078
11078
  async function fetchSlackThread(channelId, threadTs) {
11079
+ return fetchSlackMessages("conversations.replies", { channel: channelId, ts: threadTs, inclusive: "false" });
11080
+ }
11081
+ async function fetchSlackDm(channelId) {
11082
+ return fetchSlackMessages("conversations.history", { channel: channelId });
11083
+ }
11084
+ async function fetchSlackMessages(method, params) {
11079
11085
  const messages = [];
11080
11086
  let cursor = "";
11081
11087
  let received = false;
11082
11088
  do {
11083
- const page = await slackApi("conversations.replies", {
11084
- channel: channelId,
11085
- ts: threadTs,
11089
+ const page = await slackApi(method, {
11090
+ ...params,
11086
11091
  limit: "1000",
11087
- inclusive: "false",
11088
11092
  ...cursor ? { cursor } : {}
11089
11093
  });
11090
11094
  if (!page) break;
@@ -11108,8 +11112,7 @@ async function fetchSlackThread(channelId, threadTs) {
11108
11112
  }
11109
11113
  usersCursor = page.response_metadata?.next_cursor ?? "";
11110
11114
  } while (usersCursor);
11111
- const result = { messages, users };
11112
- return result;
11115
+ return { messages, users };
11113
11116
  }
11114
11117
  async function slackApi(method, params) {
11115
11118
  const token = slackToken();
@@ -11134,9 +11137,9 @@ async function slackApi(method, params) {
11134
11137
  }
11135
11138
  }
11136
11139
  async function pollSlack(source, cursorRaw) {
11137
- const result = await fetchSlackThread(source.channelId, source.threadTs);
11140
+ const result = source.threadTs ? await fetchSlackThread(source.channelId, source.threadTs) : await fetchSlackDm(source.channelId);
11138
11141
  if (!result) throw new Error("slack API request failed; check SOURCEFED_SLACK_TOKEN");
11139
- const parsed = parseSlackReadResult(result, cursorRaw);
11142
+ const parsed = parseSlackReadResult(result, cursorRaw, source.threadTs ? "thread" : "DM");
11140
11143
  return { ...parsed, terminal: false };
11141
11144
  }
11142
11145
  var slackPollMonitor = new PollMonitor3({
@@ -11153,7 +11156,8 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11153
11156
  const event = payload.event;
11154
11157
  const channelId = event?.channel;
11155
11158
  const threadTs = event?.thread_ts;
11156
- if (payload.type !== "event_callback" || event?.type !== "message" || !channelId || !threadTs) return void 0;
11159
+ if (payload.type !== "event_callback" || event?.type !== "message" || !channelId) return void 0;
11160
+ if (!threadTs && !channelId.startsWith("D")) return void 0;
11157
11161
  let text = "[message without text]";
11158
11162
  if (event.text?.trim()) text = event.text.trim();
11159
11163
  let author = event.username;
@@ -11162,12 +11166,14 @@ function parseSlackWebhook(payload, _eventName, deliveryId) {
11162
11166
  if (!author) author = "someone";
11163
11167
  let at = messageAt(event.event_ts);
11164
11168
  if (!event.event_ts) at = messageAt(event.ts);
11169
+ const source = threadTs ? { type: "slack", channelId, threadTs } : { type: "slack", channelId };
11170
+ const location = threadTs ? "thread" : "DM";
11165
11171
  return {
11166
- source: { type: "slack", channelId, threadTs },
11172
+ source,
11167
11173
  kind: "message",
11168
11174
  id: event.ts === void 0 ? void 0 : `message:${event.ts}`,
11169
11175
  at,
11170
- summary: `Slack thread message by ${author}`,
11176
+ summary: `Slack ${location} message by ${author}`,
11171
11177
  body: `${text}
11172
11178
 
11173
11179
  Slack event: ${deliveryId}`,
@@ -11220,14 +11226,16 @@ var SlackMonitor = class extends Monitor3 {
11220
11226
  constructor() {
11221
11227
  super({ type: "slack", schema: SlackSourceSchema });
11222
11228
  }
11223
- key = (source) => `slack:${source.channelId}#${source.threadTs}`;
11229
+ key = (source) => source.threadTs ? `slack:${source.channelId}#${source.threadTs}` : `slack:${source.channelId}`;
11224
11230
  icon = "\u{F04B1}";
11225
- label = () => "thread";
11231
+ label = (source) => source.threadTs ? "thread" : "DM";
11226
11232
  detail = (source) => ` ${source.channelId}`;
11227
- describe = (source) => `Slack ${source.channelId} thread ${source.threadTs}`;
11233
+ describe = (source) => source.threadTs ? `Slack ${source.channelId} thread ${source.threadTs}` : `Slack DM ${source.channelId}`;
11228
11234
  build(input) {
11229
11235
  if (input.channelId && input.threadTs) return { type: "slack", channelId: input.channelId, threadTs: input.threadTs };
11230
- if (!input.threadUrl) return { error: "channelId + threadTs or threadUrl is required for a slack monitor" };
11236
+ if (input.channelId?.startsWith("D")) return { type: "slack", channelId: input.channelId };
11237
+ if (input.channelId) return { error: "threadTs is required unless channelId is a Slack DM ID" };
11238
+ if (!input.threadUrl) return { error: "channelId or threadUrl is required for a slack monitor" };
11231
11239
  let url;
11232
11240
  try {
11233
11241
  url = new URL(input.threadUrl);
@@ -11622,6 +11630,7 @@ var SourcefedDaemon = class {
11622
11630
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11623
11631
  const stopped = await this.service.stop(id);
11624
11632
  if ("error" in stopped) return { ok: false, error: stopped.error };
11633
+ await this.purgeMonitorEvents(monitor.target, id);
11625
11634
  return { ok: true, monitor: monitorView(stopped) };
11626
11635
  }
11627
11636
  async startMonitor(target, id) {
@@ -11635,6 +11644,7 @@ var SourcefedDaemon = class {
11635
11644
  const monitor = await ownedMonitor(this.service, id, target);
11636
11645
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11637
11646
  await this.service.remove(id);
11647
+ await this.purgeMonitorEvents(monitor.target, id);
11638
11648
  return { ok: true, removed: true };
11639
11649
  }
11640
11650
  async readEvents(target) {
@@ -11708,6 +11718,8 @@ var SourcefedDaemon = class {
11708
11718
  async deliver(key, target, listeners) {
11709
11719
  const queued = await this.queue.read(target);
11710
11720
  if (queued.length === 0) return;
11721
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11722
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11711
11723
  let receivedByListener = this.received.get(key);
11712
11724
  if (!receivedByListener) {
11713
11725
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11717,7 +11729,7 @@ var SourcefedDaemon = class {
11717
11729
  for (const received of receivedByListener.values()) {
11718
11730
  for (const id of received) claimed.add(id);
11719
11731
  }
11720
- const fresh = queued.filter((event) => !claimed.has(event.id));
11732
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11721
11733
  if (fresh.length === 0) return;
11722
11734
  for (const listener of [...listeners]) {
11723
11735
  let received = receivedByListener.get(listener);
@@ -11736,6 +11748,20 @@ var SourcefedDaemon = class {
11736
11748
  }
11737
11749
  }
11738
11750
  }
11751
+ async dropQueued(target, eventIDs) {
11752
+ if (eventIDs.length === 0) return;
11753
+ await this.queue.acknowledge(target, eventIDs);
11754
+ const receivedByListener = this.received.get(targetKey(target));
11755
+ if (!receivedByListener) return;
11756
+ const ids = new Set(eventIDs);
11757
+ for (const received of receivedByListener.values()) {
11758
+ for (const id of ids) received.delete(id);
11759
+ }
11760
+ }
11761
+ async purgeMonitorEvents(target, monitorID) {
11762
+ const queued = await this.queue.read(target);
11763
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11764
+ }
11739
11765
  };
11740
11766
  function monitorView(monitor) {
11741
11767
  const definition = sourceDefinition(monitor.source);