@threadbase-sh/streamer 1.15.4 → 1.16.1
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/dist/cli.cjs +6156 -5671
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +78 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +78 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -596,6 +596,7 @@ type ApiDeps = {
|
|
|
596
596
|
handleGetOutput: (sessionId: string, res: ServerResponse) => void;
|
|
597
597
|
handleSendInput: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
598
598
|
handleCancel: (sessionId: string, res: ServerResponse) => void;
|
|
599
|
+
handleStopSession: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
599
600
|
handleSetSessionName: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
600
601
|
handleUploadFile: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
601
602
|
handleAdopt: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
@@ -725,6 +726,7 @@ declare class StreamerServer {
|
|
|
725
726
|
private log;
|
|
726
727
|
private agentConfig;
|
|
727
728
|
private agentClient;
|
|
729
|
+
private sessionStatusBus;
|
|
728
730
|
constructor(config: ServerConfig & {
|
|
729
731
|
apiKey: string;
|
|
730
732
|
});
|
|
@@ -750,6 +752,7 @@ declare class StreamerServer {
|
|
|
750
752
|
private checkExchangeRateLimit;
|
|
751
753
|
private handleListConversations;
|
|
752
754
|
private handleConversationsCount;
|
|
755
|
+
private refreshCountInBackground;
|
|
753
756
|
private handleSessionsCount;
|
|
754
757
|
private handleGetRecentSessions;
|
|
755
758
|
private handleGetPopularProjects;
|
|
@@ -771,6 +774,7 @@ declare class StreamerServer {
|
|
|
771
774
|
private handleUploadFile;
|
|
772
775
|
private handleGetOutput;
|
|
773
776
|
private handleCancel;
|
|
777
|
+
private handleStopSession;
|
|
774
778
|
private handleAdopt;
|
|
775
779
|
private handleStartSession;
|
|
776
780
|
private linkSessionToProject;
|
package/dist/index.d.ts
CHANGED
|
@@ -596,6 +596,7 @@ type ApiDeps = {
|
|
|
596
596
|
handleGetOutput: (sessionId: string, res: ServerResponse) => void;
|
|
597
597
|
handleSendInput: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
598
598
|
handleCancel: (sessionId: string, res: ServerResponse) => void;
|
|
599
|
+
handleStopSession: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
599
600
|
handleSetSessionName: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
600
601
|
handleUploadFile: (sessionId: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
601
602
|
handleAdopt: (sessionId: string, res: ServerResponse) => Promise<void>;
|
|
@@ -725,6 +726,7 @@ declare class StreamerServer {
|
|
|
725
726
|
private log;
|
|
726
727
|
private agentConfig;
|
|
727
728
|
private agentClient;
|
|
729
|
+
private sessionStatusBus;
|
|
728
730
|
constructor(config: ServerConfig & {
|
|
729
731
|
apiKey: string;
|
|
730
732
|
});
|
|
@@ -750,6 +752,7 @@ declare class StreamerServer {
|
|
|
750
752
|
private checkExchangeRateLimit;
|
|
751
753
|
private handleListConversations;
|
|
752
754
|
private handleConversationsCount;
|
|
755
|
+
private refreshCountInBackground;
|
|
753
756
|
private handleSessionsCount;
|
|
754
757
|
private handleGetRecentSessions;
|
|
755
758
|
private handleGetPopularProjects;
|
|
@@ -771,6 +774,7 @@ declare class StreamerServer {
|
|
|
771
774
|
private handleUploadFile;
|
|
772
775
|
private handleGetOutput;
|
|
773
776
|
private handleCancel;
|
|
777
|
+
private handleStopSession;
|
|
774
778
|
private handleAdopt;
|
|
775
779
|
private handleStartSession;
|
|
776
780
|
private linkSessionToProject;
|
package/dist/index.js
CHANGED
|
@@ -1189,6 +1189,7 @@ import {
|
|
|
1189
1189
|
ConversationScanner,
|
|
1190
1190
|
search
|
|
1191
1191
|
} from "@threadbase-sh/scanner";
|
|
1192
|
+
import { EventEmitter } from "events";
|
|
1192
1193
|
import {
|
|
1193
1194
|
createReadStream,
|
|
1194
1195
|
existsSync as existsSync6,
|
|
@@ -1845,6 +1846,10 @@ var createSessionRoutes = (deps) => {
|
|
|
1845
1846
|
await deps.handleAdopt(c.req.param("id"), c.env.outgoing);
|
|
1846
1847
|
return alreadyHandled6();
|
|
1847
1848
|
});
|
|
1849
|
+
app.post("/:id/stop", async (c) => {
|
|
1850
|
+
await deps.handleStopSession(c.req.param("id"), c.env.outgoing);
|
|
1851
|
+
return alreadyHandled6();
|
|
1852
|
+
});
|
|
1848
1853
|
app.get("/:id", (c) => {
|
|
1849
1854
|
deps.handleGetSession(c.req.param("id"), c.env.outgoing);
|
|
1850
1855
|
return alreadyHandled6();
|
|
@@ -3927,7 +3932,9 @@ var StreamerServer = class {
|
|
|
3927
3932
|
log = getLogger("server");
|
|
3928
3933
|
agentConfig;
|
|
3929
3934
|
agentClient = null;
|
|
3935
|
+
sessionStatusBus = new EventEmitter();
|
|
3930
3936
|
constructor(config) {
|
|
3937
|
+
this.sessionStatusBus.setMaxListeners(0);
|
|
3931
3938
|
this.apiKey = config.apiKey;
|
|
3932
3939
|
this.localNoAuth = config.localNoAuth ?? false;
|
|
3933
3940
|
this.verbose = config.verbose ?? false;
|
|
@@ -4044,6 +4051,7 @@ var StreamerServer = class {
|
|
|
4044
4051
|
if (resp) {
|
|
4045
4052
|
this.wsHub.broadcast({ type: "session_update", session: resp });
|
|
4046
4053
|
}
|
|
4054
|
+
this.sessionStatusBus.emit(`status:${session.id}`, session.status);
|
|
4047
4055
|
}
|
|
4048
4056
|
});
|
|
4049
4057
|
this.agentConfig = readAgentConfig();
|
|
@@ -4089,6 +4097,7 @@ var StreamerServer = class {
|
|
|
4089
4097
|
handleGetOutput: (id, res) => this.handleGetOutput(id, res),
|
|
4090
4098
|
handleSendInput: (id, req, res) => this.handleSendInput(id, req, res),
|
|
4091
4099
|
handleCancel: (id, res) => this.handleCancel(id, res),
|
|
4100
|
+
handleStopSession: (id, res) => this.handleStopSession(id, res),
|
|
4092
4101
|
handleSetSessionName: (id, req, res) => this.handleSetSessionName(id, req, res),
|
|
4093
4102
|
handleUploadFile: (id, req, res) => this.handleUploadFile(id, req, res),
|
|
4094
4103
|
handleAdopt: (id, res) => this.handleAdopt(id, res),
|
|
@@ -4597,22 +4606,36 @@ var StreamerServer = class {
|
|
|
4597
4606
|
async handleConversationsCount(url, res) {
|
|
4598
4607
|
const project = url.searchParams.get("project") ?? void 0;
|
|
4599
4608
|
const bustCache = url.searchParams.get("refresh") === "1";
|
|
4600
|
-
if (
|
|
4601
|
-
this.cache?.invalidate();
|
|
4602
|
-
this.scanner = null;
|
|
4603
|
-
this.scannerReady = null;
|
|
4604
|
-
}
|
|
4605
|
-
if (this.cache && !bustCache) {
|
|
4609
|
+
if (this.cache) {
|
|
4606
4610
|
const { total } = this.cache.listConversations({ project, limit: 0, offset: 0 });
|
|
4607
4611
|
json(res, 200, { total });
|
|
4612
|
+
if (bustCache) this.refreshCountInBackground();
|
|
4608
4613
|
return;
|
|
4609
4614
|
}
|
|
4610
|
-
const scanner = await this.getScanner();
|
|
4615
|
+
const scanner = await this.getScanner(true);
|
|
4611
4616
|
let metas = [...scanner.getMetadataCache().values()];
|
|
4612
4617
|
metas = applyIncludeFilter(metas, "conversations");
|
|
4613
4618
|
if (project) metas = applyProjectFilter(metas, project);
|
|
4614
4619
|
json(res, 200, { total: metas.length });
|
|
4615
4620
|
}
|
|
4621
|
+
// Fire-and-forget full rescan that reconciles the SQLite cache from disk so a
|
|
4622
|
+
// later count reflects new/removed conversations. Never awaited by the request
|
|
4623
|
+
// path — refresh=1 returns the cached total synchronously and this catches up.
|
|
4624
|
+
refreshCountInBackground() {
|
|
4625
|
+
void (async () => {
|
|
4626
|
+
try {
|
|
4627
|
+
const scanner = await this.getFreshScanner();
|
|
4628
|
+
if (this.cache) {
|
|
4629
|
+
this.cache.upsertFromScannerMeta([...scanner.getMetadataCache().values()]);
|
|
4630
|
+
}
|
|
4631
|
+
} catch (err) {
|
|
4632
|
+
this.log.warn(
|
|
4633
|
+
`Background count refresh failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
4634
|
+
{ event: "count.refresh_failed" }
|
|
4635
|
+
);
|
|
4636
|
+
}
|
|
4637
|
+
})();
|
|
4638
|
+
}
|
|
4616
4639
|
handleSessionsCount(res) {
|
|
4617
4640
|
json(res, 200, { total: this.sessionStore.list(this.ptyAttachedIds()).length });
|
|
4618
4641
|
}
|
|
@@ -5267,6 +5290,54 @@ var StreamerServer = class {
|
|
|
5267
5290
|
json(res, 400, { error: message });
|
|
5268
5291
|
}
|
|
5269
5292
|
}
|
|
5293
|
+
async handleStopSession(sessionId, res) {
|
|
5294
|
+
const STOP_TIMEOUT_MS = 5e3;
|
|
5295
|
+
const session = this.ptyManager.getSession(sessionId);
|
|
5296
|
+
if (!session) {
|
|
5297
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
5298
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
5299
|
+
return;
|
|
5300
|
+
}
|
|
5301
|
+
if (session.status === "idle") {
|
|
5302
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5303
|
+
res.end(JSON.stringify({ status: "already_idle", sessionId }));
|
|
5304
|
+
return;
|
|
5305
|
+
}
|
|
5306
|
+
res.writeHead(200, {
|
|
5307
|
+
"Content-Type": "application/x-ndjson",
|
|
5308
|
+
"Transfer-Encoding": "chunked",
|
|
5309
|
+
"Cache-Control": "no-cache",
|
|
5310
|
+
"X-Accel-Buffering": "no"
|
|
5311
|
+
});
|
|
5312
|
+
res.write(`${JSON.stringify({ event: "stopping", sessionId })}
|
|
5313
|
+
`);
|
|
5314
|
+
const idlePromise = new Promise((resolve2) => {
|
|
5315
|
+
const handler = (status) => {
|
|
5316
|
+
if (status === "idle") {
|
|
5317
|
+
this.sessionStatusBus.off(`status:${sessionId}`, handler);
|
|
5318
|
+
resolve2("idle");
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
this.sessionStatusBus.on(`status:${sessionId}`, handler);
|
|
5322
|
+
});
|
|
5323
|
+
const timeoutPromise = new Promise(
|
|
5324
|
+
(resolve2) => setTimeout(() => resolve2("timeout"), STOP_TIMEOUT_MS)
|
|
5325
|
+
);
|
|
5326
|
+
this.ptyManager.putOnHold(sessionId);
|
|
5327
|
+
this.discoveryCache = null;
|
|
5328
|
+
const outcome = await Promise.race([idlePromise, timeoutPromise]);
|
|
5329
|
+
if (outcome === "idle") {
|
|
5330
|
+
res.write(`${JSON.stringify({ event: "stopped", sessionId })}
|
|
5331
|
+
`);
|
|
5332
|
+
} else {
|
|
5333
|
+
res.write(`${JSON.stringify({ event: "timeout", sessionId })}
|
|
5334
|
+
`);
|
|
5335
|
+
this.log.warn(
|
|
5336
|
+
`[stop] session ${sessionId.slice(0, 8)} did not idle within ${STOP_TIMEOUT_MS}ms`
|
|
5337
|
+
);
|
|
5338
|
+
}
|
|
5339
|
+
res.end();
|
|
5340
|
+
}
|
|
5270
5341
|
async handleAdopt(sessionId, res) {
|
|
5271
5342
|
const discovered = await discoverClaudeProcesses();
|
|
5272
5343
|
this.sessionStore.setDiscovered(discovered);
|