opencode: notifier wrapper gains the packet id (id <n>)
This commit is contained in:
@@ -699,7 +699,9 @@ describe('MeshService opencode notifier', () => {
|
|||||||
let notifyMock: ReturnType<typeof vi.fn>;
|
let notifyMock: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
notifyMock = vi.fn(async (_text: string, _from?: number) => {});
|
notifyMock = vi.fn(
|
||||||
|
async (_id: number, _text: string, _from?: number) => {},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// myInfo frame as streamed during the initial sync — sets svc.myNodeNum
|
// myInfo frame as streamed during the initial sync — sets svc.myNodeNum
|
||||||
@@ -737,7 +739,7 @@ describe('MeshService opencode notifier', () => {
|
|||||||
|
|
||||||
await waitFor(() => notifyMock.mock.calls.length === 1);
|
await waitFor(() => notifyMock.mock.calls.length === 1);
|
||||||
expect(notifyMock).toHaveBeenCalledTimes(1);
|
expect(notifyMock).toHaveBeenCalledTimes(1);
|
||||||
expect(notifyMock).toHaveBeenCalledWith('ping', KAMIL_NUM);
|
expect(notifyMock).toHaveBeenCalledWith(1, 'ping', KAMIL_NUM); // packet id threaded through
|
||||||
} finally {
|
} finally {
|
||||||
delete process.env.MESH_NOTIFY_CHANNEL;
|
delete process.env.MESH_NOTIFY_CHANNEL;
|
||||||
svc.onModuleDestroy();
|
svc.onModuleDestroy();
|
||||||
@@ -773,10 +775,10 @@ describe('MeshService opencode notifier', () => {
|
|||||||
process.env.MESH_NOTIFY_CHANNEL = '0';
|
process.env.MESH_NOTIFY_CHANNEL = '0';
|
||||||
const svc = makeSvc();
|
const svc = makeSvc();
|
||||||
try {
|
try {
|
||||||
svc.injectMessage('hi', 42);
|
const r = svc.injectMessage('hi', 42);
|
||||||
|
|
||||||
expect(notifyMock).toHaveBeenCalledTimes(1);
|
expect(notifyMock).toHaveBeenCalledTimes(1);
|
||||||
expect(notifyMock).toHaveBeenCalledWith('hi', 42);
|
expect(notifyMock).toHaveBeenCalledWith(r.id, 'hi', 42); // generated record id threaded through
|
||||||
} finally {
|
} finally {
|
||||||
delete process.env.MESH_NOTIFY_CHANNEL;
|
delete process.env.MESH_NOTIFY_CHANNEL;
|
||||||
}
|
}
|
||||||
@@ -786,10 +788,10 @@ describe('MeshService opencode notifier', () => {
|
|||||||
process.env.MESH_NOTIFY_CHANNEL = '0';
|
process.env.MESH_NOTIFY_CHANNEL = '0';
|
||||||
const svc = makeSvc();
|
const svc = makeSvc();
|
||||||
try {
|
try {
|
||||||
svc.injectMessage('hi');
|
const r = svc.injectMessage('hi');
|
||||||
|
|
||||||
expect(notifyMock).toHaveBeenCalledTimes(1);
|
expect(notifyMock).toHaveBeenCalledTimes(1);
|
||||||
expect(notifyMock).toHaveBeenCalledWith('hi', undefined);
|
expect(notifyMock).toHaveBeenCalledWith(r.id, 'hi', undefined);
|
||||||
} finally {
|
} finally {
|
||||||
delete process.env.MESH_NOTIFY_CHANNEL;
|
delete process.env.MESH_NOTIFY_CHANNEL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,19 +433,24 @@ export class MeshService implements OnModuleInit, OnModuleDestroy {
|
|||||||
};
|
};
|
||||||
this.pushHistory(rec);
|
this.pushHistory(rec);
|
||||||
// injected records ride channel 0 — the notifier fires only when MESH_NOTIFY_CHANNEL is set to match
|
// injected records ride channel 0 — the notifier fires only when MESH_NOTIFY_CHANNEL is set to match
|
||||||
this.notifyRx(0, text, from);
|
this.notifyRx(0, text, from, rec.id);
|
||||||
return { id: rec.id };
|
return { id: rec.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fire-and-forget opencode notifier push for an rx text on `channel` (no-op unless channels match and a notifier is wired). */
|
/** Fire-and-forget opencode notifier push for an rx text on `channel` (no-op unless channels match and a notifier is wired). Both call sites pass the packet id. */
|
||||||
private notifyRx(channel: number, text: string, from?: number): void {
|
private notifyRx(
|
||||||
|
channel: number,
|
||||||
|
text: string,
|
||||||
|
from?: number,
|
||||||
|
id?: number,
|
||||||
|
): void {
|
||||||
if (!this.opencode || channel !== this.notifyChannel) return;
|
if (!this.opencode || channel !== this.notifyChannel) return;
|
||||||
// self-echo filter: our own node must never trigger a notification of itself
|
// self-echo filter: our own node must never trigger a notification of itself
|
||||||
if (from !== undefined && from === this.myNodeNum) {
|
if (from !== undefined && from === this.myNodeNum) {
|
||||||
this.logger.debug(`notify skipped: rx from our own node ${from}`);
|
this.logger.debug(`notify skipped: rx from our own node ${from}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void this.opencode.notify(text, from);
|
void this.opencode.notify(id!, text, from); // both call sites always pass the packet id
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -631,7 +636,7 @@ export class MeshService implements OnModuleInit, OnModuleDestroy {
|
|||||||
channel: pkt.channel ?? 0,
|
channel: pkt.channel ?? 0,
|
||||||
at: Date.now(),
|
at: Date.now(),
|
||||||
});
|
});
|
||||||
this.notifyRx(pkt.channel ?? 0, text, pkt.from);
|
this.notifyRx(pkt.channel ?? 0, text, pkt.from, pkt.id);
|
||||||
} else if (decoded && decoded.portnum === PORT_POSITION_APP) {
|
} else if (decoded && decoded.portnum === PORT_POSITION_APP) {
|
||||||
// live position update: the Position is serialized in Data.payload
|
// live position update: the Position is serialized in Data.payload
|
||||||
let pos: Proto.Mesh.Position | undefined;
|
let pos: Proto.Mesh.Position | undefined;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
.mockResolvedValueOnce(json([WORKSPACE]))
|
.mockResolvedValueOnce(json([WORKSPACE]))
|
||||||
.mockResolvedValueOnce(json({}));
|
.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi', 42);
|
await svc.notify(7, 'hi', 42);
|
||||||
|
|
||||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||||
const [url, init] = fetchMock.mock.calls[1];
|
const [url, init] = fetchMock.mock.calls[1];
|
||||||
@@ -89,7 +89,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: 'New mesh message from node 42: "hi" — please react to it.',
|
text: 'New mesh message (id 7) from node 42: "hi" — please react to it.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -106,7 +106,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
.mockResolvedValueOnce(json([WORKSPACE]))
|
.mockResolvedValueOnce(json([WORKSPACE]))
|
||||||
.mockResolvedValueOnce(json({}));
|
.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
const [, init] = fetchMock.mock.calls[1];
|
const [, init] = fetchMock.mock.calls[1];
|
||||||
expect(String((init as { body?: string }).body)).toBe(
|
expect(String((init as { body?: string }).body)).toBe(
|
||||||
@@ -114,7 +114,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: 'New mesh message: "hi" — please react to it.',
|
text: 'New mesh message (id 7): "hi" — please react to it.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -146,7 +146,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
);
|
);
|
||||||
fetchMock.mockResolvedValueOnce(json({}));
|
fetchMock.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
expect(fetchMock.mock.calls[1][0]).toBe(
|
expect(fetchMock.mock.calls[1][0]).toBe(
|
||||||
`${API}/proxy/abc/session/ses_pinned/message`,
|
`${API}/proxy/abc/session/ses_pinned/message`,
|
||||||
@@ -179,7 +179,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
);
|
);
|
||||||
fetchMock.mockResolvedValueOnce(json({}));
|
fetchMock.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
expect(fetchMock.mock.calls[1][0]).toBe(
|
expect(fetchMock.mock.calls[1][0]).toBe(
|
||||||
`${API}/proxy/b/session/ses_pinned/message`,
|
`${API}/proxy/b/session/ses_pinned/message`,
|
||||||
@@ -198,7 +198,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
)
|
)
|
||||||
.mockResolvedValueOnce(json({}));
|
.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
expect(fetchMock.mock.calls[1][0]).toBe(`${API}/proxy/abc/session`);
|
expect(fetchMock.mock.calls[1][0]).toBe(`${API}/proxy/abc/session`);
|
||||||
expect(fetchMock.mock.calls[2][0]).toBe(
|
expect(fetchMock.mock.calls[2][0]).toBe(
|
||||||
@@ -213,7 +213,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
.mockResolvedValueOnce(json([{ id: 'ses_a' }, { id: 'ses_b', time: {} }]))
|
.mockResolvedValueOnce(json([{ id: 'ses_a' }, { id: 'ses_b', time: {} }]))
|
||||||
.mockResolvedValueOnce(json({}));
|
.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
expect(fetchMock.mock.calls[2][0]).toBe(
|
expect(fetchMock.mock.calls[2][0]).toBe(
|
||||||
`${API}/proxy/abc/session/ses_a/message`,
|
`${API}/proxy/abc/session/ses_a/message`,
|
||||||
@@ -230,7 +230,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
.mockResolvedValueOnce(json([WORKSPACE]))
|
.mockResolvedValueOnce(json([WORKSPACE]))
|
||||||
.mockResolvedValueOnce(json({}));
|
.mockResolvedValueOnce(json({}));
|
||||||
|
|
||||||
await svc.notify('hi');
|
await svc.notify(7, 'hi');
|
||||||
|
|
||||||
// workspaces GET + message POST only — no /session discovery GET
|
// workspaces GET + message POST only — no /session discovery GET
|
||||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||||
@@ -309,7 +309,7 @@ describe('OpencodeService.notify', () => {
|
|||||||
string
|
string
|
||||||
>[]) {
|
>[]) {
|
||||||
const svc = makeService(env);
|
const svc = makeService(env);
|
||||||
await expect(svc.notify('hi', 42)).resolves.toBeUndefined();
|
await expect(svc.notify(7, 'hi', 42)).resolves.toBeUndefined();
|
||||||
expect(fetchMock).not.toHaveBeenCalled();
|
expect(fetchMock).not.toHaveBeenCalled();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ export class OpencodeService {
|
|||||||
* up to RETRY_DELAYS_MS.length + 1 attempts total; the last failure logs `opencode notify dropped
|
* up to RETRY_DELAYS_MS.length + 1 attempts total; the last failure logs `opencode notify dropped
|
||||||
* after N attempts`. No-op when disabled (no credentials).
|
* after N attempts`. No-op when disabled (no credentials).
|
||||||
*/
|
*/
|
||||||
async notify(text: string, from?: number): Promise<void> {
|
async notify(id: number, text: string, from?: number): Promise<void> {
|
||||||
if (!this.enabled) return;
|
if (!this.enabled) return;
|
||||||
const msg =
|
const msg =
|
||||||
'New mesh message' +
|
`New mesh message (id ${id})` +
|
||||||
(from != null ? ` from node ${from}` : '') +
|
(from != null ? ` from node ${from}` : '') +
|
||||||
`: "${text}" — please react to it.`;
|
`: "${text}" — please react to it.`;
|
||||||
const maxAttempts = RETRY_DELAYS_MS.length + 1;
|
const maxAttempts = RETRY_DELAYS_MS.length + 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user