oft-agt 0.5.17 → 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 +13 -8
- 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
|
@@ -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);
|
|
@@ -203,7 +207,7 @@ export function compile(input) {
|
|
|
203
207
|
|
|
204
208
|
const normal = normalizeMessageConfig(config);
|
|
205
209
|
|
|
206
|
-
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.`);
|
|
207
211
|
|
|
208
212
|
if (config.sendMode && config.replyMode && !['command', 'block', 'message'].includes(config.replyMode)) fail(`/${name}/${current} : mode de réponse inconnu.`);
|
|
209
213
|
|
|
@@ -217,12 +221,13 @@ export function compile(input) {
|
|
|
217
221
|
|
|
218
222
|
if (error) fail(`/${name}/${current} : ${error}`);
|
|
219
223
|
|
|
220
|
-
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.`);
|
|
221
225
|
|
|
222
226
|
// Only accepted arguments are emitted; tokens and unrelated JSON properties are discarded.
|
|
223
227
|
|
|
224
228
|
args = Object.fromEntries(['text', 'embeds', 'sendMode', 'replyMode', 'targetBlockId', 'messageId', 'channelMode', 'channelId', 'createdChannelBlockId', 'dmMode', 'userId', 'ephemeral'].map(key => [key, normal[key]]));
|
|
225
229
|
|
|
230
|
+
if (normal.sendMode === 'edit') args.keepButtons = normal.keepButtons;
|
|
226
231
|
args.embeds = normal.embeds.map(embed => ({
|
|
227
232
|
|
|
228
233
|
...Object.fromEntries(['title', 'description', 'url', 'color', 'timestamp'].filter(key => embed[key] !== undefined).map(key => [key, embed[key]])),
|
|
@@ -246,8 +251,8 @@ export function compile(input) {
|
|
|
246
251
|
if(children.length) args.buttons=children.map(child=>({id:child.id,config:validateButton(child.config)}));
|
|
247
252
|
for(const child of children){
|
|
248
253
|
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});
|
|
254
|
+
const childCalls=compilePath(child.id,emittedMessages,0,depth+1,path);
|
|
255
|
+
if (!buttonBranches.some(branch => branch.id === child.id)) buttonBranches.push({id:child.id,parentId:current,config,calls:childCalls});
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
} 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.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';
|