@saadnvd1/agent-os 0.3.460 → 0.3.462

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.
Files changed (38) hide show
  1. package/README.md +51 -3
  2. package/app/api/away/route.ts +56 -0
  3. package/app/api/away/seen/route.ts +12 -0
  4. package/app/api/health/route.ts +9 -0
  5. package/app/page.tsx +2 -0
  6. package/components/Away/AwayReport.tsx +16 -0
  7. package/components/Away/AwayReportDialog.tsx +193 -0
  8. package/components/Away/AwayToggle.tsx +91 -0
  9. package/components/Away/time.test.ts +20 -0
  10. package/components/Away/time.ts +32 -0
  11. package/components/Away/useAwayCommands.ts +43 -0
  12. package/components/Chat/Messages.tsx +1 -0
  13. package/components/CommandPalette/useAppCommands.ts +2 -0
  14. package/components/Pane/MobileTabBar.tsx +14 -1
  15. package/components/SessionList/SidebarHeader.tsx +2 -0
  16. package/data/away/index.ts +73 -0
  17. package/data/push/topics.ts +4 -0
  18. package/lib/away/brief.ts +23 -0
  19. package/lib/away/deploy.ts +173 -0
  20. package/lib/away/report.ts +398 -0
  21. package/lib/away/run.ts +445 -0
  22. package/lib/away/state.ts +201 -0
  23. package/lib/away/sweep.ts +216 -0
  24. package/lib/away/view.ts +51 -0
  25. package/lib/chat/events.ts +1 -1
  26. package/lib/db/changes.ts +3 -0
  27. package/lib/db/migrations.ts +69 -0
  28. package/lib/db/types.ts +4 -0
  29. package/lib/notify/index.ts +7 -0
  30. package/lib/orchestrator/digest.ts +11 -8
  31. package/lib/orchestrator/notes.ts +8 -1
  32. package/lib/orchestrator/watcher.ts +6 -0
  33. package/lib/project-config/agentos.schema.json +24 -0
  34. package/lib/project-config/schema.ts +17 -0
  35. package/lib/worktree-hooks.ts +9 -0
  36. package/package.json +1 -1
  37. package/scripts/hooks/staged-secrets.test.ts +92 -0
  38. package/scripts/hooks/staged-secrets.ts +64 -0
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { execFileSync, spawnSync } from "child_process";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+ import { scanDiff } from "./staged-secrets";
7
+
8
+ // Fakes are built here, never written whole: a literal would be the very
9
+ // thing this scan (and the merge gate) refuses.
10
+ const fake = {
11
+ github: ["gh", "p_", "a1B2".repeat(9)].join(""),
12
+ anthropic: ["sk", "-ant-", "x9Y8".repeat(8)].join(""),
13
+ aws: ["AK", "IA", "Q7".repeat(8)].join(""),
14
+ };
15
+
16
+ const diffOf = (file: string, start: number, lines: string[]) =>
17
+ [
18
+ `diff --git a/${file} b/${file}`,
19
+ "index 0000000..1111111 100644",
20
+ `--- a/${file}`,
21
+ `+++ b/${file}`,
22
+ `@@ -0,0 +${start},${lines.length} @@`,
23
+ ...lines.map((l) => `+${l}`),
24
+ ].join("\n");
25
+
26
+ describe("scanDiff", () => {
27
+ it("names the file and line of each token-shaped string", () => {
28
+ const diff = [
29
+ diffOf("lib/a.ts", 10, ["const ok = 1;", `const t = "${fake.github}";`]),
30
+ diffOf("docs/b.md", 3, [`key: ${fake.anthropic}`, "", fake.aws]),
31
+ ].join("\n");
32
+ expect(scanDiff(diff)).toEqual([
33
+ { file: "lib/a.ts", line: 11, what: expect.stringMatching(/^ghp_/) },
34
+ { file: "docs/b.md", line: 3, what: expect.stringMatching(/^sk-/) },
35
+ { file: "docs/b.md", line: 5, what: expect.stringMatching(/^AKIA/) },
36
+ ]);
37
+ });
38
+
39
+ it("flags an env-style assignment of a real-looking value, not a placeholder", () => {
40
+ const diff = diffOf(".env.example", 1, [
41
+ `API_TOKEN=${"q7R".repeat(6)}42`,
42
+ "API_KEY=${API_KEY}",
43
+ "SECRET_KEY=changeme",
44
+ ]);
45
+ expect(scanDiff(diff)).toEqual([
46
+ { file: ".env.example", line: 1, what: "API_TOKEN = …" },
47
+ ]);
48
+ });
49
+
50
+ it("passes code that builds a fake at runtime", () => {
51
+ const diff = diffOf("lib/c.test.ts", 1, [
52
+ 'const t = ["gh", "p_", "a".repeat(36)].join("");',
53
+ ]);
54
+ expect(scanDiff(diff)).toEqual([]);
55
+ });
56
+
57
+ it("doesn't take an added line starting with ++ for a file header", () => {
58
+ const diff = diffOf("lib/d.ts", 1, [`++ ${fake.github}`]);
59
+ expect(scanDiff(diff)).toEqual([
60
+ { file: "lib/d.ts", line: 1, what: expect.stringMatching(/^ghp_/) },
61
+ ]);
62
+ });
63
+
64
+ it("fails the commit on a staged secret, and passes once it's gone", () => {
65
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "staged-secrets-"));
66
+ const git = (...args: string[]) =>
67
+ execFileSync("git", args, { cwd: repo, stdio: "pipe" });
68
+ const scan = () =>
69
+ spawnSync(
70
+ path.resolve("node_modules/.bin/tsx"),
71
+ [path.resolve("scripts/hooks/staged-secrets.ts")],
72
+ { cwd: repo, encoding: "utf-8" }
73
+ );
74
+ try {
75
+ git("init", "-q");
76
+ fs.writeFileSync(
77
+ path.join(repo, "x.ts"),
78
+ `const t = "${fake.github}";\n`
79
+ );
80
+ git("add", "x.ts");
81
+ const bad = scan();
82
+ expect(bad.status).toBe(1);
83
+ expect(bad.stderr).toContain("x.ts:1");
84
+ expect(bad.stderr).toContain("at runtime");
85
+ fs.writeFileSync(path.join(repo, "x.ts"), "const t = process.env.T;\n");
86
+ git("add", "x.ts");
87
+ expect(scan().status).toBe(0);
88
+ } finally {
89
+ fs.rmSync(repo, { recursive: true, force: true });
90
+ }
91
+ });
92
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The pre-commit secret scan: the staged diff's added lines, read with the
3
+ * merge gate's own rule (lib/orchestrator/diff.ts secretsIn), so a commit
4
+ * that would fail the gate fails here first, naming the file and line.
5
+ */
6
+
7
+ import { execFileSync } from "child_process";
8
+ import { secretsIn } from "../../lib/orchestrator/diff";
9
+
10
+ export interface SecretHit {
11
+ file: string;
12
+ line: number;
13
+ what: string;
14
+ }
15
+
16
+ // Each added line of a `git diff -U0` with the file and new line number it
17
+ // lands on. Headers are only read before a file's first hunk, so an added
18
+ // line that itself starts with "++" isn't taken for one.
19
+ export function scanDiff(diff: string): SecretHit[] {
20
+ const hits: SecretHit[] = [];
21
+ let file = "";
22
+ let line = 0;
23
+ let header = false;
24
+ for (const l of diff.split("\n")) {
25
+ if (l.startsWith("diff --git ")) {
26
+ header = true;
27
+ } else if (header && l.startsWith("+++ ")) {
28
+ file = l.slice(4).replace(/^b\//, "");
29
+ } else if (l.startsWith("@@")) {
30
+ header = false;
31
+ line = Number(l.match(/\+(\d+)/)?.[1] ?? 0);
32
+ } else if (!header && l.startsWith("+")) {
33
+ const what = secretsIn(l.slice(1));
34
+ if (what) hits.push({ file, line, what });
35
+ line++;
36
+ }
37
+ }
38
+ return hits;
39
+ }
40
+
41
+ if (process.argv[1]?.endsWith("staged-secrets.ts")) {
42
+ const diff = execFileSync(
43
+ "git",
44
+ [
45
+ "diff",
46
+ "--cached",
47
+ "-U0",
48
+ "--no-color",
49
+ "--no-ext-diff",
50
+ "--diff-filter=ACMR",
51
+ ],
52
+ { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 }
53
+ );
54
+ const hits = scanDiff(diff);
55
+ for (const h of hits)
56
+ console.error(`✗ ${h.file}:${h.line}: looks like a secret (${h.what})`);
57
+ if (hits.length) {
58
+ console.error(
59
+ "The merge gate refuses token-shaped strings. For a test fixture, build the fake at runtime\n" +
60
+ '(e.g. "gh" + "p_" + "a".repeat(36)) instead of writing it as a literal.'
61
+ );
62
+ process.exit(1);
63
+ }
64
+ }