oficina-opencode 2.1.1 → 3.0.0

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/dist/v2.js ADDED
@@ -0,0 +1,419 @@
1
+ /**
2
+ * OFICINA — entry point del plugin para **OpenCode v2**.
3
+ *
4
+ * ## Por qué existe este archivo
5
+ *
6
+ * OpenCode v1 y v2 tienen APIs de plugins **incomunes**: no es que
7
+ * cambiara una firma, es que cambió la forma. v1 exportaba una funcion que
8
+ * devuelve hooks; v2 exige `export default { id, setup(ctx) }`. Un plugin
9
+ * v1 bajo v2 falla con:
10
+ *
11
+ * PluginModule.LoadError: Plugin must export a default definition with
12
+ * an id and an effect or setup function.
13
+ *
14
+ * Y al reves: un plugin v2 bajo v1 no carga.
15
+ *
16
+ * Este archivo es la implementacion **v2**. La de v1 vive en `v1.ts` y el
17
+ * motor (`core/`, `tools/`) no depende de ninguna de las dos APIs: define
18
+ * las tools en una forma neutra y cada entry la traduce.
19
+ *
20
+ * ## Que registra
21
+ *
22
+ * - **Tools** (`oficina_*`): la superficie de trabajo, via `ctx.tool.transform`.
23
+ * - **Guard de scope** (`ctx.tool.hook` + `ctx.permission.hook`): dos capas.
24
+ * La primera intercepta la tool; la segunda es el punto de enforcement del
25
+ * framework y puede `deny` de verdad.
26
+ * - **Tripwire** (`ctx.tool.hook("execute.after")`): detecta escrituras que se
27
+ * colaron por un comando de shell.
28
+ * - **Estado de la oficina** (`ctx.session.hook("context")`): el agente no
29
+ * repregunta lo que ya sabe.
30
+ * - **Auto-continuacion** (`session.idle`): la corrida sigue sola cuando el
31
+ * modelo para.
32
+ * - **Costo** (`session.usage.recorded`): el numero real que reporta el motor.
33
+ */
34
+ import { Plugin } from "@opencode/plugin";
35
+ import { z } from "zod";
36
+ import { Office } from "./core/office.js";
37
+ import { buildTools } from "./tools/index.js";
38
+ import { analyzeShellCommand, checkScope } from "./core/sandbox.js";
39
+ import { extractCommand, extractPaths, resolveScopeCard } from "./hooks/scopeGuard.js";
40
+ import { Tripwire } from "./core/tripwire.js";
41
+ import { logger } from "./core/log.js";
42
+ const log = logger("oficina:v2");
43
+ /** Tools que escriben un archivo. */
44
+ const FILE_WRITE_TOOLS = new Set([
45
+ "edit",
46
+ "write",
47
+ "patch",
48
+ "multiedit",
49
+ "apply_patch",
50
+ "notebook_edit",
51
+ "str_replace",
52
+ "str_replace_editor",
53
+ "edit_file",
54
+ "edit_notebook_file",
55
+ "create_file",
56
+ "write_file",
57
+ "create_or_update_file",
58
+ ]);
59
+ /** Tools que ejecutan shell. */
60
+ const SHELL_TOOLS = new Set([
61
+ "bash",
62
+ "shell",
63
+ "run",
64
+ "run_command",
65
+ "execute_command",
66
+ "terminal",
67
+ "powershell",
68
+ "cmd",
69
+ ]);
70
+ /** Acciones de permiso que implican escribir un archivo. */
71
+ const WRITE_ACTIONS = new Set(["edit", "write", "patch", "multiedit", "apply_patch"]);
72
+ /**
73
+ * Convierte un schema zod a JSON Schema.
74
+ *
75
+ * v2 exige JSON Schema plano en `tool.input`. zod v4 lo hace nativo.
76
+ */
77
+ function toJsonSchema(schema) {
78
+ if (!schema)
79
+ return { type: "object", additionalProperties: true };
80
+ try {
81
+ const converted = z.toJSONSchema(schema, {
82
+ io: "input",
83
+ unrepresentable: "any",
84
+ });
85
+ // Si el schema no es de un objeto, se acepta igual pero se documenta.
86
+ if (converted["type"] !== "object") {
87
+ return { type: "object", additionalProperties: true, description: converted["description"] };
88
+ }
89
+ return { ...converted, additionalProperties: false };
90
+ }
91
+ catch {
92
+ return { type: "object", additionalProperties: true };
93
+ }
94
+ }
95
+ export default Plugin.define({
96
+ id: "oficina",
97
+ async setup(ctx) {
98
+ const options = (ctx.options ?? {});
99
+ const enforce = options.enforceScope !== false;
100
+ const directory = ctx.location.directory;
101
+ const office = new Office({ cwd: directory });
102
+ await office.init();
103
+ const tripwire = new Tripwire(office.paths.projectRoot, {
104
+ maxFiles: options.tripwireMaxFiles ?? 4000,
105
+ });
106
+ /** El scope que aplica a una sesion, si tiene. */
107
+ const scopeOf = async (sessionID) => {
108
+ try {
109
+ const state = await office.loadState();
110
+ const card = await resolveScopeCard(state, sessionID, { enforce });
111
+ if (!card)
112
+ return undefined;
113
+ const policy = {
114
+ mayTouch: card.scope.mayTouch,
115
+ forbidden: card.scope.forbidden,
116
+ };
117
+ return { card, policy };
118
+ }
119
+ catch (err) {
120
+ log.debug("no se pudo resolver el scope de la sesion", { sessionID, error: String(err) });
121
+ return undefined;
122
+ }
123
+ };
124
+ /* ────────────────────────────────────────────────────────────────────────
125
+ * 1. TOOLS
126
+ * ──────────────────────────────────────────────────────────────────────── */
127
+ const defs = buildTools(office);
128
+ await ctx.tool.transform((editor) => {
129
+ for (const [name, def] of Object.entries(defs)) {
130
+ editor.add({
131
+ name,
132
+ description: def.description,
133
+ input: toJsonSchema(def.args),
134
+ execute: async (input, toolCtx) => {
135
+ const result = await def.execute((input ?? {}), {
136
+ directory,
137
+ sessionID: toolCtx.sessionID,
138
+ agent: toolCtx.agent,
139
+ messageID: toolCtx.messageID,
140
+ });
141
+ return {
142
+ content: result.output,
143
+ metadata: result.metadata,
144
+ };
145
+ },
146
+ });
147
+ }
148
+ });
149
+ /* ────────────────────────────────────────────────────────────────────────
150
+ * 2. GUARD DE SCOPE
151
+ *
152
+ * Dos capas, porque cada una cubre lo que la otra no:
153
+ *
154
+ * - `tool.hook("execute.before")`: ve el path exacto de cada tool que
155
+ * escribe, incluso los que no pasan por el sistema de permisos.
156
+ * - `permission.hook("evaluate")`: es el punto de enforcement del
157
+ * framework y puede `deny` de verdad, con motivo.
158
+ * ──────────────────────────────────────────────────────────────────────── */
159
+ /** Verifica un path contra el scope. Devuelve el motivo si viola. */
160
+ const violation = async (sessionID, rawPath) => {
161
+ const scope = await scopeOf(sessionID);
162
+ if (!scope)
163
+ return undefined;
164
+ const verdict = await checkScope(office.paths.projectRoot, rawPath, scope.policy);
165
+ if (verdict.allowed)
166
+ return undefined;
167
+ return { reason: verdict.reason, kind: verdict.kind };
168
+ };
169
+ await ctx.tool.hook("execute.before", async (event) => {
170
+ const tool = String(event.tool);
171
+ const input = (event.input ?? {});
172
+ // Lecturas: nunca se tocan.
173
+ if (!FILE_WRITE_TOOLS.has(tool) && !SHELL_TOOLS.has(tool))
174
+ return;
175
+ const scope = await scopeOf(event.sessionID);
176
+ if (!scope)
177
+ return;
178
+ if (FILE_WRITE_TOOLS.has(tool)) {
179
+ for (const rawPath of extractPaths(tool, input)) {
180
+ const bad = await violation(event.sessionID, rawPath);
181
+ if (!bad)
182
+ continue;
183
+ const mensaje = `[OFICINA] Scope violado: ${bad.reason} (card ${scope.card.id})`;
184
+ log.warn("scope violado (archivo)", { card: scope.card.id, path: rawPath, kind: bad.kind });
185
+ if (enforce)
186
+ throw new Error(mensaje);
187
+ }
188
+ return;
189
+ }
190
+ if (SHELL_TOOLS.has(tool)) {
191
+ const command = extractCommand(input);
192
+ if (!command)
193
+ return;
194
+ for (const candidate of analyzeShellCommand(command)) {
195
+ if (candidate.uncertain) {
196
+ // Hay una escritura que no sabemos interpretar. No se bloquea
197
+ // (seria inservible), pero el tripwire va a mirar.
198
+ log.warn("shell con escritura no interpretable", {
199
+ card: scope.card.id,
200
+ op: candidate.op,
201
+ });
202
+ continue;
203
+ }
204
+ const bad = await violation(event.sessionID, candidate.target);
205
+ if (!bad)
206
+ continue;
207
+ const mensaje = `[OFICINA] Scope violado: ${bad.reason} (card ${scope.card.id})`;
208
+ log.warn("scope violado (shell)", {
209
+ card: scope.card.id,
210
+ target: candidate.target,
211
+ kind: bad.kind,
212
+ });
213
+ if (enforce && options.enforceScope !== false)
214
+ throw new Error(mensaje);
215
+ }
216
+ }
217
+ });
218
+ await ctx.permission.hook("evaluate", async (event) => {
219
+ if (!WRITE_ACTIONS.has(String(event.action)))
220
+ return;
221
+ const scope = await scopeOf(event.sessionID);
222
+ if (!scope)
223
+ return;
224
+ for (const resource of event.resources) {
225
+ const bad = await violation(event.sessionID, resource);
226
+ if (!bad)
227
+ continue;
228
+ event.effect = "deny";
229
+ event.message = `[OFICINA] ${bad.reason} (card ${scope.card.id})`;
230
+ log.warn("permiso denegado por scope", {
231
+ card: scope.card.id,
232
+ resource,
233
+ kind: bad.kind,
234
+ });
235
+ return;
236
+ }
237
+ });
238
+ /* ────────────────────────────────────────────────────────────────────────
239
+ * 3. TRIPWIRE
240
+ *
241
+ * Fotografia el arbol antes de una tool y compara despues. Lo que cambio
242
+ * fuera del scope se reporta por el bus: detecta, no revierte.
243
+ * ──────────────────────────────────────────────────────────────────────── */
244
+ await ctx.tool.hook("execute.before", async (event) => {
245
+ const scope = await scopeOf(event.sessionID);
246
+ if (!scope)
247
+ return;
248
+ await tripwire.arm(String(event.id), scope.policy.mayTouch);
249
+ });
250
+ await ctx.tool.hook("execute.after", async (event) => {
251
+ const scope = await scopeOf(event.sessionID);
252
+ if (!scope)
253
+ return;
254
+ const cambios = await tripwire.check(String(event.id), scope.policy.mayTouch);
255
+ if (cambios.length === 0)
256
+ return;
257
+ const detalle = cambios.map((c) => ` - ${c.kind}: ${c.path}`).join("\n");
258
+ log.warn("escrituras fuera del scope detectadas", {
259
+ card: scope.card.id,
260
+ tool: event.tool,
261
+ cambios: cambios.length,
262
+ });
263
+ await office.bus
264
+ .send("peer", {
265
+ from: "oficina",
266
+ to: ["*"],
267
+ kind: "NOTICE",
268
+ subject: scope.card.id,
269
+ body: {
270
+ notice: "scope",
271
+ message: `Se detectaron ${cambios.length} cambio(s) fuera del scope de la card ${scope.card.id} ` +
272
+ `durante \`${event.tool}\`:\n${detalle}\n\n` +
273
+ `Si fue deliberado, avisalo por el bus. Si no, revisé lo que tocaste.`,
274
+ },
275
+ })
276
+ .catch(() => undefined);
277
+ });
278
+ /* ────────────────────────────────────────────────────────────────────────
279
+ * 4. EL ESTADO DE LA OFICINA VIAJA CON CADA LLAMADA
280
+ * ──────────────────────────────────────────────────────────────────────── */
281
+ await ctx.session.hook("context", async (event) => {
282
+ try {
283
+ const { buildSystemPrompt } = await import("./core/systemPrompt.js");
284
+ const state = await office.loadState();
285
+ event.system.push({ type: "text", text: await buildSystemPrompt(office, state) });
286
+ }
287
+ catch (err) {
288
+ log.debug("no se pudo inyectar el estado", { error: String(err) });
289
+ }
290
+ });
291
+ /* ────────────────────────────────────────────────────────────────────────
292
+ * 5. COMPACTACION
293
+ *
294
+ * El estado sobrevive a la compactacion: se lee del disco, no de la
295
+ * memoria de la conversacion.
296
+ * ──────────────────────────────────────────────────────────────────────── */
297
+ await ctx.session.hook("compaction", async (event) => {
298
+ try {
299
+ const { buildSystemPrompt } = await import("./core/systemPrompt.js");
300
+ const state = await office.loadState();
301
+ event.system.push({ type: "text", text: await buildSystemPrompt(office, state) });
302
+ }
303
+ catch (err) {
304
+ log.debug("no se pudo inyectar el estado al compactar", { error: String(err) });
305
+ }
306
+ });
307
+ /* ────────────────────────────────────────────────────────────────────────
308
+ * 6. EVENTOS: auto-continuacion y costo
309
+ * ──────────────────────────────────────────────────────────────────────── */
310
+ const maxAutoResumes = options.maxAutoResumes ?? 25;
311
+ const resumes = new Map();
312
+ const lastResume = new Map();
313
+ const controller = new AbortController();
314
+ void (async () => {
315
+ try {
316
+ for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
317
+ /* ── Auto-continuacion ── */
318
+ if (event.type === "session.idle") {
319
+ const sessionID = event.data?.sessionID;
320
+ if (!sessionID)
321
+ continue;
322
+ try {
323
+ const state = await office.loadState();
324
+ if (state.status !== "running")
325
+ continue;
326
+ if (state.halt)
327
+ continue;
328
+ const cards = Object.values(state.cards);
329
+ const pending = cards.filter((c) => c.state !== "done");
330
+ if (pending.length === 0)
331
+ continue;
332
+ const count = resumes.get(sessionID) ?? 0;
333
+ if (count >= maxAutoResumes) {
334
+ log.warn("tope de auto-reanudaciones alcanzado", { sessionID, count });
335
+ continue;
336
+ }
337
+ const last = lastResume.get(sessionID);
338
+ if (last !== undefined && Date.now() - last < 1_500)
339
+ continue;
340
+ lastResume.set(sessionID, Date.now());
341
+ resumes.set(sessionID, count + 1);
342
+ const { renderMesaBrief, snapshotMesa } = await import("./core/mesa.js");
343
+ const mesa = await snapshotMesa(office);
344
+ const mesaBrief = renderMesaBrief(mesa);
345
+ const texto = [
346
+ "[OFICINA - auto-continuacion]",
347
+ "",
348
+ `La corrida sigue abierta. Quedan ${pending.length} card(s) sin terminar.`,
349
+ "",
350
+ ...(mesaBrief ? ["---", "", "**La mesa:**", "", mesaBrief] : []),
351
+ "",
352
+ "Usá `oficina_board` para el panorama y `oficina_mesa` para ver la mesa.",
353
+ "No declares la corrida terminada mientras queden cards sin DONE.",
354
+ ].join("\n");
355
+ await ctx.session.synthetic({
356
+ sessionID,
357
+ text: texto,
358
+ description: "OFICINA: auto-continuacion",
359
+ });
360
+ }
361
+ catch (err) {
362
+ log.error("fallo en la auto-continuacion", { error: String(err) });
363
+ }
364
+ continue;
365
+ }
366
+ /* ── Costo ── */
367
+ //
368
+ // El evento trae el numero REAL que reporto el motor. No se estima.
369
+ //
370
+ // El tipo del stream publicado no incluye los eventos de usage (el
371
+ // union tipado es mas angosto que el manifiesto del schema), asi
372
+ // que se lee de forma laxa y a mano: si la forma no coincide, se
373
+ // ignora en vez de romper.
374
+ const raw = event;
375
+ if (raw.type === "session.usage.recorded" || raw.type === "session.usage.updated") {
376
+ const data = raw.data;
377
+ if (!data?.sessionID)
378
+ continue;
379
+ try {
380
+ const state = await office.loadState();
381
+ const binding = state.sessions[data.sessionID];
382
+ await office.recordUsage({
383
+ agentId: binding?.agentId ?? data.sessionID,
384
+ role: binding?.agentId ?? "agente",
385
+ cardId: binding?.cardId ?? null,
386
+ phase: binding?.cardId ? "implementation" : "exploration",
387
+ model: "desconocido",
388
+ provider: "desconocido",
389
+ cost: data.cost ?? 0,
390
+ tokens: {
391
+ input: data.tokens?.input ?? 0,
392
+ output: data.tokens?.output ?? 0,
393
+ reasoning: data.tokens?.reasoning ?? 0,
394
+ cache: { read: data.tokens?.cache?.read ?? 0, write: data.tokens?.cache?.write ?? 0 },
395
+ },
396
+ latencyMs: 0,
397
+ });
398
+ }
399
+ catch (err) {
400
+ log.debug("no se pudo registrar el uso", { error: String(err) });
401
+ }
402
+ }
403
+ }
404
+ }
405
+ catch (err) {
406
+ if (!controller.signal.aborted) {
407
+ log.error("el stream de eventos se corto", { error: String(err) });
408
+ }
409
+ }
410
+ })();
411
+ log.info("plugin inicializado", {
412
+ directory,
413
+ tools: Object.keys(defs).length,
414
+ officeDir: office.paths.officeDir,
415
+ });
416
+ return () => controller.abort();
417
+ },
418
+ });
419
+ //# sourceMappingURL=v2.js.map
package/dist/v2.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2.js","sourceRoot":"","sources":["../src/v2.ts"],"names":[],"mappings":"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"}
@@ -9,43 +9,69 @@ nadie verificó.
9
9
 
10
10
  ---
11
11
 
12
- ## Versiones
12
+ ## ⚠️ El cambio de API de plugins es el punto que hay que entender
13
13
 
14
- | Componente | Versión | Cómo se verificó |
14
+ **OpenCode v1 y v2 tienen APIs de plugins incompatibles.** No es que cambiara
15
+ una firma: cambió la forma.
16
+
17
+ | | OpenCode v1 | OpenCode v2 |
15
18
  |---|---|---|
16
- | `@opencode-ai/plugin` | `1.15.11` | Compilación con `strict` + `noUncheckedIndexedAccess` contra los tipos reales del paquete. Un hook que no exista en el tipo **no compila**. |
17
- | Node.js | `>=20` | CI corre la matriz `[20, 22]` en `ubuntu-latest` y `windows-latest`. |
18
- | TypeScript | `>=5.6` | `tsc --noEmit` en modo `strict`. |
19
+ | Export default | una **función** que devuelve hooks | un **objeto** `{ id, setup(ctx) }` |
20
+ | Registrar tools | `hooks.tool = { nombre: tool({...}) }` | `ctx.tool.transform(editor => editor.add({...}))` |
21
+ | Forma de los args | schema **zod** | **JSON Schema** plano |
22
+ | Interceptar tools | hook `tool.execute.before` | `ctx.tool.hook("execute.before")` |
23
+ | Denegar una acción | hook `permission.ask` | `ctx.permission.hook("evaluate")` → `effect = "deny"` |
24
+ | Inyectar al system prompt | `experimental.chat.system.transform` | `ctx.session.hook("context")` |
25
+ | Eventos | hook `event` | `ctx.event.subscribe({ signal })` |
26
+ | Auto-continuación | `client.session.promptAsync` | `ctx.session.synthetic({...})` |
19
27
 
20
- La API de plugin es la que manda: el plugin importa sus tipos, así que
21
- **la compatibilidad se verifica en tiempo de compilación**, no por
22
- inspección visual.
28
+ Un plugin v1 bajo v2 falla con:
23
29
 
24
- ## Hooks que usa el plugin
30
+ ```
31
+ PluginModule.LoadError: Plugin must export a default definition with an id
32
+ and an effect or setup function.
33
+ ```
25
34
 
26
- | Hook | Para qué | Si el host no lo soporta |
27
- |---|---|---|
28
- | `event` (`session.idle`) | Auto-reanudación de la corrida | La corrida sigue, pero no se reanuda sola. |
29
- | `event` (`message.updated`) | Contabilidad de costo | No hay registro de costo. La oficina sigue funcionando. |
30
- | `tool.execute.before` | Guard de scope | **Sin protección**: el scope pasa a ser una instrucción, no una restricción. |
31
- | `tool.execute.after` | Tripwire | No hay detección de escrituras coladas. |
32
- | `permission.ask` | Segunda capa de enforcement | Queda solo la primera capa. |
33
- | `experimental.chat.system.transform` | El estado viaja con cada llamada | El agente tiene que preguntar el estado. |
34
- | `experimental.session.compacting` | El estado sobrevive a la compactación | La compactación pierde el contexto de la oficina. |
35
- | `experimental.compaction.autocontinue` | Freno de emergencia | El freno frena el trabajo, pero la auto-continuación post-compactación sigue. |
35
+ Y al revés, un plugin v2 bajo v1 no carga.
36
36
 
37
- ## Mínimo verificable
37
+ ### Cómo lo resuelve AI-OFFICE
38
38
 
39
- La parte del plugin que **no degrada bien** es el guard de scope. Por eso
40
- el proyecto declara un mínimo:
39
+ El **motor** (`core/`, `tools/`) no depende de ninguna de las dos APIs:
41
40
 
42
- - **`@opencode-ai/plugin` >= 1.15.11**: a partir de esta versión existen
43
- `tool.execute.before`, `tool.execute.after` y `permission.ask` con las
44
- firmas que usa el plugin.
41
+ - Las tools se definen en una **forma neutra** (`{ description, args, execute }`).
42
+ - `src/v2.ts` las traduce a v2 (zod → JSON Schema con `z.toJSONSchema`).
43
+ - `src/v1.ts` las traduce a v1 (el helper `tool()` del plugin v1).
45
44
 
46
- Si se instala con una versión anterior, el typecheck falla en la
47
- instalación del consumidor y eso es intencional: es mejor un error de
48
- compilación que una instalación que parece protegida y no lo está.
45
+ El texto del system prompt también es compartido: vive en
46
+ `core/systemPrompt.ts` y cada entry lo inyecta donde su API lo permite. Si
47
+ viviera dentro de cada hook, las dos copias se desincronizarían.
48
+
49
+ | Entry | Versión de OpenCode |
50
+ |---|---|
51
+ | `oficina-opencode` (`.`) | **v2** — la principal |
52
+ | `oficina-opencode/v1` | v1 (legacy) |
53
+
54
+ ## Versiones
55
+
56
+ | Componente | Versión | Cómo se verificó |
57
+ |---|---|---|
58
+ | `@opencode/plugin` | `^2.1.2` | Compilación con `strict` + `noUncheckedIndexedAccess` contra los tipos reales. Un hook que no exista en el tipo **no compila**. |
59
+ | `@opencode-ai/plugin` | `^1.15.11` *(peer opcional)* | Solo lo necesita el entry `./v1`. |
60
+ | OpenCode CLI | `2.1.1` | Probado con el servicio real: el plugin carga, registra las 16 tools y responde. |
61
+ | Node.js | `>=20` | CI corre la matriz `[20, 22]` en `ubuntu-latest` y `windows-latest`. |
62
+ | TypeScript | `>=5.8` | `tsc --noEmit` en modo `strict`. |
63
+
64
+ ## Hooks que usa el plugin v2
65
+
66
+ | Hook | Para qué | Si el host no lo soporta |
67
+ |---|---|---|
68
+ | `ctx.tool.transform` | Registra las 16 tools | **Sin tools, la oficina no existe.** |
69
+ | `ctx.tool.hook("execute.before")` | Guard de scope + armado del tripwire | El scope pasa a ser una instrucción. |
70
+ | `ctx.tool.hook("execute.after")` | Tripwire: detecta escrituras coladas | No hay detección. |
71
+ | `ctx.permission.hook("evaluate")` | Segunda capa: `deny` real | Queda solo el guard propio. |
72
+ | `ctx.session.hook("context")` | El estado viaja con cada llamada | El agente repregunta lo que ya sabe. |
73
+ | `ctx.session.hook("compaction")` | El estado sobrevive a la compactación | La compactación pierde el contexto. |
74
+ | `ctx.event.subscribe` | Auto-continuación (`session.idle`) y costo (`session.usage.recorded`) | Sin auto-continuación ni contabilidad de costo. |
49
75
 
50
76
  ## Pruebas de integración por versión
51
77
 
@@ -56,26 +82,21 @@ os: [ubuntu-latest, windows-latest]
56
82
  node: [20, 22]
57
83
  ```
58
84
 
59
- En cada combinación se corre:
60
-
61
- 1. `npm run typecheck` — la API de plugin compila.
62
- 2. `npm run test` — la suite completa (los tests del plugin usan un host
63
- simulado con la misma forma que el real).
64
- 3. `npm run build` — el `dist/` se genera.
65
- 4. `npm pack --dry-run` — el paquete se puede armar.
85
+ En cada combinación: `npm run typecheck`, `npm run test`, `npm run build`,
86
+ `npm pack --dry-run`. Y en un job aparte, el smoke del CLI sobre el `dist/`
87
+ real.
66
88
 
67
- Y en un job aparte, el smoke del CLI sobre el `dist/` real.
89
+ Los tests del plugin v2 (`tests/pluginV2.test.ts`) mockean el contexto de v2
90
+ y comprueban que las tools se registran con JSON Schema, que los hooks se
91
+ instalan y que las tools responden.
68
92
 
69
93
  ## OpenCode
70
94
 
71
95
  | Versión de OpenCode | Soporte |
72
96
  |---|---|
73
- | La que trae el host actual | Sí. Es la que se prueba en desarrollo. |
74
- | Versiones anteriores | No declaradas. El plugin usa `session.prompt` con partes `subtask`, `session.promptAsync`, `session.get` y `client.app.log`. |
75
-
76
- El plugin **no fija** una versión de OpenCode: se instala como plugin y usa
77
- el API que el host expone. Lo que sí fija es la versión del **API de
78
- plugin**, que es lo que usa directamente.
97
+ | **v2.x** (>= 2.1) | **Sí.** Es la que se prueba en desarrollo y en el servicio real. |
98
+ | v1 (>= 1.18.29) | Legacy, via `oficina-opencode/v1`. Los hooks v1 funcionan; los de v2 no. |
99
+ | v1 anteriores a 1.18.29 | No. La forma de objeto no estaba soportada. |
79
100
 
80
101
  ## Requisitos del usuario, no del plugin
81
102
 
@@ -87,6 +108,26 @@ plugin**, que es lo que usa directamente.
87
108
 
88
109
  ---
89
110
 
111
+ ## Un detalle que obliga a resolver el paquete en runtime
112
+
113
+ El loader de plugins de OpenCode v2 **no resuelve bare specifiers** desde
114
+ `~/.config/opencode/plugins/`. Un `export { default } from "oficina-opencode"`
115
+ falla con:
116
+
117
+ ```
118
+ Cannot find package 'oficina-opencode' imported from
119
+ C:\Users\...\ .config\opencode\plugins\oficina.js
120
+ ```
121
+
122
+ Node sí lo resolvería (subiendo por los `node_modules`). Bun, que es el
123
+ runtime de OpenCode, no.
124
+
125
+ Por eso `oficina install` escribe un entry (`dist/pluginEntry.js`) que
126
+ **busca el paquete en runtime** — en el `node_modules` de la config, en la
127
+ cache de npm de OpenCode, o subiendo desde el propio archivo — y lo importa
128
+ por ruta absoluta. Se resuelve en runtime y no al instalar, así que
129
+ reinstalar el paquete en otro lugar no rompe el entry.
130
+
90
131
  ## Cómo se mantiene esta tabla
91
132
 
92
133
  Las cifras de versiones salen del `package.json` real y se verifican en el
package/docs/QUE-ES.md CHANGED
@@ -3,10 +3,10 @@
3
3
  <!-- BEGIN GENERATED: facts -->
4
4
  | | |
5
5
  |---|---|
6
- | **Paquete** | `oficina-opencode` v2.1.1 |
6
+ | **Paquete** | `oficina-opencode` v3.0.0 |
7
7
  | **Tools** | 16 |
8
8
  | **Hooks** | 7 |
9
- | **API de plugin** | `@opencode-ai/plugin@1.15.11` |
9
+ | **API de plugin** | `@opencode/plugin@2.1.2` |
10
10
  | **Node** | >=20 |
11
11
  | **Catálogo** | 258 agentes resolubles en 18 divisiones |
12
12
  | **Divisiones** | academic, design, engineering, finance, game-development, gis, healthcare, marketing, paid-media, product, project-management, research, sales, spatial-computing, specialized, strategy, support, testing |