openvibers/openvibe.network Commits
HO1, universal approvals: the hub shows the kind of every act that waits for you (#652)
Changes
@@@ -54,7 +54,10 @@<div class="page"><!-- Needs you (plan OpenVibe_Seven_Domains_Plan §4): the Action Center's waiting items, first on the screen.my/today.js fills it from GET /api/v1/me/actions; a waiting item has one button (Approve, Try again, or-- Open), and an empty queue leaves the section out. The hero and the sections follow unchanged. -->++ Open), and an empty queue leaves the section out. The hero and the sections follow unchanged.++ An approval says what kind of act it is in plain words (plan HO1: publishing for others to see, sending++ something to someone, spending your money), which agent asked and in which thread; the agent's name is++ shown, never its id. Review opens the approval itself — the hub never approves with one tap. --><section class="ny" id="needs-you" hidden aria-labelledby="needs-you-h"><div class="ny-h"><h2 id="needs-you-h"><span class="ny-dot" aria-hidden="true"></span>Needs you</h2>@@@ -41,6 +41,10 @@ const svc = http.createServer((req, res) => {if (u.pathname === '/actor/api/v1/actions') return json(200, { items: [item(cancelled ? { state: 'cancelled', bucket: 'failed', actions: ['open'], updated_at: iso(1) } : {}),item({ id: 'apr_1', kind: 'approval', title: 'Spend $0.40 on a desk hour', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(5), href: 'https://openvibe.actor/me/approvals' }),++ // HO1: an approval of one of the new kinds, with the kind in plain words, the agent by its name and++ // its own page.++ item({ id: 'apr_publish', kind: 'approval', title: 'Publish your post on openvibe.blog', summary: 'publishing for others to see · Weekly digest', agent: 'Vibe', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(2), href: 'https://openvibe.actor/me/approvals/apr_publish' }),++ item({ id: 'apr_send', kind: 'approval', title: 'Send one email to the venue', summary: 'sending something to someone · Trip planner', agent: 'Trip planner', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(3), href: 'https://openvibe.actor/me/approvals/apr_send' }),item({ id: 'tsk_bad', href: 'http://openvibe.actor/tasks/x' }),item({ id: 'tsk_spoof', service: 'codes' }),item({ id: 'agt_1', bucket: 'scheduled', state: 'scheduled', next_run_at: new Date(T0 + 3600000).toISOString(), actions: ['open'] }),@@@ -96,8 +100,16 @@ try {assert.ok(validate('network.me-actions-result@1', doc).valid, JSON.stringify(validate('network.me-actions-result@1', doc).errors));assert.deepStrictEqual(doc.sources, { network: 'ok', actor: 'ok', codes: 'not_connected', ai: 'timeout', domains: 'ok' });assert.strictEqual(doc.partial, true, 'a timeout makes the answer partial');-- assert.deepStrictEqual(doc.counts, { running: 1, waiting: 3, scheduled: 1, done: 0, failed: 1 });-- assert.deepStrictEqual(doc.buckets.waiting.map((x) => x.id), ['cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'apr_1', 'dpr_1'], 'newest first');++ assert.deepStrictEqual(doc.counts, { running: 1, waiting: 5, scheduled: 1, done: 0, failed: 1 });++ assert.deepStrictEqual(doc.buckets.waiting.map((x) => x.id), ['cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'apr_publish', 'apr_send', 'apr_1', 'dpr_1'], 'newest first');++ // The new kinds reach the hub as they are (plan HO1): the kind in plain words, the agent by its name, and++ // the approval's own page. The person reviews it there; the hub never approves with one tap.++ const published = doc.buckets.waiting.find((x) => x.id === 'apr_publish');++ assert.deepStrictEqual([published.kind, published.bucket, published.state, published.agent], ['approval', 'waiting', 'pending', 'Vibe']);++ assert.match(published.summary, /publishing for others to see/, 'the kind in plain words');++ assert.match(published.summary, /Weekly digest/, 'the thread it came from');++ assert.strictEqual(published.href, 'https://openvibe.actor/me/approvals/apr_publish');++ assert.deepStrictEqual(published.actions, ['approve', 'decline', 'open']);const cnf = doc.buckets.waiting[0];assert.deepStrictEqual([cnf.service, cnf.kind, cnf.title, cnf.summary, cnf.href, cnf.actions], ['network', 'approval', 'Send one email to the venue', 'Trip planner asks to use mail.send', 'https://openvibe.network/my#approvals', ['approve', 'decline', 'open']]);const all = Object.values(doc.buckets).flat().map((x) => x.id);@@@ -165,11 +165,19 @@ assert.match(CSS, /@media \(min-width:1000px\)\{[\s\S]*\.settings\{display:grid;p.sandbox.renderNeedsYou({ buckets: { waiting: [{ service: 'actor', id: 'apr_1', kind: 'approval', title: 'Run a command', agent: 'agt01M6VEKFK36XWYB3CSY1RE60YN', href: 'https://openvibe.actor/me/approvals/apr_1', actions: ['approve', 'open'] },{ service: 'actor', id: 'tsk_2', kind: 'agent_task', title: 'Summarise the inbox', href: 'https://openvibe.actor/tasks/tsk_2', actions: ['retry', 'open'] },++ // An approval of one of the new kinds (plan HO1): the kind in plain words, the agent by its name, and++ // the approval's own page. Review opens it; the hub never approves with one tap.++ { service: 'actor', id: 'apr_publish', kind: 'approval', title: 'Publish your post on openvibe.blog', summary: 'publishing for others to see · Weekly digest', agent: 'Vibe', href: 'https://openvibe.actor/me/approvals/apr_publish', actions: ['approve', 'decline', 'open'] },] } });const two = p.ids['needs-you-list'].children;assert.ok(!/agt01M6/.test(two[0].textContent), 'a raw agent id is left out');assert.strictEqual(two[0].all((c) => c.tagName === 'A')[0].href, 'https://openvibe.actor/me/approvals/apr_1', 'Review opens the approval');assert.strictEqual(two[1].all((c) => c.tagName === 'BUTTON')[0].textContent, 'Try again');++ assert.match(two[2].textContent, /publishing for others to see/, 'the kind of the act is in plain words');++ assert.match(two[2].textContent, /Weekly digest/, 'the thread it came from');++ assert.match(two[2].textContent, /Vibe/, 'the agent by its name');++ assert.strictEqual(two[2].all((c) => c.tagName === 'A')[0].href, 'https://openvibe.actor/me/approvals/apr_publish');++ assert.strictEqual(two[2].all((c) => c.tagName === 'BUTTON').length, 0, 'no one-tap approve on an approval');p.sandbox.renderNeedsYou({ buckets: { waiting: [] } });assert.strictEqual(p.ids['needs-you'].hidden, true, 'an empty queue leaves the section out');
@@ -54,7 +54,10 @@
@@ -54,7 +54,10 @@
<div class="page">
<div class="page">
<!-- Needs you (plan OpenVibe_Seven_Domains_Plan §4): the Action Center's waiting items, first on the screen.
<!-- Needs you (plan OpenVibe_Seven_Domains_Plan §4): the Action Center's waiting items, first on the screen.
my/today.js fills it from GET /api/v1/me/actions; a waiting item has one button (Approve, Try again, or
my/today.js fills it from GET /api/v1/me/actions; a waiting item has one button (Approve, Try again, or
- Open), and an empty queue leaves the section out. The hero and the sections follow unchanged. -->
+ Open), and an empty queue leaves the section out. The hero and the sections follow unchanged.
+ An approval says what kind of act it is in plain words (plan HO1: publishing for others to see, sending
+ something to someone, spending your money), which agent asked and in which thread; the agent's name is
+ shown, never its id. Review opens the approval itself — the hub never approves with one tap. -->
<section class="ny" id="needs-you" hidden aria-labelledby="needs-you-h">
<section class="ny" id="needs-you" hidden aria-labelledby="needs-you-h">
<div class="ny-h">
<div class="ny-h">
<h2 id="needs-you-h"><span class="ny-dot" aria-hidden="true"></span>Needs you</h2>
<h2 id="needs-you-h"><span class="ny-dot" aria-hidden="true"></span>Needs you</h2>
@@ -41,6 +41,10 @@ const svc = http.createServer((req, res) => {
@@ -41,6 +41,10 @@ const svc = http.createServer((req, res) => {
if (u.pathname === '/actor/api/v1/actions') return json(200, { items: [
if (u.pathname === '/actor/api/v1/actions') return json(200, { items: [
item(cancelled ? { state: 'cancelled', bucket: 'failed', actions: ['open'], updated_at: iso(1) } : {}),
item(cancelled ? { state: 'cancelled', bucket: 'failed', actions: ['open'], updated_at: iso(1) } : {}),
item({ id: 'apr_1', kind: 'approval', title: 'Spend $0.40 on a desk hour', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(5), href: 'https://openvibe.actor/me/approvals' }),
item({ id: 'apr_1', kind: 'approval', title: 'Spend $0.40 on a desk hour', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(5), href: 'https://openvibe.actor/me/approvals' }),
+ // HO1: an approval of one of the new kinds, with the kind in plain words, the agent by its name and
+ // its own page.
+ item({ id: 'apr_publish', kind: 'approval', title: 'Publish your post on openvibe.blog', summary: 'publishing for others to see · Weekly digest', agent: 'Vibe', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(2), href: 'https://openvibe.actor/me/approvals/apr_publish' }),
+ item({ id: 'apr_send', kind: 'approval', title: 'Send one email to the venue', summary: 'sending something to someone · Trip planner', agent: 'Trip planner', bucket: 'waiting', state: 'pending', actions: ['approve', 'decline', 'open'], updated_at: iso(3), href: 'https://openvibe.actor/me/approvals/apr_send' }),
item({ id: 'tsk_bad', href: 'http://openvibe.actor/tasks/x' }),
item({ id: 'tsk_bad', href: 'http://openvibe.actor/tasks/x' }),
item({ id: 'tsk_spoof', service: 'codes' }),
item({ id: 'tsk_spoof', service: 'codes' }),
item({ id: 'agt_1', bucket: 'scheduled', state: 'scheduled', next_run_at: new Date(T0 + 3600000).toISOString(), actions: ['open'] }),
item({ id: 'agt_1', bucket: 'scheduled', state: 'scheduled', next_run_at: new Date(T0 + 3600000).toISOString(), actions: ['open'] }),
@@ -96,8 +100,16 @@ try {
@@ -96,8 +100,16 @@ try {
assert.ok(validate('network.me-actions-result@1', doc).valid, JSON.stringify(validate('network.me-actions-result@1', doc).errors));
assert.ok(validate('network.me-actions-result@1', doc).valid, JSON.stringify(validate('network.me-actions-result@1', doc).errors));
assert.deepStrictEqual(doc.sources, { network: 'ok', actor: 'ok', codes: 'not_connected', ai: 'timeout', domains: 'ok' });
assert.deepStrictEqual(doc.sources, { network: 'ok', actor: 'ok', codes: 'not_connected', ai: 'timeout', domains: 'ok' });
assert.strictEqual(doc.partial, true, 'a timeout makes the answer partial');
assert.strictEqual(doc.partial, true, 'a timeout makes the answer partial');
- assert.deepStrictEqual(doc.counts, { running: 1, waiting: 3, scheduled: 1, done: 0, failed: 1 });
- assert.deepStrictEqual(doc.buckets.waiting.map((x) => x.id), ['cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'apr_1', 'dpr_1'], 'newest first');
+ assert.deepStrictEqual(doc.counts, { running: 1, waiting: 5, scheduled: 1, done: 0, failed: 1 });
+ assert.deepStrictEqual(doc.buckets.waiting.map((x) => x.id), ['cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'apr_publish', 'apr_send', 'apr_1', 'dpr_1'], 'newest first');
+ // The new kinds reach the hub as they are (plan HO1): the kind in plain words, the agent by its name, and
+ // the approval's own page. The person reviews it there; the hub never approves with one tap.
+ const published = doc.buckets.waiting.find((x) => x.id === 'apr_publish');
+ assert.deepStrictEqual([published.kind, published.bucket, published.state, published.agent], ['approval', 'waiting', 'pending', 'Vibe']);
+ assert.match(published.summary, /publishing for others to see/, 'the kind in plain words');
+ assert.match(published.summary, /Weekly digest/, 'the thread it came from');
+ assert.strictEqual(published.href, 'https://openvibe.actor/me/approvals/apr_publish');
+ assert.deepStrictEqual(published.actions, ['approve', 'decline', 'open']);
const cnf = doc.buckets.waiting[0];
const cnf = doc.buckets.waiting[0];
assert.deepStrictEqual([cnf.service, cnf.kind, cnf.title, cnf.summary, cnf.href, cnf.actions], ['network', 'approval', 'Send one email to the venue', 'Trip planner asks to use mail.send', 'https://openvibe.network/my#approvals', ['approve', 'decline', 'open']]);
assert.deepStrictEqual([cnf.service, cnf.kind, cnf.title, cnf.summary, cnf.href, cnf.actions], ['network', 'approval', 'Send one email to the venue', 'Trip planner asks to use mail.send', 'https://openvibe.network/my#approvals', ['approve', 'decline', 'open']]);
const all = Object.values(doc.buckets).flat().map((x) => x.id);
const all = Object.values(doc.buckets).flat().map((x) => x.id);
@@ -165,11 +165,19 @@ assert.match(CSS, /@media \(min-width:1000px\)\{[\s\S]*\.settings\{display:grid;
@@ -165,11 +165,19 @@ assert.match(CSS, /@media \(min-width:1000px\)\{[\s\S]*\.settings\{display:grid;
p.sandbox.renderNeedsYou({ buckets: { waiting: [
p.sandbox.renderNeedsYou({ buckets: { waiting: [
{ service: 'actor', id: 'apr_1', kind: 'approval', title: 'Run a command', agent: 'agt01M6VEKFK36XWYB3CSY1RE60YN', href: 'https://openvibe.actor/me/approvals/apr_1', actions: ['approve', 'open'] },
{ service: 'actor', id: 'apr_1', kind: 'approval', title: 'Run a command', agent: 'agt01M6VEKFK36XWYB3CSY1RE60YN', href: 'https://openvibe.actor/me/approvals/apr_1', actions: ['approve', 'open'] },
{ service: 'actor', id: 'tsk_2', kind: 'agent_task', title: 'Summarise the inbox', href: 'https://openvibe.actor/tasks/tsk_2', actions: ['retry', 'open'] },
{ service: 'actor', id: 'tsk_2', kind: 'agent_task', title: 'Summarise the inbox', href: 'https://openvibe.actor/tasks/tsk_2', actions: ['retry', 'open'] },
+ // An approval of one of the new kinds (plan HO1): the kind in plain words, the agent by its name, and
+ // the approval's own page. Review opens it; the hub never approves with one tap.
+ { service: 'actor', id: 'apr_publish', kind: 'approval', title: 'Publish your post on openvibe.blog', summary: 'publishing for others to see · Weekly digest', agent: 'Vibe', href: 'https://openvibe.actor/me/approvals/apr_publish', actions: ['approve', 'decline', 'open'] },
] } });
] } });
const two = p.ids['needs-you-list'].children;
const two = p.ids['needs-you-list'].children;
assert.ok(!/agt01M6/.test(two[0].textContent), 'a raw agent id is left out');
assert.ok(!/agt01M6/.test(two[0].textContent), 'a raw agent id is left out');
assert.strictEqual(two[0].all((c) => c.tagName === 'A')[0].href, 'https://openvibe.actor/me/approvals/apr_1', 'Review opens the approval');
assert.strictEqual(two[0].all((c) => c.tagName === 'A')[0].href, 'https://openvibe.actor/me/approvals/apr_1', 'Review opens the approval');
assert.strictEqual(two[1].all((c) => c.tagName === 'BUTTON')[0].textContent, 'Try again');
assert.strictEqual(two[1].all((c) => c.tagName === 'BUTTON')[0].textContent, 'Try again');
+ assert.match(two[2].textContent, /publishing for others to see/, 'the kind of the act is in plain words');
+ assert.match(two[2].textContent, /Weekly digest/, 'the thread it came from');
+ assert.match(two[2].textContent, /Vibe/, 'the agent by its name');
+ assert.strictEqual(two[2].all((c) => c.tagName === 'A')[0].href, 'https://openvibe.actor/me/approvals/apr_publish');
+ assert.strictEqual(two[2].all((c) => c.tagName === 'BUTTON').length, 0, 'no one-tap approve on an approval');
p.sandbox.renderNeedsYou({ buckets: { waiting: [] } });
p.sandbox.renderNeedsYou({ buckets: { waiting: [] } });
assert.strictEqual(p.ids['needs-you'].hidden, true, 'an empty queue leaves the section out');
assert.strictEqual(p.ids['needs-you'].hidden, true, 'an empty queue leaves the section out');