faberun 0.15.0 → 0.16.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/package.json +1 -1
- package/skills/faberun/references/operations.md +22 -11
- package/src/engine/cancel.mjs +20 -6
- package/src/host/preflight.mjs +5 -3
- package/src/notify/index.mjs +66 -32
- package/src/notify/session.mjs +240 -0
- package/src/plan/pipeline.mjs +14 -2
- package/src/plan/repo-facts.mjs +2 -0
- package/src/repo/worktree.mjs +28 -0
- package/src/report/locale.mjs +164 -0
- package/src/report/message.mjs +602 -0
- package/src/report/progress.mjs +33 -153
- package/src/seat/tmux.mjs +12 -3
package/src/repo/worktree.mjs
CHANGED
|
@@ -104,6 +104,11 @@ export function candidateRefName(runId) {
|
|
|
104
104
|
return `refs/faberun/${runId}/candidate`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** @param {string} runId @param {string} nodeId @returns {string} */
|
|
108
|
+
export function preservedRefName(runId, nodeId) {
|
|
109
|
+
return `refs/faberun/${runId}/preserved/${nodeId}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
/** @param {string} runId @param {string} nodeId @param {number} attempt @returns {string} */
|
|
108
113
|
function attemptBranchName(runId, nodeId, attempt) {
|
|
109
114
|
return `faberun/${runId}/${nodeId}/${attempt}`;
|
|
@@ -168,6 +173,29 @@ export function createRunRef(repo, runId, head) {
|
|
|
168
173
|
return ref;
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Keep one node's integrated commit reachable after cancel releases the run
|
|
178
|
+
* ref and the attempt branches: without a ref of its own the commit is
|
|
179
|
+
* garbage the next `git gc` collects, and the persisted snapshot alone cannot
|
|
180
|
+
* bring a pruned object back.
|
|
181
|
+
*
|
|
182
|
+
* Idempotent by the same contract `deleteRef` and `removeWorktree` honour:
|
|
183
|
+
* cancel can legitimately run twice (a retry after a partial first pass, or
|
|
184
|
+
* the operator repeating it), so a preserved ref that already names this
|
|
185
|
+
* exact commit is done, not a conflict — the second creation neither fails
|
|
186
|
+
* nor moves the target. A ref left at a different sha moves to the commit
|
|
187
|
+
* handed in now.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} repo @param {string} runId @param {string} nodeId @param {string|null|undefined} sha @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
export function createPreservedRef(repo, runId, nodeId, sha) {
|
|
192
|
+
if (!sha) throw Object.assign(new Error("a preserved ref needs an integrated commit"), { code: "git_head_required" });
|
|
193
|
+
const ref = preservedRefName(runId, nodeId);
|
|
194
|
+
if (gitHead(repo, ref) === sha) return ref;
|
|
195
|
+
runGit(["-C", repo, "update-ref", ref, sha]);
|
|
196
|
+
return ref;
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
/**
|
|
172
200
|
* @param {{repo: string, runDir: string, runId: string, nodeId: string, attempt: number, base?: string, declaredReads?: string[]}} args
|
|
173
201
|
* `base` cuts the new branch from a sealed sha instead of the run ref tip —
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language of a notification, and the labels it is written in.
|
|
3
|
+
*
|
|
4
|
+
* The controller never sees the operator's conversation with a harness; it
|
|
5
|
+
* sees what the operator wrote for faberun: the campaign goal, the journal
|
|
6
|
+
* notes and the objectives the plan gave each node. Those are the operator's
|
|
7
|
+
* prompts to this product, and a message that answers them in the same
|
|
8
|
+
* language reads as a reply rather than a log line. Detection is a count of
|
|
9
|
+
* function words in the two languages this product's operators write in;
|
|
10
|
+
* anything else, or nothing to read, is English. Quoted content -- the
|
|
11
|
+
* worker's own words, a judge finding -- keeps the language it was written
|
|
12
|
+
* in, because translating a quotation would make it stop being one.
|
|
13
|
+
*
|
|
14
|
+
* It is a module apart from `message.mjs` so the layout and the wording can
|
|
15
|
+
* change independently: a new label is one line here, a new line is one
|
|
16
|
+
* function there.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @typedef {"en"|"pt"} Language */
|
|
20
|
+
|
|
21
|
+
// `a`, `do` and `no` are left out on purpose: each is also an English word,
|
|
22
|
+
// and one article in an English sentence must not read as Portuguese.
|
|
23
|
+
const PORTUGUESE = new Set(["o", "e", "de", "da", "na", "que", "não", "nao", "para", "com", "uma", "um", "em", "os", "as", "dos", "das", "é", "se", "por", "mais", "como", "mas", "ao", "ou", "quando", "já", "também", "ser", "sem", "até", "cada", "nunca", "está", "são", "foi", "pelo", "pela", "nos", "nas", "seu", "sua", "isso", "esse", "essa", "ele", "ela"]);
|
|
24
|
+
const ENGLISH = new Set(["the", "and", "of", "to", "in", "is", "for", "with", "that", "on", "as", "are", "this", "it", "be", "by", "from", "or", "an", "not", "at", "when", "every", "never", "before", "after", "its", "into", "than", "which", "while", "each", "was", "has", "have"]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The language of the first group of samples that carries any function word
|
|
28
|
+
* at all, English when none does. Groups are passed in order of authorship:
|
|
29
|
+
* the campaign goal is the operator's own sentence, the journal notes are the
|
|
30
|
+
* orchestrating session's, the node objectives are the planner's -- and a
|
|
31
|
+
* plan written by a model in English must not outvote the person who typed
|
|
32
|
+
* the goal in Portuguese (measured 2026-09-21: two node objectives held 40
|
|
33
|
+
* English function words against a Portuguese goal's 19).
|
|
34
|
+
*
|
|
35
|
+
* @param {...readonly (string|null|undefined)[]} groups
|
|
36
|
+
* @returns {Language}
|
|
37
|
+
*/
|
|
38
|
+
export function detectLanguage(...groups) {
|
|
39
|
+
for (const samples of groups) {
|
|
40
|
+
let portuguese = 0;
|
|
41
|
+
let english = 0;
|
|
42
|
+
for (const sample of samples) {
|
|
43
|
+
if (typeof sample !== "string") continue;
|
|
44
|
+
for (const word of sample.toLowerCase().split(/[^\p{L}]+/u)) {
|
|
45
|
+
if (PORTUGUESE.has(word)) portuguese += 1;
|
|
46
|
+
else if (ENGLISH.has(word)) english += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (portuguese + english > 0) return portuguese > english ? "pt" : "en";
|
|
50
|
+
}
|
|
51
|
+
return "en";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Every phrase the message renders, per language. Node ids, run ids, model
|
|
56
|
+
* names and quoted text are never here: they are data, not wording.
|
|
57
|
+
*
|
|
58
|
+
* @type {Record<Language, Record<string, string>>}
|
|
59
|
+
*/
|
|
60
|
+
const LABELS = {
|
|
61
|
+
en: {
|
|
62
|
+
doneIn: "done in",
|
|
63
|
+
running: "running",
|
|
64
|
+
pending: "pending",
|
|
65
|
+
state: "state",
|
|
66
|
+
nothingDelivered: "nothing delivered",
|
|
67
|
+
"no-op": "no-op after",
|
|
68
|
+
failedAfter: "failed after",
|
|
69
|
+
blockedAfter: "blocked after",
|
|
70
|
+
exhaustedAfter: "exhausted after",
|
|
71
|
+
stalledAfter: "stalled after",
|
|
72
|
+
canceledAfter: "canceled after",
|
|
73
|
+
needsYou: "needs you",
|
|
74
|
+
attempt: "attempt",
|
|
75
|
+
run: "run",
|
|
76
|
+
done: "done",
|
|
77
|
+
asked: "asked",
|
|
78
|
+
did: "done",
|
|
79
|
+
proof: "proof",
|
|
80
|
+
delivered: "delivered",
|
|
81
|
+
why: "why",
|
|
82
|
+
do: "do",
|
|
83
|
+
judge: "judge",
|
|
84
|
+
missing: "missing",
|
|
85
|
+
error: "error",
|
|
86
|
+
checksGreen: "checks green",
|
|
87
|
+
checksOf: "checks passed",
|
|
88
|
+
judgePass: "judge pass",
|
|
89
|
+
judgePasses: "judge passes",
|
|
90
|
+
judgeRejected: "judge rejected",
|
|
91
|
+
noJudge: "no judge",
|
|
92
|
+
revisions: "revisions",
|
|
93
|
+
nodes: "nodes",
|
|
94
|
+
next: "next",
|
|
95
|
+
waitingOnYou: "waiting on you",
|
|
96
|
+
phases: "phases",
|
|
97
|
+
cache: "cache",
|
|
98
|
+
in: "in",
|
|
99
|
+
out: "out",
|
|
100
|
+
nextCommand: "next",
|
|
101
|
+
available: "available",
|
|
102
|
+
runUpdate: "run",
|
|
103
|
+
noSummary: "(no summary recorded)",
|
|
104
|
+
noObjective: "(no objective recorded)",
|
|
105
|
+
more: "more",
|
|
106
|
+
complete: "complete",
|
|
107
|
+
},
|
|
108
|
+
pt: {
|
|
109
|
+
doneIn: "concluído em",
|
|
110
|
+
running: "rodando",
|
|
111
|
+
pending: "pendente",
|
|
112
|
+
state: "estado",
|
|
113
|
+
nothingDelivered: "nada entregue",
|
|
114
|
+
"no-op": "sem mudança após",
|
|
115
|
+
failedAfter: "falhou após",
|
|
116
|
+
blockedAfter: "bloqueado após",
|
|
117
|
+
exhaustedAfter: "esgotado após",
|
|
118
|
+
stalledAfter: "travou após",
|
|
119
|
+
canceledAfter: "cancelado após",
|
|
120
|
+
needsYou: "precisa de você",
|
|
121
|
+
attempt: "tentativa",
|
|
122
|
+
run: "run",
|
|
123
|
+
done: "concluídos",
|
|
124
|
+
asked: "pedido",
|
|
125
|
+
did: "feito",
|
|
126
|
+
proof: "prova",
|
|
127
|
+
delivered: "entregue",
|
|
128
|
+
why: "motivo",
|
|
129
|
+
do: "ação",
|
|
130
|
+
judge: "juiz",
|
|
131
|
+
missing: "faltou",
|
|
132
|
+
error: "erro",
|
|
133
|
+
checksGreen: "checagens verdes",
|
|
134
|
+
checksOf: "checagens passaram",
|
|
135
|
+
judgePass: "juiz aprovou",
|
|
136
|
+
judgePasses: "aprovações do juiz",
|
|
137
|
+
judgeRejected: "juiz rejeitou",
|
|
138
|
+
noJudge: "sem juiz",
|
|
139
|
+
revisions: "revisões",
|
|
140
|
+
nodes: "nós",
|
|
141
|
+
next: "próximo",
|
|
142
|
+
waitingOnYou: "esperando você",
|
|
143
|
+
phases: "fases",
|
|
144
|
+
cache: "cache",
|
|
145
|
+
in: "entrada",
|
|
146
|
+
out: "saída",
|
|
147
|
+
nextCommand: "próximo passo",
|
|
148
|
+
available: "disponível",
|
|
149
|
+
runUpdate: "rode",
|
|
150
|
+
noSummary: "(sem resumo registrado)",
|
|
151
|
+
noObjective: "(sem objetivo registrado)",
|
|
152
|
+
more: "mais",
|
|
153
|
+
complete: "completa",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {Language} language
|
|
159
|
+
* @returns {(key: string) => string}
|
|
160
|
+
*/
|
|
161
|
+
export function labelsFor(language) {
|
|
162
|
+
const table = LABELS[language];
|
|
163
|
+
return (key) => table[key] ?? LABELS.en[key] ?? key;
|
|
164
|
+
}
|