@doist/doistbot-cli 1.0.4 → 1.0.6
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/dist/actions/auth.js +62 -28
- package/dist/actions/doctor.js +16 -11
- package/dist/actions/review.js +77 -11
- package/dist/auth.js +53 -8
- package/dist/config.js +40 -6
- package/dist/env.js +1 -1
- package/dist/file-store.js +1 -1
- package/dist/runtime.js +1 -1
- package/dist/terminal.js +2 -7
- package/package.json +7 -7
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +9 -85
- package/sandbox/dist/core/bot-logins.js +0 -13
- package/sandbox/dist/core/check-run.js +16 -0
- package/sandbox/dist/core/datadog-metrics.js +6 -54
- package/sandbox/dist/core/logger-span-processor.js +78 -0
- package/sandbox/dist/core/pi.js +270 -186
- package/sandbox/dist/core/tracing-setup.js +15 -0
- package/sandbox/dist/core/tracing.js +71 -0
- package/sandbox/dist/main.js +4 -1
- package/sandbox/dist/providers/config.js +1 -1
- package/sandbox/dist/providers/credentials.js +40 -0
- package/sandbox/dist/providers/gemini.js +8 -10
- package/sandbox/dist/providers/helpers.js +18 -38
- package/sandbox/dist/providers/index.js +4 -3
- package/sandbox/dist/providers/openai.js +1 -8
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +28 -55
- package/sandbox/dist/providers/schemas.js +0 -39
- package/sandbox/dist/tasks/chat/chat.js +162 -101
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
- package/sandbox/dist/tasks/issue-summarize/context.js +1 -1
- package/sandbox/dist/tasks/issue-summarize/model.js +4 -2
- package/sandbox/dist/tasks/issue-summarize/summarize.js +1 -1
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +25 -2
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +31 -8
- package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +154 -101
- package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +15 -6
- package/sandbox/dist/tasks/issue-triage/model.js +120 -36
- package/sandbox/dist/tasks/issue-triage/output.js +27 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +3 -2
- package/sandbox/dist/tasks/issue-triage/resolution.js +26 -0
- package/sandbox/dist/tasks/issue-triage/routing.js +9 -9
- package/sandbox/dist/tasks/issue-triage/side-effects.js +9 -0
- package/sandbox/dist/tasks/issue-triage/triage.js +288 -418
- package/sandbox/dist/tasks/persist-logs/dd-proxy.js +2 -2
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +1 -1
- package/sandbox/dist/tasks/persist-logs/persist-logs.js +1 -1
- package/sandbox/dist/tasks/review/check-run.js +2 -1
- package/sandbox/dist/tasks/review/conversation-context.js +15 -8
- package/sandbox/dist/tasks/review/engines/dedupe.js +19 -9
- package/sandbox/dist/tasks/review/engines/multi-focus.js +62 -36
- package/sandbox/dist/tasks/review/engines/shared.js +15 -61
- package/sandbox/dist/tasks/review/github-comment-format.js +64 -10
- package/sandbox/dist/tasks/review/local.js +2 -29
- package/sandbox/dist/tasks/review/prompt.js +2 -49
- package/sandbox/dist/tasks/review/review-summary.js +120 -0
- package/sandbox/dist/tasks/review/review.js +49 -69
- package/sandbox/dist/tasks/review/runner.js +57 -52
- package/sandbox/dist/tasks/review/summary-model.js +172 -16
- package/sandbox/dist/tasks/review/thinking-level.js +4 -2
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +9 -85
- package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
- package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
- package/sandbox/dist/providers/anthropic.js +0 -30
- package/sandbox/dist/tasks/review/author-format.js +0 -14
- package/sandbox/dist/tasks/review/author-profile.js +0 -67
- package/sandbox/dist/tasks/review/council/normalizer.js +0 -104
- package/sandbox/dist/tasks/review/council/orchestrator.js +0 -194
- package/sandbox/dist/tasks/review/council/provider-health.js +0 -31
- package/sandbox/dist/tasks/review/council/summary.js +0 -192
- package/sandbox/dist/tasks/review/council/types.js +0 -4
- package/sandbox/dist/tasks/review/council/voter.js +0 -384
- package/sandbox/dist/tasks/review/engines/council.js +0 -126
- package/sandbox/dist/tasks/review/engines/index.js +0 -12
- package/sandbox/dist/tasks/review/review-engine.js +0 -5
- package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
- package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
- package/sandbox/review-prompt.md +0 -209
- package/sandbox/vote-prompt.md +0 -69
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
2
3
|
import { isKnownDoistbotLogin } from '../../core/bot-logins.js';
|
|
3
4
|
import { recordTaskMetrics } from '../../core/datadog-metrics.js';
|
|
4
5
|
import { logger } from '../../core/logging.js';
|
|
@@ -6,9 +7,12 @@ import { invokePiPrompt } from '../../core/pi.js';
|
|
|
6
7
|
import { loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
7
8
|
import { removeIssueCommentReactionIfPresent } from '../../core/reactions.js';
|
|
8
9
|
import { cloneRepository, createGitHubClients, parseBaseContext, requiredEnv, WORKSPACE_DIR, } from '../../core/shared.js';
|
|
10
|
+
import { withPhase } from '../../core/tracing.js';
|
|
9
11
|
import { buildPrompt } from './prompt.js';
|
|
10
12
|
const CHAT_PROMPT_FILE = 'chat-prompt.md';
|
|
11
13
|
const CHAT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
14
|
+
const CHAT_PROVIDER = 'zai';
|
|
15
|
+
const CHAT_MODEL = 'z-ai/glm-5.2';
|
|
12
16
|
function parseContext() {
|
|
13
17
|
const base = parseBaseContext();
|
|
14
18
|
const reactionIdRaw = process.env.REACTION_ID;
|
|
@@ -19,6 +23,7 @@ function parseContext() {
|
|
|
19
23
|
reactionId: Number.isFinite(reactionId) ? reactionId : undefined,
|
|
20
24
|
commentType: requiredEnv('COMMENT_TYPE'),
|
|
21
25
|
eventName: requiredEnv('EVENT_NAME'),
|
|
26
|
+
dryRun: process.env.DRY_RUN?.toLowerCase() === 'true',
|
|
22
27
|
};
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
@@ -120,27 +125,48 @@ async function gatherIssueCommentContext(octokit, owner, repo, prNumber, comment
|
|
|
120
125
|
};
|
|
121
126
|
}
|
|
122
127
|
export async function invokeChatModel(prompt) {
|
|
123
|
-
const apiKey = process.env.
|
|
128
|
+
const apiKey = process.env.OPENROUTER_API_KEY?.trim();
|
|
124
129
|
if (!apiKey) {
|
|
130
|
+
// Config error: no model call attempted, so no model span. The reason is
|
|
131
|
+
// returned so the caller's root span reports the real cause.
|
|
125
132
|
logger.warn('Chat model API key missing');
|
|
126
|
-
return
|
|
133
|
+
return { ok: false, reason: 'OPENROUTER_API_KEY missing' };
|
|
127
134
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
return withPhase('chat.model', {
|
|
136
|
+
'gen_ai.operation.name': 'chat',
|
|
137
|
+
'gen_ai.provider.name': CHAT_PROVIDER,
|
|
138
|
+
'gen_ai.request.model': CHAT_MODEL,
|
|
139
|
+
}, async (span) => {
|
|
140
|
+
const result = await invokePiPrompt({
|
|
141
|
+
provider: CHAT_PROVIDER,
|
|
142
|
+
apiKey,
|
|
143
|
+
piProviderId: 'openrouter',
|
|
144
|
+
prompt,
|
|
145
|
+
cwd: WORKSPACE_DIR,
|
|
146
|
+
timeoutMs: CHAT_TIMEOUT_MS,
|
|
147
|
+
capabilityPreset: 'workspace-inspection',
|
|
148
|
+
// Keep the executed model in sync with the recorded span attr.
|
|
149
|
+
modelName: CHAT_MODEL,
|
|
140
150
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
// Failure returns a result rather than throwing — mark the span
|
|
153
|
+
// ERROR explicitly so it survives to phase.end (withPhase only
|
|
154
|
+
// auto-errors on a throw).
|
|
155
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error });
|
|
156
|
+
logger.warn('Chat model request failed', {
|
|
157
|
+
error: result.error,
|
|
158
|
+
timedOut: result.timedOut,
|
|
159
|
+
});
|
|
160
|
+
return { ok: false, reason: result.error };
|
|
161
|
+
}
|
|
162
|
+
const output = result.output.trim();
|
|
163
|
+
if (!output) {
|
|
164
|
+
const reason = 'chat model produced no output';
|
|
165
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
|
|
166
|
+
return { ok: false, reason };
|
|
167
|
+
}
|
|
168
|
+
return { ok: true, output };
|
|
169
|
+
});
|
|
144
170
|
}
|
|
145
171
|
/**
|
|
146
172
|
* Posts the response to the correct location.
|
|
@@ -169,6 +195,17 @@ async function postResponse(octokit, ctx, response) {
|
|
|
169
195
|
prNumber: ctx.prNumber,
|
|
170
196
|
});
|
|
171
197
|
}
|
|
198
|
+
function formatChatDryRunOutput(ctx, response) {
|
|
199
|
+
return JSON.stringify({
|
|
200
|
+
task: 'chat',
|
|
201
|
+
repository: `${ctx.owner}/${ctx.repo}`,
|
|
202
|
+
prNumber: ctx.prNumber,
|
|
203
|
+
commentId: ctx.commentId,
|
|
204
|
+
commentType: ctx.commentType,
|
|
205
|
+
eventName: ctx.eventName,
|
|
206
|
+
response,
|
|
207
|
+
}, null, 2);
|
|
208
|
+
}
|
|
172
209
|
/**
|
|
173
210
|
* Removes the 👀 reaction from the triggering comment.
|
|
174
211
|
*/
|
|
@@ -209,102 +246,126 @@ async function main() {
|
|
|
209
246
|
commentId: ctx.commentId,
|
|
210
247
|
};
|
|
211
248
|
logger.setBaseContext(logContext);
|
|
212
|
-
logger.info('Chat task started', {
|
|
213
|
-
commentType: ctx.commentType,
|
|
214
|
-
eventName: ctx.eventName,
|
|
215
|
-
});
|
|
216
|
-
const { app, readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
217
249
|
const reactionId = ctx.reactionId;
|
|
218
250
|
let exitCode = 0;
|
|
219
251
|
let metricOutcome = 'failure';
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
threadContext = await gatherReviewCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.commentId);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
threadContext = await gatherIssueCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.prNumber, ctx.commentId);
|
|
236
|
-
}
|
|
237
|
-
// Build prompt and invoke the chat model
|
|
238
|
-
const promptTemplate = loadPromptTemplateFile({
|
|
239
|
-
fileName: CHAT_PROMPT_FILE,
|
|
240
|
-
promptLabel: 'chat',
|
|
241
|
-
});
|
|
242
|
-
const prompt = buildPrompt({
|
|
243
|
-
promptTemplate,
|
|
244
|
-
owner: ctx.owner,
|
|
245
|
-
repo: ctx.repo,
|
|
246
|
-
prNumber: ctx.prNumber,
|
|
247
|
-
threadHistory: threadContext.threadHistory,
|
|
248
|
-
userQuestion: threadContext.userQuestion,
|
|
249
|
-
codeSnippet: threadContext.codeSnippet,
|
|
250
|
-
prTitle: threadContext.prTitle,
|
|
251
|
-
prAuthor: threadContext.prAuthor,
|
|
252
|
-
});
|
|
253
|
-
const response = await invokeChatModel(prompt);
|
|
254
|
-
if (!response) {
|
|
255
|
-
logger.warn('Chat model request returned no output');
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
// Post response
|
|
259
|
-
await postResponse(writeOctokit, ctx, response);
|
|
260
|
-
logger.info('Chat task completed');
|
|
261
|
-
metricOutcome = 'success';
|
|
262
|
-
}
|
|
263
|
-
catch (error) {
|
|
264
|
-
logger.error('Chat task failed', {
|
|
265
|
-
error: error instanceof Error ? error.message : String(error),
|
|
266
|
-
});
|
|
267
|
-
// Post error message to the PR
|
|
252
|
+
await withPhase('chat', {
|
|
253
|
+
task: 'chat',
|
|
254
|
+
'gen_ai.operation.name': 'invoke_agent',
|
|
255
|
+
'gen_ai.agent.name': 'doistbot.chat',
|
|
256
|
+
'pr.number': ctx.prNumber,
|
|
257
|
+
'comment.id': ctx.commentId,
|
|
258
|
+
'comment.type': ctx.commentType,
|
|
259
|
+
'event.name': ctx.eventName,
|
|
260
|
+
dry_run: ctx.dryRun,
|
|
261
|
+
}, async (span) => {
|
|
262
|
+
// Inside the span so auth/setup failures are captured by the root span.
|
|
263
|
+
const { app, readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
268
264
|
try {
|
|
269
|
-
|
|
265
|
+
// Clone repository
|
|
266
|
+
await cloneRepository({
|
|
267
|
+
app,
|
|
268
|
+
owner: ctx.owner,
|
|
269
|
+
repo: ctx.repo,
|
|
270
|
+
prNumber: ctx.prNumber,
|
|
271
|
+
installationId: ctx.installationId,
|
|
272
|
+
});
|
|
273
|
+
// Gather thread context from GitHub API
|
|
274
|
+
let threadContext;
|
|
270
275
|
if (ctx.commentType === 'review') {
|
|
271
|
-
await
|
|
272
|
-
owner: ctx.owner,
|
|
273
|
-
repo: ctx.repo,
|
|
274
|
-
pull_number: ctx.prNumber,
|
|
275
|
-
comment_id: ctx.commentId,
|
|
276
|
-
body: errorMessage,
|
|
277
|
-
});
|
|
276
|
+
threadContext = await gatherReviewCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.commentId);
|
|
278
277
|
}
|
|
279
278
|
else {
|
|
280
|
-
await
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
threadContext = await gatherIssueCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.prNumber, ctx.commentId);
|
|
280
|
+
}
|
|
281
|
+
// Build prompt and invoke the chat model
|
|
282
|
+
const promptTemplate = loadPromptTemplateFile({
|
|
283
|
+
fileName: CHAT_PROMPT_FILE,
|
|
284
|
+
promptLabel: 'chat',
|
|
285
|
+
});
|
|
286
|
+
const prompt = buildPrompt({
|
|
287
|
+
promptTemplate,
|
|
288
|
+
owner: ctx.owner,
|
|
289
|
+
repo: ctx.repo,
|
|
290
|
+
prNumber: ctx.prNumber,
|
|
291
|
+
threadHistory: threadContext.threadHistory,
|
|
292
|
+
userQuestion: threadContext.userQuestion,
|
|
293
|
+
codeSnippet: threadContext.codeSnippet,
|
|
294
|
+
prTitle: threadContext.prTitle,
|
|
295
|
+
prAuthor: threadContext.prAuthor,
|
|
296
|
+
});
|
|
297
|
+
const modelResult = await invokeChatModel(prompt);
|
|
298
|
+
if (!modelResult.ok) {
|
|
299
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: modelResult.reason });
|
|
300
|
+
logger.warn('Chat model produced no usable response', {
|
|
301
|
+
reason: modelResult.reason,
|
|
285
302
|
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const response = modelResult.output;
|
|
306
|
+
if (ctx.dryRun) {
|
|
307
|
+
logger.info('Dry run enabled, printing chat response instead of posting to GitHub');
|
|
308
|
+
process.stdout.write(`${formatChatDryRunOutput(ctx, response)}\n`);
|
|
286
309
|
}
|
|
310
|
+
else {
|
|
311
|
+
await postResponse(writeOctokit, ctx, response);
|
|
312
|
+
}
|
|
313
|
+
metricOutcome = 'success';
|
|
287
314
|
}
|
|
288
|
-
catch (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
315
|
+
catch (error) {
|
|
316
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
317
|
+
logger.error('Chat task failed', { error: message });
|
|
318
|
+
// The catch swallows the error to post a user reply, so set the
|
|
319
|
+
// span status explicitly — withPhase only auto-errors on rethrow.
|
|
320
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
321
|
+
if (ctx.dryRun) {
|
|
322
|
+
logger.info('Dry run enabled, skipping chat error response post');
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
try {
|
|
326
|
+
const errorMessage = 'Sorry, I encountered an error while processing your request. Please try again or contact the team maintaining the bot.';
|
|
327
|
+
if (ctx.commentType === 'review') {
|
|
328
|
+
await writeOctokit.rest.pulls.createReplyForReviewComment({
|
|
329
|
+
owner: ctx.owner,
|
|
330
|
+
repo: ctx.repo,
|
|
331
|
+
pull_number: ctx.prNumber,
|
|
332
|
+
comment_id: ctx.commentId,
|
|
333
|
+
body: errorMessage,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
await writeOctokit.rest.issues.createComment({
|
|
338
|
+
owner: ctx.owner,
|
|
339
|
+
repo: ctx.repo,
|
|
340
|
+
issue_number: ctx.prNumber,
|
|
341
|
+
body: errorMessage,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (replyError) {
|
|
346
|
+
logger.error('Failed to post error message', {
|
|
347
|
+
error: replyError instanceof Error
|
|
348
|
+
? replyError.message
|
|
349
|
+
: String(replyError),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
exitCode = 1;
|
|
292
354
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
await
|
|
355
|
+
finally {
|
|
356
|
+
// Local dry runs must stay side-effect free.
|
|
357
|
+
if (reactionId && !ctx.dryRun) {
|
|
358
|
+
await removeReaction(writeOctokit, ctx, reactionId);
|
|
359
|
+
}
|
|
360
|
+
await recordTaskMetrics({
|
|
361
|
+
type: 'chat',
|
|
362
|
+
repository: `${ctx.owner}/${ctx.repo}`,
|
|
363
|
+
model: 'zai',
|
|
364
|
+
outcome: metricOutcome,
|
|
365
|
+
durationMs: performance.now() - startedAt,
|
|
366
|
+
});
|
|
299
367
|
}
|
|
300
|
-
|
|
301
|
-
type: 'chat',
|
|
302
|
-
repository: `${ctx.owner}/${ctx.repo}`,
|
|
303
|
-
model: 'google',
|
|
304
|
-
outcome: metricOutcome,
|
|
305
|
-
durationMs: performance.now() - startedAt,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
368
|
+
});
|
|
308
369
|
if (exitCode !== 0) {
|
|
309
370
|
process.exit(exitCode);
|
|
310
371
|
}
|