@doist/doistbot-cli 1.0.7 → 1.0.8
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 +14 -2
- package/dist/actions/doctor.js +3 -1
- package/dist/actions/review.js +9 -4
- package/dist/auth.js +23 -6
- package/dist/config.js +9 -6
- package/package.json +3 -3
- package/sandbox/dist/core/pi.js +1 -1
- package/sandbox/dist/tasks/chat/chat.js +7 -6
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +1 -0
package/dist/actions/auth.js
CHANGED
|
@@ -70,13 +70,16 @@ export async function runAuthSetup(dependencies = {}) {
|
|
|
70
70
|
const status = await deps.getAuthStatus();
|
|
71
71
|
deps.writeLine('1. OpenAI Codex');
|
|
72
72
|
deps.writeLine();
|
|
73
|
-
if (status.openai.configured) {
|
|
73
|
+
if (status.openai.configured && !status.openai.refreshFailed) {
|
|
74
74
|
deps.writeLine('✓ OpenAI already configured (pi-auth).');
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
77
|
if (!deps.isInteractive) {
|
|
78
78
|
throw new Error('Cannot configure OpenAI Codex auth in non-interactive mode.');
|
|
79
79
|
}
|
|
80
|
+
if (status.openai.refreshFailed) {
|
|
81
|
+
deps.writeLine('OpenAI session could not be refreshed (expired, revoked, or offline). Reconnecting.');
|
|
82
|
+
}
|
|
80
83
|
await deps.loginOpenAICodex();
|
|
81
84
|
deps.writeLine();
|
|
82
85
|
deps.writeLine('✓ OpenAI configured.');
|
|
@@ -149,10 +152,19 @@ export async function runAuthSetup(dependencies = {}) {
|
|
|
149
152
|
}
|
|
150
153
|
export async function runAuthStatus() {
|
|
151
154
|
const status = await getAuthStatus();
|
|
152
|
-
writeLine(`OpenAI: ${
|
|
155
|
+
writeLine(`OpenAI: ${formatOpenAIStatus(status.openai)}`);
|
|
153
156
|
writeLine(`Gemini ${formatOptionalLabel()}: ${status.gemini.configured ? `configured (${status.gemini.source ?? 'unknown'})` : 'not configured'}`);
|
|
154
157
|
writeLine(`OpenRouter ${formatOptionalLabel()}: ${status.openrouter.configured ? `configured (${status.openrouter.source ?? 'unknown'})` : 'not configured'}`);
|
|
155
158
|
}
|
|
159
|
+
function formatOpenAIStatus(status) {
|
|
160
|
+
if (!status.configured) {
|
|
161
|
+
return 'not configured';
|
|
162
|
+
}
|
|
163
|
+
const base = `configured (${status.source ?? 'unknown'})`;
|
|
164
|
+
return status.refreshFailed
|
|
165
|
+
? `${base} — session could not be refreshed; check your connection or run \`doistbot auth login\``
|
|
166
|
+
: base;
|
|
167
|
+
}
|
|
156
168
|
export async function runAuthLogout(target, dependencies = {}) {
|
|
157
169
|
const deps = {
|
|
158
170
|
deleteGeminiApiKey,
|
package/dist/actions/doctor.js
CHANGED
|
@@ -4,7 +4,9 @@ import { checkGit, checkPiDependency, checkRepository, checkSandboxBuild } from
|
|
|
4
4
|
import { renderDiagnosticCheck } from '../terminal.js';
|
|
5
5
|
export function getConfiguredProviders({ status, }) {
|
|
6
6
|
return [
|
|
7
|
-
status.openai.configured
|
|
7
|
+
status.openai.configured && !status.openai.refreshFailed
|
|
8
|
+
? `OpenAI (${status.openai.source ?? 'configured'})`
|
|
9
|
+
: undefined,
|
|
8
10
|
status.gemini.configured
|
|
9
11
|
? `Gemini optional (${status.gemini.source ?? 'configured'})`
|
|
10
12
|
: undefined,
|
package/dist/actions/review.js
CHANGED
|
@@ -21,10 +21,14 @@ function getReadableProviderFailure(failure) {
|
|
|
21
21
|
if (failure.model === 'openai' &&
|
|
22
22
|
(normalized.includes('no api key found for openai') ||
|
|
23
23
|
normalized.includes('no api key found for openai-codex') ||
|
|
24
|
+
normalized.includes('no api key for provider') ||
|
|
24
25
|
normalized.includes('use /login') ||
|
|
25
26
|
normalized.includes('invalid api key') ||
|
|
26
|
-
normalized.includes('incorrect api key')
|
|
27
|
-
|
|
27
|
+
normalized.includes('incorrect api key') ||
|
|
28
|
+
normalized.includes('refresh_token_invalidated') ||
|
|
29
|
+
normalized.includes('unauthorized') ||
|
|
30
|
+
/\b401\b/.test(normalized))) {
|
|
31
|
+
return 'OpenAI Codex is not authenticated. Run `doistbot auth login` to reconnect OpenAI.';
|
|
28
32
|
}
|
|
29
33
|
if (failure.model === 'gemini' &&
|
|
30
34
|
(normalized.includes('429') ||
|
|
@@ -40,7 +44,8 @@ function getReadableProviderFailure(failure) {
|
|
|
40
44
|
normalized.includes('invalid_argument') ||
|
|
41
45
|
normalized.includes('invalid api key') ||
|
|
42
46
|
normalized.includes('no api key found for google') ||
|
|
43
|
-
normalized.includes('no api key found for gemini')
|
|
47
|
+
normalized.includes('no api key found for gemini') ||
|
|
48
|
+
normalized.includes('no api key for provider'))) {
|
|
44
49
|
return 'Gemini API key is invalid or missing. Run `doistbot auth` and paste a valid Gemini key.';
|
|
45
50
|
}
|
|
46
51
|
const firstLine = error
|
|
@@ -205,7 +210,7 @@ function getMissingAuthProviderFailures(missingProviders) {
|
|
|
205
210
|
if (missingProviders.includes('OpenAI')) {
|
|
206
211
|
failures.push({
|
|
207
212
|
model: 'openai',
|
|
208
|
-
message: 'OpenAI
|
|
213
|
+
message: 'OpenAI authentication unavailable; run `doistbot auth login`',
|
|
209
214
|
});
|
|
210
215
|
}
|
|
211
216
|
return failures;
|
package/dist/auth.js
CHANGED
|
@@ -28,9 +28,22 @@ export function getBrowserOpenCommand(platform, url) {
|
|
|
28
28
|
}
|
|
29
29
|
return { command: 'xdg-open', args: [url] };
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Check that the stored OAuth session is usable, not just present.
|
|
33
|
+
* getApiKey returns the cached access token when unexpired; when expired it
|
|
34
|
+
* attempts one cross-process-locked refresh, persists the result, and returns
|
|
35
|
+
* undefined when the refresh fails (revoked session, no network) while
|
|
36
|
+
* preserving the stored credentials. Unexpected storage errors propagate.
|
|
37
|
+
*/
|
|
38
|
+
export async function getOpenAICodexAuthState(storage) {
|
|
32
39
|
const resolvedStorage = storage ?? (await createPiAuthStorage());
|
|
33
|
-
|
|
40
|
+
if (resolvedStorage.get(OPENAI_CODEX_PROVIDER_ID)?.type !== 'oauth') {
|
|
41
|
+
return 'missing';
|
|
42
|
+
}
|
|
43
|
+
const apiKey = await resolvedStorage.getApiKey(OPENAI_CODEX_PROVIDER_ID, {
|
|
44
|
+
includeFallback: false,
|
|
45
|
+
});
|
|
46
|
+
return apiKey ? 'ready' : 'refresh-failed';
|
|
34
47
|
}
|
|
35
48
|
export async function loginOpenAICodex(storage) {
|
|
36
49
|
const resolvedStorage = storage ?? (await createPiAuthStorage());
|
|
@@ -105,11 +118,15 @@ export async function getAuthStatus({ env = process.env, storage, geminiStore =
|
|
|
105
118
|
const openRouterStored = envApiKeys.openrouter
|
|
106
119
|
? { value: undefined }
|
|
107
120
|
: await readStoredOpenRouterApiKey(openRouterStore).catch(() => ({ value: undefined }));
|
|
108
|
-
const
|
|
121
|
+
const openAICodexAuthState = await getOpenAICodexAuthState(storage);
|
|
109
122
|
return {
|
|
110
|
-
openai:
|
|
111
|
-
? { configured:
|
|
112
|
-
: {
|
|
123
|
+
openai: openAICodexAuthState === 'missing'
|
|
124
|
+
? { configured: false, source: undefined }
|
|
125
|
+
: {
|
|
126
|
+
configured: true,
|
|
127
|
+
source: 'pi-auth',
|
|
128
|
+
refreshFailed: openAICodexAuthState === 'refresh-failed',
|
|
129
|
+
},
|
|
113
130
|
gemini: envApiKeys.gemini
|
|
114
131
|
? { configured: true, source: 'env' }
|
|
115
132
|
: {
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getStoredGeminiApiKey, getStoredOpenRouterApiKey,
|
|
1
|
+
import { getOpenAICodexAuthState, getStoredGeminiApiKey, getStoredOpenRouterApiKey, PI_AUTH_API_KEY, } from './auth.js';
|
|
2
2
|
import { getEnvApiKeys } from './env.js';
|
|
3
3
|
const REQUIRED_CLI_AUTH_PROVIDERS = [{ provider: 'openai', label: 'OpenAI' }];
|
|
4
4
|
const ALL_REVIEW_FOCUSES = [
|
|
@@ -29,7 +29,7 @@ function createCliGeminiSummaryModel(credential) {
|
|
|
29
29
|
export async function getApiKeys(env = process.env, auth = {
|
|
30
30
|
getStoredGeminiApiKey,
|
|
31
31
|
getStoredOpenRouterApiKey,
|
|
32
|
-
|
|
32
|
+
getOpenAICodexAuthState,
|
|
33
33
|
}) {
|
|
34
34
|
const envApiKeys = getEnvApiKeys(env);
|
|
35
35
|
const storedGeminiApiKey = envApiKeys.gemini
|
|
@@ -38,7 +38,7 @@ export async function getApiKeys(env = process.env, auth = {
|
|
|
38
38
|
const storedOpenRouterApiKey = envApiKeys.openrouter
|
|
39
39
|
? undefined
|
|
40
40
|
: await auth.getStoredOpenRouterApiKey().catch(() => undefined);
|
|
41
|
-
const
|
|
41
|
+
const openAICodexAuthState = await auth.getOpenAICodexAuthState();
|
|
42
42
|
const openRouterApiKey = envApiKeys.openrouter || storedOpenRouterApiKey;
|
|
43
43
|
const apiKeys = {};
|
|
44
44
|
const geminiApiKey = envApiKeys.gemini || storedGeminiApiKey;
|
|
@@ -49,13 +49,16 @@ export async function getApiKeys(env = process.env, auth = {
|
|
|
49
49
|
apiKeys.deepseek = { apiKey: openRouterApiKey, piProviderId: 'openrouter' };
|
|
50
50
|
apiKeys.zai = { apiKey: openRouterApiKey, piProviderId: 'openrouter' };
|
|
51
51
|
}
|
|
52
|
-
if (
|
|
52
|
+
if (openAICodexAuthState === 'ready') {
|
|
53
53
|
apiKeys.openai = PI_AUTH_API_KEY;
|
|
54
54
|
}
|
|
55
55
|
return apiKeys;
|
|
56
56
|
}
|
|
57
57
|
export function getMissingRequiredCliAuthProviders(status) {
|
|
58
|
-
return REQUIRED_CLI_AUTH_PROVIDERS.filter(({ provider }) =>
|
|
58
|
+
return REQUIRED_CLI_AUTH_PROVIDERS.filter(({ provider }) => {
|
|
59
|
+
const providerStatus = status[provider];
|
|
60
|
+
return !providerStatus.configured || providerStatus.refreshFailed === true;
|
|
61
|
+
}).map(({ label }) => label);
|
|
59
62
|
}
|
|
60
63
|
export function getMissingRequiredCliReviewAuthProviders(apiKeys) {
|
|
61
64
|
return REQUIRED_CLI_AUTH_PROVIDERS.filter(({ provider }) => {
|
|
@@ -67,7 +70,7 @@ export function getMissingRequiredCliReviewAuthProviders(apiKeys) {
|
|
|
67
70
|
}).map(({ label }) => label);
|
|
68
71
|
}
|
|
69
72
|
export function formatMissingRequiredCliAuthProviders(missingProviders) {
|
|
70
|
-
return `${missingProviders.join(' and ')} not configured; run \`doistbot auth\``;
|
|
73
|
+
return `${missingProviders.join(' and ')} authentication unavailable (not configured, or the session could not be refreshed); run \`doistbot auth login\``;
|
|
71
74
|
}
|
|
72
75
|
export function getCliReviewRouting(apiKeys) {
|
|
73
76
|
const hasGemini = Boolean(apiKeys.gemini);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/doistbot-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Local self-review CLI for Doistbot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@inquirer/checkbox": "5.2.1",
|
|
44
44
|
"@inquirer/password": "5.1.1",
|
|
45
|
-
"@earendil-works/pi-ai": "0.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
45
|
+
"@earendil-works/pi-ai": "0.80.6",
|
|
46
|
+
"@earendil-works/pi-coding-agent": "0.80.6",
|
|
47
47
|
"@napi-rs/keyring": "1.3.0",
|
|
48
48
|
"ajv": "8.20.0",
|
|
49
49
|
"chalk": "4.1.2",
|
package/sandbox/dist/core/pi.js
CHANGED
|
@@ -13,7 +13,7 @@ export const DEFAULT_PI_MODELS = {
|
|
|
13
13
|
deepseek: 'deepseek-v4-pro',
|
|
14
14
|
gemini: 'gemini-3.1-pro-preview',
|
|
15
15
|
zai: 'glm5.2',
|
|
16
|
-
openai: 'gpt-5.
|
|
16
|
+
openai: 'gpt-5.6-terra',
|
|
17
17
|
};
|
|
18
18
|
export const DEFAULT_OPENROUTER_MODELS = {
|
|
19
19
|
deepseek: 'deepseek/deepseek-v4-pro',
|
|
@@ -11,8 +11,9 @@ import { withPhase } from '../../core/tracing.js';
|
|
|
11
11
|
import { buildPrompt } from './prompt.js';
|
|
12
12
|
const CHAT_PROMPT_FILE = 'chat-prompt.md';
|
|
13
13
|
const CHAT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
14
|
-
const CHAT_PROVIDER = '
|
|
15
|
-
const CHAT_MODEL = '
|
|
14
|
+
const CHAT_PROVIDER = 'openai';
|
|
15
|
+
const CHAT_MODEL = 'gpt-5.6-luna';
|
|
16
|
+
const CHAT_THINKING_LEVEL = 'high';
|
|
16
17
|
function parseContext() {
|
|
17
18
|
const base = parseBaseContext();
|
|
18
19
|
const reactionIdRaw = process.env.REACTION_ID;
|
|
@@ -125,12 +126,12 @@ async function gatherIssueCommentContext(octokit, owner, repo, prNumber, comment
|
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
export async function invokeChatModel(prompt) {
|
|
128
|
-
const apiKey = process.env.
|
|
129
|
+
const apiKey = process.env.OPENAI_KEY?.trim();
|
|
129
130
|
if (!apiKey) {
|
|
130
131
|
// Config error: no model call attempted, so no model span. The reason is
|
|
131
132
|
// returned so the caller's root span reports the real cause.
|
|
132
133
|
logger.warn('Chat model API key missing');
|
|
133
|
-
return { ok: false, reason: '
|
|
134
|
+
return { ok: false, reason: 'OPENAI_KEY missing' };
|
|
134
135
|
}
|
|
135
136
|
return withPhase('chat.model', {
|
|
136
137
|
'gen_ai.operation.name': 'chat',
|
|
@@ -140,11 +141,11 @@ export async function invokeChatModel(prompt) {
|
|
|
140
141
|
const result = await invokePiPrompt({
|
|
141
142
|
provider: CHAT_PROVIDER,
|
|
142
143
|
apiKey,
|
|
143
|
-
piProviderId: 'openrouter',
|
|
144
144
|
prompt,
|
|
145
145
|
cwd: WORKSPACE_DIR,
|
|
146
146
|
timeoutMs: CHAT_TIMEOUT_MS,
|
|
147
147
|
capabilityPreset: 'workspace-inspection',
|
|
148
|
+
thinkingLevel: CHAT_THINKING_LEVEL,
|
|
148
149
|
// Keep the executed model in sync with the recorded span attr.
|
|
149
150
|
modelName: CHAT_MODEL,
|
|
150
151
|
});
|
|
@@ -371,7 +372,7 @@ async function main() {
|
|
|
371
372
|
await recordTaskMetrics({
|
|
372
373
|
type: 'chat',
|
|
373
374
|
repository: `${ctx.owner}/${ctx.repo}`,
|
|
374
|
-
model: '
|
|
375
|
+
model: 'openai',
|
|
375
376
|
outcome: metricOutcome,
|
|
376
377
|
durationMs: performance.now() - startedAt,
|
|
377
378
|
});
|