@saadnvd1/agent-os 0.3.538 → 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.
- package/README.md +25 -1992
- package/lib/orchestrator/events.ts +1 -0
- package/lib/recipes/settle.ts +1 -1
- package/lib/security/auth.ts +1 -1
- package/package.json +1 -1
- package/scripts/docs-links.test.ts +160 -0
|
@@ -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))
|
package/lib/recipes/settle.ts
CHANGED
|
@@ -194,7 +194,7 @@ function tell(
|
|
|
194
194
|
queueEvent(
|
|
195
195
|
run.recipe.workspace_id,
|
|
196
196
|
`recipe:${run.id}:${status}`,
|
|
197
|
-
run.
|
|
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
|
}
|
package/lib/security/auth.ts
CHANGED
|
@@ -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
|
|
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
|
@@ -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
|
+
});
|