novahiz 0.5.1 → 0.5.2
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/CHANGELOG.md +27 -0
- package/docs/TOKENS.md +7 -6
- package/install/install.mjs +5 -6
- package/mcp/clepsydre/src/scheduler.ts +24 -3
- package/novahiz.config.example.json +0 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.5.2] - 2026-10-05
|
|
9
|
+
|
|
10
|
+
Maintenance release closing the three leftovers traced right after `0.5.1`:
|
|
11
|
+
the installer's dead `plugin` config key, the unread `tokens` block of the
|
|
12
|
+
example config, and the scheduler's 0 ms wake loop on a busy task.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `install/install.mjs` no longer writes a `"plugin"` array of **file paths**
|
|
17
|
+
into a freshly created `opencode.jsonc`. opencode never read it (the plugins
|
|
18
|
+
are loaded from `<config>/plugins`, proved in E2E: the key pointed at the
|
|
19
|
+
repo while the loaded copies came from the plugins directory) and a file
|
|
20
|
+
entry triggered `configured plugin path must be a directory` at every
|
|
21
|
+
startup. Existing configs are left untouched.
|
|
22
|
+
- `novahiz.config.example.json` no longer advertises a `tokens` block
|
|
23
|
+
(`enabled`, `trimOutputs`, `keepHeadLines`, `dedupeReads`, `capOutputTokens`,
|
|
24
|
+
`trimTools`, …) that no code ever read — a leftover of a layer that was never
|
|
25
|
+
shipped. The example now carries exactly the keys `defaultConfig()` writes,
|
|
26
|
+
and `docs/TOKENS.md` states it instead of pointing at a block that should
|
|
27
|
+
not be there.
|
|
28
|
+
- `mcp/clepsydre/src/scheduler.ts`: `arm()` no longer arms a timer for a task
|
|
29
|
+
that is already running. A past-due schedule on a busy task used to feed a
|
|
30
|
+
0 ms `wake → skip → arm` loop (one task-store read per turn) for the whole
|
|
31
|
+
execution, and a manual trigger could sustain it. The lock release now goes
|
|
32
|
+
through `release()`, which re-evaluates the calendar once the task is free,
|
|
33
|
+
so a due occurrence is still caught up exactly once instead of by the spin.
|
|
34
|
+
|
|
8
35
|
## [0.5.1] - 2026-10-05
|
|
9
36
|
|
|
10
37
|
Fix-forward release: the `v0.5.0` tag was pushed but its CI never reached
|
package/docs/TOKENS.md
CHANGED
|
@@ -95,9 +95,10 @@ from the local enforcement database.
|
|
|
95
95
|
|
|
96
96
|
## Not implemented
|
|
97
97
|
|
|
98
|
-
`novahiz.config.example.json
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
`novahiz tokens` consumes it.
|
|
103
|
-
|
|
98
|
+
There is no `tokens` section in `novahiz.config.example.json`: no code ever
|
|
99
|
+
read the one it carried (`enabled`, `trimOutputs`, `keepHeadLines`,
|
|
100
|
+
`keepTailLines`, `keepErrorLines`, `dedupeReads`, `capOutputTokens`,
|
|
101
|
+
`trimTools`, `readTools`) — a leftover of a layer that was never shipped, and
|
|
102
|
+
neither the plugin nor `novahiz tokens` consumes it. It has been removed from
|
|
103
|
+
the example rather than left to contradict this document. Token economy is
|
|
104
|
+
configured with the environment variables above, and nothing else.
|
package/install/install.mjs
CHANGED
|
@@ -236,9 +236,11 @@ async function main() {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
// opencode charge les .ts
|
|
240
|
-
//
|
|
241
|
-
//
|
|
239
|
+
// opencode charge automatiquement les .ts de <config>/plugins. Preuve E2E:
|
|
240
|
+
// le tableau "plugin" de la config pointait vers ce depôt alors que les
|
|
241
|
+
// plugins charges venaient de <config>/plugins — le tableau est donc retire
|
|
242
|
+
// du modele de config, une entree fichier y declenchait l'avertissement
|
|
243
|
+
// "configured plugin path must be a directory" a chaque demarrage.
|
|
242
244
|
for (const pluginFile of ["novahiz-plugin.ts", "novahiz-token-economy.ts"]) {
|
|
243
245
|
const pluginSource = join(home, "adapters", "opencode", pluginFile);
|
|
244
246
|
const pluginTarget = join(pluginsDir, pluginFile);
|
|
@@ -519,9 +521,6 @@ async function main() {
|
|
|
519
521
|
"skills": {
|
|
520
522
|
"paths": [skillsDir]
|
|
521
523
|
},
|
|
522
|
-
"plugin": [
|
|
523
|
-
join(home, "adapters", "opencode", "novahiz-plugin.ts")
|
|
524
|
-
],
|
|
525
524
|
"compaction": {
|
|
526
525
|
"auto": true,
|
|
527
526
|
"prune": true,
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
// bascule toujours en traitement special : une tache n'execute jamais deux
|
|
12
12
|
// fois en parallele (verrou `busy`), meme si la cadence est plus rapide que
|
|
13
13
|
// la duree de l'execution.
|
|
14
|
+
// Le verrou ne s'arme jamais : `arm()` ignore les taches busy, sinon une
|
|
15
|
+
// echeance passee donnerait un timer 0 ms en boucle (`skip -> arm`) pendant
|
|
16
|
+
// toute l'execution ; la liberation re-evalue les echeances et rattrape
|
|
17
|
+
// l'occurrence due une seule fois.
|
|
14
18
|
|
|
15
19
|
import { appendExecution, findTask, loadTasks, mutateTasks, newId, saveTasks, type Task } from "./store.ts";
|
|
16
20
|
import { nextRun, parseSchedule, type Schedule } from "./cron.ts";
|
|
@@ -125,6 +129,11 @@ export class Scheduler {
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/** Arme le prochain reveil sur l'echeance la plus proche.
|
|
132
|
+
* Une tache en execution (verrou `busy`) est ignoree : son echeance passe
|
|
133
|
+
* reste en file jusqu'a la liberation, et un reveil dessus ne ferait que
|
|
134
|
+
* boucler (`timer 0 ms -> wake -> skip -> arm`) pendant toute
|
|
135
|
+
* l'execution, a coup de lecture disque. Le reveil de liberation
|
|
136
|
+
* (`release`) re-evalue les echeances quand le verrou saute.
|
|
128
137
|
* Retourne le delai retenu (null : aucune tache a armer), pour test. */
|
|
129
138
|
arm(): number | null {
|
|
130
139
|
this.stopTimer();
|
|
@@ -133,6 +142,7 @@ export class Scheduler {
|
|
|
133
142
|
let earliest: number | null = null;
|
|
134
143
|
for (const task of tasks) {
|
|
135
144
|
if (!task.enabled || !task.schedule) continue;
|
|
145
|
+
if (this.busy.has(task.id)) continue;
|
|
136
146
|
const due = this.dueMs(task);
|
|
137
147
|
if (due === null) continue;
|
|
138
148
|
if (earliest === null || due < earliest) earliest = due;
|
|
@@ -239,6 +249,17 @@ export class Scheduler {
|
|
|
239
249
|
this.arm();
|
|
240
250
|
}
|
|
241
251
|
|
|
252
|
+
/** Libere le verrou d'une tache puis re-evalue les echeances.
|
|
253
|
+
* Sans ce reveil, une occurrence devenue due pendant l'execution
|
|
254
|
+
* (`triggerNow` / `runNow` n'avancent pas le calendrier) resterait due sur
|
|
255
|
+
* disque en panne depuis que `arm()` ignore les taches busy. Le
|
|
256
|
+
* rattrapage reste unique : le premier reveil avance `nextRunAt`, le
|
|
257
|
+
* suivant ne trouve plus rien a lancer. */
|
|
258
|
+
private release(id: string): void {
|
|
259
|
+
this.busy.delete(id);
|
|
260
|
+
this.wake();
|
|
261
|
+
}
|
|
262
|
+
|
|
242
263
|
/** Declenchement manuel DETACHE : pre-controles synchrones (tache connue,
|
|
243
264
|
* aucune execution en cours), puis execution en arriere-plan. Le serveur
|
|
244
265
|
* stdio reste disponible pour les autres requetes ; le resultat se lit
|
|
@@ -257,7 +278,7 @@ export class Scheduler {
|
|
|
257
278
|
// non capte qui tuerait le serveur.
|
|
258
279
|
})
|
|
259
280
|
.finally(() => {
|
|
260
|
-
this.
|
|
281
|
+
this.release(task.id);
|
|
261
282
|
});
|
|
262
283
|
return { ok: true };
|
|
263
284
|
}
|
|
@@ -275,7 +296,7 @@ export class Scheduler {
|
|
|
275
296
|
} catch (error) {
|
|
276
297
|
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
277
298
|
} finally {
|
|
278
|
-
this.
|
|
299
|
+
this.release(task.id);
|
|
279
300
|
}
|
|
280
301
|
}
|
|
281
302
|
|
|
@@ -300,7 +321,7 @@ export class Scheduler {
|
|
|
300
321
|
.then(() => runOne(index + 1));
|
|
301
322
|
};
|
|
302
323
|
void runOne(0).finally(() => {
|
|
303
|
-
this.
|
|
324
|
+
this.release(task.id);
|
|
304
325
|
});
|
|
305
326
|
}
|
|
306
327
|
|
|
@@ -19,17 +19,5 @@
|
|
|
19
19
|
"autoRegister": true,
|
|
20
20
|
"autoInstall": true,
|
|
21
21
|
"disabled": []
|
|
22
|
-
},
|
|
23
|
-
"tokens": {
|
|
24
|
-
"enabled": true,
|
|
25
|
-
"trimOutputs": true,
|
|
26
|
-
"maxOutputBytes": 40000,
|
|
27
|
-
"keepHeadLines": 120,
|
|
28
|
-
"keepTailLines": 40,
|
|
29
|
-
"keepErrorLines": 40,
|
|
30
|
-
"dedupeReads": true,
|
|
31
|
-
"capOutputTokens": 0,
|
|
32
|
-
"trimTools": ["read", "bash", "shell"],
|
|
33
|
-
"readTools": ["read"]
|
|
34
22
|
}
|
|
35
23
|
}
|