oft-agt 0.5.18 → 0.5.19
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 +25 -8
- package/compile-plan.mjs +3 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/execute.mjs +3 -3
- package/runtime/functions.mjs +8 -0
- package/runtime/message-config.mjs +2 -1
- package/runtime/version.mjs +1 -1
- package/update.mjs +13 -5
- package/version.mjs +1 -1
package/cli.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { acceptSecret } from './secret.mjs';
|
|
|
18
18
|
import { createUsageCollector } from './usage.mjs';
|
|
19
19
|
import { instanceDirectory, resolveInstance, listInstances, rememberInstance } from './instance.mjs';
|
|
20
20
|
import { AGENT_VERSION } from './version.mjs';
|
|
21
|
-
import { installOfficialUpdate } from './update.mjs';
|
|
21
|
+
import { installOfficialUpdate, currentDirectory, newer } from './update.mjs';
|
|
22
22
|
import { supervise } from './supervisor.mjs';
|
|
23
23
|
|
|
24
24
|
const baseHome = process.env.OFTONE_AGENT_HOME || path.join(homedir(), '.oftone-agent');
|
|
@@ -233,6 +233,7 @@ function printHelp() {
|
|
|
233
233
|
start | run Démarrer en arrière-plan, sans ressaisir le token
|
|
234
234
|
stop Arrêter l’agent et son bot, conserver la configuration
|
|
235
235
|
restart Redémarrer en arrière-plan
|
|
236
|
+
update | upgrade Mettre à jour l’instance et redémarrer sans perdre sa configuration
|
|
236
237
|
status Afficher l’état local
|
|
237
238
|
logs Lire les 100 dernières lignes du journal
|
|
238
239
|
token [--stdin] Régénérer l’authentification agent ; --stdin lit 64 caractères hexadécimaux
|
|
@@ -243,9 +244,16 @@ Options : --bot=IDENTIFIANT_COURT (voir list) pour choisir un bot ; start --fore
|
|
|
243
244
|
Après un redémarrage du serveur : start. La configuration est conservée sur disque.
|
|
244
245
|
Documentation : https://doc-builder.oftone.fr/agent`);
|
|
245
246
|
}
|
|
247
|
+
async function installSource(home, source) {
|
|
248
|
+
const install = path.join(home, 'installation');
|
|
249
|
+
await mkdir(install, { recursive: true, mode: 0o700 });
|
|
250
|
+
if (path.resolve(source) !== path.resolve(install)) for (const entry of ['cli.mjs', 'instance.mjs', 'version.mjs', 'release-key.mjs', 'update.mjs', 'supervisor.mjs', 'secret.mjs', 'usage.mjs', 'compiler.mjs', 'compile-plan.mjs', 'package.json', 'npm-shrinkwrap.json', 'runtime', 'templates']) await cp(path.join(source, entry), path.join(install, entry), { recursive: true });
|
|
251
|
+
await save(path.join(home, 'agent-release.json'), { directory: 'installation' });
|
|
252
|
+
}
|
|
253
|
+
|
|
246
254
|
async function main() {
|
|
247
255
|
const [requestedCommand, ...args] = process.argv.slice(2);
|
|
248
|
-
const command = requestedCommand==='delete'?'remove':requestedCommand || 'login';
|
|
256
|
+
const command = requestedCommand==='delete'?'remove':requestedCommand==='update'?'upgrade':requestedCommand || 'login';
|
|
249
257
|
const source = path.dirname(fileURLToPath(import.meta.url));
|
|
250
258
|
if (['help', '--help', '-h'].includes(command)) { printHelp(); return; }
|
|
251
259
|
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; }
|
|
@@ -302,22 +310,31 @@ async function main() {
|
|
|
302
310
|
if (command === 'upgrade') {
|
|
303
311
|
await stopBackground(home);
|
|
304
312
|
const source = path.dirname(fileURLToPath(import.meta.url));
|
|
305
|
-
|
|
306
|
-
await mkdir(install, { recursive: true, mode: 0o700 });
|
|
307
|
-
if (path.resolve(source) !== path.resolve(install)) for (const entry of ['cli.mjs', 'instance.mjs', 'version.mjs', 'release-key.mjs', 'update.mjs', 'supervisor.mjs', 'secret.mjs', 'usage.mjs', 'compiler.mjs', 'compile-plan.mjs', 'package.json', 'npm-shrinkwrap.json', 'runtime', 'templates']) await cp(path.join(source, entry), path.join(install, entry), { recursive: true });
|
|
308
|
-
await save(path.join(home, 'agent-release.json'), { directory: 'installation' });
|
|
313
|
+
await installSource(home, source);
|
|
309
314
|
console.log('Agent mis à jour.');
|
|
310
315
|
if (args.includes('--foreground')) { process.env.OFTONE_FOREGROUND = '1'; return supervise(home, source); }
|
|
311
316
|
await startBackground(home, source); console.log('Agent démarré en arrière-plan.'); return;
|
|
312
317
|
}
|
|
313
318
|
if (['run', 'start', 'restart'].includes(command)) {
|
|
314
|
-
|
|
319
|
+
const installed = await currentDirectory(home, source);
|
|
320
|
+
const installedVersion = JSON.parse(await readFile(path.join(installed, 'package.json'), 'utf8')).version;
|
|
321
|
+
const upgrading = newer(AGENT_VERSION, installedVersion);
|
|
322
|
+
if (command === 'restart' || upgrading) await stopBackground(home);
|
|
323
|
+
if (upgrading) { await installSource(home, source); console.log('Agent mis à jour vers ' + AGENT_VERSION + '.'); }
|
|
315
324
|
await completeCredentialRotation(home, JSON.parse(await readFile(configPath, 'utf8')));
|
|
316
325
|
if (args.includes('--foreground')) { process.env.OFTONE_FOREGROUND = '1'; return supervise(home, source); }
|
|
317
326
|
await startBackground(home, source); console.log('Agent actif en arrière-plan. Aucun token à ressaisir.'); return;
|
|
318
327
|
}
|
|
319
328
|
if (command === 'stop') { await stopBackground(home); console.log('Agent arrêté. Sa configuration est conservée.'); return; }
|
|
320
|
-
if (command === 'status') {
|
|
329
|
+
if (command === 'status') {
|
|
330
|
+
const state = await localStatus(home);
|
|
331
|
+
console.log(state.legacy ? 'Agent actif au premier plan (ancienne installation).' : state.running ? 'Agent actif en arrière-plan.' : state.starting ? 'Agent en cours de démarrage.' : 'Agent arrêté.');
|
|
332
|
+
const installed = await currentDirectory(home, source);
|
|
333
|
+
const version = JSON.parse(await readFile(path.join(installed, 'package.json'), 'utf8')).version;
|
|
334
|
+
console.log('Instance installée : ' + version + ' · CLI : ' + AGENT_VERSION);
|
|
335
|
+
if (newer(AGENT_VERSION, version)) console.log('Exécutez update pour appliquer la nouvelle version à cette instance.');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
321
338
|
if (command === 'logs') { process.stdout.write(await readLogs(home)); return; }
|
|
322
339
|
if (command === 'token') {
|
|
323
340
|
const state = await localStatus(home); await stopBackground(home);
|
package/compile-plan.mjs
CHANGED
|
@@ -207,7 +207,7 @@ export function compile(input) {
|
|
|
207
207
|
|
|
208
208
|
const normal = normalizeMessageConfig(config);
|
|
209
209
|
|
|
210
|
-
if (!['reply', 'channel', 'dm'].includes(normal.sendMode)) fail(`/${name}/${current} : type d’envoi inconnu.`);
|
|
210
|
+
if (!['reply', 'channel', 'dm', 'edit'].includes(normal.sendMode)) fail(`/${name}/${current} : type d’envoi inconnu.`);
|
|
211
211
|
|
|
212
212
|
if (config.sendMode && config.replyMode && !['command', 'block', 'message'].includes(config.replyMode)) fail(`/${name}/${current} : mode de réponse inconnu.`);
|
|
213
213
|
|
|
@@ -221,12 +221,13 @@ export function compile(input) {
|
|
|
221
221
|
|
|
222
222
|
if (error) fail(`/${name}/${current} : ${error}`);
|
|
223
223
|
|
|
224
|
-
if (normal.sendMode === 'reply' && normal.replyMode === 'block' && !emittedMessages.has(normal.targetBlockId)) fail(`/${name}/${current} : le message cible doit être envoyé avant la réponse.`);
|
|
224
|
+
if ((normal.sendMode === 'edit' || normal.sendMode === 'reply' && normal.replyMode === 'block') && !emittedMessages.has(normal.targetBlockId)) fail(`/${name}/${current} : le message cible doit être envoyé avant la réponse.`);
|
|
225
225
|
|
|
226
226
|
// Only accepted arguments are emitted; tokens and unrelated JSON properties are discarded.
|
|
227
227
|
|
|
228
228
|
args = Object.fromEntries(['text', 'embeds', 'sendMode', 'replyMode', 'targetBlockId', 'messageId', 'channelMode', 'channelId', 'createdChannelBlockId', 'dmMode', 'userId', 'ephemeral'].map(key => [key, normal[key]]));
|
|
229
229
|
|
|
230
|
+
if (normal.sendMode === 'edit') args.keepButtons = normal.keepButtons;
|
|
230
231
|
args.embeds = normal.embeds.map(embed => ({
|
|
231
232
|
|
|
232
233
|
...Object.fromEntries(['title', 'description', 'url', 'color', 'timestamp'].filter(key => embed[key] !== undefined).map(key => [key, embed[key]])),
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oft-agt",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
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.19",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"discord.js": "^14.14.0",
|
package/package.json
CHANGED
package/runtime/execute.mjs
CHANGED
|
@@ -60,7 +60,7 @@ async function executeAdmitted(command, interaction, client, options) {
|
|
|
60
60
|
|
|
61
61
|
for (const call of calls) {
|
|
62
62
|
|
|
63
|
-
if (++count >
|
|
63
|
+
if (++count > 10000) throw new Error('Plan d’exécution trop volumineux.');
|
|
64
64
|
|
|
65
65
|
if (call.fn === 'condition') {
|
|
66
66
|
|
|
@@ -135,7 +135,7 @@ async function executeAdmitted(command, interaction, client, options) {
|
|
|
135
135
|
context.signal.throwIfAborted();
|
|
136
136
|
|
|
137
137
|
const args=await (call.fn==='apiRequest'?interpolateApiArguments:interpolateVariables)(call.args,context.variableOptions);
|
|
138
|
-
const prepared=call.fn==='sendMessage'&&args.buttons?.length?(options.buttonRouter||buttonRouter).prepare(command,args.buttons,context,optionValues):null;
|
|
138
|
+
const prepared=call.fn==='sendMessage'&&!(args.sendMode==='edit'&&args.keepButtons!==false)&&args.buttons?.length?(options.buttonRouter||buttonRouter).prepare(command,args.buttons,context,optionValues):null;
|
|
139
139
|
const invoke=async()=>{try{return await registry[call.fn](context,{...args,...(prepared?{components:prepared.components}:{})});}catch(error){prepared?.cancel();throw error;}};
|
|
140
140
|
|
|
141
141
|
let result;try{result=call.fn==='wait'?await invoke():await options.scheduler.run(invoke,{signal:context.signal});}catch(error){prepared?.cancel();throw error;}
|
|
@@ -162,7 +162,7 @@ async function executeAdmitted(command, interaction, client, options) {
|
|
|
162
162
|
context.signal.throwIfAborted();
|
|
163
163
|
|
|
164
164
|
const args=interpolateError(await (call.fn==='apiRequest'?interpolateApiArguments:interpolateVariables)(call.args,{...context.variableOptions,base:{...context.variableOptions.base,...optionValues,...variables}}),variables);
|
|
165
|
-
const prepared=call.fn==='sendMessage'&&args.buttons?.length?(options.buttonRouter||buttonRouter).prepare(command,args.buttons,context,{...optionValues,...variables}):null;
|
|
165
|
+
const prepared=call.fn==='sendMessage'&&!(args.sendMode==='edit'&&args.keepButtons!==false)&&args.buttons?.length?(options.buttonRouter||buttonRouter).prepare(command,args.buttons,context,{...optionValues,...variables}):null;
|
|
166
166
|
const invoke=async()=>{try{return await registry[call.fn](context,{...args,...(prepared?{components:prepared.components}:{})});}catch(error){prepared?.cancel();throw error;}};
|
|
167
167
|
|
|
168
168
|
let result;try{result=call.fn==='wait'?await invoke():await options.scheduler.run(invoke,{signal:context.signal});}catch(error){prepared?.cancel();throw error;}
|
package/runtime/functions.mjs
CHANGED
|
@@ -63,6 +63,14 @@ async function sendMessage(context, parameters) {
|
|
|
63
63
|
|
|
64
64
|
const { interaction, client, messages } = context;
|
|
65
65
|
|
|
66
|
+
if (config.sendMode === 'edit') {
|
|
67
|
+
const message = messages.get(config.targetBlockId);
|
|
68
|
+
if (!message?.edit || !client.user?.id || message.author?.id !== client.user.id) throw runtimeError('MESSAGE_NOT_FOUND');
|
|
69
|
+
const editedPayload = { ...payload, content: config.text };
|
|
70
|
+
if (config.keepButtons) delete editedPayload.components;
|
|
71
|
+
else editedPayload.components = parameters.components || [];
|
|
72
|
+
return message.edit(editedPayload);
|
|
73
|
+
}
|
|
66
74
|
if (interaction.eventTrigger && config.ephemeral) throw runtimeError('MESSAGE_INVALID');
|
|
67
75
|
if (interaction.eventTrigger && config.sendMode === 'reply' && config.replyMode === 'command') {
|
|
68
76
|
if (interaction.eventTrigger !== 'messageCreate' || !interaction.eventMessage?.reply) throw runtimeError('MESSAGE_NOT_FOUND');
|
|
@@ -29,6 +29,7 @@ export function normalizeMessageConfig(config = {}) {
|
|
|
29
29
|
userId: config.userId || '',
|
|
30
30
|
|
|
31
31
|
ephemeral: Boolean(config.ephemeral),
|
|
32
|
+
keepButtons: config.keepButtons !== false,
|
|
32
33
|
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -96,7 +97,7 @@ export function messageConfigError(raw, nodes, allowVariables = true) {
|
|
|
96
97
|
|
|
97
98
|
if (total > 6000) return 'L’ensemble des embeds est limité à 6 000 caractères.';
|
|
98
99
|
|
|
99
|
-
if (config.sendMode === 'reply' && config.replyMode === 'block' && (!config.targetBlockId || (nodes && !nodes.some(node => (node.id === config.targetBlockId && (node.type === 'sendMessage' || node.data?.kind === 'message')))))) return 'Choisissez un bloc de message existant.';
|
|
100
|
+
if ((config.sendMode === 'edit' || config.sendMode === 'reply' && config.replyMode === 'block') && (!config.targetBlockId || (nodes && !nodes.some(node => (node.id === config.targetBlockId && (node.type === 'sendMessage' || node.data?.kind === 'message')))))) return 'Choisissez un bloc de message existant.';
|
|
100
101
|
|
|
101
102
|
if (config.sendMode === 'channel' && config.channelMode === 'created' && (!config.createdChannelBlockId || (nodes && !nodes.some(n=>n.id===config.createdChannelBlockId && (n.type==='createChannel'||n.data?.kind==='createChannel'))))) return 'Choisissez un bloc de création de salon.';
|
|
102
103
|
|
package/runtime/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RUNTIME_VERSION = '0.5.
|
|
1
|
+
export const RUNTIME_VERSION = '0.5.19';
|
package/update.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, verify } from 'node:crypto';
|
|
2
2
|
import { gunzipSync } from 'node:zlib';
|
|
3
|
-
import { readFile, writeFile, mkdir, rename, access, symlink } from 'node:fs/promises';
|
|
3
|
+
import { readFile, writeFile, mkdir, rename, access, symlink, realpath } from 'node:fs/promises';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { RELEASE_PUBLIC_KEY } from './release-key.mjs';
|
|
@@ -80,6 +80,16 @@ function run(args, cwd) {
|
|
|
80
80
|
child.once('exit', code => { clearTimeout(timer); code === 0 ? resolve() : reject(new Error('UPDATE_CHECK')); });
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
+
// Package version changes do not change the installed dependency tree.
|
|
84
|
+
export function sameDependencies(a, b) {
|
|
85
|
+
const normalize = bytes => { const lock = JSON.parse(bytes.toString()); delete lock.version; if (lock.packages?.['']) delete lock.packages[''].version; return lock; };
|
|
86
|
+
return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b));
|
|
87
|
+
}
|
|
88
|
+
export async function findNpmCli() {
|
|
89
|
+
const candidates = [process.env.npm_execpath, path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js'), ...String(process.env.PATH || '').split(path.delimiter).map(dir => path.join(dir, 'npm'))].filter(Boolean);
|
|
90
|
+
for (const candidate of candidates) { try { const resolved = await realpath(candidate); if (path.basename(resolved) === 'npm-cli.js') return resolved; } catch {} }
|
|
91
|
+
throw new Error('UPDATE_DEPENDENCIES');
|
|
92
|
+
}
|
|
83
93
|
export async function installUpdate(release, { home, currentVersion, source, report = () => {}, fetchRelease = fetch, publicKey = RELEASE_PUBLIC_KEY, prepareDependencies, check } = {}) {
|
|
84
94
|
const url = validateRelease(release, currentVersion, publicKey);
|
|
85
95
|
report('downloading');
|
|
@@ -99,12 +109,10 @@ export async function installUpdate(release, { home, currentVersion, source, rep
|
|
|
99
109
|
if (prepareDependencies) await prepareDependencies(destination);
|
|
100
110
|
else {
|
|
101
111
|
const oldLock = await readFile(path.join(source, 'npm-shrinkwrap.json'));
|
|
102
|
-
if (oldLock
|
|
112
|
+
if (sameDependencies(oldLock, entries.get('npm-shrinkwrap.json'))) {
|
|
103
113
|
try { await access(path.join(destination, 'node_modules')); } catch { await symlink(path.join(source, 'node_modules'), path.join(destination, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir'); }
|
|
104
114
|
} else {
|
|
105
|
-
const npm =
|
|
106
|
-
if (path.basename(npm) !== 'npm-cli.js') throw new Error('UPDATE_DEPENDENCIES');
|
|
107
|
-
await access(npm);
|
|
115
|
+
const npm = await findNpmCli();
|
|
108
116
|
await run([npm, 'ci', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund', '--registry=https://registry.npmjs.org'], destination);
|
|
109
117
|
}
|
|
110
118
|
}
|
package/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_VERSION = '0.5.
|
|
1
|
+
export const AGENT_VERSION = '0.5.19';
|