gitoriaLog in with ident

antcolony

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit9062978c9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre9062978c/lib/previous.hl

8.4 KB

  1. // previous.hl — mission 026 (ticket antcolony#1; the first live run, ident.worldapi.org#2, built and tested everything in
  2. // a work copy, ran out of steps, and the next worker would have started over): the brief of a ticket that had an
  3. // earlier colony session gets a short "Previous attempt" section — how that session ended, its result, what is still
  4. // open, where its work copy is, and its report path — so the next worker CONTINUES instead of starting over.
  5. //
  6. // The last colony session = the last lease event in the ticket's history (`→ in progress` with `colony-lease: <S>`,
  7. // also salvage's lease; v0: "session <S> started on <H>"). How it ended = the last marker of THAT session after it:
  8. // `colony-report: S` (`· half done` = half done), `colony-verdict: S · fail`, `colony-run: S` (no usable report),
  9. // `colony-lease-expired: S`, `colony-parked: S`. The rest comes from its run folder `runs/<S>/` on THIS host
  10. // (session.json, report.json); the work copy = every `.scratch/<name>` the report names (relative → the dev folder).
  11. import { env } from 'hl:proc'
  12. import { exists } from 'hl:fs'
  13. import { NL, readJson, fromCwd, isList, isString } from './util.hl'
  14. import { isSessionStart } from './relations.hl'
  15. // the runs folder (same rule as work.hl runsDir: COLONY_RUNS_DIR, else <COLONY_HOME>/runs) or null
  16. static runsDirOf = () => {
  17. let r = env('COLONY_RUNS_DIR')
  18. if (r != null && r != '') { return fromCwd(r) }
  19. let home = env('COLONY_HOME')
  20. if (home == null || home == '') { return null }
  21. return home + '/runs'
  22. }
  23. // a lease event → { session, host } or null
  24. static startOf = (e) => {
  25. if (!isSessionStart(e)) { return null }
  26. let t = e.text
  27. let i = t.indexOf('colony-lease: ')
  28. if (i >= 0) {
  29. let line = t.slice(i + 14)
  30. let nl = line.indexOf(NL)
  31. if (nl >= 0) { line = line.slice(0, nl) }
  32. let parts = line.split(' · ')
  33. let host = null
  34. for (p of parts) { if (p.startsWith('host ')) { host = p.slice(5) } }
  35. return { session = parts[0].trim() host = host }
  36. }
  37. // v0: "session <S> started on <H>"
  38. let j = t.indexOf(' started on ')
  39. let rest = t.slice(j + 12)
  40. let nl2 = rest.indexOf(NL)
  41. return { session = t.slice(8, j).trim() host = (nl2 >= 0 ? rest.slice(0, nl2) : rest).trim() }
  42. }
  43. // how session S ended, from the events after its start → text
  44. static endingOf = (events, fromSeq, s) => {
  45. let ending = 'no result in the ticket (it may still run, or it crashed)'
  46. for (e of events) {
  47. if (e.seq > fromSeq && e.text != null) {
  48. let t = e.text
  49. if (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)' }
  50. else if (e.kind == 'comment' && t.includes('colony-deployed: ' + s + ' ·')) { ending = 'built and deployed — ready for the creator to test' }
  51. else if (e.kind == 'comment' && t.includes('colony-verdict: ' + s + ' · fail')) { ending = 'its report FAILED the check (see that comment in the history)' }
  52. else if (t.includes('colony-run: ' + s + ' ·')) { ending = 'it stopped without a usable report (out of steps / time, or its report was incomplete)' }
  53. else if (t.includes('colony-lease-expired: ' + s + ' ·')) { ending = 'it stopped answering (the lease expired)' }
  54. else if (t.includes('colony-parked: ' + s + ' ·')) { ending = 'it was paused (usage limit / scheduler stopped)' }
  55. }
  56. }
  57. return ending
  58. }
  59. static oneLineOf = (s, n) => {
  60. let t = s.trim()
  61. let nl = t.indexOf(NL)
  62. if (nl > 0) { t = t.slice(0, nl).trim() }
  63. if (t.length > n) { t = t.slice(0, n - 1).trim() + '…' }
  64. return t
  65. }
  66. // characters that end a path inside prose: space, tab, newline, quotes, backtick, brackets, comma, colon, semicolon, <, >, =
  67. static isDelim = (c) => {
  68. 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
  69. }
  70. // every `.scratch/<name>` named in the texts (the work copies), absolute (relative ones → `folder`), nested ones dropped
  71. static scratchPaths = (texts, folder) => {
  72. let out = []
  73. for (t of texts) {
  74. let base = 0
  75. let rest = t
  76. let i = rest.indexOf('.scratch/')
  77. while (i >= 0) {
  78. let at = base + i
  79. let a = at
  80. while (a > 0 && !isDelim(t.charCodeAt(a - 1))) { a = a - 1 }
  81. let b = at + 9
  82. while (b < t.length && !isDelim(t.charCodeAt(b)) && t.charCodeAt(b) != 47) { b = b + 1 }
  83. if (b > at + 9) {
  84. let p = t.slice(a, b)
  85. // a path at the end of a sentence: `… in .scratch/dev-btn.` → without the dot / comma
  86. while (p.endsWith('.') || p.endsWith(',')) { p = p.slice(0, p.length - 1) }
  87. if (p.startsWith('./')) { p = p.slice(2) }
  88. if (!p.startsWith('/')) { p = folder + '/' + p }
  89. let nested = false
  90. for (q of out) { if (p == q || p.startsWith(q + '/')) { nested = true } }
  91. if (!nested) { out.push(p) }
  92. }
  93. base = b
  94. rest = t.slice(b)
  95. i = rest.indexOf('.scratch/')
  96. }
  97. }
  98. return out
  99. }
  100. // the texts of a report that may name its work copy
  101. static reportTexts = (r) => {
  102. let out = []
  103. for (k of ['open' 'done']) { if (isList(r[k])) { for (x of r[k]) { if (isString(x)) { out.push(x) } } } }
  104. if (isList(r.verified)) { for (v of r.verified) { if (isString(v.command)) { out.push(v.command) } } }
  105. if (isList(r.running)) { for (x of r.running) { if (isString(x.log)) { out.push(x.log) } } }
  106. if (isString(r.result)) { out.push(r.result) }
  107. return out
  108. }
  109. // the brief's "Previous attempt" section (list of lines; [] when the ticket never had a colony session).
  110. // folder = the project's dev folder (for relative work-copy paths)
  111. static previousLines = (events, folder) => {
  112. let last = null
  113. let lastEv = null
  114. let count = 0
  115. let seen = []
  116. for (e of events) {
  117. let st = startOf(e)
  118. if (st != null) {
  119. last = st
  120. lastEv = e
  121. if (!seen.includes(st.session)) {
  122. seen.push(st.session)
  123. count = count + 1
  124. }
  125. }
  126. }
  127. if (last == null) { return [] }
  128. let s = last.session
  129. let o = []
  130. o.push('## Previous attempt — continue it, do not start over')
  131. 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) + '.')
  132. let runs = runsDirOf()
  133. let dir = runs == null ? null : runs + '/' + s
  134. if (dir == null || !exists(dir + '/session.json')) {
  135. 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.')
  136. } else {
  137. let sj = readJson(dir + '/session.json')
  138. let meta = sj.error == null ? sj.value : {}
  139. let f = meta.folder == null ? folder : meta.folder
  140. let rj = exists(dir + '/report.json') ? readJson(dir + '/report.json') : { error = 'no report.json' }
  141. if (rj.error == null) {
  142. let r = rj.value
  143. o.push('- Its result: ' + (isString(r.result) ? oneLineOf(r.result, 200) : '_(none)_') + (r.complete == false ? ' _(not complete)_' : ''))
  144. if (isList(r.open) && r.open.length > 0) {
  145. o.push('- Still open (from its report):')
  146. let k = 0
  147. while (k < r.open.length && k < 5) {
  148. if (isString(r.open[k])) { o.push(' - ' + oneLineOf(r.open[k], 200)) }
  149. k = k + 1
  150. }
  151. if (r.open.length > 5) { o.push(' - … and ' + (r.open.length - 5) + ' more in the report') }
  152. }
  153. let wc = scratchPaths(reportTexts(r), f)
  154. if (wc.length > 0) {
  155. let shown = []
  156. let k2 = 0
  157. while (k2 < wc.length && k2 < 4) {
  158. shown.push('`' + wc[k2] + '`')
  159. k2 = k2 + 1
  160. }
  161. o.push('- Its work copy: ' + shown.join(', ') + ' — check what is there before you change anything.')
  162. } else {
  163. o.push('- Its work copy: its report names none (no `.scratch/…`) — it worked in `' + f + '` directly, or look in its session.')
  164. }
  165. 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`' : '') + ').')
  166. } else {
  167. let why = meta.outcome == null ? 'unknown' : oneLineOf(meta.outcome, 200)
  168. o.push('- It wrote no usable report (' + why + ').')
  169. 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).')
  170. }
  171. }
  172. o.push('- Check what of it exists, then continue from there — and say in your report what you took over.')
  173. return o
  174. }

Branches

Latest commits

  • 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