@fdcn/sourcefed 0.3.4 → 0.3.6

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
@@ -29,16 +29,11 @@ import { createSourcefedMcp } from "@fdcn/sourcefed/mcp"
29
29
 
30
30
  ## Host plugins
31
31
 
32
- OpenCode loads this package as both a server and a TUI plugin:
32
+ OpenCode loads this package as both a server and a TUI plugin from one entry:
33
33
 
34
34
  ```json
35
35
  // ~/.config/opencode/opencode.jsonc
36
- { "plugins": ["@fdcn/sourcefed@0.3.4"] }
37
- ```
38
-
39
- ```json
40
- // ~/.config/opencode/cli.json
41
- { "plugins": ["@fdcn/sourcefed@0.3.4"] }
36
+ { "plugins": ["@fdcn/sourcefed@0.3.6"] }
42
37
  ```
43
38
 
44
39
  The package exposes `./server`, `./tui`, `./opencode`, `./opencode/tui`, and `./pi` entrypoints for hosts that load them directly.
package/dist/cli.js CHANGED
@@ -44192,9 +44192,7 @@ async function runMonitor(argv) {
44192
44192
 
44193
44193
  // packages/cli/src/utils/setup.ts
44194
44194
  import { execFile } from "node:child_process";
44195
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
44196
- import os8 from "node:os";
44197
- import path8 from "node:path";
44195
+ import { readFileSync } from "node:fs";
44198
44196
  import { fileURLToPath } from "node:url";
44199
44197
  function setupSpec() {
44200
44198
  const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
@@ -44211,59 +44209,19 @@ async function setupHost(name, args, spec) {
44211
44209
  const code = error2 && typeof error2 === "object" && "code" in error2 ? String(error2.code) : "";
44212
44210
  if (code === "ENOENT") {
44213
44211
  console.error(`[sourcefed] ${name} is not on PATH \u2014 install it manually:`);
44214
- console.error(name === "opencode" ? ` add "${spec}" to the plugins array in ~/.config/opencode/opencode.jsonc and cli.json` : ` run: pi install ${spec}`);
44212
+ console.error(name === "opencode" ? ` add "${spec}" to the plugins array in ~/.config/opencode/opencode.jsonc` : ` run: pi install ${spec}`);
44215
44213
  return false;
44216
44214
  }
44217
44215
  throw error2;
44218
44216
  }
44219
44217
  }
44220
- function configureOpenCodeCliPlugin(spec) {
44221
- const file = openCodeCliConfigPath();
44222
- const config2 = readOpenCodeCliConfig(file);
44223
- const plugins = config2.plugins;
44224
- if (plugins === void 0) {
44225
- config2.plugins = [spec];
44226
- } else if (!Array.isArray(plugins)) {
44227
- throw new Error(`OpenCode CLI config has a non-array plugins setting: ${file}`);
44228
- } else if (!plugins.some((plugin) => isPluginEntry(plugin, spec))) {
44229
- config2.plugins = [...plugins, spec];
44230
- } else {
44231
- return;
44232
- }
44233
- mkdirSync(path8.dirname(file), { recursive: true });
44234
- writeFileSync(file, `${JSON.stringify(config2, null, 2)}
44235
- `);
44236
- }
44237
- function openCodeCliConfigPath() {
44238
- const configHome = process.env.XDG_CONFIG_HOME ?? path8.join(os8.homedir(), ".config");
44239
- return path8.join(configHome, "opencode", "cli.json");
44240
- }
44241
- function readOpenCodeCliConfig(file) {
44242
- try {
44243
- const value = JSON.parse(readFileSync(file, "utf8"));
44244
- if (!value || typeof value !== "object" || Array.isArray(value)) {
44245
- throw new Error(`OpenCode CLI config must contain a JSON object: ${file}`);
44246
- }
44247
- return value;
44248
- } catch (error2) {
44249
- if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
44250
- if (error2 instanceof SyntaxError) throw new Error(`OpenCode CLI config is not valid JSON: ${file}`);
44251
- throw error2;
44252
- }
44253
- }
44254
- function isPluginEntry(value, spec) {
44255
- if (value === spec) return true;
44256
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
44257
- return "package" in value && value.package === spec;
44258
- }
44259
44218
 
44260
44219
  // packages/cli/src/commands/setup.ts
44261
44220
  async function runSetup(args) {
44262
44221
  const spec = setupSpec();
44263
44222
  const requested = args[0] ?? "all";
44264
44223
  if (requested === "opencode" || requested === "all") {
44265
- const installed = await setupHost("opencode", ["plugin", "add", spec], spec);
44266
- if (installed) configureOpenCodeCliPlugin(spec);
44224
+ await setupHost("opencode", ["plugin", "add", spec], spec);
44267
44225
  }
44268
44226
  if (requested === "pi" || requested === "all") {
44269
44227
  await setupHost("pi", ["install", `npm:${spec}`], `npm:${spec}`);
@@ -44272,12 +44230,12 @@ async function runSetup(args) {
44272
44230
 
44273
44231
  // packages/cli/src/skills/command.ts
44274
44232
  import { readFile as readFile7, readdir as readdir2, stat as stat2 } from "node:fs/promises";
44275
- import path10 from "node:path";
44233
+ import path9 from "node:path";
44276
44234
 
44277
44235
  // packages/cli/src/skills/discovery.ts
44278
44236
  import { statSync } from "node:fs";
44279
44237
  import { readdir, readFile as readFile6, stat } from "node:fs/promises";
44280
- import path9 from "node:path";
44238
+ import path8 from "node:path";
44281
44239
  import { fileURLToPath as fileURLToPath2 } from "node:url";
44282
44240
 
44283
44241
  // packages/cli/src/skills/frontmatter.ts
@@ -44325,7 +44283,7 @@ async function skillsDirs() {
44325
44283
  }
44326
44284
  const directories = [];
44327
44285
  for (const name of SKILL_DIRS) {
44328
- const candidate = path9.join(skillRoot(), name);
44286
+ const candidate = path8.join(skillRoot(), name);
44329
44287
  try {
44330
44288
  if ((await stat(candidate)).isDirectory()) directories.push(candidate);
44331
44289
  } catch {
@@ -44337,7 +44295,7 @@ async function discoverSkills() {
44337
44295
  const skills = [];
44338
44296
  for (const dir of await skillsDirs()) {
44339
44297
  for (const entry of await readdir(dir)) {
44340
- const skillDir = path9.join(dir, entry);
44298
+ const skillDir = path8.join(dir, entry);
44341
44299
  let entryStat;
44342
44300
  try {
44343
44301
  entryStat = await stat(skillDir);
@@ -44345,7 +44303,7 @@ async function discoverSkills() {
44345
44303
  continue;
44346
44304
  }
44347
44305
  if (!entryStat.isDirectory()) continue;
44348
- const skillFile = path9.join(skillDir, "SKILL.md");
44306
+ const skillFile = path8.join(skillDir, "SKILL.md");
44349
44307
  try {
44350
44308
  const content = await readFile6(skillFile, "utf8");
44351
44309
  const parsed = parseFrontmatter(content);
@@ -44358,15 +44316,15 @@ async function discoverSkills() {
44358
44316
  return skills;
44359
44317
  }
44360
44318
  function skillRoot() {
44361
- let dir = path9.dirname(fileURLToPath2(import.meta.url));
44319
+ let dir = path8.dirname(fileURLToPath2(import.meta.url));
44362
44320
  for (let depth = 0; depth < 6; depth++) {
44363
- const marker = path9.join(dir, "data");
44321
+ const marker = path8.join(dir, "data");
44364
44322
  if (statSync(marker, { throwIfNoEntry: false })?.isDirectory()) return dir;
44365
- const parent = path9.dirname(dir);
44323
+ const parent = path8.dirname(dir);
44366
44324
  if (parent === dir) break;
44367
44325
  dir = parent;
44368
44326
  }
44369
- return path9.dirname(fileURLToPath2(import.meta.url));
44327
+ return path8.dirname(fileURLToPath2(import.meta.url));
44370
44328
  }
44371
44329
 
44372
44330
  // packages/cli/src/skills/command.ts
@@ -44413,7 +44371,7 @@ async function runGet(args) {
44413
44371
  output += `# ${skill.name}
44414
44372
 
44415
44373
  `;
44416
- output += await readFile7(path10.join(skill.dir, "SKILL.md"), "utf8");
44374
+ output += await readFile7(path9.join(skill.dir, "SKILL.md"), "utf8");
44417
44375
  output += "\n";
44418
44376
  if (full) output += await supplementaryFiles(skill.dir);
44419
44377
  }
@@ -44436,7 +44394,7 @@ async function runPath(name) {
44436
44394
  async function supplementaryFiles(skillDir) {
44437
44395
  let output = "";
44438
44396
  for (const subdirName of ["references", "templates"]) {
44439
- const subdir = path10.join(skillDir, subdirName);
44397
+ const subdir = path9.join(skillDir, subdirName);
44440
44398
  try {
44441
44399
  if (!(await stat2(subdir)).isDirectory()) continue;
44442
44400
  } catch {
@@ -44444,7 +44402,7 @@ async function supplementaryFiles(skillDir) {
44444
44402
  }
44445
44403
  const entries = (await readdir2(subdir)).sort();
44446
44404
  for (const entry of entries) {
44447
- const filePath = path10.join(subdir, entry);
44405
+ const filePath = path9.join(subdir, entry);
44448
44406
  try {
44449
44407
  if (!(await stat2(filePath)).isFile()) continue;
44450
44408
  } catch {
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
  });
@@ -59,38 +59,43 @@ function Sidebar(props) {
59
59
  void refresh();
60
60
  const timer = setInterval(() => void refresh(), REFRESH_MS);
61
61
  onCleanup(() => clearInterval(timer));
62
- return (() => {
63
- var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$5 = _$createElement("text"), _el$6 = _$createTextNode(` (`), _el$7 = _$createTextNode(`)`);
64
- _$insertNode(_el$, _el$2);
65
- _$setProp(_el$, "flexDirection", "column");
66
- _$setProp(_el$, "width", "100%");
67
- _$setProp(_el$, "marginTop", 1);
68
- _$insertNode(_el$2, _el$3);
69
- _$insertNode(_el$2, _el$5);
70
- _$setProp(_el$2, "flexDirection", "row");
71
- _$setProp(_el$2, "width", "100%");
72
- _$insertNode(_el$3, _$createTextNode(`Sourcefed`));
73
- _$insertNode(_el$5, _el$6);
74
- _$insertNode(_el$5, _el$7);
75
- _$insert(_el$5, () => active().length, _el$7);
76
- _$insert(_el$, _$createComponent(MonitorRows, {
77
- monitors: active,
78
- get tone() {
79
- return palette();
80
- },
81
- compact: true
82
- }), null);
83
- _$effect((_p$) => {
84
- var _v$ = palette().accent, _v$2 = palette().textMuted;
85
- _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
86
- _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
87
- return _p$;
88
- }, {
89
- e: void 0,
90
- t: void 0
91
- });
92
- return _el$;
93
- })();
62
+ return _$createComponent(Show, {
63
+ get when() {
64
+ return active().length > 0;
65
+ },
66
+ get children() {
67
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$5 = _$createElement("text"), _el$6 = _$createTextNode(` (`), _el$7 = _$createTextNode(`)`);
68
+ _$insertNode(_el$, _el$2);
69
+ _$setProp(_el$, "flexDirection", "column");
70
+ _$setProp(_el$, "width", "100%");
71
+ _$setProp(_el$, "marginTop", 1);
72
+ _$insertNode(_el$2, _el$3);
73
+ _$insertNode(_el$2, _el$5);
74
+ _$setProp(_el$2, "flexDirection", "row");
75
+ _$setProp(_el$2, "width", "100%");
76
+ _$insertNode(_el$3, _$createTextNode(`Sourcefed`));
77
+ _$insertNode(_el$5, _el$6);
78
+ _$insertNode(_el$5, _el$7);
79
+ _$insert(_el$5, () => active().length, _el$7);
80
+ _$insert(_el$, _$createComponent(MonitorRows, {
81
+ monitors: active,
82
+ get tone() {
83
+ return palette();
84
+ },
85
+ compact: true
86
+ }), null);
87
+ _$effect((_p$) => {
88
+ var _v$ = palette().accent, _v$2 = palette().textMuted;
89
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
90
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
91
+ return _p$;
92
+ }, {
93
+ e: void 0,
94
+ t: void 0
95
+ });
96
+ return _el$;
97
+ }
98
+ });
94
99
  }
95
100
  function MonitorRows(props) {
96
101
  const visible = createMemo(() => props.monitors().slice(0, 4));
@@ -98,41 +103,26 @@ function MonitorRows(props) {
98
103
  var _el$8 = _$createElement("box");
99
104
  _$setProp(_el$8, "flexDirection", "column");
100
105
  _$setProp(_el$8, "width", "100%");
101
- _$insert(_el$8, _$createComponent(Show, {
102
- get when() {
103
- return visible().length > 0;
104
- },
105
- get fallback() {
106
- return (() => {
107
- var _el$1 = _$createElement("text");
108
- _$insertNode(_el$1, _$createTextNode(`No active monitors`));
109
- _$effect((_$p) => _$setProp(_el$1, "fg", props.tone.textMuted, _$p));
110
- return _el$1;
111
- })();
112
- },
113
- get children() {
114
- return visible().map((monitor) => (() => {
115
- var _el$11 = _$createElement("box"), _el$12 = _$createElement("text"), _el$13 = _$createElement("text"), _el$14 = _$createTextNode(` `);
116
- _$insertNode(_el$11, _el$12);
117
- _$insertNode(_el$11, _el$13);
118
- _$setProp(_el$11, "flexDirection", "row");
119
- _$setProp(_el$11, "width", "100%");
120
- _$insert(_el$12, () => monitor.icon);
121
- _$insertNode(_el$13, _el$14);
122
- _$insert(_el$13, () => monitor.name, null);
123
- _$effect((_p$) => {
124
- var _v$3 = monitorTone(monitor, props.tone), _v$4 = props.tone.textMuted;
125
- _v$3 !== _p$.e && (_p$.e = _$setProp(_el$12, "fg", _v$3, _p$.e));
126
- _v$4 !== _p$.t && (_p$.t = _$setProp(_el$13, "fg", _v$4, _p$.t));
127
- return _p$;
128
- }, {
129
- e: void 0,
130
- t: void 0
131
- });
132
- return _el$11;
133
- })());
134
- }
135
- }), null);
106
+ _$insert(_el$8, () => visible().map((monitor) => (() => {
107
+ var _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"), _el$12 = _$createTextNode(` `);
108
+ _$insertNode(_el$1, _el$10);
109
+ _$insertNode(_el$1, _el$11);
110
+ _$setProp(_el$1, "flexDirection", "row");
111
+ _$setProp(_el$1, "width", "100%");
112
+ _$insert(_el$10, () => monitor.icon);
113
+ _$insertNode(_el$11, _el$12);
114
+ _$insert(_el$11, () => monitor.name, null);
115
+ _$effect((_p$) => {
116
+ var _v$3 = monitorTone(monitor, props.tone), _v$4 = props.tone.textMuted;
117
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$3, _p$.e));
118
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$4, _p$.t));
119
+ return _p$;
120
+ }, {
121
+ e: void 0,
122
+ t: void 0
123
+ });
124
+ return _el$1;
125
+ })()), null);
136
126
  _$insert(_el$8, _$createComponent(Show, {
137
127
  get when() {
138
128
  return _$memo(() => !!props.compact)() && visible().length < props.monitors().length;
@@ -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
@@ -1,3 +1,2 @@
1
1
  export declare function setupSpec(): string;
2
2
  export declare function setupHost(name: string, args: string[], spec: string): Promise<boolean>;
3
- export declare function configureOpenCodeCliPlugin(spec: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fdcn/sourcefed",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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",