@doist/doistbot-cli 1.0.5 → 1.0.7
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 +78 -24
- package/dist/actions/doctor.js +14 -7
- package/dist/actions/review.js +67 -10
- package/dist/auth.js +51 -6
- package/dist/config.js +77 -5
- package/dist/env.js +1 -0
- package/dist/runtime.js +1 -1
- package/dist/terminal.js +2 -0
- package/package.json +4 -4
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +8 -20
- package/sandbox/dist/core/check-run.js +16 -0
- package/sandbox/dist/core/datadog-metrics.js +6 -52
- package/sandbox/dist/core/logger-span-processor.js +78 -0
- package/sandbox/dist/core/pi.js +285 -174
- package/sandbox/dist/core/prompt-template.js +45 -3
- package/sandbox/dist/core/repository-map.js +24 -0
- package/sandbox/dist/core/tracing-setup.js +15 -0
- package/sandbox/dist/core/tracing.js +80 -0
- package/sandbox/dist/main.js +4 -1
- package/sandbox/dist/providers/credentials.js +12 -2
- package/sandbox/dist/providers/gemini.js +5 -1
- package/sandbox/dist/providers/helpers.js +63 -17
- package/sandbox/dist/providers/index.js +4 -0
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +19 -2
- package/sandbox/dist/tasks/chat/chat.js +173 -102
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
- package/sandbox/dist/tasks/issue-summarize/model.js +120 -49
- package/sandbox/dist/tasks/issue-summarize/prompt.js +2 -2
- package/sandbox/dist/tasks/issue-summarize/summarize.js +104 -88
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +25 -1
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +33 -10
- 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/hero-group-map.js +2 -0
- 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 +5 -4
- package/sandbox/dist/tasks/issue-triage/prompt.js +2 -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 +290 -420
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +3 -3
- package/sandbox/dist/tasks/review/check-run.js +2 -1
- package/sandbox/dist/tasks/review/conversation-context.js +2 -1
- package/sandbox/dist/tasks/review/engines/dedupe.js +110 -44
- package/sandbox/dist/tasks/review/engines/multi-focus.js +254 -113
- package/sandbox/dist/tasks/review/engines/shared.js +43 -6
- package/sandbox/dist/tasks/review/github-comment-format.js +72 -10
- package/sandbox/dist/tasks/review/local.js +7 -4
- package/sandbox/dist/tasks/review/review-summary.js +48 -7
- package/sandbox/dist/tasks/review/review.js +267 -186
- package/sandbox/dist/tasks/review/runner.js +33 -7
- package/sandbox/dist/tasks/review/summary-model.js +189 -20
- package/sandbox/dist/tasks/review/thinking-level.js +4 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +8 -20
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +1 -0
|
@@ -1,14 +1,18 @@
|
|
|
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';
|
|
5
6
|
import { invokePiPrompt } from '../../core/pi.js';
|
|
6
|
-
import { loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
7
|
+
import { loadPromptTemplateFile, resolvePromptFile } 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
|
/**
|
|
@@ -122,26 +127,57 @@ async function gatherIssueCommentContext(octokit, owner, repo, prNumber, comment
|
|
|
122
127
|
export async function invokeChatModel(prompt) {
|
|
123
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
|
-
|
|
140
|
-
|
|
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,
|
|
141
150
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
// Record why the model stopped, before the ok/failure branch so
|
|
152
|
+
// terminal "error"/"aborted"/"length" endings are reported too.
|
|
153
|
+
// stopReason values:
|
|
154
|
+
// stop Natural end — model finished or hit a stop sequence.
|
|
155
|
+
// length Hit the token cap (max_tokens or context limit).
|
|
156
|
+
// toolUse Paused to call a tool. Expected mid-agent-loop.
|
|
157
|
+
// error Provider errored mid-generation.
|
|
158
|
+
// aborted Generation was cancelled (e.g. signal/timeout).
|
|
159
|
+
if (result.stopReason) {
|
|
160
|
+
span.setAttribute('gen_ai.response.finish_reasons', [result.stopReason]);
|
|
161
|
+
}
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
// Failure returns a result rather than throwing — mark the span
|
|
164
|
+
// ERROR explicitly so it survives to phase.end (withPhase only
|
|
165
|
+
// auto-errors on a throw).
|
|
166
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error });
|
|
167
|
+
logger.warn('Chat model request failed', {
|
|
168
|
+
error: result.error,
|
|
169
|
+
timedOut: result.timedOut,
|
|
170
|
+
});
|
|
171
|
+
return { ok: false, reason: result.error };
|
|
172
|
+
}
|
|
173
|
+
const output = result.output.trim();
|
|
174
|
+
if (!output) {
|
|
175
|
+
const reason = 'chat model produced no output';
|
|
176
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
|
|
177
|
+
return { ok: false, reason };
|
|
178
|
+
}
|
|
179
|
+
return { ok: true, output };
|
|
180
|
+
});
|
|
145
181
|
}
|
|
146
182
|
/**
|
|
147
183
|
* Posts the response to the correct location.
|
|
@@ -170,6 +206,17 @@ async function postResponse(octokit, ctx, response) {
|
|
|
170
206
|
prNumber: ctx.prNumber,
|
|
171
207
|
});
|
|
172
208
|
}
|
|
209
|
+
function formatChatDryRunOutput(ctx, response) {
|
|
210
|
+
return JSON.stringify({
|
|
211
|
+
task: 'chat',
|
|
212
|
+
repository: `${ctx.owner}/${ctx.repo}`,
|
|
213
|
+
prNumber: ctx.prNumber,
|
|
214
|
+
commentId: ctx.commentId,
|
|
215
|
+
commentType: ctx.commentType,
|
|
216
|
+
eventName: ctx.eventName,
|
|
217
|
+
response,
|
|
218
|
+
}, null, 2);
|
|
219
|
+
}
|
|
173
220
|
/**
|
|
174
221
|
* Removes the 👀 reaction from the triggering comment.
|
|
175
222
|
*/
|
|
@@ -210,102 +257,126 @@ async function main() {
|
|
|
210
257
|
commentId: ctx.commentId,
|
|
211
258
|
};
|
|
212
259
|
logger.setBaseContext(logContext);
|
|
213
|
-
logger.info('Chat task started', {
|
|
214
|
-
commentType: ctx.commentType,
|
|
215
|
-
eventName: ctx.eventName,
|
|
216
|
-
});
|
|
217
|
-
const { app, readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
218
260
|
const reactionId = ctx.reactionId;
|
|
219
261
|
let exitCode = 0;
|
|
220
262
|
let metricOutcome = 'failure';
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
threadContext = await gatherReviewCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.commentId);
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
threadContext = await gatherIssueCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.prNumber, ctx.commentId);
|
|
237
|
-
}
|
|
238
|
-
// Build prompt and invoke the chat model
|
|
239
|
-
const promptTemplate = loadPromptTemplateFile({
|
|
240
|
-
fileName: CHAT_PROMPT_FILE,
|
|
241
|
-
promptLabel: 'chat',
|
|
242
|
-
});
|
|
243
|
-
const prompt = buildPrompt({
|
|
244
|
-
promptTemplate,
|
|
245
|
-
owner: ctx.owner,
|
|
246
|
-
repo: ctx.repo,
|
|
247
|
-
prNumber: ctx.prNumber,
|
|
248
|
-
threadHistory: threadContext.threadHistory,
|
|
249
|
-
userQuestion: threadContext.userQuestion,
|
|
250
|
-
codeSnippet: threadContext.codeSnippet,
|
|
251
|
-
prTitle: threadContext.prTitle,
|
|
252
|
-
prAuthor: threadContext.prAuthor,
|
|
253
|
-
});
|
|
254
|
-
const response = await invokeChatModel(prompt);
|
|
255
|
-
if (!response) {
|
|
256
|
-
logger.warn('Chat model request returned no output');
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
// Post response
|
|
260
|
-
await postResponse(writeOctokit, ctx, response);
|
|
261
|
-
logger.info('Chat task completed');
|
|
262
|
-
metricOutcome = 'success';
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
logger.error('Chat task failed', {
|
|
266
|
-
error: error instanceof Error ? error.message : String(error),
|
|
267
|
-
});
|
|
268
|
-
// Post error message to the PR
|
|
263
|
+
await withPhase('chat', {
|
|
264
|
+
task: 'chat',
|
|
265
|
+
'gen_ai.operation.name': 'invoke_agent',
|
|
266
|
+
'gen_ai.agent.name': 'doistbot.chat',
|
|
267
|
+
'pr.number': ctx.prNumber,
|
|
268
|
+
'comment.id': ctx.commentId,
|
|
269
|
+
'comment.type': ctx.commentType,
|
|
270
|
+
'event.name': ctx.eventName,
|
|
271
|
+
dry_run: ctx.dryRun,
|
|
272
|
+
}, async (span) => {
|
|
273
|
+
// Inside the span so auth/setup failures are captured by the root span.
|
|
274
|
+
const { app, readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
269
275
|
try {
|
|
270
|
-
|
|
276
|
+
// Clone repository
|
|
277
|
+
await cloneRepository({
|
|
278
|
+
app,
|
|
279
|
+
owner: ctx.owner,
|
|
280
|
+
repo: ctx.repo,
|
|
281
|
+
prNumber: ctx.prNumber,
|
|
282
|
+
installationId: ctx.installationId,
|
|
283
|
+
});
|
|
284
|
+
// Gather thread context from GitHub API
|
|
285
|
+
let threadContext;
|
|
271
286
|
if (ctx.commentType === 'review') {
|
|
272
|
-
await
|
|
273
|
-
owner: ctx.owner,
|
|
274
|
-
repo: ctx.repo,
|
|
275
|
-
pull_number: ctx.prNumber,
|
|
276
|
-
comment_id: ctx.commentId,
|
|
277
|
-
body: errorMessage,
|
|
278
|
-
});
|
|
287
|
+
threadContext = await gatherReviewCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.commentId);
|
|
279
288
|
}
|
|
280
289
|
else {
|
|
281
|
-
await
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
290
|
+
threadContext = await gatherIssueCommentContext(readOctokit, ctx.owner, ctx.repo, ctx.prNumber, ctx.commentId);
|
|
291
|
+
}
|
|
292
|
+
// Build prompt and invoke the chat model
|
|
293
|
+
const promptTemplate = loadPromptTemplateFile({
|
|
294
|
+
fileName: resolvePromptFile(import.meta.url, CHAT_PROMPT_FILE),
|
|
295
|
+
promptLabel: 'chat',
|
|
296
|
+
});
|
|
297
|
+
const prompt = buildPrompt({
|
|
298
|
+
promptTemplate,
|
|
299
|
+
owner: ctx.owner,
|
|
300
|
+
repo: ctx.repo,
|
|
301
|
+
prNumber: ctx.prNumber,
|
|
302
|
+
threadHistory: threadContext.threadHistory,
|
|
303
|
+
userQuestion: threadContext.userQuestion,
|
|
304
|
+
codeSnippet: threadContext.codeSnippet,
|
|
305
|
+
prTitle: threadContext.prTitle,
|
|
306
|
+
prAuthor: threadContext.prAuthor,
|
|
307
|
+
});
|
|
308
|
+
const modelResult = await invokeChatModel(prompt);
|
|
309
|
+
if (!modelResult.ok) {
|
|
310
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: modelResult.reason });
|
|
311
|
+
logger.warn('Chat model produced no usable response', {
|
|
312
|
+
reason: modelResult.reason,
|
|
286
313
|
});
|
|
314
|
+
return;
|
|
287
315
|
}
|
|
316
|
+
const response = modelResult.output;
|
|
317
|
+
if (ctx.dryRun) {
|
|
318
|
+
logger.info('Dry run enabled, printing chat response instead of posting to GitHub');
|
|
319
|
+
process.stdout.write(`${formatChatDryRunOutput(ctx, response)}\n`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
await postResponse(writeOctokit, ctx, response);
|
|
323
|
+
}
|
|
324
|
+
metricOutcome = 'success';
|
|
288
325
|
}
|
|
289
|
-
catch (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
326
|
+
catch (error) {
|
|
327
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
328
|
+
logger.error('Chat task failed', { error: message });
|
|
329
|
+
// The catch swallows the error to post a user reply, so set the
|
|
330
|
+
// span status explicitly — withPhase only auto-errors on rethrow.
|
|
331
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
332
|
+
if (ctx.dryRun) {
|
|
333
|
+
logger.info('Dry run enabled, skipping chat error response post');
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
try {
|
|
337
|
+
const errorMessage = 'Sorry, I encountered an error while processing your request. Please try again or contact the team maintaining the bot.';
|
|
338
|
+
if (ctx.commentType === 'review') {
|
|
339
|
+
await writeOctokit.rest.pulls.createReplyForReviewComment({
|
|
340
|
+
owner: ctx.owner,
|
|
341
|
+
repo: ctx.repo,
|
|
342
|
+
pull_number: ctx.prNumber,
|
|
343
|
+
comment_id: ctx.commentId,
|
|
344
|
+
body: errorMessage,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
await writeOctokit.rest.issues.createComment({
|
|
349
|
+
owner: ctx.owner,
|
|
350
|
+
repo: ctx.repo,
|
|
351
|
+
issue_number: ctx.prNumber,
|
|
352
|
+
body: errorMessage,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (replyError) {
|
|
357
|
+
logger.error('Failed to post error message', {
|
|
358
|
+
error: replyError instanceof Error
|
|
359
|
+
? replyError.message
|
|
360
|
+
: String(replyError),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
exitCode = 1;
|
|
293
365
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
await
|
|
366
|
+
finally {
|
|
367
|
+
// Local dry runs must stay side-effect free.
|
|
368
|
+
if (reactionId && !ctx.dryRun) {
|
|
369
|
+
await removeReaction(writeOctokit, ctx, reactionId);
|
|
370
|
+
}
|
|
371
|
+
await recordTaskMetrics({
|
|
372
|
+
type: 'chat',
|
|
373
|
+
repository: `${ctx.owner}/${ctx.repo}`,
|
|
374
|
+
model: 'zai',
|
|
375
|
+
outcome: metricOutcome,
|
|
376
|
+
durationMs: performance.now() - startedAt,
|
|
377
|
+
});
|
|
300
378
|
}
|
|
301
|
-
|
|
302
|
-
type: 'chat',
|
|
303
|
-
repository: `${ctx.owner}/${ctx.repo}`,
|
|
304
|
-
model: 'google',
|
|
305
|
-
outcome: metricOutcome,
|
|
306
|
-
durationMs: performance.now() - startedAt,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
379
|
+
});
|
|
309
380
|
if (exitCode !== 0) {
|
|
310
381
|
process.exit(exitCode);
|
|
311
382
|
}
|