lampson 0.2.6 → 0.2.7

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 CHANGED
@@ -60,15 +60,15 @@ It can only touch the folder you opened it in — not your home directory, not t
60
60
  a setting: it's how the language it's written in works. Reading and editing just happen; deleting, installing,
61
61
  `git reset`, `sudo` stop and wait for your *yes*; truly destructive commands are refused in every mode.
62
62
 
63
- ## Lamps
63
+ ## Plugins
64
64
 
65
- A **lamp** is a small folder that gives Lampson a new tool: query your database, call your company's API,
65
+ A **plugin** is a small folder that gives Lampson a new tool: query your database, call your company's API,
66
66
  deploy, send a message. Any language. **Off by default** — you turn each one on, and only then can the agent
67
67
  use it.
68
68
 
69
69
  ```
70
- ~/lampson/lamps/postgres/
71
- lamp.json ← what it's called, what it does, which tools it offers
70
+ ~/lampson/plugins/postgres/
71
+ plugin.json ← what it's called, what it does, which tools it offers
72
72
  query.py ← the code
73
73
  ```
74
74
 
@@ -79,12 +79,17 @@ use it.
79
79
  "parameters": {"type": "object", "properties": {"sql": {"type": "string"}}}, "readonly": true}]}
80
80
  ```
81
81
 
82
- The script gets the call in environment variables (`LAMP_TOOL`, `LAMP_ARGS` as JSON) and prints the result.
83
- Turn it on with `/lamps on postgres` or the **lámparas** pill in the web UI. Keep lamps global (every project)
84
- or inside a project (`.lampson/lamps/` — commit it and your team has it). The agent can *write* a lamp for you;
82
+ The script gets the call in environment variables (`PLUGIN_TOOL`, `PLUGIN_ARGS` as JSON) and prints the result.
83
+ Turn it on with `/plugins on postgres` or the **plugins** pill in the web UI. Keep plugins global (every project)
84
+ or inside a project (`.lampson/plugins/` — commit it and your team has it). The agent can *write* a plugin for you;
85
85
  turning it on is always yours.
86
86
 
87
- → [How lamps work](https://lampson.org/docs/lamps) · the example lamp ships in `lamps/example-hello/`.
87
+ → [How plugins work](https://lampson.org/docs/plugins) · the example ships in `plugins/example-hello/`.
88
+
89
+ Plugins are yours and local: any language, no sandbox unless you write them in Synsema. For tools with an
90
+ **enforced capability ceiling**, versioned and shared with any agent (Claude Code, Cursor, Lampson…), use
91
+ [lamps.sh](https://lamps.sh): `lamp add <ref>`, then `lamp mcp` as an MCP server in Lampson. (Until 0.2.6
92
+ plugins were called *lamps*; old `.lampson/lamps/` folders and `LAMP_*` variables still work for now.)
88
93
 
89
94
  ## Also in the box
90
95
 
@@ -94,7 +99,7 @@ memory it reads back next session · sessions with a readable trace of every ste
94
99
 
95
100
  ## Learn more
96
101
 
97
- - **[lampson.org/docs](https://lampson.org/docs)** — quickstart, permissions, providers, lamps, schedules…
102
+ - **[lampson.org/docs](https://lampson.org/docs)** — quickstart, permissions, providers, plugins, schedules…
98
103
  - **[guide.md](guide.md)** — the long version: how it runs, architecture, every knob, runtime notes.
99
104
 
100
105
  ## License
package/bin/lampson.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // bin/lampson.js — entry point of the npm package (`npm i -g lampson`).
3
3
  //
4
4
  // The code ships inside node_modules, but Lampson keeps STATE next to its code: the mounted `workspace`
5
- // junction, `.lampson/` (config, sessions, traces, spill), `memory/` and global `lamps/`. Living inside
5
+ // junction, `.lampson/` (config, sessions, traces, spill), `memory/` and global `plugins/`. Living inside
6
6
  // node_modules would lose all of that on every `npm i -g lampson@latest`. So this launcher keeps a stable
7
7
  // home (LAMPSON_HOME, default ~/lampson), syncs the package's code files into it when the version changes,
8
8
  // and runs the same launcher the git install uses (lampson.ps1 / lampson.sh). Everything else — synsema on
@@ -45,6 +45,9 @@ function sync() {
45
45
  try { installed = fs.readFileSync(marker, 'utf8').trim(); } catch (e) { /* first run */ }
46
46
  if (installed === pkg.version && fs.existsSync(path.join(home, 'chat.syn'))) return 'ok';
47
47
  fs.mkdirSync(home, { recursive: true });
48
+ // the folders every workspace links to (lib/workspaces.syn LINKS): if `memory/` does not exist when the
49
+ // link is made, the link is born dangling and memory(write) fails with "No such file or directory"
50
+ for (const d of ['memory', 'plugins']) fs.mkdirSync(path.join(home, d), { recursive: true });
48
51
  for (const item of CODE) {
49
52
  const src = path.join(pkgDir, item);
50
53
  if (!fs.existsSync(src)) continue;
@@ -52,11 +55,14 @@ function sync() {
52
55
  if (fs.statSync(src).isDirectory()) { fs.rmSync(dst, { recursive: true, force: true }); copyDir(src, dst); }
53
56
  else copyFile(src, dst);
54
57
  }
55
- // global lamps are user content: seed the example once, never overwrite what the user put there
56
- const lampsSrc = path.join(pkgDir, 'lamps'), lampsDst = path.join(home, 'lamps');
57
- if (fs.existsSync(lampsSrc)) {
58
- for (const e of fs.readdirSync(lampsSrc, { withFileTypes: true })) {
59
- if (e.isDirectory() && !fs.existsSync(path.join(lampsDst, e.name))) copyDir(path.join(lampsSrc, e.name), path.join(lampsDst, e.name));
58
+ // global plugins are user content: seed the example once, never overwrite what the user put there.
59
+ // Until 2026-08 the folder was `lamps/` (they were "lamps"): an existing one is renamed once, same content.
60
+ const legacyDst = path.join(home, 'lamps'), pluginsDst = path.join(home, 'plugins');
61
+ if (fs.existsSync(legacyDst) && !fs.existsSync(pluginsDst)) { fs.renameSync(legacyDst, pluginsDst); console.log('lampson: renamed ' + legacyDst + ' -> ' + pluginsDst + ' (lamps are now called plugins)'); }
62
+ const pluginsSrc = path.join(pkgDir, 'plugins');
63
+ if (fs.existsSync(pluginsSrc)) {
64
+ for (const e of fs.readdirSync(pluginsSrc, { withFileTypes: true })) {
65
+ if (e.isDirectory() && !fs.existsSync(path.join(pluginsDst, e.name))) copyDir(path.join(pluginsSrc, e.name), path.join(pluginsDst, e.name));
60
66
  }
61
67
  }
62
68
  fs.writeFileSync(marker, pkg.version + '\n');
@@ -68,7 +74,7 @@ if (args.some(a => /^--?(version|v)$/i.test(a))) { console.log('lampson ' + pkg.
68
74
 
69
75
  let state;
70
76
  try { state = sync(); } catch (e) { console.error('lampson: could not prepare ' + home + ': ' + e.message); process.exit(1); }
71
- if (state === 'installed') console.log('lampson ' + pkg.version + ' → ' + home + ' (your config, sessions and lamps live there)');
77
+ if (state === 'installed') console.log('lampson ' + pkg.version + ' → ' + home + ' (your config, sessions and plugins live there)');
72
78
  if (state === 'updated') console.log('lampson updated to ' + pkg.version + ' in ' + home);
73
79
 
74
80
  // --update under npm: the code comes from the registry, not from git
package/chat.syn CHANGED
@@ -22,8 +22,8 @@ require file("workspace")
22
22
  require file("workspace/*")
23
23
  require file.read("skills")
24
24
  require file.read("skills/*")
25
- require file.read("lamps")
26
- require file.read("lamps/*")
25
+ require file.read("plugins")
26
+ require file.read("plugins/*")
27
27
  require file("memory")
28
28
  require file("memory/*")
29
29
  require file(".lampson")
@@ -49,7 +49,7 @@ use "./lib/diff.syn" as diff
49
49
  use "./lib/line.syn" as ed
50
50
  use "./lib/ui.syn" as ui
51
51
  use "./lib/mcp.syn" as mcp
52
- use "./lib/lamps.syn" as lamps
52
+ use "./lib/plugins.syn" as plugins
53
53
  use "./lib/lsp.syn" as lsp
54
54
  use "./lib/tools/todo.syn" as todo
55
55
  use "./lib/schedule.syn" as schedule
@@ -348,9 +348,9 @@ let COMMANDS be [
348
348
  ["/agents", "", "subagentes lanzados con delegate: estado, pasos y log de cada uno"],
349
349
  ["/todo", "", "lista de tareas del agente en esta sesión (la mantiene con la tool todo)"],
350
350
  ["/mcp", "[add <nombre> <comando…> [--project] | remove <nombre>]", "servers MCP: listar, conectar o quitar (global: lampson/.lampson/mcp.json · proyecto: .lampson/mcp.json)"],
351
- ["/lamps", "[on <nombre> | off <nombre> | run <lámpara> <tool> [json] | remove <nombre>]", "lámparas (plugins de tools): listar, encender o apagar (global: lampson/lamps/ · proyecto: .lampson/lamps/)"],
351
+ ["/plugins", "[on <nombre> | off <nombre> | run <plugin> <tool> [json] | remove <nombre>]", "plugins (tools propias, en cualquier lenguaje): listar, encender o apagar (global: lampson/plugins/ · proyecto: .lampson/plugins/)"],
352
352
  ["/lsp", "[add <typescript|python|rust|go|css|html> [--project] | add <nombre> <comando…> --ext .x=lang | remove <nombre>]", "language servers (navegación semántica: symbols/definition/references/hover); arrancan en la primera consulta"],
353
- ["/schedule", "[add <json> | run <id> | on <id> | off <id> | remove <id> | log <id>]", "tareas programadas (cada 6h, todos los días a las 9…): lámpara, comando o corrida del agente; corren mientras lampson esté abierto (o con lampson --daemon start, sin nada abierto)"],
353
+ ["/schedule", "[add <json> | run <id> | on <id> | off <id> | remove <id> | log <id>]", "tareas programadas (cada 6h, todos los días a las 9…): plugin, comando o corrida del agente; corren mientras lampson esté abierto (o con lampson --daemon start, sin nada abierto)"],
354
354
  ["/approve", "<id> [yes|no]", "responder una aprobación pendiente de una tarea programada que corre en background (sin yes|no la pregunta con el menú ↑↓)"],
355
355
  ["/out", "[n]", "resultado completo de la última tool del turno (n = contar hacia atrás: /out 2 es la anteúltima)"],
356
356
  ["/verbose", "", "alternar: mostrar SIEMPRE el output completo de cada tool (queda guardado en config.json)"],
@@ -393,10 +393,10 @@ task complete_args(cmd, head, last)
393
393
  give []
394
394
  when cmd == "/memory"
395
395
  give apply((m) => m["name"], memo.list())
396
- when cmd == "/lamps"
396
+ when cmd == "/plugins"
397
397
  when first
398
398
  give ["on", "off", "run", "remove"]
399
- give apply((l) => l["name"], lamps.summary())
399
+ give apply((l) => l["name"], plugins.summary())
400
400
  when cmd == "/mcp"
401
401
  when first
402
402
  give ["add", "remove"]
@@ -565,21 +565,21 @@ task banner(ws, cfg, profile, mode, sid)
565
565
  print(" workspace " + ws)
566
566
  print(" agente " + profile + " permisos " + mode + " modelo " + cfg["model"])
567
567
  print(" sesión " + sid + " " + git.summary())
568
- -- extensiones en una línea (detalle con /lamps, /mcp, /lsp): encendidas/total
569
- let ls be lamps.summary()
570
- let lamps_on be length(where(ls, (l) => l["enabled"]))
568
+ -- extensiones en una línea (detalle con /plugins, /mcp, /lsp): encendidas/total
569
+ let ls be plugins.summary()
570
+ let plugins_on be length(where(ls, (l) => l["enabled"]))
571
571
  let ms be mcp.summary()
572
572
  let mcp_on be length(where(ms, (m) => m["status"] == "ready"))
573
573
  let ss be lsp.summary()
574
574
  let ext be []
575
575
  when length(ls) > 0
576
- set ext to append(ext, (when lamps_on > 0 then green("lamps " + text(lamps_on) + "/" + text(length(ls))) otherwise dim("lamps 0/" + text(length(ls)))))
576
+ set ext to append(ext, (when plugins_on > 0 then green("plugins " + text(plugins_on) + "/" + text(length(ls))) otherwise dim("plugins 0/" + text(length(ls)))))
577
577
  when length(ms) > 0
578
578
  set ext to append(ext, (when mcp_on == length(ms) then green("mcp " + text(mcp_on) + "/" + text(length(ms))) otherwise red("mcp " + text(mcp_on) + "/" + text(length(ms)))))
579
579
  when length(ss) > 0
580
580
  set ext to append(ext, dim("lsp " + text(length(ss))))
581
581
  when length(ext) > 0
582
- print(" extras " + join(ext, dim(" · ")) + dim(" (/lamps /mcp /lsp)"))
582
+ print(" extras " + join(ext, dim(" · ")) + dim(" (/plugins /mcp /lsp)"))
583
583
  let upd be update.line()
584
584
  when upd != ""
585
585
  print(" " + yellow("⬆ " + upd) + dim(" (o /update acá)"))
@@ -815,8 +815,8 @@ agent Sched
815
815
  require file("workspace/*")
816
816
  require file.read("skills")
817
817
  require file.read("skills/*")
818
- require file.read("lamps")
819
- require file.read("lamps/*")
818
+ require file.read("plugins")
819
+ require file.read("plugins/*")
820
820
  require file("memory")
821
821
  require file("memory/*")
822
822
  require file(".lampson")
@@ -902,13 +902,13 @@ let system_msg be system_for(profile)
902
902
  -- servers MCP (globales en .lampson/mcp.json, del proyecto en workspace/.lampson/mcp.json): arrancan antes
903
903
  -- de armar el catálogo de tools; hasta 8 s de espera a que estén listos
904
904
  let mcp_servers be mcp.start_all(8)
905
- -- mcp / lsp / lámparas van resumidos en el banner ("extras"); acá solo se avisa lo que está ROTO
905
+ -- mcp / lsp / plugins van resumidos en el banner ("extras"); acá solo se avisa lo que está ROTO
906
906
  each ms in mcp.summary()
907
907
  when ms["status"] != "ready"
908
908
  print(" " + red("○ mcp " + ms["name"] + " " + ms["status"]) + dim(when ms["error"] != nothing then " · " + text(ms["error"]) otherwise ""))
909
- each l in lamps.summary()
909
+ each l in plugins.summary()
910
910
  when l["error"] != nothing
911
- print(" " + red("○ lamp " + l["name"] + " rota") + dim(" · " + text(l["error"])))
911
+ print(" " + red("○ plugin " + l["name"] + " roto") + dim(" · " + text(l["error"])))
912
912
  let opts be opts_for(profile, lower(env("LAMPSON_PERMISSION", "ask")))
913
913
 
914
914
  -- marca de corrida (blackboard): session.save la estampa en meta.run; reanudar una sesión guardada por
@@ -1181,7 +1181,7 @@ while running
1181
1181
  let sjson be trim(slice(srest, 3, length(srest)))
1182
1182
  when sjson == ""
1183
1183
  print(" uso: /schedule add {\"name\": \"…\", \"when\": \"daily 09:00\", \"action\": {\"type\": \"bash\", \"command\": \"npm test\"}, \"permission\": \"ask\"}")
1184
- print(" " + dim("when: every 6h · daily 09:00 · mon,wed 08:30 · weekdays 09:00 action.type: lamp {lamp, tool, args} · bash {command} · prompt {prompt, agent}"))
1184
+ print(" " + dim("when: every 6h · daily 09:00 · mon,wed 08:30 · weekdays 09:00 action.type: plugin {plugin, tool, args} · bash {command} · prompt {prompt, agent}"))
1185
1185
  print(" " + dim("o pedíselo al agente en lenguaje natural: «todos los días a las 9 revisá los tests y avisame»"))
1186
1186
  otherwise
1187
1187
  try
@@ -1249,13 +1249,17 @@ while running
1249
1249
  print(" " + (when yes then green("✓ permitido ") otherwise red("✗ denegado ")) + atoks[0])
1250
1250
  otherwise
1251
1251
  print(" " + red("no hay una aprobación pendiente con id " + atoks[0]))
1252
- otherwise when input == "/lamps" or starts_with(input, "/lamps ")
1253
- let lrest be trim(slice(input, 6, length(input)))
1252
+ otherwise when input == "/plugins" or starts_with(input, "/plugins ") or input == "/lamps" or starts_with(input, "/lamps ")
1253
+ -- /lamps = el nombre viejo (hasta 2026-09): sigue andando, con aviso
1254
+ let is_old be starts_with(input, "/lamps")
1255
+ when is_old
1256
+ print(" " + dim("/lamps ahora es /plugins (las lámparas de lampson pasaron a llamarse plugins)"))
1257
+ let lrest be trim(slice(input, when is_old then 6 otherwise 8, length(input)))
1254
1258
  when starts_with(lrest, "run ")
1255
- -- /lamps run <lámpara> <tool> [json de args] → la corre el usuario, sin el modelo
1259
+ -- /plugins run <plugin> <tool> [json de args] → la corre el usuario, sin el modelo
1256
1260
  let rt be where(split(trim(slice(lrest, 4, length(lrest))), " "), (x) => x != "")
1257
1261
  when length(rt) < 2
1258
- print(" uso: /lamps run <lámpara> <tool> [{\"arg\": \"valor\"}]")
1262
+ print(" uso: /plugins run <plugin> <tool> [{\"arg\": \"valor\"}]")
1259
1263
  otherwise
1260
1264
  let rargs be {}
1261
1265
  when length(rt) > 2
@@ -1264,30 +1268,31 @@ while running
1264
1268
  recover err
1265
1269
  print(" " + red("args: JSON inválido"))
1266
1270
  try
1267
- print(lamps.call(lamps.tool_name(rt[0], rt[1]), rargs))
1271
+ print(plugins.call(plugins.tool_name(rt[0], rt[1]), rargs))
1268
1272
  recover err
1269
1273
  print(" " + red(text(err)))
1270
1274
  otherwise when starts_with(lrest, "remove ")
1271
1275
  try
1272
- print(" " + lamps.remove(trim(slice(lrest, 7, length(lrest)))))
1276
+ print(" " + plugins.remove(trim(slice(lrest, 7, length(lrest)))))
1273
1277
  recover err
1274
1278
  print(" " + red(text(err)))
1275
1279
  set opts to opts_for(profile, mode)
1276
1280
  otherwise when starts_with(lrest, "on ") or starts_with(lrest, "off ")
1277
1281
  let lon be starts_with(lrest, "on ")
1278
1282
  try
1279
- print(" " + lamps.set_enabled(trim(slice(lrest, when lon then 3 otherwise 4, length(lrest))), lon))
1283
+ print(" " + plugins.set_enabled(trim(slice(lrest, when lon then 3 otherwise 4, length(lrest))), lon))
1280
1284
  recover err
1281
1285
  print(" " + red(text(err)))
1282
1286
  set opts to opts_for(profile, mode)
1283
1287
  otherwise
1284
- let ls be lamps.summary()
1288
+ let ls be plugins.summary()
1285
1289
  when length(ls) == 0
1286
- print(" sin lámparas. Una lámpara es una carpeta con lamp.json: " + dim("lampson\\lamps\\<nombre>\\ (global) · .lampson\\lamps\\<nombre>\\ del repo (proyecto)"))
1287
- print(" " + dim("el agente también puede crearlas (write en .lampson/lamps/) y pedirte encenderlas"))
1290
+ print(" sin plugins. Un plugin es una carpeta con plugin.json: " + dim("lampson\\plugins\\<nombre>\\ (global) · .lampson\\plugins\\<nombre>\\ del repo (proyecto)"))
1291
+ print(" " + dim("el agente también puede crearlos (write en .lampson/plugins/) y pedirte encenderlos"))
1288
1292
  each l in ls
1289
- print(" " + (when l["enabled"] then green("● ") otherwise dim("○ ")) + pad(l["name"], 14) + dim(pad(l["scope"], 8) + pad(l["kind"], 5)) + (when l["error"] != nothing then red("rota: " + text(l["error"])) otherwise dim(text(length(l["tools"])) + " tools [" + join(l["tools"], ", ") + "]" + (when l["kind"] == "syn" then " techo: " + l["caps"] otherwise " $ " + l["command"]))))
1290
- print(" " + dim("encender/apagar: /lamps on <nombre> · /lamps off <nombre> · correr una vos: /lamps run <lámpara> <tool> [json]"))
1293
+ print(" " + (when l["enabled"] then green("● ") otherwise dim("○ ")) + pad(l["name"], 14) + dim(pad(l["scope"], 8) + pad(l["kind"], 5)) + (when l["error"] != nothing then red("roto: " + text(l["error"])) otherwise dim(text(length(l["tools"])) + " tools [" + join(l["tools"], ", ") + "]" + (when l["kind"] == "syn" then " techo: " + l["caps"] otherwise " $ " + l["command"]))) + (when l["legacy"] then yellow(" carpeta vieja .lampson/lamps/ → renombrala a .lampson/plugins/") otherwise ""))
1294
+ print(" " + dim("encender/apagar: /plugins on <nombre> · /plugins off <nombre> · correr uno vos: /plugins run <plugin> <tool> [json]"))
1295
+ print(" " + dim("¿herramientas con techo real, versionadas y para cualquier agente? eso son las lámparas de lamps.sh: lamp add <ref> y entran por /mcp add lamps lamp mcp"))
1291
1296
  otherwise when input == "/mcp" or starts_with(input, "/mcp ")
1292
1297
  let mrest be trim(slice(input, 4, length(input)))
1293
1298
  when starts_with(mrest, "add ")
package/lampson.ps1 CHANGED
@@ -8,7 +8,7 @@
8
8
  # lampson --agent plan · --yolo|--strict|--ask · --update · --help
9
9
  #
10
10
  # Cómo funciona (ver SPEC-WORKSPACES.md): .lampson\ws\<slug>\ es el cwd del proceso del workspace, con una junction
11
- # `workspace` al proyecto y junctions a lib/public/skills/lamps/memory de esta instalación. El registro y los procesos
11
+ # `workspace` al proyecto y junctions a lib/public/skills/plugins/memory de esta instalación. El registro y los procesos
12
12
  # los maneja lib/workspaces.syn vía cli.syn; este script solo resuelve la carpeta, llama a cli.syn y abre lo pedido.
13
13
  $ErrorActionPreference = "Stop"
14
14
  $here = Split-Path -Parent $MyInvocation.MyCommand.Path
@@ -45,6 +45,16 @@ foreach ($k in $skillMounts.Keys) {
45
45
  $link = Join-Path $here ".lampson\$k"; $src = $skillMounts[$k]
46
46
  if (-not (Test-Path -LiteralPath $link) -and (Test-Path -LiteralPath $src -PathType Container)) { New-Item -ItemType Junction -Path $link -Target $src | Out-Null }
47
47
  }
48
+ # 2026-09: las "lámparas" globales pasaron a llamarse plugins (plugins\). git renombra las del repo; las carpetas que
49
+ # vos pusiste en lamps\ se mueven una vez, y la carpeta vieja se borra solo si quedó vacía.
50
+ $oldLamps = Join-Path $here "lamps"
51
+ if (Test-Path -LiteralPath $oldLamps -PathType Container) {
52
+ $newPlugins = Join-Path $here "plugins"; New-Item -ItemType Directory -Force $newPlugins | Out-Null
53
+ foreach ($d in Get-ChildItem -LiteralPath $oldLamps -Directory -Force) {
54
+ if (-not (Test-Path -LiteralPath (Join-Path $newPlugins $d.Name))) { Move-Item -LiteralPath $d.FullName -Destination $newPlugins; Write-Host "lampson: lamps\$($d.Name) → plugins\$($d.Name) (las lámparas ahora son plugins)" }
55
+ }
56
+ if (-not (Get-ChildItem -LiteralPath $oldLamps -Force)) { Remove-Item -LiteralPath $oldLamps -Force }
57
+ }
48
58
 
49
59
  function Invoke-Cli([string]$cmd, [string]$ws) {
50
60
  # cli.syn imprime la respuesta como la ÚLTIMA línea (JSON)
package/lampson.sh CHANGED
@@ -32,6 +32,13 @@ for pair in "skills-global:$HOME/.agents/skills" "skills-claude:$HOME/.claude/sk
32
32
  link="$here/.lampson/${pair%%:*}"; src="${pair#*:}"
33
33
  [ -e "$link" ] || { [ -d "$src" ] && ln -s "$src" "$link"; } || true
34
34
  done
35
+ # 2026-09: las "lámparas" globales pasaron a llamarse plugins (plugins/). git renombra las del repo; las carpetas
36
+ # que vos pusiste en lamps/ se mueven una vez, y la carpeta vieja se borra solo si quedó vacía.
37
+ if [ -d "$here/lamps" ]; then
38
+ mkdir -p "$here/plugins"
39
+ for d in "$here"/lamps/*/; do [ -d "$d" ] || continue; n="$(basename "$d")"; [ -e "$here/plugins/$n" ] || { mv "$d" "$here/plugins/$n" && echo "lampson: lamps/$n → plugins/$n (las lámparas ahora son plugins)"; }; done
40
+ rmdir "$here/lamps" 2>/dev/null || true
41
+ fi
35
42
  cli() { # última línea = JSON
36
43
  local out; out="$(cd "$here" && LAMPSON_CMD="$1" LAMPSON_WORKSPACE="${2:-}" synsema run cli.syn)" || { echo "$out" >&2; exit 1; }
37
44
  echo "$out" | sed '$d' >&2 || true
package/lib/agents.syn CHANGED
@@ -32,7 +32,7 @@ use "./loop.syn" as loop
32
32
  use "./provider.syn" as provider
33
33
  use "./prompt.syn" as prompt
34
34
  use "./mcp.syn" as mcp
35
- use "./lamps.syn" as lamps
35
+ use "./plugins.syn" as plugins
36
36
 
37
37
  export let DIR be ".lampson/agents"
38
38
  let MAX_TASKS be 6
@@ -40,7 +40,7 @@ let PARALLEL be 4
40
40
 
41
41
  export let PROFILES be {
42
42
  "build": {
43
- "tools": ["read", "write", "edit", "ls", "find", "grep", "lsp", "bash", "process", "memory", "todo", "skill", "mcp", "lamp", "schedule", "delegate"],
43
+ "tools": ["read", "write", "edit", "ls", "find", "grep", "lsp", "bash", "process", "memory", "todo", "skill", "mcp", "plugin", "schedule", "delegate"],
44
44
  "steps": 40,
45
45
  "addendum": "You are in BUILD mode: you may read, edit, create files and run commands. Delegate exploration of large codebases to the `explore` agent and independent code review to the `review` agent when it saves context; several independent questions can go in one delegate call (they run in parallel)."
46
46
  },
@@ -105,8 +105,8 @@ export task registry_for(name)
105
105
  set reg["delegate"] to delegate
106
106
  each m in mcp.names(mcp_readonly_only(name))
107
107
  set reg[m] to "mcp"
108
- each m in lamps.names(mcp_readonly_only(name))
109
- set reg[m] to "lamp"
108
+ each m in plugins.names(mcp_readonly_only(name))
109
+ set reg[m] to "plugin"
110
110
  give reg
111
111
 
112
112
  export task catalog_for(name)
@@ -119,7 +119,7 @@ export task catalog_for(name)
119
119
  let cat be tools.catalog_subset(p["tools"])
120
120
  when contains(p["tools"], "delegate")
121
121
  set cat to append(cat, DELEGATE_SPEC)
122
- give cat + mcp.catalog(mcp_readonly_only(name)) + lamps.catalog(mcp_readonly_only(name))
122
+ give cat + mcp.catalog(mcp_readonly_only(name)) + plugins.catalog(mcp_readonly_only(name))
123
123
 
124
124
  -- Info de entorno para el system prompt (compartida por chat.syn / web.syn / delegate)
125
125
  export task env_info(cfg)
@@ -128,7 +128,7 @@ export task env_info(cfg)
128
128
  require time
129
129
  let sc be tools.shell_config()
130
130
  -- desde dónde habla el usuario (blackboard "lampson:ui", lo comparte el entry): el modelo NO debe decirle
131
- -- "/lamps" a alguien que está en la web (2026-08-28)
131
+ -- "/plugins" a alguien que está en la web (2026-08-28)
132
132
  observe "lampson:ui" as ui
133
133
  give {
134
134
  "ui": when ui == nothing then "terminal" otherwise text(ui["kind"]),
package/lib/loop.syn CHANGED
@@ -23,7 +23,7 @@
23
23
  use "./provider.syn" as provider
24
24
  use "./permission.syn" as permission
25
25
  use "./mcp.syn" as mcp
26
- use "./lamps.syn" as lamps
26
+ use "./plugins.syn" as plugins
27
27
  use "./tools/todo.syn" as todo
28
28
 
29
29
  let MAX_ERRORS_PER_TURN be 8
@@ -285,9 +285,9 @@ task execute(tc, opts, on_event)
285
285
  -- tools MCP: no son tasks Synsema (args libres); el registry las marca con "mcp"
286
286
  when registry[name] == "mcp"
287
287
  set out to text(mcp.call(name, args))
288
- -- tools de lámparas: un proceso hijo por llamada (lib/lamps.syn)
289
- otherwise when registry[name] == "lamp"
290
- set out to text(lamps.call(name, args))
288
+ -- tools de plugins: un proceso hijo por llamada (lib/plugins.syn)
289
+ otherwise when registry[name] == "plugin"
290
+ set out to text(plugins.call(name, args))
291
291
  otherwise
292
292
  set out to text(call_tool(registry[name], args))
293
293
  recover err
@@ -91,20 +91,20 @@ export task evaluate(name, args, mode)
91
91
  when mode == "strict"
92
92
  give {"decision": "deny", "reason": "strict mode (adds an LSP server)"}
93
93
  give {"decision": "ask", "reason": "configures LSP server '" + (when contains(args, "server") then text(args["server"]) otherwise "?") + "' (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ") — it will run the preset command on the first query"}
94
- when starts_with(name, "lamp_")
95
- -- tool de una lámpara (código del usuario/agente, un proceso por llamada): como las MCP
94
+ when starts_with(name, "plugin_")
95
+ -- tool de un plugin (código del usuario/agente, un proceso por llamada): como las MCP
96
96
  when mode == "yolo"
97
- give {"decision": "allow", "reason": "yolo mode (lamp tool)"}
97
+ give {"decision": "allow", "reason": "yolo mode (plugin tool)"}
98
98
  when mode == "strict"
99
- give {"decision": "deny", "reason": "strict mode (lamp tool)"}
100
- give {"decision": "ask", "reason": "lamp tool " + name}
101
- when name == "lamp"
102
- -- encender una lámpara = autorizar código a correr con las capacidades de su manifiesto: humano SIEMPRE
99
+ give {"decision": "deny", "reason": "strict mode (plugin tool)"}
100
+ give {"decision": "ask", "reason": "plugin tool " + name}
101
+ when name == "plugin"
102
+ -- encender un plugin = autorizar código a correr con las capacidades de su manifiesto: humano SIEMPRE
103
103
  let act be when contains(args, "action") then text(args["action"]) otherwise "list"
104
104
  when act == "enable" or act == "disable"
105
105
  when mode == "strict"
106
- give {"decision": "deny", "reason": "strict mode (" + act + " lamp)"}
107
- give {"decision": "ask", "reason": act + "s lamp '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'"}
106
+ give {"decision": "deny", "reason": "strict mode (" + act + " plugin)"}
107
+ give {"decision": "ask", "reason": act + "s plugin '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'"}
108
108
  give {"decision": "allow", "reason": "read-only tool"}
109
109
  when name == "mcp"
110
110
  -- conectar un server MCP = ejecutar un comando de terceros con env propio: humano siempre, incluso en yolo
@@ -115,7 +115,7 @@ export task evaluate(name, args, mode)
115
115
  give {"decision": "ask", "reason": act + "s MCP server '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'" + (when act == "add" then " → runs: " + (when contains(args, "command") then text(args["command"]) otherwise "?") otherwise "")}
116
116
  give {"decision": "allow", "reason": "read-only tool"}
117
117
  when name == "schedule"
118
- -- programar = autorizar corridas futuras SIN nadie mirando (un comando, una lámpara, o el agente entero
118
+ -- programar = autorizar corridas futuras SIN nadie mirando (un comando, un plugin, o el agente entero
119
119
  -- con un sobre de permisos): humano siempre, incluso en yolo. list/log son lectura.
120
120
  let sact be when contains(args, "action") then text(args["action"]) otherwise "list"
121
121
  when sact == "list" or sact == "log"
@@ -140,15 +140,15 @@ task one_line(s, max)
140
140
  give t
141
141
 
142
142
  export task describe_call(name, args)
143
- when starts_with(name, "mcp_") or starts_with(name, "lamp_")
143
+ when starts_with(name, "mcp_") or starts_with(name, "plugin_")
144
144
  give name + " " + one_line(json_encode(args), 160)
145
145
  when name == "lsp" and contains(args, "op") and text(args["op"]) == "add"
146
146
  give "lsp add " + (when contains(args, "server") then text(args["server"]) otherwise "?") + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
147
147
  when name == "lsp"
148
148
  give "lsp " + (when contains(args, "op") then text(args["op"]) otherwise "?") + " " + (when contains(args, "path") then text(args["path"]) otherwise "?") + (when contains(args, "line") then ":" + text(args["line"]) + (when contains(args, "character") then ":" + text(args["character"]) otherwise "") otherwise "")
149
- when name == "lamp"
149
+ when name == "plugin"
150
150
  let lact be when contains(args, "action") then text(args["action"]) otherwise "list"
151
- give "lamp " + lact + (when lact != "list" then " " + (when contains(args, "name") then text(args["name"]) otherwise "?") otherwise "") + (when lact == "create" and contains(args, "files") then " [" + join(keys(args["files"]), ", ") + "]" otherwise "")
151
+ give "plugin " + lact + (when lact != "list" then " " + (when contains(args, "name") then text(args["name"]) otherwise "?") otherwise "") + (when lact == "create" and contains(args, "files") then " [" + join(keys(args["files"]), ", ") + "]" otherwise "")
152
152
  when name == "mcp"
153
153
  let act be when contains(args, "action") then text(args["action"]) otherwise "list"
154
154
  when act == "add"
@@ -160,7 +160,7 @@ export task describe_call(name, args)
160
160
  let sact be arg(args, "action", "list")
161
161
  when sact == "add"
162
162
  let what be arg(args, "kind", "?")
163
- let body be when what == "lamp" then "lamp " + arg(args, "lamp", "?") + "." + arg(args, "tool", "?") + (when contains(args, "args") then " " + one_line(json_encode(args["args"]), 80) otherwise "") otherwise (when what == "bash" then "$ " + one_line(arg(args, "command", "?"), 120) otherwise "agent " + arg(args, "agent", "build") + ": " + one_line(arg(args, "prompt", "?"), 160))
163
+ let body be when what == "plugin" then "plugin " + arg(args, "plugin", "?") + "." + arg(args, "tool", "?") + (when contains(args, "args") then " " + one_line(json_encode(args["args"]), 80) otherwise "") otherwise (when what == "bash" then "$ " + one_line(arg(args, "command", "?"), 120) otherwise "agent " + arg(args, "agent", "build") + ": " + one_line(arg(args, "prompt", "?"), 160))
164
164
  give "schedule add «" + arg(args, "name", "?") + "» · " + arg(args, "at", "?") + " · " + body + " · permission " + arg(args, "permission", "ask") + (when contains(args, "notify") then " · notify " + text(args["notify"]) otherwise "")
165
165
  give "schedule " + sact + (when contains(args, "id") then " " + text(args["id"]) otherwise "")
166
166
  when name == "skill" and is_install(args)