openvibers/openvibe.actor Commits

openvibe-contracts v0.214.0, with your agents' suggestions over the API (the route the new actor.agent.manage lists) (#120)

OP OpenVibers committed 18 days ago · e93f30dc79 · 6 files, +30 −9

* openvibe-contracts v0.214.0 (was v0.213.0)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v0.214.0 release.

* Your agents' suggestions over the API (GET /api/v1/me/agents/:id/suggestions, POST …/:pid/apply|dismiss|undo; actor.agent.manage, contracts 0.214.0)

Changes

ddiff --git a/README.md b/README.md
iindex 7c879bf..4bb7421 100644
---- a/README.md
++++ b/README.md
@@@ -52,7 +52,7 @@ Rate cards are typed in by a person from each provider's price page, with the pa
| `GET /me/approvals`, `GET/POST /me/approvals/:id` (signed in) | the person | the approval inbox ([server/agents/approvals.js](server/agents/approvals.js)): when one of the person's own agents runs while they are away and would start a coding task on their machine (the `codes` agent), the run asks instead and ends with a link; nothing reaches Codes until the person approves, which starts it as their own task (declining runs nothing; a request expires after 7 days). In the account export (`approvals.json`) and deletion. |
| `GET/POST /api/v1/me/agents/:id/memory`, `PATCH/DELETE /api/v1/me/agents/:id/memory/:mem`, `DELETE /api/v1/me/agents/:id/memory` | `actor.agent.manage` for a token | control its memory |
| `POST /api/v1/me/agents/:id/run`, `GET /api/v1/me/agents/:id/runs` | `actor.agent.run` for a token | run now or list its tasks |
--| `POST /me/agents/:id/suggestions/:pid` (signed in) | the person | what the agent suggested adding to its instructions (a run that passed its check may end with `IMPROVE: <line>`; at most three wait, the same line once), under "Suggestions from your agent" on its page: apply adds the line, dismiss drops it, undo restores the instructions while they are as it left them. In the account export (`agent-suggestions.json`) and deletion. The API routes follow once `actor.agent.manage` lists them (contracts). |
++| `GET /api/v1/me/agents/:id/suggestions`, `POST /api/v1/me/agents/:id/suggestions/:pid/{apply,dismiss,undo}`; `POST /me/agents/:id/suggestions/:pid` (signed in) | `actor.agent.manage` for a token | what the agent suggested adding to its instructions (a run that passed its check may end with `IMPROVE: <line>`; at most three wait, the same line once), also under "Suggestions from your agent" on its page: apply adds the line, dismiss drops it, undo restores the instructions while they are as it left them. In the account export (`agent-suggestions.json`) and deletion. |
| `POST /api/v1/route` | public | dry run: which agent and why (heuristic class; nothing runs or is charged) |
| `GET /api/v1/resources` | `actor.resource.read` (service token) | `common.resource-list-result@1` for `actor.task`; filters: `project`, `kind`, `cursor`, `limit` |
| `GET /api/v1/resources/:ovrn` | `actor.resource.read` (service token) | one `common.resource-summary@1` for a project-owned task |
@@@ -245,7 +245,7 @@ Reporting a vulnerability: [SECURITY.md](SECURITY.md).
Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
<!-- versions:start -->
--- openvibe-contracts: v0.213.0
++- openvibe-contracts: v0.214.0
- openvibe-sdk: v0.65.0
- openvibe-shared: v4.13.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 3305c68..f54d8d2 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -7,11 +7,11 @@
"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4950,
"domain": "openvibe.actor",
-- "contracts": "openvibe-contracts v0.213.0",
++ "contracts": "openvibe-contracts v0.214.0",
"sdk": "openvibe-sdk v0.65.0",
"shared": "openvibe-shared v4.13.0",
"packages": [
-- "openvibe-contracts v0.213.0",
++ "openvibe-contracts v0.214.0",
"openvibe-sdk v0.65.0",
"openvibe-shared v4.13.0"
],
ddiff --git a/package-lock.json b/package-lock.json
iindex 5ade916..915c261 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -15,7 +15,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.213.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.214.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.65.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.13.0",
"pg": "^8.23.0"
@@@ -871,9 +871,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.213.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.213.0",
-- "integrity": "sha512-QeJamMrqMoGyvPoD80pyGZepF3GTaKJYVWBVeFsigVulZBmt8zeQOeJZ3TMwtUydT0bPje3kUt98/czwXvR7sg==",
++ "version": "0.214.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.214.0",
++ "integrity": "sha512-xlKYB4Zw0HueHXpnnSPref1BRDDYbhfMRn7EoZrPt2P5sOQmsWkaxWj06CKIJ32IfmrnKGIXmsvUfDOb8hwH3Q==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 40bdcb2..acabcc9 100644
---- a/package.json
++++ b/package.json
@@@ -28,7 +28,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.213.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.214.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.65.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.13.0",
"pg": "^8.23.0"
ddiff --git a/server/http/api.js b/server/http/api.js
iindex 125b627..dcfcc9d 100644
---- a/server/http/api.js
++++ b/server/http/api.js
@@@ -89,6 +89,17 @@ function createApi(ctx) {
const page = await store.listTasks(s, row.owner, { limit, before, via: `agent:${row.id}` });
res.json({ tasks: page.rows.map((t) => store.toWire(t, { baseUrl: config.baseUrl })), next: page.next });
});
++ // Suggestions the agent made for its instructions (agents/personal.js): list, then apply, dismiss or undo one.
++ r.get('/me/agents/:id/suggestions', principal.requireCapability('actor.agent.manage'), owner, limits.reads('actor.agent.manage'), async (req, res) => {
++ const row = await personal.get(s, req.principal.requester, req.params.id);
++ if (!row) return problem(req, res, 404, 'actor.agent.not_found', 'No such agent.');
++ res.json({ suggestions: await personal.proposals(s, row.owner, row.id) });
++ });
++ r.post('/me/agents/:id/suggestions/:pid/:action', principal.requireCapability('actor.agent.manage'), owner, limits.budget('actor.agent.manage'), async (req, res) => {
++ const out = await personal.decide(s, req.principal.requester, req.params.id, req.params.pid, req.params.action);
++ if (out.error) return problem(req, res, out.status, out.status === 404 ? 'actor.agent.not_found' : 'actor.agent.invalid', out.error);
++ res.json({ suggestion: out.row, agent: out.agent });
++ });
r.get('/me/agents/:id/memory', principal.requireCapability('actor.agent.manage'), owner, limits.reads('actor.agent.manage'), async (req, res) => {
const row = await loadAgent(req, res); if (row) res.json({ memory: await personal.memory(s, row.owner, row.id) });
});
ddiff --git a/test/agents-suggestions.test.js b/test/agents-suggestions.test.js
iindex b24d346..8cbd0e1 100644
---- a/test/agents-suggestions.test.js
++++ b/test/agents-suggestions.test.js
@@@ -67,6 +67,16 @@ const SAME = { 'sec-fetch-site': 'same-origin' };
const two = (await personal.proposals(t.ctx.s, owner, made.id)).find((p) => p.text === 'Two.');
assert.strictEqual((await t.get(`/me/agents/${made.id}/suggestions/${two.id}`, { as: stranger, form: { decision: 'apply' }, headers: SAME })).status, 404);
});
++ await check('the API lists them and decides one (actor.agent.manage); a bad action is refused', async () => {
++ const list = await t.get(`/api/v1/me/agents/${made.id}/suggestions`, { as: who });
++ assert.strictEqual(list.status, 200);
++ const two = list.json().suggestions.find((p) => p.text === 'Two.');
++ assert.ok(two && two.state === 'pending');
++ const applied = await t.get(`/api/v1/me/agents/${made.id}/suggestions/${two.id}/apply`, { as: who, method: 'POST', json: {}, headers: SAME });
++ assert.strictEqual(applied.status, 200, applied.text);
++ assert.match(applied.json().agent.instructions, /\n- Two\.$/);
++ assert.strictEqual((await t.get(`/api/v1/me/agents/${made.id}/suggestions/${two.id}/explode`, { as: who, method: 'POST', json: {}, headers: SAME })).status, 404);
++ });
} finally { await t.close(); }
done();
})();