@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.
Files changed (82) hide show
  1. package/dist/actions/auth.js +62 -28
  2. package/dist/actions/doctor.js +16 -11
  3. package/dist/actions/review.js +77 -11
  4. package/dist/auth.js +53 -8
  5. package/dist/config.js +40 -6
  6. package/dist/env.js +1 -1
  7. package/dist/file-store.js +1 -1
  8. package/dist/runtime.js +1 -1
  9. package/dist/terminal.js +2 -7
  10. package/package.json +7 -7
  11. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
  12. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +9 -85
  13. package/sandbox/dist/core/bot-logins.js +0 -13
  14. package/sandbox/dist/core/check-run.js +16 -0
  15. package/sandbox/dist/core/datadog-metrics.js +6 -54
  16. package/sandbox/dist/core/logger-span-processor.js +78 -0
  17. package/sandbox/dist/core/pi.js +270 -186
  18. package/sandbox/dist/core/tracing-setup.js +15 -0
  19. package/sandbox/dist/core/tracing.js +71 -0
  20. package/sandbox/dist/main.js +4 -1
  21. package/sandbox/dist/providers/config.js +1 -1
  22. package/sandbox/dist/providers/credentials.js +40 -0
  23. package/sandbox/dist/providers/gemini.js +8 -10
  24. package/sandbox/dist/providers/helpers.js +18 -38
  25. package/sandbox/dist/providers/index.js +4 -3
  26. package/sandbox/dist/providers/openai.js +1 -8
  27. package/sandbox/dist/providers/openrouter-pi.js +25 -0
  28. package/sandbox/dist/providers/pi-review.js +28 -55
  29. package/sandbox/dist/providers/schemas.js +0 -39
  30. package/sandbox/dist/tasks/chat/chat.js +162 -101
  31. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
  32. package/sandbox/dist/tasks/issue-summarize/context.js +1 -1
  33. package/sandbox/dist/tasks/issue-summarize/model.js +4 -2
  34. package/sandbox/dist/tasks/issue-summarize/summarize.js +1 -1
  35. package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
  36. package/sandbox/dist/tasks/issue-triage/context.js +25 -2
  37. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +31 -8
  38. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +154 -101
  39. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +15 -6
  40. package/sandbox/dist/tasks/issue-triage/model.js +120 -36
  41. package/sandbox/dist/tasks/issue-triage/output.js +27 -0
  42. package/sandbox/dist/tasks/issue-triage/pr-creator.js +3 -2
  43. package/sandbox/dist/tasks/issue-triage/resolution.js +26 -0
  44. package/sandbox/dist/tasks/issue-triage/routing.js +9 -9
  45. package/sandbox/dist/tasks/issue-triage/side-effects.js +9 -0
  46. package/sandbox/dist/tasks/issue-triage/triage.js +288 -418
  47. package/sandbox/dist/tasks/persist-logs/dd-proxy.js +2 -2
  48. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +1 -1
  49. package/sandbox/dist/tasks/persist-logs/persist-logs.js +1 -1
  50. package/sandbox/dist/tasks/review/check-run.js +2 -1
  51. package/sandbox/dist/tasks/review/conversation-context.js +15 -8
  52. package/sandbox/dist/tasks/review/engines/dedupe.js +19 -9
  53. package/sandbox/dist/tasks/review/engines/multi-focus.js +62 -36
  54. package/sandbox/dist/tasks/review/engines/shared.js +15 -61
  55. package/sandbox/dist/tasks/review/github-comment-format.js +64 -10
  56. package/sandbox/dist/tasks/review/local.js +2 -29
  57. package/sandbox/dist/tasks/review/prompt.js +2 -49
  58. package/sandbox/dist/tasks/review/review-summary.js +120 -0
  59. package/sandbox/dist/tasks/review/review.js +49 -69
  60. package/sandbox/dist/tasks/review/runner.js +57 -52
  61. package/sandbox/dist/tasks/review/summary-model.js +172 -16
  62. package/sandbox/dist/tasks/review/thinking-level.js +4 -2
  63. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
  64. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +9 -85
  65. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  66. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  67. package/sandbox/dist/providers/anthropic.js +0 -30
  68. package/sandbox/dist/tasks/review/author-format.js +0 -14
  69. package/sandbox/dist/tasks/review/author-profile.js +0 -67
  70. package/sandbox/dist/tasks/review/council/normalizer.js +0 -104
  71. package/sandbox/dist/tasks/review/council/orchestrator.js +0 -194
  72. package/sandbox/dist/tasks/review/council/provider-health.js +0 -31
  73. package/sandbox/dist/tasks/review/council/summary.js +0 -192
  74. package/sandbox/dist/tasks/review/council/types.js +0 -4
  75. package/sandbox/dist/tasks/review/council/voter.js +0 -384
  76. package/sandbox/dist/tasks/review/engines/council.js +0 -126
  77. package/sandbox/dist/tasks/review/engines/index.js +0 -12
  78. package/sandbox/dist/tasks/review/review-engine.js +0 -5
  79. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  80. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  81. package/sandbox/review-prompt.md +0 -209
  82. 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.GEMINI_API_KEY?.trim();
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 null;
133
+ return { ok: false, reason: 'OPENROUTER_API_KEY missing' };
127
134
  }
128
- const result = await invokePiPrompt({
129
- provider: 'gemini',
130
- apiKey,
131
- prompt,
132
- cwd: WORKSPACE_DIR,
133
- timeoutMs: CHAT_TIMEOUT_MS,
134
- capabilityPreset: 'workspace-inspection',
135
- });
136
- if (!result.ok) {
137
- logger.warn('Chat model request failed', {
138
- error: result.error,
139
- timedOut: result.timedOut,
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
- return null;
142
- }
143
- return result.output.trim() || null;
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
- try {
221
- // Clone repository
222
- await cloneRepository({
223
- app,
224
- owner: ctx.owner,
225
- repo: ctx.repo,
226
- prNumber: ctx.prNumber,
227
- installationId: ctx.installationId,
228
- });
229
- // Gather thread context from GitHub API
230
- let threadContext;
231
- if (ctx.commentType === 'review') {
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
- const errorMessage = 'Sorry, I encountered an error while processing your request. Please try again or contact the team maintaining the bot.';
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 writeOctokit.rest.pulls.createReplyForReviewComment({
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 writeOctokit.rest.issues.createComment({
281
- owner: ctx.owner,
282
- repo: ctx.repo,
283
- issue_number: ctx.prNumber,
284
- body: errorMessage,
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 (replyError) {
289
- logger.error('Failed to post error message', {
290
- error: replyError instanceof Error ? replyError.message : String(replyError),
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
- exitCode = 1;
294
- }
295
- finally {
296
- // Always remove reaction
297
- if (reactionId) {
298
- await removeReaction(writeOctokit, ctx, reactionId);
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
- await recordTaskMetrics({
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
  }