oft-agt 0.5.16 → 0.5.18
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/cli.mjs +10 -6
- package/compile-plan.mjs +12 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/version.mjs +1 -1
- package/usage.mjs +1 -1
- package/version.mjs +1 -1
package/cli.mjs
CHANGED
|
@@ -51,6 +51,7 @@ async function credentials(config) {
|
|
|
51
51
|
}
|
|
52
52
|
async function stopBot() {
|
|
53
53
|
const current = child;
|
|
54
|
+
child = null;
|
|
54
55
|
if (!current || current.exitCode !== null || current.signalCode) return;
|
|
55
56
|
await new Promise(resolve => { const timer = setTimeout(() => current.kill('SIGKILL'), 5000); current.once('exit', () => { clearTimeout(timer); resolve(); }); current.kill('SIGTERM'); });
|
|
56
57
|
}
|
|
@@ -89,6 +90,7 @@ async function deploy(config, message, notify) {
|
|
|
89
90
|
await run(process.execPath, ['bot.mjs', '--deploy'], { cwd: target, env, stdio: 'ignore' });
|
|
90
91
|
const previous = child;
|
|
91
92
|
const previousId = active;
|
|
93
|
+
child = null;
|
|
92
94
|
if (previous && previous.exitCode === null && !previous.signalCode) {
|
|
93
95
|
await new Promise(resolve => { const timeout = setTimeout(() => { previous.kill('SIGKILL'); }, 5000); previous.once('exit', () => { clearTimeout(timeout); resolve(); }); previous.kill('SIGTERM'); });
|
|
94
96
|
}
|
|
@@ -109,7 +111,8 @@ async function deploy(config, message, notify) {
|
|
|
109
111
|
}
|
|
110
112
|
throw error;
|
|
111
113
|
}
|
|
112
|
-
child
|
|
114
|
+
const deployedChild=child;
|
|
115
|
+
deployedChild.once('exit', () => { if (child === deployedChild && active === message.id) notify('runtime-failed'); });
|
|
113
116
|
active = message.id;
|
|
114
117
|
await save(path.join(home, 'active.json'), { id: message.id });
|
|
115
118
|
notify('running');
|
|
@@ -203,7 +206,7 @@ async function start(config) {
|
|
|
203
206
|
if (message.type === 'deploy') {
|
|
204
207
|
console.log(`[${new Date().toISOString()}] Nouveau déploiement reçu.`);
|
|
205
208
|
if (await exists(path.join(home, 'paused.json'))) await unlink(path.join(home, 'paused.json'));
|
|
206
|
-
try { await deploy(config, message, async state => { send({ type: 'deployment-status', id: message.id, state, runtimeVersion:state==='running'?await runtimeVersion():null }); console.log(({ received: 'Déploiement validé.', installing: 'Préparation du bot.', running: 'Déploiement terminé. Bot démarré.', failed: 'Échec du déploiement.' })[state] || 'État du déploiement modifié.'); }); }
|
|
209
|
+
try { await deploy(config, message, async state => { if(state==='runtime-failed'){send({type:'runtime-status',state:'failed'});return;} send({ type: 'deployment-status', id: message.id, state, runtimeVersion:state==='running'?await runtimeVersion():null }); console.log(({ received: 'Déploiement validé.', installing: 'Préparation du bot.', running: 'Déploiement terminé. Bot démarré.', failed: 'Échec du déploiement.' })[state] || 'État du déploiement modifié.'); }); }
|
|
207
210
|
catch { send({ type: 'deployment-status', id: message.id, state: 'failed' }); console.error('Déploiement refusé ou échoué.'); }
|
|
208
211
|
}
|
|
209
212
|
}).catch(() => console.error('Message refusé.'));
|
|
@@ -233,7 +236,7 @@ function printHelp() {
|
|
|
233
236
|
status Afficher l’état local
|
|
234
237
|
logs Lire les 100 dernières lignes du journal
|
|
235
238
|
token [--stdin] Régénérer l’authentification agent ; --stdin lit 64 caractères hexadécimaux
|
|
236
|
-
remove --yes
|
|
239
|
+
remove | delete --yes Supprimer une instance arrêtée de la liste et révoquer sa liaison
|
|
237
240
|
list Afficher les instances installées
|
|
238
241
|
help Afficher cette aide
|
|
239
242
|
Options : --bot=IDENTIFIANT_COURT (voir list) pour choisir un bot ; start --foreground pour diagnostiquer.
|
|
@@ -242,7 +245,7 @@ Documentation : https://doc-builder.oftone.fr/agent`);
|
|
|
242
245
|
}
|
|
243
246
|
async function main() {
|
|
244
247
|
const [requestedCommand, ...args] = process.argv.slice(2);
|
|
245
|
-
const command = requestedCommand || 'login';
|
|
248
|
+
const command = requestedCommand==='delete'?'remove':requestedCommand || 'login';
|
|
246
249
|
const source = path.dirname(fileURLToPath(import.meta.url));
|
|
247
250
|
if (['help', '--help', '-h'].includes(command)) { printHelp(); return; }
|
|
248
251
|
if (command === 'list') { for (const instance of await listInstances(baseHome)) console.log(`${instance.agentId.slice(0, 8)} · ${(await localStatus(instance.home)).running ? 'actif' : 'arrêté'}`); return; }
|
|
@@ -327,7 +330,8 @@ async function main() {
|
|
|
327
330
|
}
|
|
328
331
|
if (command === 'remove') {
|
|
329
332
|
if (!args.includes('--yes')) { console.log('Supprime l’agent sélectionné et ses données locales, y compris son token Discord. Relancez avec remove --yes pour confirmer.'); return; }
|
|
330
|
-
await
|
|
333
|
+
const state=await localStatus(home);if(state.running||state.starting||state.legacy)throw Error('REMOVE_RUNNING');
|
|
334
|
+
const config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
331
335
|
let revoked = false;
|
|
332
336
|
try { const response = await fetch(`${config.relay}/v1/agents/${config.object}/remove`, { method: 'POST', headers: { Authorization: `Bearer ${config.credential}` }, signal: AbortSignal.timeout(15000) }); revoked = response.ok; } catch {}
|
|
333
337
|
await removeLocalInstance(home, baseHome, config);
|
|
@@ -335,5 +339,5 @@ async function main() {
|
|
|
335
339
|
}
|
|
336
340
|
throw new Error('COMMAND_UNKNOWN');
|
|
337
341
|
}
|
|
338
|
-
main().catch(error => { const messages = { LOGIN_BUSY: 'Une connexion Discord est déjà en cours sur cette machine.', LOGIN_EXPIRED: 'La demande a expiré. Relancez npx oft-agt login.', LOGIN_START_FAILED: 'Impossible de démarrer la connexion. Réessayez dans quelques minutes.', LOGIN_FAILED: 'Connexion refusée. Relancez npx oft-agt login.', AUTH_ROTATION_BUSY: 'Une modification de l’authentification est déjà en cours pour cette instance. Réessayez après sa fin.', COMMAND_UNKNOWN: 'Commande inconnue. Consultez oftone-agent help.', LEGACY_FOREGROUND: 'Un ancien agent fonctionne au premier plan. Arrêtez-le avec Ctrl+C une seule fois, puis utilisez start.', DAEMON_START_FAILED: 'L’agent n’a pas démarré. Consultez logs.', DAEMON_START_TIMEOUT: 'Démarrage non confirmé. Consultez status et logs avant de réessayer.', DAEMON_STOP_TIMEOUT: 'L’arrêt n’est pas encore confirmé. Consultez status et logs.', AUTH_ROTATION_FAILED: 'Changement d’authentification non confirmé. Réessayez token ou start pour reprendre sans perdre la liaison.', AUTH_ROTATION_INVALID: 'Token d’authentification invalide : 64 caractères hexadécimaux attendus.', PAIRING_INVALID: 'Commande de liaison invalide. Copiez à nouveau la commande complète depuis le dashboard.', PAIRING_REJECTED: 'Commande de liaison expirée, déjà utilisée ou révoquée. Générez-en une nouvelle depuis le bot concerné.', INSTANCE_INVALID: 'Identifiant d’instance invalide. Copiez la commande depuis les paramètres du bot concerné.', INSTANCE_NOT_FOUND: 'Cette liaison n’est pas installée sur cette machine. Utilisez la commande locale de cette instance ou configurez une nouvelle liaison.', INSTANCE_REQUIRED: 'Précisez l’instance avec la commande affichée dans les paramètres du bot ou la commande locale affichée à l’installation.', INSTANCE_EXISTS: 'Cette instance existe déjà. Utilisez sa commande locale de démarrage.', AGENT_ACTIVE: 'Cet agent est déjà actif. Arrêtez uniquement le processus de cette instance avant de le relancer.' }; console.error(messages[error.code || error.message] || 'Échec de l’agent. Vérifiez la liaison et la configuration locale. Aucun secret affiché.'); process.exitCode = 1; });
|
|
342
|
+
main().catch(error => { const messages = { REMOVE_RUNNING:'Arrêtez ce bot avec stop avant de le supprimer de la liste.', LOGIN_BUSY: 'Une connexion Discord est déjà en cours sur cette machine.', LOGIN_EXPIRED: 'La demande a expiré. Relancez npx oft-agt login.', LOGIN_START_FAILED: 'Impossible de démarrer la connexion. Réessayez dans quelques minutes.', LOGIN_FAILED: 'Connexion refusée. Relancez npx oft-agt login.', AUTH_ROTATION_BUSY: 'Une modification de l’authentification est déjà en cours pour cette instance. Réessayez après sa fin.', COMMAND_UNKNOWN: 'Commande inconnue. Consultez oftone-agent help.', LEGACY_FOREGROUND: 'Un ancien agent fonctionne au premier plan. Arrêtez-le avec Ctrl+C une seule fois, puis utilisez start.', DAEMON_START_FAILED: 'L’agent n’a pas démarré. Consultez logs.', DAEMON_START_TIMEOUT: 'Démarrage non confirmé. Consultez status et logs avant de réessayer.', DAEMON_STOP_TIMEOUT: 'L’arrêt n’est pas encore confirmé. Consultez status et logs.', AUTH_ROTATION_FAILED: 'Changement d’authentification non confirmé. Réessayez token ou start pour reprendre sans perdre la liaison.', AUTH_ROTATION_INVALID: 'Token d’authentification invalide : 64 caractères hexadécimaux attendus.', PAIRING_INVALID: 'Commande de liaison invalide. Copiez à nouveau la commande complète depuis le dashboard.', PAIRING_REJECTED: 'Commande de liaison expirée, déjà utilisée ou révoquée. Générez-en une nouvelle depuis le bot concerné.', INSTANCE_INVALID: 'Identifiant d’instance invalide. Copiez la commande depuis les paramètres du bot concerné.', INSTANCE_NOT_FOUND: 'Cette liaison n’est pas installée sur cette machine. Utilisez la commande locale de cette instance ou configurez une nouvelle liaison.', INSTANCE_REQUIRED: 'Précisez l’instance avec la commande affichée dans les paramètres du bot ou la commande locale affichée à l’installation.', INSTANCE_EXISTS: 'Cette instance existe déjà. Utilisez sa commande locale de démarrage.', AGENT_ACTIVE: 'Cet agent est déjà actif. Arrêtez uniquement le processus de cette instance avant de le relancer.' }; console.error(messages[error.code || error.message] || 'Échec de l’agent. Vérifiez la liaison et la configuration locale. Aucun secret affiché.'); process.exitCode = 1; });
|
|
339
343
|
|
package/compile-plan.mjs
CHANGED
|
@@ -46,9 +46,9 @@ export function compile(input) {
|
|
|
46
46
|
if (!plain(command)) fail(`Commande ${commandIndex + 1} : configuration absente.`);
|
|
47
47
|
|
|
48
48
|
validateEventGraph(command);
|
|
49
|
-
const name = text(command.name ?? entry.name, 'Nom
|
|
49
|
+
const name = text(command.name ?? entry.name, 'Nom', command.trigger?.kind==='event'?100:32, true);
|
|
50
50
|
|
|
51
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(name)) fail(`/${name} : utilisez des minuscules, chiffres, tirets ou underscores.`);
|
|
51
|
+
if (command.trigger?.kind!=='event' && !/^[a-z0-9_-]{1,32}$/.test(name)) fail(`/${name} : utilisez des minuscules, chiffres, tirets ou underscores.`);
|
|
52
52
|
|
|
53
53
|
if (names.has(name)) fail(`Commande dupliquée : /${name}.`);
|
|
54
54
|
|
|
@@ -134,7 +134,7 @@ export function compile(input) {
|
|
|
134
134
|
|
|
135
135
|
if (next.has(edge.source)) fail(`/${name}/${edge.source} : plusieurs sorties ne sont pas encore prises en charge. Reliez les actions en séquence.`);
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
|
|
139
139
|
next.set(edge.source, edge.target);
|
|
140
140
|
|
|
@@ -153,7 +153,9 @@ export function compile(input) {
|
|
|
153
153
|
}
|
|
154
154
|
const visited = new Set(options.map(option => option.id));
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
let expanded = 0;
|
|
157
|
+
function compilePath(rootId, inheritedMessages = new Set(), inheritedWait = 0, depth = 0, ancestry = new Set()) {
|
|
158
|
+
const path = new Set(ancestry);
|
|
157
159
|
|
|
158
160
|
if (depth > 32) fail(`/${name} : conditions imbriquées trop profondément (32 maximum).`);
|
|
159
161
|
|
|
@@ -167,7 +169,9 @@ export function compile(input) {
|
|
|
167
169
|
|
|
168
170
|
while (current) {
|
|
169
171
|
|
|
170
|
-
if (
|
|
172
|
+
if (path.has(current)) fail(`/${name} : boucle détectée.`);
|
|
173
|
+
path.add(current);
|
|
174
|
+
if (++expanded > 10000) fail(`/${name} : parcours trop complexe (10 000 étapes maximum).`);
|
|
171
175
|
|
|
172
176
|
visited.add(current);
|
|
173
177
|
|
|
@@ -185,7 +189,7 @@ export function compile(input) {
|
|
|
185
189
|
|
|
186
190
|
const rules = conditionConfigs.get(current).rules;
|
|
187
191
|
|
|
188
|
-
calls.push({ id: current, fn: 'condition', args: { kind: block.type, rules }, branches: rules.map(rule => ({ id: branches.get(rule.id), ruleId: rule.id, calls: compilePath(branches.get(rule.id), emittedMessages, waitBudget, depth + 1) })), otherwise: { id: branches.get('else'), calls: compilePath(branches.get('else'), emittedMessages, waitBudget, depth + 1) } });
|
|
192
|
+
calls.push({ id: current, fn: 'condition', args: { kind: block.type, rules }, branches: rules.map(rule => ({ id: branches.get(rule.id), ruleId: rule.id, calls: compilePath(branches.get(rule.id), emittedMessages, waitBudget, depth + 1, path) })), otherwise: { id: branches.get('else'), calls: compilePath(branches.get('else'), emittedMessages, waitBudget, depth + 1, path) } });
|
|
189
193
|
|
|
190
194
|
} else if (isVariableBlock(block.type)) {
|
|
191
195
|
fn = block.type; args = validateVariableBlock(block.type, config, variableDefinitions);
|
|
@@ -246,8 +250,8 @@ export function compile(input) {
|
|
|
246
250
|
if(children.length) args.buttons=children.map(child=>({id:child.id,config:validateButton(child.config)}));
|
|
247
251
|
for(const child of children){
|
|
248
252
|
const config=validateButton(child.config);
|
|
249
|
-
const childCalls=compilePath(child.id,emittedMessages,0,depth+1);
|
|
250
|
-
buttonBranches.push({id:child.id,parentId:current,config,calls:childCalls});
|
|
253
|
+
const childCalls=compilePath(child.id,emittedMessages,0,depth+1,path);
|
|
254
|
+
if (!buttonBranches.some(branch => branch.id === child.id)) buttonBranches.push({id:child.id,parentId:current,config,calls:childCalls});
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
} else if (block.type === 'apiRequest') { fn='apiRequest'; args=validateApiRequest(config); if([...blocks.values()].some(b=>b.id!==block.id&&b.type==='apiRequest'&&b.config?.name===args.name))fail('Chaque requête API doit avoir un nom unique.');
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oft-agt",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.18",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "oft-agt",
|
|
9
|
-
"version": "0.5.
|
|
9
|
+
"version": "0.5.18",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"discord.js": "^14.14.0",
|
package/package.json
CHANGED
package/runtime/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RUNTIME_VERSION = '0.5.
|
|
1
|
+
export const RUNTIME_VERSION = '0.5.18';
|
package/usage.mjs
CHANGED
|
@@ -12,7 +12,7 @@ export async function createUsageCollector(home, now = () => Date.now()) {
|
|
|
12
12
|
let writing = Promise.resolve();
|
|
13
13
|
return {
|
|
14
14
|
record(event) {
|
|
15
|
-
if (event?.type !== 'usage' ||
|
|
15
|
+
if (event?.type !== 'usage' || (typeof event.command!=='string'||!event.command.trim()||event.command.length>100||/[\u0000-\u001f\u007f]/.test(event.command)) || typeof event.success !== 'boolean' || !Number.isFinite(event.durationMs) || event.durationMs < 0 || event.durationMs > 900000) return;
|
|
16
16
|
const current = Math.floor(now() / HOUR) * HOUR;
|
|
17
17
|
const hour = event.hour ?? current;
|
|
18
18
|
if (!Number.isSafeInteger(hour) || hour % HOUR || hour > current || hour < current - 719 * HOUR) return;
|
package/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_VERSION = '0.5.
|
|
1
|
+
export const AGENT_VERSION = '0.5.18';
|