lampson 0.1.1 → 0.1.3
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/README.md +17 -1
- package/chat.syn +387 -25
- package/lib/diff.syn +168 -0
- package/lib/line.syn +370 -0
- package/lib/loop.syn +25 -11
- package/lib/md.syn +229 -0
- package/lib/permission.syn +36 -1
- package/lib/prompt.syn +1 -1
- package/lib/tools/edit.syn +4 -1
- package/lib/tools/write.syn +4 -0
- package/lib/ui.syn +161 -0
- package/package.json +2 -2
- package/public/index.html +11 -1
- package/skills/synsema/SKILL.md +15 -2
package/lib/diff.syn
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
-- lib/diff.syn — diff de líneas para mostrar en la terminal qué cambió edit/write
|
|
2
|
+
-- diff(old, new, ctx, color) → {"added": n, "removed": m, "lines": [texto ya formateado…]}
|
|
3
|
+
-- Algoritmo: se recorta el prefijo y sufijo comunes (una edición es local) y se hace LCS solo sobre el
|
|
4
|
+
-- medio. Si el medio supera LIMIT² celdas (80×80 líneas cambiadas ≈ un rewrite) se muestra como bloque
|
|
5
|
+
-- quitado + bloque agregado: exacto y en milisegundos (el LCS interpretado a 300×300 tardaba 2 minutos).
|
|
6
|
+
|
|
7
|
+
use "./ui.syn" as ui
|
|
8
|
+
|
|
9
|
+
let ESC be decode(bytes("1b", "hex"))
|
|
10
|
+
let LIMIT be 80
|
|
11
|
+
|
|
12
|
+
-- línea de diff con fondo (verde/rojo) rellenada hasta el ancho útil, cortada si se pasa
|
|
13
|
+
task row(color, kind, num, body, width)
|
|
14
|
+
let n be sgr(color, "2", lpad(num, width) + " │ ")
|
|
15
|
+
let w be ui.cols() - 8 - width - 3
|
|
16
|
+
when w < 30
|
|
17
|
+
set w to 30
|
|
18
|
+
let txt be ui.cut(kind + " " + body, w)
|
|
19
|
+
let pad be rep(" ", w - ui.width(txt))
|
|
20
|
+
when kind == "+"
|
|
21
|
+
give n + ui.bg_add(color, sgr(color, "32", txt + pad))
|
|
22
|
+
when kind == "-"
|
|
23
|
+
give n + ui.bg_del(color, sgr(color, "31", txt + pad))
|
|
24
|
+
give n + sgr(color, "2", ui.cut(" " + body, w))
|
|
25
|
+
|
|
26
|
+
task rep(ch, n)
|
|
27
|
+
let out be ""
|
|
28
|
+
while length(out) < n
|
|
29
|
+
set out to out + ch
|
|
30
|
+
give out
|
|
31
|
+
|
|
32
|
+
task sgr(color, code, s)
|
|
33
|
+
when not color
|
|
34
|
+
give s
|
|
35
|
+
give ESC + "[" + code + "m" + s + ESC + "[0m"
|
|
36
|
+
|
|
37
|
+
task lpad(s, n)
|
|
38
|
+
let out be s
|
|
39
|
+
while length(out) < n
|
|
40
|
+
set out to " " + out
|
|
41
|
+
give out
|
|
42
|
+
|
|
43
|
+
-- LCS clásico: devuelve la lista de ops {"op": " "|"-"|"+", "a": idx_old, "b": idx_new}
|
|
44
|
+
task lcs_ops(a, b)
|
|
45
|
+
let n be length(a)
|
|
46
|
+
let m be length(b)
|
|
47
|
+
-- tabla (n+1)×(m+1) aplanada, fila por fila
|
|
48
|
+
-- MIGA: append copia la lista entera (O(n) cada vez) → construirla con append era O(n²·m²).
|
|
49
|
+
-- range() la crea de una y set por índice es O(1).
|
|
50
|
+
let table be range(0, (n + 1) * (m + 1))
|
|
51
|
+
let z be 0
|
|
52
|
+
while z < (n + 1) * (m + 1)
|
|
53
|
+
set table[z] to 0
|
|
54
|
+
set z to z + 1
|
|
55
|
+
let i be n - 1
|
|
56
|
+
while i >= 0
|
|
57
|
+
let j be m - 1
|
|
58
|
+
while j >= 0
|
|
59
|
+
when a[i] == b[j]
|
|
60
|
+
set table[i * (m + 1) + j] to table[(i + 1) * (m + 1) + j + 1] + 1
|
|
61
|
+
otherwise
|
|
62
|
+
let down be table[(i + 1) * (m + 1) + j]
|
|
63
|
+
let right be table[i * (m + 1) + j + 1]
|
|
64
|
+
set table[i * (m + 1) + j] to (when down >= right then down otherwise right)
|
|
65
|
+
set j to j - 1
|
|
66
|
+
set i to i - 1
|
|
67
|
+
let ops be []
|
|
68
|
+
set i to 0
|
|
69
|
+
let j be 0
|
|
70
|
+
while i < n and j < m
|
|
71
|
+
when a[i] == b[j]
|
|
72
|
+
set ops to append(ops, {"op": " ", "a": i, "b": j})
|
|
73
|
+
set i to i + 1
|
|
74
|
+
set j to j + 1
|
|
75
|
+
otherwise when table[(i + 1) * (m + 1) + j] >= table[i * (m + 1) + j + 1]
|
|
76
|
+
set ops to append(ops, {"op": "-", "a": i, "b": j})
|
|
77
|
+
set i to i + 1
|
|
78
|
+
otherwise
|
|
79
|
+
set ops to append(ops, {"op": "+", "a": i, "b": j})
|
|
80
|
+
set j to j + 1
|
|
81
|
+
while i < n
|
|
82
|
+
set ops to append(ops, {"op": "-", "a": i, "b": j})
|
|
83
|
+
set i to i + 1
|
|
84
|
+
while j < m
|
|
85
|
+
set ops to append(ops, {"op": "+", "a": i, "b": j})
|
|
86
|
+
set j to j + 1
|
|
87
|
+
give ops
|
|
88
|
+
|
|
89
|
+
export task diff(old, new, ctx, color)
|
|
90
|
+
let a be when old == "" then [] otherwise split(old, "\n")
|
|
91
|
+
let b be when new == "" then [] otherwise split(new, "\n")
|
|
92
|
+
-- prefijo común
|
|
93
|
+
let pre be 0
|
|
94
|
+
while pre < length(a) and pre < length(b) and a[pre] == b[pre]
|
|
95
|
+
set pre to pre + 1
|
|
96
|
+
-- sufijo común (sin pisar el prefijo)
|
|
97
|
+
let suf be 0
|
|
98
|
+
while suf < length(a) - pre and suf < length(b) - pre and a[length(a) - 1 - suf] == b[length(b) - 1 - suf]
|
|
99
|
+
set suf to suf + 1
|
|
100
|
+
let mid_a be slice(a, pre, length(a) - suf)
|
|
101
|
+
let mid_b be slice(b, pre, length(b) - suf)
|
|
102
|
+
let ops be []
|
|
103
|
+
when length(mid_a) * length(mid_b) > LIMIT * LIMIT
|
|
104
|
+
each e in enumerate(mid_a)
|
|
105
|
+
set ops to append(ops, {"op": "-", "a": e["index"], "b": 0})
|
|
106
|
+
each e in enumerate(mid_b)
|
|
107
|
+
set ops to append(ops, {"op": "+", "a": length(mid_a), "b": e["index"]})
|
|
108
|
+
otherwise
|
|
109
|
+
set ops to lcs_ops(mid_a, mid_b)
|
|
110
|
+
let added be 0
|
|
111
|
+
let removed be 0
|
|
112
|
+
each o in ops
|
|
113
|
+
when o["op"] == "+"
|
|
114
|
+
set added to added + 1
|
|
115
|
+
otherwise when o["op"] == "-"
|
|
116
|
+
set removed to removed + 1
|
|
117
|
+
-- render: ctx líneas de contexto antes y después del bloque cambiado (números de línea del archivo NUEVO
|
|
118
|
+
-- para "+"/" ", del viejo para "-"), con "⋯" entre hunks
|
|
119
|
+
let width be length(text(length(b)))
|
|
120
|
+
let lines be []
|
|
121
|
+
-- contexto previo (del prefijo común)
|
|
122
|
+
let start be when pre - ctx > 0 then pre - ctx otherwise 0
|
|
123
|
+
when start > 0
|
|
124
|
+
set lines to append(lines, sgr(color, "2", lpad("", width) + " ⋯"))
|
|
125
|
+
let k be start
|
|
126
|
+
while k < pre
|
|
127
|
+
set lines to append(lines, row(color, " ", text(k + 1), a[k], width))
|
|
128
|
+
set k to k + 1
|
|
129
|
+
-- medio: contexto interno acotado (ctx a cada lado de un cambio)
|
|
130
|
+
let last_change be -1
|
|
131
|
+
let idx be 0
|
|
132
|
+
each o in ops
|
|
133
|
+
when o["op"] != " "
|
|
134
|
+
set last_change to idx
|
|
135
|
+
set idx to idx + 1
|
|
136
|
+
set idx to 0
|
|
137
|
+
let skipping be false
|
|
138
|
+
each o in ops
|
|
139
|
+
let near be false
|
|
140
|
+
let w be idx - ctx
|
|
141
|
+
while w <= idx + ctx and not near
|
|
142
|
+
when w >= 0 and w < length(ops) and ops[w]["op"] != " "
|
|
143
|
+
set near to true
|
|
144
|
+
set w to w + 1
|
|
145
|
+
when o["op"] == " "
|
|
146
|
+
when near
|
|
147
|
+
set lines to append(lines, row(color, " ", text(pre + o["b"] + 1), mid_b[o["b"]], width))
|
|
148
|
+
set skipping to false
|
|
149
|
+
otherwise when not skipping
|
|
150
|
+
set lines to append(lines, sgr(color, "2", lpad("", width) + " ⋯"))
|
|
151
|
+
set skipping to true
|
|
152
|
+
otherwise when o["op"] == "-"
|
|
153
|
+
set lines to append(lines, row(color, "-", text(pre + o["a"] + 1), mid_a[o["a"]], width))
|
|
154
|
+
set skipping to false
|
|
155
|
+
otherwise
|
|
156
|
+
set lines to append(lines, row(color, "+", text(pre + o["b"] + 1), mid_b[o["b"]], width))
|
|
157
|
+
set skipping to false
|
|
158
|
+
set idx to idx + 1
|
|
159
|
+
-- contexto posterior (del sufijo común)
|
|
160
|
+
let after_start be length(b) - suf
|
|
161
|
+
let upto be when after_start + ctx < length(b) then after_start + ctx otherwise length(b)
|
|
162
|
+
set k to after_start
|
|
163
|
+
while k < upto
|
|
164
|
+
set lines to append(lines, row(color, " ", text(k + 1), b[k], width))
|
|
165
|
+
set k to k + 1
|
|
166
|
+
when upto < length(b)
|
|
167
|
+
set lines to append(lines, sgr(color, "2", lpad("", width) + " ⋯"))
|
|
168
|
+
give {"added": added, "removed": removed, "lines": lines}
|
package/lib/line.syn
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
-- lib/line.syn — editor de línea para la terminal (Synsema v0.6.11+: term_open/term_recv)
|
|
2
|
+
-- read(prompt, color, ctx) → texto (puede tener "\n"), INTERRUPT si ctx["idle"]() pidió cortar,
|
|
3
|
+
-- nothing en EOF. Sin TTY (pipe/CI) delega en ctx["fallback"](prompt).
|
|
4
|
+
-- choose(question, options, color) → índice elegido, -1 sin TTY (usar approve), nothing en EOF/Esc.
|
|
5
|
+
-- Teclas: ←→ Home End · ↑↓ historial o menú · Tab completa · Alt+Enter salto de línea · Enter envía
|
|
6
|
+
-- Ctrl+A/E inicio/fin · Ctrl+U borra la línea · Ctrl+W borra palabra · Ctrl+O = /out · Esc cierra el menú
|
|
7
|
+
-- Menú: al escribir "/" aparecen los comandos (primero los recientes); con "/cmd " los argumentos que
|
|
8
|
+
-- devuelva ctx["complete"](cmd, prefijo) (archivos, servers, flags…). Máx MENU_ROWS filas.
|
|
9
|
+
-- MIGA: dibujar SIEMPRE con term_write (print queda buffereado). El runtime restaura la terminal al cerrar.
|
|
10
|
+
|
|
11
|
+
use "./ui.syn" as ui
|
|
12
|
+
|
|
13
|
+
let ESC be decode(bytes("1b", "hex"))
|
|
14
|
+
let MENU_ROWS be 8
|
|
15
|
+
export let INTERRUPT be ESC + "interrupt"
|
|
16
|
+
|
|
17
|
+
task sgr(color, code, s)
|
|
18
|
+
when not color or s == ""
|
|
19
|
+
give s
|
|
20
|
+
give ESC + "[" + code + "m" + s + ESC + "[0m"
|
|
21
|
+
|
|
22
|
+
task vis(s)
|
|
23
|
+
give ui.width(s)
|
|
24
|
+
|
|
25
|
+
task rep(ch, n)
|
|
26
|
+
let out be ""
|
|
27
|
+
while length(out) < n
|
|
28
|
+
set out to out + ch
|
|
29
|
+
give out
|
|
30
|
+
|
|
31
|
+
task starts(s, p)
|
|
32
|
+
give starts_with(s, p)
|
|
33
|
+
|
|
34
|
+
-- ---------- candidatos ----------
|
|
35
|
+
|
|
36
|
+
-- buf = texto del editor. give {"kind": "cmd"|"arg"|"", "items": [{"label","fill","desc"}], "token": prefijo}
|
|
37
|
+
task candidates(buf, ctx)
|
|
38
|
+
when not starts(buf, "/") or contains(buf, "\n")
|
|
39
|
+
give {"kind": "", "items": [], "token": ""}
|
|
40
|
+
let sp be capture(buf, "^(/\S*)\s+(.*)$")
|
|
41
|
+
when sp == nothing
|
|
42
|
+
-- comando a medias: recientes primero cuando es "/" pelado
|
|
43
|
+
let items be []
|
|
44
|
+
let seen be {}
|
|
45
|
+
when buf == "/"
|
|
46
|
+
each r in ctx["recent"]
|
|
47
|
+
each c in ctx["commands"]
|
|
48
|
+
when c[0] == r and not contains(seen, r)
|
|
49
|
+
set seen[r] to true
|
|
50
|
+
set items to append(items, {"label": c[0], "fill": c[0] + (when c[1] != "" then " " otherwise ""), "args": c[1], "desc": c[2], "recent": true})
|
|
51
|
+
each c in ctx["commands"]
|
|
52
|
+
when starts(c[0], buf) and not contains(seen, c[0])
|
|
53
|
+
set seen[c[0]] to true
|
|
54
|
+
set items to append(items, {"label": c[0], "fill": c[0] + (when c[1] != "" then " " otherwise ""), "args": c[1], "desc": c[2], "recent": false})
|
|
55
|
+
give {"kind": "cmd", "items": items, "token": buf}
|
|
56
|
+
let cmd be sp[0]
|
|
57
|
+
let rest be sp[1]
|
|
58
|
+
-- último token (lo que se completa); lo anterior queda fijo
|
|
59
|
+
let toks be split(rest, " ")
|
|
60
|
+
let last be toks[length(toks) - 1]
|
|
61
|
+
let head be slice(rest, 0, length(rest) - length(last))
|
|
62
|
+
let items be []
|
|
63
|
+
let cands be ctx["complete"](cmd, head, last)
|
|
64
|
+
each c in cands
|
|
65
|
+
when starts(c, last)
|
|
66
|
+
set items to append(items, {"label": c, "fill": cmd + " " + head + c + (when ends_with(c, "/") then "" otherwise " "), "args": "", "desc": "", "recent": false})
|
|
67
|
+
give {"kind": "arg", "items": items, "token": last}
|
|
68
|
+
|
|
69
|
+
-- ---------- dibujo ----------
|
|
70
|
+
|
|
71
|
+
-- filas físicas que ocupa un texto de ancho w en una terminal de cols columnas
|
|
72
|
+
task rows_of(w, cols)
|
|
73
|
+
when cols <= 0
|
|
74
|
+
give 1
|
|
75
|
+
give floor(w / cols) + 1
|
|
76
|
+
|
|
77
|
+
-- dibuja prompt + buffer (multilínea) + menú; deja el cursor en su lugar. give filas totales dibujadas
|
|
78
|
+
-- (para poder volver al inicio en el próximo redraw)
|
|
79
|
+
task draw(h, st, color, ctx)
|
|
80
|
+
let size be term_size(h)
|
|
81
|
+
let cols be size["cols"]
|
|
82
|
+
let lines be split(st["buf"], "\n")
|
|
83
|
+
-- posición del cursor: línea y columna dentro del buffer
|
|
84
|
+
let cl be 0
|
|
85
|
+
let cc be st["cur"]
|
|
86
|
+
let i be 0
|
|
87
|
+
let found be false
|
|
88
|
+
while i < length(lines) and not found
|
|
89
|
+
when cc <= length(lines[i])
|
|
90
|
+
set cl to i
|
|
91
|
+
set found to true
|
|
92
|
+
otherwise
|
|
93
|
+
set cc to cc - length(lines[i]) - 1
|
|
94
|
+
set i to i + 1
|
|
95
|
+
let out be ""
|
|
96
|
+
-- volver al inicio de lo dibujado la vez anterior y limpiar hacia abajo
|
|
97
|
+
when st["cursor_row"] > 0
|
|
98
|
+
set out to out + ESC + "[" + text(st["cursor_row"]) + "A"
|
|
99
|
+
set out to out + "\r" + ESC + "[J"
|
|
100
|
+
let cont be sgr(color, "2", " … ")
|
|
101
|
+
let rows_before_cursor be 0
|
|
102
|
+
let total be 0
|
|
103
|
+
let cursor_col be 0
|
|
104
|
+
set i to 0
|
|
105
|
+
each l in lines
|
|
106
|
+
let pre be when i == 0 then st["prompt"] otherwise cont
|
|
107
|
+
set out to out + (when i > 0 then "\r\n" otherwise "") + pre + (when st["final"] and l != "" then sgr(color, "7", " " + l + " ") otherwise l)
|
|
108
|
+
let r be rows_of(vis(pre) + ui.width(l), cols)
|
|
109
|
+
when i < cl
|
|
110
|
+
set rows_before_cursor to rows_before_cursor + r
|
|
111
|
+
otherwise when i == cl
|
|
112
|
+
let ccw be ui.width(slice(l, 0, cc))
|
|
113
|
+
set rows_before_cursor to rows_before_cursor + floor((vis(pre) + ccw) / cols)
|
|
114
|
+
set cursor_col to (vis(pre) + ccw) % cols
|
|
115
|
+
set total to total + r
|
|
116
|
+
set i to i + 1
|
|
117
|
+
-- menú
|
|
118
|
+
let menu_rows be 0
|
|
119
|
+
when st["menu"] and length(st["items"]) > 0
|
|
120
|
+
let items be st["items"]
|
|
121
|
+
let from be 0
|
|
122
|
+
when st["sel"] >= MENU_ROWS
|
|
123
|
+
set from to st["sel"] - MENU_ROWS + 1
|
|
124
|
+
let k be from
|
|
125
|
+
while k < length(items) and k < from + MENU_ROWS
|
|
126
|
+
let it be items[k]
|
|
127
|
+
let row be ""
|
|
128
|
+
when st["kind"] == "cmd"
|
|
129
|
+
let full be it["label"] + (when it["args"] != "" then " " + it["args"] otherwise "")
|
|
130
|
+
let name be when length(full) > 34 then slice(full, 0, 33) + "…" otherwise full
|
|
131
|
+
let padn be name + rep(" ", 34 - length(name))
|
|
132
|
+
let desc be when length(it["desc"]) > cols - 40 then slice(it["desc"], 0, cols - 43) + "…" otherwise it["desc"]
|
|
133
|
+
set row to (when k == st["sel"] then sgr(color, "7", " " + padn + " ") otherwise " " + sgr(color, "1", it["label"]) + sgr(color, "2", slice(padn, length(it["label"]), length(padn)) + " ")) + (when it["recent"] then sgr(color, "36", "↺ ") otherwise " ") + sgr(color, "2", desc)
|
|
134
|
+
otherwise
|
|
135
|
+
set row to when k == st["sel"] then sgr(color, "7", " " + it["label"] + " ") otherwise " " + it["label"]
|
|
136
|
+
set out to out + "\r\n" + " " + row
|
|
137
|
+
set menu_rows to menu_rows + 1
|
|
138
|
+
set k to k + 1
|
|
139
|
+
when length(items) > MENU_ROWS
|
|
140
|
+
set out to out + "\r\n" + sgr(color, "2", " (" + text(length(items)) + " opciones · ↑↓ elegir · Tab completa · Esc cierra)")
|
|
141
|
+
set menu_rows to menu_rows + 1
|
|
142
|
+
-- recolocar el cursor: subir (filas debajo de la línea del cursor + menú), ir a la columna
|
|
143
|
+
let below be total - rows_before_cursor - 1 + menu_rows
|
|
144
|
+
when below > 0
|
|
145
|
+
set out to out + ESC + "[" + text(below) + "A"
|
|
146
|
+
set out to out + "\r"
|
|
147
|
+
when cursor_col > 0
|
|
148
|
+
set out to out + ESC + "[" + text(cursor_col) + "C"
|
|
149
|
+
term_write(h, out)
|
|
150
|
+
set st["cursor_row"] to rows_before_cursor
|
|
151
|
+
give st
|
|
152
|
+
|
|
153
|
+
-- ---------- edición ----------
|
|
154
|
+
|
|
155
|
+
task insert(st, s)
|
|
156
|
+
set st["buf"] to slice(st["buf"], 0, st["cur"]) + s + slice(st["buf"], st["cur"], length(st["buf"]))
|
|
157
|
+
set st["cur"] to st["cur"] + length(s)
|
|
158
|
+
give st
|
|
159
|
+
|
|
160
|
+
task refresh_menu(st, ctx)
|
|
161
|
+
let c be candidates(st["buf"], ctx)
|
|
162
|
+
set st["kind"] to c["kind"]
|
|
163
|
+
set st["items"] to c["items"]
|
|
164
|
+
set st["sel"] to 0
|
|
165
|
+
set st["nav"] to false
|
|
166
|
+
set st["menu"] to c["kind"] != "" and length(c["items"]) > 0 and not st["menu_off"]
|
|
167
|
+
give st
|
|
168
|
+
|
|
169
|
+
task word_start(buf, cur)
|
|
170
|
+
let i be cur
|
|
171
|
+
while i > 0 and slice(buf, i - 1, i) == " "
|
|
172
|
+
set i to i - 1
|
|
173
|
+
while i > 0 and slice(buf, i - 1, i) != " " and slice(buf, i - 1, i) != "\n"
|
|
174
|
+
set i to i - 1
|
|
175
|
+
give i
|
|
176
|
+
|
|
177
|
+
task accept(st)
|
|
178
|
+
let it be st["items"][st["sel"]]
|
|
179
|
+
set st["buf"] to it["fill"]
|
|
180
|
+
set st["cur"] to length(it["fill"])
|
|
181
|
+
set st["menu"] to false
|
|
182
|
+
give st
|
|
183
|
+
|
|
184
|
+
export task read(prompt, color, ctx)
|
|
185
|
+
let h be term_open({"ctrl_c": "exit"})
|
|
186
|
+
when h == nothing
|
|
187
|
+
give ctx["fallback"](prompt)
|
|
188
|
+
ui.set_cols(term_size(h)["cols"])
|
|
189
|
+
let st be {"buf": "", "cur": 0, "prompt": prompt, "cursor_row": 0, "menu": false, "menu_off": false, "kind": "", "items": [], "sel": 0, "nav": false, "final": false}
|
|
190
|
+
let hist be ctx["history"]
|
|
191
|
+
let hi be length(hist)
|
|
192
|
+
let draft be ""
|
|
193
|
+
let result be nothing
|
|
194
|
+
let done be false
|
|
195
|
+
term_write(h, "\r\n")
|
|
196
|
+
set st to draw(h, st, color, ctx)
|
|
197
|
+
while not done
|
|
198
|
+
let ev be term_recv(h, 1)
|
|
199
|
+
let dirty be true
|
|
200
|
+
when ev == nothing
|
|
201
|
+
set dirty to false
|
|
202
|
+
when ctx["idle"]()
|
|
203
|
+
set result to INTERRUPT
|
|
204
|
+
set done to true
|
|
205
|
+
otherwise when ev["type"] == "focus"
|
|
206
|
+
set dirty to false
|
|
207
|
+
otherwise when ev["type"] == "eof"
|
|
208
|
+
set result to nothing
|
|
209
|
+
set done to true
|
|
210
|
+
otherwise when ev["type"] == "paste"
|
|
211
|
+
set st to insert(st, replace_text(ev["text"], "\r", ""))
|
|
212
|
+
set st to refresh_menu(st, ctx)
|
|
213
|
+
otherwise when ev["type"] == "resize"
|
|
214
|
+
ui.set_cols(ev["cols"])
|
|
215
|
+
otherwise when ev["type"] == "key"
|
|
216
|
+
let k be ev["key"]
|
|
217
|
+
let changed be true
|
|
218
|
+
when k == "enter" and ev["alt"]
|
|
219
|
+
set st to insert(st, "\n")
|
|
220
|
+
otherwise when k == "enter"
|
|
221
|
+
when st["menu"] and st["kind"] == "cmd" and st["buf"] != st["items"][st["sel"]]["label"] and st["buf"] != st["items"][st["sel"]]["fill"]
|
|
222
|
+
set st to accept(st)
|
|
223
|
+
when st["items"][st["sel"]]["args"] == ""
|
|
224
|
+
set result to trim(st["buf"])
|
|
225
|
+
set done to true
|
|
226
|
+
otherwise when st["menu"] and st["kind"] == "arg" and st["nav"]
|
|
227
|
+
-- Enter solo completa el argumento si el humano navegó el menú; si no, envía tal cual
|
|
228
|
+
set st to accept(st)
|
|
229
|
+
otherwise
|
|
230
|
+
set result to st["buf"]
|
|
231
|
+
set done to true
|
|
232
|
+
otherwise when k == "tab" and st["menu"]
|
|
233
|
+
set st to accept(st)
|
|
234
|
+
otherwise when k == "escape"
|
|
235
|
+
when st["menu"]
|
|
236
|
+
set st["menu"] to false
|
|
237
|
+
set st["menu_off"] to true
|
|
238
|
+
set changed to false
|
|
239
|
+
otherwise when k == "up" and st["menu"]
|
|
240
|
+
set st["sel"] to (when st["sel"] > 0 then st["sel"] - 1 otherwise length(st["items"]) - 1)
|
|
241
|
+
set st["nav"] to true
|
|
242
|
+
set changed to false
|
|
243
|
+
otherwise when k == "down" and st["menu"]
|
|
244
|
+
set st["sel"] to (when st["sel"] < length(st["items"]) - 1 then st["sel"] + 1 otherwise 0)
|
|
245
|
+
set st["nav"] to true
|
|
246
|
+
set changed to false
|
|
247
|
+
otherwise when k == "up"
|
|
248
|
+
when hi > 0
|
|
249
|
+
when hi == length(hist)
|
|
250
|
+
set draft to st["buf"]
|
|
251
|
+
set hi to hi - 1
|
|
252
|
+
set st["buf"] to hist[hi]
|
|
253
|
+
set st["cur"] to length(st["buf"])
|
|
254
|
+
set changed to false
|
|
255
|
+
otherwise when k == "down"
|
|
256
|
+
when hi < length(hist)
|
|
257
|
+
set hi to hi + 1
|
|
258
|
+
set st["buf"] to (when hi == length(hist) then draft otherwise hist[hi])
|
|
259
|
+
set st["cur"] to length(st["buf"])
|
|
260
|
+
set changed to false
|
|
261
|
+
otherwise when k == "left"
|
|
262
|
+
when st["cur"] > 0
|
|
263
|
+
set st["cur"] to st["cur"] - 1
|
|
264
|
+
set changed to false
|
|
265
|
+
otherwise when k == "right"
|
|
266
|
+
when st["cur"] < length(st["buf"])
|
|
267
|
+
set st["cur"] to st["cur"] + 1
|
|
268
|
+
set changed to false
|
|
269
|
+
otherwise when k == "home" or (k == "char" and ev["ctrl"] and ev["text"] == "a")
|
|
270
|
+
set st["cur"] to 0
|
|
271
|
+
set changed to false
|
|
272
|
+
otherwise when k == "end" or (k == "char" and ev["ctrl"] and ev["text"] == "e")
|
|
273
|
+
set st["cur"] to length(st["buf"])
|
|
274
|
+
set changed to false
|
|
275
|
+
otherwise when k == "backspace"
|
|
276
|
+
when st["cur"] > 0
|
|
277
|
+
set st["buf"] to slice(st["buf"], 0, st["cur"] - 1) + slice(st["buf"], st["cur"], length(st["buf"]))
|
|
278
|
+
set st["cur"] to st["cur"] - 1
|
|
279
|
+
otherwise when k == "delete"
|
|
280
|
+
when st["cur"] < length(st["buf"])
|
|
281
|
+
set st["buf"] to slice(st["buf"], 0, st["cur"]) + slice(st["buf"], st["cur"] + 1, length(st["buf"]))
|
|
282
|
+
otherwise when k == "char" and ev["ctrl"] and ev["text"] == "u"
|
|
283
|
+
set st["buf"] to ""
|
|
284
|
+
set st["cur"] to 0
|
|
285
|
+
otherwise when k == "char" and ev["ctrl"] and ev["text"] == "w"
|
|
286
|
+
let ws be word_start(st["buf"], st["cur"])
|
|
287
|
+
set st["buf"] to slice(st["buf"], 0, ws) + slice(st["buf"], st["cur"], length(st["buf"]))
|
|
288
|
+
set st["cur"] to ws
|
|
289
|
+
otherwise when k == "char" and ev["ctrl"] and ev["text"] == "o"
|
|
290
|
+
set result to "/out"
|
|
291
|
+
set done to true
|
|
292
|
+
otherwise when k == "char" and ev["ctrl"] and ev["text"] == "l"
|
|
293
|
+
term_write(h, ESC + "[2J" + ESC + "[H")
|
|
294
|
+
set st["cursor_row"] to 0
|
|
295
|
+
set changed to false
|
|
296
|
+
otherwise when k == "char" and not ev["ctrl"]
|
|
297
|
+
set st to insert(st, ev["text"])
|
|
298
|
+
otherwise
|
|
299
|
+
set changed to false
|
|
300
|
+
when changed
|
|
301
|
+
set st["menu_off"] to false
|
|
302
|
+
set st to refresh_menu(st, ctx)
|
|
303
|
+
when not done and dirty
|
|
304
|
+
set st to draw(h, st, color, ctx)
|
|
305
|
+
-- dejar el prompt limpio (sin menú) en el scrollback y bajar a una línea nueva
|
|
306
|
+
set st["menu"] to false
|
|
307
|
+
set st["final"] to result != nothing and result != INTERRUPT and trim(st["buf"]) != ""
|
|
308
|
+
set st to draw(h, st, color, ctx)
|
|
309
|
+
let lines be split(st["buf"], "\n")
|
|
310
|
+
let size be term_size(h)
|
|
311
|
+
let tail be 0
|
|
312
|
+
let i be 0
|
|
313
|
+
each l in lines
|
|
314
|
+
let pre be (when i == 0 then vis(st["prompt"]) otherwise 4) + (when st["final"] then 2 otherwise 0)
|
|
315
|
+
when i > 0 or true
|
|
316
|
+
set tail to tail + rows_of(pre + length(l), size["cols"])
|
|
317
|
+
set i to i + 1
|
|
318
|
+
let down be tail - st["cursor_row"] - 1
|
|
319
|
+
term_write(h, (when down > 0 then ESC + "[" + text(down) + "B" otherwise "") + "\r\n")
|
|
320
|
+
term_close(h)
|
|
321
|
+
give result
|
|
322
|
+
|
|
323
|
+
-- menú vertical de opciones (aprobaciones): ↑↓ + Enter, o la letra inicial / número; Esc = nothing
|
|
324
|
+
export task choose(question, options, color)
|
|
325
|
+
let h be term_open({"ctrl_c": "exit"})
|
|
326
|
+
when h == nothing
|
|
327
|
+
give -1
|
|
328
|
+
let sel be 0
|
|
329
|
+
let done be false
|
|
330
|
+
let result be nothing
|
|
331
|
+
let drawn be 0
|
|
332
|
+
while not done
|
|
333
|
+
let out be ""
|
|
334
|
+
when drawn > 0
|
|
335
|
+
set out to out + ESC + "[" + text(drawn) + "A"
|
|
336
|
+
set out to out + "\r" + ESC + "[J" + question
|
|
337
|
+
let i be 0
|
|
338
|
+
each o in options
|
|
339
|
+
set out to out + "\r\n" + " " + (when i == sel then sgr(color, "7", " " + o + " ") otherwise " " + sgr(color, "2", o))
|
|
340
|
+
set i to i + 1
|
|
341
|
+
term_write(h, out)
|
|
342
|
+
set drawn to length(options)
|
|
343
|
+
let ev be term_recv(h, 600)
|
|
344
|
+
when ev == nothing
|
|
345
|
+
set result to nothing
|
|
346
|
+
set done to true
|
|
347
|
+
otherwise when ev["type"] == "eof"
|
|
348
|
+
set done to true
|
|
349
|
+
otherwise when ev["type"] == "key"
|
|
350
|
+
let k be ev["key"]
|
|
351
|
+
when k == "up"
|
|
352
|
+
set sel to (when sel > 0 then sel - 1 otherwise length(options) - 1)
|
|
353
|
+
otherwise when k == "down" or k == "tab"
|
|
354
|
+
set sel to (when sel < length(options) - 1 then sel + 1 otherwise 0)
|
|
355
|
+
otherwise when k == "enter"
|
|
356
|
+
set result to sel
|
|
357
|
+
set done to true
|
|
358
|
+
otherwise when k == "escape"
|
|
359
|
+
set done to true
|
|
360
|
+
otherwise when k == "char" and not ev["ctrl"]
|
|
361
|
+
let t be lower(ev["text"])
|
|
362
|
+
let j be 0
|
|
363
|
+
each o in options
|
|
364
|
+
when t == text(j + 1) or t == lower(slice(o, 0, 1))
|
|
365
|
+
set result to j
|
|
366
|
+
set done to true
|
|
367
|
+
set j to j + 1
|
|
368
|
+
term_write(h, "\r\n")
|
|
369
|
+
term_close(h)
|
|
370
|
+
give result
|
package/lib/loop.syn
CHANGED
|
@@ -55,7 +55,7 @@ export task explore_cap()
|
|
|
55
55
|
let e be env("LAMPSON_EXPLORE_CAP", "")
|
|
56
56
|
when e != ""
|
|
57
57
|
give floor(number(e))
|
|
58
|
-
give
|
|
58
|
+
give 24
|
|
59
59
|
|
|
60
60
|
-- {streak, note, refuse}: nuevo streak tras esta llamada y qué hacer con ella
|
|
61
61
|
export task explore_verdict(streak, name, cap)
|
|
@@ -100,6 +100,9 @@ export task spill(name, id, out)
|
|
|
100
100
|
require file(".lampson/*")
|
|
101
101
|
when name == "read" or length(out) <= SPILL_CAP
|
|
102
102
|
give out
|
|
103
|
+
-- el informe de los subagentes es el entregable: no se manda a disco (hasta 60k)
|
|
104
|
+
when name == "delegate" and length(out) <= SPILL_CAP * 6
|
|
105
|
+
give out
|
|
103
106
|
let path be SPILL_DIR + "/" + id + ".txt"
|
|
104
107
|
write_file(path, out)
|
|
105
108
|
let half be floor(SPILL_CAP / 2)
|
|
@@ -276,17 +279,21 @@ task execute(tc, opts, on_event)
|
|
|
276
279
|
when not approved
|
|
277
280
|
emit(on_event, "tool_denied", {"call": tc, "reason": "user declined (" + verdict["reason"] + ")"}, tag)
|
|
278
281
|
give "DENIED by the user (" + verdict["reason"] + "). Do not retry this exact action; ask the user or propose an alternative."
|
|
282
|
+
emit(on_event, "busy", {"label": permission.describe_call(name, args), "kind": "tool", "name": name}, tag)
|
|
283
|
+
let out be ""
|
|
279
284
|
try
|
|
280
285
|
-- tools MCP: no son tasks Synsema (args libres); el registry las marca con "mcp"
|
|
281
286
|
when registry[name] == "mcp"
|
|
282
|
-
|
|
287
|
+
set out to text(mcp.call(name, args))
|
|
283
288
|
-- tools de lámparas: un proceso hijo por llamada (lib/lamps.syn)
|
|
284
|
-
when registry[name] == "lamp"
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
289
|
+
otherwise when registry[name] == "lamp"
|
|
290
|
+
set out to text(lamps.call(name, args))
|
|
291
|
+
otherwise
|
|
292
|
+
set out to text(call_tool(registry[name], args))
|
|
288
293
|
recover err
|
|
289
|
-
|
|
294
|
+
set out to "ERROR: " + err
|
|
295
|
+
emit(on_event, "idle", nothing, tag)
|
|
296
|
+
give out
|
|
290
297
|
|
|
291
298
|
export task run_turn(cfg, messages, opts, on_event)
|
|
292
299
|
require net
|
|
@@ -353,7 +360,9 @@ export task run_turn(cfg, messages, opts, on_event)
|
|
|
353
360
|
when last_step
|
|
354
361
|
let why be when over_budget then "Token budget for this turn is exhausted (" + text(spent) + " of " + text(floor(budget)) + " tokens)" otherwise "Step limit reached for this turn (" + text(max_steps) + " tool calls)"
|
|
355
362
|
set msgs to append(msgs, {"role": "user", "content": "[harness] " + why + ". Do NOT call tools now. Reply with a short status: what you completed, what is verified, and exactly what remains to do next. The user can say 'continue' to resume."})
|
|
363
|
+
emit(on_event, "busy", {"label": "pensando", "kind": "llm"}, tag)
|
|
356
364
|
let r be provider.chat_retry(cfg, msgs, cat, 3)
|
|
365
|
+
emit(on_event, "idle", nothing, tag)
|
|
357
366
|
when r["error"] != nothing
|
|
358
367
|
emit(on_event, "error", r["error"], tag)
|
|
359
368
|
set stopped to "error: " + r["error"]
|
|
@@ -406,16 +415,21 @@ export task run_turn(cfg, messages, opts, on_event)
|
|
|
406
415
|
when ex["note"] != ""
|
|
407
416
|
set out to out + ex["note"]
|
|
408
417
|
emit(on_event, "inbox", "tope de exploración a la mitad (" + text(explore_streak) + "/" + text(cap) + " lecturas seguidas sin actuar): aviso al modelo", tag)
|
|
409
|
-
|
|
418
|
+
-- solo cuentan los errores REALES de la tool; los rechazos del harness (tope de lecturas,
|
|
419
|
+
-- repeticiones) ya castigan con el rechazo y no deben además cortar el turno
|
|
420
|
+
when (starts_with(out, "ERROR") or starts_with(out, "DENIED")) and not starts_with(out, "ERROR: exploration cap") and not starts_with(out, "ERROR: this turn already made") and not starts_with(out, "ERROR: you already called")
|
|
410
421
|
set errors to errors + 1
|
|
411
422
|
emit(on_event, "tool_result", {"call": tc, "output": out}, tag)
|
|
412
423
|
set msgs to append(msgs, tool_result_msg(tc, out))
|
|
413
424
|
when errors >= MAX_ERRORS_PER_TURN
|
|
414
425
|
set msgs to append(msgs, {"role": "user", "content": "[harness] Too many tool errors this turn. Stop calling tools and report what happened."})
|
|
426
|
+
emit(on_event, "busy", {"label": "pensando", "kind": "llm"}, tag)
|
|
415
427
|
let fin be provider.chat_retry(cfg, msgs, [], 2)
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
428
|
+
emit(on_event, "idle", nothing, tag)
|
|
429
|
+
let fin_text be strip_raw_tool_calls(fin["text"])
|
|
430
|
+
set msgs to append(msgs, {"role": "assistant", "content": fin_text, "tool_calls": []})
|
|
431
|
+
emit(on_event, "assistant", fin_text, tag)
|
|
432
|
+
give {"messages": msgs, "text": fin_text, "steps": steps, "usage": usage, "stopped": "too_many_errors"}
|
|
419
433
|
set stopped to when spent > budget then "budget" otherwise "max_steps"
|
|
420
434
|
give {"messages": msgs, "text": final_text, "steps": steps, "usage": usage, "stopped": stopped}
|
|
421
435
|
|