@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/README.md
CHANGED
|
@@ -33,7 +33,7 @@ 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.
|
|
36
|
+
{ "plugins": ["@fdcn/sourcefed@0.3.7"] }
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
The package exposes `./server`, `./tui`, `./opencode`, `./opencode/tui`, and `./pi` entrypoints for hosts that load them directly.
|
|
@@ -11387,29 +11387,6 @@ async function writeResponse(outgoing, response) {
|
|
|
11387
11387
|
stream.pipe(outgoing);
|
|
11388
11388
|
});
|
|
11389
11389
|
}
|
|
11390
|
-
var RequestRouter = class {
|
|
11391
|
-
pending = /* @__PURE__ */ new Map();
|
|
11392
|
-
cursor = 1;
|
|
11393
|
-
nextID() {
|
|
11394
|
-
return this.cursor++;
|
|
11395
|
-
}
|
|
11396
|
-
register(id) {
|
|
11397
|
-
return new Promise((resolve, reject) => {
|
|
11398
|
-
this.pending.set(id, { resolve, reject });
|
|
11399
|
-
});
|
|
11400
|
-
}
|
|
11401
|
-
settle(id, frame) {
|
|
11402
|
-
const pending = this.pending.get(id);
|
|
11403
|
-
if (!pending) return;
|
|
11404
|
-
this.pending.delete(id);
|
|
11405
|
-
if (frame.error) pending.reject(new Error(frame.error));
|
|
11406
|
-
else pending.resolve(frame.result);
|
|
11407
|
-
}
|
|
11408
|
-
rejectAll(error) {
|
|
11409
|
-
for (const { reject } of this.pending.values()) reject(error);
|
|
11410
|
-
this.pending.clear();
|
|
11411
|
-
}
|
|
11412
|
-
};
|
|
11413
11390
|
function defaultDaemonUrl() {
|
|
11414
11391
|
const port = process.env.SOURCEFED_DAEMON_PORT ?? 18787;
|
|
11415
11392
|
return `http://127.0.0.1:${port}`;
|
|
@@ -11802,164 +11779,225 @@ function parseDaemonFrame(line) {
|
|
|
11802
11779
|
return void 0;
|
|
11803
11780
|
}
|
|
11804
11781
|
}
|
|
11782
|
+
var DaemonEventStream = class {
|
|
11783
|
+
constructor(url, headers, onEvents, acknowledge, onClose) {
|
|
11784
|
+
this.url = url;
|
|
11785
|
+
this.headers = headers;
|
|
11786
|
+
this.onEvents = onEvents;
|
|
11787
|
+
this.acknowledge = acknowledge;
|
|
11788
|
+
this.onClose = onClose;
|
|
11789
|
+
}
|
|
11790
|
+
controller = new AbortController();
|
|
11791
|
+
subscribed = Promise.withResolvers();
|
|
11792
|
+
stream;
|
|
11793
|
+
delivering = Promise.resolve();
|
|
11794
|
+
closing;
|
|
11795
|
+
ready = false;
|
|
11796
|
+
failedDelivery = false;
|
|
11797
|
+
async start(signal) {
|
|
11798
|
+
const abort = () => {
|
|
11799
|
+
void this.close();
|
|
11800
|
+
};
|
|
11801
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
11802
|
+
const setup = withTimeout(this.subscribed.promise, 15e3, `sourcefed event stream did not subscribe at ${this.url}`);
|
|
11803
|
+
if (signal?.aborted) abort();
|
|
11804
|
+
const running = this.run();
|
|
11805
|
+
void running.finally(() => signal?.removeEventListener("abort", abort));
|
|
11806
|
+
try {
|
|
11807
|
+
await setup;
|
|
11808
|
+
} catch (error) {
|
|
11809
|
+
await this.close();
|
|
11810
|
+
throw error;
|
|
11811
|
+
}
|
|
11812
|
+
}
|
|
11813
|
+
close() {
|
|
11814
|
+
if (!this.closing) {
|
|
11815
|
+
this.subscribed.reject(new Error("sourcefed event stream closed"));
|
|
11816
|
+
if (!this.ready) this.controller.abort();
|
|
11817
|
+
this.closing = this.drain();
|
|
11818
|
+
}
|
|
11819
|
+
return this.closing;
|
|
11820
|
+
}
|
|
11821
|
+
async drain() {
|
|
11822
|
+
try {
|
|
11823
|
+
await withTimeout(this.delivering, 15e3, "sourcefed event delivery did not drain");
|
|
11824
|
+
} catch (error) {
|
|
11825
|
+
console.error(`[sourcefed] closing event stream: ${String(error)}`);
|
|
11826
|
+
} finally {
|
|
11827
|
+
this.controller.abort();
|
|
11828
|
+
this.stream?.abort();
|
|
11829
|
+
this.onClose();
|
|
11830
|
+
}
|
|
11831
|
+
}
|
|
11832
|
+
async run() {
|
|
11833
|
+
let consecutiveFailures = 0;
|
|
11834
|
+
while (!this.controller.signal.aborted) {
|
|
11835
|
+
this.failedDelivery = false;
|
|
11836
|
+
try {
|
|
11837
|
+
await this.read();
|
|
11838
|
+
consecutiveFailures = 0;
|
|
11839
|
+
} catch (error) {
|
|
11840
|
+
if (!this.controller.signal.aborted && !this.failedDelivery && ++consecutiveFailures === 1) {
|
|
11841
|
+
console.error(`[sourcefed] event stream ended: ${String(error)}`);
|
|
11842
|
+
}
|
|
11843
|
+
}
|
|
11844
|
+
await this.waitForDelivery();
|
|
11845
|
+
if (!this.controller.signal.aborted) await pause(500, this.controller.signal);
|
|
11846
|
+
}
|
|
11847
|
+
}
|
|
11848
|
+
async read() {
|
|
11849
|
+
const controller = new AbortController();
|
|
11850
|
+
const abort = () => controller.abort();
|
|
11851
|
+
this.stream = controller;
|
|
11852
|
+
this.controller.signal.addEventListener("abort", abort, { once: true });
|
|
11853
|
+
let reader;
|
|
11854
|
+
try {
|
|
11855
|
+
this.controller.signal.throwIfAborted();
|
|
11856
|
+
const response = await fetch(this.url, { signal: controller.signal, headers: this.headers });
|
|
11857
|
+
if (!response.ok || !response.body) {
|
|
11858
|
+
await response.body?.cancel();
|
|
11859
|
+
throw new Error(`event stream failed: ${response.status}`);
|
|
11860
|
+
}
|
|
11861
|
+
reader = response.body.getReader();
|
|
11862
|
+
const decoder = new TextDecoder();
|
|
11863
|
+
let buffer = "";
|
|
11864
|
+
while (!controller.signal.aborted) {
|
|
11865
|
+
const { done, value } = await reader.read();
|
|
11866
|
+
if (done) return;
|
|
11867
|
+
buffer += decoder.decode(value, { stream: true });
|
|
11868
|
+
const frames = buffer.split("\n\n");
|
|
11869
|
+
buffer = frames.pop() ?? "";
|
|
11870
|
+
for (const chunk of frames) await this.frame(chunk);
|
|
11871
|
+
}
|
|
11872
|
+
} finally {
|
|
11873
|
+
await reader?.cancel().catch(() => void 0);
|
|
11874
|
+
reader?.releaseLock();
|
|
11875
|
+
this.controller.signal.removeEventListener("abort", abort);
|
|
11876
|
+
if (this.stream === controller) this.stream = void 0;
|
|
11877
|
+
}
|
|
11878
|
+
}
|
|
11879
|
+
async frame(chunk) {
|
|
11880
|
+
if (this.closing || this.controller.signal.aborted) return;
|
|
11881
|
+
for (const line of chunk.split("\n")) {
|
|
11882
|
+
if (!line.startsWith("data: ")) continue;
|
|
11883
|
+
const frame = parseDaemonFrame(line.slice(6));
|
|
11884
|
+
if (!frame || !("type" in frame)) continue;
|
|
11885
|
+
if (frame.type === "subscribed") {
|
|
11886
|
+
this.ready = true;
|
|
11887
|
+
this.subscribed.resolve();
|
|
11888
|
+
}
|
|
11889
|
+
if (frame.type === "event" && frame.events.length > 0) {
|
|
11890
|
+
this.deliver(frame.events);
|
|
11891
|
+
await this.waitForDelivery();
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
}
|
|
11895
|
+
deliver(events) {
|
|
11896
|
+
this.delivering = this.delivering.then(async () => {
|
|
11897
|
+
if (this.failedDelivery || this.controller.signal.aborted) return;
|
|
11898
|
+
try {
|
|
11899
|
+
await this.onEvents(events);
|
|
11900
|
+
await this.acknowledge(events, this.controller.signal);
|
|
11901
|
+
} catch (error) {
|
|
11902
|
+
if (!this.controller.signal.aborted) console.error(`[sourcefed] event delivery failed: ${String(error)}`);
|
|
11903
|
+
this.failedDelivery = true;
|
|
11904
|
+
this.stream?.abort();
|
|
11905
|
+
}
|
|
11906
|
+
});
|
|
11907
|
+
}
|
|
11908
|
+
async waitForDelivery() {
|
|
11909
|
+
if (this.controller.signal.aborted) return;
|
|
11910
|
+
const stopped = Promise.withResolvers();
|
|
11911
|
+
const abort = () => stopped.resolve();
|
|
11912
|
+
this.controller.signal.addEventListener("abort", abort, { once: true });
|
|
11913
|
+
try {
|
|
11914
|
+
await Promise.race([this.delivering, stopped.promise]);
|
|
11915
|
+
} finally {
|
|
11916
|
+
this.controller.signal.removeEventListener("abort", abort);
|
|
11917
|
+
}
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
function pause(ms, signal) {
|
|
11921
|
+
return new Promise((resolve) => {
|
|
11922
|
+
const finish = () => {
|
|
11923
|
+
clearTimeout(timer);
|
|
11924
|
+
signal.removeEventListener("abort", finish);
|
|
11925
|
+
resolve();
|
|
11926
|
+
};
|
|
11927
|
+
const timer = setTimeout(finish, ms);
|
|
11928
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
11929
|
+
if (signal.aborted) finish();
|
|
11930
|
+
});
|
|
11931
|
+
}
|
|
11805
11932
|
async function connectDaemonClient(options) {
|
|
11806
11933
|
const url = options.url ?? process.env.SOURCEFED_DAEMON_URL ?? defaultDaemonUrl();
|
|
11807
11934
|
return new HttpDaemonClient(url, options.token ?? process.env.SOURCEFED_DAEMON_TOKEN);
|
|
11808
11935
|
}
|
|
11809
11936
|
var HttpDaemonClient = class {
|
|
11810
|
-
baseURL;
|
|
11811
|
-
token;
|
|
11812
|
-
router = new RequestRouter();
|
|
11813
|
-
listeners = /* @__PURE__ */ new Map();
|
|
11814
|
-
streams = /* @__PURE__ */ new Set();
|
|
11815
11937
|
constructor(baseURL, token) {
|
|
11816
|
-
this.baseURL = baseURL.replace(/\/+$/, "");
|
|
11817
11938
|
this.token = token;
|
|
11939
|
+
this.baseURL = baseURL.replace(/\/+$/, "");
|
|
11818
11940
|
}
|
|
11941
|
+
baseURL;
|
|
11942
|
+
streams = /* @__PURE__ */ new Set();
|
|
11943
|
+
lifetime = new AbortController();
|
|
11944
|
+
cursor = 0;
|
|
11945
|
+
closing;
|
|
11819
11946
|
headers() {
|
|
11820
11947
|
return this.token ? { authorization: `Bearer ${this.token}` } : {};
|
|
11821
11948
|
}
|
|
11822
|
-
async request(method, params) {
|
|
11823
|
-
|
|
11824
|
-
|
|
11949
|
+
async request(method, params, signal = this.lifetime.signal) {
|
|
11950
|
+
if (this.closing && signal === this.lifetime.signal) throw new Error("daemon closed");
|
|
11951
|
+
signal.throwIfAborted();
|
|
11952
|
+
const response = await fetch(`${this.baseURL}/rpc`, {
|
|
11953
|
+
method: "POST",
|
|
11954
|
+
headers: { "content-type": "application/json", ...this.headers() },
|
|
11955
|
+
body: JSON.stringify({ id: ++this.cursor, method, params }),
|
|
11956
|
+
signal: AbortSignal.any([signal, this.lifetime.signal, AbortSignal.timeout(15e3)])
|
|
11957
|
+
});
|
|
11958
|
+
const frame = await response.json();
|
|
11959
|
+
if (!response.ok || frame.error) throw new Error(frame.error ?? `daemon request failed: ${response.status}`);
|
|
11960
|
+
return frame.result;
|
|
11961
|
+
}
|
|
11962
|
+
async subscribe(target, onEvents, options) {
|
|
11963
|
+
if (this.closing) throw new Error("daemon closed");
|
|
11964
|
+
this.lifetime.signal.throwIfAborted();
|
|
11965
|
+
const stream = new DaemonEventStream(
|
|
11966
|
+
`${this.baseURL}/events?target=${encodeTarget(target)}`,
|
|
11967
|
+
this.headers(),
|
|
11968
|
+
onEvents,
|
|
11969
|
+
(events, signal) => this.acknowledge(target, events, signal),
|
|
11970
|
+
() => this.streams.delete(stream)
|
|
11971
|
+
);
|
|
11972
|
+
this.streams.add(stream);
|
|
11825
11973
|
try {
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
headers: { "content-type": "application/json", ...this.headers() },
|
|
11829
|
-
body: JSON.stringify({ id, method, params })
|
|
11830
|
-
});
|
|
11831
|
-
const frame = await response.json();
|
|
11832
|
-
if (!response.ok || frame.error) throw new Error(frame.error ?? `daemon request failed: ${response.status}`);
|
|
11833
|
-
this.router.settle(id, { result: frame.result });
|
|
11974
|
+
await stream.start(options?.signal);
|
|
11975
|
+
return { close: () => stream.close() };
|
|
11834
11976
|
} catch (error) {
|
|
11835
|
-
|
|
11977
|
+
await stream.close();
|
|
11978
|
+
throw error;
|
|
11836
11979
|
}
|
|
11837
|
-
return promise;
|
|
11838
11980
|
}
|
|
11839
|
-
async
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
if (!callbacks) {
|
|
11843
|
-
callbacks = /* @__PURE__ */ new Set();
|
|
11844
|
-
this.listeners.set(key, callbacks);
|
|
11845
|
-
}
|
|
11846
|
-
callbacks.add(onEvents);
|
|
11847
|
-
const controller = new AbortController();
|
|
11848
|
-
this.streams.add(controller);
|
|
11849
|
-
const url = `${this.baseURL}/events?target=${encodeTarget(target)}`;
|
|
11850
|
-
let markSubscribed;
|
|
11851
|
-
const subscribed = new Promise((resolve) => {
|
|
11852
|
-
markSubscribed = () => resolve();
|
|
11853
|
-
});
|
|
11854
|
-
let delivering = Promise.resolve();
|
|
11855
|
-
let activeStream;
|
|
11856
|
-
let retryStream = false;
|
|
11857
|
-
const deliver = (events) => {
|
|
11858
|
-
if (!callbacks?.has(onEvents) || events.length === 0) return Promise.resolve();
|
|
11859
|
-
delivering = delivering.then(async () => {
|
|
11860
|
-
try {
|
|
11861
|
-
await onEvents(events);
|
|
11862
|
-
} catch (error) {
|
|
11863
|
-
console.error(`[sourcefed] event delivery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
11864
|
-
retryStream = true;
|
|
11865
|
-
activeStream?.abort();
|
|
11866
|
-
return;
|
|
11867
|
-
}
|
|
11868
|
-
await acknowledge(events);
|
|
11869
|
-
}).catch((error) => {
|
|
11870
|
-
console.error(`[sourcefed] event delivery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
11871
|
-
});
|
|
11872
|
-
return delivering;
|
|
11873
|
-
};
|
|
11874
|
-
const acknowledge = async (events) => {
|
|
11875
|
-
let attempt = 0;
|
|
11876
|
-
while (!controller.signal.aborted && callbacks?.has(onEvents)) {
|
|
11877
|
-
try {
|
|
11878
|
-
await this.request("monitor.ack", { target, eventIDs: events.map((event) => event.id) });
|
|
11879
|
-
return;
|
|
11880
|
-
} catch (error) {
|
|
11881
|
-
attempt += 1;
|
|
11882
|
-
if (attempt === 3) {
|
|
11883
|
-
console.error(`[sourcefed] event acknowledgement still failing; retrying without repeating delivery: ${error instanceof Error ? error.message : String(error)}`);
|
|
11884
|
-
}
|
|
11885
|
-
await sleep(Math.min(250 * 2 ** (attempt - 1), 4e3));
|
|
11886
|
-
}
|
|
11887
|
-
}
|
|
11888
|
-
};
|
|
11889
|
-
const runStream = async () => {
|
|
11890
|
-
const streamController = new AbortController();
|
|
11891
|
-
const closeStream = () => streamController.abort();
|
|
11892
|
-
activeStream = streamController;
|
|
11893
|
-
controller.signal.addEventListener("abort", closeStream, { once: true });
|
|
11981
|
+
async acknowledge(target, events, signal) {
|
|
11982
|
+
let attempt = 0;
|
|
11983
|
+
while (!signal.aborted) {
|
|
11894
11984
|
try {
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
const reader = response.body.getReader();
|
|
11898
|
-
const decoder = new TextDecoder();
|
|
11899
|
-
let buffer = "";
|
|
11900
|
-
while (true) {
|
|
11901
|
-
const { done, value } = await reader.read();
|
|
11902
|
-
if (done) break;
|
|
11903
|
-
buffer += decoder.decode(value, { stream: true });
|
|
11904
|
-
const frames = buffer.split("\n\n");
|
|
11905
|
-
buffer = frames.pop() ?? "";
|
|
11906
|
-
for (const chunk of frames) {
|
|
11907
|
-
for (const line of chunk.split("\n")) {
|
|
11908
|
-
if (!line.startsWith("data: ")) continue;
|
|
11909
|
-
const frame = parseDaemonFrame(line.slice(6));
|
|
11910
|
-
if (!frame || !("type" in frame)) continue;
|
|
11911
|
-
if (frame.type === "subscribed") markSubscribed();
|
|
11912
|
-
if (frame.type === "event") void deliver(frame.events);
|
|
11913
|
-
}
|
|
11914
|
-
}
|
|
11915
|
-
}
|
|
11916
|
-
consecutiveFailures = 0;
|
|
11985
|
+
await this.request("monitor.ack", { target, eventIDs: events.map((event) => event.id) }, signal);
|
|
11986
|
+
return;
|
|
11917
11987
|
} catch (error) {
|
|
11918
|
-
if (
|
|
11919
|
-
|
|
11920
|
-
if (consecutiveFailures === 1) {
|
|
11921
|
-
console.error(`[sourcefed] event stream ended: ${error instanceof Error ? error.message : String(error)}`);
|
|
11922
|
-
}
|
|
11988
|
+
if (++attempt === 3) {
|
|
11989
|
+
console.error(`[sourcefed] event acknowledgement still failing; retrying without repeating delivery: ${String(error)}`);
|
|
11923
11990
|
}
|
|
11924
|
-
|
|
11925
|
-
controller.signal.removeEventListener("abort", closeStream);
|
|
11926
|
-
if (activeStream === streamController) activeStream = void 0;
|
|
11927
|
-
retryStream = false;
|
|
11991
|
+
await pause(Math.min(250 * 2 ** Math.min(attempt - 1, 4), 4e3), signal);
|
|
11928
11992
|
}
|
|
11929
|
-
};
|
|
11930
|
-
let consecutiveFailures = 0;
|
|
11931
|
-
void (async () => {
|
|
11932
|
-
while (!controller.signal.aborted && callbacks?.has(onEvents)) {
|
|
11933
|
-
await runStream();
|
|
11934
|
-
if (controller.signal.aborted) break;
|
|
11935
|
-
await sleep(500);
|
|
11936
|
-
}
|
|
11937
|
-
})();
|
|
11938
|
-
try {
|
|
11939
|
-
await withTimeout(subscribed, 15e3, `sourcefed event stream did not subscribe at ${url}`);
|
|
11940
|
-
} catch (error) {
|
|
11941
|
-
const callbacks2 = this.listeners.get(key);
|
|
11942
|
-
callbacks2?.delete(onEvents);
|
|
11943
|
-
if (callbacks2 && callbacks2.size === 0) this.listeners.delete(key);
|
|
11944
|
-
controller.abort();
|
|
11945
|
-
this.streams.delete(controller);
|
|
11946
|
-
throw error;
|
|
11947
11993
|
}
|
|
11948
|
-
return {
|
|
11949
|
-
close: async () => {
|
|
11950
|
-
const callbacks2 = this.listeners.get(key);
|
|
11951
|
-
callbacks2?.delete(onEvents);
|
|
11952
|
-
if (callbacks2 && callbacks2.size === 0) this.listeners.delete(key);
|
|
11953
|
-
controller.abort();
|
|
11954
|
-
this.streams.delete(controller);
|
|
11955
|
-
}
|
|
11956
|
-
};
|
|
11957
11994
|
}
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11995
|
+
close() {
|
|
11996
|
+
this.closing ??= Promise.all([...this.streams].map((stream) => stream.close())).then(() => {
|
|
11997
|
+
this.lifetime.abort();
|
|
11998
|
+
this.streams.clear();
|
|
11999
|
+
});
|
|
12000
|
+
return this.closing;
|
|
11963
12001
|
}
|
|
11964
12002
|
};
|
|
11965
12003
|
async function dispatchDaemonRequest(daemon, method, params) {
|