@dirwin517/bot-farm 0.1.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.
Files changed (55) hide show
  1. package/.idea/bot-farm.iml +9 -0
  2. package/.idea/misc.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/LICENSE +21 -0
  6. package/README.md +584 -0
  7. package/package.json +13 -0
  8. package/src/api.mjs +567 -0
  9. package/src/cli.mjs +241 -0
  10. package/src/craft.mjs +172 -0
  11. package/src/db.mjs +93 -0
  12. package/src/farmhands.mjs +111 -0
  13. package/src/git.mjs +578 -0
  14. package/src/identity.mjs +199 -0
  15. package/src/legacy-defaults.mjs +44 -0
  16. package/src/mcp.mjs +681 -0
  17. package/src/mesh.mjs +222 -0
  18. package/src/packets.mjs +125 -0
  19. package/src/patches.mjs +141 -0
  20. package/src/pipelines.mjs +2145 -0
  21. package/src/pricing.mjs +91 -0
  22. package/src/projects.mjs +148 -0
  23. package/src/public/classic.html +1810 -0
  24. package/src/public/index.html +3284 -0
  25. package/src/questions.mjs +69 -0
  26. package/src/quota.mjs +76 -0
  27. package/src/registry.mjs +88 -0
  28. package/src/replay.mjs +151 -0
  29. package/src/rooms.mjs +320 -0
  30. package/src/server.mjs +765 -0
  31. package/src/store.mjs +546 -0
  32. package/src/supervisor.mjs +1803 -0
  33. package/src/tasks.mjs +191 -0
  34. package/src/template.mjs +86 -0
  35. package/src/tools.mjs +241 -0
  36. package/src/workspaces.mjs +605 -0
  37. package/src/ws.mjs +130 -0
  38. package/src/yaml.mjs +266 -0
  39. package/test/board.mjs +108 -0
  40. package/test/craft.mjs +281 -0
  41. package/test/extend.mjs +145 -0
  42. package/test/flow.mjs +165 -0
  43. package/test/handoff.mjs +228 -0
  44. package/test/linked.mjs +83 -0
  45. package/test/loop.mjs +168 -0
  46. package/test/mock-opencode.mjs +217 -0
  47. package/test/parallel.mjs +135 -0
  48. package/test/patches.mjs +93 -0
  49. package/test/repos.mjs +118 -0
  50. package/test/roles.mjs +157 -0
  51. package/test/shot.mjs +65 -0
  52. package/test/smoke.mjs +70 -0
  53. package/test/transport.mjs +131 -0
  54. package/test/workspaces.mjs +228 -0
  55. package/test/workspaces.mjs.tmp +0 -0
@@ -0,0 +1,199 @@
1
+ // Every session gets a stable handle and avatar derived from its id.
2
+ //
3
+ // The handle is not decoration: agents address each other by "@brisk-otter",
4
+ // never by "ses_01H9Z...". A model copies a three-syllable handle reliably and
5
+ // a 26-character ulid unreliably, and the operator can hold six of them in
6
+ // their head at once.
7
+
8
+ // The adjective carries the job. @prying-heron reads as a tester before you
9
+ // have looked at anything else on the card, and when four bots are talking in
10
+ // one room the handle is doing the work a job title would.
11
+ const ADJECTIVES = [
12
+ "brisk", "amber", "quiet", "copper", "velvet", "nimble", "dusty", "hollow",
13
+ "clever", "glassy", "rusty", "silver", "patient", "crisp", "wandering", "murmur",
14
+ "pale", "swift", "tidal", "moss", "lantern", "gentle", "brave", "salty",
15
+ "paper", "iron", "plum", "drifting", "candid", "humming", "cobalt", "mellow",
16
+ ]
17
+
18
+ const ROLE_ADJECTIVES = {
19
+ // Product: framing, scoping, drawing the shape of the thing.
20
+ product: [
21
+ "framing", "scoping", "sketching", "charting", "mapping", "shaping",
22
+ "drafting", "defining", "aligning", "outlining", "plotting", "clarifying",
23
+ "sifting", "naming", "tracing", "weighing",
24
+ ],
25
+ // Dev: making, joining, finishing.
26
+ dev: [
27
+ "stacking", "moulding", "polishing", "welding", "forging", "wiring",
28
+ "threading", "bolting", "carving", "splicing", "tuning", "grafting",
29
+ "patching", "sanding", "riveting", "kneading",
30
+ ],
31
+ // QA: the ones who do not believe you.
32
+ qa: [
33
+ "prying", "probing", "nervous", "doubting", "squinting", "prodding",
34
+ "restless", "watchful", "snooping", "needling", "sceptical", "twitchy",
35
+ "poking", "sniffing", "fretting", "inquisitive",
36
+ ],
37
+ // Review: weighing the whole chain against what was asked.
38
+ reviewer: [
39
+ "weighing", "tallying", "auditing", "squaring", "measuring", "exacting",
40
+ "comparing", "reconciling", "scrupulous", "unblinking", "steady", "sifting",
41
+ "checking", "thorough", "literal", "deliberate",
42
+ ],
43
+ // Ops: keeping it up.
44
+ ops: [
45
+ "steady", "vigilant", "hardy", "watchful", "ready", "unflappable",
46
+ "tireless", "sturdy", "braced", "wakeful", "durable", "constant",
47
+ ],
48
+ // Research / docs.
49
+ research: [
50
+ "reading", "digging", "rummaging", "quoting", "indexing", "annotating",
51
+ "citing", "leafing", "combing", "collating", "footnoting", "marginal",
52
+ ],
53
+ writer: [
54
+ "lucid", "plain", "spare", "fluent", "eloquent", "pithy", "limpid",
55
+ "unfussy", "measured", "readable", "concise", "clean",
56
+ ],
57
+ }
58
+
59
+ /** Personas can supply their own list; otherwise the role's, otherwise generic. */
60
+ export function adjectivesFor(role, custom) {
61
+ if (Array.isArray(custom) && custom.length) return custom
62
+ if (!role) return ADJECTIVES
63
+ const key = String(role).toLowerCase()
64
+ return (
65
+ ROLE_ADJECTIVES[key] ??
66
+ ROLE_ADJECTIVES[Object.keys(ROLE_ADJECTIVES).find((k) => key.includes(k)) ?? ""] ??
67
+ ADJECTIVES
68
+ )
69
+ }
70
+
71
+ export { ROLE_ADJECTIVES }
72
+
73
+ // Every creature here has a distinct glyph, because the glyph *is* the avatar.
74
+ // A handle of "velvet-hedgehog" and a hedgehog face are the same fact twice,
75
+ // which is what makes a wall of 40 cards scannable.
76
+ const CREATURES = [
77
+ ["otter", "\u{1F9A6}"], ["owl", "\u{1F989}"], ["fox", "\u{1F98A}"], ["hare", "\u{1F407}"],
78
+ ["seal", "\u{1F9AD}"], ["beetle", "\u{1FAB2}"], ["badger", "\u{1F9A1}"], ["bison", "\u{1F9AC}"],
79
+ ["gecko", "\u{1F98E}"], ["toad", "\u{1F438}"], ["koi", "\u{1F420}"], ["crab", "\u{1F980}"],
80
+ ["squid", "\u{1F991}"], ["moth", "\u{1F98B}"], ["snail", "\u{1F40C}"], ["bat", "\u{1F987}"],
81
+ ["boar", "\u{1F417}"], ["camel", "\u{1F42A}"], ["llama", "\u{1F999}"], ["sloth", "\u{1F9A5}"],
82
+ ["skunk", "\u{1F9A8}"], ["raccoon", "\u{1F99D}"], ["swan", "\u{1F9A2}"], ["duck", "\u{1F986}"],
83
+ ["peacock", "\u{1F99A}"], ["parrot", "\u{1F99C}"], ["dodo", "\u{1FAB6}"], ["flamingo", "\u{1F9A9}"],
84
+ ["hedgehog", "\u{1F994}"], ["mouse", "\u{1F42D}"], ["hamster", "\u{1F439}"], ["wolf", "\u{1F43A}"],
85
+ ["panda", "\u{1F43C}"], ["koala", "\u{1F428}"], ["tiger", "\u{1F42F}"], ["lion", "\u{1F981}"],
86
+ ["monkey", "\u{1F435}"], ["penguin", "\u{1F427}"], ["eagle", "\u{1F985}"], ["rooster", "\u{1F413}"],
87
+ ["horse", "\u{1F434}"], ["zebra", "\u{1F993}"], ["deer", "\u{1F98C}"], ["goat", "\u{1F410}"],
88
+ ["chipmunk", "\u{1F43F}"], ["bear", "\u{1F43B}"], ["whale", "\u{1F433}"], ["shark", "\u{1F988}"],
89
+ ["octopus", "\u{1F419}"], ["ladybug", "\u{1F41E}"], ["ant", "\u{1F41C}"], ["bee", "\u{1F41D}"],
90
+ ["cricket", "\u{1F997}"], ["lobster", "\u{1F99E}"], ["turtle", "\u{1F422}"], ["snake", "\u{1F40D}"],
91
+ ["elephant", "\u{1F418}"], ["rhino", "\u{1F98F}"], ["hippo", "\u{1F99B}"], ["giraffe", "\u{1F992}"],
92
+ ["kangaroo", "\u{1F998}"], ["gorilla", "\u{1F98D}"], ["beaver", "\u{1F9AB}"], ["pufferfish", "\u{1F421}"],
93
+ ]
94
+
95
+ const GLYPH = new Map(CREATURES)
96
+
97
+ // FNV-1a: stable across processes, unlike anything built on Math.random.
98
+ export function hash(str) {
99
+ let h = 0x811c9dc5
100
+ for (let i = 0; i < str.length; i++) {
101
+ h ^= str.charCodeAt(i)
102
+ h = Math.imul(h, 0x01000193) >>> 0
103
+ }
104
+ return h >>> 0
105
+ }
106
+
107
+ function rng(seed) {
108
+ let s = seed || 1
109
+ return () => {
110
+ s ^= s << 13; s >>>= 0
111
+ s ^= s >> 17
112
+ s ^= s << 5; s >>>= 0
113
+ return s / 0x100000000
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Handles must be unique among live sessions or addressing breaks, so a
119
+ * collision walks to the next free pair rather than appending a number.
120
+ */
121
+ export function handleFor(id, taken = new Set(), { role = null, adjectives = null } = {}) {
122
+ const h = hash(id)
123
+ const pool = adjectivesFor(role, adjectives)
124
+ for (let i = 0; i < pool.length * CREATURES.length; i++) {
125
+ const a = pool[(h + i * 7) % pool.length]
126
+ const c = CREATURES[(Math.floor(h / 32) + i * 3) % CREATURES.length][0]
127
+ const name = `${a}-${c}`
128
+ if (!taken.has(name)) return name
129
+ }
130
+ return `agent-${id.slice(-4)}`
131
+ }
132
+
133
+ // [glyph-side accent, background]. Sixteen backgrounds against sixty-four
134
+ // creatures is a thousand faces before two of them look alike, and the pairs
135
+ // are picked for contrast against the panel rather than for variety alone.
136
+ const PALETTE = [
137
+ ["#ffab2e", "#6b4310"], ["#4cc3ff", "#134a63"], ["#b494ff", "#3c2d70"],
138
+ ["#5fd0d6", "#14484b"], ["#ff8f6b", "#6b2d19"], ["#e6c14a", "#584613"],
139
+ ["#7fd17f", "#1e4a24"], ["#ff8fb8", "#66253f"], ["#8fb3ff", "#22386e"],
140
+ ["#d1a0ff", "#4a2a63"], ["#ffd166", "#5f4611"], ["#66e0c0", "#134c3f"],
141
+ ["#ff9ed8", "#5e2450"], ["#9be36b", "#33531b"], ["#6fc0ff", "#173f61"],
142
+ ["#ffbf7a", "#61401b"],
143
+ ]
144
+
145
+ /**
146
+ * The avatar is the creature in the handle, on a colour drawn from the
147
+ * adjective. Two axes a person can actually name ("the orange otter") beat a
148
+ * random bit-grid, which is why this is a glyph and not an identicon: at 26px
149
+ * a 5x5 noise square is indistinguishable from the next 5x5 noise square, and
150
+ * forty of them on one screen is noise by definition.
151
+ *
152
+ * Identicon bits are kept as the fallback for anything without a handle.
153
+ */
154
+ /** The avatar's colours, so the rest of the UI can match a bot to its face. */
155
+ export function colorFor(id) {
156
+ const [fg, bg] = PALETTE[hash("avatar:" + id) % PALETTE.length]
157
+ return { fg, bg }
158
+ }
159
+
160
+ export function avatarFor(id, { size = 56, handle = null } = {}) {
161
+ const h = hash("avatar:" + id)
162
+ const [fg, bg] = PALETTE[h % PALETTE.length]
163
+ const creature = handle?.split("-").slice(1).join("-")
164
+ const glyph = creature && GLYPH.get(creature)
165
+ if (glyph) {
166
+ return (
167
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}" role="img" aria-label="${handle}">` +
168
+ `<rect width="32" height="32" rx="7" fill="${bg}"/>` +
169
+ `<rect x="0.6" y="0.6" width="30.8" height="30.8" rx="6.6" fill="none" stroke="${fg}" stroke-opacity=".55"/>` +
170
+ `<text x="16" y="17.5" font-size="21" text-anchor="middle" dominant-baseline="central">${glyph}</text>` +
171
+ `</svg>`
172
+ )
173
+ }
174
+ const rand = rng(h)
175
+ const cells = []
176
+ for (let x = 0; x < 3; x++) {
177
+ for (let y = 0; y < 5; y++) if (rand() > 0.45) cells.push([x, y], [4 - x, y])
178
+ }
179
+ return (
180
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 5" width="${size}" height="${size}" ` +
181
+ `shape-rendering="crispEdges" role="img" aria-hidden="true">` +
182
+ `<rect width="5" height="5" fill="${bg}"/>` +
183
+ cells.map(([x, y]) => `<rect x="${x}" y="${y}" width="1" height="1" fill="${fg}"/>`).join("") +
184
+ `</svg>`
185
+ )
186
+ }
187
+
188
+ export { GLYPH }
189
+
190
+ export function avatarDataUri(id, opts) {
191
+ return "data:image/svg+xml;base64," + Buffer.from(avatarFor(id, opts)).toString("base64")
192
+ }
193
+
194
+ /** Tokens authenticate an MCP caller back to one session. */
195
+ export function mintToken() {
196
+ const b = Buffer.alloc(18)
197
+ for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256)
198
+ return b.toString("base64url")
199
+ }
@@ -0,0 +1,44 @@
1
+ // Built-in definitions as they shipped before, so a workspace file that
2
+ // still has them unmodified can be moved to the current ones. Anything you
3
+ // have edited is left alone.
4
+ export const LEGACY = {
5
+ agents: {
6
+ product: [`id: product
7
+ title: Product Bot
8
+ role: product
9
+ # model: anthropic/claude-sonnet-4-5
10
+ # tools: [read, grep, glob] # omit for everything the session has
11
+ # may_spawn: false
12
+ prompt: |
13
+ You are the product analyst on this change.
14
+
15
+ Your job is to turn a rough story into acceptance criteria that a developer
16
+ can build against and a tester can verify. Read the code before you write
17
+ them: criteria that ignore how the system actually works are worse than none.
18
+
19
+ Write criteria that are observable. "Login works" is not a criterion.
20
+ "After SSO login, the user lands on /home and the session cookie is set with
21
+ SameSite=Lax" is. Between four and eight of them is usually right.
22
+
23
+ You do not write production code.
24
+ `],
25
+ },
26
+ stages: {
27
+ "story/analyse": [`stages:
28
+ - id: analyse
29
+ persona: product
30
+ title: Sharpen the story into acceptance criteria
31
+ receives: []
32
+ prompt: |
33
+ Story to analyse:
34
+
35
+ {{story}}
36
+
37
+ Read enough of the codebase to understand how this area works today,
38
+ then write the acceptance criteria. Put them in acceptance_criteria on
39
+ your handoff — everything downstream is built and tested against that
40
+ list, so it is the most important thing you produce.
41
+
42
+ `],
43
+ },
44
+ }