openvibers/openvibe.actor Commits

A task waiting for its person says so on its record (needs_you: what and the desk's link), so Vibe can ask them in the chat (Desks plan D4) (#133)

OP OpenVibers committed 17 days ago · 986a193733 · 3 files, +41 −2

Changes

ddiff --git a/server/http/api.js b/server/http/api.js
iindex dcfcc9d..9f533fe 100644
---- a/server/http/api.js
++++ b/server/http/api.js
@@@ -172,7 +172,12 @@ function createApi(ctx) {
r.get('/tasks/:id', principal.requireCapability('actor.task.read'), limits.reads('actor.task.read'), async (req, res) => {
const row = await load(req, res);
-- if (row) res.json(wire(row));
++ if (!row) return;
++ const out = wire(row);
++ // Waiting for its person on its desk (Desks plan D4): what, and the link to the desk.
++ const needs = await store.needsYou(s, row);
++ if (needs) out.needs_you = needs;
++ res.json(out);
});
r.get('/tasks/:id/events', principal.requireCapability('actor.task.read'), limits.budget('actor.task.stream'), async (req, res) => {
ddiff --git a/server/tasks/store.js b/server/tasks/store.js
iindex ac0b013..596485d 100644
---- a/server/tasks/store.js
++++ b/server/tasks/store.js
@@@ -66,6 +66,27 @@ async function appendEvent(s, taskId, body) {
});
}
++/** A task's newest event, or null. */
++async function lastEvent(s, taskId) {
++ const row = await s.db.maybe('SELECT body FROM task_events WHERE task_id = $1 ORDER BY seq DESC LIMIT 1', [taskId]);
++ if (!row) return null;
++ return typeof row.body === 'string' ? JSON.parse(row.body) : row.body;
++}
++
++/**
++ * Desks plan D4: while an agent waits for its person (hand_to_person), its newest step is the "Needs you" line, until
++ * the person has used the desk and let go. Then the task's record says so (needs_you: what, the link to the desk and
++ * the step's seq), so whatever shows the task (Vibe's chat) can ask the person where they are.
++ */
++async function needsYou(s, row) {
++ if (!row || !OPEN.includes(row.state)) return null;
++ const e = await lastEvent(s, row.id);
++ if (!e || e.step !== 'text' || !/^Needs you: /.test(String(e.text || ''))) return null;
++ const text = String(e.text).replace(/^Needs you: /, '');
++ const url = (text.match(/https:\/\/\S+$/) || [])[0] || null;
++ return { seq: Number(e.seq), what: text.replace(/\s*Open your agent's desk.*$/s, '').trim().slice(0, 300), url };
++}
++
async function eventsAfter(s, taskId, after = 0) {
const rows = await s.db.many('SELECT body FROM task_events WHERE task_id = $1 AND seq > $2 ORDER BY seq', [taskId, after]);
return rows.map((r) => parse(r.body));
@@@ -160,4 +181,4 @@ function toWire(row, { baseUrl }) {
return t;
}
--module.exports = { insertTask, getTask, byIdempotency, listTasks, updateTask, appendEvent, eventsAfter, addSpend, spendToday, spendDays, lockSpendToday, failInterrupted, prune, toWire, subjectRef, dayOf, parseJson: parse, OPEN, RETENTION_DAYS };
++module.exports = { insertTask, getTask, byIdempotency, listTasks, updateTask, appendEvent, eventsAfter, lastEvent, needsYou, addSpend, spendToday, spendDays, lockSpendToday, failInterrupted, prune, toWire, subjectRef, dayOf, parseJson: parse, OPEN, RETENTION_DAYS };
ddiff --git a/test/desk-handoff.test.js b/test/desk-handoff.test.js
iindex aa9b948..b83d7ea 100644
---- a/test/desk-handoff.test.js
++++ b/test/desk-handoff.test.js
@@@ -110,6 +110,19 @@ const toolsFor = (bot) => createOpenVibeTools({ config, fetchImpl: bot.fetchImpl
assert.ok(!page.text.includes('href="https://evil.example'), 'another address stays text');
assert.ok(page.text.includes('data-bot="https://openvibe.bot"'));
});
++ await check('the task\'s record says it needs its person while the hand-off waits, and stops saying so after', async () => {
++ const bea = t.network.addUser('desk-bea');
++ const id = t.ctx.s.newId('tsk');
++ await store.insertTask(t.ctx.s, { id, requester: `user:${bea.subject}`, task: 'Book it', mode: 'balanced', budget_task: 0.05, budget_day: 1, created_at: t.ctx.s.iso() });
++ const watch = `https://openvibe.bot/machines/${PC}/screen?desk=desk-0123456789`;
++ await store.appendEvent(t.ctx.s, id, { kind: 'output', step: 'plan', agent: 'openvibe-runtime', text: 'OpenVibe runtime takes it.' });
++ assert.strictEqual((await t.get(`/api/v1/tasks/${id}`, { as: bea })).json().needs_you, undefined, 'nothing waits yet');
++ const e = await store.appendEvent(t.ctx.s, id, { kind: 'output', step: 'text', text: `Needs you: Sign in to the airline. Open your agent's desk, take it over, do it, then let go: ${watch}` });
++ const waiting = (await t.get(`/api/v1/tasks/${id}`, { as: bea })).json();
++ assert.deepStrictEqual(waiting.needs_you, { seq: e.seq, what: 'Sign in to the airline.', url: watch });
++ await store.appendEvent(t.ctx.s, id, { kind: 'output', step: 'tool_result', tool: 'hand_to_person', text: '{"done":true}' });
++ assert.strictEqual((await t.get(`/api/v1/tasks/${id}`, { as: bea })).json().needs_you, undefined, 'the person let go: nothing waits');
++ });
await t.close();
done();
})();