fix: improve RemotePromptRecord.ref type safety (#1041)
Split RemotePromptRecord into a discriminated union of PendingPromptRecord (ref is undefined) and DispatchedPromptRecord (ref is required). This makes the type system enforce that ref is always present after dispatch. Also removes a redundant truthiness check on dispatch.ref in manager.ts, since RemoteDispatchResult.ref is already non-optional. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9201c0ce16
commit
6240926ab6
3 changed files with 34 additions and 9 deletions
|
|
@ -80,11 +80,9 @@ export async function tryRemoteQuestions(
|
|||
markPromptAnswered(prompt.id, answer);
|
||||
|
||||
// Best-effort acknowledgement gives remote users a visible receipt signal.
|
||||
if (dispatch.ref) {
|
||||
try {
|
||||
await adapter.acknowledgeAnswer?.(dispatch.ref);
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
try {
|
||||
await adapter.acknowledgeAnswer?.(dispatch.ref);
|
||||
} catch { /* best-effort */ }
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify({ answers: formatForTool(answer) }) }],
|
||||
|
|
|
|||
|
|
@ -51,11 +51,15 @@ export function updatePromptRecord(
|
|||
): RemotePromptRecord | null {
|
||||
const current = readPromptRecord(id);
|
||||
if (!current) return null;
|
||||
const next: RemotePromptRecord = {
|
||||
const merged = {
|
||||
...current,
|
||||
...updates,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
// After spreading, the merged object satisfies one of the union members
|
||||
// but TypeScript can't prove it statically. The invariant is maintained
|
||||
// by callers: once `ref` is set via markPromptDispatched it is never removed.
|
||||
const next = merged as RemotePromptRecord;
|
||||
writePromptRecord(next);
|
||||
return next;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,17 +44,16 @@ export interface RemoteAnswer {
|
|||
|
||||
export type RemotePromptStatus = "pending" | "answered" | "timed_out" | "failed" | "cancelled";
|
||||
|
||||
export interface RemotePromptRecord {
|
||||
/** Shared fields present on every prompt record regardless of dispatch state. */
|
||||
interface RemotePromptRecordBase {
|
||||
version: 1;
|
||||
id: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
status: RemotePromptStatus;
|
||||
channel: RemoteChannel;
|
||||
timeoutAt: number;
|
||||
pollIntervalMs: number;
|
||||
questions: RemoteQuestion[];
|
||||
ref?: RemotePromptRef;
|
||||
response?: RemoteAnswer;
|
||||
lastPollAt?: number;
|
||||
lastError?: string;
|
||||
|
|
@ -63,6 +62,30 @@ export interface RemotePromptRecord {
|
|||
};
|
||||
}
|
||||
|
||||
/** Record before the prompt has been dispatched to a channel. */
|
||||
export interface PendingPromptRecord extends RemotePromptRecordBase {
|
||||
status: "pending";
|
||||
ref?: undefined;
|
||||
}
|
||||
|
||||
/** Record after the prompt has been dispatched (ref is always present). */
|
||||
export interface DispatchedPromptRecord extends RemotePromptRecordBase {
|
||||
status: RemotePromptStatus;
|
||||
ref: RemotePromptRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* A prompt record is either pre-dispatch (no ref) or post-dispatch (ref required).
|
||||
*
|
||||
* Narrow via `record.ref`:
|
||||
* ```ts
|
||||
* if (record.ref) {
|
||||
* // DispatchedPromptRecord — ref is RemotePromptRef
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type RemotePromptRecord = PendingPromptRecord | DispatchedPromptRecord;
|
||||
|
||||
export interface RemoteDispatchResult {
|
||||
ref: RemotePromptRef;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue