Merge branch 'main' into feat/tavily-source-attribution
This commit is contained in:
commit
3398056c2b
@ -55,6 +55,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI/config: make `config set --strict-json` enforce real JSON, prefer `JSON.parse` with JSON5 fallback for machine-written cron/subagent stores, and relabel raw config surfaces as `JSON/JSON5` to match actual compatibility. Related: #48415, #43127, #14529, #21332. Thanks @adhitShet and @vincentkoc.
|
||||
- CLI/Ollama onboarding: keep the interactive model picker for explicit `openclaw onboard --auth-choice ollama` runs so setup still selects a default model without reintroducing pre-picker auto-pulls. (#49249) Thanks @BruceMacD.
|
||||
- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev.
|
||||
- Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev.
|
||||
|
||||
@ -1076,6 +1076,7 @@
|
||||
"group": "Extensions",
|
||||
"pages": [
|
||||
"plugins/building-extensions",
|
||||
"plugins/sdk-migration",
|
||||
"plugins/architecture",
|
||||
"plugins/community",
|
||||
"plugins/bundles",
|
||||
|
||||
@ -284,10 +284,12 @@ Azure Bastion Standard SKU runs approximately **\$140/month** and the VM (Standa
|
||||
To reduce costs:
|
||||
|
||||
- **Deallocate the VM** when not in use (stops compute billing; disk charges remain). The OpenClaw Gateway will not be reachable while the VM is deallocated — restart it when you need it live again:
|
||||
|
||||
```bash
|
||||
az vm deallocate -g "${RG}" -n "${VM_NAME}"
|
||||
az vm start -g "${RG}" -n "${VM_NAME}" # restart later
|
||||
```
|
||||
|
||||
- **Delete Bastion when not needed** and recreate it when you need SSH access. Bastion is the largest cost component and takes only a few minutes to provision.
|
||||
- **Use the Basic Bastion SKU** (~\$38/month) if you only need Portal-based SSH and don't require CLI tunneling (`az network bastion ssh`).
|
||||
|
||||
|
||||
144
docs/plugins/sdk-migration.md
Normal file
144
docs/plugins/sdk-migration.md
Normal file
@ -0,0 +1,144 @@
|
||||
---
|
||||
title: "Plugin SDK Migration"
|
||||
summary: "Migrate from openclaw/plugin-sdk/compat to focused subpath imports"
|
||||
read_when:
|
||||
- You see the OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED warning
|
||||
- You are updating a plugin from the monolithic plugin-sdk import to scoped subpaths
|
||||
- You maintain an external OpenClaw plugin
|
||||
---
|
||||
|
||||
# Plugin SDK Migration
|
||||
|
||||
OpenClaw is migrating from a single monolithic `openclaw/plugin-sdk/compat` barrel
|
||||
to **focused subpath imports** (`openclaw/plugin-sdk/<subpath>`). This page explains
|
||||
what changed, why, and how to migrate.
|
||||
|
||||
## Why this change
|
||||
|
||||
The monolithic compat barrel re-exported everything from a single entry point.
|
||||
This caused:
|
||||
|
||||
- **Slow startup**: importing one helper pulled in dozens of unrelated modules.
|
||||
- **Circular dependency risk**: broad re-exports made it easy to create import cycles.
|
||||
- **Unclear API surface**: no way to tell which exports were stable vs internal.
|
||||
|
||||
Focused subpaths fix all three: each subpath is a small, self-contained module
|
||||
with a clear purpose.
|
||||
|
||||
## What triggers the warning
|
||||
|
||||
If your plugin imports from the compat barrel, you will see:
|
||||
|
||||
```
|
||||
[OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED] Warning: openclaw/plugin-sdk/compat is
|
||||
deprecated for new plugins. Migrate to focused openclaw/plugin-sdk/<subpath> imports.
|
||||
```
|
||||
|
||||
The compat barrel still works at runtime. This is a deprecation warning, not an
|
||||
error. But new plugins **must not** use it, and existing plugins should migrate
|
||||
before compat is removed.
|
||||
|
||||
## How to migrate
|
||||
|
||||
### Step 1: Find compat imports
|
||||
|
||||
Search your extension for imports from the compat path:
|
||||
|
||||
```bash
|
||||
grep -r "plugin-sdk/compat" extensions/my-plugin/
|
||||
```
|
||||
|
||||
### Step 2: Replace with focused subpaths
|
||||
|
||||
Each export from compat maps to a specific subpath. Replace the import source:
|
||||
|
||||
```typescript
|
||||
// Before (compat barrel)
|
||||
import {
|
||||
createChannelReplyPipeline,
|
||||
createPluginRuntimeStore,
|
||||
resolveControlCommandGate,
|
||||
} from "openclaw/plugin-sdk/compat";
|
||||
|
||||
// After (focused subpaths)
|
||||
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
||||
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
|
||||
```
|
||||
|
||||
### Step 3: Verify
|
||||
|
||||
Run the build and tests:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm test -- extensions/my-plugin/
|
||||
```
|
||||
|
||||
## Subpath reference
|
||||
|
||||
| Subpath | Purpose | Key exports |
|
||||
| ----------------------------------- | ------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `plugin-sdk/core` | Plugin entry definitions, base types | `defineChannelPluginEntry`, `definePluginEntry` |
|
||||
| `plugin-sdk/channel-setup` | Setup wizard adapters | `createOptionalChannelSetupSurface` |
|
||||
| `plugin-sdk/channel-pairing` | DM pairing primitives | `createChannelPairingController` |
|
||||
| `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring | `createChannelReplyPipeline` |
|
||||
| `plugin-sdk/channel-config-helpers` | Config adapter factories | `createHybridChannelConfigAdapter`, `createScopedChannelConfigAdapter` |
|
||||
| `plugin-sdk/channel-config-schema` | Config schema builders | Channel config schema types |
|
||||
| `plugin-sdk/channel-policy` | Group/DM policy resolution | `resolveChannelGroupRequireMention` |
|
||||
| `plugin-sdk/channel-lifecycle` | Account status tracking | `createAccountStatusSink` |
|
||||
| `plugin-sdk/channel-runtime` | Runtime wiring helpers | Channel runtime utilities |
|
||||
| `plugin-sdk/channel-send-result` | Send result types | Reply result types |
|
||||
| `plugin-sdk/runtime-store` | Persistent plugin storage | `createPluginRuntimeStore` |
|
||||
| `plugin-sdk/allow-from` | Allowlist formatting | `formatAllowFromLowercase`, `formatNormalizedAllowFromEntries` |
|
||||
| `plugin-sdk/allowlist-resolution` | Allowlist input mapping | `mapAllowlistResolutionInputs` |
|
||||
| `plugin-sdk/command-auth` | Command gating | `resolveControlCommandGate` |
|
||||
| `plugin-sdk/secret-input` | Secret input parsing | Secret input helpers |
|
||||
| `plugin-sdk/webhook-ingress` | Webhook request helpers | Webhook target utilities |
|
||||
| `plugin-sdk/reply-payload` | Message reply types | Reply payload types |
|
||||
| `plugin-sdk/provider-onboard` | Provider onboarding patches | Onboarding config helpers |
|
||||
| `plugin-sdk/keyed-async-queue` | Ordered async queue | `KeyedAsyncQueue` |
|
||||
| `plugin-sdk/testing` | Test utilities | Test helpers and mocks |
|
||||
|
||||
Use the narrowest subpath that has what you need. If you cannot find an export,
|
||||
check the source at `src/plugin-sdk/` or ask in Discord.
|
||||
|
||||
## Compat barrel removal timeline
|
||||
|
||||
- **Now**: compat barrel emits a deprecation warning at runtime.
|
||||
- **Next major release**: compat barrel will be removed. Plugins still using it will
|
||||
fail to import.
|
||||
|
||||
Bundled plugins (under `extensions/`) have already been migrated. External plugins
|
||||
should migrate before the next major release.
|
||||
|
||||
## Suppressing the warning temporarily
|
||||
|
||||
If you need to suppress the warning while migrating:
|
||||
|
||||
```bash
|
||||
OPENCLAW_SUPPRESS_PLUGIN_SDK_COMPAT_WARNING=1 openclaw gateway run
|
||||
```
|
||||
|
||||
This is a temporary escape hatch, not a permanent solution.
|
||||
|
||||
## Internal barrel pattern
|
||||
|
||||
Within your extension, use local barrel files (`api.ts`, `runtime-api.ts`) for
|
||||
internal code sharing instead of importing through the plugin SDK:
|
||||
|
||||
```typescript
|
||||
// extensions/my-plugin/api.ts — public contract for this extension
|
||||
export { MyConfig } from "./src/config.js";
|
||||
export { MyRuntime } from "./src/runtime.js";
|
||||
```
|
||||
|
||||
Never import your own extension back through `openclaw/plugin-sdk/<your-extension>`
|
||||
from production files. That path is for external consumers only. See
|
||||
[Building Extensions](/plugins/building-extensions#step-4-use-local-barrels-for-internal-imports).
|
||||
|
||||
## Related
|
||||
|
||||
- [Building Extensions](/plugins/building-extensions)
|
||||
- [Plugin Architecture](/plugins/architecture)
|
||||
- [Plugin Manifest](/plugins/manifest)
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Feishu extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/feishu.js";
|
||||
export * from "openclaw/plugin-sdk/feishu";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Google Chat extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/googlechat.js";
|
||||
export * from "openclaw/plugin-sdk/googlechat";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled IRC extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../../src/plugin-sdk/irc.js";
|
||||
export * from "openclaw/plugin-sdk/irc";
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
// Private runtime barrel for the bundled LINE extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/line.js";
|
||||
export { resolveExactLineGroupConfigKey } from "../../src/plugin-sdk/line-core.js";
|
||||
export * from "openclaw/plugin-sdk/line";
|
||||
export { resolveExactLineGroupConfigKey } from "openclaw/plugin-sdk/line-core";
|
||||
export {
|
||||
formatDocsLink,
|
||||
setSetupChannelEnabled,
|
||||
splitSetupEntries,
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
} from "../../src/plugin-sdk/line-core.js";
|
||||
} from "openclaw/plugin-sdk/line-core";
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
resolveLineAccount,
|
||||
type OpenClawConfig,
|
||||
type ResolvedLineAccount,
|
||||
} from "../runtime-api.js";
|
||||
} from "openclaw/plugin-sdk/line-core";
|
||||
|
||||
export function normalizeLineAllowFrom(entry: string): string {
|
||||
return entry.replace(/^line:(?:user:)?/i, "");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { resolveExactLineGroupConfigKey, type OpenClawConfig } from "../runtime-api.js";
|
||||
import { resolveExactLineGroupConfigKey, type OpenClawConfig } from "openclaw/plugin-sdk/line-core";
|
||||
|
||||
type LineGroupContext = {
|
||||
cfg: OpenClawConfig;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { ChannelSetupAdapter, OpenClawConfig } from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
listLineAccountIds,
|
||||
normalizeAccountId,
|
||||
resolveLineAccount,
|
||||
type LineConfig,
|
||||
} from "../runtime-api.js";
|
||||
} from "openclaw/plugin-sdk/line-core";
|
||||
import type { ChannelSetupAdapter, OpenClawConfig } from "openclaw/plugin-sdk/setup";
|
||||
|
||||
const channel = "line" as const;
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { createAllowFromSection, createTopLevelChannelDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatDocsLink,
|
||||
@ -7,7 +6,8 @@ import {
|
||||
splitSetupEntries,
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
} from "../runtime-api.js";
|
||||
} from "openclaw/plugin-sdk/line-core";
|
||||
import { createAllowFromSection, createTopLevelChannelDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
isLineConfigured,
|
||||
listLineAccountIds,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Mattermost extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/mattermost.js";
|
||||
export * from "openclaw/plugin-sdk/mattermost";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Microsoft Teams extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/msteams.js";
|
||||
export * from "openclaw/plugin-sdk/msteams";
|
||||
|
||||
@ -141,7 +141,7 @@ describe("resolveGraphChatId", () => {
|
||||
}),
|
||||
);
|
||||
// Should filter by user AAD object ID
|
||||
const callUrl = (fetchFn.mock.calls[0] as [string, unknown])[0];
|
||||
const callUrl = (fetchFn.mock.calls[0] as unknown as [string, unknown])[0];
|
||||
expect(callUrl).toContain("user-aad-object-id-123");
|
||||
expect(result).toBe("19:dm-chat-id@unq.gbl.spaces");
|
||||
});
|
||||
|
||||
@ -50,9 +50,14 @@ const runtimeStub: PluginRuntime = createPluginRuntimeMock({
|
||||
},
|
||||
});
|
||||
|
||||
const noopUpdateActivity = async () => {};
|
||||
const noopDeleteActivity = async () => {};
|
||||
|
||||
const createNoopAdapter = (): MSTeamsAdapter => ({
|
||||
continueConversation: async () => {},
|
||||
process: async () => {},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
|
||||
const createRecordedSendActivity = (
|
||||
@ -81,6 +86,8 @@ const createFallbackAdapter = (proactiveSent: string[]): MSTeamsAdapter => ({
|
||||
});
|
||||
},
|
||||
process: async () => {},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
|
||||
describe("msteams messenger", () => {
|
||||
@ -195,6 +202,8 @@ describe("msteams messenger", () => {
|
||||
});
|
||||
},
|
||||
process: async () => {},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
|
||||
const ids = await sendMSTeamsMessages({
|
||||
@ -366,6 +375,8 @@ describe("msteams messenger", () => {
|
||||
await logic({ sendActivity: createRecordedSendActivity(attempts, 503) });
|
||||
},
|
||||
process: async () => {},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
|
||||
const ids = await sendMSTeamsMessages({
|
||||
|
||||
@ -42,6 +42,8 @@ function createDeps(): MSTeamsMessageHandlerDeps {
|
||||
const adapter: MSTeamsAdapter = {
|
||||
continueConversation: async () => {},
|
||||
process: async () => {},
|
||||
updateActivity: async () => {},
|
||||
deleteActivity: async () => {},
|
||||
};
|
||||
const conversationStore: MSTeamsConversationStore = {
|
||||
upsert: async () => {},
|
||||
@ -82,6 +84,8 @@ function createActivityHandler(): MSTeamsActivityHandler {
|
||||
handler = {
|
||||
onMessage: () => handler,
|
||||
onMembersAdded: () => handler,
|
||||
onReactionsAdded: () => handler,
|
||||
onReactionsRemoved: () => handler,
|
||||
run: async () => {},
|
||||
};
|
||||
return handler;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Nextcloud Talk extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/nextcloud-talk.js";
|
||||
export * from "openclaw/plugin-sdk/nextcloud-talk";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Nostr extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/nostr.js";
|
||||
export * from "openclaw/plugin-sdk/nostr";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Signal extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../../src/plugin-sdk/signal.js";
|
||||
export * from "openclaw/plugin-sdk/signal";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Tlon extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/tlon.js";
|
||||
export * from "openclaw/plugin-sdk/tlon";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Twitch extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/twitch.js";
|
||||
export * from "openclaw/plugin-sdk/twitch";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Voice Call extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/voice-call.js";
|
||||
export * from "openclaw/plugin-sdk/voice-call";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Zalo extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/zalo.js";
|
||||
export * from "openclaw/plugin-sdk/zalo";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Private runtime barrel for the bundled Zalo Personal extension.
|
||||
// Keep this barrel thin and aligned with the local extension surface.
|
||||
|
||||
export * from "../../src/plugin-sdk/zalouser.js";
|
||||
export * from "openclaw/plugin-sdk/zalouser";
|
||||
|
||||
60
package.json
60
package.json
@ -193,10 +193,50 @@
|
||||
"types": "./dist/plugin-sdk/discord-core.d.ts",
|
||||
"default": "./dist/plugin-sdk/discord-core.js"
|
||||
},
|
||||
"./plugin-sdk/feishu": {
|
||||
"types": "./dist/plugin-sdk/feishu.d.ts",
|
||||
"default": "./dist/plugin-sdk/feishu.js"
|
||||
},
|
||||
"./plugin-sdk/googlechat": {
|
||||
"types": "./dist/plugin-sdk/googlechat.d.ts",
|
||||
"default": "./dist/plugin-sdk/googlechat.js"
|
||||
},
|
||||
"./plugin-sdk/irc": {
|
||||
"types": "./dist/plugin-sdk/irc.d.ts",
|
||||
"default": "./dist/plugin-sdk/irc.js"
|
||||
},
|
||||
"./plugin-sdk/line": {
|
||||
"types": "./dist/plugin-sdk/line.d.ts",
|
||||
"default": "./dist/plugin-sdk/line.js"
|
||||
},
|
||||
"./plugin-sdk/line-core": {
|
||||
"types": "./dist/plugin-sdk/line-core.d.ts",
|
||||
"default": "./dist/plugin-sdk/line-core.js"
|
||||
},
|
||||
"./plugin-sdk/matrix": {
|
||||
"types": "./dist/plugin-sdk/matrix.d.ts",
|
||||
"default": "./dist/plugin-sdk/matrix.js"
|
||||
},
|
||||
"./plugin-sdk/mattermost": {
|
||||
"types": "./dist/plugin-sdk/mattermost.d.ts",
|
||||
"default": "./dist/plugin-sdk/mattermost.js"
|
||||
},
|
||||
"./plugin-sdk/msteams": {
|
||||
"types": "./dist/plugin-sdk/msteams.d.ts",
|
||||
"default": "./dist/plugin-sdk/msteams.js"
|
||||
},
|
||||
"./plugin-sdk/nextcloud-talk": {
|
||||
"types": "./dist/plugin-sdk/nextcloud-talk.d.ts",
|
||||
"default": "./dist/plugin-sdk/nextcloud-talk.js"
|
||||
},
|
||||
"./plugin-sdk/nostr": {
|
||||
"types": "./dist/plugin-sdk/nostr.d.ts",
|
||||
"default": "./dist/plugin-sdk/nostr.js"
|
||||
},
|
||||
"./plugin-sdk/signal": {
|
||||
"types": "./dist/plugin-sdk/signal.d.ts",
|
||||
"default": "./dist/plugin-sdk/signal.js"
|
||||
},
|
||||
"./plugin-sdk/slack": {
|
||||
"types": "./dist/plugin-sdk/slack.d.ts",
|
||||
"default": "./dist/plugin-sdk/slack.js"
|
||||
@ -205,6 +245,26 @@
|
||||
"types": "./dist/plugin-sdk/slack-core.d.ts",
|
||||
"default": "./dist/plugin-sdk/slack-core.js"
|
||||
},
|
||||
"./plugin-sdk/tlon": {
|
||||
"types": "./dist/plugin-sdk/tlon.d.ts",
|
||||
"default": "./dist/plugin-sdk/tlon.js"
|
||||
},
|
||||
"./plugin-sdk/twitch": {
|
||||
"types": "./dist/plugin-sdk/twitch.d.ts",
|
||||
"default": "./dist/plugin-sdk/twitch.js"
|
||||
},
|
||||
"./plugin-sdk/voice-call": {
|
||||
"types": "./dist/plugin-sdk/voice-call.d.ts",
|
||||
"default": "./dist/plugin-sdk/voice-call.js"
|
||||
},
|
||||
"./plugin-sdk/zalo": {
|
||||
"types": "./dist/plugin-sdk/zalo.d.ts",
|
||||
"default": "./dist/plugin-sdk/zalo.js"
|
||||
},
|
||||
"./plugin-sdk/zalouser": {
|
||||
"types": "./dist/plugin-sdk/zalouser.d.ts",
|
||||
"default": "./dist/plugin-sdk/zalouser.js"
|
||||
},
|
||||
"./plugin-sdk/imessage": {
|
||||
"types": "./dist/plugin-sdk/imessage.d.ts",
|
||||
"default": "./dist/plugin-sdk/imessage.js"
|
||||
|
||||
@ -38,9 +38,24 @@
|
||||
"telegram-core",
|
||||
"discord",
|
||||
"discord-core",
|
||||
"feishu",
|
||||
"googlechat",
|
||||
"irc",
|
||||
"line",
|
||||
"line-core",
|
||||
"matrix",
|
||||
"mattermost",
|
||||
"msteams",
|
||||
"nextcloud-talk",
|
||||
"nostr",
|
||||
"signal",
|
||||
"slack",
|
||||
"slack-core",
|
||||
"tlon",
|
||||
"twitch",
|
||||
"voice-call",
|
||||
"zalo",
|
||||
"zalouser",
|
||||
"imessage",
|
||||
"imessage-core",
|
||||
"whatsapp",
|
||||
|
||||
@ -76,6 +76,33 @@ describe("getSubagentDepthFromSessionStore", () => {
|
||||
expect(depth).toBe(2);
|
||||
});
|
||||
|
||||
it("accepts JSON5 syntax in the on-disk depth store for backward compatibility", () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-subagent-depth-json5-"));
|
||||
const storeTemplate = path.join(tmpDir, "sessions-{agentId}.json");
|
||||
const storePath = storeTemplate.replaceAll("{agentId}", "main");
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
`{
|
||||
// hand-edited legacy store
|
||||
"agent:main:subagent:flat": {
|
||||
sessionId: "subagent-flat",
|
||||
spawnDepth: 2,
|
||||
},
|
||||
}`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const depth = getSubagentDepthFromSessionStore("subagent:flat", {
|
||||
cfg: {
|
||||
session: {
|
||||
store: storeTemplate,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(depth).toBe(2);
|
||||
});
|
||||
|
||||
it("falls back to session-key segment counting when metadata is missing", () => {
|
||||
const key = "agent:main:subagent:flat";
|
||||
const depth = getSubagentDepthFromSessionStore(key, {
|
||||
|
||||
@ -11,6 +11,14 @@ type SessionDepthEntry = {
|
||||
spawnedBy?: unknown;
|
||||
};
|
||||
|
||||
function parseSessionDepthStore(raw: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return JSON5.parse(raw);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSpawnDepth(value: unknown): number | undefined {
|
||||
if (typeof value === "number") {
|
||||
return Number.isInteger(value) && value >= 0 ? value : undefined;
|
||||
@ -37,7 +45,7 @@ function normalizeSessionKey(value: unknown): string | undefined {
|
||||
function readSessionStore(storePath: string): Record<string, SessionDepthEntry> {
|
||||
try {
|
||||
const raw = fs.readFileSync(storePath, "utf-8");
|
||||
const parsed = JSON5.parse(raw);
|
||||
const parsed = parseSessionDepthStore(raw);
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, SessionDepthEntry>;
|
||||
}
|
||||
|
||||
@ -442,6 +442,15 @@ describe("config cli", () => {
|
||||
expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects JSON5-only object syntax when strict parsing is enabled", async () => {
|
||||
await expect(
|
||||
runConfigCommand(["config", "set", "gateway.auth", "{mode:'token'}", "--strict-json"]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts --strict-json with batch mode and applies batch payload", async () => {
|
||||
const resolved: OpenClawConfig = { gateway: { port: 18789 } };
|
||||
setSnapshot(resolved, resolved);
|
||||
@ -470,6 +479,8 @@ describe("config cli", () => {
|
||||
expect(helpText).toContain("--strict-json");
|
||||
expect(helpText).toContain("--json");
|
||||
expect(helpText).toContain("Legacy alias for --strict-json");
|
||||
expect(helpText).toContain("Value (JSON/JSON5 or raw string)");
|
||||
expect(helpText).toContain("Strict JSON parsing (error instead of");
|
||||
expect(helpText).toContain("--ref-provider");
|
||||
expect(helpText).toContain("--provider-source");
|
||||
expect(helpText).toContain("--batch-json");
|
||||
|
||||
@ -159,9 +159,9 @@ function parseValue(raw: string, opts: ConfigSetParseOpts): unknown {
|
||||
const trimmed = raw.trim();
|
||||
if (opts.strictJson) {
|
||||
try {
|
||||
return JSON5.parse(trimmed);
|
||||
return JSON.parse(trimmed);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to parse JSON5 value: ${String(err)}`, { cause: err });
|
||||
throw new Error(`Failed to parse JSON value: ${String(err)}`, { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1280,8 +1280,8 @@ export function registerConfigCli(program: Command) {
|
||||
.command("set")
|
||||
.description(CONFIG_SET_DESCRIPTION)
|
||||
.argument("[path]", "Config path (dot or bracket notation)")
|
||||
.argument("[value]", "Value (JSON5 or raw string)")
|
||||
.option("--strict-json", "Strict JSON5 parsing (error instead of raw string fallback)", false)
|
||||
.argument("[value]", "Value (JSON/JSON5 or raw string)")
|
||||
.option("--strict-json", "Strict JSON parsing (error instead of raw string fallback)", false)
|
||||
.option("--json", "Legacy alias for --strict-json", false)
|
||||
.option(
|
||||
"--dry-run",
|
||||
|
||||
@ -99,7 +99,7 @@ function resolveUserPath(
|
||||
export const STATE_DIR = resolveStateDir();
|
||||
|
||||
/**
|
||||
* Config file path (JSON5).
|
||||
* Config file path (JSON or JSON5).
|
||||
* Can be overridden via OPENCLAW_CONFIG_PATH.
|
||||
* Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json)
|
||||
*/
|
||||
|
||||
@ -56,6 +56,38 @@ describe("cron store", () => {
|
||||
await expect(loadCronStore(store.storePath)).rejects.toThrow(/Failed to parse cron store/i);
|
||||
});
|
||||
|
||||
it("accepts JSON5 syntax when loading an existing cron store", async () => {
|
||||
const store = await makeStorePath();
|
||||
await fs.mkdir(path.dirname(store.storePath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
store.storePath,
|
||||
`{
|
||||
// hand-edited legacy store
|
||||
version: 1,
|
||||
jobs: [
|
||||
{
|
||||
id: 'job-1',
|
||||
name: 'Job 1',
|
||||
enabled: true,
|
||||
createdAtMs: 1,
|
||||
updatedAtMs: 1,
|
||||
schedule: { kind: 'every', everyMs: 60000 },
|
||||
sessionTarget: 'main',
|
||||
wakeMode: 'next-heartbeat',
|
||||
payload: { kind: 'systemEvent', text: 'tick-job-1' },
|
||||
state: {},
|
||||
},
|
||||
],
|
||||
}`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await expect(loadCronStore(store.storePath)).resolves.toMatchObject({
|
||||
version: 1,
|
||||
jobs: [{ id: "job-1", enabled: true }],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not create a backup file when saving unchanged content", async () => {
|
||||
const store = await makeStorePath();
|
||||
const payload = makeStore("job-1", true);
|
||||
|
||||
@ -10,6 +10,14 @@ export const DEFAULT_CRON_DIR = path.join(CONFIG_DIR, "cron");
|
||||
export const DEFAULT_CRON_STORE_PATH = path.join(DEFAULT_CRON_DIR, "jobs.json");
|
||||
const serializedStoreCache = new Map<string, string>();
|
||||
|
||||
function parseCronStoreRaw(raw: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return JSON5.parse(raw);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveCronStorePath(storePath?: string) {
|
||||
if (storePath?.trim()) {
|
||||
const raw = storePath.trim();
|
||||
@ -26,7 +34,7 @@ export async function loadCronStore(storePath: string): Promise<CronStoreFile> {
|
||||
const raw = await fs.promises.readFile(storePath, "utf-8");
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON5.parse(raw);
|
||||
parsed = parseCronStoreRaw(raw);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to parse cron store at ${storePath}: ${String(err)}`, {
|
||||
cause: err,
|
||||
|
||||
@ -11,6 +11,7 @@ import { extractArchive, resolvePackedRootDir } from "./archive.js";
|
||||
let fixtureRoot = "";
|
||||
let fixtureCount = 0;
|
||||
const directorySymlinkType = process.platform === "win32" ? "junction" : undefined;
|
||||
const ARCHIVE_EXTRACT_TIMEOUT_MS = 15_000;
|
||||
|
||||
async function makeTempDir(prefix = "case") {
|
||||
const dir = path.join(fixtureRoot, `${prefix}-${fixtureCount++}`);
|
||||
@ -67,7 +68,7 @@ async function expectExtractedSizeBudgetExceeded(params: {
|
||||
extractArchive({
|
||||
archivePath: params.archivePath,
|
||||
destDir: params.destDir,
|
||||
timeoutMs: params.timeoutMs ?? 5_000,
|
||||
timeoutMs: params.timeoutMs ?? ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
limits: { maxExtractedBytes: params.maxExtractedBytes },
|
||||
}),
|
||||
).rejects.toThrow("archive extracted size exceeds limit");
|
||||
@ -93,7 +94,11 @@ describe("archive utils", () => {
|
||||
fileName: "hello.txt",
|
||||
content: "hi",
|
||||
});
|
||||
await extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 });
|
||||
await extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
});
|
||||
const rootDir = await resolvePackedRootDir(extractDir);
|
||||
const content = await fs.readFile(path.join(rootDir, "hello.txt"), "utf-8");
|
||||
expect(content).toBe("hi");
|
||||
@ -118,7 +123,11 @@ describe("archive utils", () => {
|
||||
await createDirectorySymlink(realExtractDir, extractDir);
|
||||
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "destination-symlink",
|
||||
} satisfies Partial<ArchiveSecurityError>);
|
||||
@ -135,7 +144,11 @@ describe("archive utils", () => {
|
||||
await fs.writeFile(archivePath, await zip.generateAsync({ type: "nodebuffer" }));
|
||||
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toThrow(/(escapes destination|absolute)/i);
|
||||
});
|
||||
});
|
||||
@ -151,7 +164,11 @@ describe("archive utils", () => {
|
||||
await fs.writeFile(archivePath, await zip.generateAsync({ type: "nodebuffer" }));
|
||||
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "destination-symlink-traversal",
|
||||
} satisfies Partial<ArchiveSecurityError>);
|
||||
@ -186,7 +203,11 @@ describe("archive utils", () => {
|
||||
timing: "after-realpath",
|
||||
run: async () => {
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "destination-symlink-traversal",
|
||||
} satisfies Partial<ArchiveSecurityError>);
|
||||
@ -222,7 +243,11 @@ describe("archive utils", () => {
|
||||
|
||||
try {
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "destination-symlink-traversal",
|
||||
} satisfies Partial<ArchiveSecurityError>);
|
||||
@ -245,7 +270,11 @@ describe("archive utils", () => {
|
||||
await tar.c({ cwd: insideDir, file: archivePath }, ["../outside.txt"]);
|
||||
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toThrow(/escapes destination/i);
|
||||
});
|
||||
});
|
||||
@ -261,7 +290,11 @@ describe("archive utils", () => {
|
||||
await tar.c({ cwd: archiveRoot, file: archivePath }, ["escape"]);
|
||||
|
||||
await expect(
|
||||
extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 }),
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "destination-symlink-traversal",
|
||||
} satisfies Partial<ArchiveSecurityError>);
|
||||
@ -308,7 +341,7 @@ describe("archive utils", () => {
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: 5_000,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
limits: { maxArchiveBytes: Math.max(1, stat.size - 1) },
|
||||
}),
|
||||
).rejects.toThrow("archive size exceeds limit");
|
||||
@ -328,7 +361,7 @@ describe("archive utils", () => {
|
||||
extractArchive({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
timeoutMs: 5_000,
|
||||
timeoutMs: ARCHIVE_EXTRACT_TIMEOUT_MS,
|
||||
}),
|
||||
).rejects.toThrow(/absolute|drive path|escapes destination/i);
|
||||
});
|
||||
|
||||
@ -2,8 +2,13 @@
|
||||
|
||||
export { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
export { AcpRuntimeError, isAcpRuntimeError } from "../acp/runtime/errors.js";
|
||||
export { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "../acp/runtime/registry.js";
|
||||
export type { AcpRuntimeErrorCode } from "../acp/runtime/errors.js";
|
||||
export {
|
||||
getAcpRuntimeBackend,
|
||||
registerAcpRuntimeBackend,
|
||||
requireAcpRuntimeBackend,
|
||||
unregisterAcpRuntimeBackend,
|
||||
} from "../acp/runtime/registry.js";
|
||||
export type {
|
||||
AcpRuntime,
|
||||
AcpRuntimeCapabilities,
|
||||
|
||||
@ -8,11 +8,11 @@ const shouldWarnCompatImport =
|
||||
|
||||
if (shouldWarnCompatImport) {
|
||||
process.emitWarning(
|
||||
"openclaw/plugin-sdk/compat is deprecated for new plugins. Migrate to focused openclaw/plugin-sdk/<subpath> imports.",
|
||||
"openclaw/plugin-sdk/compat is deprecated for new plugins. Migrate to focused openclaw/plugin-sdk/<subpath> imports. See https://docs.openclaw.ai/plugins/sdk-migration",
|
||||
{
|
||||
code: "OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED",
|
||||
detail:
|
||||
"Bundled plugins must use scoped plugin-sdk subpaths. External plugins may keep compat temporarily while migrating.",
|
||||
"Bundled plugins must use scoped plugin-sdk subpaths. External plugins may keep compat temporarily while migrating. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@ export type { LineConfig } from "../line/types.js";
|
||||
export {
|
||||
createTopLevelChannelDmPolicy,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatDocsLink,
|
||||
setSetupChannelEnabled,
|
||||
setTopLevelChannelDmPolicyWithAllowFrom,
|
||||
splitSetupEntries,
|
||||
} from "./setup.js";
|
||||
export { formatDocsLink } from "../terminal/links.js";
|
||||
export type { ChannelSetupAdapter, ChannelSetupDmPolicy, ChannelSetupWizard } from "./setup.js";
|
||||
export {
|
||||
listLineAccountIds,
|
||||
|
||||
@ -43,3 +43,7 @@ export {
|
||||
normalizeOptionalSecretInput,
|
||||
normalizeSecretInput,
|
||||
} from "../utils/normalize-secret-input.js";
|
||||
export {
|
||||
listKnownProviderAuthEnvVarNames,
|
||||
omitEnvKeysCaseInsensitive,
|
||||
} from "../secrets/provider-env-vars.js";
|
||||
|
||||
@ -62,6 +62,14 @@ function resolveControlCommandGate(params) {
|
||||
return { commandAuthorized, shouldBlock };
|
||||
}
|
||||
|
||||
function onDiagnosticEvent(listener) {
|
||||
const monolithic = loadMonolithicSdk();
|
||||
if (!monolithic || typeof monolithic.onDiagnosticEvent !== "function") {
|
||||
throw new Error("openclaw/plugin-sdk root alias could not resolve onDiagnosticEvent");
|
||||
}
|
||||
return monolithic.onDiagnosticEvent(listener);
|
||||
}
|
||||
|
||||
function getPackageRoot() {
|
||||
return path.resolve(__dirname, "..", "..");
|
||||
}
|
||||
@ -152,6 +160,7 @@ function tryLoadMonolithicSdk() {
|
||||
|
||||
const fastExports = {
|
||||
emptyPluginConfigSchema,
|
||||
onDiagnosticEvent,
|
||||
resolveControlCommandGate,
|
||||
};
|
||||
|
||||
|
||||
@ -180,7 +180,11 @@ describe("plugin-sdk root alias", () => {
|
||||
const lazyRootSdk = lazyModule.moduleExports;
|
||||
|
||||
expect(typeof lazyRootSdk.onDiagnosticEvent).toBe("function");
|
||||
expect(lazyRootSdk.onDiagnosticEvent).toBe(onDiagnosticEvent);
|
||||
expect(
|
||||
typeof (lazyRootSdk.onDiagnosticEvent as (listener: () => void) => () => void)(
|
||||
() => undefined,
|
||||
),
|
||||
).toBe("function");
|
||||
expect("onDiagnosticEvent" in lazyRootSdk).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@ -34,14 +34,14 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
|
||||
'export { probeIMessage } from "./src/probe.js";',
|
||||
'export { sendMessageIMessage } from "./src/send.js";',
|
||||
],
|
||||
"extensions/googlechat/runtime-api.ts": ['export * from "../../src/plugin-sdk/googlechat.js";'],
|
||||
"extensions/googlechat/runtime-api.ts": ['export * from "openclaw/plugin-sdk/googlechat";'],
|
||||
"extensions/matrix/runtime-api.ts": [
|
||||
'export * from "./src/auth-precedence.js";',
|
||||
'export * from "./helper-api.js";',
|
||||
'export * from "./thread-bindings-runtime.js";',
|
||||
],
|
||||
"extensions/nextcloud-talk/runtime-api.ts": [
|
||||
'export * from "../../src/plugin-sdk/nextcloud-talk.js";',
|
||||
'export * from "openclaw/plugin-sdk/nextcloud-talk";',
|
||||
],
|
||||
"extensions/signal/runtime-api.ts": ['export * from "./src/runtime-api.js";'],
|
||||
"extensions/slack/runtime-api.ts": [
|
||||
|
||||
@ -61,30 +61,15 @@ describe("plugin-sdk subpath exports", () => {
|
||||
expect(pluginSdkSubpaths).not.toContain("acpx");
|
||||
expect(pluginSdkSubpaths).not.toContain("compat");
|
||||
expect(pluginSdkSubpaths).not.toContain("device-pair");
|
||||
expect(pluginSdkSubpaths).not.toContain("feishu");
|
||||
expect(pluginSdkSubpaths).not.toContain("google");
|
||||
expect(pluginSdkSubpaths).not.toContain("googlechat");
|
||||
expect(pluginSdkSubpaths).not.toContain("irc");
|
||||
expect(pluginSdkSubpaths).not.toContain("line");
|
||||
expect(pluginSdkSubpaths).not.toContain("line-core");
|
||||
expect(pluginSdkSubpaths).not.toContain("lobster");
|
||||
expect(pluginSdkSubpaths).not.toContain("mattermost");
|
||||
expect(pluginSdkSubpaths).not.toContain("msteams");
|
||||
expect(pluginSdkSubpaths).not.toContain("nextcloud-talk");
|
||||
expect(pluginSdkSubpaths).not.toContain("nostr");
|
||||
expect(pluginSdkSubpaths).not.toContain("pairing-access");
|
||||
expect(pluginSdkSubpaths).not.toContain("qwen-portal-auth");
|
||||
expect(pluginSdkSubpaths).not.toContain("reply-prefix");
|
||||
expect(pluginSdkSubpaths).not.toContain("signal");
|
||||
expect(pluginSdkSubpaths).not.toContain("signal-core");
|
||||
expect(pluginSdkSubpaths).not.toContain("synology-chat");
|
||||
expect(pluginSdkSubpaths).not.toContain("tlon");
|
||||
expect(pluginSdkSubpaths).not.toContain("twitch");
|
||||
expect(pluginSdkSubpaths).not.toContain("typing");
|
||||
expect(pluginSdkSubpaths).not.toContain("voice-call");
|
||||
expect(pluginSdkSubpaths).not.toContain("zalo");
|
||||
expect(pluginSdkSubpaths).not.toContain("zai");
|
||||
expect(pluginSdkSubpaths).not.toContain("zalouser");
|
||||
expect(pluginSdkSubpaths).not.toContain("provider-model-definitions");
|
||||
});
|
||||
|
||||
|
||||
@ -1062,7 +1062,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
}
|
||||
<div class="field config-raw-field">
|
||||
<span style="display:flex;align-items:center;gap:8px;">
|
||||
Raw JSON5
|
||||
Raw config (JSON/JSON5)
|
||||
${
|
||||
sensitiveCount > 0
|
||||
? html`
|
||||
@ -1087,7 +1087,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
</span>
|
||||
<textarea
|
||||
class="${blurred ? "config-raw-redacted" : ""}"
|
||||
placeholder=${blurred ? REDACTED_PLACEHOLDER : "Raw JSON5 config"}
|
||||
placeholder=${blurred ? REDACTED_PLACEHOLDER : "Raw config (JSON/JSON5)"}
|
||||
.value=${blurred ? "" : props.raw}
|
||||
?readonly=${blurred}
|
||||
@input=${(e: Event) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user