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 +14 -9
- package/bin/lampson.js +13 -7
- package/chat.syn +35 -30
- package/lampson.ps1 +11 -1
- package/lampson.sh +7 -0
- package/lib/agents.syn +6 -6
- package/lib/loop.syn +4 -4
- package/lib/permission.syn +14 -14
- package/lib/plugins.syn +408 -0
- package/lib/prompt.syn +2 -2
- package/lib/sched_run.syn +10 -10
- package/lib/schedule.syn +36 -21
- package/lib/tools/memo.syn +27 -5
- package/lib/tools.syn +30 -30
- package/lib/workspaces.syn +34 -9
- package/package.json +3 -3
- package/{lamps/example-hello/lamp.json → plugins/example-hello/plugin.json} +2 -2
- package/plugins/example-hello/plugin.syn +19 -0
- package/public/css/panel.css +3 -3
- package/public/hub.html +1 -1
- package/public/index.html +3 -3
- package/public/js/app.js +1 -1
- package/public/js/chat.js +4 -4
- package/public/js/memory.js +2 -1
- package/public/js/{lamps.js → plugins.js} +43 -41
- package/public/js/schedules.js +9 -9
- package/public/js/workspaces.js +1 -1
- package/skills/lampson/SKILL.md +18 -11
- package/web.syn +17 -16
- package/lamps/example-hello/lamp.syn +0 -19
- package/lib/lamps.syn +0 -386
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
|
-
##
|
|
63
|
+
## Plugins
|
|
64
64
|
|
|
65
|
-
A **
|
|
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/
|
|
71
|
-
|
|
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 (`
|
|
83
|
-
Turn it on with `/
|
|
84
|
-
or inside a project (`.lampson/
|
|
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
|
|
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,
|
|
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 `
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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("
|
|
26
|
-
require file.read("
|
|
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/
|
|
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
|
-
["/
|
|
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…):
|
|
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 == "/
|
|
396
|
+
when cmd == "/plugins"
|
|
397
397
|
when first
|
|
398
398
|
give ["on", "off", "run", "remove"]
|
|
399
|
-
give apply((l) => l["name"],
|
|
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 /
|
|
569
|
-
let ls be
|
|
570
|
-
let
|
|
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
|
|
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(" (/
|
|
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("
|
|
819
|
-
require file.read("
|
|
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 /
|
|
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
|
|
909
|
+
each l in plugins.summary()
|
|
910
910
|
when l["error"] != nothing
|
|
911
|
-
print(" " + red("○
|
|
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:
|
|
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
|
-
|
|
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
|
-
-- /
|
|
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: /
|
|
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(
|
|
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(" " +
|
|
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(" " +
|
|
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
|
|
1288
|
+
let ls be plugins.summary()
|
|
1285
1289
|
when length(ls) == 0
|
|
1286
|
-
print(" sin
|
|
1287
|
-
print(" " + dim("el agente también puede
|
|
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("
|
|
1290
|
-
print(" " + dim("encender/apagar: /
|
|
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/
|
|
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 "./
|
|
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", "
|
|
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
|
|
109
|
-
set reg[m] to "
|
|
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)) +
|
|
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
|
-
-- "/
|
|
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 "./
|
|
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
|
|
289
|
-
otherwise when registry[name] == "
|
|
290
|
-
set out to text(
|
|
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
|
package/lib/permission.syn
CHANGED
|
@@ -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, "
|
|
95
|
-
-- tool de
|
|
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 (
|
|
97
|
+
give {"decision": "allow", "reason": "yolo mode (plugin tool)"}
|
|
98
98
|
when mode == "strict"
|
|
99
|
-
give {"decision": "deny", "reason": "strict mode (
|
|
100
|
-
give {"decision": "ask", "reason": "
|
|
101
|
-
when name == "
|
|
102
|
-
-- encender
|
|
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 + "
|
|
107
|
-
give {"decision": "ask", "reason": act + "s
|
|
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,
|
|
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, "
|
|
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 == "
|
|
149
|
+
when name == "plugin"
|
|
150
150
|
let lact be when contains(args, "action") then text(args["action"]) otherwise "list"
|
|
151
|
-
give "
|
|
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 == "
|
|
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)
|