openvibers/openvibe.codes Commits

One 'make' mode: new or existing, worked out from the words (#448)

OP OpenVibers committed 2 h ago · 1319694bc2 · 2 files, +51 −4

Changes

ddiff --git a/server/http/tasks-api.js b/server/http/tasks-api.js
iindex 2b0a596..f497d88 100644
---- a/server/http/tasks-api.js
++++ b/server/http/tasks-api.js
@@@ -44,11 +44,31 @@ const USER = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const TASK = /^tsk_[0-9A-HJKMNP-TV-Z]{26}$/;
const FIL = /^fil_[0-9A-HJKMNP-TV-Z]{26}$/;
// The Dock's Code tab shape (the home composer's three ways, as JSON): POST /tasks dispatches on it.
--const DOCK_MODES = ['create', 'openvibe', 'repo'];
++const DOCK_MODES = ['create', 'openvibe', 'repo', 'make'];
++// A Git address in the words: a URL on a Git host, or git@host:owner/name. A bare owner/name counts only when it is one
++// of the person's own recent repositories (prose like "and/or" is not a repository).
++const GIT_URL = /(?:https?:\/\/(?:www\.)?(?:github\.com|gitlab\.com|codeberg\.org|bitbucket\.org|git\.openvibe\.codes)\/[\w.-]+\/[\w.-]+|git@[\w.-]+:[\w.-]+\/[\w.-]+)/i;
function createTasksApi({ config, store, keys, run, harnesses, actorLimits, tasks = null, modelPlans = null, fleet = null, sharing = null, conductor = null, attachments = null, races = null, log = console }) {
const r = asyncRouter();
const db = store.db;
++ /** The repository the words name: a Git address in them, or one of the person's recent repositories by its name. */
++ async function repoFromWords(subject, text) {
++ const hit = GIT_URL.exec(text);
++ if (hit && start.normalizeRepo(hit[0])) return hit[0];
++ let rows = [];
++ try { rows = await db.many(`SELECT DISTINCT repo FROM run_tasks WHERE subject = $1 AND repo IS NOT NULL AND repo <> '' AND (mode IS NULL OR mode <> 'openvibe') ORDER BY repo LIMIT 50`, [subject]); } catch { rows = []; }
++ const words = String(text).toLowerCase();
++ for (const r of rows) {
++ const full = String(r.repo || '').replace(/\.git$/, '');
++ const path = full.replace(/^https?:\/\/[^/]+\//, '').toLowerCase();
++ const name = path.split('/').pop();
++ if (!name || name.length < 3) continue;
++ const esc = (x) => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
++ if (new RegExp(`(^|[^\\w-])(${esc(path)}|${esc(name)})([^\\w-]|$)`).test(words)) return full;
++ }
++ return '';
++ }
const taskStore = tasks || createRunTasks({ store, run, config, log });
const taskEvents = createTaskEvents({ store, config, log, attempts: taskStore.attempts });
const problem = (req, res, status, code, detail) => http.sendProblem(res, status, code, { detail, ctx: req.ov });
@@@ -259,10 +279,17 @@ function createTasksApi({ config, store, keys, run, harnesses, actorLimits, task
}
const first = (v) => (Array.isArray(v) ? v[0] : v);
const text = String(first(b.text) || '').trim().slice(0, 16000);
-- const mode = String(first(b.mode) || '').trim();
-- if (!DOCK_MODES.includes(mode)) return problem(req, res, 422, 'codes.task_invalid', 'mode is create, openvibe or repo');
++ let mode = String(first(b.mode) || '').trim();
++ if (!DOCK_MODES.includes(mode)) return problem(req, res, 422, 'codes.task_invalid', 'mode is make, openvibe, create or repo');
if (!text) return problem(req, res, 422, 'codes.task_invalid', 'write what you want made or changed first');
-- const repoIn = String(first(b.repo) || '').trim().slice(0, 500);
++ let repoIn = String(first(b.repo) || '').trim().slice(0, 500);
++ // Make or change (2026-11-13, the owner: "combine … create and my code … infer that information based on the
++ // prompt"): a repository named in the words, or one of yours mentioned by name, means work on it; otherwise it
++ // is something new.
++ if (mode === 'make') {
++ if (!repoIn) repoIn = await repoFromWords(p.subject, text);
++ mode = repoIn ? 'repo' : 'create';
++ }
const named = [
...(Array.isArray(b.attachments) ? b.attachments : b.attachments ? [b.attachments] : []),
...(Array.isArray(b.attachment_ids) ? b.attachment_ids : b.attachment_ids ? [b.attachment_ids] : []),
ddiff --git a/test/dock-tasks.test.js b/test/dock-tasks.test.js
iindex 20e0864..0faeef2 100644
---- a/test/dock-tasks.test.js
++++ b/test/dock-tasks.test.js
@@@ -104,6 +104,26 @@ function assertSummary(s) {
assert.ok(String(r.json().url).startsWith('https://openvibe.space'), r.json().url);
});
++ await check('make works out new or existing from the words (2026-11-13: Create and My code are one)', async () => {
++ // Nothing named and nothing of yours mentioned: something new, handed over like create.
++ let r = await api('POST', '/tasks', { as: ada, body: { text: 'A bakery site with today\u2019s hours', mode: 'make' } });
++ assert.strictEqual(r.status, 200, r.text);
++ assert.strictEqual(r.json().id, null, 'something new: no task yet');
++ assert.ok(String(r.json().url).startsWith('https://openvibe.space'), r.json().url);
++ // A Git address in the words: work on that repository.
++ r = await api('POST', '/tasks', { as: ada, body: { text: 'Add a test for the date parser in https://github.com/octocat/Hello-World', mode: 'make' } });
++ assert.ok(r.status === 200 || r.status === 201, r.text);
++ assert.match(String(r.json().id), /^tsk_/, 'a task on the named repository');
++ let row = await t.ctx.store.db.maybe('SELECT mode, repo FROM run_tasks WHERE id = $1', [r.json().id]);
++ assert.strictEqual(row.mode, 'repo');
++ // One of yours by its name (the task above made Hello-World one of ada's repositories).
++ r = await api('POST', '/tasks', { as: ada, body: { text: 'Now fix the README typo in hello-world', mode: 'make' } });
++ assert.ok(r.status === 200 || r.status === 201, r.text);
++ assert.match(String(r.json().id), /^tsk_/, 'a task on your repository, found by its name');
++ row = await t.ctx.store.db.maybe('SELECT mode, repo FROM run_tasks WHERE id = $1', [r.json().id]);
++ assert.match(String(row.repo), /octocat\/Hello-World/i);
++ });
++
await check('openvibe mode works out the project from the words, like the site', async () => {
const r = await api('POST', '/tasks', { as: ada, body: { text: 'Make Live faster on phones', mode: 'openvibe' } });
assert.strictEqual(r.status, 201, r.text);