Merge branch 'main' into feat/19809-slack-typing-reaction
This commit is contained in:
commit
2c0a34ffb7
17
CHANGELOG.md
17
CHANGELOG.md
@ -17,15 +17,22 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Slack/system-event session routing: resolve reaction/member/pin/interaction system-event session keys through channel/account bindings (with sender-aware DM routing) so inbound Slack events target the correct agent session in multi-account setups instead of defaulting to `agent:main`. Fixes #22188 and #26129. Thanks @paulomcg and @daht-mad.
|
- Slack/system-event session routing: resolve reaction/member/pin/interaction system-event session keys through channel/account bindings (with sender-aware DM routing) so inbound Slack events target the correct agent session in multi-account setups instead of defaulting to `agent:main`. (#34045) Thanks @paulomcg, @daht-mad and @vincentkoc.
|
||||||
|
- Gateway/HTTP tools invoke media compatibility: preserve raw media payload access for direct `/tools/invoke` clients by allowing media `nodes` invoke commands only in HTTP tool context, while keeping agent-context media invoke blocking to prevent base64 prompt bloat. (#34365) Thanks @obviyus.
|
||||||
|
- Agents/Nodes media outputs: add dedicated `photos_latest` action handling, block media-returning `nodes invoke` commands, keep metadata-only `camera.list` invoke allowed, and normalize empty `photos_latest` results to a consistent response shape to prevent base64 context bloat. (#34332) Thanks @obviyus.
|
||||||
|
- TUI/session-key canonicalization: normalize `openclaw tui --session` values to lowercase so uppercase session names no longer drop real-time streaming updates due to gateway/TUI key mismatches. (#33866, #34013) thanks @lynnzc.
|
||||||
|
- Outbound/send config threading: pass resolved SecretRef config through outbound adapters and helper send paths so send flows do not reload unresolved runtime config. (#33987) Thanks @joshavant.
|
||||||
- Sessions/subagent attachments: remove `attachments[].content.maxLength` from `sessions_spawn` schema to avoid llama.cpp GBNF repetition overflow, and preflight UTF-8 byte size before buffer allocation while keeping runtime file-size enforcement unchanged. (#33648) Thanks @anisoptera.
|
- Sessions/subagent attachments: remove `attachments[].content.maxLength` from `sessions_spawn` schema to avoid llama.cpp GBNF repetition overflow, and preflight UTF-8 byte size before buffer allocation while keeping runtime file-size enforcement unchanged. (#33648) Thanks @anisoptera.
|
||||||
- Runtime/tool-state stability: recover from dangling Anthropic `tool_use` after compaction, serialize long-running Discord handler runs without blocking new inbound events, and prevent stale busy snapshots from suppressing stuck-channel recovery. (from #33630, #33583) Thanks @kevinWangSheng and @theotarr.
|
- Runtime/tool-state stability: recover from dangling Anthropic `tool_use` after compaction, serialize long-running Discord handler runs without blocking new inbound events, and prevent stale busy snapshots from suppressing stuck-channel recovery. (from #33630, #33583) Thanks @kevinWangSheng and @theotarr.
|
||||||
|
- ACP/Discord startup hardening: clean up stuck ACP worker children on gateway restart, unbind stale ACP thread bindings during Discord startup reconciliation, and add per-thread listener watchdog timeouts so wedged turns cannot block later messages. (#33699) Thanks @dutifulbob.
|
||||||
- Extensions/media local-root propagation: consistently forward `mediaLocalRoots` through extension `sendMedia` adapters (Google Chat, Slack, iMessage, Signal, WhatsApp), preserving non-local media behavior while restoring local attachment resolution from configured roots. Synthesis of #33581, #33545, #33540, #33536, #33528. Thanks @bmendonca3.
|
- Extensions/media local-root propagation: consistently forward `mediaLocalRoots` through extension `sendMedia` adapters (Google Chat, Slack, iMessage, Signal, WhatsApp), preserving non-local media behavior while restoring local attachment resolution from configured roots. Synthesis of #33581, #33545, #33540, #33536, #33528. Thanks @bmendonca3.
|
||||||
- Gateway/security default response headers: add `Permissions-Policy: camera=(), microphone=(), geolocation=()` to baseline gateway HTTP security headers for all responses. (#30186) thanks @habakan.
|
- Gateway/security default response headers: add `Permissions-Policy: camera=(), microphone=(), geolocation=()` to baseline gateway HTTP security headers for all responses. (#30186) thanks @habakan.
|
||||||
- Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into `openclaw/plugin-sdk/core` and `openclaw/plugin-sdk/telegram`, and preserve `api.runtime` reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy.
|
- Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into `openclaw/plugin-sdk/core` and `openclaw/plugin-sdk/telegram`, and preserve `api.runtime` reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy.
|
||||||
|
- Plugins/startup performance: reduce bursty plugin discovery/manifest overhead with short in-process caches, skip importing bundled memory plugins that are disabled by slot selection, and speed legacy root `openclaw/plugin-sdk` compatibility via runtime root-alias routing while preserving backward compatibility. Thanks @gumadeiras.
|
||||||
- Build/lazy runtime boundaries: replace ineffective dynamic import sites with dedicated lazy runtime boundaries across Slack slash handling, Telegram audit, CLI send deps, memory fallback, and outbound delivery paths while preserving behavior. (#33690) thanks @gumadeiras.
|
- Build/lazy runtime boundaries: replace ineffective dynamic import sites with dedicated lazy runtime boundaries across Slack slash handling, Telegram audit, CLI send deps, memory fallback, and outbound delivery paths while preserving behavior. (#33690) thanks @gumadeiras.
|
||||||
- Config/heartbeat legacy-path handling: auto-migrate top-level `heartbeat` into `agents.defaults.heartbeat` (with merge semantics that preserve explicit defaults), and keep startup failures on non-migratable legacy entries in the detailed invalid-config path instead of generic migration-failed errors. (#32706) thanks @xiwan.
|
- Config/heartbeat legacy-path handling: auto-migrate top-level `heartbeat` into `agents.defaults.heartbeat` (with merge semantics that preserve explicit defaults), and keep startup failures on non-migratable legacy entries in the detailed invalid-config path instead of generic migration-failed errors. (#32706) thanks @xiwan.
|
||||||
- Plugins/SDK subpath parity: add channel-specific plugin SDK subpaths for Discord, Slack, Signal, iMessage, WhatsApp, and LINE; migrate bundled plugin entrypoints to scoped subpaths/core with CI guardrails; and keep `openclaw/plugin-sdk` root import compatibility for existing external plugins. (#33737) thanks @gumadeiras.
|
- Plugins/SDK subpath parity: expand plugin SDK subpaths across bundled channels/extensions (Discord, Slack, Signal, iMessage, WhatsApp, LINE, and bundled companion plugins), with build/export/type/runtime wiring so scoped imports resolve consistently in source and dist while preserving compatibility. (#33737) thanks @gumadeiras.
|
||||||
|
- Plugins/bundled scoped-import migration: migrate bundled plugins from monolithic `openclaw/plugin-sdk` imports to scoped subpaths (or `openclaw/plugin-sdk/core`) across registration and startup-sensitive runtime files, add CI/release guardrails to prevent regressions, and keep root `openclaw/plugin-sdk` support for external/community plugins. Thanks @gumadeiras.
|
||||||
- Routing/session duplicate suppression synthesis: align shared session delivery-context inheritance, channel-paired route-field merges, and reply-surface target matching so dmScope=main turns avoid cross-surface duplicate replies while thread-aware forwarding keeps intended routing semantics. (from #33629, #26889, #17337, #33250) Thanks @Yuandiaodiaodiao, @kevinwildenradt, @Glucksberg, and @bmendonca3.
|
- Routing/session duplicate suppression synthesis: align shared session delivery-context inheritance, channel-paired route-field merges, and reply-surface target matching so dmScope=main turns avoid cross-surface duplicate replies while thread-aware forwarding keeps intended routing semantics. (from #33629, #26889, #17337, #33250) Thanks @Yuandiaodiaodiao, @kevinwildenradt, @Glucksberg, and @bmendonca3.
|
||||||
- Routing/legacy session route inheritance: preserve external route metadata inheritance for legacy channel session keys (`agent:<agent>:<channel>:<peer>` and `...:thread:<id>`) so `chat.send` does not incorrectly fall back to webchat when valid delivery context exists. Follow-up to #33786.
|
- Routing/legacy session route inheritance: preserve external route metadata inheritance for legacy channel session keys (`agent:<agent>:<channel>:<peer>` and `...:thread:<id>`) so `chat.send` does not incorrectly fall back to webchat when valid delivery context exists. Follow-up to #33786.
|
||||||
- Routing/legacy route guard tightening: require legacy session-key channel hints to match the saved delivery channel before inheriting external routing metadata, preventing custom namespaced keys like `agent:<agent>:work:<ticket>` from inheriting stale non-webchat routes.
|
- Routing/legacy route guard tightening: require legacy session-key channel hints to match the saved delivery channel before inheriting external routing metadata, preventing custom namespaced keys like `agent:<agent>:work:<ticket>` from inheriting stale non-webchat routes.
|
||||||
@ -51,6 +58,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Discord/auto presence health signal: add runtime availability-driven presence updates plus connected-state reporting to improve health monitoring and operator visibility. (#33277) Thanks @thewilloftheshadow.
|
- Discord/auto presence health signal: add runtime availability-driven presence updates plus connected-state reporting to improve health monitoring and operator visibility. (#33277) Thanks @thewilloftheshadow.
|
||||||
- Telegram/draft-stream boundary stability: materialize DM draft previews at assistant-message/tool boundaries, serialize lane-boundary callbacks before final delivery, and scope preview cleanup to the active preview so multi-step Telegram streams no longer lose, overwrite, or leave stale preview bubbles. (#33842) Thanks @ngutman.
|
- Telegram/draft-stream boundary stability: materialize DM draft previews at assistant-message/tool boundaries, serialize lane-boundary callbacks before final delivery, and scope preview cleanup to the active preview so multi-step Telegram streams no longer lose, overwrite, or leave stale preview bubbles. (#33842) Thanks @ngutman.
|
||||||
- Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.
|
- Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.
|
||||||
|
- Telegram/DM draft final delivery: materialize text-only `sendMessageDraft` previews into one permanent final message and skip duplicate final payload sends, while preserving fallback behavior when materialization fails. (#34318) Thanks @Brotherinlaw-13.
|
||||||
- Telegram/draft preview boundary + silent-token reliability: stabilize answer-lane message boundaries across late-partial/message-start races, preserve/reset finalized preview state at the correct boundaries, and suppress `NO_REPLY` lead-fragment leaks without broad heartbeat-prefix false positives. (#33169) Thanks @obviyus.
|
- Telegram/draft preview boundary + silent-token reliability: stabilize answer-lane message boundaries across late-partial/message-start races, preserve/reset finalized preview state at the correct boundaries, and suppress `NO_REPLY` lead-fragment leaks without broad heartbeat-prefix false positives. (#33169) Thanks @obviyus.
|
||||||
- Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow.
|
- Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow.
|
||||||
- Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow.
|
- Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow.
|
||||||
@ -66,12 +74,17 @@ Docs: https://docs.openclaw.ai
|
|||||||
- iOS/TTS playback fallback: keep voice playback resilient by switching from PCM to MP3 when provider format support is unavailable, while avoiding sticky fallback on generic local playback errors. (#33032) thanks @mbelinky.
|
- iOS/TTS playback fallback: keep voice playback resilient by switching from PCM to MP3 when provider format support is unavailable, while avoiding sticky fallback on generic local playback errors. (#33032) thanks @mbelinky.
|
||||||
- Telegram/multi-account default routing clarity: warn only for ambiguous (2+) account setups without an explicit default, add `openclaw doctor` warnings for missing/invalid multi-account defaults across channels, and document explicit-default guidance for channel routing and Telegram config. (#32544) thanks @Sid-Qin.
|
- Telegram/multi-account default routing clarity: warn only for ambiguous (2+) account setups without an explicit default, add `openclaw doctor` warnings for missing/invalid multi-account defaults across channels, and document explicit-default guidance for channel routing and Telegram config. (#32544) thanks @Sid-Qin.
|
||||||
- Telegram/plugin outbound hook parity: run `message_sending` + `message_sent` in Telegram reply delivery, include reply-path hook metadata (`mediaUrls`, `threadId`), and report `message_sent.success=false` when hooks blank text and no outbound message is delivered. (#32649) Thanks @KimGLee.
|
- Telegram/plugin outbound hook parity: run `message_sending` + `message_sent` in Telegram reply delivery, include reply-path hook metadata (`mediaUrls`, `threadId`), and report `message_sent.success=false` when hooks blank text and no outbound message is delivered. (#32649) Thanks @KimGLee.
|
||||||
|
- CLI/Coding-agent reliability: switch default `claude-cli` non-interactive args to `--permission-mode bypassPermissions`, auto-normalize legacy `--dangerously-skip-permissions` backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. (#28610, #31149, #34055). Thanks @niceysam, @cryptomaltese and @vincentkoc.
|
||||||
|
- ACP/ACPX session bootstrap: retry with `sessions new` when `sessions ensure` returns no session identifiers so ACP spawns avoid `NO_SESSION`/`ACP_TURN_FAILED` failures on affected agents. (#28786, #31338, #34055). Thanks @Sid-Qin and @vincentkoc.
|
||||||
|
- ACP/sessions_spawn parent stream visibility: add `streamTo: "parent"` for `runtime: "acp"` to forward initial child-run progress/no-output/completion updates back into the requester session as system events (instead of direct child delivery), and emit a tail-able session-scoped relay log (`<sessionId>.acp-stream.jsonl`, returned as `streamLogPath` when available), improving orchestrator visibility for blocked or long-running harness turns. (#34310, #29909; reopened from #34055). Thanks @vincentkoc.
|
||||||
- Agents/bootstrap truncation warning handling: unify bootstrap budget/truncation analysis across embedded + CLI runtime, `/context`, and `openclaw doctor`; add `agents.defaults.bootstrapPromptTruncationWarning` (`off|once|always`, default `once`) and persist warning-signature metadata so truncation warnings are consistent and deduped across turns. (#32769) Thanks @gumadeiras.
|
- Agents/bootstrap truncation warning handling: unify bootstrap budget/truncation analysis across embedded + CLI runtime, `/context`, and `openclaw doctor`; add `agents.defaults.bootstrapPromptTruncationWarning` (`off|once|always`, default `once`) and persist warning-signature metadata so truncation warnings are consistent and deduped across turns. (#32769) Thanks @gumadeiras.
|
||||||
- Agents/Skills runtime loading: propagate run config into embedded attempt and compaction skill-entry loading so explicitly enabled bundled companion skills are discovered consistently when skill snapshots do not already provide resolved entries. Thanks @gumadeiras.
|
- Agents/Skills runtime loading: propagate run config into embedded attempt and compaction skill-entry loading so explicitly enabled bundled companion skills are discovered consistently when skill snapshots do not already provide resolved entries. Thanks @gumadeiras.
|
||||||
- Agents/Session startup date grounding: substitute `YYYY-MM-DD` placeholders in startup/post-compaction AGENTS context and append runtime current-time lines for `/new` and `/reset` prompts so daily-memory references resolve correctly. (#32381) Thanks @chengzhichao-xydt.
|
- Agents/Session startup date grounding: substitute `YYYY-MM-DD` placeholders in startup/post-compaction AGENTS context and append runtime current-time lines for `/new` and `/reset` prompts so daily-memory references resolve correctly. (#32381) Thanks @chengzhichao-xydt.
|
||||||
- Agents/Compaction continuity: expand staged-summary merge instructions to preserve active task status, batch progress, latest user request, and follow-up commitments so compaction handoffs retain in-flight work context. (#8903) thanks @joetomasone.
|
- Agents/Compaction continuity: expand staged-summary merge instructions to preserve active task status, batch progress, latest user request, and follow-up commitments so compaction handoffs retain in-flight work context. (#8903) thanks @joetomasone.
|
||||||
- Gateway/status self version reporting: make Gateway self version in `openclaw status` prefer runtime `VERSION` (while preserving explicit `OPENCLAW_VERSION` override), preventing stale post-upgrade app version output. (#32655) thanks @liuxiaopai-ai.
|
- Gateway/status self version reporting: make Gateway self version in `openclaw status` prefer runtime `VERSION` (while preserving explicit `OPENCLAW_VERSION` override), preventing stale post-upgrade app version output. (#32655) thanks @liuxiaopai-ai.
|
||||||
- Memory/QMD index isolation: set `QMD_CONFIG_DIR` alongside `XDG_CONFIG_HOME` so QMD config state stays per-agent despite upstream XDG handling bugs, preventing cross-agent collection indexing and excess disk/CPU usage. (#27028) thanks @HenryLoenwind.
|
- Memory/QMD index isolation: set `QMD_CONFIG_DIR` alongside `XDG_CONFIG_HOME` so QMD config state stays per-agent despite upstream XDG handling bugs, preventing cross-agent collection indexing and excess disk/CPU usage. (#27028) thanks @HenryLoenwind.
|
||||||
|
- CLI/Coding-agent reliability: switch default `claude-cli` non-interactive args to `--permission-mode bypassPermissions`, auto-normalize legacy `--dangerously-skip-permissions` backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. Related to #28261. Landed from contributor PRs #28610 and #31149. Thanks @niceysam, @cryptomaltese and @vincentkoc.
|
||||||
|
- ACP/ACPX session bootstrap: retry with `sessions new` when `sessions ensure` returns no session identifiers so ACP spawns avoid `NO_SESSION`/`ACP_TURN_FAILED` failures on affected agents. Related to #28786. Landed from contributor PR #31338. Thanks @Sid-Qin and @vincentkoc.
|
||||||
- LINE/auth boundary hardening synthesis: enforce strict LINE webhook authn/z boundary semantics across pairing-store account scoping, DM/group allowlist separation, fail-closed webhook auth/runtime behavior, and replay/duplication controls (including in-flight replay reservation and post-success dedupe marking). (from #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777) Thanks @bmendonca3, @davidahmann, @harshang03, @haosenwang1018, @liuxiaopai-ai, @coygeek, and @Takhoffman.
|
- LINE/auth boundary hardening synthesis: enforce strict LINE webhook authn/z boundary semantics across pairing-store account scoping, DM/group allowlist separation, fail-closed webhook auth/runtime behavior, and replay/duplication controls (including in-flight replay reservation and post-success dedupe marking). (from #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777) Thanks @bmendonca3, @davidahmann, @harshang03, @haosenwang1018, @liuxiaopai-ai, @coygeek, and @Takhoffman.
|
||||||
- LINE/media download synthesis: fix file-media download handling and M4A audio classification across overlapping LINE regressions. (from #26386, #27761, #27787, #29509, #29755, #29776, #29785, #32240) Thanks @kevinWangSheng, @loiie45e, @carrotRakko, @Sid-Qin, @codeafridi, and @bmendonca3.
|
- LINE/media download synthesis: fix file-media download handling and M4A audio classification across overlapping LINE regressions. (from #26386, #27761, #27787, #29509, #29755, #29776, #29785, #32240) Thanks @kevinWangSheng, @loiie45e, @carrotRakko, @Sid-Qin, @codeafridi, and @bmendonca3.
|
||||||
- LINE/context and routing synthesis: fix group/room peer routing and command-authorization context propagation, and keep processing later events in mixed-success webhook batches. (from #21955, #24475, #27035, #28286) Thanks @lailoo, @mcaxtr, @jervyclaw, @Glucksberg, and @Takhoffman.
|
- LINE/context and routing synthesis: fix group/room peer routing and command-authorization context propagation, and keep processing later events in mixed-success webhook batches. (from #21955, #24475, #27035, #28286) Thanks @lailoo, @mcaxtr, @jervyclaw, @Glucksberg, and @Takhoffman.
|
||||||
|
|||||||
6
apps/ios/ActivityWidget/Assets.xcassets/Contents.json
Normal file
6
apps/ios/ActivityWidget/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
31
apps/ios/ActivityWidget/Info.plist
Normal file
31
apps/ios/ActivityWidget/Info.plist
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>OpenClaw Activity</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>2026.3.2</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>20260301</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.widgetkit-extension</string>
|
||||||
|
</dict>
|
||||||
|
<key>NSSupportsLiveActivities</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct OpenClawActivityWidgetBundle: WidgetBundle {
|
||||||
|
var body: some Widget {
|
||||||
|
OpenClawLiveActivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
84
apps/ios/ActivityWidget/OpenClawLiveActivity.swift
Normal file
84
apps/ios/ActivityWidget/OpenClawLiveActivity.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import ActivityKit
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct OpenClawLiveActivity: Widget {
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
ActivityConfiguration(for: OpenClawActivityAttributes.self) { context in
|
||||||
|
lockScreenView(context: context)
|
||||||
|
} dynamicIsland: { context in
|
||||||
|
DynamicIsland {
|
||||||
|
DynamicIslandExpandedRegion(.leading) {
|
||||||
|
statusDot(state: context.state)
|
||||||
|
}
|
||||||
|
DynamicIslandExpandedRegion(.center) {
|
||||||
|
Text(context.state.statusText)
|
||||||
|
.font(.subheadline)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
DynamicIslandExpandedRegion(.trailing) {
|
||||||
|
trailingView(state: context.state)
|
||||||
|
}
|
||||||
|
} compactLeading: {
|
||||||
|
statusDot(state: context.state)
|
||||||
|
} compactTrailing: {
|
||||||
|
Text(context.state.statusText)
|
||||||
|
.font(.caption2)
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(maxWidth: 64)
|
||||||
|
} minimal: {
|
||||||
|
statusDot(state: context.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func lockScreenView(context: ActivityViewContext<OpenClawActivityAttributes>) -> some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
statusDot(state: context.state)
|
||||||
|
.frame(width: 10, height: 10)
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text("OpenClaw")
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
Text(context.state.statusText)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
trailingView(state: context.state)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func trailingView(state: OpenClawActivityAttributes.ContentState) -> some View {
|
||||||
|
if state.isConnecting {
|
||||||
|
ProgressView().controlSize(.small)
|
||||||
|
} else if state.isDisconnected {
|
||||||
|
Image(systemName: "wifi.slash")
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
} else if state.isIdle {
|
||||||
|
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||||
|
.foregroundStyle(.green)
|
||||||
|
} else {
|
||||||
|
Text(state.startedAt, style: .timer)
|
||||||
|
.font(.caption)
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func statusDot(state: OpenClawActivityAttributes.ContentState) -> some View {
|
||||||
|
Circle()
|
||||||
|
.fill(dotColor(state: state))
|
||||||
|
.frame(width: 6, height: 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dotColor(state: OpenClawActivityAttributes.ContentState) -> Color {
|
||||||
|
if state.isDisconnected { return .red }
|
||||||
|
if state.isConnecting { return .gray }
|
||||||
|
if state.isIdle { return .green }
|
||||||
|
return .blue
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM)
|
|||||||
OPENCLAW_APP_BUNDLE_ID = ai.openclaw.ios
|
OPENCLAW_APP_BUNDLE_ID = ai.openclaw.ios
|
||||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.ios.watchkitapp
|
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.ios.watchkitapp
|
||||||
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.ios.watchkitapp.extension
|
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.ios.watchkitapp.extension
|
||||||
|
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.ios.activitywidget
|
||||||
|
|
||||||
// Local contributors can override this by running scripts/ios-configure-signing.sh.
|
// Local contributors can override this by running scripts/ios-configure-signing.sh.
|
||||||
// Keep include after defaults: xcconfig is evaluated top-to-bottom.
|
// Keep include after defaults: xcconfig is evaluated top-to-bottom.
|
||||||
|
|||||||
@ -54,6 +54,8 @@
|
|||||||
<string>OpenClaw needs microphone access for voice wake.</string>
|
<string>OpenClaw needs microphone access for voice wake.</string>
|
||||||
<key>NSSpeechRecognitionUsageDescription</key>
|
<key>NSSpeechRecognitionUsageDescription</key>
|
||||||
<string>OpenClaw uses on-device speech recognition for voice wake.</string>
|
<string>OpenClaw uses on-device speech recognition for voice wake.</string>
|
||||||
|
<key>NSSupportsLiveActivities</key>
|
||||||
|
<true/>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
|||||||
125
apps/ios/Sources/LiveActivity/LiveActivityManager.swift
Normal file
125
apps/ios/Sources/LiveActivity/LiveActivityManager.swift
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import ActivityKit
|
||||||
|
import Foundation
|
||||||
|
import os
|
||||||
|
|
||||||
|
/// Minimal Live Activity lifecycle focused on connection health + stale cleanup.
|
||||||
|
@MainActor
|
||||||
|
final class LiveActivityManager {
|
||||||
|
static let shared = LiveActivityManager()
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "ai.openclaw.ios", category: "LiveActivity")
|
||||||
|
private var currentActivity: Activity<OpenClawActivityAttributes>?
|
||||||
|
private var activityStartDate: Date = .now
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
self.hydrateCurrentAndPruneDuplicates()
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActive: Bool {
|
||||||
|
guard let activity = self.currentActivity else { return false }
|
||||||
|
guard activity.activityState == .active else {
|
||||||
|
self.currentActivity = nil
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func startActivity(agentName: String, sessionKey: String) {
|
||||||
|
self.hydrateCurrentAndPruneDuplicates()
|
||||||
|
|
||||||
|
if self.currentActivity != nil {
|
||||||
|
self.handleConnecting()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let authInfo = ActivityAuthorizationInfo()
|
||||||
|
guard authInfo.areActivitiesEnabled else {
|
||||||
|
self.logger.info("Live Activities disabled; skipping start")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.activityStartDate = .now
|
||||||
|
let attributes = OpenClawActivityAttributes(agentName: agentName, sessionKey: sessionKey)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let activity = try Activity.request(
|
||||||
|
attributes: attributes,
|
||||||
|
content: ActivityContent(state: self.connectingState(), staleDate: nil),
|
||||||
|
pushType: nil)
|
||||||
|
self.currentActivity = activity
|
||||||
|
self.logger.info("started live activity id=\(activity.id, privacy: .public)")
|
||||||
|
} catch {
|
||||||
|
self.logger.error("failed to start live activity: \(error.localizedDescription, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleConnecting() {
|
||||||
|
self.updateCurrent(state: self.connectingState())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleReconnect() {
|
||||||
|
self.updateCurrent(state: self.idleState())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDisconnect() {
|
||||||
|
self.updateCurrent(state: self.disconnectedState())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hydrateCurrentAndPruneDuplicates() {
|
||||||
|
let active = Activity<OpenClawActivityAttributes>.activities
|
||||||
|
guard !active.isEmpty else {
|
||||||
|
self.currentActivity = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let keeper = active.max { lhs, rhs in
|
||||||
|
lhs.content.state.startedAt < rhs.content.state.startedAt
|
||||||
|
} ?? active[0]
|
||||||
|
|
||||||
|
self.currentActivity = keeper
|
||||||
|
self.activityStartDate = keeper.content.state.startedAt
|
||||||
|
|
||||||
|
let stale = active.filter { $0.id != keeper.id }
|
||||||
|
for activity in stale {
|
||||||
|
Task {
|
||||||
|
await activity.end(
|
||||||
|
ActivityContent(state: self.disconnectedState(), staleDate: nil),
|
||||||
|
dismissalPolicy: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateCurrent(state: OpenClawActivityAttributes.ContentState) {
|
||||||
|
guard let activity = self.currentActivity else { return }
|
||||||
|
Task {
|
||||||
|
await activity.update(ActivityContent(state: state, staleDate: nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func connectingState() -> OpenClawActivityAttributes.ContentState {
|
||||||
|
OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Connecting...",
|
||||||
|
isIdle: false,
|
||||||
|
isDisconnected: false,
|
||||||
|
isConnecting: true,
|
||||||
|
startedAt: self.activityStartDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func idleState() -> OpenClawActivityAttributes.ContentState {
|
||||||
|
OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Idle",
|
||||||
|
isIdle: true,
|
||||||
|
isDisconnected: false,
|
||||||
|
isConnecting: false,
|
||||||
|
startedAt: self.activityStartDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func disconnectedState() -> OpenClawActivityAttributes.ContentState {
|
||||||
|
OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Disconnected",
|
||||||
|
isIdle: false,
|
||||||
|
isDisconnected: true,
|
||||||
|
isConnecting: false,
|
||||||
|
startedAt: self.activityStartDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import ActivityKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Shared schema used by iOS app + Live Activity widget extension.
|
||||||
|
struct OpenClawActivityAttributes: ActivityAttributes {
|
||||||
|
var agentName: String
|
||||||
|
var sessionKey: String
|
||||||
|
|
||||||
|
struct ContentState: Codable, Hashable {
|
||||||
|
var statusText: String
|
||||||
|
var isIdle: Bool
|
||||||
|
var isDisconnected: Bool
|
||||||
|
var isConnecting: Bool
|
||||||
|
var startedAt: Date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
extension OpenClawActivityAttributes {
|
||||||
|
static let preview = OpenClawActivityAttributes(agentName: "main", sessionKey: "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OpenClawActivityAttributes.ContentState {
|
||||||
|
static let connecting = OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Connecting...",
|
||||||
|
isIdle: false,
|
||||||
|
isDisconnected: false,
|
||||||
|
isConnecting: true,
|
||||||
|
startedAt: .now)
|
||||||
|
|
||||||
|
static let idle = OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Idle",
|
||||||
|
isIdle: true,
|
||||||
|
isDisconnected: false,
|
||||||
|
isConnecting: false,
|
||||||
|
startedAt: .now)
|
||||||
|
|
||||||
|
static let disconnected = OpenClawActivityAttributes.ContentState(
|
||||||
|
statusText: "Disconnected",
|
||||||
|
isIdle: false,
|
||||||
|
isDisconnected: true,
|
||||||
|
isConnecting: false,
|
||||||
|
startedAt: .now)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1695,6 +1695,7 @@ extension NodeAppModel {
|
|||||||
self.operatorGatewayTask = nil
|
self.operatorGatewayTask = nil
|
||||||
self.voiceWakeSyncTask?.cancel()
|
self.voiceWakeSyncTask?.cancel()
|
||||||
self.voiceWakeSyncTask = nil
|
self.voiceWakeSyncTask = nil
|
||||||
|
LiveActivityManager.shared.handleDisconnect()
|
||||||
self.gatewayHealthMonitor.stop()
|
self.gatewayHealthMonitor.stop()
|
||||||
Task {
|
Task {
|
||||||
await self.operatorGateway.disconnect()
|
await self.operatorGateway.disconnect()
|
||||||
@ -1731,6 +1732,7 @@ private extension NodeAppModel {
|
|||||||
self.operatorConnected = false
|
self.operatorConnected = false
|
||||||
self.voiceWakeSyncTask?.cancel()
|
self.voiceWakeSyncTask?.cancel()
|
||||||
self.voiceWakeSyncTask = nil
|
self.voiceWakeSyncTask = nil
|
||||||
|
LiveActivityManager.shared.handleDisconnect()
|
||||||
self.gatewayDefaultAgentId = nil
|
self.gatewayDefaultAgentId = nil
|
||||||
self.gatewayAgents = []
|
self.gatewayAgents = []
|
||||||
self.selectedAgentId = GatewaySettingsStore.loadGatewaySelectedAgentId(stableID: stableID)
|
self.selectedAgentId = GatewaySettingsStore.loadGatewaySelectedAgentId(stableID: stableID)
|
||||||
@ -1811,6 +1813,7 @@ private extension NodeAppModel {
|
|||||||
await self.refreshAgentsFromGateway()
|
await self.refreshAgentsFromGateway()
|
||||||
await self.refreshShareRouteFromGateway()
|
await self.refreshShareRouteFromGateway()
|
||||||
await self.startVoiceWakeSync()
|
await self.startVoiceWakeSync()
|
||||||
|
await MainActor.run { LiveActivityManager.shared.handleReconnect() }
|
||||||
await MainActor.run { self.startGatewayHealthMonitor() }
|
await MainActor.run { self.startGatewayHealthMonitor() }
|
||||||
},
|
},
|
||||||
onDisconnected: { [weak self] reason in
|
onDisconnected: { [weak self] reason in
|
||||||
@ -1818,6 +1821,7 @@ private extension NodeAppModel {
|
|||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.operatorConnected = false
|
self.operatorConnected = false
|
||||||
self.talkMode.updateGatewayConnected(false)
|
self.talkMode.updateGatewayConnected(false)
|
||||||
|
LiveActivityManager.shared.handleDisconnect()
|
||||||
}
|
}
|
||||||
GatewayDiagnostics.log("operator gateway disconnected reason=\(reason)")
|
GatewayDiagnostics.log("operator gateway disconnected reason=\(reason)")
|
||||||
await MainActor.run { self.stopGatewayHealthMonitor() }
|
await MainActor.run { self.stopGatewayHealthMonitor() }
|
||||||
@ -1882,6 +1886,14 @@ private extension NodeAppModel {
|
|||||||
self.gatewayStatusText = (attempt == 0) ? "Connecting…" : "Reconnecting…"
|
self.gatewayStatusText = (attempt == 0) ? "Connecting…" : "Reconnecting…"
|
||||||
self.gatewayServerName = nil
|
self.gatewayServerName = nil
|
||||||
self.gatewayRemoteAddress = nil
|
self.gatewayRemoteAddress = nil
|
||||||
|
let liveActivity = LiveActivityManager.shared
|
||||||
|
if liveActivity.isActive {
|
||||||
|
liveActivity.handleConnecting()
|
||||||
|
} else {
|
||||||
|
liveActivity.startActivity(
|
||||||
|
agentName: self.selectedAgentId ?? "main",
|
||||||
|
sessionKey: self.mainSessionKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|||||||
@ -62,3 +62,7 @@ Sources/Voice/VoiceWakePreferences.swift
|
|||||||
../../Swabble/Sources/SwabbleKit/WakeWordGate.swift
|
../../Swabble/Sources/SwabbleKit/WakeWordGate.swift
|
||||||
Sources/Voice/TalkModeManager.swift
|
Sources/Voice/TalkModeManager.swift
|
||||||
Sources/Voice/TalkOrbOverlay.swift
|
Sources/Voice/TalkOrbOverlay.swift
|
||||||
|
Sources/LiveActivity/OpenClawActivityAttributes.swift
|
||||||
|
Sources/LiveActivity/LiveActivityManager.swift
|
||||||
|
ActivityWidget/OpenClawActivityWidgetBundle.swift
|
||||||
|
ActivityWidget/OpenClawLiveActivity.swift
|
||||||
|
|||||||
@ -38,6 +38,8 @@ targets:
|
|||||||
dependencies:
|
dependencies:
|
||||||
- target: OpenClawShareExtension
|
- target: OpenClawShareExtension
|
||||||
embed: true
|
embed: true
|
||||||
|
- target: OpenClawActivityWidget
|
||||||
|
embed: true
|
||||||
- target: OpenClawWatchApp
|
- target: OpenClawWatchApp
|
||||||
- package: OpenClawKit
|
- package: OpenClawKit
|
||||||
- package: OpenClawKit
|
- package: OpenClawKit
|
||||||
@ -84,6 +86,7 @@ targets:
|
|||||||
TARGETED_DEVICE_FAMILY: "1"
|
TARGETED_DEVICE_FAMILY: "1"
|
||||||
SWIFT_VERSION: "6.0"
|
SWIFT_VERSION: "6.0"
|
||||||
SWIFT_STRICT_CONCURRENCY: complete
|
SWIFT_STRICT_CONCURRENCY: complete
|
||||||
|
SUPPORTS_LIVE_ACTIVITIES: YES
|
||||||
ENABLE_APPINTENTS_METADATA: NO
|
ENABLE_APPINTENTS_METADATA: NO
|
||||||
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
|
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
|
||||||
info:
|
info:
|
||||||
@ -115,6 +118,7 @@ targets:
|
|||||||
NSLocationAlwaysAndWhenInUseUsageDescription: OpenClaw can share your location in the background when you enable Always.
|
NSLocationAlwaysAndWhenInUseUsageDescription: OpenClaw can share your location in the background when you enable Always.
|
||||||
NSMicrophoneUsageDescription: OpenClaw needs microphone access for voice wake.
|
NSMicrophoneUsageDescription: OpenClaw needs microphone access for voice wake.
|
||||||
NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for voice wake.
|
NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for voice wake.
|
||||||
|
NSSupportsLiveActivities: true
|
||||||
UISupportedInterfaceOrientations:
|
UISupportedInterfaceOrientations:
|
||||||
- UIInterfaceOrientationPortrait
|
- UIInterfaceOrientationPortrait
|
||||||
- UIInterfaceOrientationPortraitUpsideDown
|
- UIInterfaceOrientationPortraitUpsideDown
|
||||||
@ -164,6 +168,37 @@ targets:
|
|||||||
NSExtensionActivationSupportsImageWithMaxCount: 10
|
NSExtensionActivationSupportsImageWithMaxCount: 10
|
||||||
NSExtensionActivationSupportsMovieWithMaxCount: 1
|
NSExtensionActivationSupportsMovieWithMaxCount: 1
|
||||||
|
|
||||||
|
OpenClawActivityWidget:
|
||||||
|
type: app-extension
|
||||||
|
platform: iOS
|
||||||
|
configFiles:
|
||||||
|
Debug: Signing.xcconfig
|
||||||
|
Release: Signing.xcconfig
|
||||||
|
sources:
|
||||||
|
- path: ActivityWidget
|
||||||
|
- path: Sources/LiveActivity/OpenClawActivityAttributes.swift
|
||||||
|
dependencies:
|
||||||
|
- sdk: WidgetKit.framework
|
||||||
|
- sdk: ActivityKit.framework
|
||||||
|
settings:
|
||||||
|
base:
|
||||||
|
CODE_SIGN_IDENTITY: "Apple Development"
|
||||||
|
CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)"
|
||||||
|
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID)"
|
||||||
|
SWIFT_VERSION: "6.0"
|
||||||
|
SWIFT_STRICT_CONCURRENCY: complete
|
||||||
|
SUPPORTS_LIVE_ACTIVITIES: YES
|
||||||
|
info:
|
||||||
|
path: ActivityWidget/Info.plist
|
||||||
|
properties:
|
||||||
|
CFBundleDisplayName: OpenClaw Activity
|
||||||
|
CFBundleShortVersionString: "2026.3.2"
|
||||||
|
CFBundleVersion: "20260301"
|
||||||
|
NSSupportsLiveActivities: true
|
||||||
|
NSExtension:
|
||||||
|
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||||
|
|
||||||
OpenClawWatchApp:
|
OpenClawWatchApp:
|
||||||
type: application.watchapp2
|
type: application.watchapp2
|
||||||
platform: watchOS
|
platform: watchOS
|
||||||
|
|||||||
1
changelog/fragments/ios-live-activity-status-cleanup.md
Normal file
1
changelog/fragments/ios-live-activity-status-cleanup.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
- iOS: add Live Activity connection status (connecting/idle/disconnected) on Lock Screen and Dynamic Island, and clean up duplicate/stale activities before starting a new one (#33591) (thanks @mbelinky, @leepokai)
|
||||||
@ -185,8 +185,8 @@ Input modes:
|
|||||||
OpenClaw ships a default for `claude-cli`:
|
OpenClaw ships a default for `claude-cli`:
|
||||||
|
|
||||||
- `command: "claude"`
|
- `command: "claude"`
|
||||||
- `args: ["-p", "--output-format", "json", "--dangerously-skip-permissions"]`
|
- `args: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions"]`
|
||||||
- `resumeArgs: ["-p", "--output-format", "json", "--dangerously-skip-permissions", "--resume", "{sessionId}"]`
|
- `resumeArgs: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions", "--resume", "{sessionId}"]`
|
||||||
- `modelArg: "--model"`
|
- `modelArg: "--model"`
|
||||||
- `systemPromptArg: "--append-system-prompt"`
|
- `systemPromptArg: "--append-system-prompt"`
|
||||||
- `sessionArg: "--session-id"`
|
- `sessionArg: "--session-id"`
|
||||||
|
|||||||
@ -219,7 +219,7 @@ OPENCLAW_LIVE_SETUP_TOKEN=1 OPENCLAW_LIVE_SETUP_TOKEN_PROFILE=anthropic:setup-to
|
|||||||
- Defaults:
|
- Defaults:
|
||||||
- Model: `claude-cli/claude-sonnet-4-6`
|
- Model: `claude-cli/claude-sonnet-4-6`
|
||||||
- Command: `claude`
|
- Command: `claude`
|
||||||
- Args: `["-p","--output-format","json","--dangerously-skip-permissions"]`
|
- Args: `["-p","--output-format","json","--permission-mode","bypassPermissions"]`
|
||||||
- Overrides (optional):
|
- Overrides (optional):
|
||||||
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-opus-4-6"`
|
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-opus-4-6"`
|
||||||
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.3-codex"`
|
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.3-codex"`
|
||||||
|
|||||||
@ -119,6 +119,8 @@ Interface details:
|
|||||||
- `mode: "session"` requires `thread: true`
|
- `mode: "session"` requires `thread: true`
|
||||||
- `cwd` (optional): requested runtime working directory (validated by backend/runtime policy).
|
- `cwd` (optional): requested runtime working directory (validated by backend/runtime policy).
|
||||||
- `label` (optional): operator-facing label used in session/banner text.
|
- `label` (optional): operator-facing label used in session/banner text.
|
||||||
|
- `streamTo` (optional): `"parent"` streams initial ACP run progress summaries back to the requester session as system events.
|
||||||
|
- When available, accepted responses include `streamLogPath` pointing to a session-scoped JSONL log (`<sessionId>.acp-stream.jsonl`) you can tail for full relay history.
|
||||||
|
|
||||||
## Sandbox compatibility
|
## Sandbox compatibility
|
||||||
|
|
||||||
|
|||||||
@ -472,7 +472,7 @@ Core parameters:
|
|||||||
- `sessions_list`: `kinds?`, `limit?`, `activeMinutes?`, `messageLimit?` (0 = none)
|
- `sessions_list`: `kinds?`, `limit?`, `activeMinutes?`, `messageLimit?` (0 = none)
|
||||||
- `sessions_history`: `sessionKey` (or `sessionId`), `limit?`, `includeTools?`
|
- `sessions_history`: `sessionKey` (or `sessionId`), `limit?`, `includeTools?`
|
||||||
- `sessions_send`: `sessionKey` (or `sessionId`), `message`, `timeoutSeconds?` (0 = fire-and-forget)
|
- `sessions_send`: `sessionKey` (or `sessionId`), `message`, `timeoutSeconds?` (0 = fire-and-forget)
|
||||||
- `sessions_spawn`: `task`, `label?`, `runtime?`, `agentId?`, `model?`, `thinking?`, `cwd?`, `runTimeoutSeconds?`, `thread?`, `mode?`, `cleanup?`, `sandbox?`, `attachments?`, `attachAs?`
|
- `sessions_spawn`: `task`, `label?`, `runtime?`, `agentId?`, `model?`, `thinking?`, `cwd?`, `runTimeoutSeconds?`, `thread?`, `mode?`, `cleanup?`, `sandbox?`, `streamTo?`, `attachments?`, `attachAs?`
|
||||||
- `session_status`: `sessionKey?` (default current; accepts `sessionId`), `model?` (`default` clears override)
|
- `session_status`: `sessionKey?` (default current; accepts `sessionId`), `model?` (`default` clears override)
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
@ -483,6 +483,7 @@ Notes:
|
|||||||
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
||||||
- Delivery/announce happens after completion and is best-effort; `status: "ok"` confirms the agent run finished, not that the announce was delivered.
|
- Delivery/announce happens after completion and is best-effort; `status: "ok"` confirms the agent run finished, not that the announce was delivered.
|
||||||
- `sessions_spawn` supports `runtime: "subagent" | "acp"` (`subagent` default). For ACP runtime behavior, see [ACP Agents](/tools/acp-agents).
|
- `sessions_spawn` supports `runtime: "subagent" | "acp"` (`subagent` default). For ACP runtime behavior, see [ACP Agents](/tools/acp-agents).
|
||||||
|
- For ACP runtime, `streamTo: "parent"` routes initial-run progress summaries back to the requester session as system events instead of direct child delivery.
|
||||||
- `sessions_spawn` starts a sub-agent run and posts an announce reply back to the requester chat.
|
- `sessions_spawn` starts a sub-agent run and posts an announce reply back to the requester chat.
|
||||||
- Supports one-shot mode (`mode: "run"`) and persistent thread-bound mode (`mode: "session"` with `thread: true`).
|
- Supports one-shot mode (`mode: "run"`) and persistent thread-bound mode (`mode: "session"` with `thread: true`).
|
||||||
- If `thread: true` and `mode` is omitted, mode defaults to `session`.
|
- If `thread: true` and `mode` is omitted, mode defaults to `session`.
|
||||||
@ -496,6 +497,7 @@ Notes:
|
|||||||
- Configure limits via `tools.sessions_spawn.attachments` (`enabled`, `maxTotalBytes`, `maxFiles`, `maxFileBytes`, `retainOnSessionKeep`).
|
- Configure limits via `tools.sessions_spawn.attachments` (`enabled`, `maxTotalBytes`, `maxFiles`, `maxFileBytes`, `retainOnSessionKeep`).
|
||||||
- `attachAs.mountPath` is a reserved hint for future mount implementations.
|
- `attachAs.mountPath` is a reserved hint for future mount implementations.
|
||||||
- `sessions_spawn` is non-blocking and returns `status: "accepted"` immediately.
|
- `sessions_spawn` is non-blocking and returns `status: "accepted"` immediately.
|
||||||
|
- ACP `streamTo: "parent"` responses may include `streamLogPath` (session-scoped `*.acp-stream.jsonl`) for tailing progress history.
|
||||||
- `sessions_send` runs a reply‑back ping‑pong (reply `REPLY_SKIP` to stop; max turns via `session.agentToAgent.maxPingPongTurns`, 0–5).
|
- `sessions_send` runs a reply‑back ping‑pong (reply `REPLY_SKIP` to stop; max turns via `session.agentToAgent.maxPingPongTurns`, 0–5).
|
||||||
- After the ping‑pong, the target agent runs an **announce step**; reply `ANNOUNCE_SKIP` to suppress the announcement.
|
- After the ping‑pong, the target agent runs an **announce step**; reply `ANNOUNCE_SKIP` to suppress the announcement.
|
||||||
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility: "spawned"`, OpenClaw clamps `tools.sessions.visibility` to `tree`.
|
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility: "spawned"`, OpenClaw clamps `tools.sessions.visibility` to `tree`.
|
||||||
|
|||||||
@ -112,6 +112,7 @@ Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when
|
|||||||
authoring plugins:
|
authoring plugins:
|
||||||
|
|
||||||
- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers.
|
- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers.
|
||||||
|
- `openclaw/plugin-sdk/compat` for bundled/internal plugin code that needs broader shared runtime helpers than `core`.
|
||||||
- `openclaw/plugin-sdk/telegram` for Telegram channel plugins.
|
- `openclaw/plugin-sdk/telegram` for Telegram channel plugins.
|
||||||
- `openclaw/plugin-sdk/discord` for Discord channel plugins.
|
- `openclaw/plugin-sdk/discord` for Discord channel plugins.
|
||||||
- `openclaw/plugin-sdk/slack` for Slack channel plugins.
|
- `openclaw/plugin-sdk/slack` for Slack channel plugins.
|
||||||
@ -119,12 +120,41 @@ authoring plugins:
|
|||||||
- `openclaw/plugin-sdk/imessage` for iMessage channel plugins.
|
- `openclaw/plugin-sdk/imessage` for iMessage channel plugins.
|
||||||
- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins.
|
- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins.
|
||||||
- `openclaw/plugin-sdk/line` for LINE channel plugins.
|
- `openclaw/plugin-sdk/line` for LINE channel plugins.
|
||||||
|
- `openclaw/plugin-sdk/msteams` for the bundled Microsoft Teams plugin surface.
|
||||||
|
- Bundled extension-specific subpaths are also available:
|
||||||
|
`openclaw/plugin-sdk/acpx`, `openclaw/plugin-sdk/bluebubbles`,
|
||||||
|
`openclaw/plugin-sdk/copilot-proxy`, `openclaw/plugin-sdk/device-pair`,
|
||||||
|
`openclaw/plugin-sdk/diagnostics-otel`, `openclaw/plugin-sdk/diffs`,
|
||||||
|
`openclaw/plugin-sdk/feishu`,
|
||||||
|
`openclaw/plugin-sdk/google-gemini-cli-auth`, `openclaw/plugin-sdk/googlechat`,
|
||||||
|
`openclaw/plugin-sdk/irc`, `openclaw/plugin-sdk/llm-task`,
|
||||||
|
`openclaw/plugin-sdk/lobster`, `openclaw/plugin-sdk/matrix`,
|
||||||
|
`openclaw/plugin-sdk/mattermost`, `openclaw/plugin-sdk/memory-core`,
|
||||||
|
`openclaw/plugin-sdk/memory-lancedb`,
|
||||||
|
`openclaw/plugin-sdk/minimax-portal-auth`,
|
||||||
|
`openclaw/plugin-sdk/nextcloud-talk`, `openclaw/plugin-sdk/nostr`,
|
||||||
|
`openclaw/plugin-sdk/open-prose`, `openclaw/plugin-sdk/phone-control`,
|
||||||
|
`openclaw/plugin-sdk/qwen-portal-auth`, `openclaw/plugin-sdk/synology-chat`,
|
||||||
|
`openclaw/plugin-sdk/talk-voice`, `openclaw/plugin-sdk/test-utils`,
|
||||||
|
`openclaw/plugin-sdk/thread-ownership`, `openclaw/plugin-sdk/tlon`,
|
||||||
|
`openclaw/plugin-sdk/twitch`, `openclaw/plugin-sdk/voice-call`,
|
||||||
|
`openclaw/plugin-sdk/zalo`, and `openclaw/plugin-sdk/zalouser`.
|
||||||
|
|
||||||
Compatibility note:
|
Compatibility note:
|
||||||
|
|
||||||
- `openclaw/plugin-sdk` remains supported for existing external plugins.
|
- `openclaw/plugin-sdk` remains supported for existing external plugins.
|
||||||
- New and migrated bundled plugins should use channel subpaths (or `core`) to
|
- New and migrated bundled plugins should use channel or extension-specific
|
||||||
keep startup imports scoped.
|
subpaths; use `core` for generic surfaces and `compat` only when broader
|
||||||
|
shared helpers are required.
|
||||||
|
|
||||||
|
Performance note:
|
||||||
|
|
||||||
|
- Plugin discovery and manifest metadata use short in-process caches to reduce
|
||||||
|
bursty startup/reload work.
|
||||||
|
- Set `OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1` or
|
||||||
|
`OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1` to disable these caches.
|
||||||
|
- Tune cache windows with `OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS` and
|
||||||
|
`OPENCLAW_PLUGIN_MANIFEST_CACHE_MS`.
|
||||||
|
|
||||||
## Discovery & precedence
|
## Discovery & precedence
|
||||||
|
|
||||||
@ -144,13 +174,21 @@ OpenClaw scans, in order:
|
|||||||
- `~/.openclaw/extensions/*.ts`
|
- `~/.openclaw/extensions/*.ts`
|
||||||
- `~/.openclaw/extensions/*/index.ts`
|
- `~/.openclaw/extensions/*/index.ts`
|
||||||
|
|
||||||
4. Bundled extensions (shipped with OpenClaw, **disabled by default**)
|
4. Bundled extensions (shipped with OpenClaw, mostly disabled by default)
|
||||||
|
|
||||||
- `<openclaw>/extensions/*`
|
- `<openclaw>/extensions/*`
|
||||||
|
|
||||||
Bundled plugins must be enabled explicitly via `plugins.entries.<id>.enabled`
|
Most bundled plugins must be enabled explicitly via
|
||||||
or `openclaw plugins enable <id>`. Installed plugins are enabled by default,
|
`plugins.entries.<id>.enabled` or `openclaw plugins enable <id>`.
|
||||||
but can be disabled the same way.
|
|
||||||
|
Default-on bundled plugin exceptions:
|
||||||
|
|
||||||
|
- `device-pair`
|
||||||
|
- `phone-control`
|
||||||
|
- `talk-voice`
|
||||||
|
- active memory slot plugin (default slot: `memory-core`)
|
||||||
|
|
||||||
|
Installed plugins are enabled by default, but can be disabled the same way.
|
||||||
|
|
||||||
Hardening notes:
|
Hardening notes:
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/acpx";
|
||||||
import { createAcpxPluginConfigSchema } from "./src/config.js";
|
import { createAcpxPluginConfigSchema } from "./src/config.js";
|
||||||
import { createAcpxRuntimeService } from "./src/service.js";
|
import { createAcpxRuntimeService } from "./src/service.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/acpx";
|
||||||
|
|
||||||
export const ACPX_PERMISSION_MODES = ["approve-all", "approve-reads", "deny-all"] as const;
|
export const ACPX_PERMISSION_MODES = ["approve-all", "approve-reads", "deny-all"] as const;
|
||||||
export type AcpxPermissionMode = (typeof ACPX_PERMISSION_MODES)[number];
|
export type AcpxPermissionMode = (typeof ACPX_PERMISSION_MODES)[number];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { PluginLogger } from "openclaw/plugin-sdk";
|
import type { PluginLogger } from "openclaw/plugin-sdk/acpx";
|
||||||
import { ACPX_PINNED_VERSION, ACPX_PLUGIN_ROOT, buildAcpxLocalInstallCommand } from "./config.js";
|
import { ACPX_PINNED_VERSION, ACPX_PLUGIN_ROOT, buildAcpxLocalInstallCommand } from "./config.js";
|
||||||
import {
|
import {
|
||||||
resolveSpawnFailure,
|
resolveSpawnFailure,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "openclaw/plugin-sdk";
|
import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "openclaw/plugin-sdk/acpx";
|
||||||
import {
|
import {
|
||||||
asOptionalBoolean,
|
asOptionalBoolean,
|
||||||
asOptionalString,
|
asOptionalString,
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { createWindowsCmdShimFixture } from "../../../shared/windows-cmd-shim-test-fixtures.js";
|
import { createWindowsCmdShimFixture } from "../../../shared/windows-cmd-shim-test-fixtures.js";
|
||||||
import { resolveSpawnCommand, type SpawnCommandCache } from "./process.js";
|
import {
|
||||||
|
resolveSpawnCommand,
|
||||||
|
spawnAndCollect,
|
||||||
|
type SpawnCommandCache,
|
||||||
|
waitForExit,
|
||||||
|
} from "./process.js";
|
||||||
|
|
||||||
const tempDirs: string[] = [];
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
@ -225,3 +231,62 @@ describe("resolveSpawnCommand", () => {
|
|||||||
expect(second.args[0]).toBe(scriptPath);
|
expect(second.args[0]).toBe(scriptPath);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("waitForExit", () => {
|
||||||
|
it("resolves when the child already exited before waiting starts", async () => {
|
||||||
|
const child = spawn(process.execPath, ["-e", "process.exit(0)"], {
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
child.once("close", () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
child.once("error", reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const exit = await waitForExit(child);
|
||||||
|
expect(exit.code).toBe(0);
|
||||||
|
expect(exit.signal).toBeNull();
|
||||||
|
expect(exit.error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("spawnAndCollect", () => {
|
||||||
|
it("returns abort error immediately when signal is already aborted", async () => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
controller.abort();
|
||||||
|
const result = await spawnAndCollect(
|
||||||
|
{
|
||||||
|
command: process.execPath,
|
||||||
|
args: ["-e", "process.exit(0)"],
|
||||||
|
cwd: process.cwd(),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ signal: controller.signal },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.code).toBeNull();
|
||||||
|
expect(result.error?.name).toBe("AbortError");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("terminates a running process when signal aborts", async () => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const resultPromise = spawnAndCollect(
|
||||||
|
{
|
||||||
|
command: process.execPath,
|
||||||
|
args: ["-e", "setTimeout(() => process.stdout.write('done'), 10_000)"],
|
||||||
|
cwd: process.cwd(),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ signal: controller.signal },
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
const result = await resultPromise;
|
||||||
|
expect(result.error?.name).toBe("AbortError");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import type {
|
|||||||
WindowsSpawnProgram,
|
WindowsSpawnProgram,
|
||||||
WindowsSpawnProgramCandidate,
|
WindowsSpawnProgramCandidate,
|
||||||
WindowsSpawnResolution,
|
WindowsSpawnResolution,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/acpx";
|
||||||
import {
|
import {
|
||||||
applyWindowsSpawnProgramPolicy,
|
applyWindowsSpawnProgramPolicy,
|
||||||
materializeWindowsSpawnProgram,
|
materializeWindowsSpawnProgram,
|
||||||
resolveWindowsSpawnProgramCandidate,
|
resolveWindowsSpawnProgramCandidate,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/acpx";
|
||||||
|
|
||||||
export type SpawnExit = {
|
export type SpawnExit = {
|
||||||
code: number | null;
|
code: number | null;
|
||||||
@ -114,6 +114,12 @@ export function resolveSpawnCommand(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAbortError(): Error {
|
||||||
|
const error = new Error("Operation aborted.");
|
||||||
|
error.name = "AbortError";
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
export function spawnWithResolvedCommand(
|
export function spawnWithResolvedCommand(
|
||||||
params: {
|
params: {
|
||||||
command: string;
|
command: string;
|
||||||
@ -140,6 +146,15 @@ export function spawnWithResolvedCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForExit(child: ChildProcessWithoutNullStreams): Promise<SpawnExit> {
|
export async function waitForExit(child: ChildProcessWithoutNullStreams): Promise<SpawnExit> {
|
||||||
|
// Handle callers that start waiting after the child has already exited.
|
||||||
|
if (child.exitCode !== null || child.signalCode !== null) {
|
||||||
|
return {
|
||||||
|
code: child.exitCode,
|
||||||
|
signal: child.signalCode,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return await new Promise<SpawnExit>((resolve) => {
|
return await new Promise<SpawnExit>((resolve) => {
|
||||||
let settled = false;
|
let settled = false;
|
||||||
const finish = (result: SpawnExit) => {
|
const finish = (result: SpawnExit) => {
|
||||||
@ -167,12 +182,23 @@ export async function spawnAndCollect(
|
|||||||
cwd: string;
|
cwd: string;
|
||||||
},
|
},
|
||||||
options?: SpawnCommandOptions,
|
options?: SpawnCommandOptions,
|
||||||
|
runtime?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
},
|
||||||
): Promise<{
|
): Promise<{
|
||||||
stdout: string;
|
stdout: string;
|
||||||
stderr: string;
|
stderr: string;
|
||||||
code: number | null;
|
code: number | null;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
}> {
|
}> {
|
||||||
|
if (runtime?.signal?.aborted) {
|
||||||
|
return {
|
||||||
|
stdout: "",
|
||||||
|
stderr: "",
|
||||||
|
code: null,
|
||||||
|
error: createAbortError(),
|
||||||
|
};
|
||||||
|
}
|
||||||
const child = spawnWithResolvedCommand(params, options);
|
const child = spawnWithResolvedCommand(params, options);
|
||||||
child.stdin.end();
|
child.stdin.end();
|
||||||
|
|
||||||
@ -185,13 +211,43 @@ export async function spawnAndCollect(
|
|||||||
stderr += String(chunk);
|
stderr += String(chunk);
|
||||||
});
|
});
|
||||||
|
|
||||||
const exit = await waitForExit(child);
|
let abortKillTimer: NodeJS.Timeout | undefined;
|
||||||
return {
|
let aborted = false;
|
||||||
stdout,
|
const onAbort = () => {
|
||||||
stderr,
|
aborted = true;
|
||||||
code: exit.code,
|
try {
|
||||||
error: exit.error,
|
child.kill("SIGTERM");
|
||||||
|
} catch {
|
||||||
|
// Ignore kill races when child already exited.
|
||||||
|
}
|
||||||
|
abortKillTimer = setTimeout(() => {
|
||||||
|
if (child.exitCode !== null || child.signalCode !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
child.kill("SIGKILL");
|
||||||
|
} catch {
|
||||||
|
// Ignore kill races when child already exited.
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
abortKillTimer.unref?.();
|
||||||
};
|
};
|
||||||
|
runtime?.signal?.addEventListener("abort", onAbort, { once: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const exit = await waitForExit(child);
|
||||||
|
return {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
code: exit.code,
|
||||||
|
error: aborted ? createAbortError() : exit.error,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
runtime?.signal?.removeEventListener("abort", onAbort);
|
||||||
|
if (abortKillTimer) {
|
||||||
|
clearTimeout(abortKillTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveSpawnFailure(
|
export function resolveSpawnFailure(
|
||||||
|
|||||||
@ -75,14 +75,35 @@ const setValue = command === "set" ? String(args[commandIndex + 2] || "") : "";
|
|||||||
|
|
||||||
if (command === "sessions" && args[commandIndex + 1] === "ensure") {
|
if (command === "sessions" && args[commandIndex + 1] === "ensure") {
|
||||||
writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
|
writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
|
||||||
emitJson({
|
if (process.env.MOCK_ACPX_ENSURE_EMPTY === "1") {
|
||||||
action: "session_ensured",
|
emitJson({ action: "session_ensured", name: ensureName });
|
||||||
acpxRecordId: "rec-" + ensureName,
|
} else {
|
||||||
acpxSessionId: "sid-" + ensureName,
|
emitJson({
|
||||||
agentSessionId: "inner-" + ensureName,
|
action: "session_ensured",
|
||||||
name: ensureName,
|
acpxRecordId: "rec-" + ensureName,
|
||||||
created: true,
|
acpxSessionId: "sid-" + ensureName,
|
||||||
});
|
agentSessionId: "inner-" + ensureName,
|
||||||
|
name: ensureName,
|
||||||
|
created: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === "sessions" && args[commandIndex + 1] === "new") {
|
||||||
|
writeLog({ kind: "new", agent, args, sessionName: ensureName });
|
||||||
|
if (process.env.MOCK_ACPX_NEW_EMPTY === "1") {
|
||||||
|
emitJson({ action: "session_created", name: ensureName });
|
||||||
|
} else {
|
||||||
|
emitJson({
|
||||||
|
action: "session_created",
|
||||||
|
acpxRecordId: "rec-" + ensureName,
|
||||||
|
acpxSessionId: "sid-" + ensureName,
|
||||||
|
agentSessionId: "inner-" + ensureName,
|
||||||
|
name: ensureName,
|
||||||
|
created: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -377,4 +377,51 @@ describe("AcpxRuntime", () => {
|
|||||||
expect(report.code).toBe("ACP_BACKEND_UNAVAILABLE");
|
expect(report.code).toBe("ACP_BACKEND_UNAVAILABLE");
|
||||||
expect(report.installCommand).toContain("acpx");
|
expect(report.installCommand).toContain("acpx");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to 'sessions new' when 'sessions ensure' returns no session IDs", async () => {
|
||||||
|
process.env.MOCK_ACPX_ENSURE_EMPTY = "1";
|
||||||
|
try {
|
||||||
|
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||||
|
const handle = await runtime.ensureSession({
|
||||||
|
sessionKey: "agent:claude:acp:fallback-test",
|
||||||
|
agent: "claude",
|
||||||
|
mode: "persistent",
|
||||||
|
});
|
||||||
|
expect(handle.backend).toBe("acpx");
|
||||||
|
expect(handle.acpxRecordId).toBe("rec-agent:claude:acp:fallback-test");
|
||||||
|
expect(handle.agentSessionId).toBe("inner-agent:claude:acp:fallback-test");
|
||||||
|
|
||||||
|
const logs = await readMockRuntimeLogEntries(logPath);
|
||||||
|
expect(logs.some((entry) => entry.kind === "ensure")).toBe(true);
|
||||||
|
expect(logs.some((entry) => entry.kind === "new")).toBe(true);
|
||||||
|
} finally {
|
||||||
|
delete process.env.MOCK_ACPX_ENSURE_EMPTY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails with ACP_SESSION_INIT_FAILED when both ensure and new omit session IDs", async () => {
|
||||||
|
process.env.MOCK_ACPX_ENSURE_EMPTY = "1";
|
||||||
|
process.env.MOCK_ACPX_NEW_EMPTY = "1";
|
||||||
|
try {
|
||||||
|
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
runtime.ensureSession({
|
||||||
|
sessionKey: "agent:claude:acp:fallback-fail",
|
||||||
|
agent: "claude",
|
||||||
|
mode: "persistent",
|
||||||
|
}),
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
code: "ACP_SESSION_INIT_FAILED",
|
||||||
|
message: expect.stringContaining("neither 'sessions ensure' nor 'sessions new'"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const logs = await readMockRuntimeLogEntries(logPath);
|
||||||
|
expect(logs.some((entry) => entry.kind === "ensure")).toBe(true);
|
||||||
|
expect(logs.some((entry) => entry.kind === "new")).toBe(true);
|
||||||
|
} finally {
|
||||||
|
delete process.env.MOCK_ACPX_ENSURE_EMPTY;
|
||||||
|
delete process.env.MOCK_ACPX_NEW_EMPTY;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import type {
|
|||||||
AcpRuntimeStatus,
|
AcpRuntimeStatus,
|
||||||
AcpRuntimeTurnInput,
|
AcpRuntimeTurnInput,
|
||||||
PluginLogger,
|
PluginLogger,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/acpx";
|
||||||
import { AcpRuntimeError } from "openclaw/plugin-sdk";
|
import { AcpRuntimeError } from "openclaw/plugin-sdk/acpx";
|
||||||
import { type ResolvedAcpxPluginConfig } from "./config.js";
|
import { type ResolvedAcpxPluginConfig } from "./config.js";
|
||||||
import { checkAcpxVersion } from "./ensure.js";
|
import { checkAcpxVersion } from "./ensure.js";
|
||||||
import {
|
import {
|
||||||
@ -179,7 +179,7 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
const cwd = asTrimmedString(input.cwd) || this.config.cwd;
|
const cwd = asTrimmedString(input.cwd) || this.config.cwd;
|
||||||
const mode = input.mode;
|
const mode = input.mode;
|
||||||
|
|
||||||
const events = await this.runControlCommand({
|
let events = await this.runControlCommand({
|
||||||
args: this.buildControlArgs({
|
args: this.buildControlArgs({
|
||||||
cwd,
|
cwd,
|
||||||
command: [agent, "sessions", "ensure", "--name", sessionName],
|
command: [agent, "sessions", "ensure", "--name", sessionName],
|
||||||
@ -187,12 +187,36 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
cwd,
|
cwd,
|
||||||
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||||
});
|
});
|
||||||
const ensuredEvent = events.find(
|
let ensuredEvent = events.find(
|
||||||
(event) =>
|
(event) =>
|
||||||
asOptionalString(event.agentSessionId) ||
|
asOptionalString(event.agentSessionId) ||
|
||||||
asOptionalString(event.acpxSessionId) ||
|
asOptionalString(event.acpxSessionId) ||
|
||||||
asOptionalString(event.acpxRecordId),
|
asOptionalString(event.acpxRecordId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!ensuredEvent) {
|
||||||
|
events = await this.runControlCommand({
|
||||||
|
args: this.buildControlArgs({
|
||||||
|
cwd,
|
||||||
|
command: [agent, "sessions", "new", "--name", sessionName],
|
||||||
|
}),
|
||||||
|
cwd,
|
||||||
|
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||||
|
});
|
||||||
|
ensuredEvent = events.find(
|
||||||
|
(event) =>
|
||||||
|
asOptionalString(event.agentSessionId) ||
|
||||||
|
asOptionalString(event.acpxSessionId) ||
|
||||||
|
asOptionalString(event.acpxRecordId),
|
||||||
|
);
|
||||||
|
if (!ensuredEvent) {
|
||||||
|
throw new AcpRuntimeError(
|
||||||
|
"ACP_SESSION_INIT_FAILED",
|
||||||
|
`ACP session init failed: neither 'sessions ensure' nor 'sessions new' returned valid session identifiers for ${sessionName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const acpxRecordId = ensuredEvent ? asOptionalString(ensuredEvent.acpxRecordId) : undefined;
|
const acpxRecordId = ensuredEvent ? asOptionalString(ensuredEvent.acpxRecordId) : undefined;
|
||||||
const agentSessionId = ensuredEvent ? asOptionalString(ensuredEvent.agentSessionId) : undefined;
|
const agentSessionId = ensuredEvent ? asOptionalString(ensuredEvent.agentSessionId) : undefined;
|
||||||
const backendSessionId = ensuredEvent
|
const backendSessionId = ensuredEvent
|
||||||
@ -329,7 +353,10 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
return ACPX_CAPABILITIES;
|
return ACPX_CAPABILITIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatus(input: { handle: AcpRuntimeHandle }): Promise<AcpRuntimeStatus> {
|
async getStatus(input: {
|
||||||
|
handle: AcpRuntimeHandle;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}): Promise<AcpRuntimeStatus> {
|
||||||
const state = this.resolveHandleState(input.handle);
|
const state = this.resolveHandleState(input.handle);
|
||||||
const events = await this.runControlCommand({
|
const events = await this.runControlCommand({
|
||||||
args: this.buildControlArgs({
|
args: this.buildControlArgs({
|
||||||
@ -339,6 +366,7 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
cwd: state.cwd,
|
cwd: state.cwd,
|
||||||
fallbackCode: "ACP_TURN_FAILED",
|
fallbackCode: "ACP_TURN_FAILED",
|
||||||
ignoreNoSession: true,
|
ignoreNoSession: true,
|
||||||
|
signal: input.signal,
|
||||||
});
|
});
|
||||||
const detail = events.find((event) => !toAcpxErrorEvent(event)) ?? events[0];
|
const detail = events.find((event) => !toAcpxErrorEvent(event)) ?? events[0];
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
@ -562,6 +590,7 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
cwd: string;
|
cwd: string;
|
||||||
fallbackCode: AcpRuntimeErrorCode;
|
fallbackCode: AcpRuntimeErrorCode;
|
||||||
ignoreNoSession?: boolean;
|
ignoreNoSession?: boolean;
|
||||||
|
signal?: AbortSignal;
|
||||||
}): Promise<AcpxJsonObject[]> {
|
}): Promise<AcpxJsonObject[]> {
|
||||||
const result = await spawnAndCollect(
|
const result = await spawnAndCollect(
|
||||||
{
|
{
|
||||||
@ -570,6 +599,9 @@ export class AcpxRuntime implements AcpRuntime {
|
|||||||
cwd: params.cwd,
|
cwd: params.cwd,
|
||||||
},
|
},
|
||||||
this.spawnCommandOptions,
|
this.spawnCommandOptions,
|
||||||
|
{
|
||||||
|
signal: params.signal,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { AcpRuntime, OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
|
import type { AcpRuntime, OpenClawPluginServiceContext } from "openclaw/plugin-sdk/acpx";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { AcpRuntimeError } from "../../../src/acp/runtime/errors.js";
|
import { AcpRuntimeError } from "../../../src/acp/runtime/errors.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import type {
|
|||||||
OpenClawPluginService,
|
OpenClawPluginService,
|
||||||
OpenClawPluginServiceContext,
|
OpenClawPluginServiceContext,
|
||||||
PluginLogger,
|
PluginLogger,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/acpx";
|
||||||
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "openclaw/plugin-sdk";
|
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "openclaw/plugin-sdk/acpx";
|
||||||
import { resolveAcpxPluginConfig, type ResolvedAcpxPluginConfig } from "./config.js";
|
import { resolveAcpxPluginConfig, type ResolvedAcpxPluginConfig } from "./config.js";
|
||||||
import { ensureAcpx } from "./ensure.js";
|
import { ensureAcpx } from "./ensure.js";
|
||||||
import { ACPX_BACKEND_ID, AcpxRuntime } from "./runtime.js";
|
import { ACPX_BACKEND_ID, AcpxRuntime } from "./runtime.js";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { bluebubblesPlugin } from "./src/channel.js";
|
import { bluebubblesPlugin } from "./src/channel.js";
|
||||||
import { setBlueBubblesRuntime } from "./src/runtime.js";
|
import { setBlueBubblesRuntime } from "./src/runtime.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||||
import { normalizeResolvedSecretInputString } from "./secret-input.js";
|
import { normalizeResolvedSecretInputString } from "./secret-input.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_ACCOUNT_ID,
|
DEFAULT_ACCOUNT_ID,
|
||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
normalizeOptionalAccountId,
|
normalizeOptionalAccountId,
|
||||||
} from "openclaw/plugin-sdk/account-id";
|
} from "openclaw/plugin-sdk/account-id";
|
||||||
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
||||||
import { normalizeBlueBubblesServerUrl, type BlueBubblesAccountConfig } from "./types.js";
|
import { normalizeBlueBubblesServerUrl, type BlueBubblesAccountConfig } from "./types.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||||
import { bluebubblesMessageActions } from "./actions.js";
|
import { bluebubblesMessageActions } from "./actions.js";
|
||||||
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
readStringParam,
|
readStringParam,
|
||||||
type ChannelMessageActionAdapter,
|
type ChannelMessageActionAdapter,
|
||||||
type ChannelMessageActionName,
|
type ChannelMessageActionName,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||||
import { sendBlueBubblesAttachment } from "./attachments.js";
|
import { sendBlueBubblesAttachment } from "./attachments.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import "./test-mocks.js";
|
import "./test-mocks.js";
|
||||||
import { downloadBlueBubblesAttachment, sendBlueBubblesAttachment } from "./attachments.js";
|
import { downloadBlueBubblesAttachment, sendBlueBubblesAttachment } from "./attachments.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||||
import { postMultipartFormData } from "./multipart.js";
|
import { postMultipartFormData } from "./multipart.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import type { ChannelAccountSnapshot, ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
|
import type {
|
||||||
|
ChannelAccountSnapshot,
|
||||||
|
ChannelPlugin,
|
||||||
|
OpenClawConfig,
|
||||||
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import {
|
import {
|
||||||
applyAccountNameToChannelSection,
|
applyAccountNameToChannelSection,
|
||||||
buildChannelConfigSchema,
|
buildChannelConfigSchema,
|
||||||
@ -13,7 +17,7 @@ import {
|
|||||||
resolveBlueBubblesGroupRequireMention,
|
resolveBlueBubblesGroupRequireMention,
|
||||||
resolveBlueBubblesGroupToolPolicy,
|
resolveBlueBubblesGroupToolPolicy,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import {
|
import {
|
||||||
listBlueBubblesAccountIds,
|
listBlueBubblesAccountIds,
|
||||||
type ResolvedBlueBubblesAccount,
|
type ResolvedBlueBubblesAccount,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||||
import { postMultipartFormData } from "./multipart.js";
|
import { postMultipartFormData } from "./multipart.js";
|
||||||
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk";
|
import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { buildSecretInputSchema, hasConfiguredSecretInput } from "./secret-input.js";
|
import { buildSecretInputSchema, hasConfiguredSecretInput } from "./secret-input.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||||
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { sendBlueBubblesMedia } from "./media-send.js";
|
import { sendBlueBubblesMedia } from "./media-send.js";
|
||||||
import { setBlueBubblesRuntime } from "./runtime.js";
|
import { setBlueBubblesRuntime } from "./runtime.js";
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { resolveChannelMediaMaxBytes, type OpenClawConfig } from "openclaw/plugin-sdk";
|
import { resolveChannelMediaMaxBytes, type OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||||
import { sendBlueBubblesAttachment } from "./attachments.js";
|
import { sendBlueBubblesAttachment } from "./attachments.js";
|
||||||
import { resolveBlueBubblesMessageId } from "./monitor.js";
|
import { resolveBlueBubblesMessageId } from "./monitor.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import type { NormalizedWebhookMessage } from "./monitor-normalize.js";
|
import type { NormalizedWebhookMessage } from "./monitor-normalize.js";
|
||||||
import type { BlueBubblesCoreRuntime, WebhookTarget } from "./monitor-shared.js";
|
import type { BlueBubblesCoreRuntime, WebhookTarget } from "./monitor-shared.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import {
|
import {
|
||||||
DM_GROUP_ACCESS_REASON,
|
DM_GROUP_ACCESS_REASON,
|
||||||
createScopedPairingAccess,
|
createScopedPairingAccess,
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
resolveControlCommandGate,
|
resolveControlCommandGate,
|
||||||
stripMarkdown,
|
stripMarkdown,
|
||||||
type HistoryEntry,
|
type HistoryEntry,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { downloadBlueBubblesAttachment } from "./attachments.js";
|
import { downloadBlueBubblesAttachment } from "./attachments.js";
|
||||||
import { markBlueBubblesChatRead, sendBlueBubblesTyping } from "./chat.js";
|
import { markBlueBubblesChatRead, sendBlueBubblesTyping } from "./chat.js";
|
||||||
import { fetchBlueBubblesHistory } from "./history.js";
|
import { fetchBlueBubblesHistory } from "./history.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { normalizeWebhookPath, type OpenClawConfig } from "openclaw/plugin-sdk";
|
import { normalizeWebhookPath, type OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||||
import { getBlueBubblesRuntime } from "./runtime.js";
|
import { getBlueBubblesRuntime } from "./runtime.js";
|
||||||
import type { BlueBubblesAccountConfig } from "./types.js";
|
import type { BlueBubblesAccountConfig } from "./types.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
||||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
readWebhookBodyOrReject,
|
readWebhookBodyOrReject,
|
||||||
resolveWebhookTargetWithAuthOrRejectSync,
|
resolveWebhookTargetWithAuthOrRejectSync,
|
||||||
resolveWebhookTargets,
|
resolveWebhookTargets,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
|
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
|
||||||
import { normalizeWebhookMessage, normalizeWebhookReaction } from "./monitor-normalize.js";
|
import { normalizeWebhookMessage, normalizeWebhookReaction } from "./monitor-normalize.js";
|
||||||
import { logVerbose, processMessage, processReaction } from "./monitor-processing.js";
|
import { logVerbose, processMessage, processReaction } from "./monitor-processing.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
||||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { createEmptyPluginRegistry } from "../../../src/plugins/registry.js";
|
import { createEmptyPluginRegistry } from "../../../src/plugins/registry.js";
|
||||||
import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
|
import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { WizardPrompter } from "openclaw/plugin-sdk";
|
import type { WizardPrompter } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk", () => ({
|
vi.mock("openclaw/plugin-sdk/bluebubbles", () => ({
|
||||||
DEFAULT_ACCOUNT_ID: "default",
|
DEFAULT_ACCOUNT_ID: "default",
|
||||||
addWildcardAllowFrom: vi.fn(),
|
addWildcardAllowFrom: vi.fn(),
|
||||||
formatDocsLink: (_url: string, fallback: string) => fallback,
|
formatDocsLink: (_url: string, fallback: string) => fallback,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
OpenClawConfig,
|
OpenClawConfig,
|
||||||
DmPolicy,
|
DmPolicy,
|
||||||
WizardPrompter,
|
WizardPrompter,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import {
|
import {
|
||||||
DEFAULT_ACCOUNT_ID,
|
DEFAULT_ACCOUNT_ID,
|
||||||
addWildcardAllowFrom,
|
addWildcardAllowFrom,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
mergeAllowFromEntries,
|
mergeAllowFromEntries,
|
||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
promptAccountId,
|
promptAccountId,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import {
|
import {
|
||||||
listBlueBubblesAccountIds,
|
listBlueBubblesAccountIds,
|
||||||
resolveBlueBubblesAccount,
|
resolveBlueBubblesAccount,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BaseProbeResult } from "openclaw/plugin-sdk";
|
import type { BaseProbeResult } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { normalizeSecretInputString } from "./secret-input.js";
|
import { normalizeSecretInputString } from "./secret-input.js";
|
||||||
import { buildBlueBubblesApiUrl, blueBubblesFetchWithTimeout } from "./types.js";
|
import { buildBlueBubblesApiUrl, blueBubblesFetchWithTimeout } from "./types.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||||
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
||||||
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
|
|
||||||
let runtime: PluginRuntime | null = null;
|
let runtime: PluginRuntime | null = null;
|
||||||
type LegacyRuntimeLogShape = { log?: (message: string) => void };
|
type LegacyRuntimeLogShape = { log?: (message: string) => void };
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {
|
|||||||
hasConfiguredSecretInput,
|
hasConfiguredSecretInput,
|
||||||
normalizeResolvedSecretInputString,
|
normalizeResolvedSecretInputString,
|
||||||
normalizeSecretInputString,
|
normalizeSecretInputString,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
|
export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import "./test-mocks.js";
|
import "./test-mocks.js";
|
||||||
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { stripMarkdown } from "openclaw/plugin-sdk";
|
import { stripMarkdown } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||||
import {
|
import {
|
||||||
getCachedBlueBubblesPrivateApiStatus,
|
getCachedBlueBubblesPrivateApiStatus,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
type ParsedChatTarget,
|
type ParsedChatTarget,
|
||||||
resolveServicePrefixedAllowTarget,
|
resolveServicePrefixedAllowTarget,
|
||||||
resolveServicePrefixedTarget,
|
resolveServicePrefixedTarget,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/bluebubbles";
|
||||||
|
|
||||||
export type BlueBubblesService = "imessage" | "sms" | "auto";
|
export type BlueBubblesService = "imessage" | "sms" | "auto";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
|
|
||||||
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/bluebubbles";
|
||||||
|
|
||||||
export type BlueBubblesGroupConfig = {
|
export type BlueBubblesGroupConfig = {
|
||||||
/** If true, only respond in this group when mentioned. */
|
/** If true, only respond in this group when mentioned. */
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
type OpenClawPluginApi,
|
type OpenClawPluginApi,
|
||||||
type ProviderAuthContext,
|
type ProviderAuthContext,
|
||||||
type ProviderAuthResult,
|
type ProviderAuthResult,
|
||||||
} from "openclaw/plugin-sdk/core";
|
} from "openclaw/plugin-sdk/copilot-proxy";
|
||||||
|
|
||||||
const DEFAULT_BASE_URL = "http://localhost:3000/v1";
|
const DEFAULT_BASE_URL = "http://localhost:3000/v1";
|
||||||
const DEFAULT_API_KEY = "n/a";
|
const DEFAULT_API_KEY = "n/a";
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/device-pair";
|
||||||
import {
|
import {
|
||||||
approveDevicePairing,
|
approveDevicePairing,
|
||||||
listDevicePairing,
|
listDevicePairing,
|
||||||
resolveGatewayBindUrl,
|
resolveGatewayBindUrl,
|
||||||
runPluginCommandWithTimeout,
|
runPluginCommandWithTimeout,
|
||||||
resolveTailnetHostWithRunner,
|
resolveTailnetHostWithRunner,
|
||||||
} from "openclaw/plugin-sdk/core";
|
} from "openclaw/plugin-sdk/device-pair";
|
||||||
import qrcode from "qrcode-terminal";
|
import qrcode from "qrcode-terminal";
|
||||||
import {
|
import {
|
||||||
armPairNotifyOnce,
|
armPairNotifyOnce,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { promises as fs } from "node:fs";
|
import { promises as fs } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/device-pair";
|
||||||
import { listDevicePairing } from "openclaw/plugin-sdk/core";
|
import { listDevicePairing } from "openclaw/plugin-sdk/device-pair";
|
||||||
|
|
||||||
const NOTIFY_STATE_FILE = "device-pair-notify.json";
|
const NOTIFY_STATE_FILE = "device-pair-notify.json";
|
||||||
const NOTIFY_POLL_INTERVAL_MS = 10_000;
|
const NOTIFY_POLL_INTERVAL_MS = 10_000;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
import { createDiagnosticsOtelService } from "./src/service.js";
|
import { createDiagnosticsOtelService } from "./src/service.js";
|
||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
|
|||||||
@ -98,16 +98,18 @@ vi.mock("@opentelemetry/semantic-conventions", () => ({
|
|||||||
ATTR_SERVICE_NAME: "service.name",
|
ATTR_SERVICE_NAME: "service.name",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk", async () => {
|
vi.mock("openclaw/plugin-sdk/diagnostics-otel", async () => {
|
||||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk")>("openclaw/plugin-sdk");
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/diagnostics-otel")>(
|
||||||
|
"openclaw/plugin-sdk/diagnostics-otel",
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
registerLogTransport: registerLogTransportMock,
|
registerLogTransport: registerLogTransportMock,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
import type { OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginServiceContext } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
import { emitDiagnosticEvent } from "openclaw/plugin-sdk";
|
import { emitDiagnosticEvent } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
import { createDiagnosticsOtelService } from "./service.js";
|
import { createDiagnosticsOtelService } from "./service.js";
|
||||||
|
|
||||||
const OTEL_TEST_STATE_DIR = "/tmp/openclaw-diagnostics-otel-test";
|
const OTEL_TEST_STATE_DIR = "/tmp/openclaw-diagnostics-otel-test";
|
||||||
|
|||||||
@ -9,8 +9,15 @@ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|||||||
import { NodeSDK } from "@opentelemetry/sdk-node";
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
||||||
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
||||||
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
||||||
import type { DiagnosticEventPayload, OpenClawPluginService } from "openclaw/plugin-sdk";
|
import type {
|
||||||
import { onDiagnosticEvent, redactSensitiveText, registerLogTransport } from "openclaw/plugin-sdk";
|
DiagnosticEventPayload,
|
||||||
|
OpenClawPluginService,
|
||||||
|
} from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
|
import {
|
||||||
|
onDiagnosticEvent,
|
||||||
|
redactSensitiveText,
|
||||||
|
registerLogTransport,
|
||||||
|
} from "openclaw/plugin-sdk/diagnostics-otel";
|
||||||
|
|
||||||
const DEFAULT_SERVICE_NAME = "openclaw";
|
const DEFAULT_SERVICE_NAME = "openclaw";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diffs";
|
||||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/core";
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/diffs";
|
||||||
import {
|
import {
|
||||||
diffsPluginConfigSchema,
|
diffsPluginConfigSchema,
|
||||||
resolveDiffsPluginDefaults,
|
resolveDiffsPluginDefaults,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/diffs";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const { launchMock } = vi.hoisted(() => ({
|
const { launchMock } = vi.hoisted(() => ({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { constants as fsConstants } from "node:fs";
|
import { constants as fsConstants } from "node:fs";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/diffs";
|
||||||
import { chromium } from "playwright-core";
|
import { chromium } from "playwright-core";
|
||||||
import type { DiffRenderOptions, DiffTheme } from "./types.js";
|
import type { DiffRenderOptions, DiffTheme } from "./types.js";
|
||||||
import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
|
import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/diffs";
|
||||||
import {
|
import {
|
||||||
DIFF_IMAGE_QUALITY_PRESETS,
|
DIFF_IMAGE_QUALITY_PRESETS,
|
||||||
DIFF_INDICATORS,
|
DIFF_INDICATORS,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import type { PluginLogger } from "openclaw/plugin-sdk";
|
import type { PluginLogger } from "openclaw/plugin-sdk/diffs";
|
||||||
import type { DiffArtifactStore } from "./store.js";
|
import type { DiffArtifactStore } from "./store.js";
|
||||||
import { DIFF_ARTIFACT_ID_PATTERN, DIFF_ARTIFACT_TOKEN_PATTERN } from "./types.js";
|
import { DIFF_ARTIFACT_ID_PATTERN, DIFF_ARTIFACT_TOKEN_PATTERN } from "./types.js";
|
||||||
import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
|
import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { PluginLogger } from "openclaw/plugin-sdk";
|
import type { PluginLogger } from "openclaw/plugin-sdk/diffs";
|
||||||
import type { DiffArtifactMeta, DiffOutputFormat } from "./types.js";
|
import type { DiffArtifactMeta, DiffOutputFormat } from "./types.js";
|
||||||
|
|
||||||
const DEFAULT_TTL_MS = 30 * 60 * 1000;
|
const DEFAULT_TTL_MS = 30 * 60 * 1000;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diffs";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { DiffScreenshotter } from "./browser.js";
|
import type { DiffScreenshotter } from "./browser.js";
|
||||||
import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js";
|
import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/diffs";
|
||||||
import { PlaywrightDiffScreenshotter, type DiffScreenshotter } from "./browser.js";
|
import { PlaywrightDiffScreenshotter, type DiffScreenshotter } from "./browser.js";
|
||||||
import { resolveDiffImageRenderOptions } from "./config.js";
|
import { resolveDiffImageRenderOptions } from "./config.js";
|
||||||
import { renderDiffDocument } from "./render.js";
|
import { renderDiffDocument } from "./render.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/diffs";
|
||||||
|
|
||||||
const DEFAULT_GATEWAY_PORT = 18789;
|
const DEFAULT_GATEWAY_PORT = 18789;
|
||||||
|
|
||||||
|
|||||||
@ -302,10 +302,11 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|||||||
textChunkLimit: 2000,
|
textChunkLimit: 2000,
|
||||||
pollMaxOptions: 10,
|
pollMaxOptions: 10,
|
||||||
resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
|
resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
|
||||||
sendText: async ({ to, text, accountId, deps, replyToId, silent }) => {
|
sendText: async ({ cfg, to, text, accountId, deps, replyToId, silent }) => {
|
||||||
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
||||||
const result = await send(to, text, {
|
const result = await send(to, text, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
cfg,
|
||||||
replyTo: replyToId ?? undefined,
|
replyTo: replyToId ?? undefined,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
silent: silent ?? undefined,
|
silent: silent ?? undefined,
|
||||||
@ -313,6 +314,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|||||||
return { channel: "discord", ...result };
|
return { channel: "discord", ...result };
|
||||||
},
|
},
|
||||||
sendMedia: async ({
|
sendMedia: async ({
|
||||||
|
cfg,
|
||||||
to,
|
to,
|
||||||
text,
|
text,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
@ -325,6 +327,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|||||||
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
||||||
const result = await send(to, text, {
|
const result = await send(to, text, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
cfg,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
mediaLocalRoots,
|
mediaLocalRoots,
|
||||||
replyTo: replyToId ?? undefined,
|
replyTo: replyToId ?? undefined,
|
||||||
@ -333,8 +336,9 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|||||||
});
|
});
|
||||||
return { channel: "discord", ...result };
|
return { channel: "discord", ...result };
|
||||||
},
|
},
|
||||||
sendPoll: async ({ to, poll, accountId, silent }) =>
|
sendPoll: async ({ cfg, to, poll, accountId, silent }) =>
|
||||||
await getDiscordRuntime().channel.discord.sendPollDiscord(to, poll, {
|
await getDiscordRuntime().channel.discord.sendPollDiscord(to, poll, {
|
||||||
|
cfg,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
silent: silent ?? undefined,
|
silent: silent ?? undefined,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/feishu";
|
||||||
import { registerFeishuBitableTools } from "./src/bitable.js";
|
import { registerFeishuBitableTools } from "./src/bitable.js";
|
||||||
import { feishuPlugin } from "./src/channel.js";
|
import { feishuPlugin } from "./src/channel.js";
|
||||||
import { registerFeishuChatTools } from "./src/chat.js";
|
import { registerFeishuChatTools } from "./src/chat.js";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
||||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||||
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
|
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
|
||||||
import type {
|
import type {
|
||||||
FeishuConfig,
|
FeishuConfig,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||||
import { createFeishuToolClient } from "./tool-account.js";
|
import { createFeishuToolClient } from "./tool-account.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
|
||||||
import type { FeishuMessageEvent } from "./bot.js";
|
import type { FeishuMessageEvent } from "./bot.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import {
|
import {
|
||||||
buildAgentMediaPayload,
|
buildAgentMediaPayload,
|
||||||
buildPendingHistoryContextFromMap,
|
buildPendingHistoryContextFromMap,
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
resolveOpenProviderRuntimeGroupPolicy,
|
resolveOpenProviderRuntimeGroupPolicy,
|
||||||
resolveDefaultGroupPolicy,
|
resolveDefaultGroupPolicy,
|
||||||
warnMissingProviderGroupPolicyFallbackOnce,
|
warnMissingProviderGroupPolicyFallbackOnce,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/feishu";
|
||||||
import { resolveFeishuAccount } from "./accounts.js";
|
import { resolveFeishuAccount } from "./accounts.js";
|
||||||
import { createFeishuClient } from "./client.js";
|
import { createFeishuClient } from "./client.js";
|
||||||
import { tryRecordMessage, tryRecordMessagePersistent } from "./dedup.js";
|
import { tryRecordMessage, tryRecordMessagePersistent } from "./dedup.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import { resolveFeishuAccount } from "./accounts.js";
|
import { resolveFeishuAccount } from "./accounts.js";
|
||||||
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
|
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import {
|
import {
|
||||||
buildBaseChannelStatusSummary,
|
buildBaseChannelStatusSummary,
|
||||||
createDefaultChannelRuntimeState,
|
createDefaultChannelRuntimeState,
|
||||||
@ -6,7 +6,7 @@ import {
|
|||||||
PAIRING_APPROVED_MESSAGE,
|
PAIRING_APPROVED_MESSAGE,
|
||||||
resolveAllowlistProviderRuntimeGroupPolicy,
|
resolveAllowlistProviderRuntimeGroupPolicy,
|
||||||
resolveDefaultGroupPolicy,
|
resolveDefaultGroupPolicy,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/feishu";
|
||||||
import {
|
import {
|
||||||
resolveFeishuAccount,
|
resolveFeishuAccount,
|
||||||
resolveFeishuCredentials,
|
resolveFeishuCredentials,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||||
import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js";
|
import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js";
|
||||||
import { createFeishuClient } from "./client.js";
|
import { createFeishuClient } from "./client.js";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
createDedupeCache,
|
createDedupeCache,
|
||||||
createPersistentDedupe,
|
createPersistentDedupe,
|
||||||
readJsonFileWithFallback,
|
readJsonFileWithFallback,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/feishu";
|
||||||
|
|
||||||
// Persistent TTL: 24 hours — survives restarts & WebSocket reconnects.
|
// Persistent TTL: 24 hours — survives restarts & WebSocket reconnects.
|
||||||
const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { resolveFeishuAccount } from "./accounts.js";
|
import { resolveFeishuAccount } from "./accounts.js";
|
||||||
import { createFeishuClient } from "./client.js";
|
import { createFeishuClient } from "./client.js";
|
||||||
import { normalizeFeishuTarget } from "./targets.js";
|
import { normalizeFeishuTarget } from "./targets.js";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { describe, expect, test, vi } from "vitest";
|
import { describe, expect, test, vi } from "vitest";
|
||||||
import { registerFeishuDocTools } from "./docx.js";
|
import { registerFeishuDocTools } from "./docx.js";
|
||||||
import { createToolFactoryHarness } from "./tool-factory-test-harness.js";
|
import { createToolFactoryHarness } from "./tool-factory-test-harness.js";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { isAbsolute } from "node:path";
|
|||||||
import { basename } from "node:path";
|
import { basename } from "node:path";
|
||||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||||
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
||||||
import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js";
|
import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
||||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||||
import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js";
|
import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js";
|
||||||
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
|
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/feishu";
|
||||||
import type { DynamicAgentCreationConfig } from "./types.js";
|
import type { DynamicAgentCreationConfig } from "./types.js";
|
||||||
|
|
||||||
export type MaybeCreateDynamicAgentResult = {
|
export type MaybeCreateDynamicAgentResult = {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import { withTempDownloadPath, type ClawdbotConfig } from "openclaw/plugin-sdk";
|
import { withTempDownloadPath, type ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { resolveFeishuAccount } from "./accounts.js";
|
import { resolveFeishuAccount } from "./accounts.js";
|
||||||
import { createFeishuClient } from "./client.js";
|
import { createFeishuClient } from "./client.js";
|
||||||
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import * as Lark from "@larksuiteoapi/node-sdk";
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk/feishu";
|
||||||
import { resolveFeishuAccount } from "./accounts.js";
|
import { resolveFeishuAccount } from "./accounts.js";
|
||||||
import { raceWithTimeoutAndAbort } from "./async.js";
|
import { raceWithTimeoutAndAbort } from "./async.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { hasControlCommand } from "../../../src/auto-reply/command-detection.js";
|
import { hasControlCommand } from "../../../src/auto-reply/command-detection.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
|
import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import { probeFeishu } from "./probe.js";
|
import { probeFeishu } from "./probe.js";
|
||||||
import type { ResolvedFeishuAccount } from "./types.js";
|
import type { ResolvedFeishuAccount } from "./types.js";
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
type RuntimeEnv,
|
type RuntimeEnv,
|
||||||
WEBHOOK_ANOMALY_COUNTER_DEFAULTS as WEBHOOK_ANOMALY_COUNTER_DEFAULTS_FROM_SDK,
|
WEBHOOK_ANOMALY_COUNTER_DEFAULTS as WEBHOOK_ANOMALY_COUNTER_DEFAULTS_FROM_SDK,
|
||||||
WEBHOOK_RATE_LIMIT_DEFAULTS as WEBHOOK_RATE_LIMIT_DEFAULTS_FROM_SDK,
|
WEBHOOK_RATE_LIMIT_DEFAULTS as WEBHOOK_RATE_LIMIT_DEFAULTS_FROM_SDK,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/feishu";
|
||||||
|
|
||||||
export const wsClients = new Map<string, Lark.WSClient>();
|
export const wsClients = new Map<string, Lark.WSClient>();
|
||||||
export const httpServers = new Map<string, http.Server>();
|
export const httpServers = new Map<string, http.Server>();
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
applyBasicWebhookRequestGuards,
|
applyBasicWebhookRequestGuards,
|
||||||
type RuntimeEnv,
|
type RuntimeEnv,
|
||||||
installRequestBodyLimitGuard,
|
installRequestBodyLimitGuard,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk/feishu";
|
||||||
import { createFeishuWSClient } from "./client.js";
|
import { createFeishuWSClient } from "./client.js";
|
||||||
import {
|
import {
|
||||||
botOpenIds,
|
botOpenIds,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||||
import { listEnabledFeishuAccounts, resolveFeishuAccount } from "./accounts.js";
|
import { listEnabledFeishuAccounts, resolveFeishuAccount } from "./accounts.js";
|
||||||
import {
|
import {
|
||||||
monitorSingleAccount,
|
monitorSingleAccount,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import type { AddressInfo } from "node:net";
|
import type { AddressInfo } from "node:net";
|
||||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/feishu";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { feishuOnboardingAdapter } from "./onboarding.js";
|
import { feishuOnboardingAdapter } from "./onboarding.js";
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user