@unblocklabs/unblock-memory 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -0
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +4 -1
- package/dist/src/plugin.js +2 -0
- package/dist/src/response-audit.d.ts +87 -0
- package/dist/src/response-audit.js +193 -0
- package/dist/src/response-config.d.ts +13 -0
- package/dist/src/response-config.js +43 -0
- package/dist/src/response-episodes.d.ts +68 -0
- package/dist/src/response-episodes.js +242 -0
- package/dist/src/response-identity.d.ts +15 -0
- package/dist/src/response-identity.js +34 -0
- package/dist/src/response-judge.d.ts +224 -0
- package/dist/src/response-judge.js +248 -0
- package/dist/src/response-memory.d.ts +8 -0
- package/dist/src/response-memory.js +25 -0
- package/dist/src/response-outcome.d.ts +30 -0
- package/dist/src/response-outcome.js +51 -0
- package/dist/src/response-reviews.d.ts +27 -0
- package/dist/src/response-reviews.js +116 -0
- package/dist/src/response-runtime.d.ts +3 -0
- package/dist/src/response-runtime.js +150 -0
- package/dist/src/response-stages.d.ts +184 -0
- package/dist/src/response-stages.js +38 -0
- package/dist/src/response-store.d.ts +180 -0
- package/dist/src/response-store.js +411 -0
- package/dist/src/response-text.d.ts +6 -0
- package/dist/src/response-text.js +37 -0
- package/dist/src/typesafe-review.d.ts +5 -0
- package/dist/src/typesafe-review.js +6 -5
- package/openclaw.plugin.json +20 -1
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ResponseAuditConfig } from "./response-config.js";
|
|
2
|
+
export declare const RESPONSE_EXTRACTOR_VERSION = 5;
|
|
3
|
+
type Row = {
|
|
4
|
+
seq: number;
|
|
5
|
+
eventJson: string;
|
|
6
|
+
createdAt: number;
|
|
7
|
+
};
|
|
8
|
+
export type ResponseSession = {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
accountId: string;
|
|
11
|
+
chatType: string;
|
|
12
|
+
conversationId: string;
|
|
13
|
+
};
|
|
14
|
+
type Text = {
|
|
15
|
+
seq: number;
|
|
16
|
+
role: "user" | "assistant";
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
19
|
+
export type ResponseEpisode = {
|
|
20
|
+
id: string;
|
|
21
|
+
inputHash: string;
|
|
22
|
+
session: ResponseSession;
|
|
23
|
+
senderId: string;
|
|
24
|
+
thread: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
model: string;
|
|
27
|
+
before: Text[];
|
|
28
|
+
request: Text[];
|
|
29
|
+
answer: Text[];
|
|
30
|
+
feedback: Text[];
|
|
31
|
+
followup: {
|
|
32
|
+
status: "pending" | "complete" | "partial" | "unavailable" | "oversized";
|
|
33
|
+
messages: Text[];
|
|
34
|
+
};
|
|
35
|
+
memorySearchCalls: number;
|
|
36
|
+
contextLimited: boolean;
|
|
37
|
+
};
|
|
38
|
+
type ResponseCoverage = {
|
|
39
|
+
completedResponses: number;
|
|
40
|
+
eligible: number;
|
|
41
|
+
noFeedback: number;
|
|
42
|
+
pendingFeedback: number;
|
|
43
|
+
oversized: number;
|
|
44
|
+
filteredEvents: number;
|
|
45
|
+
};
|
|
46
|
+
/** Never deduce human identity from text or the user role alone. */
|
|
47
|
+
export declare function responseEpisodes(session: ResponseSession, rows: readonly Row[], config: ResponseAuditConfig): {
|
|
48
|
+
episodes: ResponseEpisode[];
|
|
49
|
+
coverage: ResponseCoverage;
|
|
50
|
+
};
|
|
51
|
+
/** Bounded, read-only active transcript snapshot; archived/deleted branches are excluded. */
|
|
52
|
+
export declare class ResponseTranscriptReader {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(path: string, agentId: string);
|
|
55
|
+
sessions(config: ResponseAuditConfig, now: number, after?: string): ResponseSession[];
|
|
56
|
+
/** null = confirmed absent/ineligible; undefined = over budget, not evidence of deletion. */
|
|
57
|
+
read(input: ResponseSession | string, config: ResponseAuditConfig): (ReturnType<typeof responseEpisodes> & {
|
|
58
|
+
revision: string;
|
|
59
|
+
}) | null | undefined;
|
|
60
|
+
read(input: ResponseSession | string, config: ResponseAuditConfig, previousRevision: string | undefined): (ReturnType<typeof responseEpisodes> & {
|
|
61
|
+
revision: string;
|
|
62
|
+
}) | {
|
|
63
|
+
unchanged: true;
|
|
64
|
+
revision: string;
|
|
65
|
+
} | null | undefined;
|
|
66
|
+
close(): void;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { messageText } from "./whisperer-context.js";
|
|
4
|
+
import { responseUserText } from "./response-text.js";
|
|
5
|
+
export const RESPONSE_EXTRACTOR_VERSION = 5;
|
|
6
|
+
const MAX_EVENTS = 2000, MAX_SESSION_BYTES = 2_000_000, MAX_EPISODE_CHARS = 24_000;
|
|
7
|
+
function record(v) {
|
|
8
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
9
|
+
}
|
|
10
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
11
|
+
/** Never deduce human identity from text or the user role alone. */
|
|
12
|
+
export function responseEpisodes(session, rows, config) {
|
|
13
|
+
const coverage = { completedResponses: 0, eligible: 0, noFeedback: 0,
|
|
14
|
+
pendingFeedback: 0, oversized: 0, filteredEvents: 0 };
|
|
15
|
+
const episodes = [];
|
|
16
|
+
let history = [], historyDropped = false;
|
|
17
|
+
let followupTarget;
|
|
18
|
+
const closeFollowup = (safe, partial = false) => {
|
|
19
|
+
if (!followupTarget)
|
|
20
|
+
return;
|
|
21
|
+
if (!safe || !current?.answer.length || (!partial && !current.final))
|
|
22
|
+
followupTarget.followup = { status: "unavailable", messages: [] };
|
|
23
|
+
else if (current.answer.length > 6 || JSON.stringify(current.answer).length > 12_000) {
|
|
24
|
+
followupTarget.followup = { status: "oversized", messages: [] };
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
followupTarget.followup = { status: partial ? "partial" : "complete", messages: [...current.answer] };
|
|
28
|
+
followupTarget = undefined;
|
|
29
|
+
};
|
|
30
|
+
let current;
|
|
31
|
+
let pending = [];
|
|
32
|
+
const finish = (closed) => {
|
|
33
|
+
if (!current?.final) {
|
|
34
|
+
current = undefined;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
coverage.completedResponses++;
|
|
38
|
+
if (!current.feedback.length)
|
|
39
|
+
coverage.noFeedback++;
|
|
40
|
+
else if (!closed)
|
|
41
|
+
coverage.pendingFeedback++;
|
|
42
|
+
else if (current.feedback.length > 6 || JSON.stringify(current).length > MAX_EPISODE_CHARS)
|
|
43
|
+
coverage.oversized++;
|
|
44
|
+
else {
|
|
45
|
+
const { final: _final, ...content } = current;
|
|
46
|
+
const id = hash([session.sessionId, current.answer.at(-1).seq]);
|
|
47
|
+
const episode = { id, inputHash: "", session, ...content, followup: { status: "pending", messages: [] } };
|
|
48
|
+
episodes.push(episode);
|
|
49
|
+
followupTarget = episode;
|
|
50
|
+
coverage.eligible++;
|
|
51
|
+
}
|
|
52
|
+
current = undefined;
|
|
53
|
+
};
|
|
54
|
+
const boundary = () => { closeFollowup(false); finish(false); history = []; historyDropped = false; pending = []; };
|
|
55
|
+
const remember = (text) => {
|
|
56
|
+
history.push(text);
|
|
57
|
+
if (history.length > config.historyMessages) {
|
|
58
|
+
history.shift();
|
|
59
|
+
historyDropped = true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
let e;
|
|
64
|
+
try {
|
|
65
|
+
e = record(JSON.parse(row.eventJson));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
coverage.filteredEvents++;
|
|
69
|
+
boundary();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (e?.type !== "message") {
|
|
73
|
+
// Compaction/context rewrites cannot silently join unrelated transcript segments.
|
|
74
|
+
if (e?.type === "compaction")
|
|
75
|
+
boundary();
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const m = record(e.message), meta = record(m?.__openclaw);
|
|
79
|
+
if (!m) {
|
|
80
|
+
boundary();
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (m.role === "toolResult")
|
|
84
|
+
continue; // Tool bodies/thinking are never sent.
|
|
85
|
+
if (m.role === "user") {
|
|
86
|
+
const identity = record(meta?.senderIdentity), transport = record(meta?.transport);
|
|
87
|
+
const senderId = meta?.senderId;
|
|
88
|
+
const human = identity?.senderKind !== "bot" && (identity?.senderKind === "human" || meta?.senderIsOwner === true);
|
|
89
|
+
if (m.provenance !== undefined || !human ||
|
|
90
|
+
typeof senderId !== "string" || !config.senderIds.includes(senderId) ||
|
|
91
|
+
transport?.channel !== "slack" || transport.conversationRef !== session.conversationId) {
|
|
92
|
+
coverage.filteredEvents++;
|
|
93
|
+
boundary();
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const raw = typeof meta?.upstreamUserText === "string" ? meta.upstreamUserText : messageText(m)?.text;
|
|
97
|
+
const visible = raw ? responseUserText(raw, senderId) : undefined;
|
|
98
|
+
if (!visible) {
|
|
99
|
+
coverage.filteredEvents++;
|
|
100
|
+
boundary();
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const thread = typeof transport.threadId === "string" ? transport.threadId : "";
|
|
104
|
+
if ((current && (current.senderId !== senderId || current.thread !== thread)) ||
|
|
105
|
+
pending.some(p => p.senderId !== senderId || p.thread !== thread))
|
|
106
|
+
boundary();
|
|
107
|
+
closeFollowup(true);
|
|
108
|
+
if (visible.contextLimited)
|
|
109
|
+
historyDropped = true;
|
|
110
|
+
const text = { seq: row.seq, role: "user", text: visible.text };
|
|
111
|
+
if (current) {
|
|
112
|
+
if (!current.final)
|
|
113
|
+
boundary();
|
|
114
|
+
else
|
|
115
|
+
current.feedback.push(text);
|
|
116
|
+
}
|
|
117
|
+
pending.push({ text, senderId, thread });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (m.role !== "assistant") {
|
|
121
|
+
coverage.filteredEvents++;
|
|
122
|
+
boundary();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (m.provenance !== undefined || meta?.turnTainted === true) {
|
|
126
|
+
coverage.filteredEvents++;
|
|
127
|
+
boundary();
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (m.provider === "openclaw" && (m.model === "delivery-mirror" || m.model === "gateway-injected")) {
|
|
131
|
+
// Preserve only preceding clean assistant evidence, never the synthetic notice.
|
|
132
|
+
// This is not a completed response and cannot enter the original quality grade.
|
|
133
|
+
closeFollowup(true, true);
|
|
134
|
+
coverage.filteredEvents++;
|
|
135
|
+
boundary();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (pending.length) {
|
|
139
|
+
const request = pending.map(p => p.text), identity = pending[0];
|
|
140
|
+
finish(true);
|
|
141
|
+
current = { request, before: [...history], answer: [], feedback: [], senderId: identity.senderId,
|
|
142
|
+
thread: identity.thread, final: false, timestamp: row.createdAt, model: "unknown",
|
|
143
|
+
memorySearchCalls: 0, contextLimited: historyDropped };
|
|
144
|
+
pending = [];
|
|
145
|
+
request.forEach(remember);
|
|
146
|
+
}
|
|
147
|
+
if (!current)
|
|
148
|
+
continue;
|
|
149
|
+
if (meta?.turnTainted === true || m.stopReason === "error" || m.stopReason === "aborted") {
|
|
150
|
+
boundary();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(m.content)) {
|
|
154
|
+
current.memorySearchCalls += m.content.filter(p => {
|
|
155
|
+
const block = record(p);
|
|
156
|
+
return block?.type === "toolCall" && (block.name === "memory_search" || block.name === "memory_get");
|
|
157
|
+
}).length;
|
|
158
|
+
}
|
|
159
|
+
const text = messageText(m)?.text;
|
|
160
|
+
if (!text || text === "NO_REPLY" || text === "HEARTBEAT_OK" || m.channel === "analysis")
|
|
161
|
+
continue;
|
|
162
|
+
const item = { seq: row.seq, role: "assistant", text };
|
|
163
|
+
current.answer.push(item);
|
|
164
|
+
remember(item);
|
|
165
|
+
current.model = typeof m.model === "string" ? m.model : "unknown";
|
|
166
|
+
current.timestamp = row.createdAt;
|
|
167
|
+
current.final = meta?.runTerminal === true || (m.stopReason === "stop" && m.channel !== "commentary");
|
|
168
|
+
}
|
|
169
|
+
if (current?.final)
|
|
170
|
+
closeFollowup(true);
|
|
171
|
+
finish(false); // No next assistant turn: the feedback block may still grow.
|
|
172
|
+
for (const episode of episodes) {
|
|
173
|
+
const { inputHash: _hash, session: s, ...content } = episode;
|
|
174
|
+
episode.inputHash = hash([RESPONSE_EXTRACTOR_VERSION, s.sessionId, s.accountId, s.chatType, s.conversationId, content]);
|
|
175
|
+
}
|
|
176
|
+
return { episodes, coverage };
|
|
177
|
+
}
|
|
178
|
+
/** Bounded, read-only active transcript snapshot; archived/deleted branches are excluded. */
|
|
179
|
+
export class ResponseTranscriptReader {
|
|
180
|
+
#db;
|
|
181
|
+
constructor(path, agentId) {
|
|
182
|
+
this.#db = new DatabaseSync(path, { readOnly: true });
|
|
183
|
+
try {
|
|
184
|
+
this.#db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000");
|
|
185
|
+
const version = this.#db.prepare("PRAGMA user_version").get()?.user_version;
|
|
186
|
+
const meta = this.#db.prepare("SELECT role,agent_id,schema_version FROM schema_meta WHERE meta_key='primary'").get();
|
|
187
|
+
if (![17, 18, 19].includes(Number(version)) || meta?.role !== "agent" || meta.agent_id !== agentId || meta.schema_version !== version) {
|
|
188
|
+
throw new Error("Unsupported response-audit transcript schema or agent");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.#db.close();
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
sessions(config, now, after = "") {
|
|
197
|
+
return this.#db.prepare(`SELECT w.session_id sessionId, COALESCE(w.account_id,c.account_id,'') accountId,
|
|
198
|
+
w.chat_type chatType, w.primary_conversation_id conversationId
|
|
199
|
+
FROM session_windows w JOIN conversations c ON c.conversation_id=w.primary_conversation_id
|
|
200
|
+
WHERE COALESCE(w.channel,c.channel)='slack' AND w.chat_type IN (${config.chatTypes.map(() => "?").join(",")})
|
|
201
|
+
AND w.session_id>?
|
|
202
|
+
AND EXISTS (SELECT 1 FROM transcript_events e JOIN session_transcript_active_events a
|
|
203
|
+
ON a.session_id=e.session_id AND a.event_seq=e.seq WHERE e.session_id=w.session_id AND e.created_at >= ?
|
|
204
|
+
AND json_extract(e.event_json,'$.message.role')='user'
|
|
205
|
+
AND json_extract(e.event_json,'$.message.__openclaw.senderId') IN (${config.senderIds.map(() => "?").join(",")}))
|
|
206
|
+
ORDER BY w.session_id LIMIT 101`)
|
|
207
|
+
.all(...config.chatTypes, after, now - config.lookbackDays * 86400_000, ...config.senderIds);
|
|
208
|
+
}
|
|
209
|
+
read(input, config, previousRevision) {
|
|
210
|
+
this.#db.exec("BEGIN");
|
|
211
|
+
try {
|
|
212
|
+
const sessionId = typeof input === "string" ? input : input.sessionId;
|
|
213
|
+
const window = this.#db.prepare(`SELECT w.chat_type chatType,COALESCE(w.channel,c.channel) provider,
|
|
214
|
+
COALESCE(w.account_id,c.account_id,'') accountId,w.primary_conversation_id conversationId
|
|
215
|
+
FROM session_windows w JOIN conversations c ON c.conversation_id=w.primary_conversation_id WHERE w.session_id=?`).get(sessionId);
|
|
216
|
+
if (!window || window.provider !== "slack" || !config.chatTypes.some(type => type === window.chatType) ||
|
|
217
|
+
(typeof input !== "string" && (window.accountId !== input.accountId ||
|
|
218
|
+
window.conversationId !== input.conversationId || window.chatType !== input.chatType)))
|
|
219
|
+
return null;
|
|
220
|
+
const session = { sessionId, accountId: String(window.accountId),
|
|
221
|
+
conversationId: String(window.conversationId), chatType: String(window.chatType) };
|
|
222
|
+
const count = this.#db.prepare(`SELECT COUNT(*) n,COALESCE(SUM(length(e.event_json)),0) bytes
|
|
223
|
+
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
224
|
+
WHERE a.session_id=?`).get(session.sessionId);
|
|
225
|
+
if (Number(count.n) > MAX_EVENTS || Number(count.bytes) > MAX_SESSION_BYTES)
|
|
226
|
+
return undefined;
|
|
227
|
+
const rows = this.#db.prepare(`SELECT e.seq,e.event_json eventJson,e.created_at createdAt
|
|
228
|
+
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
229
|
+
WHERE a.session_id=? ORDER BY a.active_position`).all(session.sessionId);
|
|
230
|
+
// Exact active content catches in-place edits and branch changes even when writer
|
|
231
|
+
// watermarks are absent. Raw text is never retained in the checkpoint database.
|
|
232
|
+
const revision = hash([RESPONSE_EXTRACTOR_VERSION, session, config.historyMessages, [...config.senderIds].sort(), rows]);
|
|
233
|
+
if (revision === previousRevision)
|
|
234
|
+
return { unchanged: true, revision };
|
|
235
|
+
return { ...responseEpisodes(session, rows, config), revision };
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
this.#db.exec("COMMIT");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
close() { this.#db.close(); }
|
|
242
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResponseEpisode } from "./response-episodes.js";
|
|
2
|
+
export type ResponseHuman = {
|
|
3
|
+
key: string;
|
|
4
|
+
provider: "slack";
|
|
5
|
+
accountScope: string;
|
|
6
|
+
senderId: string;
|
|
7
|
+
personId: string | null;
|
|
8
|
+
};
|
|
9
|
+
/** Identity is trusted metadata, never inferred from names or transcript text. */
|
|
10
|
+
export declare class ResponsePeople {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(path?: string);
|
|
13
|
+
resolve(e: ResponseEpisode): ResponseHuman;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
/** Identity is trusted metadata, never inferred from names or transcript text. */
|
|
4
|
+
export class ResponsePeople {
|
|
5
|
+
#db;
|
|
6
|
+
constructor(path) {
|
|
7
|
+
if (!path || !existsSync(path))
|
|
8
|
+
return;
|
|
9
|
+
try {
|
|
10
|
+
this.#db = new DatabaseSync(path, { readOnly: true });
|
|
11
|
+
this.#db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
this.#db?.close();
|
|
15
|
+
this.#db = undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
resolve(e) {
|
|
19
|
+
const accountScope = e.session.accountId, senderId = e.senderId;
|
|
20
|
+
let personId = null;
|
|
21
|
+
if (accountScope) {
|
|
22
|
+
try {
|
|
23
|
+
const row = this.#db?.prepare(`SELECT p.id FROM people p JOIN person_identities i ON i.person_id=p.id
|
|
24
|
+
WHERE i.provider='slack' AND i.account_scope=? AND i.external_id=? AND p.status='active'`).get(accountScope, senderId);
|
|
25
|
+
if (typeof row?.id === "string")
|
|
26
|
+
personId = row.id;
|
|
27
|
+
}
|
|
28
|
+
catch { /* Optional people-store compatibility must not break the audit. */ }
|
|
29
|
+
}
|
|
30
|
+
return { key: JSON.stringify(["slack", accountScope || `unknown-session:${e.session.sessionId}`, senderId]),
|
|
31
|
+
provider: "slack", accountScope, senderId, personId };
|
|
32
|
+
}
|
|
33
|
+
close() { this.#db?.close(); }
|
|
34
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import type { ResponseEpisode } from "./response-episodes.js";
|
|
3
|
+
export declare const RESPONSE_RUBRIC_VERSION = "jev-1.13.0:response-v10";
|
|
4
|
+
export declare const RESPONSE_STAGE_VERSIONS: {
|
|
5
|
+
readonly quality: "quality-v9";
|
|
6
|
+
readonly feedback: "feedback-v9";
|
|
7
|
+
readonly sentiment: "sentiment-v10";
|
|
8
|
+
readonly retrospective: "retrospective-v9";
|
|
9
|
+
readonly memory: "memory-v1";
|
|
10
|
+
};
|
|
11
|
+
declare const qualitySchema: Type.TObject<{
|
|
12
|
+
answers: Type.TObject<{
|
|
13
|
+
taskType: Type.TObject<{
|
|
14
|
+
type: Type.TLiteral<"choice">;
|
|
15
|
+
choice: Type.TEnum<("action" | "question" | "artifact" | "discussion" | "other")[]>;
|
|
16
|
+
confidence: Type.TNumber;
|
|
17
|
+
probabilities: Type.TObject<{
|
|
18
|
+
[k: string]: Type.TNumber;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
assessability: Type.TObject<{
|
|
22
|
+
type: Type.TLiteral<"choice">;
|
|
23
|
+
choice: Type.TEnum<("assessable" | "not_assessable")[]>;
|
|
24
|
+
confidence: Type.TNumber;
|
|
25
|
+
probabilities: Type.TObject<{
|
|
26
|
+
[k: string]: Type.TNumber;
|
|
27
|
+
}>;
|
|
28
|
+
}>;
|
|
29
|
+
fitAssessability: Type.TObject<{
|
|
30
|
+
type: Type.TLiteral<"choice">;
|
|
31
|
+
choice: Type.TEnum<("assessable" | "not_assessable")[]>;
|
|
32
|
+
confidence: Type.TNumber;
|
|
33
|
+
probabilities: Type.TObject<{
|
|
34
|
+
[k: string]: Type.TNumber;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
underdelivery: Type.TObject<{
|
|
38
|
+
type: Type.TLiteral<"noul">;
|
|
39
|
+
noul: Type.TNumber;
|
|
40
|
+
}>;
|
|
41
|
+
failureReason: Type.TObject<{
|
|
42
|
+
type: Type.TLiteral<"choice">;
|
|
43
|
+
choice: Type.TEnum<("none_or_unclear" | "missing_requested_work" | "wrong_deliverable" | "missed_constraint" | "insufficient_answer_depth" | "unnecessary_deferral")[]>;
|
|
44
|
+
confidence: Type.TNumber;
|
|
45
|
+
probabilities: Type.TObject<{
|
|
46
|
+
[k: string]: Type.TNumber;
|
|
47
|
+
}>;
|
|
48
|
+
}>;
|
|
49
|
+
fulfillment: Type.TObject<{
|
|
50
|
+
type: Type.TLiteral<"score">;
|
|
51
|
+
score: Type.TNumber;
|
|
52
|
+
confidence: Type.TNumber;
|
|
53
|
+
probabilities: Type.TObject<{
|
|
54
|
+
"0": Type.TNumber;
|
|
55
|
+
"1": Type.TNumber;
|
|
56
|
+
"2": Type.TNumber;
|
|
57
|
+
"3": Type.TNumber;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
deliverableFit: Type.TObject<{
|
|
61
|
+
type: Type.TLiteral<"score">;
|
|
62
|
+
score: Type.TNumber;
|
|
63
|
+
confidence: Type.TNumber;
|
|
64
|
+
probabilities: Type.TObject<{
|
|
65
|
+
"0": Type.TNumber;
|
|
66
|
+
"1": Type.TNumber;
|
|
67
|
+
"2": Type.TNumber;
|
|
68
|
+
"3": Type.TNumber;
|
|
69
|
+
}>;
|
|
70
|
+
}>;
|
|
71
|
+
consistency: Type.TObject<{
|
|
72
|
+
type: Type.TLiteral<"choice">;
|
|
73
|
+
choice: Type.TEnum<("not_assessable" | "consistent" | "contradicted")[]>;
|
|
74
|
+
confidence: Type.TNumber;
|
|
75
|
+
probabilities: Type.TObject<{
|
|
76
|
+
[k: string]: Type.TNumber;
|
|
77
|
+
}>;
|
|
78
|
+
}>;
|
|
79
|
+
}>;
|
|
80
|
+
}>;
|
|
81
|
+
declare const sentimentSchema: Type.TObject<{
|
|
82
|
+
sentiment: Type.TObject<{
|
|
83
|
+
type: Type.TLiteral<"choice">;
|
|
84
|
+
choice: Type.TEnum<("satisfied" | "dissatisfied" | "mixed" | "neutral" | "unrelated" | "unclear")[]>;
|
|
85
|
+
confidence: Type.TNumber;
|
|
86
|
+
probabilities: Type.TObject<{
|
|
87
|
+
[k: string]: Type.TNumber;
|
|
88
|
+
}>;
|
|
89
|
+
}>;
|
|
90
|
+
annoyance: Type.TObject<{
|
|
91
|
+
type: Type.TLiteral<"noul">;
|
|
92
|
+
noul: Type.TNumber;
|
|
93
|
+
}>;
|
|
94
|
+
frustration: Type.TObject<{
|
|
95
|
+
type: Type.TLiteral<"noul">;
|
|
96
|
+
noul: Type.TNumber;
|
|
97
|
+
}>;
|
|
98
|
+
dissatisfactionIntensity: Type.TObject<{
|
|
99
|
+
type: Type.TLiteral<"score">;
|
|
100
|
+
score: Type.TNumber;
|
|
101
|
+
confidence: Type.TNumber;
|
|
102
|
+
probabilities: Type.TObject<{
|
|
103
|
+
"0": Type.TNumber;
|
|
104
|
+
"1": Type.TNumber;
|
|
105
|
+
"2": Type.TNumber;
|
|
106
|
+
"3": Type.TNumber;
|
|
107
|
+
}>;
|
|
108
|
+
}>;
|
|
109
|
+
}>;
|
|
110
|
+
declare const feedbackSchema: Type.TObject<{
|
|
111
|
+
answers: Type.TObject<{
|
|
112
|
+
feedbackType: Type.TObject<{
|
|
113
|
+
type: Type.TLiteral<"choice">;
|
|
114
|
+
choice: Type.TEnum<("mixed" | "unrelated" | "unclear" | "acceptance" | "correction" | "continuation")[]>;
|
|
115
|
+
confidence: Type.TNumber;
|
|
116
|
+
probabilities: Type.TObject<{
|
|
117
|
+
[k: string]: Type.TNumber;
|
|
118
|
+
}>;
|
|
119
|
+
}>;
|
|
120
|
+
target: Type.TObject<{
|
|
121
|
+
type: Type.TLiteral<"choice">;
|
|
122
|
+
choice: Type.TEnum<("delivery" | "mixed" | "unclear" | "current_answer" | "earlier_behavior" | "proactive_action" | "external" | "new_work")[]>;
|
|
123
|
+
confidence: Type.TNumber;
|
|
124
|
+
probabilities: Type.TObject<{
|
|
125
|
+
[k: string]: Type.TNumber;
|
|
126
|
+
}>;
|
|
127
|
+
}>;
|
|
128
|
+
avoidableRework: Type.TObject<{
|
|
129
|
+
type: Type.TLiteral<"noul">;
|
|
130
|
+
noul: Type.TNumber;
|
|
131
|
+
}>;
|
|
132
|
+
repeatedConstraint: Type.TObject<{
|
|
133
|
+
type: Type.TLiteral<"noul">;
|
|
134
|
+
noul: Type.TNumber;
|
|
135
|
+
}>;
|
|
136
|
+
memoryGap: Type.TObject<{
|
|
137
|
+
type: Type.TLiteral<"noul">;
|
|
138
|
+
noul: Type.TNumber;
|
|
139
|
+
}>;
|
|
140
|
+
}>;
|
|
141
|
+
}>;
|
|
142
|
+
type ResponseQuality = Static<typeof qualitySchema>["answers"];
|
|
143
|
+
type ResponseFeedback = Static<typeof feedbackSchema>["answers"] & Partial<Static<typeof sentimentSchema>>;
|
|
144
|
+
export type ResponseJudgment = {
|
|
145
|
+
quality: ResponseQuality;
|
|
146
|
+
feedback: ResponseFeedback;
|
|
147
|
+
};
|
|
148
|
+
export type ResponseStages = {
|
|
149
|
+
quality: ResponseQuality;
|
|
150
|
+
feedback: Static<typeof feedbackSchema>["answers"];
|
|
151
|
+
sentiment: Static<typeof sentimentSchema>;
|
|
152
|
+
retrospective: Awaited<ReturnType<typeof judgeResponseFollowup>>;
|
|
153
|
+
memory: Awaited<ReturnType<typeof judgeMemoryOpportunity>>;
|
|
154
|
+
};
|
|
155
|
+
type StageCache = Partial<ResponseStages> & {
|
|
156
|
+
begin: (stages: (keyof ResponseStages)[]) => void;
|
|
157
|
+
save: <K extends keyof ResponseStages>(stage: K, result: ResponseStages[K]) => void;
|
|
158
|
+
};
|
|
159
|
+
/** Separate requests are deliberate: later feedback must not leak into the original quality grade. */
|
|
160
|
+
export declare function judgeResponse(episode: ResponseEpisode, params: {
|
|
161
|
+
apiKey: string;
|
|
162
|
+
timeoutMs: number;
|
|
163
|
+
signal: AbortSignal;
|
|
164
|
+
}, sentimentEnabled?: boolean, cache?: StageCache): Promise<ResponseJudgment>;
|
|
165
|
+
declare const retrospectiveSchema: Type.TObject<{
|
|
166
|
+
answers: Type.TObject<{
|
|
167
|
+
correction: Type.TObject<{
|
|
168
|
+
type: Type.TLiteral<"noul">;
|
|
169
|
+
noul: Type.TNumber;
|
|
170
|
+
}>;
|
|
171
|
+
deliveryAdmission: Type.TObject<{
|
|
172
|
+
type: Type.TLiteral<"noul">;
|
|
173
|
+
noul: Type.TNumber;
|
|
174
|
+
}>;
|
|
175
|
+
regression: Type.TObject<{
|
|
176
|
+
type: Type.TLiteral<"noul">;
|
|
177
|
+
noul: Type.TNumber;
|
|
178
|
+
}>;
|
|
179
|
+
scopeClarification: Type.TObject<{
|
|
180
|
+
type: Type.TLiteral<"noul">;
|
|
181
|
+
noul: Type.TNumber;
|
|
182
|
+
}>;
|
|
183
|
+
outcome: Type.TObject<{
|
|
184
|
+
type: Type.TLiteral<"choice">;
|
|
185
|
+
choice: Type.TEnum<("unknown" | "reported_shortfall" | "acknowledged_success")[]>;
|
|
186
|
+
confidence: Type.TNumber;
|
|
187
|
+
probabilities: Type.TObject<{
|
|
188
|
+
[k: string]: Type.TNumber;
|
|
189
|
+
}>;
|
|
190
|
+
}>;
|
|
191
|
+
reason: Type.TObject<{
|
|
192
|
+
type: Type.TLiteral<"choice">;
|
|
193
|
+
choice: Type.TEnum<("none_or_unclear" | "missing_requested_work" | "unnecessary_deferral" | "regression" | "incorrect_claim" | "wrong_scope" | "failed_delivery")[]>;
|
|
194
|
+
confidence: Type.TNumber;
|
|
195
|
+
probabilities: Type.TObject<{
|
|
196
|
+
[k: string]: Type.TNumber;
|
|
197
|
+
}>;
|
|
198
|
+
}>;
|
|
199
|
+
}>;
|
|
200
|
+
}>;
|
|
201
|
+
/** Later evidence is kept in a third request and never changes the original grade. */
|
|
202
|
+
export declare function judgeResponseFollowup(episode: ResponseEpisode, params: {
|
|
203
|
+
apiKey: string;
|
|
204
|
+
timeoutMs: number;
|
|
205
|
+
signal: AbortSignal;
|
|
206
|
+
}): Promise<{
|
|
207
|
+
status: ResponseEpisode["followup"]["status"];
|
|
208
|
+
judgment: Static<typeof retrospectiveSchema>["answers"] | null;
|
|
209
|
+
}>;
|
|
210
|
+
export declare function judgeMemoryOpportunity(episode: ResponseEpisode, candidates: readonly {
|
|
211
|
+
path: string;
|
|
212
|
+
text: string;
|
|
213
|
+
hash: string;
|
|
214
|
+
}[], params: {
|
|
215
|
+
apiKey: string;
|
|
216
|
+
timeoutMs: number;
|
|
217
|
+
signal: AbortSignal;
|
|
218
|
+
}): Promise<{
|
|
219
|
+
path: string;
|
|
220
|
+
hash: string;
|
|
221
|
+
usefulness: number;
|
|
222
|
+
basis: string;
|
|
223
|
+
}[]>;
|
|
224
|
+
export {};
|