antcolony
All repositories: gitoria
8.4 KB
// previous.hl — mission 026 (ticket antcolony#1; the first live run, ident.worldapi.org#2, built and tested everything in// a work copy, ran out of steps, and the next worker would have started over): the brief of a ticket that had an// earlier colony session gets a short "Previous attempt" section — how that session ended, its result, what is still// open, where its work copy is, and its report path — so the next worker CONTINUES instead of starting over.//// The last colony session = the last lease event in the ticket's history (`→ in progress` with `colony-lease: <S>`,// also salvage's lease; v0: "session <S> started on <H>"). How it ended = the last marker of THAT session after it:// `colony-report: S` (`· half done` = half done), `colony-verdict: S · fail`, `colony-run: S` (no usable report),// `colony-lease-expired: S`, `colony-parked: S`. The rest comes from its run folder `runs/<S>/` on THIS host// (session.json, report.json); the work copy = every `.scratch/<name>` the report names (relative → the dev folder).import { env } from 'hl:proc'import { exists } from 'hl:fs'import { NL, readJson, fromCwd, isList, isString } from './util.hl'import { isSessionStart } from './relations.hl'// the runs folder (same rule as work.hl runsDir: COLONY_RUNS_DIR, else <COLONY_HOME>/runs) or nullstatic runsDirOf = () => {let r = env('COLONY_RUNS_DIR')if (r != null && r != '') { return fromCwd(r) }let home = env('COLONY_HOME')if (home == null || home == '') { return null }return home + '/runs'}// a lease event → { session, host } or nullstatic startOf = (e) => {if (!isSessionStart(e)) { return null }let t = e.textlet i = t.indexOf('colony-lease: ')if (i >= 0) {let line = t.slice(i + 14)let nl = line.indexOf(NL)if (nl >= 0) { line = line.slice(0, nl) }let parts = line.split(' · ')let host = nullfor (p of parts) { if (p.startsWith('host ')) { host = p.slice(5) } }return { session = parts[0].trim() host = host }}// v0: "session <S> started on <H>"let j = t.indexOf(' started on ')let rest = t.slice(j + 12)let nl2 = rest.indexOf(NL)return { session = t.slice(8, j).trim() host = (nl2 >= 0 ? rest.slice(0, nl2) : rest).trim() }}// how session S ended, from the events after its start → textstatic endingOf = (events, fromSeq, s) => {let ending = 'no result in the ticket (it may still run, or it crashed)'for (e of events) {if (e.seq > fromSeq && e.text != null) {let t = e.textif (e.kind == 'comment' && t.includes('colony-report: ' + s + ' ·')) { ending = t.includes(' · half done') ? 'HALF DONE — its report said the work is not complete' : t.includes(' · ready to deploy') ? 'built, its report was posted — waiting for the deploy (mission 027)' : 'its report was posted (ready for the creator to test)' }else if (e.kind == 'comment' && t.includes('colony-deployed: ' + s + ' ·')) { ending = 'built and deployed — ready for the creator to test' }else if (e.kind == 'comment' && t.includes('colony-verdict: ' + s + ' · fail')) { ending = 'its report FAILED the check (see that comment in the history)' }else if (t.includes('colony-run: ' + s + ' ·')) { ending = 'it stopped without a usable report (out of steps / time, or its report was incomplete)' }else if (t.includes('colony-lease-expired: ' + s + ' ·')) { ending = 'it stopped answering (the lease expired)' }else if (t.includes('colony-parked: ' + s + ' ·')) { ending = 'it was paused (usage limit / scheduler stopped)' }}}return ending}static oneLineOf = (s, n) => {let t = s.trim()let nl = t.indexOf(NL)if (nl > 0) { t = t.slice(0, nl).trim() }if (t.length > n) { t = t.slice(0, n - 1).trim() + '…' }return t}// characters that end a path inside prose: space, tab, newline, quotes, backtick, brackets, comma, colon, semicolon, <, >, =static isDelim = (c) => {return c == 32 || c == 9 || c == 10 || c == 13 || c == 34 || c == 39 || c == 96 || c == 40 || c == 41 || c == 44 || c == 58 || c == 59 || c == 91 || c == 93 || c == 60 || c == 62 || c == 61 || c == 123 || c == 125}// every `.scratch/<name>` named in the texts (the work copies), absolute (relative ones → `folder`), nested ones droppedstatic scratchPaths = (texts, folder) => {let out = []for (t of texts) {let base = 0let rest = tlet i = rest.indexOf('.scratch/')while (i >= 0) {let at = base + ilet a = atwhile (a > 0 && !isDelim(t.charCodeAt(a - 1))) { a = a - 1 }let b = at + 9while (b < t.length && !isDelim(t.charCodeAt(b)) && t.charCodeAt(b) != 47) { b = b + 1 }if (b > at + 9) {let p = t.slice(a, b)// a path at the end of a sentence: `… in .scratch/dev-btn.` → without the dot / commawhile (p.endsWith('.') || p.endsWith(',')) { p = p.slice(0, p.length - 1) }if (p.startsWith('./')) { p = p.slice(2) }if (!p.startsWith('/')) { p = folder + '/' + p }let nested = falsefor (q of out) { if (p == q || p.startsWith(q + '/')) { nested = true } }if (!nested) { out.push(p) }}base = brest = t.slice(b)i = rest.indexOf('.scratch/')}}return out}// the texts of a report that may name its work copystatic reportTexts = (r) => {let out = []for (k of ['open' 'done']) { if (isList(r[k])) { for (x of r[k]) { if (isString(x)) { out.push(x) } } } }if (isList(r.verified)) { for (v of r.verified) { if (isString(v.command)) { out.push(v.command) } } }if (isList(r.running)) { for (x of r.running) { if (isString(x.log)) { out.push(x.log) } } }if (isString(r.result)) { out.push(r.result) }return out}// the brief's "Previous attempt" section (list of lines; [] when the ticket never had a colony session).// folder = the project's dev folder (for relative work-copy paths)static previousLines = (events, folder) => {let last = nulllet lastEv = nulllet count = 0let seen = []for (e of events) {let st = startOf(e)if (st != null) {last = stlastEv = eif (!seen.includes(st.session)) {seen.push(st.session)count = count + 1}}}if (last == null) { return [] }let s = last.sessionlet o = []o.push('## Previous attempt — continue it, do not start over')o.push('- Session `' + s + '` (started ' + lastEv.when + ' on ' + last.host + (count > 1 ? '; ' + count + ' colony sessions on this ticket so far' : '') + '): ' + endingOf(events, lastEv.seq, s) + '.')let runs = runsDirOf()let dir = runs == null ? null : runs + '/' + sif (dir == null || !exists(dir + '/session.json')) {o.push('- Its run files: `' + last.host + ':runs/' + s + '/` of the scheduler — not on this host, so nothing more is known here. Look in the dev folder (e.g. `.scratch/`) for its work.')} else {let sj = readJson(dir + '/session.json')let meta = sj.error == null ? sj.value : {}let f = meta.folder == null ? folder : meta.folderlet rj = exists(dir + '/report.json') ? readJson(dir + '/report.json') : { error = 'no report.json' }if (rj.error == null) {let r = rj.valueo.push('- Its result: ' + (isString(r.result) ? oneLineOf(r.result, 200) : '_(none)_') + (r.complete == false ? ' _(not complete)_' : ''))if (isList(r.open) && r.open.length > 0) {o.push('- Still open (from its report):')let k = 0while (k < r.open.length && k < 5) {if (isString(r.open[k])) { o.push(' - ' + oneLineOf(r.open[k], 200)) }k = k + 1}if (r.open.length > 5) { o.push(' - … and ' + (r.open.length - 5) + ' more in the report') }}let wc = scratchPaths(reportTexts(r), f)if (wc.length > 0) {let shown = []let k2 = 0while (k2 < wc.length && k2 < 4) {shown.push('`' + wc[k2] + '`')k2 = k2 + 1}o.push('- Its work copy: ' + shown.join(', ') + ' — check what is there before you change anything.')} else {o.push('- Its work copy: its report names none (no `.scratch/…`) — it worked in `' + f + '` directly, or look in its session.')}o.push('- Its report: `' + dir + '/report.json` (the whole run: `' + dir + '/` — `brief.md`, the session `claude-result*.json`' + (exists(dir + '/verdict.json') ? ', the check `verdict.json`' : '') + ').')} else {let why = meta.outcome == null ? 'unknown' : oneLineOf(meta.outcome, 200)o.push('- It wrote no usable report (' + why + ').')o.push('- Its run files: `' + dir + '/` — its session `claude-result*.json` shows what it did; its work is in `' + f + '` (look in `.scratch/` for a work copy).')}}o.push('- Check what of it exists, then continue from there — and say in your report what you took over.')return o}
Branches
- mainmain branch
Latest commits
- 3a4d0324antcolony#37: a too-long report gets up to 3 fix tries, finished work is never thrown away for lengthmre
- a6af7883tracker: worker box sees calendar.worldapi.org (login to copy)mre
- c613d26btemplates: bridges to external components (login.js for ident's selector) are allowed (creator 2026-09-27)mre
- 9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre
- 7f9660eeState of 2026-09-27, before the move to gitoriamre