fix: improve compaction queueing and oauth flows
This commit is contained in:
@@ -11,6 +11,9 @@
|
||||
- Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior.
|
||||
|
||||
### Fixes
|
||||
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
|
||||
- Auth: lock auth profile refreshes to avoid multi-instance OAuth logouts; keep credentials on refresh failure.
|
||||
- Onboarding: prompt immediately for OpenAI Codex redirect URL on remote/headless logins.
|
||||
- Typing indicators: stop typing once the reply dispatcher drains to prevent stuck typing across Discord/Telegram/WhatsApp.
|
||||
- WhatsApp/Telegram: add groupPolicy handling for group messages and normalize allowFrom matching (tg/telegram prefixes). Thanks @mneves75.
|
||||
- Auto-reply: add configurable ack reactions for inbound messages (default 👀 or `identity.emoji`) with scope controls. Thanks @obviyus for PR #178.
|
||||
|
||||
10
package.json
10
package.json
@@ -85,10 +85,10 @@
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@grammyjs/transformer-throttler": "^1.2.1",
|
||||
"@homebridge/ciao": "^1.3.4",
|
||||
"@mariozechner/pi-agent-core": "^0.36.0",
|
||||
"@mariozechner/pi-ai": "^0.36.0",
|
||||
"@mariozechner/pi-coding-agent": "^0.36.0",
|
||||
"@mariozechner/pi-tui": "^0.36.0",
|
||||
"@mariozechner/pi-agent-core": "^0.37.2",
|
||||
"@mariozechner/pi-ai": "^0.37.2",
|
||||
"@mariozechner/pi-coding-agent": "^0.37.2",
|
||||
"@mariozechner/pi-tui": "^0.37.2",
|
||||
"@sinclair/typebox": "0.34.46",
|
||||
"@slack/bolt": "^4.6.0",
|
||||
"@slack/web-api": "^7.13.0",
|
||||
@@ -110,6 +110,7 @@
|
||||
"json5": "^2.2.3",
|
||||
"long": "5.3.2",
|
||||
"playwright-core": "1.57.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"sharp": "^0.34.5",
|
||||
"tslog": "^4.10.2",
|
||||
@@ -126,6 +127,7 @@
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
|
||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -29,17 +29,17 @@ importers:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: ^0.36.0
|
||||
version: 0.36.0(ws@8.19.0)(zod@4.3.5)
|
||||
specifier: ^0.37.2
|
||||
version: 0.37.2(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: ^0.36.0
|
||||
version: 0.36.0(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
specifier: ^0.37.2
|
||||
version: 0.37.2(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-coding-agent':
|
||||
specifier: ^0.36.0
|
||||
version: 0.36.0(ws@8.19.0)(zod@4.3.5)
|
||||
specifier: ^0.37.2
|
||||
version: 0.37.2(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-tui':
|
||||
specifier: ^0.36.0
|
||||
version: 0.36.0
|
||||
specifier: ^0.37.2
|
||||
version: 0.37.2
|
||||
'@sinclair/typebox':
|
||||
specifier: 0.34.46
|
||||
version: 0.34.46
|
||||
@@ -103,6 +103,9 @@ importers:
|
||||
playwright-core:
|
||||
specifier: 1.57.0
|
||||
version: 1.57.0
|
||||
proper-lockfile:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
qrcode-terminal:
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0(patch_hash=ed82029850dbdf551f5df1de320945af52b8ea8500cc7bd4f39258e7a3d92e12)
|
||||
@@ -146,6 +149,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^25.0.3
|
||||
version: 25.0.3
|
||||
'@types/proper-lockfile':
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.4
|
||||
'@types/qrcode-terminal':
|
||||
specifier: ^0.12.2
|
||||
version: 0.12.2
|
||||
@@ -804,22 +810,22 @@ packages:
|
||||
peerDependencies:
|
||||
lit: ^3.3.1
|
||||
|
||||
'@mariozechner/pi-agent-core@0.36.0':
|
||||
resolution: {integrity: sha512-86BI1/j/MLxQHSWRXVLz8+NuSmDvLQebNb40+lFDI9XI9YBh8+r5fkYgU43u4j2TvANZ7iW6SFFnhWhzy8y6dg==}
|
||||
'@mariozechner/pi-agent-core@0.37.2':
|
||||
resolution: {integrity: sha512-GAN1lDVmlY1yH/FCfvpH29f2WBoqqMQkda7zKthOJO9l8tagxnlCWtq078CjzUGYlTDhKSf388XlOuDByBGYLA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@mariozechner/pi-ai@0.36.0':
|
||||
resolution: {integrity: sha512-xkzTgvdMzAZ/L/TgMH8z9Zi+aH0EWc54l5ygiafwvCgDk7xvfbylQG6pa9yn5zEn9T4NF9byJNk+nMHnycZvMQ==}
|
||||
'@mariozechner/pi-ai@0.37.2':
|
||||
resolution: {integrity: sha512-IhhvlPrgkdrlbS7QnV+qJPmlzKyae/aI1kenclG18/dXCypxUU50OuzGoVwrXvXw/RIHRwodhd7w4IH38Z7W4Q==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.36.0':
|
||||
resolution: {integrity: sha512-lKdpuGE0yVs/96GnDhrPLEEFhRteHRtnkfX04KIBpcsEXXg2vyAlpxtjtZ9nlhYqLLIY7qJRkeyjbhcFFfbAAA==}
|
||||
'@mariozechner/pi-coding-agent@0.37.2':
|
||||
resolution: {integrity: sha512-wRFqcyY76h4mONO1si2oAn9WVKnhmVV28dPHjQXVPrl7uSwMCLn+Fcde/nmbL29pYfiU1il4GmUR+iSyoxBUVQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-tui@0.36.0':
|
||||
resolution: {integrity: sha512-4n+nmTd36q0AVCbqWmjtTHTjIEwlGayKKhc+4QbpN9U3Z9jyQQa8Za1P2OHRmi6Jeu+ISuf4VBDvgmgCaxPZYg==}
|
||||
'@mariozechner/pi-tui@0.37.2':
|
||||
resolution: {integrity: sha512-XNV+jEeWJxQ8U3r5njRotVs6DnEIunkLHSA4nnF4OaRRgrcsafD8M4Pm/3RywSucclVK8P7+KoGiBB2Lokkmuw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@mistralai/mistralai@1.10.0':
|
||||
@@ -1272,6 +1278,9 @@ packages:
|
||||
'@types/node@25.0.3':
|
||||
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
|
||||
|
||||
'@types/proper-lockfile@4.1.4':
|
||||
resolution: {integrity: sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==}
|
||||
|
||||
'@types/qrcode-terminal@0.12.2':
|
||||
resolution: {integrity: sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==}
|
||||
|
||||
@@ -1284,6 +1293,9 @@ packages:
|
||||
'@types/retry@0.12.0':
|
||||
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
||||
|
||||
'@types/retry@0.12.5':
|
||||
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
|
||||
|
||||
'@types/send@1.2.1':
|
||||
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
||||
|
||||
@@ -3588,10 +3600,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- tailwindcss
|
||||
|
||||
'@mariozechner/pi-agent-core@0.36.0(ws@8.19.0)(zod@4.3.5)':
|
||||
'@mariozechner/pi-agent-core@0.37.2(ws@8.19.0)(zod@4.3.5)':
|
||||
dependencies:
|
||||
'@mariozechner/pi-ai': 0.36.0(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-tui': 0.36.0
|
||||
'@mariozechner/pi-ai': 0.37.2(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-tui': 0.37.2
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- bufferutil
|
||||
@@ -3600,7 +3612,7 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-ai@0.36.0(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)':
|
||||
'@mariozechner/pi-ai@0.37.2(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.71.2(zod@4.3.5)
|
||||
'@google/genai': 1.34.0
|
||||
@@ -3620,12 +3632,12 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.36.0(ws@8.19.0)(zod@4.3.5)':
|
||||
'@mariozechner/pi-coding-agent@0.37.2(ws@8.19.0)(zod@4.3.5)':
|
||||
dependencies:
|
||||
'@crosscopy/clipboard': 0.2.8
|
||||
'@mariozechner/pi-agent-core': 0.36.0(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-ai': 0.36.0(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-tui': 0.36.0
|
||||
'@mariozechner/pi-agent-core': 0.37.2(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-ai': 0.37.2(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||
'@mariozechner/pi-tui': 0.37.2
|
||||
chalk: 5.6.2
|
||||
cli-highlight: 2.1.11
|
||||
diff: 8.0.2
|
||||
@@ -3633,6 +3645,7 @@ snapshots:
|
||||
glob: 11.1.0
|
||||
jiti: 2.6.1
|
||||
marked: 15.0.12
|
||||
proper-lockfile: 4.1.2
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
@@ -3642,7 +3655,7 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-tui@0.36.0':
|
||||
'@mariozechner/pi-tui@0.37.2':
|
||||
dependencies:
|
||||
'@types/mime-types': 2.1.4
|
||||
chalk: 5.6.2
|
||||
@@ -4038,6 +4051,10 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/proper-lockfile@4.1.4':
|
||||
dependencies:
|
||||
'@types/retry': 0.12.5
|
||||
|
||||
'@types/qrcode-terminal@0.12.2': {}
|
||||
|
||||
'@types/qs@6.14.0': {}
|
||||
@@ -4046,6 +4063,8 @@ snapshots:
|
||||
|
||||
'@types/retry@0.12.0': {}
|
||||
|
||||
'@types/retry@0.12.5': {}
|
||||
|
||||
'@types/send@1.2.1':
|
||||
dependencies:
|
||||
'@types/node': 25.0.3
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type OAuthCredentials,
|
||||
type OAuthProvider,
|
||||
} from "@mariozechner/pi-ai";
|
||||
import lockfile from "proper-lockfile";
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveOAuthPath } from "../config/paths.js";
|
||||
@@ -68,6 +69,83 @@ function saveJsonFile(pathname: string, data: unknown) {
|
||||
fs.chmodSync(pathname, 0o600);
|
||||
}
|
||||
|
||||
function ensureAuthStoreFile(pathname: string) {
|
||||
if (fs.existsSync(pathname)) return;
|
||||
const payload: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {},
|
||||
};
|
||||
saveJsonFile(pathname, payload);
|
||||
}
|
||||
|
||||
function buildOAuthApiKey(
|
||||
provider: OAuthProvider,
|
||||
credentials: OAuthCredentials,
|
||||
): string {
|
||||
const needsProjectId =
|
||||
provider === "google-gemini-cli" || provider === "google-antigravity";
|
||||
return needsProjectId
|
||||
? JSON.stringify({
|
||||
token: credentials.access,
|
||||
projectId: credentials.projectId,
|
||||
})
|
||||
: credentials.access;
|
||||
}
|
||||
|
||||
async function refreshOAuthTokenWithLock(params: {
|
||||
profileId: string;
|
||||
provider: OAuthProvider;
|
||||
}): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {
|
||||
const authPath = resolveAuthStorePath();
|
||||
ensureAuthStoreFile(authPath);
|
||||
|
||||
let release: (() => Promise<void>) | undefined;
|
||||
try {
|
||||
release = await lockfile.lock(authPath, {
|
||||
retries: {
|
||||
retries: 10,
|
||||
factor: 2,
|
||||
minTimeout: 100,
|
||||
maxTimeout: 10_000,
|
||||
randomize: true,
|
||||
},
|
||||
stale: 30_000,
|
||||
});
|
||||
|
||||
const store = ensureAuthProfileStore();
|
||||
const cred = store.profiles[params.profileId];
|
||||
if (!cred || cred.type !== "oauth") return null;
|
||||
|
||||
if (Date.now() < cred.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(cred.provider, cred),
|
||||
newCredentials: cred,
|
||||
};
|
||||
}
|
||||
|
||||
const oauthCreds: Record<string, OAuthCredentials> = {
|
||||
[cred.provider]: cred,
|
||||
};
|
||||
const result = await getOAuthApiKey(cred.provider, oauthCreds);
|
||||
if (!result) return null;
|
||||
store.profiles[params.profileId] = {
|
||||
...cred,
|
||||
...result.newCredentials,
|
||||
type: "oauth",
|
||||
};
|
||||
saveAuthProfileStore(store);
|
||||
return result;
|
||||
} finally {
|
||||
if (release) {
|
||||
try {
|
||||
await release();
|
||||
} catch {
|
||||
// ignore unlock errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coerceLegacyStore(raw: unknown): LegacyAuthStore | null {
|
||||
if (!raw || typeof raw !== "object") return null;
|
||||
const record = raw as Record<string, unknown>;
|
||||
@@ -323,23 +401,41 @@ export async function resolveApiKeyForProfile(params: {
|
||||
if (cred.type === "api_key") {
|
||||
return { apiKey: cred.key, provider: cred.provider, email: cred.email };
|
||||
}
|
||||
if (Date.now() < cred.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(cred.provider, cred),
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
}
|
||||
|
||||
const oauthCreds: Record<string, OAuthCredentials> = {
|
||||
[cred.provider]: cred,
|
||||
};
|
||||
const result = await getOAuthApiKey(cred.provider, oauthCreds);
|
||||
if (!result) return null;
|
||||
store.profiles[profileId] = {
|
||||
...cred,
|
||||
...result.newCredentials,
|
||||
type: "oauth",
|
||||
};
|
||||
saveAuthProfileStore(store);
|
||||
return {
|
||||
apiKey: result.apiKey,
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
try {
|
||||
const result = await refreshOAuthTokenWithLock({
|
||||
profileId,
|
||||
provider: cred.provider,
|
||||
});
|
||||
if (!result) return null;
|
||||
return {
|
||||
apiKey: result.apiKey,
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
} catch (error) {
|
||||
const refreshedStore = ensureAuthProfileStore();
|
||||
const refreshed = refreshedStore.profiles[profileId];
|
||||
if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(refreshed.provider, refreshed),
|
||||
provider: refreshed.provider,
|
||||
email: refreshed.email ?? cred.email,
|
||||
};
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`OAuth token refresh failed for ${cred.provider}: ${message}. ` +
|
||||
"Please try again or re-authenticate.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function markAuthProfileGood(params: {
|
||||
|
||||
@@ -113,6 +113,7 @@ export type EmbeddedPiCompactResult = {
|
||||
type EmbeddedPiQueueHandle = {
|
||||
queueMessage: (text: string) => Promise<void>;
|
||||
isStreaming: () => boolean;
|
||||
isCompacting: () => boolean;
|
||||
abort: () => void;
|
||||
};
|
||||
|
||||
@@ -212,6 +213,7 @@ export function queueEmbeddedPiMessage(
|
||||
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
||||
if (!handle) return false;
|
||||
if (!handle.isStreaming()) return false;
|
||||
if (handle.isCompacting()) return false;
|
||||
void handle.queueMessage(text);
|
||||
return true;
|
||||
}
|
||||
@@ -810,21 +812,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
aborted = true;
|
||||
void session.abort();
|
||||
};
|
||||
const queueHandle: EmbeddedPiQueueHandle = {
|
||||
queueMessage: async (text: string) => {
|
||||
await session.steer(text);
|
||||
},
|
||||
isStreaming: () => session.isStreaming,
|
||||
abort: abortRun,
|
||||
};
|
||||
ACTIVE_EMBEDDED_RUNS.set(params.sessionId, queueHandle);
|
||||
|
||||
const {
|
||||
assistantTexts,
|
||||
toolMetas,
|
||||
unsubscribe,
|
||||
waitForCompactionRetry,
|
||||
} = subscribeEmbeddedPiSession({
|
||||
const subscription = subscribeEmbeddedPiSession({
|
||||
session,
|
||||
runId: params.runId,
|
||||
verboseLevel: params.verboseLevel,
|
||||
@@ -837,6 +825,22 @@ export async function runEmbeddedPiAgent(params: {
|
||||
onAgentEvent: params.onAgentEvent,
|
||||
enforceFinalTag: params.enforceFinalTag,
|
||||
});
|
||||
const {
|
||||
assistantTexts,
|
||||
toolMetas,
|
||||
unsubscribe,
|
||||
waitForCompactionRetry,
|
||||
} = subscription;
|
||||
|
||||
const queueHandle: EmbeddedPiQueueHandle = {
|
||||
queueMessage: async (text: string) => {
|
||||
await session.steer(text);
|
||||
},
|
||||
isStreaming: () => session.isStreaming,
|
||||
isCompacting: () => subscription.isCompacting(),
|
||||
abort: abortRun,
|
||||
};
|
||||
ACTIVE_EMBEDDED_RUNS.set(params.sessionId, queueHandle);
|
||||
|
||||
let abortWarnTimer: NodeJS.Timeout | undefined;
|
||||
const abortTimer = setTimeout(
|
||||
|
||||
@@ -968,6 +968,7 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
});
|
||||
}
|
||||
|
||||
expect(subscription.isCompacting()).toBe(true);
|
||||
expect(subscription.assistantTexts.length).toBe(0);
|
||||
|
||||
let resolved = false;
|
||||
@@ -1004,6 +1005,8 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
listener({ type: "auto_compaction_start" });
|
||||
}
|
||||
|
||||
expect(subscription.isCompacting()).toBe(true);
|
||||
|
||||
let resolved = false;
|
||||
const waitPromise = subscription.waitForCompactionRetry().then(() => {
|
||||
resolved = true;
|
||||
@@ -1018,6 +1021,7 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
|
||||
await waitPromise;
|
||||
expect(resolved).toBe(true);
|
||||
expect(subscription.isCompacting()).toBe(false);
|
||||
});
|
||||
|
||||
it("waits for multiple compaction retries before resolving", async () => {
|
||||
|
||||
@@ -604,6 +604,7 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
assistantTexts,
|
||||
toolMetas,
|
||||
unsubscribe,
|
||||
isCompacting: () => compactionInFlight || pendingCompactionRetry > 0,
|
||||
waitForCompactionRetry: () => {
|
||||
if (compactionInFlight || pendingCompactionRetry > 0) {
|
||||
ensureCompactionPromise();
|
||||
|
||||
@@ -356,12 +356,19 @@ export async function runOnboardingWizard(
|
||||
"OpenAI Codex OAuth",
|
||||
);
|
||||
const spin = prompter.progress("Starting OAuth flow…");
|
||||
let manualCodePromise: Promise<string> | undefined;
|
||||
try {
|
||||
const creds = await loginOpenAICodex({
|
||||
onAuth: async ({ url }) => {
|
||||
if (isRemote) {
|
||||
spin.stop("OAuth URL ready");
|
||||
runtime.log(`\nOpen this URL in your LOCAL browser:\n\n${url}\n`);
|
||||
manualCodePromise = prompter
|
||||
.text({
|
||||
message: "Paste the redirect URL (or authorization code)",
|
||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
||||
})
|
||||
.then((value) => String(value));
|
||||
} else {
|
||||
spin.update("Complete sign-in in browser…");
|
||||
await openUrl(url);
|
||||
@@ -369,6 +376,9 @@ export async function runOnboardingWizard(
|
||||
}
|
||||
},
|
||||
onPrompt: async (prompt) => {
|
||||
if (manualCodePromise) {
|
||||
return manualCodePromise;
|
||||
}
|
||||
const code = await prompter.text({
|
||||
message: prompt.message,
|
||||
placeholder: prompt.placeholder,
|
||||
@@ -376,6 +386,20 @@ export async function runOnboardingWizard(
|
||||
});
|
||||
return String(code);
|
||||
},
|
||||
onManualCodeInput: isRemote
|
||||
? () => {
|
||||
if (!manualCodePromise) {
|
||||
manualCodePromise = prompter
|
||||
.text({
|
||||
message: "Paste the redirect URL (or authorization code)",
|
||||
validate: (value) =>
|
||||
value?.trim() ? undefined : "Required",
|
||||
})
|
||||
.then((value) => String(value));
|
||||
}
|
||||
return manualCodePromise;
|
||||
}
|
||||
: undefined,
|
||||
onProgress: (msg) => spin.update(msg),
|
||||
});
|
||||
spin.stop("OpenAI OAuth complete");
|
||||
|
||||
Reference in New Issue
Block a user