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.
|
- Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior.
|
||||||
|
|
||||||
### Fixes
|
### 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.
|
- 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.
|
- 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.
|
- 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",
|
"@clack/prompts": "^0.11.0",
|
||||||
"@grammyjs/transformer-throttler": "^1.2.1",
|
"@grammyjs/transformer-throttler": "^1.2.1",
|
||||||
"@homebridge/ciao": "^1.3.4",
|
"@homebridge/ciao": "^1.3.4",
|
||||||
"@mariozechner/pi-agent-core": "^0.36.0",
|
"@mariozechner/pi-agent-core": "^0.37.2",
|
||||||
"@mariozechner/pi-ai": "^0.36.0",
|
"@mariozechner/pi-ai": "^0.37.2",
|
||||||
"@mariozechner/pi-coding-agent": "^0.36.0",
|
"@mariozechner/pi-coding-agent": "^0.37.2",
|
||||||
"@mariozechner/pi-tui": "^0.36.0",
|
"@mariozechner/pi-tui": "^0.37.2",
|
||||||
"@sinclair/typebox": "0.34.46",
|
"@sinclair/typebox": "0.34.46",
|
||||||
"@slack/bolt": "^4.6.0",
|
"@slack/bolt": "^4.6.0",
|
||||||
"@slack/web-api": "^7.13.0",
|
"@slack/web-api": "^7.13.0",
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"long": "5.3.2",
|
"long": "5.3.2",
|
||||||
"playwright-core": "1.57.0",
|
"playwright-core": "1.57.0",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"tslog": "^4.10.2",
|
"tslog": "^4.10.2",
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
|
"@types/proper-lockfile": "^4.1.4",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@vitest/coverage-v8": "^4.0.16",
|
"@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
|
specifier: ^1.3.4
|
||||||
version: 1.3.4
|
version: 1.3.4
|
||||||
'@mariozechner/pi-agent-core':
|
'@mariozechner/pi-agent-core':
|
||||||
specifier: ^0.36.0
|
specifier: ^0.37.2
|
||||||
version: 0.36.0(ws@8.19.0)(zod@4.3.5)
|
version: 0.37.2(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-ai':
|
'@mariozechner/pi-ai':
|
||||||
specifier: ^0.36.0
|
specifier: ^0.37.2
|
||||||
version: 0.36.0(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
version: 0.37.2(patch_hash=628fb051b6f4886984a846a5ee7aa0a571c3360d35b8d114e4684e5edcd100c5)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-coding-agent':
|
'@mariozechner/pi-coding-agent':
|
||||||
specifier: ^0.36.0
|
specifier: ^0.37.2
|
||||||
version: 0.36.0(ws@8.19.0)(zod@4.3.5)
|
version: 0.37.2(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-tui':
|
'@mariozechner/pi-tui':
|
||||||
specifier: ^0.36.0
|
specifier: ^0.37.2
|
||||||
version: 0.36.0
|
version: 0.37.2
|
||||||
'@sinclair/typebox':
|
'@sinclair/typebox':
|
||||||
specifier: 0.34.46
|
specifier: 0.34.46
|
||||||
version: 0.34.46
|
version: 0.34.46
|
||||||
@@ -103,6 +103,9 @@ importers:
|
|||||||
playwright-core:
|
playwright-core:
|
||||||
specifier: 1.57.0
|
specifier: 1.57.0
|
||||||
version: 1.57.0
|
version: 1.57.0
|
||||||
|
proper-lockfile:
|
||||||
|
specifier: ^4.1.2
|
||||||
|
version: 4.1.2
|
||||||
qrcode-terminal:
|
qrcode-terminal:
|
||||||
specifier: ^0.12.0
|
specifier: ^0.12.0
|
||||||
version: 0.12.0(patch_hash=ed82029850dbdf551f5df1de320945af52b8ea8500cc7bd4f39258e7a3d92e12)
|
version: 0.12.0(patch_hash=ed82029850dbdf551f5df1de320945af52b8ea8500cc7bd4f39258e7a3d92e12)
|
||||||
@@ -146,6 +149,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.0.3
|
specifier: ^25.0.3
|
||||||
version: 25.0.3
|
version: 25.0.3
|
||||||
|
'@types/proper-lockfile':
|
||||||
|
specifier: ^4.1.4
|
||||||
|
version: 4.1.4
|
||||||
'@types/qrcode-terminal':
|
'@types/qrcode-terminal':
|
||||||
specifier: ^0.12.2
|
specifier: ^0.12.2
|
||||||
version: 0.12.2
|
version: 0.12.2
|
||||||
@@ -804,22 +810,22 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
lit: ^3.3.1
|
lit: ^3.3.1
|
||||||
|
|
||||||
'@mariozechner/pi-agent-core@0.36.0':
|
'@mariozechner/pi-agent-core@0.37.2':
|
||||||
resolution: {integrity: sha512-86BI1/j/MLxQHSWRXVLz8+NuSmDvLQebNb40+lFDI9XI9YBh8+r5fkYgU43u4j2TvANZ7iW6SFFnhWhzy8y6dg==}
|
resolution: {integrity: sha512-GAN1lDVmlY1yH/FCfvpH29f2WBoqqMQkda7zKthOJO9l8tagxnlCWtq078CjzUGYlTDhKSf388XlOuDByBGYLA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@mariozechner/pi-ai@0.36.0':
|
'@mariozechner/pi-ai@0.37.2':
|
||||||
resolution: {integrity: sha512-xkzTgvdMzAZ/L/TgMH8z9Zi+aH0EWc54l5ygiafwvCgDk7xvfbylQG6pa9yn5zEn9T4NF9byJNk+nMHnycZvMQ==}
|
resolution: {integrity: sha512-IhhvlPrgkdrlbS7QnV+qJPmlzKyae/aI1kenclG18/dXCypxUU50OuzGoVwrXvXw/RIHRwodhd7w4IH38Z7W4Q==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mariozechner/pi-coding-agent@0.36.0':
|
'@mariozechner/pi-coding-agent@0.37.2':
|
||||||
resolution: {integrity: sha512-lKdpuGE0yVs/96GnDhrPLEEFhRteHRtnkfX04KIBpcsEXXg2vyAlpxtjtZ9nlhYqLLIY7qJRkeyjbhcFFfbAAA==}
|
resolution: {integrity: sha512-wRFqcyY76h4mONO1si2oAn9WVKnhmVV28dPHjQXVPrl7uSwMCLn+Fcde/nmbL29pYfiU1il4GmUR+iSyoxBUVQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mariozechner/pi-tui@0.36.0':
|
'@mariozechner/pi-tui@0.37.2':
|
||||||
resolution: {integrity: sha512-4n+nmTd36q0AVCbqWmjtTHTjIEwlGayKKhc+4QbpN9U3Z9jyQQa8Za1P2OHRmi6Jeu+ISuf4VBDvgmgCaxPZYg==}
|
resolution: {integrity: sha512-XNV+jEeWJxQ8U3r5njRotVs6DnEIunkLHSA4nnF4OaRRgrcsafD8M4Pm/3RywSucclVK8P7+KoGiBB2Lokkmuw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@mistralai/mistralai@1.10.0':
|
'@mistralai/mistralai@1.10.0':
|
||||||
@@ -1272,6 +1278,9 @@ packages:
|
|||||||
'@types/node@25.0.3':
|
'@types/node@25.0.3':
|
||||||
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
|
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
|
||||||
|
|
||||||
|
'@types/proper-lockfile@4.1.4':
|
||||||
|
resolution: {integrity: sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==}
|
||||||
|
|
||||||
'@types/qrcode-terminal@0.12.2':
|
'@types/qrcode-terminal@0.12.2':
|
||||||
resolution: {integrity: sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==}
|
resolution: {integrity: sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==}
|
||||||
|
|
||||||
@@ -1284,6 +1293,9 @@ packages:
|
|||||||
'@types/retry@0.12.0':
|
'@types/retry@0.12.0':
|
||||||
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
||||||
|
|
||||||
|
'@types/retry@0.12.5':
|
||||||
|
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
||||||
|
|
||||||
@@ -3588,10 +3600,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- tailwindcss
|
- 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:
|
dependencies:
|
||||||
'@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)
|
||||||
'@mariozechner/pi-tui': 0.36.0
|
'@mariozechner/pi-tui': 0.37.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -3600,7 +3612,7 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- 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:
|
dependencies:
|
||||||
'@anthropic-ai/sdk': 0.71.2(zod@4.3.5)
|
'@anthropic-ai/sdk': 0.71.2(zod@4.3.5)
|
||||||
'@google/genai': 1.34.0
|
'@google/genai': 1.34.0
|
||||||
@@ -3620,12 +3632,12 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- 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:
|
dependencies:
|
||||||
'@crosscopy/clipboard': 0.2.8
|
'@crosscopy/clipboard': 0.2.8
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@mariozechner/pi-tui': 0.36.0
|
'@mariozechner/pi-tui': 0.37.2
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
diff: 8.0.2
|
diff: 8.0.2
|
||||||
@@ -3633,6 +3645,7 @@ snapshots:
|
|||||||
glob: 11.1.0
|
glob: 11.1.0
|
||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
marked: 15.0.12
|
marked: 15.0.12
|
||||||
|
proper-lockfile: 4.1.2
|
||||||
sharp: 0.34.5
|
sharp: 0.34.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
@@ -3642,7 +3655,7 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@mariozechner/pi-tui@0.36.0':
|
'@mariozechner/pi-tui@0.37.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mime-types': 2.1.4
|
'@types/mime-types': 2.1.4
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
@@ -4038,6 +4051,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
|
'@types/proper-lockfile@4.1.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/retry': 0.12.5
|
||||||
|
|
||||||
'@types/qrcode-terminal@0.12.2': {}
|
'@types/qrcode-terminal@0.12.2': {}
|
||||||
|
|
||||||
'@types/qs@6.14.0': {}
|
'@types/qs@6.14.0': {}
|
||||||
@@ -4046,6 +4063,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/retry@0.12.0': {}
|
'@types/retry@0.12.0': {}
|
||||||
|
|
||||||
|
'@types/retry@0.12.5': {}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.0.3
|
'@types/node': 25.0.3
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type OAuthCredentials,
|
type OAuthCredentials,
|
||||||
type OAuthProvider,
|
type OAuthProvider,
|
||||||
} from "@mariozechner/pi-ai";
|
} from "@mariozechner/pi-ai";
|
||||||
|
import lockfile from "proper-lockfile";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { resolveOAuthPath } from "../config/paths.js";
|
import { resolveOAuthPath } from "../config/paths.js";
|
||||||
@@ -68,6 +69,83 @@ function saveJsonFile(pathname: string, data: unknown) {
|
|||||||
fs.chmodSync(pathname, 0o600);
|
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 {
|
function coerceLegacyStore(raw: unknown): LegacyAuthStore | null {
|
||||||
if (!raw || typeof raw !== "object") return null;
|
if (!raw || typeof raw !== "object") return null;
|
||||||
const record = raw as Record<string, unknown>;
|
const record = raw as Record<string, unknown>;
|
||||||
@@ -323,23 +401,41 @@ export async function resolveApiKeyForProfile(params: {
|
|||||||
if (cred.type === "api_key") {
|
if (cred.type === "api_key") {
|
||||||
return { apiKey: cred.key, provider: cred.provider, email: cred.email };
|
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> = {
|
try {
|
||||||
[cred.provider]: cred,
|
const result = await refreshOAuthTokenWithLock({
|
||||||
};
|
profileId,
|
||||||
const result = await getOAuthApiKey(cred.provider, oauthCreds);
|
provider: cred.provider,
|
||||||
if (!result) return null;
|
});
|
||||||
store.profiles[profileId] = {
|
if (!result) return null;
|
||||||
...cred,
|
return {
|
||||||
...result.newCredentials,
|
apiKey: result.apiKey,
|
||||||
type: "oauth",
|
provider: cred.provider,
|
||||||
};
|
email: cred.email,
|
||||||
saveAuthProfileStore(store);
|
};
|
||||||
return {
|
} catch (error) {
|
||||||
apiKey: result.apiKey,
|
const refreshedStore = ensureAuthProfileStore();
|
||||||
provider: cred.provider,
|
const refreshed = refreshedStore.profiles[profileId];
|
||||||
email: cred.email,
|
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: {
|
export function markAuthProfileGood(params: {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export type EmbeddedPiCompactResult = {
|
|||||||
type EmbeddedPiQueueHandle = {
|
type EmbeddedPiQueueHandle = {
|
||||||
queueMessage: (text: string) => Promise<void>;
|
queueMessage: (text: string) => Promise<void>;
|
||||||
isStreaming: () => boolean;
|
isStreaming: () => boolean;
|
||||||
|
isCompacting: () => boolean;
|
||||||
abort: () => void;
|
abort: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,6 +213,7 @@ export function queueEmbeddedPiMessage(
|
|||||||
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
||||||
if (!handle) return false;
|
if (!handle) return false;
|
||||||
if (!handle.isStreaming()) return false;
|
if (!handle.isStreaming()) return false;
|
||||||
|
if (handle.isCompacting()) return false;
|
||||||
void handle.queueMessage(text);
|
void handle.queueMessage(text);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -810,21 +812,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
aborted = true;
|
aborted = true;
|
||||||
void session.abort();
|
void session.abort();
|
||||||
};
|
};
|
||||||
const queueHandle: EmbeddedPiQueueHandle = {
|
const subscription = subscribeEmbeddedPiSession({
|
||||||
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({
|
|
||||||
session,
|
session,
|
||||||
runId: params.runId,
|
runId: params.runId,
|
||||||
verboseLevel: params.verboseLevel,
|
verboseLevel: params.verboseLevel,
|
||||||
@@ -837,6 +825,22 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
onAgentEvent: params.onAgentEvent,
|
onAgentEvent: params.onAgentEvent,
|
||||||
enforceFinalTag: params.enforceFinalTag,
|
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;
|
let abortWarnTimer: NodeJS.Timeout | undefined;
|
||||||
const abortTimer = setTimeout(
|
const abortTimer = setTimeout(
|
||||||
|
|||||||
@@ -968,6 +968,7 @@ describe("subscribeEmbeddedPiSession", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(subscription.isCompacting()).toBe(true);
|
||||||
expect(subscription.assistantTexts.length).toBe(0);
|
expect(subscription.assistantTexts.length).toBe(0);
|
||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
@@ -1004,6 +1005,8 @@ describe("subscribeEmbeddedPiSession", () => {
|
|||||||
listener({ type: "auto_compaction_start" });
|
listener({ type: "auto_compaction_start" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(subscription.isCompacting()).toBe(true);
|
||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const waitPromise = subscription.waitForCompactionRetry().then(() => {
|
const waitPromise = subscription.waitForCompactionRetry().then(() => {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
@@ -1018,6 +1021,7 @@ describe("subscribeEmbeddedPiSession", () => {
|
|||||||
|
|
||||||
await waitPromise;
|
await waitPromise;
|
||||||
expect(resolved).toBe(true);
|
expect(resolved).toBe(true);
|
||||||
|
expect(subscription.isCompacting()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("waits for multiple compaction retries before resolving", async () => {
|
it("waits for multiple compaction retries before resolving", async () => {
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
assistantTexts,
|
assistantTexts,
|
||||||
toolMetas,
|
toolMetas,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
|
isCompacting: () => compactionInFlight || pendingCompactionRetry > 0,
|
||||||
waitForCompactionRetry: () => {
|
waitForCompactionRetry: () => {
|
||||||
if (compactionInFlight || pendingCompactionRetry > 0) {
|
if (compactionInFlight || pendingCompactionRetry > 0) {
|
||||||
ensureCompactionPromise();
|
ensureCompactionPromise();
|
||||||
|
|||||||
@@ -356,12 +356,19 @@ export async function runOnboardingWizard(
|
|||||||
"OpenAI Codex OAuth",
|
"OpenAI Codex OAuth",
|
||||||
);
|
);
|
||||||
const spin = prompter.progress("Starting OAuth flow…");
|
const spin = prompter.progress("Starting OAuth flow…");
|
||||||
|
let manualCodePromise: Promise<string> | undefined;
|
||||||
try {
|
try {
|
||||||
const creds = await loginOpenAICodex({
|
const creds = await loginOpenAICodex({
|
||||||
onAuth: async ({ url }) => {
|
onAuth: async ({ url }) => {
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
spin.stop("OAuth URL ready");
|
spin.stop("OAuth URL ready");
|
||||||
runtime.log(`\nOpen this URL in your LOCAL browser:\n\n${url}\n`);
|
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 {
|
} else {
|
||||||
spin.update("Complete sign-in in browser…");
|
spin.update("Complete sign-in in browser…");
|
||||||
await openUrl(url);
|
await openUrl(url);
|
||||||
@@ -369,6 +376,9 @@ export async function runOnboardingWizard(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPrompt: async (prompt) => {
|
onPrompt: async (prompt) => {
|
||||||
|
if (manualCodePromise) {
|
||||||
|
return manualCodePromise;
|
||||||
|
}
|
||||||
const code = await prompter.text({
|
const code = await prompter.text({
|
||||||
message: prompt.message,
|
message: prompt.message,
|
||||||
placeholder: prompt.placeholder,
|
placeholder: prompt.placeholder,
|
||||||
@@ -376,6 +386,20 @@ export async function runOnboardingWizard(
|
|||||||
});
|
});
|
||||||
return String(code);
|
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),
|
onProgress: (msg) => spin.update(msg),
|
||||||
});
|
});
|
||||||
spin.stop("OpenAI OAuth complete");
|
spin.stop("OpenAI OAuth complete");
|
||||||
|
|||||||
Reference in New Issue
Block a user