zooid 0.7.4 → 0.9.0
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 +5 -5
- package/dist/bin.js +218 -114
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-OKGNOROJ.js → chunk-LOILUENL.js} +389 -51
- package/dist/chunk-LOILUENL.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +13 -9
- package/src/bin.ts +1 -1
- package/src/commands/dev.ts +26 -12
- package/src/daemon/start-daemon.ts +29 -10
- package/src/web/fetch.integration.test.ts +61 -0
- package/src/web/fetch.test.ts +139 -0
- package/src/web/fetch.ts +85 -0
- package/src/web/pin.test.ts +24 -0
- package/src/web/pin.ts +13 -0
- package/src/web/resolve.test.ts +55 -27
- package/src/web/resolve.ts +22 -10
- package/src/web/test-helpers.ts +28 -0
- package/dist/chunk-OKGNOROJ.js.map +0 -1
- package/dist/web/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/web/assets/index-BqOX0Pv4.js +0 -305
- package/dist/web/assets/index-C-ZtBp7U.css +0 -1
- package/dist/web/assets/index-Dc8BJYf_.js +0 -118066
- package/dist/web/assets/reaction-picker-emoji-xD2HkULN.js +0 -716
- package/dist/web/favicon.svg +0 -1
- package/dist/web/index.html +0 -14
package/dist/bin.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ApprovalCorrelator,
|
|
4
4
|
MatrixClient,
|
|
5
|
+
MediaClient,
|
|
5
6
|
SpawnRegistry,
|
|
6
7
|
buildAcpRegistry,
|
|
7
8
|
createMatrixTransport,
|
|
@@ -15,17 +16,17 @@ import {
|
|
|
15
16
|
renderRegistration,
|
|
16
17
|
startDaemonSocketServer,
|
|
17
18
|
startWorkforcePublisher
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-LOILUENL.js";
|
|
19
20
|
|
|
20
21
|
// src/bin.ts
|
|
21
|
-
import { resolve as
|
|
22
|
+
import { resolve as resolve6 } from "path";
|
|
22
23
|
import { cac } from "cac";
|
|
23
24
|
|
|
24
25
|
// src/commands/dev.ts
|
|
25
26
|
import chalk from "chalk";
|
|
26
27
|
import { Listr } from "listr2";
|
|
27
|
-
import { existsSync as
|
|
28
|
-
import { dirname as dirname6, resolve as
|
|
28
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
29
|
+
import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
|
|
29
30
|
import { fileURLToPath } from "url";
|
|
30
31
|
import { serve as serve2 } from "@hono/node-server";
|
|
31
32
|
|
|
@@ -189,7 +190,7 @@ MATRIX_HS_TOKEN=${tokens.hsToken}
|
|
|
189
190
|
import { readFileSync as readFileSync2 } from "fs";
|
|
190
191
|
import { mkdir, unlink } from "fs/promises";
|
|
191
192
|
import { tmpdir } from "os";
|
|
192
|
-
import { dirname as dirname2, join as join3 } from "path";
|
|
193
|
+
import { dirname as dirname2, isAbsolute, join as join3, resolve } from "path";
|
|
193
194
|
import { serve } from "@hono/node-server";
|
|
194
195
|
|
|
195
196
|
// ../transport-http/src/server.ts
|
|
@@ -466,18 +467,18 @@ var defaultExec = async (cmd, args) => {
|
|
|
466
467
|
|
|
467
468
|
// src/daemon/start-daemon.ts
|
|
468
469
|
function listenAsync(server) {
|
|
469
|
-
return new Promise((
|
|
470
|
+
return new Promise((resolve7) => {
|
|
470
471
|
const check = () => {
|
|
471
472
|
const addr = server.address();
|
|
472
|
-
if (addr && typeof addr === "object")
|
|
473
|
+
if (addr && typeof addr === "object") resolve7(addr.port);
|
|
473
474
|
else setImmediate(check);
|
|
474
475
|
};
|
|
475
476
|
check();
|
|
476
477
|
});
|
|
477
478
|
}
|
|
478
479
|
function closeAsync(server) {
|
|
479
|
-
return new Promise((
|
|
480
|
-
server.close(() =>
|
|
480
|
+
return new Promise((resolve7) => {
|
|
481
|
+
server.close(() => resolve7());
|
|
481
482
|
});
|
|
482
483
|
}
|
|
483
484
|
async function startDaemon(opts = {}) {
|
|
@@ -538,6 +539,11 @@ async function startDaemon(opts = {}) {
|
|
|
538
539
|
homeserver: matrix.transport.homeserver,
|
|
539
540
|
asToken: matrix.transport.as_token
|
|
540
541
|
});
|
|
542
|
+
const mediaClient = new MediaClient({
|
|
543
|
+
homeserver: matrix.transport.homeserver,
|
|
544
|
+
asToken: matrix.transport.as_token
|
|
545
|
+
});
|
|
546
|
+
const isContainerRuntime = config.runtime !== "local";
|
|
541
547
|
const bindings = [];
|
|
542
548
|
for (const a of Object.values(config.agents)) {
|
|
543
549
|
if (a.matrix?.transport !== matrix.name) continue;
|
|
@@ -548,21 +554,26 @@ async function startDaemon(opts = {}) {
|
|
|
548
554
|
trigger: a.matrix.trigger
|
|
549
555
|
};
|
|
550
556
|
if (a.matrix.display_name !== void 0) binding.displayName = a.matrix.display_name;
|
|
557
|
+
const workspaceDir = isAbsolute(a.workdir) ? a.workdir : resolve(configDir, a.workdir);
|
|
558
|
+
binding.workspaceDir = workspaceDir;
|
|
559
|
+
binding.agentWorkspacePath = isContainerRuntime ? "/workspace" : workspaceDir;
|
|
551
560
|
bindings.push(binding);
|
|
552
561
|
}
|
|
562
|
+
const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
|
|
563
|
+
const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
|
|
553
564
|
const transport = createMatrixTransport({
|
|
554
565
|
agents: registry,
|
|
555
566
|
approvals,
|
|
556
567
|
client,
|
|
557
568
|
bindings,
|
|
558
569
|
hsToken: matrix.transport.hs_token,
|
|
559
|
-
adminUserId: opts.adminUserId
|
|
570
|
+
adminUserId: opts.adminUserId,
|
|
571
|
+
botUserId: asUserId,
|
|
572
|
+
media: mediaClient
|
|
560
573
|
});
|
|
561
574
|
const requestedPort = matrix.transport.port ?? 9e3;
|
|
562
575
|
server = serve({ fetch: transport.app.fetch, port: requestedPort, hostname: "0.0.0.0" });
|
|
563
576
|
port = await listenAsync(server);
|
|
564
|
-
const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
|
|
565
|
-
const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
|
|
566
577
|
const spaceLocalpart = matrix.transport.space ?? "dev";
|
|
567
578
|
const adminUserIds = opts.adminUserId ? [opts.adminUserId] : [];
|
|
568
579
|
let spaceRoomId;
|
|
@@ -573,7 +584,8 @@ async function startDaemon(opts = {}) {
|
|
|
573
584
|
serverName,
|
|
574
585
|
spaceLocalpart,
|
|
575
586
|
preset: "public_chat",
|
|
576
|
-
admins: adminUserIds
|
|
587
|
+
admins: adminUserIds,
|
|
588
|
+
joinRule: opts.publicWorkforceSpace ? "public" : "invite"
|
|
577
589
|
});
|
|
578
590
|
console.log(`[matrix] ensured workforce space #${spaceLocalpart}:${serverName} \u2192 ${spaceRoomId}`);
|
|
579
591
|
} catch (err) {
|
|
@@ -600,7 +612,7 @@ async function startDaemon(opts = {}) {
|
|
|
600
612
|
asUserId,
|
|
601
613
|
getAgents: () => bindings
|
|
602
614
|
});
|
|
603
|
-
console.log(`[matrix] published
|
|
615
|
+
console.log(`[matrix] published dev.zooid.workforce (${bindings.length} agents)`);
|
|
604
616
|
void publisher;
|
|
605
617
|
} catch (err) {
|
|
606
618
|
console.warn("[matrix] workforce roster publication failed:", err);
|
|
@@ -688,8 +700,8 @@ var TuwunelService = class {
|
|
|
688
700
|
async stop() {
|
|
689
701
|
if (this.child && this.child.exitCode === null) {
|
|
690
702
|
this.child.kill("SIGTERM");
|
|
691
|
-
await new Promise((
|
|
692
|
-
this.child.on("exit", () =>
|
|
703
|
+
await new Promise((resolve7) => {
|
|
704
|
+
this.child.on("exit", () => resolve7());
|
|
693
705
|
});
|
|
694
706
|
}
|
|
695
707
|
this.child = null;
|
|
@@ -706,7 +718,7 @@ var TuwunelService = class {
|
|
|
706
718
|
`Tuwunel container exited before serving HTTP (exit=${state.exitCode}${state.error ? `, error=${state.error}` : ""}). Check the engine logs (\`${this.opts.engine} logs ${this.opts.name}\`).`
|
|
707
719
|
);
|
|
708
720
|
}
|
|
709
|
-
await new Promise((
|
|
721
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
710
722
|
}
|
|
711
723
|
while (Date.now() < deadline) {
|
|
712
724
|
try {
|
|
@@ -714,7 +726,7 @@ var TuwunelService = class {
|
|
|
714
726
|
if (r.ok) return;
|
|
715
727
|
} catch {
|
|
716
728
|
}
|
|
717
|
-
await new Promise((
|
|
729
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
718
730
|
}
|
|
719
731
|
const finalState = await this.inspectState().catch(() => null);
|
|
720
732
|
const detail = finalState ? ` (container status=${finalState.status}${finalState.exitCode !== void 0 ? `, exit=${finalState.exitCode}` : ""})` : "";
|
|
@@ -747,12 +759,12 @@ var TuwunelService = class {
|
|
|
747
759
|
}
|
|
748
760
|
};
|
|
749
761
|
function execEngine(engine, args) {
|
|
750
|
-
return new Promise((
|
|
762
|
+
return new Promise((resolve7, reject) => {
|
|
751
763
|
const child = spawn(engine, args, { stdio: "pipe" });
|
|
752
764
|
let stderr = "";
|
|
753
765
|
child.stderr.on("data", (b) => stderr += String(b));
|
|
754
766
|
child.on("exit", (code) => {
|
|
755
|
-
if (code === 0)
|
|
767
|
+
if (code === 0) resolve7();
|
|
756
768
|
else reject(new Error(`${engine} ${args.join(" ")} failed: ${stderr.trim()}`));
|
|
757
769
|
});
|
|
758
770
|
child.on("error", reject);
|
|
@@ -760,34 +772,115 @@ function execEngine(engine, args) {
|
|
|
760
772
|
}
|
|
761
773
|
|
|
762
774
|
// src/web/resolve.ts
|
|
763
|
-
import { existsSync as
|
|
764
|
-
import { dirname as dirname3, join as
|
|
775
|
+
import { existsSync as existsSync3 } from "fs";
|
|
776
|
+
import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
777
|
+
|
|
778
|
+
// src/web/fetch.ts
|
|
779
|
+
import { mkdirSync as mkdirSync3, renameSync, rmSync, existsSync as existsSync2, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
780
|
+
import { join as join4 } from "path";
|
|
781
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
782
|
+
import * as tar from "tar";
|
|
783
|
+
var PKG = "@zooid/web";
|
|
784
|
+
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
785
|
+
async function fetchWebBundle(opts) {
|
|
786
|
+
const target = join4(opts.cacheDir, opts.version);
|
|
787
|
+
if (existsSync2(join4(target, "index.html"))) return target;
|
|
788
|
+
if (existsSync2(target)) rmSync(target, { recursive: true, force: true });
|
|
789
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
790
|
+
const registry = (opts.registryUrl ?? DEFAULT_REGISTRY).replace(/\/$/, "");
|
|
791
|
+
let tgz;
|
|
792
|
+
try {
|
|
793
|
+
const metaRes = await f(`${registry}/${PKG}`);
|
|
794
|
+
if (!metaRes.ok) throw new Error(`registry metadata: HTTP ${metaRes.status}`);
|
|
795
|
+
const meta = await metaRes.json();
|
|
796
|
+
const dist = meta.versions?.[opts.version]?.dist;
|
|
797
|
+
if (!dist?.tarball || !dist.integrity) {
|
|
798
|
+
throw new Error(`${PKG}@${opts.version} not found on the registry`);
|
|
799
|
+
}
|
|
800
|
+
const tgzRes = await f(dist.tarball);
|
|
801
|
+
if (!tgzRes.ok) throw new Error(`tarball: HTTP ${tgzRes.status}`);
|
|
802
|
+
tgz = Buffer.from(await tgzRes.arrayBuffer());
|
|
803
|
+
const sha = "sha512-" + createHash("sha512").update(tgz).digest("base64");
|
|
804
|
+
if (sha !== dist.integrity) {
|
|
805
|
+
throw new Error(`integrity mismatch for ${PKG}@${opts.version}: got ${sha}`);
|
|
806
|
+
}
|
|
807
|
+
} catch (err) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`Failed to fetch ${PKG}@${opts.version} from ${registry}.
|
|
810
|
+
Cache: ${opts.cacheDir}
|
|
811
|
+
Offline? Point ZOOID_DEV_WEB_ROOT_OVERRIDE at a built dist as a manual escape hatch.
|
|
812
|
+
Cause: ${err instanceof Error ? err.message : String(err)}`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
const tmp = join4(opts.cacheDir, `.tmp-${randomUUID2().slice(0, 8)}`);
|
|
816
|
+
mkdirSync3(tmp, { recursive: true });
|
|
817
|
+
const tgzPath = join4(tmp, ".bundle.tgz");
|
|
818
|
+
try {
|
|
819
|
+
writeFileSync3(tgzPath, tgz);
|
|
820
|
+
await tar.extract({
|
|
821
|
+
file: tgzPath,
|
|
822
|
+
cwd: tmp,
|
|
823
|
+
strip: 2,
|
|
824
|
+
// package/dist/<file> → <file>
|
|
825
|
+
filter: (p) => p === "package/dist" || p.startsWith("package/dist/")
|
|
826
|
+
});
|
|
827
|
+
rmSync(tgzPath);
|
|
828
|
+
if (!existsSync2(join4(tmp, "index.html"))) {
|
|
829
|
+
throw new Error(`${PKG}@${opts.version} tarball has no dist/index.html`);
|
|
830
|
+
}
|
|
831
|
+
renameSync(tmp, target);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
834
|
+
throw err;
|
|
835
|
+
}
|
|
836
|
+
for (const entry of readdirSync(opts.cacheDir)) {
|
|
837
|
+
if (entry !== opts.version) {
|
|
838
|
+
rmSync(join4(opts.cacheDir, entry), { recursive: true, force: true });
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return target;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/web/resolve.ts
|
|
765
845
|
var ENV_OVERRIDE = "ZOOID_DEV_WEB_ROOT_OVERRIDE";
|
|
766
|
-
function
|
|
846
|
+
async function ensureWebRoot(opts) {
|
|
767
847
|
const override = process.env[ENV_OVERRIDE];
|
|
768
|
-
if (override &&
|
|
769
|
-
const
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
if (fromSource && existsSync2(join4(fromSource, "dist", "index.html"))) {
|
|
773
|
-
return join4(fromSource, "dist");
|
|
848
|
+
if (override && existsSync3(join5(override, "index.html"))) return resolve2(override);
|
|
849
|
+
const fromSource = webSourcePackage(opts.cliRoot);
|
|
850
|
+
if (fromSource && existsSync3(join5(fromSource, "dist", "index.html"))) {
|
|
851
|
+
return join5(fromSource, "dist");
|
|
774
852
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
853
|
+
if (!opts.version) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
`No @zooid/web version pin (zooid.webVersion) in the cli package.json and no monorepo sibling build found.
|
|
856
|
+
Set ${ENV_OVERRIDE} to a built dist, or run from the monorepo.`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
opts.onProgress?.(`Fetching @zooid/web ${opts.version}\u2026`);
|
|
860
|
+
const fetchBundleFn = opts.fetchBundle ?? fetchWebBundle;
|
|
861
|
+
return fetchBundleFn({ version: opts.version, cacheDir: opts.cacheDir });
|
|
781
862
|
}
|
|
782
863
|
function webSourcePackage(cliRoot) {
|
|
783
864
|
const workspaceRoot = dirname3(dirname3(dirname3(cliRoot)));
|
|
784
|
-
const candidate =
|
|
785
|
-
return
|
|
865
|
+
const candidate = join5(workspaceRoot, "zoon", "packages", "web");
|
|
866
|
+
return existsSync3(join5(candidate, "package.json")) ? candidate : null;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/web/pin.ts
|
|
870
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
871
|
+
import { join as join6 } from "path";
|
|
872
|
+
function readZoonWebPin(cliRoot) {
|
|
873
|
+
try {
|
|
874
|
+
const pkg = JSON.parse(readFileSync3(join6(cliRoot, "package.json"), "utf8"));
|
|
875
|
+
return pkg.zooid?.webVersion;
|
|
876
|
+
} catch {
|
|
877
|
+
return void 0;
|
|
878
|
+
}
|
|
786
879
|
}
|
|
787
880
|
|
|
788
881
|
// src/web/static.ts
|
|
789
|
-
import { readFileSync as
|
|
790
|
-
import { extname, join as
|
|
882
|
+
import { readFileSync as readFileSync4, statSync } from "fs";
|
|
883
|
+
import { extname, join as join7, normalize } from "path";
|
|
791
884
|
import { Hono as Hono2 } from "hono";
|
|
792
885
|
var MIME = {
|
|
793
886
|
".html": "text/html; charset=utf-8",
|
|
@@ -813,19 +906,19 @@ function webStatic(opts) {
|
|
|
813
906
|
const requested = decodeURIComponent(url.pathname);
|
|
814
907
|
const wantFile = requested === "/" ? "/index.html" : requested;
|
|
815
908
|
const safe = normalize(wantFile).replace(/^(\.\.[/\\])+/g, "");
|
|
816
|
-
const filePath =
|
|
909
|
+
const filePath = join7(opts.webRoot, safe);
|
|
817
910
|
if (!filePath.startsWith(opts.webRoot)) return c.notFound();
|
|
818
911
|
try {
|
|
819
912
|
const stat = statSync(filePath);
|
|
820
913
|
if (stat.isFile()) {
|
|
821
|
-
const body =
|
|
914
|
+
const body = readFileSync4(filePath);
|
|
822
915
|
const ct = MIME[extname(filePath)] ?? "application/octet-stream";
|
|
823
916
|
return c.body(body, 200, { "content-type": ct });
|
|
824
917
|
}
|
|
825
918
|
} catch {
|
|
826
919
|
}
|
|
827
920
|
if (isAssetPath(requested)) return c.notFound();
|
|
828
|
-
const indexBytes =
|
|
921
|
+
const indexBytes = readFileSync4(join7(opts.webRoot, "index.html"));
|
|
829
922
|
return c.body(indexBytes, 200, {
|
|
830
923
|
"content-type": MIME[".html"]
|
|
831
924
|
});
|
|
@@ -835,11 +928,11 @@ function webStatic(opts) {
|
|
|
835
928
|
|
|
836
929
|
// src/web/watch.ts
|
|
837
930
|
import { spawn as spawn2 } from "child_process";
|
|
838
|
-
import { existsSync as
|
|
839
|
-
import { join as
|
|
931
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
932
|
+
import { join as join8 } from "path";
|
|
840
933
|
async function startWebWatch(opts) {
|
|
841
|
-
const distPath =
|
|
842
|
-
const indexPath =
|
|
934
|
+
const distPath = join8(opts.webPackageDir, "dist");
|
|
935
|
+
const indexPath = join8(distPath, "index.html");
|
|
843
936
|
const timeoutMs = opts.firstBuildTimeoutMs ?? 6e4;
|
|
844
937
|
const spawnTime = Date.now();
|
|
845
938
|
const child = spawn2(
|
|
@@ -873,7 +966,7 @@ async function startWebWatch(opts) {
|
|
|
873
966
|
attachStdio();
|
|
874
967
|
throw new Error(`vite build --watch exited before first build (code ${child.exitCode})`);
|
|
875
968
|
}
|
|
876
|
-
if (
|
|
969
|
+
if (existsSync4(indexPath)) {
|
|
877
970
|
const m = statSync2(indexPath).mtimeMs;
|
|
878
971
|
if (m >= spawnTime - 500) return makeHandle();
|
|
879
972
|
}
|
|
@@ -893,12 +986,12 @@ async function startWebWatch(opts) {
|
|
|
893
986
|
async function stopChild(child) {
|
|
894
987
|
if (child.exitCode !== null) return;
|
|
895
988
|
child.kill("SIGTERM");
|
|
896
|
-
await new Promise((
|
|
989
|
+
await new Promise((resolve7) => {
|
|
897
990
|
let done = false;
|
|
898
991
|
const finish = () => {
|
|
899
992
|
if (done) return;
|
|
900
993
|
done = true;
|
|
901
|
-
|
|
994
|
+
resolve7();
|
|
902
995
|
};
|
|
903
996
|
child.once("exit", finish);
|
|
904
997
|
setTimeout(() => {
|
|
@@ -913,7 +1006,7 @@ async function stopChild(child) {
|
|
|
913
1006
|
|
|
914
1007
|
// src/observability/paths.ts
|
|
915
1008
|
import { mkdir as mkdir2, readdir, rm, symlink, unlink as unlink2 } from "fs/promises";
|
|
916
|
-
import { join as
|
|
1009
|
+
import { join as join9 } from "path";
|
|
917
1010
|
function localDateSlug(d) {
|
|
918
1011
|
const y = d.getFullYear();
|
|
919
1012
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -923,19 +1016,19 @@ function localDateSlug(d) {
|
|
|
923
1016
|
var DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
924
1017
|
function resolveLogPaths({ dataDir, now }) {
|
|
925
1018
|
const slug = localDateSlug(now ?? /* @__PURE__ */ new Date());
|
|
926
|
-
const logsDir =
|
|
927
|
-
const dayDir =
|
|
1019
|
+
const logsDir = join9(dataDir, "logs");
|
|
1020
|
+
const dayDir = join9(logsDir, slug);
|
|
928
1021
|
return {
|
|
929
1022
|
dataDir,
|
|
930
1023
|
logsDir,
|
|
931
1024
|
dayDir,
|
|
932
1025
|
daySlug: slug,
|
|
933
|
-
todayLink:
|
|
934
|
-
tuwunelLog:
|
|
935
|
-
daemonLog:
|
|
936
|
-
devLog:
|
|
937
|
-
agentLog: (n) =>
|
|
938
|
-
agentTap: (n) =>
|
|
1026
|
+
todayLink: join9(logsDir, "today"),
|
|
1027
|
+
tuwunelLog: join9(dayDir, "tuwunel.log"),
|
|
1028
|
+
daemonLog: join9(dayDir, "daemon.log"),
|
|
1029
|
+
devLog: join9(dayDir, "dev.log"),
|
|
1030
|
+
agentLog: (n) => join9(dayDir, `agent-${n}.log`),
|
|
1031
|
+
agentTap: (n) => join9(dayDir, `agent-${n}.acp.jsonl`)
|
|
939
1032
|
};
|
|
940
1033
|
}
|
|
941
1034
|
async function ensureDayFolder(p) {
|
|
@@ -948,7 +1041,7 @@ async function ensureDayFolder(p) {
|
|
|
948
1041
|
}
|
|
949
1042
|
async function pruneOldDays(opts) {
|
|
950
1043
|
if (opts.retainDays <= 0) return [];
|
|
951
|
-
const logsDir =
|
|
1044
|
+
const logsDir = join9(opts.dataDir, "logs");
|
|
952
1045
|
let entries;
|
|
953
1046
|
try {
|
|
954
1047
|
entries = await readdir(logsDir);
|
|
@@ -967,7 +1060,7 @@ async function pruneOldDays(opts) {
|
|
|
967
1060
|
const [y, m, d] = name.split("-").map(Number);
|
|
968
1061
|
const t = new Date(y, m - 1, d).getTime();
|
|
969
1062
|
if (t < cutoff) {
|
|
970
|
-
await rm(
|
|
1063
|
+
await rm(join9(logsDir, name), { recursive: true, force: true });
|
|
971
1064
|
removed.push(name);
|
|
972
1065
|
}
|
|
973
1066
|
}
|
|
@@ -995,14 +1088,14 @@ var JsonlSink = class {
|
|
|
995
1088
|
await this.readyPromise;
|
|
996
1089
|
const capped = capStrings(obj, this.maxStringLen);
|
|
997
1090
|
const line = JSON.stringify(capped) + "\n";
|
|
998
|
-
return new Promise((
|
|
999
|
-
this.stream.write(line, (err) => err ? reject(err) :
|
|
1091
|
+
return new Promise((resolve7, reject) => {
|
|
1092
|
+
this.stream.write(line, (err) => err ? reject(err) : resolve7());
|
|
1000
1093
|
});
|
|
1001
1094
|
}
|
|
1002
1095
|
async close() {
|
|
1003
1096
|
await this.readyPromise;
|
|
1004
|
-
return new Promise((
|
|
1005
|
-
this.stream.end(() =>
|
|
1097
|
+
return new Promise((resolve7) => {
|
|
1098
|
+
this.stream.end(() => resolve7());
|
|
1006
1099
|
});
|
|
1007
1100
|
}
|
|
1008
1101
|
};
|
|
@@ -1077,8 +1170,8 @@ function captureChildToFile(child, path) {
|
|
|
1077
1170
|
const stream = createWriteStream2(path, { flags: "a" });
|
|
1078
1171
|
if (child.stdout) child.stdout.on("data", (b) => stream.write(b));
|
|
1079
1172
|
if (child.stderr) child.stderr.on("data", (b) => stream.write(b));
|
|
1080
|
-
await new Promise((
|
|
1081
|
-
child.on("exit", () => stream.end(() =>
|
|
1173
|
+
await new Promise((resolve7) => {
|
|
1174
|
+
child.on("exit", () => stream.end(() => resolve7()));
|
|
1082
1175
|
});
|
|
1083
1176
|
})();
|
|
1084
1177
|
}
|
|
@@ -1113,10 +1206,10 @@ function buildShutdown(layers) {
|
|
|
1113
1206
|
var CLI_ROOT = (() => {
|
|
1114
1207
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1115
1208
|
for (let i = 0; i < 8; i++) {
|
|
1116
|
-
const pkgPath =
|
|
1117
|
-
if (
|
|
1209
|
+
const pkgPath = resolve3(dir, "package.json");
|
|
1210
|
+
if (existsSync5(pkgPath)) {
|
|
1118
1211
|
try {
|
|
1119
|
-
const pkg = JSON.parse(
|
|
1212
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
1120
1213
|
if (pkg.name === "zooid" || pkg.name === "@zooid/cli") return dir;
|
|
1121
1214
|
} catch {
|
|
1122
1215
|
}
|
|
@@ -1132,14 +1225,14 @@ async function runDev(flags) {
|
|
|
1132
1225
|
const found = findConfigFile(cwd);
|
|
1133
1226
|
if (!found) throw new Error(`zooid.yaml not found in ${cwd}`);
|
|
1134
1227
|
loadEnvFiles(cwd);
|
|
1135
|
-
const dataRoot =
|
|
1228
|
+
const dataRoot = resolve3(cwd, flags.dataDir);
|
|
1136
1229
|
const layout = resolveDataLayout(dataRoot);
|
|
1137
1230
|
const paths = resolvePaths(layout.matrixDir);
|
|
1138
1231
|
const logPaths = resolveLogPaths({ dataDir: layout.dataRoot });
|
|
1139
1232
|
const tokens = ensureTokens(paths.envPath);
|
|
1140
1233
|
process.env.MATRIX_AS_TOKEN = tokens.asToken;
|
|
1141
1234
|
process.env.MATRIX_HS_TOKEN = tokens.hsToken;
|
|
1142
|
-
const rawYaml =
|
|
1235
|
+
const rawYaml = readFileSync5(found.path, "utf8");
|
|
1143
1236
|
const preview = loadZooidConfig(rawYaml, { configDir: dirname6(found.path) });
|
|
1144
1237
|
const matrix = findMatrixTransport(preview);
|
|
1145
1238
|
if (!matrix) {
|
|
@@ -1220,6 +1313,10 @@ async function runDev(flags) {
|
|
|
1220
1313
|
installSignalHandlers: false,
|
|
1221
1314
|
adminUserId: `@${flags.adminUser}:${shape.serverName}`,
|
|
1222
1315
|
agentsDir: layout.agentsDir,
|
|
1316
|
+
// Local-only homeserver: make the workforce space publicly joinable
|
|
1317
|
+
// so a self-service-registered dev account lands in #<space> from
|
|
1318
|
+
// the web client without needing an invite.
|
|
1319
|
+
publicWorkforceSpace: true,
|
|
1223
1320
|
onTap: (agentName, event) => captures[agentName]?.onTap(event),
|
|
1224
1321
|
prepullLog: (line) => {
|
|
1225
1322
|
t.output = line.replace(/^\[zooid\]\s+/, "").trim();
|
|
@@ -1229,14 +1326,14 @@ async function runDev(flags) {
|
|
|
1229
1326
|
},
|
|
1230
1327
|
...flags.watchWeb ? [
|
|
1231
1328
|
{
|
|
1232
|
-
title: "Start @
|
|
1329
|
+
title: "Start @zooid/web watcher (vite build --watch)",
|
|
1233
1330
|
task: async () => {
|
|
1234
|
-
const pkgDir = typeof flags.watchWeb === "string" ?
|
|
1331
|
+
const pkgDir = typeof flags.watchWeb === "string" ? resolve3(flags.watchWeb) : webSourcePackage(CLI_ROOT);
|
|
1235
1332
|
if (!pkgDir) {
|
|
1236
|
-
const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/
|
|
1333
|
+
const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/zooid-clients/packages/web";
|
|
1237
1334
|
throw new Error(
|
|
1238
|
-
`--watch-web: @
|
|
1239
|
-
Pass an explicit path: --watch-web=/path/to/
|
|
1335
|
+
`--watch-web: @zooid/web not found at ${defaultPath}.
|
|
1336
|
+
Pass an explicit path: --watch-web=/path/to/zooid-clients/packages/web`
|
|
1240
1337
|
);
|
|
1241
1338
|
}
|
|
1242
1339
|
ctx.webWatch = await startWebWatch({ webPackageDir: pkgDir });
|
|
@@ -1244,9 +1341,16 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
|
|
|
1244
1341
|
}
|
|
1245
1342
|
] : [],
|
|
1246
1343
|
{
|
|
1247
|
-
title: `Serve @
|
|
1248
|
-
task: () => {
|
|
1249
|
-
const webRoot = ctx.webWatch?.distPath ??
|
|
1344
|
+
title: `Serve @zooid/web on http://localhost:${flags.uiPort}`,
|
|
1345
|
+
task: async (_, t) => {
|
|
1346
|
+
const webRoot = ctx.webWatch?.distPath ?? await ensureWebRoot({
|
|
1347
|
+
cliRoot: CLI_ROOT,
|
|
1348
|
+
cacheDir: join10(layout.dataRoot, "web"),
|
|
1349
|
+
version: readZoonWebPin(CLI_ROOT),
|
|
1350
|
+
onProgress: (msg) => {
|
|
1351
|
+
t.output = msg;
|
|
1352
|
+
}
|
|
1353
|
+
});
|
|
1250
1354
|
const app = webStatic({ webRoot, homeserverUrl: homeserver });
|
|
1251
1355
|
ctx.uiServer = serve2({ fetch: app.fetch, port: flags.uiPort });
|
|
1252
1356
|
}
|
|
@@ -1295,7 +1399,7 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
|
|
|
1295
1399
|
chalk.bold("UI: ") + ` http://localhost:${flags.uiPort}`,
|
|
1296
1400
|
` ${chalk.cyan("admin user:")} ${flags.adminUser} / ${flags.adminPassword}`,
|
|
1297
1401
|
` ${chalk.cyan("data dir:")} ${layout.dataRoot}`,
|
|
1298
|
-
...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @
|
|
1402
|
+
...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/web)`] : [],
|
|
1299
1403
|
"",
|
|
1300
1404
|
chalk.dim("Press Ctrl-C to stop."),
|
|
1301
1405
|
""
|
|
@@ -1308,8 +1412,8 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
|
|
|
1308
1412
|
}
|
|
1309
1413
|
function loadEnvFiles(cwd) {
|
|
1310
1414
|
for (const name of [".env.local", ".env"]) {
|
|
1311
|
-
const path =
|
|
1312
|
-
if (!
|
|
1415
|
+
const path = resolve3(cwd, name);
|
|
1416
|
+
if (!existsSync5(path)) continue;
|
|
1313
1417
|
try {
|
|
1314
1418
|
process.loadEnvFile(path);
|
|
1315
1419
|
} catch (err) {
|
|
@@ -1319,8 +1423,8 @@ function loadEnvFiles(cwd) {
|
|
|
1319
1423
|
}
|
|
1320
1424
|
|
|
1321
1425
|
// src/commands/init.ts
|
|
1322
|
-
import { existsSync as
|
|
1323
|
-
import { dirname as dirname7, join as
|
|
1426
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1427
|
+
import { dirname as dirname7, join as join12, resolve as resolve4 } from "path";
|
|
1324
1428
|
|
|
1325
1429
|
// src/commands/init/generators.ts
|
|
1326
1430
|
function generateZooidYaml(opts) {
|
|
@@ -1473,14 +1577,14 @@ function findOpencodeProvider(id) {
|
|
|
1473
1577
|
}
|
|
1474
1578
|
|
|
1475
1579
|
// src/commands/init/sniff.ts
|
|
1476
|
-
import { existsSync as
|
|
1580
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1477
1581
|
import { homedir } from "os";
|
|
1478
|
-
import { join as
|
|
1582
|
+
import { join as join11 } from "path";
|
|
1479
1583
|
function sniffCredentials(preset, home = homedir()) {
|
|
1480
1584
|
const meta = findSimplePreset(preset);
|
|
1481
1585
|
if (!meta) return { found: false };
|
|
1482
|
-
const full =
|
|
1483
|
-
if (
|
|
1586
|
+
const full = join11(home, meta.credentialDir);
|
|
1587
|
+
if (existsSync6(full)) return { found: true, path: full };
|
|
1484
1588
|
return { found: false };
|
|
1485
1589
|
}
|
|
1486
1590
|
|
|
@@ -1497,10 +1601,10 @@ var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
|
|
|
1497
1601
|
"yarn.lock"
|
|
1498
1602
|
]);
|
|
1499
1603
|
async function runInit(opts) {
|
|
1500
|
-
const dir =
|
|
1501
|
-
|
|
1604
|
+
const dir = resolve4(opts.dir);
|
|
1605
|
+
mkdirSync4(dir, { recursive: true });
|
|
1502
1606
|
if (!opts.force) {
|
|
1503
|
-
const blocking =
|
|
1607
|
+
const blocking = readdirSync2(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
|
|
1504
1608
|
if (blocking.length > 0) {
|
|
1505
1609
|
throw new Error(
|
|
1506
1610
|
`${dir} is non-empty (use --force to allow scaffolding into it). Conflicting entries: ${blocking.join(", ")}`
|
|
@@ -1567,14 +1671,14 @@ async function runInit(opts) {
|
|
|
1567
1671
|
}
|
|
1568
1672
|
writes.push({ path: ".gitignore", content: generateGitignore() });
|
|
1569
1673
|
for (const w of writes) {
|
|
1570
|
-
const full =
|
|
1571
|
-
const exists =
|
|
1674
|
+
const full = join12(dir, w.path);
|
|
1675
|
+
const exists = existsSync7(full);
|
|
1572
1676
|
if (exists && !opts.overwrite) {
|
|
1573
1677
|
console.warn(`\u26A0 ${w.path} exists; left as-is (use --force --overwrite to replace)`);
|
|
1574
1678
|
continue;
|
|
1575
1679
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1680
|
+
mkdirSync4(dirname7(full), { recursive: true });
|
|
1681
|
+
writeFileSync4(full, w.content);
|
|
1578
1682
|
console.log(`\u2713 Created ${w.path}`);
|
|
1579
1683
|
}
|
|
1580
1684
|
if ((opts.preset === "claude" || opts.preset === "codex") && opts.auth === "subscription") {
|
|
@@ -1691,8 +1795,8 @@ async function resolveOptions(flags) {
|
|
|
1691
1795
|
|
|
1692
1796
|
// src/commands/logs.ts
|
|
1693
1797
|
import { readFile, readdir as readdir2, readlink } from "fs/promises";
|
|
1694
|
-
import { existsSync as
|
|
1695
|
-
import { join as
|
|
1798
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1799
|
+
import { join as join13 } from "path";
|
|
1696
1800
|
var KNOWN_SOURCES = ["tuwunel", "daemon", "dev"];
|
|
1697
1801
|
async function runLogs(flags) {
|
|
1698
1802
|
const writer = flags.writer ?? ((s) => process.stdout.write(s));
|
|
@@ -1711,8 +1815,8 @@ async function runLogs(flags) {
|
|
|
1711
1815
|
writer("no logs yet\n");
|
|
1712
1816
|
return;
|
|
1713
1817
|
}
|
|
1714
|
-
const dayDir =
|
|
1715
|
-
if (!
|
|
1818
|
+
const dayDir = join13(flags.dataDir, "logs", day);
|
|
1819
|
+
if (!existsSync8(dayDir)) {
|
|
1716
1820
|
writer(`no logs for ${day}
|
|
1717
1821
|
`);
|
|
1718
1822
|
return;
|
|
@@ -1732,7 +1836,7 @@ async function runLogs(flags) {
|
|
|
1732
1836
|
return;
|
|
1733
1837
|
}
|
|
1734
1838
|
const path = resolveSourcePath(dayDir, flags.source);
|
|
1735
|
-
if (!
|
|
1839
|
+
if (!existsSync8(path)) {
|
|
1736
1840
|
writer(`no such source: ${flags.source}
|
|
1737
1841
|
`);
|
|
1738
1842
|
return;
|
|
@@ -1740,7 +1844,7 @@ async function runLogs(flags) {
|
|
|
1740
1844
|
writer(await readFile(path, "utf8"));
|
|
1741
1845
|
}
|
|
1742
1846
|
async function resolveTodaySlug(dataDir) {
|
|
1743
|
-
const link =
|
|
1847
|
+
const link = join13(dataDir, "logs", "today");
|
|
1744
1848
|
try {
|
|
1745
1849
|
return await readlink(link);
|
|
1746
1850
|
} catch {
|
|
@@ -1749,18 +1853,18 @@ async function resolveTodaySlug(dataDir) {
|
|
|
1749
1853
|
}
|
|
1750
1854
|
function resolveSourcePath(dayDir, source) {
|
|
1751
1855
|
if (source.startsWith("agent-")) {
|
|
1752
|
-
if (source.endsWith(".acp")) return
|
|
1753
|
-
return
|
|
1856
|
+
if (source.endsWith(".acp")) return join13(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
|
|
1857
|
+
return join13(dayDir, `${source}.log`);
|
|
1754
1858
|
}
|
|
1755
1859
|
if (KNOWN_SOURCES.includes(source))
|
|
1756
|
-
return
|
|
1757
|
-
return
|
|
1860
|
+
return join13(dayDir, `${source}.log`);
|
|
1861
|
+
return join13(dayDir, source);
|
|
1758
1862
|
}
|
|
1759
1863
|
async function dumpByTurn(dayDir, turnId, writer) {
|
|
1760
1864
|
const entries = await readdir2(dayDir);
|
|
1761
1865
|
const taps = entries.filter((e) => e.endsWith(".acp.jsonl")).sort();
|
|
1762
1866
|
for (const f of taps) {
|
|
1763
|
-
const text = await readFile(
|
|
1867
|
+
const text = await readFile(join13(dayDir, f), "utf8");
|
|
1764
1868
|
for (const line of text.split("\n")) {
|
|
1765
1869
|
if (!line) continue;
|
|
1766
1870
|
try {
|
|
@@ -1774,14 +1878,14 @@ async function dumpByTurn(dayDir, turnId, writer) {
|
|
|
1774
1878
|
|
|
1775
1879
|
// src/commands/start.ts
|
|
1776
1880
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1777
|
-
import { resolve as
|
|
1881
|
+
import { resolve as resolve5 } from "path";
|
|
1778
1882
|
async function runStart(flags) {
|
|
1779
1883
|
if (flags.printToken) {
|
|
1780
1884
|
process.stdout.write(`${randomBytes2(32).toString("hex")}
|
|
1781
1885
|
`);
|
|
1782
1886
|
return;
|
|
1783
1887
|
}
|
|
1784
|
-
const dataRoot =
|
|
1888
|
+
const dataRoot = resolve5(process.cwd(), flags.dataDir ?? "./data");
|
|
1785
1889
|
const layout = resolveDataLayout(dataRoot);
|
|
1786
1890
|
const handle = await startDaemon({
|
|
1787
1891
|
cliFlags: flags,
|
|
@@ -1794,7 +1898,7 @@ async function runStart(flags) {
|
|
|
1794
1898
|
}
|
|
1795
1899
|
|
|
1796
1900
|
// src/commands/status.ts
|
|
1797
|
-
import { readFileSync as
|
|
1901
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1798
1902
|
import { dirname as dirname8 } from "path";
|
|
1799
1903
|
import chalk2 from "chalk";
|
|
1800
1904
|
async function probe(url, timeoutMs = 2e3) {
|
|
@@ -1819,7 +1923,7 @@ async function collectStatus(opts) {
|
|
|
1819
1923
|
agents: []
|
|
1820
1924
|
};
|
|
1821
1925
|
}
|
|
1822
|
-
const cfg = loadZooidConfig(
|
|
1926
|
+
const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
|
|
1823
1927
|
configDir: dirname8(found.path)
|
|
1824
1928
|
});
|
|
1825
1929
|
const matrixEntry = Object.entries(cfg.transports).find(
|
|
@@ -1850,7 +1954,7 @@ async function runStatus(flags) {
|
|
|
1850
1954
|
if (port === void 0) {
|
|
1851
1955
|
const found = findConfigFile(cwd);
|
|
1852
1956
|
if (found) {
|
|
1853
|
-
const cfg = loadZooidConfig(
|
|
1957
|
+
const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
|
|
1854
1958
|
configDir: dirname8(found.path)
|
|
1855
1959
|
});
|
|
1856
1960
|
const matrix = findMatrixTransport(cfg);
|
|
@@ -1887,7 +1991,7 @@ cli.command("start", "Run the daemon (production entry-point)").option("--data <
|
|
|
1887
1991
|
});
|
|
1888
1992
|
cli.command("dev", "Tuwunel + daemon + UI for local development").option("--data <dir>", "Persistent data root dir", { default: "./data" }).option("--engine <docker|podman>", "Container engine", { default: "docker" }).option("--ui-port <n>", "UI HTTP port", { default: 5173 }).option("--admin-user <name>", "Admin username", { default: "admin" }).option("--admin-password <pw>", "Admin password", { default: "admin" }).option(
|
|
1889
1993
|
"--watch-web [path]",
|
|
1890
|
-
"Run vite build --watch on @
|
|
1994
|
+
"Run vite build --watch on @zooid/web. Path defaults to sibling ../zooid-clients/packages/web."
|
|
1891
1995
|
).action(async (flags) => {
|
|
1892
1996
|
await runDev({
|
|
1893
1997
|
dataDir: flags.data,
|
|
@@ -1904,14 +2008,14 @@ cli.command(
|
|
|
1904
2008
|
).option("--data <dir>", "Persistent data root dir", { default: "./data" }).option("--day <YYYY-MM-DD>", "Day partition (defaults to today)").option("--turn <id>", "Filter ACP taps to a single turn id").option("-f, --follow", "Tail the file (not yet implemented)").option("--keep <n>", "For `logs prune`: days to retain", { default: 14 }).action(async (source, flags) => {
|
|
1905
2009
|
if (source === "prune") {
|
|
1906
2010
|
await runLogs({
|
|
1907
|
-
dataDir:
|
|
2011
|
+
dataDir: resolve6(process.cwd(), flags.data),
|
|
1908
2012
|
subcommand: "prune",
|
|
1909
2013
|
keep: Number(flags.keep)
|
|
1910
2014
|
});
|
|
1911
2015
|
return;
|
|
1912
2016
|
}
|
|
1913
2017
|
await runLogs({
|
|
1914
|
-
dataDir:
|
|
2018
|
+
dataDir: resolve6(process.cwd(), flags.data),
|
|
1915
2019
|
source,
|
|
1916
2020
|
day: flags.day,
|
|
1917
2021
|
turn: flags.turn,
|