@couralabs/claudin 0.1.6 → 0.1.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/claudin.js +100 -28
- package/package.json +1 -1
package/claudin.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/daemon/src/auth/connectionCredentials.ts
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
var createRunId = () => crypto.randomUUID();
|
|
7
|
+
var createAccessToken = () => randomBytes(32).toString("base64url");
|
|
8
|
+
function appendCredentials(url, { token, runId }) {
|
|
9
|
+
const target = new URL(url);
|
|
10
|
+
target.searchParams.set("token", token);
|
|
11
|
+
target.searchParams.set("runId", runId);
|
|
12
|
+
return target.toString();
|
|
13
|
+
}
|
|
3
14
|
// node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/util.js
|
|
4
15
|
function getEnumValues(entries) {
|
|
5
16
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -7550,8 +7561,8 @@ class DaemonClient {
|
|
|
7550
7561
|
this.socket = socket;
|
|
7551
7562
|
socket.addEventListener("message", (event) => this.notifyListeners(String(event.data)));
|
|
7552
7563
|
}
|
|
7553
|
-
static async connect(url) {
|
|
7554
|
-
const socket = new WebSocket(url);
|
|
7564
|
+
static async connect(url, credentials) {
|
|
7565
|
+
const socket = new WebSocket(appendCredentials(url, credentials));
|
|
7555
7566
|
await new Promise((resolve, reject) => {
|
|
7556
7567
|
socket.addEventListener("open", () => resolve(), { once: true });
|
|
7557
7568
|
socket.addEventListener("error", () => reject(new Error(`Cannot reach ${url}`)), {
|
|
@@ -8816,8 +8827,10 @@ class PermissionGate {
|
|
|
8816
8827
|
}
|
|
8817
8828
|
open(kind, toolUseId, input, signal, announcement) {
|
|
8818
8829
|
return new Promise((settle) => {
|
|
8819
|
-
this.
|
|
8820
|
-
|
|
8830
|
+
const onAbort = () => this.dismiss(kind, toolUseId);
|
|
8831
|
+
const detach = () => signal.removeEventListener("abort", onAbort);
|
|
8832
|
+
this.pending.set(toolUseId, { kind, input, settle, detach });
|
|
8833
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
8821
8834
|
this.emit(announcement);
|
|
8822
8835
|
});
|
|
8823
8836
|
}
|
|
@@ -8832,6 +8845,7 @@ class PermissionGate {
|
|
|
8832
8845
|
if (pending?.kind !== kind)
|
|
8833
8846
|
return;
|
|
8834
8847
|
this.pending.delete(toolUseId);
|
|
8848
|
+
pending.detach();
|
|
8835
8849
|
return pending;
|
|
8836
8850
|
}
|
|
8837
8851
|
listIds(kind) {
|
|
@@ -9211,7 +9225,6 @@ class AgentSession {
|
|
|
9211
9225
|
try {
|
|
9212
9226
|
yield* sink;
|
|
9213
9227
|
} finally {
|
|
9214
|
-
this.pumping = false;
|
|
9215
9228
|
this.listenGates(() => {});
|
|
9216
9229
|
}
|
|
9217
9230
|
}
|
|
@@ -9306,9 +9319,11 @@ class AgentSession {
|
|
|
9306
9319
|
if (this.pendingTurns === 0 && this.liveTaskIds.size === 0)
|
|
9307
9320
|
break;
|
|
9308
9321
|
}
|
|
9322
|
+
this.pumping = false;
|
|
9309
9323
|
sink.end();
|
|
9310
9324
|
} catch (error) {
|
|
9311
9325
|
this.pendingTurns = 0;
|
|
9326
|
+
this.pumping = false;
|
|
9312
9327
|
sink.end(error);
|
|
9313
9328
|
}
|
|
9314
9329
|
}
|
|
@@ -9332,14 +9347,13 @@ class AgentSessionManager {
|
|
|
9332
9347
|
}
|
|
9333
9348
|
createSession(options = {}) {
|
|
9334
9349
|
const sessionId = options.sessionId ?? crypto.randomUUID();
|
|
9335
|
-
this.
|
|
9350
|
+
this.replaceSession(sessionId, { ...options, sessionId });
|
|
9336
9351
|
return sessionId;
|
|
9337
9352
|
}
|
|
9338
9353
|
async resumeSession(sessionId, options = {}) {
|
|
9339
9354
|
if (!await this.runtime.getInfo(sessionId))
|
|
9340
9355
|
return this.createSession({ ...options, sessionId });
|
|
9341
|
-
|
|
9342
|
-
this.sessions.set(sessionId, resumed);
|
|
9356
|
+
this.replaceSession(sessionId, { ...options, resume: sessionId });
|
|
9343
9357
|
return sessionId;
|
|
9344
9358
|
}
|
|
9345
9359
|
async forkSession(sessionId, forkOptions, options = {}) {
|
|
@@ -9354,6 +9368,10 @@ class AgentSessionManager {
|
|
|
9354
9368
|
for (const sessionId of [...this.sessions.keys()])
|
|
9355
9369
|
this.closeSession(sessionId);
|
|
9356
9370
|
}
|
|
9371
|
+
replaceSession(sessionId, options) {
|
|
9372
|
+
this.closeSession(sessionId);
|
|
9373
|
+
this.sessions.set(sessionId, new AgentSession(sessionId, options, this.runtime));
|
|
9374
|
+
}
|
|
9357
9375
|
requireSession(sessionId) {
|
|
9358
9376
|
const session = this.sessions.get(sessionId);
|
|
9359
9377
|
if (!session)
|
|
@@ -9503,6 +9521,31 @@ class ReplayClient {
|
|
|
9503
9521
|
import { once } from "events";
|
|
9504
9522
|
import { WebSocketServer } from "ws";
|
|
9505
9523
|
|
|
9524
|
+
// packages/daemon/src/auth/checkConnection.ts
|
|
9525
|
+
import { timingSafeEqual } from "crypto";
|
|
9526
|
+
var isSameSecret = (given, expected) => {
|
|
9527
|
+
const givenBytes = Buffer.from(given);
|
|
9528
|
+
const expectedBytes = Buffer.from(expected);
|
|
9529
|
+
return givenBytes.length === expectedBytes.length && timingSafeEqual(givenBytes, expectedBytes);
|
|
9530
|
+
};
|
|
9531
|
+
function readRunId(request) {
|
|
9532
|
+
return new URL(request.url ?? "/", "ws://127.0.0.1").searchParams.get("runId");
|
|
9533
|
+
}
|
|
9534
|
+
function checkConnection(request, token, liveRunIds) {
|
|
9535
|
+
if (request.headers.origin)
|
|
9536
|
+
return { status: 403, reason: "Browser origins are not allowed" };
|
|
9537
|
+
const params = new URL(request.url ?? "/", "ws://127.0.0.1").searchParams;
|
|
9538
|
+
if (!isSameSecret(params.get("token") ?? "", token)) {
|
|
9539
|
+
return { status: 401, reason: "Invalid access token" };
|
|
9540
|
+
}
|
|
9541
|
+
const runId = params.get("runId");
|
|
9542
|
+
if (!runId)
|
|
9543
|
+
return { status: 400, reason: "Missing run id" };
|
|
9544
|
+
if (liveRunIds.has(runId))
|
|
9545
|
+
return { status: 409, reason: "Run id already connected" };
|
|
9546
|
+
return;
|
|
9547
|
+
}
|
|
9548
|
+
|
|
9506
9549
|
// packages/daemon/src/persistence/rateLimitCache.ts
|
|
9507
9550
|
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
9508
9551
|
import { homedir as homedir4 } from "os";
|
|
@@ -11360,11 +11403,18 @@ var workspaceHandlers = {
|
|
|
11360
11403
|
|
|
11361
11404
|
// packages/daemon/src/server/handleRequest.ts
|
|
11362
11405
|
var handlers = { ...sessionHandlers, ...agentHandlers, ...workspaceHandlers, ...optionsHandlers };
|
|
11406
|
+
var SESSION_SWITCH_TYPES = new Set([
|
|
11407
|
+
"session.created",
|
|
11408
|
+
"session.restored",
|
|
11409
|
+
"session.forked"
|
|
11410
|
+
]);
|
|
11363
11411
|
async function* handleRequest(context, request) {
|
|
11364
|
-
|
|
11412
|
+
const origin = context.session;
|
|
11413
|
+
await origin.dispatch(request);
|
|
11365
11414
|
const handler = handlers[request.type];
|
|
11366
11415
|
for await (const response of handler(context, request)) {
|
|
11367
|
-
|
|
11416
|
+
const target = SESSION_SWITCH_TYPES.has(response.type) ? context.session : origin;
|
|
11417
|
+
await target.dispatch(response);
|
|
11368
11418
|
yield response;
|
|
11369
11419
|
}
|
|
11370
11420
|
}
|
|
@@ -11382,7 +11432,12 @@ function publishEvent(clients, origin, event, toOrigin = true) {
|
|
|
11382
11432
|
}
|
|
11383
11433
|
|
|
11384
11434
|
// packages/daemon/src/server/startDaemon.ts
|
|
11385
|
-
var
|
|
11435
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
11436
|
+
async function reportFailure(context, server, socket, error) {
|
|
11437
|
+
const failure = { type: "request.failed", reason: String(error) };
|
|
11438
|
+
await context.session.dispatch(failure).catch(() => {});
|
|
11439
|
+
publishEvent(server.clients, socket, failure);
|
|
11440
|
+
}
|
|
11386
11441
|
async function answerMessage(context, server, socket, data) {
|
|
11387
11442
|
try {
|
|
11388
11443
|
const request = requestEventSchema.parse(JSON.parse(data));
|
|
@@ -11391,15 +11446,27 @@ async function answerMessage(context, server, socket, data) {
|
|
|
11391
11446
|
publishEvent(server.clients, socket, response);
|
|
11392
11447
|
}
|
|
11393
11448
|
} catch (error) {
|
|
11394
|
-
|
|
11395
|
-
await context.session.dispatch(failure);
|
|
11396
|
-
publishEvent(server.clients, socket, failure);
|
|
11449
|
+
await reportFailure(context, server, socket, error);
|
|
11397
11450
|
}
|
|
11398
11451
|
}
|
|
11399
|
-
async function startDaemon(port
|
|
11452
|
+
async function startDaemon(port, token, context) {
|
|
11400
11453
|
const daemonContext = context ?? await createDaemonContext();
|
|
11401
|
-
const
|
|
11402
|
-
server
|
|
11454
|
+
const liveRunIds = new Set;
|
|
11455
|
+
const server = new WebSocketServer({
|
|
11456
|
+
port,
|
|
11457
|
+
host: LOOPBACK_HOST,
|
|
11458
|
+
verifyClient: ({ req }, respond) => {
|
|
11459
|
+
const rejection = checkConnection(req, token, liveRunIds);
|
|
11460
|
+
if (rejection)
|
|
11461
|
+
respond(false, rejection.status, rejection.reason);
|
|
11462
|
+
else
|
|
11463
|
+
respond(true);
|
|
11464
|
+
}
|
|
11465
|
+
});
|
|
11466
|
+
server.on("connection", (socket, request) => {
|
|
11467
|
+
const runId = readRunId(request) ?? "";
|
|
11468
|
+
liveRunIds.add(runId);
|
|
11469
|
+
socket.once("close", () => liveRunIds.delete(runId));
|
|
11403
11470
|
socket.on("message", (data) => answerMessage(daemonContext, server, socket, data.toString()));
|
|
11404
11471
|
});
|
|
11405
11472
|
server.once("close", () => daemonContext.agents.closeAll());
|
|
@@ -24302,7 +24369,8 @@ function useColor() {
|
|
|
24302
24369
|
var program = new Command;
|
|
24303
24370
|
|
|
24304
24371
|
// apps/cli/src/index.ts
|
|
24305
|
-
var
|
|
24372
|
+
var DEFAULT_PORT = "7777";
|
|
24373
|
+
var runId = createRunId();
|
|
24306
24374
|
var program2 = new Command().name("claudin").enablePositionalOptions();
|
|
24307
24375
|
var printResumeHint = (sessionId) => {
|
|
24308
24376
|
if (!sessionId)
|
|
@@ -24316,10 +24384,12 @@ var exitOnDestroy = (renderer) => {
|
|
|
24316
24384
|
renderer.on("destroy", () => process.exit(0));
|
|
24317
24385
|
};
|
|
24318
24386
|
var openContext = (sessionId) => createDaemonContext(undefined, sessionId).catch((error) => program2.error(error.message));
|
|
24319
|
-
var daemonUrl = (port) => `ws://
|
|
24320
|
-
program2.option("-p, --port <port>", "daemon port",
|
|
24321
|
-
const
|
|
24322
|
-
const
|
|
24387
|
+
var daemonUrl = (port) => `ws://127.0.0.1:${Number(port)}`;
|
|
24388
|
+
program2.option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-s, --session <id>", "resume a previous session").action(async (opts) => {
|
|
24389
|
+
const token = createAccessToken();
|
|
24390
|
+
const server = await startDaemon(Number(opts.port), token, await openContext(opts.session));
|
|
24391
|
+
const client = await DaemonClient.connect(daemonUrl(opts.port), { token, runId });
|
|
24392
|
+
const renderer = await startTui(client, {
|
|
24323
24393
|
resumeSessionId: opts.session,
|
|
24324
24394
|
onExit: printResumeHint
|
|
24325
24395
|
});
|
|
@@ -24329,14 +24399,16 @@ program2.option("-p, --port <port>", "daemon port", DEFAULT_PORT2).option("-s, -
|
|
|
24329
24399
|
server.close(() => process.exit(0));
|
|
24330
24400
|
});
|
|
24331
24401
|
});
|
|
24332
|
-
program2.command("daemon").description("Run the daemon without a UI so terminals can attach to it").option("-p, --port <port>", "daemon port",
|
|
24333
|
-
|
|
24334
|
-
|
|
24335
|
-
|
|
24402
|
+
program2.command("daemon").description("Run the daemon without a UI so terminals can attach to it").option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-s, --session <id>", "serve a previous session").action(async (opts) => {
|
|
24403
|
+
const token = createAccessToken();
|
|
24404
|
+
await startDaemon(Number(opts.port), token, await openContext(opts.session));
|
|
24405
|
+
process.stdout.write(`Daemon on ${daemonUrl(opts.port)} (run ${runId})
|
|
24406
|
+
Attach with: claudin attach -p ${opts.port} --token ${token}
|
|
24336
24407
|
`);
|
|
24337
24408
|
});
|
|
24338
|
-
program2.command("attach").description("Attach another terminal to a running daemon's live session").option("-p, --port <port>", "daemon port",
|
|
24339
|
-
const
|
|
24409
|
+
program2.command("attach").description("Attach another terminal to a running daemon's live session").option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-t, --token <token>", "access token printed by the daemon (or CLAUDIN_TOKEN)").action(async (opts) => {
|
|
24410
|
+
const token = opts.token ?? process.env.CLAUDIN_TOKEN ?? program2.error("An access token is required: pass --token or set CLAUDIN_TOKEN");
|
|
24411
|
+
const client = await DaemonClient.connect(daemonUrl(opts.port), { token, runId }).catch((error) => program2.error(error.message));
|
|
24340
24412
|
const renderer = await startTui(client, { attach: true, onExit: printResumeHint });
|
|
24341
24413
|
client.onDisconnect(() => renderer.destroy());
|
|
24342
24414
|
exitOnDestroy(renderer);
|