@fdcn/sourcefed 0.3.4 → 0.3.5

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/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
  // ~/.config/opencode/opencode.jsonc
36
- { "plugins": ["@fdcn/sourcefed@0.3.4"] }
36
+ { "plugins": ["@fdcn/sourcefed@0.3.5"] }
37
37
  ```
38
38
 
39
39
  ```json
40
40
  // ~/.config/opencode/cli.json
41
- { "plugins": ["@fdcn/sourcefed@0.3.4"] }
41
+ { "plugins": ["@fdcn/sourcefed@0.3.5"] }
42
42
  ```
43
43
 
44
44
  The package exposes `./server`, `./tui`, `./opencode`, `./opencode/tui`, and `./pi` entrypoints for hosts that load them directly.
package/dist/guidance.md CHANGED
@@ -1,71 +1,20 @@
1
- # sourcefed — monitoring guidance
1
+ # sourcefed monitors
2
2
 
3
- sourcefed is a monitor service. It lets you create **monitors** that watch a Jira issue, GitHub
4
- PR, or Slack thread and route NEW events into the host session where the monitor was created.
5
- The OpenCode and Pi adapters drive the Sourcefed daemon client (`@sourcefed/daemon`) for
6
- monitor tools and event delivery; the same daemon can be served through MCP (`@sourcefed/mcp`)
7
- or driven directly by the CLI.
3
+ Monitors watch a Jira issue, GitHub PR, or Slack thread and bring new events into this session.
8
4
 
9
- All transports speak the same daemon JSON protocol over HTTP (`POST /rpc` +
10
- `GET /events` SSE). The MCP adapter adds the 2026 resource-subscription flow
11
- (`subscriptions/listen`, `notifications/resources/updated`, `monitor_events_ack`)
12
- on top. When `SOURCEFED_DAEMON_URL` is unset, the OpenCode and Pi adapters
13
- spawn one local HTTP daemon automatically (if none is running) and connect to it.
5
+ Create one with `monitor_create` when:
14
6
 
15
- ## When to create a monitor
7
+ - you work on a Jira ticket: `{ name: "<key>", sourceType: "jira", issueKey: "PROJ-123" }`
8
+ (new comments, edits, status, assignee, priority, labels);
9
+ - your branch has a PR: `{ name: "<pr>", sourceType: "github", repo: "owner/name", prNumber: 42 }`
10
+ (reviews, comments, CI failures, conflicts, merge or close);
11
+ - you were given a Slack thread: `{ name: "<thread>", sourceType: "slack", threadUrl: "<url>" }`
12
+ (new messages; read-only, never replies).
16
13
 
17
- - **You found or are working on a Jira ticket** → create a Jira monitor for its issue key. You
18
- will be notified of new comments, description edits, and summary/status/assignee/priority/label changes.
19
- - **You are on a branch with an active PR (or just opened one)** → create a GitHub monitor for
20
- `repo#prNumber`. You will be notified of new reviews (including bot reviews), PR and
21
- line-level comments, CI failures, merge conflicts, and the PR being merged or closed.
22
- - **You were given a Slack thread** → create a Slack monitor for the thread URL. It is
23
- detect-only: it reads new messages and routes notifications into this session, but never replies.
14
+ Keep the default `pollIntervalSec` unless the work is time-sensitive (min 15).
15
+ `monitor_list`, `monitor_status { id }` and `monitor_stop { id }` act on this session's monitors.
16
+ GitHub monitors end when the PR merges.
24
17
 
25
- ## How to create one
26
-
27
- Use the `monitor_create` tool:
28
-
29
- - Jira: `monitor_create({ name: "<key>", sourceType: "jira", issueKey: "PROJ-12345" })`
30
- - GitHub: `monitor_create({ name: "<pr>", sourceType: "github", repo: "owner/name", prNumber: 42 })`
31
- - Slack: `monitor_create({ name: "<thread>", sourceType: "slack", threadUrl: "<slack-thread-url>" })`
32
-
33
- GitHub and Slack use webhook delivery when configured and fall back to polling when unavailable.
34
- Jira always uses polling. Slack monitors are
35
- always detect-only and never reply. You can lower
36
- `pollIntervalSec` (min 15) for time-sensitive work, but prefer the default to avoid hammering APIs.
37
- Actionable events trigger an agent turn. Stable all-passing CI is suppressed to avoid notification noise; other informational events are added as context only.
38
-
39
- The abstract `Monitor` consolidates transport fallback and recovery and composes whichever
40
- generic `PollMonitor` and `WebhookMonitor` transports a source exposes as class attributes.
41
- Each provider is its own package (`@sourcefed/provider-{jira,github,slack}`) with monitor and
42
- event modules; the built-in registry (`SOURCE_MAP`) is composed by the daemon, which imports
43
- the provider packages directly. Jira exposes only polling;
44
- GitHub and Slack expose both. Do not add source-specific transport branches to tools,
45
- scheduling, or the daemon.
46
-
47
- ## Skills
48
-
49
- The CLI bundles skills that teach agents how to use sourcefed, in the agent-browser style: a
50
- thin discovery stub points at CLI-served content that always matches the installed version.
51
-
52
- ```bash
53
- sourcefed skills # list available skills
54
- sourcefed skills get core # load the core usage guide (--full adds references)
55
- ```
56
-
57
- ## Lifecycle
58
-
59
- - Monitors belong to the target that created them; list, status, and stop only affect that target's monitors.
60
- - `monitor_list` — see monitors created by this session.
61
- - `monitor_status { id }` — detail on one of this session's monitors.
62
- - `monitor_stop { id }` — stop routing events for one of this session's monitors.
63
- - GitHub monitors remove themselves after the PR is merged. Jira monitors remove themselves when
64
- `SOURCEFED_JIRA_TERMINAL_STATUS` matches the issue status; leave it unset to disable automatic Jira removal.
65
-
66
- ## When an event arrives
67
-
68
- Events are fed into your session as a message (e.g. a new review comment, a CI failure, a Jira
69
- status change). Treat it like any new input: read it, decide whether it needs action, and act
70
- (or tell the user it needs a decision). If the event is non-actionable (e.g. CI all-passing),
71
- acknowledge it and move on — do not fabricate work from it.
18
+ An event arrives as a message. Act on it if it needs action, or tell the user it needs a
19
+ decision; acknowledge an informational one (such as CI passing) and move on. Never invent work
20
+ from it. `sourcefed skills get core` has the full guide.
package/dist/opencode.js CHANGED
@@ -24,6 +24,7 @@ import { fileURLToPath } from "node:url";
24
24
  import { Rpc } from "@opencode/plugin";
25
25
  var TARGET_KIND = "opencode-session";
26
26
  var GATE_TIMEOUT_MS = 3e3;
27
+ var RESUBSCRIBE_MS = 15e3;
27
28
  var ChauffeurGate = Rpc.define({
28
29
  id: "chauffeur",
29
30
  methods: {
@@ -61,8 +62,15 @@ var OpenCodeBridge = class {
61
62
  listeners = /* @__PURE__ */ new Map();
62
63
  lastAttemptAt = 0;
63
64
  lastError;
65
+ timer;
66
+ /**
67
+ * Chauffeur or the CLI can create a monitor for a session here at any time,
68
+ * without this bridge's tools, so check for new targets periodically too.
69
+ */
64
70
  async start() {
65
71
  await this.ensureDaemon();
72
+ this.timer = setInterval(() => void this.refresh(), RESUBSCRIBE_MS);
73
+ this.timer.unref?.();
66
74
  }
67
75
  async ensureTarget(sessionID) {
68
76
  await this.ensureDaemon();
@@ -91,11 +99,17 @@ var OpenCodeBridge = class {
91
99
  return this.daemon.request(name.replace(/_/g, "."), { ...arguments_, target: this.target(sessionID) });
92
100
  }
93
101
  async close() {
102
+ clearInterval(this.timer);
103
+ this.timer = void 0;
94
104
  for (const pending of this.listeners.values()) await pending.then((listener) => listener.close(), () => void 0);
95
105
  this.listeners.clear();
96
106
  await this.daemon?.close();
97
107
  this.daemon = void 0;
98
108
  }
109
+ async refresh() {
110
+ if (!this.daemon) return this.ensureDaemon();
111
+ await this.resubscribe();
112
+ }
99
113
  async ensureDaemon() {
100
114
  if (this.daemon) return;
101
115
  if (Date.now() - this.lastAttemptAt < 15e3) return;
@@ -123,6 +137,7 @@ var OpenCodeBridge = class {
123
137
  }
124
138
  }
125
139
  async resubscribeTarget(sessionID) {
140
+ if (this.listeners.has(sessionID)) return;
126
141
  const exists = await this.session.get({ sessionID }).then(() => true, () => false);
127
142
  if (!exists) return;
128
143
  await this.ensureTarget(sessionID).catch((error) => {
@@ -325,12 +340,13 @@ var opencode_default = Plugin.define({
325
340
 
326
341
  // packages/sourcefed/src/server.ts
327
342
  var guidance = readFileSync(fileURLToPath2(new URL("./guidance.md", import.meta.url)), "utf8");
343
+ var heading = guidance.split("\n", 1)[0] ?? guidance;
328
344
  var server_default = Plugin2.define({
329
345
  id: "sourcefed",
330
346
  async setup(ctx) {
331
347
  const cleanup = await setupSourcefed(ctx);
332
348
  await ctx.session.hook("context", (event) => {
333
- if (!event.system.some((part) => part.type === "text" && part.text.includes("sourcefed"))) {
349
+ if (!event.system.some((part) => part.type === "text" && part.text.includes(heading))) {
334
350
  event.system.push({ type: "text", text: guidance });
335
351
  }
336
352
  });
@@ -8,11 +8,17 @@ export declare class OpenCodeBridge {
8
8
  private readonly listeners;
9
9
  private lastAttemptAt;
10
10
  private lastError;
11
+ private timer;
11
12
  constructor(session: SessionDomain, rpc?: RpcDomain | undefined);
13
+ /**
14
+ * Chauffeur or the CLI can create a monitor for a session here at any time,
15
+ * without this bridge's tools, so check for new targets periodically too.
16
+ */
12
17
  start(): Promise<void>;
13
18
  ensureTarget(sessionID: string): Promise<void>;
14
19
  callTool(name: string, arguments_: Record<string, unknown>, sessionID: string): Promise<unknown>;
15
20
  close(): Promise<void>;
21
+ private refresh;
16
22
  private ensureDaemon;
17
23
  /**
18
24
  * Monitors outlive this process, so after a restart nothing listens for their
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fdcn/sourcefed",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Live Jira, GitHub, and Slack monitoring for agent hosts: CLI, daemon, MCP server, and host plugins.",
5
5
  "license": "MIT",
6
6
  "type": "module",