feat: decouple manual image sharing from automation gating
The "Share a saved image" panel was wired to the automated exchange engine, so an operator could only share an image right after an inbound image request, limited to assets the auto-exchanger would itself pick. Add a manual path that offers any enabled, unused library image at any point (no request detection, tier, seller, or boundary gating) and sends it with no reciprocal "your turn" text. Safety flags now surface as a non-blocking warning instead of disabling the panel. The automated auto-send path keeps all its guardrails; the shared image-delivery step is extracted into a helper used by both paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
e984bf20ac
commit
c8689af04d
9 files changed
+192
-67
No files matched your search
@@ -133,7 +133,7 @@ describe('conversations route', () => {
|
||||
expect(first.resources.activityLogRepo.insert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('lists and sends only request-matched library media through the owning runtime', async () => {
|
||||
it('lists every enabled image and shares the operator-selected one through the owning runtime', async () => {
|
||||
const accountId = '@t2_one:reddit.com';
|
||||
const assetId = '11111111-1111-4111-8111-111111111111';
|
||||
const conversation = {
|
||||
@@ -151,8 +151,9 @@ describe('conversations route', () => {
|
||||
sendCount: 0, lastSentAt: null,
|
||||
};
|
||||
const mediaExchange = {
|
||||
listShareable: vi.fn().mockReturnValue({ requestKind: 'selfie', assets: [asset] }),
|
||||
handleSelected: vi.fn().mockResolvedValue({ kind: 'sent', assetId, requestKind: 'selfie' }),
|
||||
listShareableManual: vi.fn().mockReturnValue([asset]),
|
||||
detect: vi.fn().mockReturnValue('selfie'),
|
||||
shareManual: vi.fn().mockResolvedValue({ kind: 'sent', assetId, requestKind: 'generic' }),
|
||||
};
|
||||
const runtime = {
|
||||
snapshot: () => ({ state: 'running' }),
|
||||
@@ -184,16 +185,17 @@ describe('conversations route', () => {
|
||||
const optionsResponse = await request(app, `/api/conversations/${encodeURIComponent(ownedId)}/shareable-media`);
|
||||
expect(optionsResponse.status).toBe(200);
|
||||
expect(await optionsResponse.json()).toEqual({
|
||||
requestKind: 'selfie', reason: null,
|
||||
requestKind: 'selfie', reason: null, warning: null,
|
||||
assets: [expect.objectContaining({ id: assetId, urlPath: '/library-media/nested/selfie.jpg' })],
|
||||
});
|
||||
expect(mediaExchange.listShareableManual).toHaveBeenCalledWith('room-one');
|
||||
|
||||
const sendResponse = await request(app, `/api/conversations/${encodeURIComponent(ownedId)}/share-media`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ assetId }),
|
||||
});
|
||||
expect(sendResponse.status).toBe(200);
|
||||
expect(await sendResponse.json()).toEqual({ success: true });
|
||||
expect(mediaExchange.handleSelected).toHaveBeenCalledWith('room-one', '$request', 'send a selfie', assetId);
|
||||
expect(mediaExchange.shareManual).toHaveBeenCalledWith('room-one', assetId);
|
||||
expect(runtime.resources.conversationStateRepo.save).toHaveBeenCalledWith('room-one', expect.objectContaining({
|
||||
automation: expect.objectContaining({ status: 'user-overridden' }),
|
||||
}));
|
||||
|
||||
Reference in new issue
Block a user