@fdcn/sourcefed 0.3.6 → 0.3.7
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 +1 -1
- package/dist/{chunk-X3BZSHKZ.js → chunk-E6J3BAXP.js} +198 -160
- package/dist/cli.js +200 -165
- package/dist/daemon.js +200 -162
- package/dist/mcp.js +1 -1
- package/dist/opencode.js +258 -121
- package/dist/pi.js +1 -1
- package/dist.dts/adapters/opencode/bridge.d.ts +66 -22
- package/dist.dts/adapters/opencode/subscriptions.d.ts +46 -0
- package/dist.dts/adapters/opencode/tool-result.d.ts +2 -1
- package/dist.dts/adapters/opencode/tools/monitor_create.d.ts +3 -2
- package/dist.dts/adapters/opencode/tools/monitor_list.d.ts +3 -2
- package/dist.dts/adapters/opencode/tools/monitor_start.d.ts +3 -2
- package/dist.dts/adapters/opencode/tools/monitor_status.d.ts +3 -2
- package/dist.dts/adapters/opencode/tools/monitor_stop.d.ts +3 -2
- package/dist.dts/packages/daemon/src/event-stream.d.ts +26 -0
- package/dist.dts/packages/daemon/src/types/client.d.ts +3 -1
- package/dist.dts/packages/daemon/src/utils/index.d.ts +0 -1
- package/package.json +1 -1
- package/dist.dts/packages/daemon/src/utils/forward.d.ts +0 -32
- package/dist.dts/packages/daemon/src/utils/gate.d.ts +0 -33
- package/dist.dts/packages/daemon/src/utils/request-router.d.ts +0 -11
- /package/dist.dts/{packages/daemon/src/forward.test.d.ts → adapters/opencode/lifecycle.test.d.ts} +0 -0
- /package/dist.dts/packages/daemon/src/{gate.test.d.ts → stream-lifecycle.test.d.ts} +0 -0
package/dist/opencode.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
daemonCommand,
|
|
8
8
|
daemonEnvironment,
|
|
9
9
|
spawnLocalDaemon
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-E6J3BAXP.js";
|
|
11
11
|
import "./chunk-MLKGABMK.js";
|
|
12
12
|
|
|
13
13
|
// packages/sourcefed/src/server.ts
|
|
@@ -22,9 +22,193 @@ import { Plugin } from "@opencode/plugin";
|
|
|
22
22
|
import { existsSync } from "node:fs";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { Rpc } from "@opencode/plugin";
|
|
25
|
+
|
|
26
|
+
// adapters/opencode/subscriptions.ts
|
|
27
|
+
var REFRESH_MS = 15e3;
|
|
28
|
+
var OpenCodeSubscriptions = class {
|
|
29
|
+
constructor(connect) {
|
|
30
|
+
this.connect = connect;
|
|
31
|
+
}
|
|
32
|
+
owners = /* @__PURE__ */ new Set();
|
|
33
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
34
|
+
lifetime = new AbortController();
|
|
35
|
+
daemon;
|
|
36
|
+
connecting;
|
|
37
|
+
refreshing;
|
|
38
|
+
dirty = false;
|
|
39
|
+
lastAttemptAt = -Infinity;
|
|
40
|
+
timer;
|
|
41
|
+
get closed() {
|
|
42
|
+
return this.lifetime.signal.aborted;
|
|
43
|
+
}
|
|
44
|
+
async add(owner) {
|
|
45
|
+
if (this.closed) throw new Error("sourcefed subscription hub is closed");
|
|
46
|
+
this.owners.add(owner);
|
|
47
|
+
if (!this.timer) {
|
|
48
|
+
this.timer = setInterval(() => void this.refresh(), REFRESH_MS);
|
|
49
|
+
this.timer.unref?.();
|
|
50
|
+
}
|
|
51
|
+
await this.refresh();
|
|
52
|
+
}
|
|
53
|
+
async remove(owner) {
|
|
54
|
+
this.owners.delete(owner);
|
|
55
|
+
const closing = [];
|
|
56
|
+
for (const [id, subscription] of this.subscriptions) {
|
|
57
|
+
if (this.ownerFor(subscription.info)) continue;
|
|
58
|
+
this.subscriptions.delete(id);
|
|
59
|
+
closing.push(this.closeSubscription(subscription));
|
|
60
|
+
}
|
|
61
|
+
if (this.owners.size > 0) {
|
|
62
|
+
await Promise.all(closing);
|
|
63
|
+
await this.refresh();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.lifetime.abort();
|
|
67
|
+
clearInterval(this.timer);
|
|
68
|
+
this.timer = void 0;
|
|
69
|
+
await Promise.all(closing);
|
|
70
|
+
await this.daemon?.close();
|
|
71
|
+
this.daemon = void 0;
|
|
72
|
+
await this.connecting;
|
|
73
|
+
await this.refreshing;
|
|
74
|
+
}
|
|
75
|
+
async request(method, params) {
|
|
76
|
+
const daemon = await this.ensureDaemon();
|
|
77
|
+
if (!daemon || this.closed) throw new Error("sourcefed daemon unavailable or bridge closed");
|
|
78
|
+
return daemon.request(method, params);
|
|
79
|
+
}
|
|
80
|
+
refresh() {
|
|
81
|
+
if (this.closed) return Promise.resolve();
|
|
82
|
+
this.dirty = true;
|
|
83
|
+
if (!this.refreshing) {
|
|
84
|
+
this.refreshing = this.refreshLoop();
|
|
85
|
+
}
|
|
86
|
+
return this.refreshing;
|
|
87
|
+
}
|
|
88
|
+
async refreshLoop() {
|
|
89
|
+
try {
|
|
90
|
+
do {
|
|
91
|
+
this.dirty = false;
|
|
92
|
+
try {
|
|
93
|
+
const daemon = await this.ensureDaemon();
|
|
94
|
+
if (daemon && !this.closed) await this.reconcile(daemon);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (!this.closed) console.error(`[sourcefed] refresh failed: ${String(error)}`);
|
|
97
|
+
}
|
|
98
|
+
} while (this.dirty && !this.closed);
|
|
99
|
+
} finally {
|
|
100
|
+
this.refreshing = void 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async ensureDaemon() {
|
|
104
|
+
if (this.closed) return void 0;
|
|
105
|
+
if (this.daemon) return this.daemon;
|
|
106
|
+
if (this.connecting) return this.connecting;
|
|
107
|
+
if (Date.now() - this.lastAttemptAt < REFRESH_MS) return void 0;
|
|
108
|
+
this.lastAttemptAt = Date.now();
|
|
109
|
+
this.connecting = this.connect().then(async (daemon) => {
|
|
110
|
+
if (this.closed) {
|
|
111
|
+
await daemon.close();
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
this.daemon = daemon;
|
|
115
|
+
return daemon;
|
|
116
|
+
}).catch((error) => {
|
|
117
|
+
console.error(`[sourcefed] daemon unavailable: ${String(error)}`);
|
|
118
|
+
return void 0;
|
|
119
|
+
}).finally(() => {
|
|
120
|
+
this.connecting = void 0;
|
|
121
|
+
});
|
|
122
|
+
return this.connecting;
|
|
123
|
+
}
|
|
124
|
+
async reconcile(daemon) {
|
|
125
|
+
const result = await daemon.request("monitor.targets", { kind: "opencode-session" });
|
|
126
|
+
const targets = enabledSessions(result);
|
|
127
|
+
for (const [id, subscription] of this.subscriptions) {
|
|
128
|
+
if (targets.has(id)) continue;
|
|
129
|
+
this.subscriptions.delete(id);
|
|
130
|
+
await this.closeSubscription(subscription);
|
|
131
|
+
}
|
|
132
|
+
for (const id of targets) {
|
|
133
|
+
if (this.closed) return;
|
|
134
|
+
const reader = this.owners.values().next().value;
|
|
135
|
+
if (!reader) return;
|
|
136
|
+
let info;
|
|
137
|
+
try {
|
|
138
|
+
info = await reader.getSession(id, this.lifetime.signal);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (!error || typeof error !== "object" || !("_tag" in error) || error._tag !== "SessionNotFoundError") {
|
|
141
|
+
if (!this.closed) console.error(`[sourcefed] session ${id} lookup failed: ${String(error)}`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (this.closed) return;
|
|
146
|
+
const owner = info && this.ownerFor(info);
|
|
147
|
+
const existing = this.subscriptions.get(id);
|
|
148
|
+
if (!owner || !info) {
|
|
149
|
+
if (existing) {
|
|
150
|
+
this.subscriptions.delete(id);
|
|
151
|
+
await this.closeSubscription(existing);
|
|
152
|
+
}
|
|
153
|
+
} else if (existing) {
|
|
154
|
+
existing.info = info;
|
|
155
|
+
} else {
|
|
156
|
+
await this.subscribe(daemon, id, info);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
ownerFor(info) {
|
|
161
|
+
const candidates = [...this.owners].filter((owner) => owner.location.project.id === info.projectID);
|
|
162
|
+
return candidates.find((owner) => owner.location.directory === info.location.directory) ?? candidates[0];
|
|
163
|
+
}
|
|
164
|
+
async subscribe(daemon, id, info) {
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const subscription = {
|
|
167
|
+
info,
|
|
168
|
+
controller,
|
|
169
|
+
listener: daemon.subscribe({ kind: "opencode-session", id }, async (events) => {
|
|
170
|
+
await this.route(id, subscription, events);
|
|
171
|
+
}, { signal: controller.signal })
|
|
172
|
+
};
|
|
173
|
+
this.subscriptions.set(id, subscription);
|
|
174
|
+
try {
|
|
175
|
+
await subscription.listener;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (this.subscriptions.get(id) === subscription) this.subscriptions.delete(id);
|
|
178
|
+
if (!controller.signal.aborted) console.error(`[sourcefed] subscribe ${id} failed: ${String(error)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async route(id, subscription, events) {
|
|
182
|
+
const reader = this.owners.values().next().value;
|
|
183
|
+
if (!reader) throw new Error("sourcefed session owner unloaded");
|
|
184
|
+
const info = await reader.getSession(id, this.lifetime.signal);
|
|
185
|
+
const owner = this.ownerFor(info);
|
|
186
|
+
if (!owner) throw new Error("sourcefed session has no loaded project owner");
|
|
187
|
+
subscription.info = info;
|
|
188
|
+
await owner.routeEvents(events);
|
|
189
|
+
}
|
|
190
|
+
async closeSubscription(subscription) {
|
|
191
|
+
subscription.controller.abort();
|
|
192
|
+
await subscription.listener.then((listener) => listener.close(), () => void 0);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
function enabledSessions(result) {
|
|
196
|
+
if (!result || typeof result !== "object" || !("targets" in result) || !Array.isArray(result.targets)) {
|
|
197
|
+
throw new Error("invalid monitor.targets response");
|
|
198
|
+
}
|
|
199
|
+
const ids = /* @__PURE__ */ new Set();
|
|
200
|
+
for (const target of result.targets) {
|
|
201
|
+
if (!target || target.kind !== "opencode-session" || typeof target.id !== "string") {
|
|
202
|
+
throw new Error("invalid opencode-session target");
|
|
203
|
+
}
|
|
204
|
+
ids.add(target.id);
|
|
205
|
+
}
|
|
206
|
+
return ids;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// adapters/opencode/bridge.ts
|
|
25
210
|
var TARGET_KIND = "opencode-session";
|
|
26
211
|
var GATE_TIMEOUT_MS = 3e3;
|
|
27
|
-
var RESUBSCRIBE_MS = 15e3;
|
|
28
212
|
var ChauffeurGate = Rpc.define({
|
|
29
213
|
id: "chauffeur",
|
|
30
214
|
methods: {
|
|
@@ -52,97 +236,53 @@ var ChauffeurGate = Rpc.define({
|
|
|
52
236
|
},
|
|
53
237
|
events: {}
|
|
54
238
|
});
|
|
55
|
-
var
|
|
239
|
+
var HUB = Symbol.for("sourcefed.opencode.subscriptions.v1");
|
|
240
|
+
var shared = globalThis;
|
|
241
|
+
function subscriptions() {
|
|
242
|
+
if (!shared[HUB] || shared[HUB].closed) shared[HUB] = new OpenCodeSubscriptions(connectDaemon);
|
|
243
|
+
return shared[HUB];
|
|
244
|
+
}
|
|
56
245
|
var OpenCodeBridge = class {
|
|
57
|
-
constructor(session, rpc) {
|
|
246
|
+
constructor(session, rpc, location, hub = subscriptions()) {
|
|
58
247
|
this.session = session;
|
|
59
248
|
this.rpc = rpc;
|
|
249
|
+
this.location = location;
|
|
250
|
+
this.hub = hub;
|
|
60
251
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
lastError;
|
|
65
|
-
timer;
|
|
252
|
+
started;
|
|
253
|
+
closed = false;
|
|
254
|
+
closing;
|
|
66
255
|
/**
|
|
67
256
|
* Chauffeur or the CLI can create a monitor for a session here at any time,
|
|
68
257
|
* without this bridge's tools, so check for new targets periodically too.
|
|
69
258
|
*/
|
|
70
259
|
async start() {
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
260
|
+
if (this.closed) throw new Error("sourcefed bridge is closed");
|
|
261
|
+
this.started ??= this.hub.add(this);
|
|
262
|
+
await this.started;
|
|
74
263
|
}
|
|
75
|
-
async
|
|
76
|
-
await this.ensureDaemon();
|
|
77
|
-
const existing = this.listeners.get(sessionID);
|
|
78
|
-
if (existing) {
|
|
79
|
-
await existing;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const pending = this.daemon.subscribe(this.target(sessionID), async (events) => {
|
|
83
|
-
await this.routeEvents(events);
|
|
84
|
-
});
|
|
85
|
-
this.listeners.set(sessionID, pending);
|
|
264
|
+
async callTool(name, arguments_, sessionID) {
|
|
86
265
|
try {
|
|
87
|
-
await
|
|
266
|
+
await this.start();
|
|
267
|
+
const method = name.replace(/_/g, ".");
|
|
268
|
+
const result = await this.hub.request(method, { ...arguments_, target: this.target(sessionID) });
|
|
269
|
+
if (["monitor.create", "monitor.start", "monitor.stop", "monitor.remove"].includes(method)) await this.hub.refresh();
|
|
270
|
+
return result;
|
|
88
271
|
} catch (error) {
|
|
89
|
-
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async callTool(name, arguments_, sessionID) {
|
|
94
|
-
await this.ensureDaemon();
|
|
95
|
-
if (!this.daemon) {
|
|
96
|
-
return { ok: false, error: `sourcefed daemon unavailable: ${this.lastError ?? "not started"}` };
|
|
272
|
+
return { ok: false, error: String(error) };
|
|
97
273
|
}
|
|
98
|
-
await this.ensureTarget(sessionID);
|
|
99
|
-
return this.daemon.request(name.replace(/_/g, "."), { ...arguments_, target: this.target(sessionID) });
|
|
100
274
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
await this.daemon?.close();
|
|
107
|
-
this.daemon = void 0;
|
|
275
|
+
close() {
|
|
276
|
+
if (this.closing) return this.closing;
|
|
277
|
+
this.closed = true;
|
|
278
|
+
this.closing = this.started ? this.hub.remove(this) : Promise.resolve();
|
|
279
|
+
return this.closing;
|
|
108
280
|
}
|
|
109
281
|
async refresh() {
|
|
110
|
-
if (!this.
|
|
111
|
-
await this.resubscribe();
|
|
282
|
+
if (!this.closed) await this.hub.refresh();
|
|
112
283
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (Date.now() - this.lastAttemptAt < 15e3) return;
|
|
116
|
-
this.lastAttemptAt = Date.now();
|
|
117
|
-
try {
|
|
118
|
-
this.daemon = await connectDaemon();
|
|
119
|
-
void this.resubscribe();
|
|
120
|
-
} catch (error) {
|
|
121
|
-
this.lastError = error instanceof Error ? error.message : String(error);
|
|
122
|
-
console.error(`[sourcefed] daemon unavailable: ${this.lastError}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Monitors outlive this process, so after a restart nothing listens for their
|
|
127
|
-
* sessions until something subscribes. Subscribe every session that has an
|
|
128
|
-
* enabled monitor and still exists here; a deleted session is skipped, since
|
|
129
|
-
* delivering to it would fail and retry forever.
|
|
130
|
-
*/
|
|
131
|
-
async resubscribe() {
|
|
132
|
-
try {
|
|
133
|
-
const { targets } = await this.daemon.request("monitor.targets", { kind: TARGET_KIND });
|
|
134
|
-
await Promise.all(targets.map((target) => this.resubscribeTarget(target.id)));
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.error(`[sourcefed] resubscribe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async resubscribeTarget(sessionID) {
|
|
140
|
-
if (this.listeners.has(sessionID)) return;
|
|
141
|
-
const exists = await this.session.get({ sessionID }).then(() => true, () => false);
|
|
142
|
-
if (!exists) return;
|
|
143
|
-
await this.ensureTarget(sessionID).catch((error) => {
|
|
144
|
-
console.error(`[sourcefed] subscribe ${sessionID} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
-
});
|
|
284
|
+
getSession(sessionID, signal) {
|
|
285
|
+
return this.session.get({ sessionID }, { signal: AbortSignal.any([signal, AbortSignal.timeout(3e3)]) });
|
|
146
286
|
}
|
|
147
287
|
// Every event arrives as a synthetic message, never as a user prompt, so it is
|
|
148
288
|
// not mistaken for the user's own words. Actionable events resume an idle
|
|
@@ -182,7 +322,7 @@ var OpenCodeBridge = class {
|
|
|
182
322
|
},
|
|
183
323
|
{ signal: AbortSignal.timeout(GATE_TIMEOUT_MS) }
|
|
184
324
|
);
|
|
185
|
-
return reply.deliver
|
|
325
|
+
return !(reply && typeof reply === "object" && "deliver" in reply && reply.deliver === false);
|
|
186
326
|
} catch {
|
|
187
327
|
return true;
|
|
188
328
|
}
|
|
@@ -207,22 +347,15 @@ function cliEntry() {
|
|
|
207
347
|
if (existsSync(bundled)) return bundled;
|
|
208
348
|
return fileURLToPath(import.meta.resolve("@sourcefed/cli"));
|
|
209
349
|
}
|
|
210
|
-
function setOpenCodeBridge(bridge) {
|
|
211
|
-
activeBridge = bridge;
|
|
212
|
-
}
|
|
213
|
-
function getOpenCodeBridge() {
|
|
214
|
-
if (!activeBridge) throw new Error("Sourcefed OpenCode bridge is not initialized");
|
|
215
|
-
return activeBridge;
|
|
216
|
-
}
|
|
217
350
|
|
|
218
351
|
// adapters/opencode/tool-result.ts
|
|
219
|
-
async function callMonitorTool(name, arguments_, sessionID) {
|
|
220
|
-
const result = await
|
|
352
|
+
async function callMonitorTool(bridge, name, arguments_, sessionID) {
|
|
353
|
+
const result = await bridge.callTool(name, arguments_, sessionID);
|
|
221
354
|
return JSON.stringify(result);
|
|
222
355
|
}
|
|
223
356
|
|
|
224
357
|
// adapters/opencode/tools/monitor_create.ts
|
|
225
|
-
var
|
|
358
|
+
var monitor_create_default = (bridge) => ({
|
|
226
359
|
name: "monitor_create",
|
|
227
360
|
description: "Create a detect-only monitor that watches a Jira issue, GitHub PR, Slack thread, or Slack DM and routes NEW events into the current session. Slack monitors only read and notify; they never reply. GitHub and Slack use webhooks when configured and polling otherwise; Jira uses polling.",
|
|
228
361
|
input: {
|
|
@@ -242,13 +375,12 @@ var definition = {
|
|
|
242
375
|
additionalProperties: false
|
|
243
376
|
},
|
|
244
377
|
async execute(input, context) {
|
|
245
|
-
return { content: await callMonitorTool("monitor_create", input ?? {}, context.sessionID) };
|
|
378
|
+
return { content: await callMonitorTool(bridge, "monitor_create", input ?? {}, context.sessionID) };
|
|
246
379
|
}
|
|
247
|
-
};
|
|
248
|
-
var monitor_create_default = definition;
|
|
380
|
+
});
|
|
249
381
|
|
|
250
382
|
// adapters/opencode/tools/monitor_list.ts
|
|
251
|
-
var
|
|
383
|
+
var monitor_list_default = (bridge) => ({
|
|
252
384
|
name: "monitor_list",
|
|
253
385
|
description: "List monitors created by the current session (id, name, source, delivery, enabled).",
|
|
254
386
|
input: {
|
|
@@ -257,13 +389,12 @@ var definition2 = {
|
|
|
257
389
|
additionalProperties: false
|
|
258
390
|
},
|
|
259
391
|
async execute(_input, context) {
|
|
260
|
-
return { content: await callMonitorTool("monitor_list", {}, context.sessionID) };
|
|
392
|
+
return { content: await callMonitorTool(bridge, "monitor_list", {}, context.sessionID) };
|
|
261
393
|
}
|
|
262
|
-
};
|
|
263
|
-
var monitor_list_default = definition2;
|
|
394
|
+
});
|
|
264
395
|
|
|
265
396
|
// adapters/opencode/tools/monitor_status.ts
|
|
266
|
-
var
|
|
397
|
+
var monitor_status_default = (bridge) => ({
|
|
267
398
|
name: "monitor_status",
|
|
268
399
|
description: "Get the status of a monitor created by the current session.",
|
|
269
400
|
input: {
|
|
@@ -276,13 +407,12 @@ var definition3 = {
|
|
|
276
407
|
},
|
|
277
408
|
async execute(input, context) {
|
|
278
409
|
const args = input ?? {};
|
|
279
|
-
return { content: await callMonitorTool("monitor_status", { id: args.id }, context.sessionID) };
|
|
410
|
+
return { content: await callMonitorTool(bridge, "monitor_status", { id: args.id }, context.sessionID) };
|
|
280
411
|
}
|
|
281
|
-
};
|
|
282
|
-
var monitor_status_default = definition3;
|
|
412
|
+
});
|
|
283
413
|
|
|
284
414
|
// adapters/opencode/tools/monitor_start.ts
|
|
285
|
-
var
|
|
415
|
+
var monitor_start_default = (bridge) => ({
|
|
286
416
|
name: "monitor_start",
|
|
287
417
|
description: "Start (re-enable) a stopped monitor created by the current session.",
|
|
288
418
|
input: {
|
|
@@ -295,13 +425,12 @@ var definition4 = {
|
|
|
295
425
|
},
|
|
296
426
|
async execute(input, context) {
|
|
297
427
|
const args = input ?? {};
|
|
298
|
-
return { content: await callMonitorTool("monitor_start", { id: args.id }, context.sessionID) };
|
|
428
|
+
return { content: await callMonitorTool(bridge, "monitor_start", { id: args.id }, context.sessionID) };
|
|
299
429
|
}
|
|
300
|
-
};
|
|
301
|
-
var monitor_start_default = definition4;
|
|
430
|
+
});
|
|
302
431
|
|
|
303
432
|
// adapters/opencode/tools/monitor_stop.ts
|
|
304
|
-
var
|
|
433
|
+
var monitor_stop_default = (bridge) => ({
|
|
305
434
|
name: "monitor_stop",
|
|
306
435
|
description: "Stop a monitor created by the current session.",
|
|
307
436
|
input: {
|
|
@@ -314,23 +443,26 @@ var definition5 = {
|
|
|
314
443
|
},
|
|
315
444
|
async execute(input, context) {
|
|
316
445
|
const args = input ?? {};
|
|
317
|
-
return { content: await callMonitorTool("monitor_stop", { id: args.id }, context.sessionID) };
|
|
446
|
+
return { content: await callMonitorTool(bridge, "monitor_stop", { id: args.id }, context.sessionID) };
|
|
318
447
|
}
|
|
319
|
-
};
|
|
320
|
-
var monitor_stop_default = definition5;
|
|
448
|
+
});
|
|
321
449
|
|
|
322
450
|
// adapters/opencode/index.ts
|
|
323
451
|
async function setupSourcefed(ctx) {
|
|
324
|
-
const bridge = new OpenCodeBridge(ctx.session, ctx.rpc);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
452
|
+
const bridge = new OpenCodeBridge(ctx.session, ctx.rpc, ctx.location);
|
|
453
|
+
try {
|
|
454
|
+
await bridge.start();
|
|
455
|
+
await ctx.tool.transform((editor) => {
|
|
456
|
+
editor.add(monitor_create_default(bridge));
|
|
457
|
+
editor.add(monitor_list_default(bridge));
|
|
458
|
+
editor.add(monitor_status_default(bridge));
|
|
459
|
+
editor.add(monitor_start_default(bridge));
|
|
460
|
+
editor.add(monitor_stop_default(bridge));
|
|
461
|
+
});
|
|
462
|
+
} catch (error) {
|
|
463
|
+
await bridge.close();
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
334
466
|
return () => bridge.close();
|
|
335
467
|
}
|
|
336
468
|
var opencode_default = Plugin.define({
|
|
@@ -345,11 +477,16 @@ var server_default = Plugin2.define({
|
|
|
345
477
|
id: "sourcefed",
|
|
346
478
|
async setup(ctx) {
|
|
347
479
|
const cleanup = await setupSourcefed(ctx);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
event.system.
|
|
351
|
-
|
|
352
|
-
|
|
480
|
+
try {
|
|
481
|
+
await ctx.session.hook("context", (event) => {
|
|
482
|
+
if (!event.system.some((part) => part.type === "text" && part.text.includes(heading))) {
|
|
483
|
+
event.system.push({ type: "text", text: guidance });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
} catch (error) {
|
|
487
|
+
await cleanup?.();
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
353
490
|
return cleanup;
|
|
354
491
|
}
|
|
355
492
|
});
|
package/dist/pi.js
CHANGED
|
@@ -1,34 +1,80 @@
|
|
|
1
|
+
import { type QueuedMonitorEvent } from "@fdcn/sourcefed/core";
|
|
1
2
|
import { type Plugin } from "@opencode/plugin";
|
|
3
|
+
import { OpenCodeSubscriptions, type SessionLocation } from "./subscriptions.js";
|
|
2
4
|
type SessionDomain = Plugin.Context["session"];
|
|
3
|
-
type RpcDomain =
|
|
5
|
+
type RpcDomain = (definition: typeof ChauffeurGate) => {
|
|
6
|
+
gate(input: unknown, options: {
|
|
7
|
+
signal: AbortSignal;
|
|
8
|
+
}): Promise<unknown>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Chauffeur's gate, declared here so sourcefed does not depend on Chauffeur.
|
|
12
|
+
* When the Chauffeur plugin runs in this OpenCode process, it decides which
|
|
13
|
+
* events reach the session; otherwise every event is delivered.
|
|
14
|
+
*/
|
|
15
|
+
declare const ChauffeurGate: {
|
|
16
|
+
readonly id: "chauffeur";
|
|
17
|
+
readonly methods: {
|
|
18
|
+
readonly gate: {
|
|
19
|
+
readonly input: {
|
|
20
|
+
readonly type: "object";
|
|
21
|
+
readonly properties: {
|
|
22
|
+
readonly sessionID: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
};
|
|
25
|
+
readonly monitorID: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly source: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
};
|
|
31
|
+
readonly kind: {
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
};
|
|
34
|
+
readonly summary: {
|
|
35
|
+
readonly type: "string";
|
|
36
|
+
};
|
|
37
|
+
readonly body: {
|
|
38
|
+
readonly type: "string";
|
|
39
|
+
};
|
|
40
|
+
readonly actionable: {
|
|
41
|
+
readonly type: "boolean";
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
readonly required: readonly ["sessionID", "source", "kind", "summary", "actionable"];
|
|
45
|
+
};
|
|
46
|
+
readonly output: {
|
|
47
|
+
readonly type: "object";
|
|
48
|
+
readonly properties: {
|
|
49
|
+
readonly deliver: {
|
|
50
|
+
readonly type: "boolean";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
readonly required: readonly ["deliver"];
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
readonly events: {};
|
|
58
|
+
};
|
|
4
59
|
export declare class OpenCodeBridge {
|
|
5
60
|
private readonly session;
|
|
6
|
-
private readonly rpc
|
|
7
|
-
|
|
8
|
-
private readonly
|
|
9
|
-
private
|
|
10
|
-
private
|
|
11
|
-
private
|
|
12
|
-
constructor(session: SessionDomain, rpc
|
|
61
|
+
private readonly rpc;
|
|
62
|
+
readonly location: SessionLocation;
|
|
63
|
+
private readonly hub;
|
|
64
|
+
private started;
|
|
65
|
+
private closed;
|
|
66
|
+
private closing;
|
|
67
|
+
constructor(session: Pick<SessionDomain, "get" | "synthetic">, rpc: RpcDomain | undefined, location: SessionLocation, hub?: OpenCodeSubscriptions);
|
|
13
68
|
/**
|
|
14
69
|
* Chauffeur or the CLI can create a monitor for a session here at any time,
|
|
15
70
|
* without this bridge's tools, so check for new targets periodically too.
|
|
16
71
|
*/
|
|
17
72
|
start(): Promise<void>;
|
|
18
|
-
ensureTarget(sessionID: string): Promise<void>;
|
|
19
73
|
callTool(name: string, arguments_: Record<string, unknown>, sessionID: string): Promise<unknown>;
|
|
20
74
|
close(): Promise<void>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Monitors outlive this process, so after a restart nothing listens for their
|
|
25
|
-
* sessions until something subscribes. Subscribe every session that has an
|
|
26
|
-
* enabled monitor and still exists here; a deleted session is skipped, since
|
|
27
|
-
* delivering to it would fail and retry forever.
|
|
28
|
-
*/
|
|
29
|
-
private resubscribe;
|
|
30
|
-
private resubscribeTarget;
|
|
31
|
-
private routeEvents;
|
|
75
|
+
refresh(): Promise<void>;
|
|
76
|
+
getSession(sessionID: string, signal: AbortSignal): Promise<import("@opencode/client").SessionInfo>;
|
|
77
|
+
routeEvents(events: QueuedMonitorEvent[]): Promise<void>;
|
|
32
78
|
/**
|
|
33
79
|
* Ask Chauffeur's gate, when it runs in this process. Only an explicit
|
|
34
80
|
* `deliver: false` withholds the event; no gate, an error, or a slow answer
|
|
@@ -37,6 +83,4 @@ export declare class OpenCodeBridge {
|
|
|
37
83
|
private admitted;
|
|
38
84
|
private target;
|
|
39
85
|
}
|
|
40
|
-
export declare function setOpenCodeBridge(bridge: OpenCodeBridge): void;
|
|
41
|
-
export declare function getOpenCodeBridge(): OpenCodeBridge;
|
|
42
86
|
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { QueuedMonitorEvent } from "@fdcn/sourcefed/core";
|
|
2
|
+
import type { DaemonClient } from "@fdcn/sourcefed/daemon";
|
|
3
|
+
export type SessionLocation = {
|
|
4
|
+
directory: string;
|
|
5
|
+
project: {
|
|
6
|
+
id: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
type SessionInfo = {
|
|
10
|
+
projectID: string;
|
|
11
|
+
location: {
|
|
12
|
+
directory: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export interface SubscriptionOwner {
|
|
16
|
+
readonly location: SessionLocation;
|
|
17
|
+
getSession(sessionID: string, signal: AbortSignal): Promise<SessionInfo>;
|
|
18
|
+
routeEvents(events: QueuedMonitorEvent[]): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** One client, timer and stream per enabled session, shared by all plugin locations. */
|
|
21
|
+
export declare class OpenCodeSubscriptions {
|
|
22
|
+
private readonly connect;
|
|
23
|
+
private readonly owners;
|
|
24
|
+
private readonly subscriptions;
|
|
25
|
+
private readonly lifetime;
|
|
26
|
+
private daemon;
|
|
27
|
+
private connecting;
|
|
28
|
+
private refreshing;
|
|
29
|
+
private dirty;
|
|
30
|
+
private lastAttemptAt;
|
|
31
|
+
private timer;
|
|
32
|
+
constructor(connect: () => Promise<DaemonClient>);
|
|
33
|
+
get closed(): boolean;
|
|
34
|
+
add(owner: SubscriptionOwner): Promise<void>;
|
|
35
|
+
remove(owner: SubscriptionOwner): Promise<void>;
|
|
36
|
+
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
37
|
+
refresh(): Promise<void>;
|
|
38
|
+
private refreshLoop;
|
|
39
|
+
private ensureDaemon;
|
|
40
|
+
private reconcile;
|
|
41
|
+
private ownerFor;
|
|
42
|
+
private subscribe;
|
|
43
|
+
private route;
|
|
44
|
+
private closeSubscription;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { OpenCodeBridge } from "./bridge.js";
|
|
2
|
+
export declare function callMonitorTool(bridge: OpenCodeBridge, name: string, arguments_: Record<string, unknown>, sessionID: string): Promise<string>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolContext } from "@opencode/plugin/promise/tool";
|
|
2
|
-
|
|
2
|
+
import type { OpenCodeBridge } from "../bridge.js";
|
|
3
|
+
declare const _default: (bridge: OpenCodeBridge) => {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
input: {
|
|
@@ -50,4 +51,4 @@ declare const definition: {
|
|
|
50
51
|
content: string;
|
|
51
52
|
}>;
|
|
52
53
|
};
|
|
53
|
-
export default
|
|
54
|
+
export default _default;
|