@a5c-ai/tasks-mux 5.0.1-staging.0cf58b544cb8
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 +103 -0
- package/dist/auth/forge-interface.d.ts +67 -0
- package/dist/auth/forge-interface.d.ts.map +1 -0
- package/dist/auth/forge-interface.js +69 -0
- package/dist/auth/github-app.d.ts +64 -0
- package/dist/auth/github-app.d.ts.map +1 -0
- package/dist/auth/github-app.js +141 -0
- package/dist/auth/github-oauth.d.ts +27 -0
- package/dist/auth/github-oauth.d.ts.map +1 -0
- package/dist/auth/github-oauth.js +89 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/jwt.d.ts +24 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +43 -0
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +36 -0
- package/dist/auth/ssh-keys.d.ts +21 -0
- package/dist/auth/ssh-keys.d.ts.map +1 -0
- package/dist/auth/ssh-keys.js +59 -0
- package/dist/auth/types.d.ts +165 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +53 -0
- package/dist/backend.d.ts +117 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +15 -0
- package/dist/backends/git-native.d.ts +51 -0
- package/dist/backends/git-native.d.ts.map +1 -0
- package/dist/backends/git-native.js +324 -0
- package/dist/backends/github-issues.d.ts +77 -0
- package/dist/backends/github-issues.d.ts.map +1 -0
- package/dist/backends/github-issues.js +796 -0
- package/dist/backends/index.d.ts +48 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +139 -0
- package/dist/backends/server.d.ts +41 -0
- package/dist/backends/server.d.ts.map +1 -0
- package/dist/backends/server.js +298 -0
- package/dist/cli/auth-store.d.ts +49 -0
- package/dist/cli/auth-store.d.ts.map +1 -0
- package/dist/cli/auth-store.js +150 -0
- package/dist/cli/client-config.d.ts +10 -0
- package/dist/cli/client-config.d.ts.map +1 -0
- package/dist/cli/client-config.js +87 -0
- package/dist/cli/commands/ask.d.ts +3 -0
- package/dist/cli/commands/ask.d.ts.map +1 -0
- package/dist/cli/commands/ask.js +171 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +510 -0
- package/dist/cli/commands/breakpoints.d.ts +3 -0
- package/dist/cli/commands/breakpoints.d.ts.map +1 -0
- package/dist/cli/commands/breakpoints.js +152 -0
- package/dist/cli/commands/responder-loop.d.ts +3 -0
- package/dist/cli/commands/responder-loop.d.ts.map +1 -0
- package/dist/cli/commands/responder-loop.js +78 -0
- package/dist/cli/commands/responders.d.ts +3 -0
- package/dist/cli/commands/responders.d.ts.map +1 -0
- package/dist/cli/commands/responders.js +74 -0
- package/dist/cli/commands/server.d.ts +3 -0
- package/dist/cli/commands/server.d.ts.map +1 -0
- package/dist/cli/commands/server.js +34 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +143 -0
- package/dist/cli/program.d.ts +6 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +32 -0
- package/dist/client/answer-poller.d.ts +52 -0
- package/dist/client/answer-poller.d.ts.map +1 -0
- package/dist/client/answer-poller.js +199 -0
- package/dist/client/auth-client.d.ts +200 -0
- package/dist/client/auth-client.d.ts.map +1 -0
- package/dist/client/auth-client.js +309 -0
- package/dist/client/breakpoint-router.d.ts +45 -0
- package/dist/client/breakpoint-router.d.ts.map +1 -0
- package/dist/client/breakpoint-router.js +45 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/client/profile-validator.d.ts +34 -0
- package/dist/client/profile-validator.d.ts.map +1 -0
- package/dist/client/profile-validator.js +89 -0
- package/dist/client/responder-client.d.ts +39 -0
- package/dist/client/responder-client.d.ts.map +1 -0
- package/dist/client/responder-client.js +72 -0
- package/dist/client/responder-matcher.d.ts +49 -0
- package/dist/client/responder-matcher.d.ts.map +1 -0
- package/dist/client/responder-matcher.js +226 -0
- package/dist/client/server-client.d.ts +124 -0
- package/dist/client/server-client.d.ts.map +1 -0
- package/dist/client/server-client.js +266 -0
- package/dist/client/timeout-manager.d.ts +47 -0
- package/dist/client/timeout-manager.d.ts.map +1 -0
- package/dist/client/timeout-manager.js +77 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +93 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +2 -0
- package/dist/harness/interaction-provider.d.ts +71 -0
- package/dist/harness/interaction-provider.d.ts.map +1 -0
- package/dist/harness/interaction-provider.js +124 -0
- package/dist/harness/routing-rules.d.ts +7 -0
- package/dist/harness/routing-rules.d.ts.map +1 -0
- package/dist/harness/routing-rules.js +37 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/mcp/backend-resolver.d.ts +43 -0
- package/dist/mcp/backend-resolver.d.ts.map +1 -0
- package/dist/mcp/backend-resolver.js +111 -0
- package/dist/mcp/http-transport.d.ts +37 -0
- package/dist/mcp/http-transport.d.ts.map +1 -0
- package/dist/mcp/http-transport.js +103 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/server.d.ts +20 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +121 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts +32 -0
- package/dist/mcp/tools/answer-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/answer-breakpoint.js +45 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts +58 -0
- package/dist/mcp/tools/ask-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/ask-breakpoint.js +78 -0
- package/dist/mcp/tools/check-status.d.ts +16 -0
- package/dist/mcp/tools/check-status.d.ts.map +1 -0
- package/dist/mcp/tools/check-status.js +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts +18 -0
- package/dist/mcp/tools/claim-breakpoint.d.ts.map +1 -0
- package/dist/mcp/tools/claim-breakpoint.js +28 -0
- package/dist/mcp/tools/list-breakpoints.d.ts +16 -0
- package/dist/mcp/tools/list-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/list-breakpoints.js +14 -0
- package/dist/mcp/tools/list-responders.d.ts +18 -0
- package/dist/mcp/tools/list-responders.d.ts.map +1 -0
- package/dist/mcp/tools/list-responders.js +37 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts +18 -0
- package/dist/mcp/tools/poll-breakpoints.d.ts.map +1 -0
- package/dist/mcp/tools/poll-breakpoints.js +36 -0
- package/dist/mcp/tools/verify-answer.d.ts +16 -0
- package/dist/mcp/tools/verify-answer.d.ts.map +1 -0
- package/dist/mcp/tools/verify-answer.js +38 -0
- package/dist/proven/index.d.ts +5 -0
- package/dist/proven/index.d.ts.map +1 -0
- package/dist/proven/index.js +3 -0
- package/dist/proven/keys.d.ts +33 -0
- package/dist/proven/keys.d.ts.map +1 -0
- package/dist/proven/keys.js +117 -0
- package/dist/proven/sign.d.ts +16 -0
- package/dist/proven/sign.d.ts.map +1 -0
- package/dist/proven/sign.js +60 -0
- package/dist/proven/types.d.ts +26 -0
- package/dist/proven/types.d.ts.map +1 -0
- package/dist/proven/types.js +5 -0
- package/dist/proven/verify.d.ts +6 -0
- package/dist/proven/verify.d.ts.map +1 -0
- package/dist/proven/verify.js +58 -0
- package/dist/types.d.ts +4034 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +244 -0
- package/package.json +86 -0
- package/responder/README.md +42 -0
- package/responder/backend-responder.json +9 -0
- package/responder/devops-responder.json +9 -0
- package/responder/frontend-responder.json +9 -0
- package/responder/schema.json +52 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { generateBreakpointId, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, BREAKPOINTS_DIR, BreakpointSchema, BreakpointPublicAnswerSchema, ProvenBreakpointAnswerSchema, isProvenBreakpointAnswer, } from "../types.js";
|
|
4
|
+
import { signAnswer, signAnswerWithKeyRecord } from "../proven/sign.js";
|
|
5
|
+
import { verifyAnswer as verifyProvenAnswer } from "../proven/verify.js";
|
|
6
|
+
import { selectBreakpointAnswer as selectPublicBreakpointAnswer } from "../backend.js";
|
|
7
|
+
export class GitNativeBackend {
|
|
8
|
+
name = "git-native";
|
|
9
|
+
breakpointsDir;
|
|
10
|
+
defaultPollIntervalMs;
|
|
11
|
+
defaultTimeoutMs;
|
|
12
|
+
signingKeyPath;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.breakpointsDir = options?.breakpointsDir
|
|
15
|
+
?? path.resolve(process.cwd(), BREAKPOINTS_DIR);
|
|
16
|
+
this.defaultPollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
17
|
+
this.defaultTimeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
18
|
+
this.signingKeyPath = options?.signingKeyPath;
|
|
19
|
+
}
|
|
20
|
+
breakpointPath(id) {
|
|
21
|
+
return path.join(this.breakpointsDir, `${id}.json`);
|
|
22
|
+
}
|
|
23
|
+
answerPath(id) {
|
|
24
|
+
return path.join(this.breakpointsDir, `${id}.answer.json`);
|
|
25
|
+
}
|
|
26
|
+
provenPath(id) {
|
|
27
|
+
return path.join(this.breakpointsDir, `${id}.proven.json`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load the signing key from the configured signingKeyPath.
|
|
31
|
+
* Returns null if no signing key is configured or the file cannot be read.
|
|
32
|
+
*/
|
|
33
|
+
async loadSigningKey() {
|
|
34
|
+
if (!this.signingKeyPath)
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
const raw = await fs.readFile(this.signingKeyPath, "utf-8");
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Load a proven answer file for a breakpoint, if it exists.
|
|
46
|
+
*/
|
|
47
|
+
async loadProvenAnswer(id) {
|
|
48
|
+
try {
|
|
49
|
+
const raw = await fs.readFile(this.provenPath(id), "utf-8");
|
|
50
|
+
return ProvenBreakpointAnswerSchema.parse(JSON.parse(raw));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async loadStoredAnswer(id) {
|
|
57
|
+
try {
|
|
58
|
+
const raw = await fs.readFile(this.answerPath(id), "utf-8");
|
|
59
|
+
return BreakpointPublicAnswerSchema.parse(JSON.parse(raw));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async loadPublicAnswer(id) {
|
|
66
|
+
const [storedAnswer, provenAnswer] = await Promise.all([
|
|
67
|
+
this.loadStoredAnswer(id),
|
|
68
|
+
this.loadProvenAnswer(id),
|
|
69
|
+
]);
|
|
70
|
+
return provenAnswer ?? storedAnswer;
|
|
71
|
+
}
|
|
72
|
+
async submitBreakpoint(params) {
|
|
73
|
+
await fs.mkdir(this.breakpointsDir, { recursive: true });
|
|
74
|
+
const id = generateBreakpointId();
|
|
75
|
+
const now = new Date().toISOString();
|
|
76
|
+
const timeoutMs = params.routing.timeoutMs || this.defaultTimeoutMs;
|
|
77
|
+
const breakpoint = {
|
|
78
|
+
id,
|
|
79
|
+
text: params.text,
|
|
80
|
+
context: params.context,
|
|
81
|
+
status: "pending",
|
|
82
|
+
routing: params.routing,
|
|
83
|
+
answers: [],
|
|
84
|
+
projectId: params.projectId,
|
|
85
|
+
repoId: params.repoId,
|
|
86
|
+
createdAt: now,
|
|
87
|
+
updatedAt: now,
|
|
88
|
+
expiresAt: new Date(Date.now() + timeoutMs).toISOString(),
|
|
89
|
+
};
|
|
90
|
+
// Validate before writing
|
|
91
|
+
BreakpointSchema.parse(breakpoint);
|
|
92
|
+
await fs.writeFile(this.breakpointPath(id), JSON.stringify(breakpoint, null, 2) + "\n", "utf-8");
|
|
93
|
+
return breakpoint;
|
|
94
|
+
}
|
|
95
|
+
async getBreakpoint(id) {
|
|
96
|
+
const raw = await fs.readFile(this.breakpointPath(id), "utf-8");
|
|
97
|
+
const breakpoint = BreakpointSchema.parse(JSON.parse(raw));
|
|
98
|
+
const answer = await this.loadPublicAnswer(id);
|
|
99
|
+
if (answer) {
|
|
100
|
+
const existingIndex = breakpoint.answers.findIndex((candidate) => candidate.id === answer.id);
|
|
101
|
+
if (existingIndex === -1) {
|
|
102
|
+
breakpoint.answers.push(answer);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
breakpoint.answers[existingIndex] = answer;
|
|
106
|
+
}
|
|
107
|
+
if (breakpoint.status === "pending" || breakpoint.status === "claimed") {
|
|
108
|
+
breakpoint.status = "answered";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const selectedAnswer = selectPublicBreakpointAnswer(breakpoint);
|
|
112
|
+
if (selectedAnswer && isProvenBreakpointAnswer(selectedAnswer)) {
|
|
113
|
+
const verification = await this.verifyProvenFile(selectedAnswer);
|
|
114
|
+
// Attach verification result as metadata on the breakpoint
|
|
115
|
+
breakpoint
|
|
116
|
+
.provenVerification = verification;
|
|
117
|
+
}
|
|
118
|
+
return breakpoint;
|
|
119
|
+
}
|
|
120
|
+
async waitForAnswer(id, options) {
|
|
121
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
122
|
+
const pollIntervalMs = options?.pollIntervalMs ?? this.defaultPollIntervalMs;
|
|
123
|
+
const signal = options?.signal;
|
|
124
|
+
const startTime = Date.now();
|
|
125
|
+
while (true) {
|
|
126
|
+
if (signal?.aborted) {
|
|
127
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
128
|
+
return {
|
|
129
|
+
answered: false,
|
|
130
|
+
breakpoint,
|
|
131
|
+
allAnswers: breakpoint.answers,
|
|
132
|
+
resolution: "aborted",
|
|
133
|
+
elapsedMs: Date.now() - startTime,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Check for answer file
|
|
137
|
+
try {
|
|
138
|
+
await fs.access(this.answerPath(id));
|
|
139
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
140
|
+
const answer = selectPublicBreakpointAnswer(breakpoint);
|
|
141
|
+
return {
|
|
142
|
+
answered: true,
|
|
143
|
+
breakpoint: { ...breakpoint, status: "answered" },
|
|
144
|
+
answer,
|
|
145
|
+
allAnswers: breakpoint.answers,
|
|
146
|
+
resolution: "answered",
|
|
147
|
+
elapsedMs: Date.now() - startTime,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// No answer yet
|
|
152
|
+
}
|
|
153
|
+
// Check cancellation status
|
|
154
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
155
|
+
if (breakpoint.status === "cancelled") {
|
|
156
|
+
return {
|
|
157
|
+
answered: false,
|
|
158
|
+
breakpoint,
|
|
159
|
+
allAnswers: breakpoint.answers,
|
|
160
|
+
resolution: "cancelled",
|
|
161
|
+
elapsedMs: Date.now() - startTime,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (Date.now() - startTime >= timeoutMs) {
|
|
165
|
+
return {
|
|
166
|
+
answered: false,
|
|
167
|
+
breakpoint,
|
|
168
|
+
allAnswers: breakpoint.answers,
|
|
169
|
+
resolution: "timeout",
|
|
170
|
+
elapsedMs: Date.now() - startTime,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Wait before next poll
|
|
174
|
+
await new Promise((resolve) => {
|
|
175
|
+
const timer = setTimeout(resolve, pollIntervalMs);
|
|
176
|
+
if (signal) {
|
|
177
|
+
const onAbort = () => {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
resolve();
|
|
180
|
+
};
|
|
181
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async listPendingBreakpoints(responderId) {
|
|
187
|
+
let files;
|
|
188
|
+
try {
|
|
189
|
+
files = await fs.readdir(this.breakpointsDir);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
const pending = [];
|
|
195
|
+
for (const file of files) {
|
|
196
|
+
if (!file.endsWith(".json") || file.includes(".answer.") || file.includes(".proven.")) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const raw = await fs.readFile(path.join(this.breakpointsDir, file), "utf-8");
|
|
201
|
+
const bp = BreakpointSchema.parse(JSON.parse(raw));
|
|
202
|
+
if (bp.status !== "pending" && bp.status !== "routed")
|
|
203
|
+
continue;
|
|
204
|
+
// Check expiration
|
|
205
|
+
if (new Date(bp.expiresAt) < new Date())
|
|
206
|
+
continue;
|
|
207
|
+
// Filter by responder if specified
|
|
208
|
+
if (responderId && bp.routing.targetResponders.length > 0) {
|
|
209
|
+
if (!bp.routing.targetResponders.includes(responderId))
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
// Check if answer already exists
|
|
213
|
+
try {
|
|
214
|
+
await fs.access(this.answerPath(bp.id));
|
|
215
|
+
continue; // Already answered
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// No answer, include it
|
|
219
|
+
}
|
|
220
|
+
pending.push(bp);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Skip malformed files
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return pending;
|
|
227
|
+
}
|
|
228
|
+
async answerBreakpoint(id, answer) {
|
|
229
|
+
// Verify breakpoint exists
|
|
230
|
+
await this.getBreakpoint(id);
|
|
231
|
+
const answerId = generateBreakpointId();
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
const breakpointAnswer = {
|
|
234
|
+
id: answerId,
|
|
235
|
+
breakpointId: id,
|
|
236
|
+
responderId: answer.responderId,
|
|
237
|
+
responderName: answer.responderName,
|
|
238
|
+
text: answer.text,
|
|
239
|
+
approved: answer.approved,
|
|
240
|
+
confidence: answer.confidence ?? 80,
|
|
241
|
+
references: answer.references ?? [],
|
|
242
|
+
followUpQuestions: answer.followUpQuestions ?? [],
|
|
243
|
+
answeredAt: now,
|
|
244
|
+
decisionMemory: answer.decisionMemory
|
|
245
|
+
? { ...answer.decisionMemory, savedAt: now }
|
|
246
|
+
: undefined,
|
|
247
|
+
};
|
|
248
|
+
if (answer.sign === false && answer.keyFingerprint) {
|
|
249
|
+
throw new Error("keyFingerprint cannot be used when sign=false");
|
|
250
|
+
}
|
|
251
|
+
let publicAnswer = breakpointAnswer;
|
|
252
|
+
if (answer.keyFingerprint) {
|
|
253
|
+
publicAnswer = await signAnswer(breakpointAnswer, answer.keyFingerprint, this.breakpointsDir);
|
|
254
|
+
}
|
|
255
|
+
else if (answer.sign === true) {
|
|
256
|
+
const signingKey = await this.loadSigningKey();
|
|
257
|
+
if (!signingKey) {
|
|
258
|
+
throw new Error("answer_breakpoint.sign requested, but no signing key is configured");
|
|
259
|
+
}
|
|
260
|
+
publicAnswer = signAnswerWithKeyRecord(breakpointAnswer, signingKey);
|
|
261
|
+
}
|
|
262
|
+
else if (answer.sign !== false) {
|
|
263
|
+
const signingKey = await this.loadSigningKey();
|
|
264
|
+
if (signingKey) {
|
|
265
|
+
publicAnswer = signAnswerWithKeyRecord(breakpointAnswer, signingKey);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
BreakpointPublicAnswerSchema.parse(publicAnswer);
|
|
269
|
+
await fs.writeFile(this.answerPath(id), JSON.stringify(publicAnswer, null, 2) + "\n", "utf-8");
|
|
270
|
+
if (isProvenBreakpointAnswer(publicAnswer)) {
|
|
271
|
+
await fs.writeFile(this.provenPath(id), JSON.stringify(publicAnswer, null, 2) + "\n", "utf-8");
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
await fs.rm(this.provenPath(id), { force: true });
|
|
275
|
+
}
|
|
276
|
+
// Update the breakpoint status
|
|
277
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
278
|
+
delete breakpoint.provenVerification;
|
|
279
|
+
breakpoint.status = "answered";
|
|
280
|
+
breakpoint.updatedAt = now;
|
|
281
|
+
await fs.writeFile(this.breakpointPath(id), JSON.stringify(breakpoint, null, 2) + "\n", "utf-8");
|
|
282
|
+
return publicAnswer;
|
|
283
|
+
}
|
|
284
|
+
async cancelBreakpoint(id) {
|
|
285
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
286
|
+
delete breakpoint.provenVerification;
|
|
287
|
+
breakpoint.status = "cancelled";
|
|
288
|
+
breakpoint.updatedAt = new Date().toISOString();
|
|
289
|
+
await fs.writeFile(this.breakpointPath(id), JSON.stringify(breakpoint, null, 2) + "\n", "utf-8");
|
|
290
|
+
}
|
|
291
|
+
async claimBreakpoint(id, responderId) {
|
|
292
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
293
|
+
delete breakpoint.provenVerification;
|
|
294
|
+
breakpoint.status = "claimed";
|
|
295
|
+
breakpoint.claimedByResponderId = responderId;
|
|
296
|
+
breakpoint.updatedAt = new Date().toISOString();
|
|
297
|
+
await fs.writeFile(this.breakpointPath(id), JSON.stringify(breakpoint, null, 2) + "\n", "utf-8");
|
|
298
|
+
return breakpoint;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Verify the selected public answer against trusted public keys.
|
|
302
|
+
*/
|
|
303
|
+
async verifyAnswer(id) {
|
|
304
|
+
const breakpoint = await this.getBreakpoint(id);
|
|
305
|
+
const answer = selectPublicBreakpointAnswer(breakpoint);
|
|
306
|
+
if (!answer || !isProvenBreakpointAnswer(answer)) {
|
|
307
|
+
return {
|
|
308
|
+
valid: false,
|
|
309
|
+
reason: "No signed answer found",
|
|
310
|
+
verifiedAt: new Date().toISOString(),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return this.verifyProvenFile(answer);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Verify a loaded ProvenBreakpointAnswer against trusted keys in the
|
|
317
|
+
* breakpoints directory.
|
|
318
|
+
*/
|
|
319
|
+
async verifyProvenFile(provenAnswer) {
|
|
320
|
+
// The proven/verify module's loadTrustedPublicKeys uses baseDir/.keys/trusted/
|
|
321
|
+
// Our breakpointsDir IS the .breakpoints directory, so we pass it as baseDir.
|
|
322
|
+
return verifyProvenAnswer(provenAnswer, this.breakpointsDir);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { BreakpointBackend, SubmitBreakpointParams, WaitForAnswerOptions, SubmitAnswerParams, ListRespondersParams } from "../backend.js";
|
|
2
|
+
import type { Breakpoint, BreakpointAnswer, BreakpointWaitResult, ResponderProfile, GitHubIssuesBackendConfig } from "../types.js";
|
|
3
|
+
interface ParsedAnswer {
|
|
4
|
+
text: string;
|
|
5
|
+
confidence?: number;
|
|
6
|
+
references?: string[];
|
|
7
|
+
responderId?: string;
|
|
8
|
+
responderName?: string;
|
|
9
|
+
breakpointId?: string;
|
|
10
|
+
answeredAt?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a GitHub token by trying `gh auth token` first,
|
|
14
|
+
* then falling back to the GITHUB_TOKEN environment variable.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getGitHubToken(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Parse an answer from a GitHub issue comment body.
|
|
19
|
+
* Looks for a hidden JSON payload block, a "## Answer" heading anywhere
|
|
20
|
+
* in the body, a JSON code block with an "answer" field, or a sufficiently
|
|
21
|
+
* long plain-text comment (for assigned responders handled upstream).
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseAnswerFromComment(body: string, options?: {
|
|
24
|
+
isAssignedResponder?: boolean;
|
|
25
|
+
}): ParsedAnswer | null;
|
|
26
|
+
/**
|
|
27
|
+
* BreakpointBackend implementation backed by GitHub Issues.
|
|
28
|
+
*
|
|
29
|
+
* Each breakpoint maps to a GitHub issue; answers are detected
|
|
30
|
+
* from issue comments containing payload blocks, "## Answer" markers, or JSON blocks.
|
|
31
|
+
*/
|
|
32
|
+
export declare class GitHubIssuesBackend implements BreakpointBackend {
|
|
33
|
+
readonly name = "github-issues";
|
|
34
|
+
private owner;
|
|
35
|
+
private repo;
|
|
36
|
+
private labels;
|
|
37
|
+
private assignees;
|
|
38
|
+
private defaultPollIntervalMs;
|
|
39
|
+
private defaultTimeoutMs;
|
|
40
|
+
/**
|
|
41
|
+
* Maps breakpointId -> GitHub issue number.
|
|
42
|
+
*
|
|
43
|
+
* Used for legacy non-`gh-{number}` IDs within a single process.
|
|
44
|
+
* New breakpoints always use `gh-{number}` IDs and remain durable
|
|
45
|
+
* across backend restarts without this map.
|
|
46
|
+
*/
|
|
47
|
+
private issueMap;
|
|
48
|
+
private tokenOverride?;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the GitHub issue number from a breakpoint ID.
|
|
51
|
+
* Checks the in-memory map first, then parses the `gh-{number}` format
|
|
52
|
+
* so that IDs survive across backend instances.
|
|
53
|
+
*/
|
|
54
|
+
private resolveIssueNumber;
|
|
55
|
+
constructor(config: GitHubIssuesBackendConfig);
|
|
56
|
+
/**
|
|
57
|
+
* Override the token resolution for testing.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
setToken(token: string): void;
|
|
61
|
+
private getToken;
|
|
62
|
+
private githubFetch;
|
|
63
|
+
private repoPath;
|
|
64
|
+
private mapIssueToBreakpoint;
|
|
65
|
+
private buildAnswerFromComment;
|
|
66
|
+
private formatIssueBody;
|
|
67
|
+
submitBreakpoint(params: SubmitBreakpointParams): Promise<Breakpoint>;
|
|
68
|
+
getBreakpoint(id: string): Promise<Breakpoint>;
|
|
69
|
+
waitForAnswer(id: string, options?: WaitForAnswerOptions): Promise<BreakpointWaitResult>;
|
|
70
|
+
listResponders(_params?: ListRespondersParams): Promise<ResponderProfile[]>;
|
|
71
|
+
claimBreakpoint(id: string, responderId: string): Promise<Breakpoint>;
|
|
72
|
+
answerBreakpoint(id: string, answer: SubmitAnswerParams): Promise<BreakpointAnswer>;
|
|
73
|
+
listPendingBreakpoints(responderId?: string): Promise<Breakpoint[]>;
|
|
74
|
+
cancelBreakpoint(id: string): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=github-issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-issues.d.ts","sourceRoot":"","sources":["../../src/backends/github-issues.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EAKpB,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAwDrB,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA0LD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAevC;AAQD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1C,YAAY,GAAG,IAAI,CAwDrB;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,YAAW,iBAAiB;IAC3D,QAAQ,CAAC,IAAI,mBAAmB;IAEhC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,gBAAgB,CAAS;IAEjC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAA6B;IAE7C,OAAO,CAAC,aAAa,CAAC,CAAS;IAE/B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;gBAcd,MAAM,EAAE,yBAAyB;IAS7C;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B,OAAO,CAAC,QAAQ;YAKF,WAAW;IA6CzB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,oBAAoB;IAsD5B,OAAO,CAAC,sBAAsB;IAuB9B,OAAO,CAAC,eAAe;IAgHjB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC;IAiCrE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAsC9C,aAAa,CACjB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,oBAAoB,CAAC;IAmG1B,cAAc,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAY3E,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BrE,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,gBAAgB,CAAC;IAwEtB,sBAAsB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IA0BnE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBlD"}
|