Merge branch 'main' into perplexity-search
This commit is contained in:
commit
824eea4b50
205
.github/labeler.yml
vendored
205
.github/labeler.yml
vendored
@ -1,109 +1,172 @@
|
||||
"channel: bluebubbles":
|
||||
- "extensions/bluebubbles/**"
|
||||
- "docs/channels/bluebubbles.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/bluebubbles/**"
|
||||
- "docs/channels/bluebubbles.md"
|
||||
"channel: discord":
|
||||
- "src/discord/**"
|
||||
- "extensions/discord/**"
|
||||
- "docs/channels/discord.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/discord/**"
|
||||
- "extensions/discord/**"
|
||||
- "docs/channels/discord.md"
|
||||
"channel: googlechat":
|
||||
- "extensions/googlechat/**"
|
||||
- "docs/channels/googlechat.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/googlechat/**"
|
||||
- "docs/channels/googlechat.md"
|
||||
"channel: imessage":
|
||||
- "src/imessage/**"
|
||||
- "extensions/imessage/**"
|
||||
- "docs/channels/imessage.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/imessage/**"
|
||||
- "extensions/imessage/**"
|
||||
- "docs/channels/imessage.md"
|
||||
"channel: line":
|
||||
- "extensions/line/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/line/**"
|
||||
"channel: matrix":
|
||||
- "extensions/matrix/**"
|
||||
- "docs/channels/matrix.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/matrix/**"
|
||||
- "docs/channels/matrix.md"
|
||||
"channel: mattermost":
|
||||
- "extensions/mattermost/**"
|
||||
- "docs/channels/mattermost.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/mattermost/**"
|
||||
- "docs/channels/mattermost.md"
|
||||
"channel: msteams":
|
||||
- "extensions/msteams/**"
|
||||
- "docs/channels/msteams.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/msteams/**"
|
||||
- "docs/channels/msteams.md"
|
||||
"channel: nextcloud-talk":
|
||||
- "extensions/nextcloud-talk/**"
|
||||
- "docs/channels/nextcloud-talk.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/nextcloud-talk/**"
|
||||
- "docs/channels/nextcloud-talk.md"
|
||||
"channel: nostr":
|
||||
- "extensions/nostr/**"
|
||||
- "docs/channels/nostr.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/nostr/**"
|
||||
- "docs/channels/nostr.md"
|
||||
"channel: signal":
|
||||
- "src/signal/**"
|
||||
- "extensions/signal/**"
|
||||
- "docs/channels/signal.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/signal/**"
|
||||
- "extensions/signal/**"
|
||||
- "docs/channels/signal.md"
|
||||
"channel: slack":
|
||||
- "src/slack/**"
|
||||
- "extensions/slack/**"
|
||||
- "docs/channels/slack.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/slack/**"
|
||||
- "extensions/slack/**"
|
||||
- "docs/channels/slack.md"
|
||||
"channel: telegram":
|
||||
- "src/telegram/**"
|
||||
- "extensions/telegram/**"
|
||||
- "docs/channels/telegram.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/telegram/**"
|
||||
- "extensions/telegram/**"
|
||||
- "docs/channels/telegram.md"
|
||||
"channel: tlon":
|
||||
- "extensions/tlon/**"
|
||||
- "docs/channels/tlon.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/tlon/**"
|
||||
- "docs/channels/tlon.md"
|
||||
"channel: voice-call":
|
||||
- "extensions/voice-call/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/voice-call/**"
|
||||
"channel: whatsapp-web":
|
||||
- "src/web/**"
|
||||
- "extensions/whatsapp/**"
|
||||
- "docs/channels/whatsapp.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/web/**"
|
||||
- "extensions/whatsapp/**"
|
||||
- "docs/channels/whatsapp.md"
|
||||
"channel: zalo":
|
||||
- "extensions/zalo/**"
|
||||
- "docs/channels/zalo.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/zalo/**"
|
||||
- "docs/channels/zalo.md"
|
||||
"channel: zalouser":
|
||||
- "extensions/zalouser/**"
|
||||
- "docs/channels/zalouser.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/zalouser/**"
|
||||
- "docs/channels/zalouser.md"
|
||||
|
||||
"app: android":
|
||||
- "apps/android/**"
|
||||
- "docs/platforms/android.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "apps/android/**"
|
||||
- "docs/platforms/android.md"
|
||||
"app: ios":
|
||||
- "apps/ios/**"
|
||||
- "docs/platforms/ios.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "apps/ios/**"
|
||||
- "docs/platforms/ios.md"
|
||||
"app: macos":
|
||||
- "apps/macos/**"
|
||||
- "docs/platforms/macos.md"
|
||||
- "docs/platforms/mac/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "apps/macos/**"
|
||||
- "docs/platforms/macos.md"
|
||||
- "docs/platforms/mac/**"
|
||||
"app: web-ui":
|
||||
- "ui/**"
|
||||
- "src/gateway/control-ui.ts"
|
||||
- "src/gateway/control-ui-shared.ts"
|
||||
- "src/infra/control-ui-assets.ts"
|
||||
|
||||
"cli":
|
||||
- "src/cli/**"
|
||||
- "src/commands/**"
|
||||
- "src/tui/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "ui/**"
|
||||
- "src/gateway/control-ui.ts"
|
||||
- "src/gateway/control-ui-shared.ts"
|
||||
- "src/infra/control-ui-assets.ts"
|
||||
|
||||
"gateway":
|
||||
- "src/gateway/**"
|
||||
- "src/daemon/**"
|
||||
- "docs/gateway/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/gateway/**"
|
||||
- "src/daemon/**"
|
||||
- "docs/gateway/**"
|
||||
|
||||
"docs":
|
||||
- "docs/**"
|
||||
- "docs.acp.md"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "docs/**"
|
||||
- "docs.acp.md"
|
||||
|
||||
"extensions: copilot-proxy":
|
||||
- "extensions/copilot-proxy/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/copilot-proxy/**"
|
||||
"extensions: diagnostics-otel":
|
||||
- "extensions/diagnostics-otel/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/diagnostics-otel/**"
|
||||
"extensions: google-antigravity-auth":
|
||||
- "extensions/google-antigravity-auth/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/google-antigravity-auth/**"
|
||||
"extensions: google-gemini-cli-auth":
|
||||
- "extensions/google-gemini-cli-auth/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/google-gemini-cli-auth/**"
|
||||
"extensions: llm-task":
|
||||
- "extensions/llm-task/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/llm-task/**"
|
||||
"extensions: lobster":
|
||||
- "extensions/lobster/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/lobster/**"
|
||||
"extensions: memory-core":
|
||||
- "extensions/memory-core/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/memory-core/**"
|
||||
"extensions: memory-lancedb":
|
||||
- "extensions/memory-lancedb/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/memory-lancedb/**"
|
||||
"extensions: open-prose":
|
||||
- "extensions/open-prose/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/open-prose/**"
|
||||
"extensions: qwen-portal-auth":
|
||||
- "extensions/qwen-portal-auth/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/qwen-portal-auth/**"
|
||||
|
||||
6
.github/workflows/labeler.yml
vendored
6
.github/workflows/labeler.yml
vendored
@ -12,6 +12,12 @@ jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: "2729701"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
configuration-path: .github/labeler.yml
|
||||
repo-token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
@ -13,6 +13,12 @@ Status: unreleased.
|
||||
- Docs: add Render deployment guide. (#1975) Thanks @anurag.
|
||||
- CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi.
|
||||
- macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn.
|
||||
- Browser: fall back to URL matching for extension relay target resolution. (#1999) Thanks @jonit-dev.
|
||||
- Update: ignore dist/control-ui for dirty checks and restore after ui builds. (#1976) Thanks @Glucksberg.
|
||||
- Telegram: allow caption param for media sends. (#1888) Thanks @mguellsegarra.
|
||||
- Telegram: avoid block replies when streaming is disabled. (#1885) Thanks @ivancasco.
|
||||
- Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla.
|
||||
- Routing: precompile session key regexes. (#1697) Thanks @Ray0907.
|
||||
|
||||
## 2026.1.24-3
|
||||
|
||||
|
||||
@ -281,6 +281,7 @@ async function loginAntigravity(params: {
|
||||
openUrl: (url: string) => Promise<void>;
|
||||
prompt: (message: string) => Promise<string>;
|
||||
note: (message: string, title?: string) => Promise<void>;
|
||||
log: (message: string) => void;
|
||||
progress: { update: (msg: string) => void; stop: (msg?: string) => void };
|
||||
}): Promise<{
|
||||
access: string;
|
||||
@ -314,6 +315,11 @@ async function loginAntigravity(params: {
|
||||
].join("\n"),
|
||||
"Google Antigravity OAuth",
|
||||
);
|
||||
// Output raw URL below the box for easy copying (fixes #1772)
|
||||
params.log("");
|
||||
params.log("Copy this URL:");
|
||||
params.log(authUrl);
|
||||
params.log("");
|
||||
}
|
||||
|
||||
if (!needsManual) {
|
||||
@ -382,6 +388,7 @@ const antigravityPlugin = {
|
||||
openUrl: ctx.openUrl,
|
||||
prompt: async (message) => String(await ctx.prompter.text({ message })),
|
||||
note: ctx.prompter.note,
|
||||
log: (message) => ctx.runtime.log(message),
|
||||
progress: spin,
|
||||
});
|
||||
|
||||
|
||||
@ -369,12 +369,13 @@ export async function runAgentTurnWithFallback(params: {
|
||||
// Use pipeline if available (block streaming enabled), otherwise send directly
|
||||
if (params.blockStreamingEnabled && params.blockReplyPipeline) {
|
||||
params.blockReplyPipeline.enqueue(blockPayload);
|
||||
} else {
|
||||
// Send directly when flushing before tool execution (no streaming).
|
||||
} else if (params.blockStreamingEnabled) {
|
||||
// Send directly when flushing before tool execution (no pipeline but streaming enabled).
|
||||
// Track sent key to avoid duplicate in final payloads.
|
||||
directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
|
||||
await params.opts?.onBlockReply?.(blockPayload);
|
||||
}
|
||||
// When streaming is disabled entirely, blocks are accumulated in final text instead.
|
||||
}
|
||||
: undefined,
|
||||
onBlockReplyFlush:
|
||||
|
||||
@ -337,12 +337,56 @@ async function pageTargetId(page: Page): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function findPageByTargetId(browser: Browser, targetId: string): Promise<Page | null> {
|
||||
async function findPageByTargetId(
|
||||
browser: Browser,
|
||||
targetId: string,
|
||||
cdpUrl?: string,
|
||||
): Promise<Page | null> {
|
||||
const pages = await getAllPages(browser);
|
||||
// First, try the standard CDP session approach
|
||||
for (const page of pages) {
|
||||
const tid = await pageTargetId(page).catch(() => null);
|
||||
if (tid && tid === targetId) return page;
|
||||
}
|
||||
// If CDP sessions fail (e.g., extension relay blocks Target.attachToBrowserTarget),
|
||||
// fall back to URL-based matching using the /json/list endpoint
|
||||
if (cdpUrl) {
|
||||
try {
|
||||
const baseUrl = cdpUrl
|
||||
.replace(/\/+$/, "")
|
||||
.replace(/^ws:/, "http:")
|
||||
.replace(/\/cdp$/, "");
|
||||
const response = await fetch(`${baseUrl}/json/list`);
|
||||
if (response.ok) {
|
||||
const targets = (await response.json()) as Array<{
|
||||
id: string;
|
||||
url: string;
|
||||
title?: string;
|
||||
}>;
|
||||
const target = targets.find((t) => t.id === targetId);
|
||||
if (target) {
|
||||
// Try to find a page with matching URL
|
||||
const urlMatch = pages.filter((p) => p.url() === target.url);
|
||||
if (urlMatch.length === 1) {
|
||||
return urlMatch[0];
|
||||
}
|
||||
// If multiple URL matches, use index-based matching as fallback
|
||||
// This works when Playwright and the relay enumerate tabs in the same order
|
||||
if (urlMatch.length > 1) {
|
||||
const sameUrlTargets = targets.filter((t) => t.url === target.url);
|
||||
if (sameUrlTargets.length === urlMatch.length) {
|
||||
const idx = sameUrlTargets.findIndex((t) => t.id === targetId);
|
||||
if (idx >= 0 && idx < urlMatch.length) {
|
||||
return urlMatch[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore fetch errors and fall through to return null
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -355,7 +399,7 @@ export async function getPageForTargetId(opts: {
|
||||
if (!pages.length) throw new Error("No pages available in the connected browser.");
|
||||
const first = pages[0];
|
||||
if (!opts.targetId) return first;
|
||||
const found = await findPageByTargetId(browser, opts.targetId);
|
||||
const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||
if (!found) {
|
||||
// Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget),
|
||||
// which prevents us from resolving a page's targetId via newCDPSession(). If Playwright
|
||||
@ -496,7 +540,7 @@ export async function closePageByTargetIdViaPlaywright(opts: {
|
||||
targetId: string;
|
||||
}): Promise<void> {
|
||||
const { browser } = await connectBrowser(opts.cdpUrl);
|
||||
const page = await findPageByTargetId(browser, opts.targetId);
|
||||
const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||
if (!page) {
|
||||
throw new Error("tab not found");
|
||||
}
|
||||
@ -512,7 +556,7 @@ export async function focusPageByTargetIdViaPlaywright(opts: {
|
||||
targetId: string;
|
||||
}): Promise<void> {
|
||||
const { browser } = await connectBrowser(opts.cdpUrl);
|
||||
const page = await findPageByTargetId(browser, opts.targetId);
|
||||
const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||
if (!page) {
|
||||
throw new Error("tab not found");
|
||||
}
|
||||
|
||||
@ -13,11 +13,9 @@ const providerId = "telegram";
|
||||
function readTelegramSendParams(params: Record<string, unknown>) {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const mediaUrl = readStringParam(params, "media", { trim: false });
|
||||
const content =
|
||||
readStringParam(params, "message", {
|
||||
required: !mediaUrl,
|
||||
allowEmpty: true,
|
||||
}) ?? "";
|
||||
const message = readStringParam(params, "message", { required: !mediaUrl, allowEmpty: true });
|
||||
const caption = readStringParam(params, "caption", { allowEmpty: true });
|
||||
const content = message || caption || "";
|
||||
const replyTo = readStringParam(params, "replyTo");
|
||||
const threadId = readStringParam(params, "threadId");
|
||||
const buttons = params.buttons;
|
||||
|
||||
@ -129,9 +129,10 @@ export async function checkGitUpdateStatus(params: {
|
||||
).catch(() => null);
|
||||
const upstream = upstreamRes && upstreamRes.code === 0 ? upstreamRes.stdout.trim() : null;
|
||||
|
||||
const dirtyRes = await runCommandWithTimeout(["git", "-C", root, "status", "--porcelain"], {
|
||||
timeoutMs,
|
||||
}).catch(() => null);
|
||||
const dirtyRes = await runCommandWithTimeout(
|
||||
["git", "-C", root, "status", "--porcelain", "--", ":!dist/control-ui/"],
|
||||
{ timeoutMs },
|
||||
).catch(() => null);
|
||||
const dirty = dirtyRes && dirtyRes.code === 0 ? dirtyRes.stdout.trim().length > 0 : null;
|
||||
|
||||
const fetchOk = params.fetch
|
||||
|
||||
@ -44,7 +44,7 @@ describe("runGatewayUpdate", () => {
|
||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
||||
[`git -C ${tempDir} status --porcelain`]: { stdout: " M README.md" },
|
||||
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: " M README.md" },
|
||||
});
|
||||
|
||||
const result = await runGatewayUpdate({
|
||||
@ -69,7 +69,7 @@ describe("runGatewayUpdate", () => {
|
||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
||||
[`git -C ${tempDir} status --porcelain`]: { stdout: "" },
|
||||
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: "" },
|
||||
[`git -C ${tempDir} rev-parse --abbrev-ref --symbolic-full-name @{upstream}`]: {
|
||||
stdout: "origin/main",
|
||||
},
|
||||
@ -103,7 +103,7 @@ describe("runGatewayUpdate", () => {
|
||||
const { runner, calls } = createRunner({
|
||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||
[`git -C ${tempDir} status --porcelain`]: { stdout: "" },
|
||||
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: "" },
|
||||
[`git -C ${tempDir} fetch --all --prune --tags`]: { stdout: "" },
|
||||
[`git -C ${tempDir} tag --list v* --sort=-v:refname`]: {
|
||||
stdout: `${stableTag}\n${betaTag}\n`,
|
||||
@ -112,6 +112,7 @@ describe("runGatewayUpdate", () => {
|
||||
"pnpm install": { stdout: "" },
|
||||
"pnpm build": { stdout: "" },
|
||||
"pnpm ui:build": { stdout: "" },
|
||||
[`git -C ${tempDir} checkout -- dist/control-ui/`]: { stdout: "" },
|
||||
"pnpm clawdbot doctor --non-interactive": { stdout: "" },
|
||||
});
|
||||
|
||||
|
||||
@ -346,10 +346,14 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
const channel: UpdateChannel = opts.channel ?? "dev";
|
||||
const branch = channel === "dev" ? await readBranchName(runCommand, gitRoot, timeoutMs) : null;
|
||||
const needsCheckoutMain = channel === "dev" && branch !== DEV_BRANCH;
|
||||
gitTotalSteps = channel === "dev" ? (needsCheckoutMain ? 10 : 9) : 8;
|
||||
gitTotalSteps = channel === "dev" ? (needsCheckoutMain ? 11 : 10) : 9;
|
||||
|
||||
const statusCheck = await runStep(
|
||||
step("clean check", ["git", "-C", gitRoot, "status", "--porcelain"], gitRoot),
|
||||
step(
|
||||
"clean check",
|
||||
["git", "-C", gitRoot, "status", "--porcelain", "--", ":!dist/control-ui/"],
|
||||
gitRoot,
|
||||
),
|
||||
);
|
||||
steps.push(statusCheck);
|
||||
const hasUncommittedChanges =
|
||||
@ -654,6 +658,17 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
);
|
||||
steps.push(uiBuildStep);
|
||||
|
||||
// Restore dist/control-ui/ to committed state to prevent dirty repo after update
|
||||
// (ui:build regenerates assets with new hashes, which would block future updates)
|
||||
const restoreUiStep = await runStep(
|
||||
step(
|
||||
"restore control-ui",
|
||||
["git", "-C", gitRoot, "checkout", "--", "dist/control-ui/"],
|
||||
gitRoot,
|
||||
),
|
||||
);
|
||||
steps.push(restoreUiStep);
|
||||
|
||||
const doctorStep = await runStep(
|
||||
step(
|
||||
"clawdbot doctor",
|
||||
|
||||
@ -11,6 +11,12 @@ export const DEFAULT_AGENT_ID = "main";
|
||||
export const DEFAULT_MAIN_KEY = "main";
|
||||
export const DEFAULT_ACCOUNT_ID = "default";
|
||||
|
||||
// Pre-compiled regex
|
||||
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
|
||||
const LEADING_DASH_RE = /^-+/;
|
||||
const TRAILING_DASH_RE = /-+$/;
|
||||
|
||||
function normalizeToken(value: string | undefined | null): string {
|
||||
return (value ?? "").trim().toLowerCase();
|
||||
}
|
||||
@ -52,14 +58,14 @@ export function normalizeAgentId(value: string | undefined | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) return DEFAULT_AGENT_ID;
|
||||
// Keep it path-safe + shell-friendly.
|
||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
||||
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||
// Best-effort fallback: collapse invalid characters to "-"
|
||||
return (
|
||||
trimmed
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, "-")
|
||||
.replace(/^-+/, "")
|
||||
.replace(/-+$/, "")
|
||||
.replace(INVALID_CHARS_RE, "-")
|
||||
.replace(LEADING_DASH_RE, "")
|
||||
.replace(TRAILING_DASH_RE, "")
|
||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||
);
|
||||
}
|
||||
@ -67,13 +73,13 @@ export function normalizeAgentId(value: string | undefined | null): string {
|
||||
export function sanitizeAgentId(value: string | undefined | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) return DEFAULT_AGENT_ID;
|
||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
||||
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||
return (
|
||||
trimmed
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/gi, "-")
|
||||
.replace(/^-+/, "")
|
||||
.replace(/-+$/, "")
|
||||
.replace(INVALID_CHARS_RE, "-")
|
||||
.replace(LEADING_DASH_RE, "")
|
||||
.replace(TRAILING_DASH_RE, "")
|
||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||
);
|
||||
}
|
||||
@ -81,13 +87,13 @@ export function sanitizeAgentId(value: string | undefined | null): string {
|
||||
export function normalizeAccountId(value: string | undefined | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) return DEFAULT_ACCOUNT_ID;
|
||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
||||
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||
return (
|
||||
trimmed
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, "-")
|
||||
.replace(/^-+/, "")
|
||||
.replace(/-+$/, "")
|
||||
.replace(INVALID_CHARS_RE, "-")
|
||||
.replace(LEADING_DASH_RE, "")
|
||||
.replace(TRAILING_DASH_RE, "")
|
||||
.slice(0, 64) || DEFAULT_ACCOUNT_ID
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user