@saadnvd1/agent-os 0.3.539 → 0.3.541

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.
@@ -148,6 +148,7 @@ export function recordConditions(
148
148
  db.prepare(
149
149
  `DELETE FROM orchestrator_events WHERE workspace_id = ? AND (
150
150
  (subject IS NOT NULL
151
+ AND NOT (key LIKE '${ONCE}%' AND delivered_at IS NULL)
151
152
  AND subject NOT IN (SELECT id FROM sessions)
152
153
  AND subject NOT IN (SELECT id FROM stack_items)
153
154
  AND subject NOT IN (SELECT id FROM stacks))
@@ -194,7 +194,7 @@ function tell(
194
194
  queueEvent(
195
195
  run.recipe.workspace_id,
196
196
  `recipe:${run.id}:${status}`,
197
- run.recipe.name,
197
+ run.session_id,
198
198
  `Recipe "${run.recipe.name}" run #${run.seq} ${bad ? status.replace("_", " ") : "finished ok"}${reported ? `: ${untrusted(run.recipe.name, reported)}` : "."}${pr}`
199
199
  );
200
200
  }
@@ -54,7 +54,7 @@ const TAILSCALE_V4 = /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./;
54
54
  const LOOPBACK = new Set(["127.0.0.1", "::1"]);
55
55
  const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
56
56
  // A proxy must send one of these, or it looks like this machine itself.
57
- // Proxies that send none need AGENTOS_AUTH, see README "Security".
57
+ // Proxies that send none need AGENTOS_AUTH, see docs/reference/security.md.
58
58
  const FORWARDING = new Set([
59
59
  "forwarded",
60
60
  "via",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.539",
3
+ "version": "0.3.541",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"
@@ -0,0 +1,160 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const root = path.resolve(__dirname, "..");
7
+ const REPO = "saadnvd1/agent-os";
8
+
9
+ function slug(heading: string): string {
10
+ return heading
11
+ .trim()
12
+ .toLowerCase()
13
+ .replace(/[`*]/g, "")
14
+ .replace(/[^\w\- ]/g, "")
15
+ .replace(/ /g, "-");
16
+ }
17
+
18
+ function headingSlugs(text: string): Set<string> {
19
+ const seen = new Map<string, number>();
20
+ const out = new Set<string>();
21
+ let fence = false;
22
+ for (const line of text.split("\n")) {
23
+ if (line.startsWith("```")) fence = !fence;
24
+ const m = !fence && /^#+ (.*)$/.exec(line);
25
+ if (!m) continue;
26
+ const base = slug(m[1]);
27
+ const n = seen.get(base) ?? 0;
28
+ seen.set(base, n + 1);
29
+ out.add(n ? `${base}-${n}` : base);
30
+ }
31
+ return out;
32
+ }
33
+
34
+ function markdownFiles(): string[] {
35
+ const out = execFileSync(
36
+ "git",
37
+ ["ls-files", "--", "README.md", "docs/*.md", "docs/**/*.md"],
38
+ { cwd: root, encoding: "utf8" }
39
+ );
40
+ return out.split("\n").filter(Boolean);
41
+ }
42
+
43
+ /** Link targets with their line numbers, skipping code. */
44
+ function targets(text: string): { line: number; target: string }[] {
45
+ const found: { line: number; target: string }[] = [];
46
+ let fence = false;
47
+ text.split("\n").forEach((raw, i) => {
48
+ if (raw.startsWith("```")) fence = !fence;
49
+ if (fence) return;
50
+ const line = raw.replace(/`[^`]*`/g, "");
51
+ for (const m of line.matchAll(/\]\(([^)\s]+)\)/g))
52
+ found.push({ line: i + 1, target: m[1] });
53
+ for (const m of line.matchAll(/(?:src|srcset)="([^"]+)"/g))
54
+ found.push({ line: i + 1, target: m[1] });
55
+ });
56
+ return found;
57
+ }
58
+
59
+ /** A repo path inside a link to this repository on github.com, if it is one. */
60
+ function repoPath(target: string): string | null {
61
+ const m = new RegExp(
62
+ `^https://(?:github\\.com/${REPO}(?:/(?:blob|tree)/main)?|raw\\.githubusercontent\\.com/${REPO}/main)/?(.*)$`
63
+ ).exec(target);
64
+ return m ? m[1] : null;
65
+ }
66
+
67
+ function readTracked(rel: string): string | null {
68
+ const file = path.join(root, rel.replace(/\/$/, ""));
69
+ if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) return null;
70
+ return fs.readFileSync(file, "utf8");
71
+ }
72
+
73
+ describe("slug", () => {
74
+ it("follows GitHub's anchors", () => {
75
+ expect(slug("Project config (`agentos.json`)")).toBe(
76
+ "project-config-agentosjson"
77
+ );
78
+ expect(slug("Windows (WSL2)")).toBe("windows-wsl2");
79
+ });
80
+ });
81
+
82
+ describe("markdown docs", () => {
83
+ const files = markdownFiles();
84
+
85
+ it("have links that resolve", () => {
86
+ const broken: string[] = [];
87
+ for (const file of files) {
88
+ const text = fs.readFileSync(path.join(root, file), "utf8");
89
+ const own = headingSlugs(text);
90
+ const inGuides = file.startsWith("docs/guides/");
91
+ for (const { line, target } of targets(text)) {
92
+ const at = `${file}:${line} -> ${target}`;
93
+ if (target.startsWith("mailto:")) continue;
94
+ let rel: string | null;
95
+ let anchor = "";
96
+ if (target.startsWith("#")) {
97
+ if (!own.has(target.slice(1))) broken.push(at);
98
+ continue;
99
+ }
100
+ if (/^https?:/.test(target)) {
101
+ const hash = target.indexOf("#");
102
+ const url = hash < 0 ? target : target.slice(0, hash);
103
+ anchor = hash < 0 ? "" : target.slice(hash + 1);
104
+ rel = repoPath(url);
105
+ if (rel === null) continue;
106
+ if (rel === "") rel = "README.md";
107
+ } else {
108
+ const [p, a = ""] = target.split("#");
109
+ anchor = a;
110
+ rel = path.posix.normalize(
111
+ path.posix.join(path.posix.dirname(file), p)
112
+ );
113
+ if (
114
+ inGuides &&
115
+ !rel.startsWith("docs/guides/") &&
116
+ !p.startsWith("http")
117
+ )
118
+ broken.push(`${at} (guides are published alone; link by full URL)`);
119
+ }
120
+ const abs = path.join(root, rel.replace(/\/$/, ""));
121
+ if (!fs.existsSync(abs)) {
122
+ broken.push(at);
123
+ continue;
124
+ }
125
+ if (anchor && rel.endsWith(".md")) {
126
+ const dest = readTracked(rel);
127
+ if (dest === null || !headingSlugs(dest).has(anchor)) broken.push(at);
128
+ }
129
+ }
130
+ }
131
+ expect(broken).toEqual([]);
132
+ });
133
+
134
+ it("keep guide front matter complete, with unique order", () => {
135
+ const orders = new Map<string, string>();
136
+ const problems: string[] = [];
137
+ for (const file of files) {
138
+ if (!file.startsWith("docs/guides/") || file.endsWith("README.md"))
139
+ continue;
140
+ const text = fs.readFileSync(path.join(root, file), "utf8");
141
+ const fm = /^---\n([\s\S]*?)\n---\n/.exec(text)?.[1] ?? "";
142
+ for (const key of ["title", "description"])
143
+ if (!new RegExp(`^${key}: .+`, "m").test(fm))
144
+ problems.push(`${file}: missing ${key}`);
145
+ const order = /^order: (\d+)$/m.exec(fm)?.[1];
146
+ if (!order) problems.push(`${file}: missing numeric order`);
147
+ else if (orders.has(order))
148
+ problems.push(`${file}: order ${order} also in ${orders.get(order)}`);
149
+ else orders.set(order, file);
150
+ }
151
+ expect(problems).toEqual([]);
152
+ });
153
+
154
+ it("keeps the README a front page", () => {
155
+ const lines = fs
156
+ .readFileSync(path.join(root, "README.md"), "utf8")
157
+ .split("\n").length;
158
+ expect(lines).toBeLessThan(250);
159
+ });
160
+ });