From 26fa98c359a2411b418efcb272bfb0350baa1a7c Mon Sep 17 00:00:00 2001 From: Dimitrij Denissenko Date: Fri, 13 Mar 2026 13:17:38 +0000 Subject: [PATCH 01/55] Align rendering with Matrix' CommonMark guidelines --- pkg/channels/matrix/matrix.go | 5 +- pkg/channels/matrix/matrix_test.go | 101 +++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/pkg/channels/matrix/matrix.go b/pkg/channels/matrix/matrix.go index bec5dfdac..71561fcc6 100644 --- a/pkg/channels/matrix/matrix.go +++ b/pkg/channels/matrix/matrix.go @@ -273,8 +273,9 @@ func (c *MatrixChannel) Stop(ctx context.Context) error { } func markdownToHTML(md string) string { - p := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs) - renderer := mdhtml.NewRenderer(mdhtml.RendererOptions{Flags: mdhtml.CommonFlags}) + extensions := (parser.CommonExtensions | parser.NoEmptyLineBeforeBlock) &^ parser.DefinitionLists + p := parser.NewWithExtensions(extensions) + renderer := mdhtml.NewRenderer(mdhtml.RendererOptions{Flags: mdhtml.UseXHTML}) return strings.TrimSpace(string(markdown.ToHTML([]byte(md), p, renderer))) } diff --git a/pkg/channels/matrix/matrix_test.go b/pkg/channels/matrix/matrix_test.go index 07a35c021..1331ed22c 100644 --- a/pkg/channels/matrix/matrix_test.go +++ b/pkg/channels/matrix/matrix_test.go @@ -340,23 +340,96 @@ func TestMatrixOutboundContent(t *testing.T) { } func TestMarkdownToHTML(t *testing.T) { - tests := []struct { + cases := []struct { name string - input string - contains string + md string + rendered string }{ - {"bold", "**hello**", "hello"}, - {"italic", "_world_", "world"}, - {"header", "### Title", ""}, - {"inline code", "`x`", "x"}, - {"plain text", "just text", "just text"}, + { + name: "paragraph", + md: "just **some** text with _custom_ formatting and `inline` code", + rendered: "

just some text with custom formatting and inline code

", + }, + { + name: "heading", + md: "### Title", + rendered: `

Title

`, + }, + { + name: "fenced code block", + md: "```\nfoo()\n```", + rendered: "
foo()\n
", + }, + { + name: "loose list", + md: "- Item one\n\n- Item two\n", + rendered: ``, + }, + { + name: "tight list", + md: "- Alpha\n- Beta\n", + rendered: ``, + }, + { + name: "list item with nested sublist", + md: "1. Steps overview:\n\n - Step A\n - Step B\n", + rendered: `
    +
  1. Steps overview:

    + +
      +
    • Step A
    • +
    • Step B
    • +
  2. +
`, + }, + { + // Definition list syntax is not enabled; the term and definition are + // rendered as a plain paragraph rather than
/
/
elements. + name: "definition list syntax renders as plain paragraph", + md: "Term\n: Definition of the term.\n", + rendered: "

Term\n: Definition of the term.

", + }, + { + name: "comprehensive document with headings, paragraphs, list, and code block", + md: "# Overview\n\nThis is a sample document designed to demonstrate various Markdown elements in a single block of text.\n\nThe first paragraph introduces the concept of structured data.\n\n## Details\n\nThe following is a list:\n\n* First\n* Second\n* Third\n\nThe second paragraph focuses on details. Below is a generic code snippet:\n\n```python\ndef calculate_area(radius):\n import math\n return math.pi * (radius ** 2)\n```\n\nThis concludes the generic sample text.\n", + rendered: `

Overview

+ +

This is a sample document designed to demonstrate various Markdown elements in a single block of text.

+ +

The first paragraph introduces the concept of structured data.

+ +

Details

+ +

The following is a list:

+ +
    +
  • First
  • +
  • Second
  • +
  • Third
  • +
+ +

The second paragraph focuses on details. Below is a generic code snippet:

+ +
def calculate_area(radius):
+    import math
+    return math.pi * (radius ** 2)
+
+ +

This concludes the generic sample text.

`, + }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := markdownToHTML(tt.input) - if !strings.Contains(got, tt.contains) { - t.Fatalf("markdownToHTML(%q) = %q, want it to contain %q", tt.input, got, tt.contains) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := markdownToHTML(tc.md); got != tc.rendered { + t.Fatalf("markdownToHTML(%q)\n got: %q\nwant: %q", tc.md, got, tc.rendered) } }) } From d08bb02f8fe5092ff02469d10d372f897240504b Mon Sep 17 00:00:00 2001 From: Christoforus Surjoputro Date: Fri, 20 Mar 2026 15:35:18 +0700 Subject: [PATCH 02/55] update restart policy to unless-stopped --- docker/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b26cf4199..0bf46a2ae 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,7 +24,7 @@ services: picoclaw-gateway: image: docker.io/sipeed/picoclaw:latest container_name: picoclaw-gateway - restart: on-failure + restart: unless-stopped profiles: - gateway # Uncomment to access host network; leave commented unless needed. @@ -40,7 +40,7 @@ services: picoclaw-launcher: image: docker.io/sipeed/picoclaw:launcher container_name: picoclaw-launcher - restart: on-failure + restart: unless-stopped profiles: - launcher environment: From ba1538f31d72acd50d1e1acc3bd69187712e9e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Minh=20=C4=90=E1=BA=A1t?= Date: Fri, 20 Mar 2026 21:48:29 +0700 Subject: [PATCH 03/55] Fix: cannot create session github copilot --- pkg/providers/github_copilot_provider.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go index 6d642b2b5..9e2089219 100644 --- a/pkg/providers/github_copilot_provider.go +++ b/pkg/providers/github_copilot_provider.go @@ -41,8 +41,9 @@ func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*Gi } session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ - Model: model, - Hooks: &copilot.SessionHooks{}, + Model: model,, + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Hooks: &copilot.SessionHooks{} }) if err != nil { client.Stop() From efbe80691305dbc66f1881a4e103d89f2cd8175e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Minh=20=C4=90=E1=BA=A1t?= Date: Fri, 20 Mar 2026 22:15:13 +0700 Subject: [PATCH 04/55] Fix bug double , --- pkg/providers/github_copilot_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go index 9e2089219..8748fd384 100644 --- a/pkg/providers/github_copilot_provider.go +++ b/pkg/providers/github_copilot_provider.go @@ -41,7 +41,7 @@ func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*Gi } session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ - Model: model,, + Model: model, OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Hooks: &copilot.SessionHooks{} }) From e4b104c0de1a259bf0946418dd91003ffe0e66ca Mon Sep 17 00:00:00 2001 From: "enoch.z" Date: Fri, 20 Mar 2026 23:15:18 +0800 Subject: [PATCH 05/55] Update helpers.go correction of the promt for the "picoclaw onboard" command. --- cmd/picoclaw/internal/onboard/helpers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/picoclaw/internal/onboard/helpers.go b/cmd/picoclaw/internal/onboard/helpers.go index 6f1d4bdd7..626698fec 100644 --- a/cmd/picoclaw/internal/onboard/helpers.go +++ b/cmd/picoclaw/internal/onboard/helpers.go @@ -97,7 +97,11 @@ func onboard(encrypt bool) { fmt.Println("") fmt.Println(" See README.md for 17+ supported providers.") fmt.Println("") - fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"") + if encrypt { + fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"") + } else { + fmt.Println(" 2. Chat: picoclaw agent -m \"Hello!\"") + } } // promptPassphrase reads the encryption passphrase twice from the terminal From 8f56cceb078a37c55bdf63215ba9c6ae3705ad10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Minh=20=C4=90=E1=BA=A1t?= Date: Fri, 20 Mar 2026 22:17:17 +0700 Subject: [PATCH 06/55] missing , --- pkg/providers/github_copilot_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go index 8748fd384..e2d1d7d98 100644 --- a/pkg/providers/github_copilot_provider.go +++ b/pkg/providers/github_copilot_provider.go @@ -43,7 +43,7 @@ func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*Gi session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ Model: model, OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Hooks: &copilot.SessionHooks{} + Hooks: &copilot.SessionHooks{}, }) if err != nil { client.Stop() From ae021ef843f71ee4e3fc4a85b456912ce9c45350 Mon Sep 17 00:00:00 2001 From: kathent Date: Wed, 25 Mar 2026 10:14:16 +0800 Subject: [PATCH 07/55] fix: more accurate deny pattern for disk wiping --- pkg/tools/shell.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index 78ad2b26d..24e2ea1cf 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -35,7 +35,7 @@ var ( regexp.MustCompile(`\brmdir\s+/s\b`), // Match disk wiping commands (must be followed by space/args) regexp.MustCompile( - `\b(format|mkfs|diskpart)\b\s`, + `(^|[^-\w])\b(format|mkfs|diskpart)\b\s`, ), regexp.MustCompile(`\bdd\s+if=`), // Block writes to block devices (all common naming schemes). From f30f57bfabc2d64bd280e28fe5949a2bdf0e958c Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:28:04 +0800 Subject: [PATCH 08/55] docs: add cron job behavior guide --- README.md | 3 + docs/cron.md | 125 ++++++++++++++++++++++++++++++++++++ docs/tools_configuration.md | 5 +- 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 docs/cron.md diff --git a/README.md b/README.md index f627e261e..15aa13ab3 100644 --- a/README.md +++ b/README.md @@ -550,6 +550,8 @@ PicoClaw supports scheduled reminders and recurring tasks through the `cron` too * **Recurring tasks**: "Remind me every 2 hours" -> triggers every 2 hours * **Cron expressions**: "Remind me at 9am daily" -> uses cron expression +See [docs/cron.md](docs/cron.md) for current schedule types, execution modes, command-job gates, and persistence details. + ## 📚 Documentation For detailed guides beyond this README: @@ -559,6 +561,7 @@ For detailed guides beyond this README: | [Docker & Quick Start](docs/docker.md) | Docker Compose setup, Launcher/Agent modes | | [Chat Apps](docs/chat-apps.md) | All 17+ channel setup guides | | [Configuration](docs/configuration.md) | Environment variables, workspace layout, security sandbox | +| [Scheduled Tasks and Cron Jobs](docs/cron.md) | Cron schedule types, deliver modes, command gates, job storage | | [Providers & Models](docs/providers.md) | 30+ LLM providers, model routing, model_list configuration | | [Spawn & Async Tasks](docs/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration | | [Hooks](docs/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks | diff --git a/docs/cron.md b/docs/cron.md new file mode 100644 index 000000000..2ce888df9 --- /dev/null +++ b/docs/cron.md @@ -0,0 +1,125 @@ +# Scheduled Tasks and Cron Jobs + +> Back to [README](../README.md) + +PicoClaw stores scheduled jobs in the current workspace and can run them either as reminders, full agent turns, or shell commands. + +## Schedule Types + +PicoClaw currently uses three schedule forms in the cron tool: + +- `at_seconds`: one-time job, relative to now. After it runs, the job is removed from the store. +- `every_seconds`: recurring interval, in seconds. +- `cron_expr`: recurring cron expression such as `0 9 * * *`. + +The CLI command `picoclaw cron add` currently supports recurring jobs only: + +- `--every ` +- `--cron ''` + +There is no CLI flag for a one-time `at` job today. + +Examples: + +```bash +picoclaw cron add --name "Daily summary" --message "Summarize today's logs" --cron "0 18 * * *" +picoclaw cron add --name "Ping" --message "heartbeat" --every 300 --deliver +``` + +## Execution Modes + +Jobs are stored with a message payload and can execute in three stable user-facing modes: + +### `deliver: false` + +This is the default for the cron tool. + +When the job fires, PicoClaw sends the saved message back through the agent loop as a new agent turn. Use this for scheduled work that may need reasoning, tools, or a generated reply. + +### `deliver: true` + +When the job fires, PicoClaw publishes the saved message directly to the target channel and recipient without agent processing. + +The CLI `picoclaw cron add --deliver` flag uses this mode. + +### `command` + +When a cron-tool job includes `command`, PicoClaw runs that shell command through the `exec` tool and publishes the command output back to the channel. + +For command jobs, `deliver` is forced to `false` when the job is created. The saved `message` becomes descriptive text only; the scheduled action is the shell command. + +The current CLI `picoclaw cron add` command does not expose a `command` flag. + +## Config and Security Gates + +### `tools.cron` + +`tools.cron.enabled` controls whether the agent-facing `cron` tool is registered. Default: `true`. + +If you disable `tools.cron`, users can no longer create or manage jobs through the agent tool. The gateway still starts the cron service and the CLI still uses the same job store, so keep `tools.cron.enabled` on if you expect scheduled agent-turn or command jobs to execute through the gateway. + +`tools.cron.exec_timeout_minutes` sets the timeout used for scheduled command execution. Default: `5`. Set `0` for no timeout. + +### `tools.exec` + +Scheduled command jobs depend on `tools.exec.enabled`. Default: `true`. + +If `tools.exec.enabled` is `false`: + +- new command jobs are rejected by the cron tool +- existing command jobs publish a `command execution is disabled` error when they fire + +`tools.exec.allow_remote` is still enforced by the exec tool, but cron command scheduling already requires an internal channel when the job is created. In practice, reminder jobs can be scheduled from remote channels, while scheduled command jobs are limited to internal channels. + +### `allow_command` + +`tools.cron.allow_command` defaults to `true`. + +This is not a hard disable switch. If you set `allow_command` to `false`, PicoClaw still allows a command job when the caller explicitly passes `command_confirm: true`. + +Command jobs also require an internal channel. Non-command reminders do not have that restriction. + +Example: + +```json +{ + "tools": { + "cron": { + "enabled": true, + "exec_timeout_minutes": 5, + "allow_command": true + }, + "exec": { + "enabled": true + } + } +} +``` + +## Persistence and Location + +Cron jobs are stored in: + +```text +/cron/jobs.json +``` + +By default, the workspace is: + +```text +~/.picoclaw/workspace +``` + +If `PICOCLAW_HOME` is set, the default workspace becomes: + +```text +$PICOCLAW_HOME/workspace +``` + +Both the gateway and `picoclaw cron` CLI subcommands use the same `cron/jobs.json` file. + +Notes: + +- one-time `at_seconds` jobs are deleted after they run +- recurring jobs stay in the store until removed +- disabled jobs stay in the store and still appear in `picoclaw cron list` diff --git a/docs/tools_configuration.md b/docs/tools_configuration.md index b5907b991..3380a0a67 100644 --- a/docs/tools_configuration.md +++ b/docs/tools_configuration.md @@ -226,8 +226,11 @@ The cron tool is used for scheduling periodic tasks. | Config | Type | Default | Description | |------------------------|------|---------|------------------------------------------------| +| `enabled` | bool | true | Register the agent-facing cron tool | +| `allow_command` | bool | true | Allow command jobs without extra confirmation | | `exec_timeout_minutes` | int | 5 | Execution timeout in minutes, 0 means no limit | -| `allow_command` | bool | false | Allow cron tasks to execute shell commands | + +For schedule types, execution modes (`deliver`, agent turn, and command jobs), persistence, and the current command-security gates, see [Scheduled Tasks and Cron Jobs](cron.md). ## MCP Tool From 59babde0cf99610817eda0cbaa55d3d2b24bcb33 Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:34:20 +0800 Subject: [PATCH 09/55] docs: clarify cron disabled execution behavior --- docs/cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cron.md b/docs/cron.md index 2ce888df9..6483fa137 100644 --- a/docs/cron.md +++ b/docs/cron.md @@ -56,7 +56,7 @@ The current CLI `picoclaw cron add` command does not expose a `command` flag. `tools.cron.enabled` controls whether the agent-facing `cron` tool is registered. Default: `true`. -If you disable `tools.cron`, users can no longer create or manage jobs through the agent tool. The gateway still starts the cron service and the CLI still uses the same job store, so keep `tools.cron.enabled` on if you expect scheduled agent-turn or command jobs to execute through the gateway. +If you disable `tools.cron`, users can no longer create or manage jobs through the agent tool. The gateway still starts `CronService`, but it does not install the job execution callback. As a result, due jobs do not actually run; one-time jobs may be deleted and recurring jobs may be rescheduled without executing their payload. The CLI still uses the same job store. `tools.cron.exec_timeout_minutes` sets the timeout used for scheduled command execution. Default: `5`. Set `0` for no timeout. From 6b1d08f4541a4a2355a302c166388da769a1b262 Mon Sep 17 00:00:00 2001 From: Huangting-xy <266665499+Huangting-xy@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:20:54 +0800 Subject: [PATCH 10/55] docs(configuration): add security config reference at document start Add a prominent reference to security_configuration.md at the beginning of the configuration guide. This helps new users quickly find information about storing API keys in .security.yml. Fixes #1986 Co-Authored-By: Claude Opus 4.6 --- docs/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index d39806887..c2a1e123c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,6 +6,8 @@ Config file: `~/.picoclaw/config.json` +> **Security Configuration:** For storing API keys, tokens, and other sensitive data, see the [Security Configuration Guide](security_configuration.md). + ### Environment Variables You can override default paths using environment variables. This is useful for portable installations, containerized deployments, or running picoclaw as a system service. These variables are independent and control different paths. From 8d5fc736d6416967245c9603d784583bbeab6701 Mon Sep 17 00:00:00 2001 From: Badgerbees Date: Fri, 27 Mar 2026 20:04:21 +0700 Subject: [PATCH 11/55] security: add open-by-default warning and '*' allow_from support --- pkg/channels/base.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/channels/base.go b/pkg/channels/base.go index 882e72d08..f6ea691e7 100644 --- a/pkg/channels/base.go +++ b/pkg/channels/base.go @@ -112,6 +112,18 @@ func NewBaseChannel( for _, opt := range opts { opt(bc) } + + // Security Audit: Check for open-by-default (unsecured) channels. + // PicoClaw aims to be secure-by-default. If allow_from is empty, the bot + // currently defaults to accepting messages from ANYONE. To explicitly + // acknowledge and permit this (e.g. for a public bot), use ["*"]. + if len(bc.allowList) == 0 { + logger.WarnCF("channels", "SECURITY: Channel allows EVERYONE (allow_from is empty)", map[string]any{ + "channel": bc.name, + "hint": "Set allow_from to your ID, or use '*' to explicitly acknowledge open access.", + }) + } + return bc } @@ -187,6 +199,9 @@ func (c *BaseChannel) IsAllowed(senderID string) bool { } for _, allowed := range c.allowList { + if allowed == "*" { + return true + } // Strip leading "@" from allowed value for username matching trimmed := strings.TrimPrefix(allowed, "@") allowedID := trimmed @@ -221,7 +236,7 @@ func (c *BaseChannel) IsAllowedSender(sender bus.SenderInfo) bool { } for _, allowed := range c.allowList { - if identity.MatchAllowed(sender, allowed) { + if allowed == "*" || identity.MatchAllowed(sender, allowed) { return true } } From 5d0cf36a180a65317aa2fe02db84ef8752fdd3c5 Mon Sep 17 00:00:00 2001 From: BeaconCat Date: Sat, 28 Mar 2026 17:29:16 +0800 Subject: [PATCH 12/55] docs: add macOS Gatekeeper bypass guide to WebUI Launcher section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a collapsible macOS security warning guide under the WebUI Launcher section in all 8 README languages (en/zh/fr/ja/pt-br/vi/id/it). - New assets: macos-gatekeeper-warning.jpg, macos-gatekeeper-allow.jpg - Updated asset: launcher-tui.jpg - Two-step guide: shows Gatekeeper warning screenshot, then Privacy & Security → Open Anyway flow Co-Authored-By: Claude Opus 4.6 --- README.fr.md | 23 +++++++++++++++++++++++ README.id.md | 23 +++++++++++++++++++++++ README.it.md | 23 +++++++++++++++++++++++ README.ja.md | 23 +++++++++++++++++++++++ README.md | 23 +++++++++++++++++++++++ README.pt-br.md | 23 +++++++++++++++++++++++ README.vi.md | 23 +++++++++++++++++++++++ README.zh.md | 23 +++++++++++++++++++++++ assets/launcher-tui.jpg | Bin 104522 -> 277431 bytes assets/macos-gatekeeper-allow.jpg | Bin 0 -> 459540 bytes assets/macos-gatekeeper-warning.jpg | Bin 0 -> 195256 bytes 11 files changed, 184 insertions(+) create mode 100644 assets/macos-gatekeeper-allow.jpg create mode 100644 assets/macos-gatekeeper-warning.jpg diff --git a/README.fr.md b/README.fr.md index a4fa628c9..727f67c2c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -257,6 +257,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — Avertissement de sécurité au premier lancement + +macOS peut bloquer `picoclaw-launcher` au premier lancement car il est téléchargé depuis Internet et n'est pas notarisé via le Mac App Store. + +**Étape 1 :** Double-cliquez sur `picoclaw-launcher`. Un avertissement de sécurité s'affiche : + +

+Avertissement macOS Gatekeeper +

+ +> *"picoclaw-launcher" n'a pas pu être ouvert — Apple n'a pas pu vérifier que "picoclaw-launcher" ne contient pas de logiciel malveillant susceptible de nuire à votre Mac ou de compromettre votre confidentialité.* + +**Étape 2 :** Ouvrez **Réglages Système** → **Confidentialité et sécurité** → faites défiler jusqu'à la section **Sécurité** → cliquez sur **Ouvrir quand même** → confirmez en cliquant sur **Ouvrir quand même** dans la boîte de dialogue. + +

+macOS Confidentialité et sécurité — Ouvrir quand même +

+ +Après cette étape unique, `picoclaw-launcher` s'ouvrira normalement lors des lancements suivants. + +
+ ### 💻 TUI Launcher (Recommandé pour les environnements sans interface / SSH) Le TUI (Terminal UI) Launcher fournit une interface terminal complète pour la configuration et la gestion. Idéal pour les serveurs, Raspberry Pi et autres environnements sans interface graphique. diff --git a/README.id.md b/README.id.md index 6d62dcb9b..20713314f 100644 --- a/README.id.md +++ b/README.id.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — Peringatan Keamanan saat Pertama Kali Diluncurkan + +macOS mungkin memblokir `picoclaw-launcher` saat pertama kali diluncurkan karena diunduh dari internet dan tidak dinotarisasi melalui Mac App Store. + +**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat peringatan keamanan: + +

+Peringatan macOS Gatekeeper +

+ +> *"picoclaw-launcher" Tidak Dapat Dibuka — Apple tidak dapat memverifikasi bahwa "picoclaw-launcher" bebas dari malware yang dapat membahayakan Mac Anda atau mengancam privasi Anda.* + +**Langkah 2:** Buka **Pengaturan Sistem** → **Privasi & Keamanan** → gulir ke bawah ke bagian **Keamanan** → klik **Tetap Buka** → konfirmasi dengan mengklik **Tetap Buka** pada dialog. + +

+macOS Privasi & Keamanan — Tetap Buka +

+ +Setelah langkah satu kali ini, `picoclaw-launcher` akan terbuka secara normal pada peluncuran berikutnya. + +
+ ### 💻 TUI Launcher (Direkomendasikan untuk Headless / SSH) TUI (Terminal UI) Launcher menyediakan antarmuka terminal lengkap untuk konfigurasi dan manajemen. Ideal untuk server, Raspberry Pi, dan lingkungan headless lainnya. diff --git a/README.it.md b/README.it.md index 1ed73ee54..5c75dab3f 100644 --- a/README.it.md +++ b/README.it.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — Avviso di sicurezza al primo avvio + +macOS potrebbe bloccare `picoclaw-launcher` al primo avvio perché è stato scaricato da internet e non è notarizzato tramite il Mac App Store. + +**Passo 1:** Fai doppio clic su `picoclaw-launcher`. Verrà visualizzato un avviso di sicurezza: + +

+Avviso macOS Gatekeeper +

+ +> *"picoclaw-launcher" Non Aperto — Apple non è riuscita a verificare che "picoclaw-launcher" sia privo di malware che potrebbe danneggiare il Mac o compromettere la privacy.* + +**Passo 2:** Apri **Impostazioni di Sistema** → **Privacy e sicurezza** → scorri fino alla sezione **Sicurezza** → clicca su **Apri comunque** → conferma cliccando su **Apri comunque** nella finestra di dialogo. + +

+macOS Privacy e sicurezza — Apri comunque +

+ +Dopo questo passaggio una tantum, `picoclaw-launcher` si aprirà normalmente ai lanci successivi. + +
+ ### 💻 TUI Launcher (Consigliato per Headless / SSH) Il TUI (Terminal UI) Launcher fornisce un'interfaccia terminale completa per la configurazione e la gestione. Ideale per server, Raspberry Pi e altri ambienti headless. diff --git a/README.ja.md b/README.ja.md index 9165986ba..cccba8517 100644 --- a/README.ja.md +++ b/README.ja.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — 初回起動時のセキュリティ警告 + +`picoclaw-launcher` はインターネットからダウンロードされ、Mac App Store を通じて公証されていないため、macOS が初回起動時にブロックする場合があります。 + +**ステップ 1:** `picoclaw-launcher` をダブルクリックすると、セキュリティ警告が表示されます: + +

+macOS Gatekeeper 警告 +

+ +> *"picoclaw-launcher" は開けません — "picoclaw-launcher" がMacに害を与えたりプライバシーを侵害するマルウェアを含まないことをAppleは確認できません。* + +**ステップ 2:** **システム設定** → **プライバシーとセキュリティ** を開き、**セキュリティ** セクションまでスクロールして **このまま開く** をクリック → ダイアログで再度 **開く** をクリックします。 + +

+macOS プライバシーとセキュリティ — このまま開く +

+ +この操作を一度行うと、以降の起動では警告が表示されなくなります。 + +
+ ### 💻 TUI Launcher(ヘッドレス / SSH 向け推奨) TUI(Terminal UI)Launcher は設定と管理のためのフル機能ターミナルインターフェースを提供します。サーバー、Raspberry Pi、その他のヘッドレス環境に最適です。 diff --git a/README.md b/README.md index f627e261e..bdefaeb18 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — First Launch Security Warning + +macOS may block `picoclaw-launcher` on first launch because it is downloaded from the internet and not notarized through the Mac App Store. + +**Step 1:** Double-click `picoclaw-launcher`. You will see a security warning: + +

+macOS Gatekeeper warning +

+ +> *"picoclaw-launcher" Not Opened — Apple could not verify "picoclaw-launcher" is free of malware that may harm your Mac or compromise your privacy.* + +**Step 2:** Open **System Settings** → **Privacy & Security** → scroll down to the **Security** section → click **Open Anyway** → confirm by clicking **Open Anyway** in the dialog. + +

+macOS Privacy & Security — Open Anyway +

+ +After this one-time step, `picoclaw-launcher` will open normally on subsequent launches. + +
+ ### 💻 TUI Launcher (Recommended for Headless / SSH) The TUI (Terminal UI) Launcher provides a full-featured terminal interface for configuration and management. Ideal for servers, Raspberry Pi, and other headless environments. diff --git a/README.pt-br.md b/README.pt-br.md index d4b303e24..40f5f3d4f 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — Aviso de segurança no primeiro lançamento + +O macOS pode bloquear o `picoclaw-launcher` no primeiro lançamento porque ele foi baixado da internet e não é notarizado pela Mac App Store. + +**Passo 1:** Dê um duplo clique em `picoclaw-launcher`. Você verá um aviso de segurança: + +

+Aviso do macOS Gatekeeper +

+ +> *"picoclaw-launcher" não foi aberto — A Apple não conseguiu verificar se "picoclaw-launcher" está livre de malware que possa prejudicar seu Mac ou comprometer sua privacidade.* + +**Passo 2:** Abra **Configurações do Sistema** → **Privacidade e Segurança** → role até a seção **Segurança** → clique em **Abrir Mesmo Assim** → confirme clicando em **Abrir Mesmo Assim** na caixa de diálogo. + +

+macOS Privacidade e Segurança — Abrir Mesmo Assim +

+ +Após esta etapa única, o `picoclaw-launcher` abrirá normalmente nos lançamentos seguintes. + +
+ ### 💻 TUI Launcher (Recomendado para Headless / SSH) O TUI (Terminal UI) Launcher fornece uma interface de terminal completa para configuração e gerenciamento. Ideal para servidores, Raspberry Pi e outros ambientes headless. diff --git a/README.vi.md b/README.vi.md index ceeb02b63..a345e99fc 100644 --- a/README.vi.md +++ b/README.vi.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — Cảnh báo bảo mật khi khởi chạy lần đầu + +macOS có thể chặn `picoclaw-launcher` khi khởi chạy lần đầu vì nó được tải từ internet và chưa được công chứng qua Mac App Store. + +**Bước 1:** Nhấp đúp vào `picoclaw-launcher`. Bạn sẽ thấy cảnh báo bảo mật: + +

+Cảnh báo macOS Gatekeeper +

+ +> *"picoclaw-launcher" Không Mở Được — Apple không thể xác minh "picoclaw-launcher" không chứa phần mềm độc hại có thể gây hại cho Mac hoặc xâm phạm quyền riêng tư của bạn.* + +**Bước 2:** Mở **Cài đặt Hệ thống** → **Quyền riêng tư & Bảo mật** → cuộn xuống phần **Bảo mật** → nhấp **Vẫn Mở** → xác nhận bằng cách nhấp **Vẫn Mở** trong hộp thoại. + +

+macOS Quyền riêng tư & Bảo mật — Vẫn Mở +

+ +Sau bước này, `picoclaw-launcher` sẽ mở bình thường trong các lần khởi chạy tiếp theo. + +
+ ### 💻 TUI Launcher (Khuyến nghị cho Headless / SSH) TUI (Terminal UI) Launcher cung cấp giao diện terminal đầy đủ tính năng để cấu hình và quản lý. Lý tưởng cho máy chủ, Raspberry Pi và các môi trường headless khác. diff --git a/README.zh.md b/README.zh.md index 93abf89d3..5e1e3c394 100644 --- a/README.zh.md +++ b/README.zh.md @@ -254,6 +254,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d +
+macOS — 首次启动安全警告 + +macOS 可能会在首次启动时拦截 `picoclaw-launcher`,因为它从互联网下载,未经 Mac App Store 公证。 + +**第一步:** 双击 `picoclaw-launcher`,会出现安全警告: + +

+macOS Gatekeeper 警告 +

+ +> *"picoclaw-launcher" 无法打开 — Apple 无法验证 "picoclaw-launcher" 不含可能损害 Mac 或危及隐私的恶意软件。* + +**第二步:** 打开**系统设置** → **隐私与安全性** → 向下滚动找到**安全性**部分 → 点击**仍要打开** → 在弹窗中再次点击**打开**。 + +

+macOS 隐私与安全性 — 仍要打开 +

+ +完成这一次操作后,后续启动 `picoclaw-launcher` 将不再弹出警告。 + +
+ ### 💻 TUI Launcher(推荐无头环境 / SSH) TUI(终端 UI)Launcher 提供功能完整的终端配置与管理界面,适合服务器、树莓派等无显示器环境。 diff --git a/assets/launcher-tui.jpg b/assets/launcher-tui.jpg index 72c8aea3f7eb2b82b87d831f5b6f0f5951570740..659c97794fe251de00d076833665704c0a1a5110 100644 GIT binary patch literal 277431 zcmeFZcT`i`yEYm{MCrXtl-_$Mk}YgNy427kAYD30&_I;l*;1veNben~LMTdAIst(M z0YQ)iBrYYu%|74#_Br?5Kh7QZD}UVkOUB9=D_L`{`L6kvXFl&cbFp->0b(@J)z<}G zx^xMIAiqHuD-B4-JouzW*>bGdnlGu(-60!QwW*Y;Es+-Q6P`9sf8X{seyg{#&j~Ac}t; z*?$x4Kgq>Jmh1ABD->6#{+8>~P9Of0qjbqNTV*o;(UB5EOJywJU#HZ^zJb z>jGr69)BJsVPJXo=B@vDX;>TasO$2YUL|Rmi?8&UO)Kf^?}{_X1?U6x^uElO$LSMh z&-b8mmZGJ5B|nZYK!O*bgQxv}f2I3(n|rU*t5LJlq|6IY`{nch(2Hi(1;`Y7WO}|t z^NZXs>jQF6R@9>Z>ib{Mg!-C}tAg#in8&o=Ap%=n%=p~^{ylGe>ghZ+%XZD!?RUFfosVTubp z7sOw+vPh`hc5p^7arX$&-f$ABNSc&w_4sRcLd2l>YWN!uGYD^BaDy(Zkgbkowq0QA zY6gnqpP1#;jXJ%Jv}y%bLQikoAwo|MldSW91HP>?^zTDM3(RG8LPeG<+EacJ7l_Sg zuW?w{6fL{mA9om3fV595B*B3X^zMeRmsPJ(0!{tbjce2kbfw zqrHq{CYlH7n{0=#C$8PVuM=U!3lKMc!Q^;CO7hXY7T$Hr*zh>&uISQ~!Rj`bJ!kLJ zyFoC+Wy3rn#yk$APTgf#6eus^y zCuCnZj4Ka1d*ftP^QMZyT>4g3Y!D^yzY4vqO_wJ}9GV|NMVrY&ioQPqzi9YlPLm#~ z{i#n>8y^hEpDe~l-kikI_|vw>*VpVeuXuOBw5B-gQwPPO6x1*?UCw_9rd?~8alZgX z2}Q0n>G()Z_78l6w%e_&X|wGN8}qOE^KT303wy48l+Q?elNMki;Uq{9K&LUJ!8AoT z`?%k~D#Dhfe%N`jjWB{JKm>0WrYjwwOt=gOgZ_|0R<1LxGur**9b+fr>NAN3|A zCL?d3zc1l}y06odZWHu|zR56PcHu0o!}BEZ-^$%r8z2k*JO*`7XytStei4oNf46jbogsd8CBVzxyTY;;5HPg_x%|j|FYl2dvYWPUIFzcbX z<(D$WOm+DZ#!j$ioSB6%t~OmEE+TRC{*mqdV`{b>ujFyjQKlEpc!y?`O-=MG)%hW< zqR-+%=Go7W3`5=jN#u>n=&rr91$Bc_Z7_aU1s_lp8gxqg}=3nC$St7Z=Ypp za(vA{JorTLA5w<1LSW^3mAe-px=66rPxb92=?!fU;y0fsA4EDkSh^)UjD0UxYfG^H zx(kkfjGy6fSb+WUBvFxVK9uS8ZA^Ao#7!q7#l^Q4W2;If8SWPE|7wIR?yOSbz;wWK z$%PJTG-TL~%Plm%bO=$K&hZqx|NV89b+%Uk(JhG>F!>d`XO`syL(dY~v(;v+%-IA) z@P$k`C<734tlp!btv=*t>+7bK%jnWvXial#%3D0!%PO4RIpJQKmm!0BvQ>>)d6kfQ zP|Tig;B)%_FfV5;|BmX@`~GXV|G4J@)bew<02N~mz2SzBd)-gdCS?#IJUgsuR6c0< z%}bg;AFMKdG+-;`0YAwbWBfyDQwjZS$Za`)7}702=$b>rl`u|7ziyXv%Q+CR6?F+f z#2eK1=7=x|m)gbr^0vsyO!AlnbVOd2!urhCH!kffW(9H@oKKk( zZStADl{}|&%9LlaEdJxL>kt1?G>zo{*$a<;(+iM$Iw0IvM|gszoIao+w~S3KgtD4GB^38uzr`uc6WGT zg=mQZ*ogDdKEI8TlLlA%gq(RGdLwARTc#{&LV?!J-oOlfzj6MXs#d6)+AT4e9Nd~B zw*Kn4*Y>Ju9$I=3HvI9Ov!$m?Q)iE#1~cYW^gB0znzJxcgUx&`3c&7uQIEl7z;D-z z^?hIb5Pf#p?Dm3?jmoVD8c*vbQGEDE76Whs<1rFfZ#rm@!`Egst~?g((A;*-rD^VH zAG#1U;l~{36IbNmW*1Uw(nno*2FwK#%`K2G4jDU5B{mLEekx5=Z#hU--K&G36~=>& z`sz4cnzD|5gKfqG5$bBid{%*>UNfocfO_!HO%*m11HFNNG{EAdNZFVNHXo8$B$R6~ z*U-BBASEh8-!)XKri<~e=mC@4&F4{SN(FF#T8Ot6+bt^tHUX|d18xHa1G9{t2KRi= zQ&AJ8ThxCcKUQ&0G25$t5Yr*e8vRFCR`c(sl=L4}@_)j@8TJX>^mG4ECdMCPCUdbJXlN|7 z&Ak*~vgy=YyN;9uPy`+BFhdvKM|kJO8B3~|Be&`AU?Wg5z)Ay@A&CrzsR z?A+x{)n;gyK-F4z$D~0lK7_!)BHoy;q^eb>HojB%j{*~OaX(2c1sm{TnPLV(Q%IL^Zbu}?(%l@{y3gNB)q7WS8PAG8y~#T$o?x?oBy zKOb~oHc`lAiY>A;wt8hg`zoO#{9}!ouJv&zc!-aeKyf{N=s~F5oSdz$+}84fVc~tB zE!%CwG_##F9))o^110=ymFdqckHb$5CP$^#-tukx|6M5YSWReOzuoCu>|0l{;Pq@3 zKYOP& z1!$v$jLhz-=spN(ZVU+(vbFU^LaegmsGI+~b~EbQ71(q?UcV#}%18=54MH-_LVPwG zIDl?re0y$<4d9L*>Jykh>4q{a>no>Z*m<^NvOiC#yLFefbwQN%X&*LfmEBDRU4SG= z*7)LnZi3!SZt|{&*?9AJmQjNWCO6H&GQFV}M&Nig_Rznu4x8qRyQ9)LT6!Md&o-9Z zZ@y_=H}JavZF?lHaV&1qi~KeWbd-r zhCaWAZ-|~baY-#BxQ)j9CO#;_!^fK2ZU3Ylpt&VLJCCOt@CSrM4)hrgtX={3$ z9`*;4mTGfp&MG#hW3A*xcjje7++N^~p$uE`69M7mXUXXz!uxGycIe}vc$-^#5r?RW zm%q@jl8G&Q0q9^0k5qT8WLw?RH-G@iO4r6bfl(cc1}pcqnPUDl zb|r5}NR^syFbsMRX}l4^GeFiRhw0d^oK!cT2bwoY!u|>BNp^Z$f|#7QuMs)dzw^l* zNoEMe+==l{jCHuF)-a61pf($Z;j!TMpF{m@0CaRs;!^`egTP1JoA(VZ7;U%V?{mG~ zvE(O|Ka9%<%M0+VG(SDfl8ALar4t!0p(l}-7Exb->kx%X`@Q@ybV>Z;IvqFhlf{1X zDk4mqKsi+6ks<#lL3Hk~>vzpfbU(6s9Dmz9B(aPW8-dTKZlpiq^dw%wpH6H9s&s$9 zlHuP){Ywxv|JQ8<74?|k2?~n$ere>0*x0nzag$!=@OeZp=Q0G4pHRRkNk{ULagMXw z56g{eo25W^0m}Fads-kGnmb%ekRzC3N-W#_Zi=q{xIkZ+ZeE_?+Kb zL3AA7{98UCRnkGVxQ{+Y;$HaoP@#BYc-G4rtMDfk9CviBc)8OBj0N`mSB_-pN&19Q z5(|{r*!<~0v4l19BeCzsJmjua=~1z3xg+3BoUgwO7T4GQ7N$`~F%g799K?>=UVtcd z&N5IB8&djtdlYab&(2v0IZaqLAmWvs(n2DxQt=72{hGk@-2E9JcEP?8bB90g>~^;W zpz!w?SV&NO`iMUuvc8|b;p;9HigLF_YP{T2X`GPG2H)%d_)wrNX9t-oG(n}6IYqB; zz84hiluo|mANU;-D}@z+#pYdrJR}-0d@`41&}Y|eBe{W=VQ$T5WNv_;f4O}ow1CGo zaEo<3T&I1*gY5{(l|0qlGy3%uE-{HPjrW?4Cb^U_;2@@P5~sqsR5(?PRVR!=+(&l& z8=4Ua3U>%#%n@0Aow1d#U!pLmTDvYa3b4T?CjfXnR+E+B63!USan4nt z5#h5oq`n@Bo+w-I0WF&X-HfEIjJqTdu(AP8gHrW8ya3Tgs>&p`mar^omw6upPaFwX zy2jAHRl~$o&HaaSGIc7(m)x})wr>hDz8yG=CF|WVIjg{OmJHp$({TZs;re#evN<|% zh*{U_jK_H#h+zGb)*{Xp3SNV`kw)EfZBM>c3FtX$82iGl`}%sGyF_tWFI>%fV^%8Z zZ0XYPi`u9_wH%H#q{6R2U6&(Sk;(+Ubb%Qph1c1eHNGZ}sOgF3Jd5dx#ExJ_DyO>* zUn7-qlCRUjXPm*v*#tm2q|&`GLQi$-NHUUzX0RWV9Q}26Sdx#wPq`Ozo6AM8)F4fS z!-9!TJfKR=_}l3s=gu!HY4zW#+snC(0V%s?6RK-DzJ*_2>`Q~=psoG$cK3$PU`+O0 zcC2CsJy~AnLoR!;q^w)!K?SgL&u?GB%VAhp)VUJLAFqc-C1@(ap`V666~YxE zL~EuDLg{2!$X^aU%7pNwqpkrl%PTQ1vZ&Hv&71FAidk^Hk;Wr=)>KsSKB)uJ|HmyVW+oAVFV8} zn5-mBFdQo8hf1rx4D+vcn1X>vQ|sFdvG1+#rGAVjGV6cVUKYt>(`q`VfQ_AehqkKh zpAZCyEHQfEYAsyU%V&xl}PHOv3nv90p#!-g3sAh?c>fa0JB zX#C?F*7FUm>onezxbAU*F9lCJTYt!2^IL%D)s26yk&UVC&Q|_Q1-88v`IHP}WHrTY zkV0o|ku*M!hssy(%=hSk<4?w$g77rP>gOttin0T8ZyH~b_+s#Z$A8s^jq6uGSCxBP zk03!jNB@A&$Kn}~ud1YbjWl_*wxxGCeS?lhwqZdcM8_@$>dy_oR0*u#EsnsN65 zd8vvC-dMYp#%6vlkvT30A(VOPoZ6ZbpO?S>wYqA_lAT#W_E3sIY6D64tUdC!lMhTm z-~T0I-5Ng??|IZcT=BKbac*R4hE;MbN+D22AmbWk8s+`S0ci6DDDDE}lb+rsGswUS z#5>}r3TkZEjn68u*b1H>#cNaBh*0I183c*EtoWc2F(JZ$#Z+B0Q4)LN zt@^^)D)z4aspq7n2@I;*P%u-mAT5i%J zjhDyEb#Df&{;OOw4wIXOcHWw{q5hJUUn0#Br$iWGJsRrm8%(rpM}#(T?>ebA;~#`s zSK=_RIp&dVo}aoH9M2OeEs5mX`_Fj0L7Z$vQ*?&?ISq7!=%Np8Z_m&d5zO3t->iRI zzab1LBe0;MG^mZY=bD80T&9;pqD*fhs<6l-xj%(V&BxGzD zeKrAddpJ@spoGa?m73&O1VlrozkW_2z^QU}5Rlgl48s5fO%+=aJ!Wl@d*6F@yeXM` z_soz9;xPA0zj`Kn1t7&I_Gn>|$2X$uhHXc0%uV#Acc!y6<~<6G8GHFP(23a& z%}3(h{c7z?Nj9UVd)YgQC5m4CzG9BO>8xb!SA^bQkB*eV_YXkRET$NUVvhMyT8t#PUyQqg3kDojH~{1&1NQA_qRu=+h;%jTwmw3 z+VfZApyd03#9+7v)|8K69X!;>vXupKnCQF!o%6@XL-dOR7cOUDJM7V<6}s55nvG-L zW-dQl>SaOEO#;yKI>){!!rP)QHswu)dgx089~WVi#hs*$jkDJqhK#0x6L~3yxH`Bu zD(k_a2o^=Ob2DbiDAJL)Un=!sp83dTpqeA3U&SU@ywflIiT>alj<1nr_;DhgKOhW^ zMT$X3;fcGaw{>@O9%$^t&NeIT>vgRWd>Z6p1f7@ZhpKsWz>nLQCP^z&aSh5pM z28gIrUf>3V6Xhn80%g<`?|b?CJs}_1G-#RCP07KbPkDE@XVDmre1F;w+-CS&%*|vz zC$jM_tVg5hAUdapr~zJk?C78bj1V>i1_A3fb0;#_b_;~&w|(kNt#WugrN*y@^G)AN z+mHCPRkZ_wVRwcuK+CzChXWD(qfS5#zGC?N%BMDsL@7xnFryc42D7& zq|tk|Eu64%3FpRa?|T*I*=)LOVus!CX!|L%2p$QXyr?9#JcohE| zee$Bpxy8ZH;iy*GD|y&eblwJ77_V{0w~3p@V0Fw>cKc~wCrO+>J)eYH_Pbty0Fkh` zJ~`4&g35uwsTR@_6}5i3=k}xDA$Ak&1mn7Iu%=Oe0Xk`#WGQ{&)ZtYz`W0m_q17Zq zXP=Kfuv5m!fk=74baR?7a&o<0xTWUX1&HCtuFMGo1IcvOSFyC}AVV=tN?Za?`?W(w z*je&npZyq2P?ge#p$>9=-J^QalUg5bNhN~DvF?lWW-NdU;wf}CfqAv<7 zgHH$gT$Zd07HpIlb?vp7dYkg+B%|hik|P1OZ*gjeph0U2lif_@7381zo!B0+ioRF4 zRG)ifa1!^tLNA!EW)=QkMB~1F)yo7@!|AArM*5~SC+^{CD$tQ8?$M`;L#j;!K0~W8 zx-V;d!}v5FN=?3T0;i~3^YB*Q+V$;U!%hdvuBe zb6E)4Mmr-&z4%`{3_x-scE9u)$M2f>focACivy!FITBJ+0scyp)dVT(mTX!lFqZ*! znT;L;cSgLx&;BI|B2C9xEeDP&?VKN2gU7C4iDdseT?6!w+#9CtsArQd%b=6;Xzz{v z(v~!(*QwW`5M=*&+gJ=O9;8$>Ai5DQ3bqs8{go)&euQBYMKRLV(tQ#~n zWgJbL%Jt5V6t+k5ekw=MGP`pjM|HEpo%7D^PD%jb>0Cnp`Srt7^T>(5%NbLq$%npm zK(y(no~@2K3U zwE4$sUY(6(`WDHf4>6!j$^Tx%HlcDEgZW(GHd**;P+RP;+%7Y42Gum20S8a&@yhkk zRCNE*V>E;>j=+wx;@i#MMRE@2XyJPtb2d@UZYL>D6gc_FNHTMeWRdH#W-H^LIN&O4)l#@- z`*Tr_I@_U}#YFY)Y*JF28aNM%H)U8#M)$YJr}czyq_Y5}Ly-*K_|e$V8$Koxw-h_` z9StUgSA30p*uySM+_#MxxS}AvN@W4}CaIJ4)jbjo=jt)Vp`uEtQ9kKhh)iPC$B-qqI2bFC4O9QE<(>yifM^dQ{HYARewtSdkk?eAC1tp>djE{7?2TZ`mE=K2&4H z-RH?1h2n^Fu>R)Jsx^Nf^_!#Vnz>iH<$Qitdsf7%K}Jm$yvqJF!X~a{C_Mp6bIt)M zqj(XS*!F%L9mSA-g`h1(8n;(u{jB>$i#DUrpRs&|Gvl&ObXnYqP`y#;XC5i z`JEz-Lk?G~t8uHWV2f*wzl@duz=k>Z{RGr)9Hvrfb?I3*lZ)+5DNN}S(5`(Ic2;iG z3Msu%R(PUX2lpAcDLhgoik(LmX9uh%<@jXna3*C1Hvn-+z0_fxLTucG=f@%ETi0+E zxw8WnXc*enn!dGqOtFG1l8vMT2yLcEftenj^O1Z$HXvE5hQrHm_|2EMbayw+_zSf^ zN$$*_GJMZxv+toW)Za7idsTVM^k8)V8`-H3t+PYK2%4wFfU|n2XD}|E4#E8W0yNC+ zo86cAYRS>w(C<4@#Z)Awmd{hrvxow%)|na>9`wq^6zjWPN3h@QNZogtZ!6&>nFG&o z*Gn54a~&3xWSD_u%Y0SSY~LH(HKDJ_`$Tm6att5 z?ckf`EZ;TXY?R58ferE#u1lI~u9a4<`G82L%XjZAyvk0)A}P~2G#Dad9$Gz@#gg9r zBYH|9k`KR@H?=y=VQ%$QEQ5_Fh>S_?d(Q3No;bd(*pM*P~ zrSY@8Ufr^*U1do>F5rkeRD$!2V(H}GboI~mSQhilt_5-17mQCjcs)v02%yb07IfEs zCpkobw-^8IJiH>w%HEP2PZD^2J`Zh!r$9Ie@gv;QMKyMS#~?rs~X-WX&_~W5<1`;%s(16ZQEic7M zT=Mb{_ozTjjhpvmd`?LbWU?6++^7H4$b7e9K?ac6*^fBYK_bqo_WM+k+<29v|RG8u`82p`7xnCfT z##Xx!u_MP5suK{Ys8p)ic~%hD8hYce<`sa{{3GQ(^t*htwq=U$Io zC&}@#YZm6O2BI1ucdCKZ$CnI!_P`0hBD_8wHd`N;eC&4~xKR;HmcJNx3pq=mbs2f-5o2bqPgWK#enpwE>0qe@tzUeF~0 zqI567v~gyEONr#0m8@;+B#Cjur^^l5ir@N3IF2|$#YO9jq}~KY+P;v!rq(cp5E(v~ zKteZ^gNGo1AE?5-xnP^)_IAxM!@bZx8e6$3VGv8pN1al&wIp_b(*zMd zIh(?}*XQRy4M2zeL~J>t)Y>CPX;^YRibWBne+7aWH**|w-F}UjWx6{U$6`;CYs3`; zI@RSXo(+!Ea0kB<>(rGF<<7nUDMBJnSr9yfx`FycGQkvknn9^glY3NV;8F1QC z48#`r*k*jr$9;a?Xl%L4p!!s(7Xff-Pd>Q-?MwVytw}D_7aYqco@oetCV&lqvyJ{s z-o>&6k!atL12%|IIV3E%-A+qCJOaD(Hh*EyWCtr(Ow$vX3Zk5@eBxD+p zryfGm03!T1dhKlps2GU92M)^ZneU*reLUFSoX| zX2M!Isnn!cycePvNW&)1x_2BNewds$MGoXFstwWvg*}KqT2pMt#)+pccJzVMB3I_@ zbkIH@JZo;yMRvX{WgOZPh^TcF(qbH1sED^(5VjPX-zI=#$U)NZ0d$1uV}g zq^Cn&p@2wz&8a@38}|hH2O$PUea?a8jw5c4M#^@jDR-JZ@HcAja``|vXB@`+C|P1u zsI_|mkhw<}cXRY$u>vrSQm)vHP(k-I zVO!=O_B?7?s&bV7b@If~;#8+BLi|CN17?-e8YQRQ!q(76piw8l&?p+!mb7LtOEqE5 zszf_lAlrSZR#^weG%!2r9yAc>7Z^3V1yF}8HY3dIbc^X#_cf)tZhI?9xcFGd2&Xq} z)@}|RGW00ooRmI5^r}Xd-NQ{|Ugf$YQj`s0OoPEA?#zP(P2cE|jevkLJ}YW+dLOOF zqsPWu-zGLUWL?E+43_uo$D7D3jS`=JLOyvD`5xS+lHB7%fW7OIepUAY>(A#&e8kQ@ zS)3~w=ww)K=z~q=+OiJ6r}g^a&8_I{2i5|cB}+a;#ziuQOmvdPkM!ce0IZl1@VJjD z88T%u<-R?V>{k8Cu#sTY&=0ToXc)Q*Wjgzw!q!=?eEhI+C~6x~=ZA z9eo=zL%f)fhVJ-2eP^2=^Z zkOn50#ygbfMph+6_?G^{tLvRe%;q{<5Cc8QbQ;Wx4d(5j(2KqR&69pI4He!PSzf0% zRGSLOgEP7|&8u2a#lADV6gwWs){?@ph!4!>h^Z~C{uHws5k{mw%jtKy0MSIQmRvuX zUd|-u5=4ejtR(6A9xIs?q{617@D0d1#sSb6iUennJ(e9JST|~`?nFvUp3pmtF7T8V zy5192q?M$RYlgo>%e$JhNUoKK45l=0BA-~tdL7Me-I+<9VR_l z=X(WCb+V`-ai<4AouF9h8?tjOc#5oF{J1ypfTkeixTS{@nn9*@b4f*u2XYr6w{cw9 zdD-u}i0PP6*_q$k1Ycf}gn5$5t2jI?+N=KN(Bg+cM7~1J@Tz`N)zg}k_W>^;R|PT< z@yTW0)a`s}(#7nxf5Rr`|(E;4ReVUL+|(^1C(pk0k8} zGaRixzT^Vk?U!72MJYx{nOiwa`kJGC`fQM~sPb*6X+g8-_X5QRYOtS=SCj2>+>*Q31jpPh8;8K+q95seYn0rDSmhQ#} z>ATe;O0DL69qqe%IOLj(H=^O8gDitCnwfmER%3M#B?bnh!hlDcsFEb~_ato?yEV~j ziyeEi7f$C;uEc*}&YG?ILAdCAF;?7J;*1|@U6o@OA{rnQ|H9~d8GD)bnM<5PVs%%j z8~f(T`v-0Em2=6hnB}I6mS)B;8biCi=fWJ02Eoc+=maxc-~UhMe;k>L=T0>8XM-x$+`3{rs{$+5dL^hVH9ffIj{S z#gk9J^r9-tH9w;yPRW-)CU1N(E%5v=dw@3Xkad8OOdR@OfHF?bi{m@sW9MX8!`DAM zyN!|9C5Kv-^JfcC;5zw&k+0_yv#75($jIwy&IO3%m;Rsb|LS1zsK5W5s^Q0vU+qc1 z4gb@4q5oMNdejdl1`_)P=$8z9_v@2TQp4c|=-Ick+nTMVpKP z`_By*e?R8?m*oGN^Z&wL{-y2zpR@D&=wZ20_TI`-Rxef`amu^%W~||Ih2oXeSHm}8 zP$waqC+gW^9bqQHu?Id!`Xj{MCr%@~pG7{4{H%#(HM=MKI!P4tzwGEM2Vkenk@NjL zJv7jv=~yrY$!!yLX=QcYb`s~yC#AakEx0^J<>Rx^N_z(l`|FH-@3~B{jcv%`aF5`M zHOD2VLE>VLio){emG__wngTB;E^NT{OStSCoR@XCRE4|cxE?vjl*SF=@TwEiU>+(9 zDmYVIgKnC1zL{S+cimlGwvDXUt5pVp24-RN@SUT2Nvy-E+*A-UJ+Tv|HrW}*#4Ez+ z7H%D!>#mljlT=u(%lUAJGJ0rXXzN9yL8{G2nsPREMz(mOPN%_qWkXpc!}%z@_2=jc zS12@kof7G@*{jZB^QCv@VI8i0?X*3nz^toTU;E)hMV-a8o|H*~DN*69^8EVrYkQ;> zAhu~rsgJvDJ*)l6q{hen$<8V~oc@?r5^)CO{bY#A3>>0fgB*Jv(?-CxnsYH!0+Qz z>C)Me$MMICB+Kh@*5smxHyU^(r2kMAxM|HnCR#W0`#WGyky1DY9>gfo8+i5-0UPYn zA@<^K_??76n|AGV3;ceq^dzH4>aTs^}U#C zlQ%~WW3n;)g=K2S*eus;|9yi1A0-1Hl5W_ga-(rg0WeD#agmrMSD*}3G9S2RHgWY{s;n3!8QL@OI!fP|_QrbXTgM3(o4qh) zP?v`6wfgAM?~Y}r_f(6is-7;kS6Iu8G+aF*VcNCR2|j2^_VBooo(`r=7vMOyak-6l z*vgXca=W9wQk;B_lC^j8;8coFb)7}XFO1$`7VMMhsZivnU3{E>x(8MyiDBS0n6Y3q zA&1%X6IZ?U%x#>;cCX_i{c~OnvMgG8?pMt-z!>k3Lyekp*drLQ;Akb(4WRG)b!p!p zH`shJL>qp@W`-HD>fn{7UXGH`mXo92V{DF9-e5Zm1Ed>FtEHDk1fun|j?y zwvmvCu_+UEosx;AtLkL=>lM9Ov&~4_A-7-K{_(i)oDJClQzC59IHRdFRJPSPfZ})- z@J$`rBY1>W~E0yB&i zkt1%K{?Tem-m1v;;c42ST3rFEn2CTN3euWb2RiRxk(w!TsW$5zvc`&_x^UlcHv9>`ts2{8DdF72Dk5%G39C?7s+@~%-JLHp zmn%{m-W4?wH+!s@_w$#NPmV|4BO28Y!ID=4v$k!#Z6>-E#|STwXz9_7Zv&REk_KEG zv&sF~cz^T`srguAo_$wI)VTbG*&kQ_0tse5r4r2icxg5N_aZiIU*Hsu^!kV;$dHrb zPc?57B4Yz=?o`<#Sw*zauPZf0g_&O><%_Rlxm?DX3V zRC}gbV$ofx!t+XIAKqZB^*&g_+DUF_1?TFK(_sth{v+veP9i{fcvbxQbh=Y}Ec1T( z!3VnGH$SZ>Z(cjt`qk%b*1lTTO^bdv|gpXdMVyr4N2pJG<&HMzxq6 zMOpiFK_=jdd^a{!pU@01eZRfY1EzK@uiEG=;TTrq1IIUI^H#8#(iB;bYD*0nj}_gm zoEe%jxtHIzDcE=KbuS#Z_^v;B{c`Agk_vej$^|H)hoWwhqlhl8*Sm?Ubal5&Y$M}| z?KRI;HvQ$N<_4SjKWR(>k;Q`|6#hsiiHK@hPpgl-G5rdSvq4%hd(l5+JBek~=2cn} zwbdgu6LM>Uh%hn*svMjbcu2YROOI(G4IJO%KxUH>Mje_%#>2XC#%T>{6|s{N`eWjX zjY>XKCT0fGUjbaw&kNAC^Ko#iNV+CFB6`z5Mw1Q5bOJ1}hM)CJ2pp-kvGGLR?1(oj z17Vdb4!;(g9Is1BeXrZf9d8d^5kHU&qVmGW6WPw{BQ+5QGB=QX0NW-MjL?0OSjWE@ za_3C}d3)A#Z2m3cq{)Ksr*YPVOw%>GQt0fmyMa82&RguUP zY9A7M0U948Fp&9{rqKg6!f!b19enV{P7(a2a@w}F2>exu+2EI>3`on^&b%^}Wd{@uy7PQ+p zuv3wNs!DQ=-Zv|+sUrID5i__VIeZ792FSJc8?T-Vd=S~MAqbf(dOVWAlEiOHPM~mM zA3^%RVME6N#|Znmbiq4wM3|*N=gJ;@=@{{dCOEiCxNKVz{@}mVub8^;l^ed|151sx&}+t1bW zc;f=Zh!99xv(i$n{S>gs_C@X6ctA_gy`jwoSo&~;NzAHnsXf#F?uS9^%&kXTq77a9 z(2YEJM}1XkdB5|iEJ?3(MzF*FO%J2z*E$MOx6H9zYyEfPcLowveBU-)!|#we?r+2W zY;FJ;dl#RATm z`=FXtxq-R#u2o-N@T#0nDX0|l$k*_8T#vs1(r6A02&Rh0yo=w%%M&{v5>p3Qp%*{{<+c7%CAK&;^O#rOP!V zt6cFpR@5YWG^jKXF|3jTTmnH&hYtnB-_JJny%cjsA&_b(1yIyt3~ zxK0T%l!-o?RT6LhnPDv9&~D>#^qE-)@g+&WZreO9=7oDe_tS@b639n)inp?I z1mr&iKkSRnh^k8)4}4)@;&-59*`QmTyt}0tQqN7WK+`LXk~f`^>rnxQq+|6E zkAz}Jv;!Qy{9T>(Ca%H>VbmZu>>{8`Z)Q=sTd6Z9_CdK}dk)r%q&v$!hn~+YK0Zr- zR}#A>1muQc5t4qP08%sO&)ooT|IiyTxeO8E9(>YHW4o?Q-Ho#C8!xLgBR1i2#SkEL zvrZ3Nl1h@RLims7o4oQW+jHWYvu2y~RigQNu-)30r6bw+E0@V}1>)xZKti4wT^KYi zJt2k_-DZuqtl(nT!NEt|8tc%` zaxOKtvmm~S*EDlu*PW}M{@zqG<}K0cX&tpfY1%~dV?-W#SJw?2mw@B4#9`-MvnpC0 zE?r-%@-4I(Ji_bmGzbb1Wv_Ads`6TIULShWO>>Hc(uA@244egcQdr&cL>@}JSnpIl$Ob0iruDZgO9a{8g&$K>3q zu9kUPc*uPz-n;3YW(}54;$A42Hjp|hU8CSbun^^yw}pj$b#AO}+r4#l`$z~9i5It8 zVAoUxj3te#Yasyy0#Eeka9FVAJe!V(bZPaT5~Z;PbUtJB9UB0i`-Y=#gP8!TO62`B5>iAClp4xbKKr^qJ%+<+2 z{m&ST=9G%;iH<<3YuTh)3RathSOkilB^rTnGcpT@N9;SB1a)V{qO*>9C zmnFU;867|jPav!LftQP?o9b^e$jk4lGHHWAs-_C``w$;SFsla>Jzn@=;mJT-dVkz> zT)1_3)G&lzMR=m~Ft$;xo`w6(OFW{sn=OB4#F=0al)}eX$7yYzksOQBn>fD$oDp)d z>Cxl@qzlkHC}YnZtp8QyAAhEgMBbR$j=BvUx|>YRzb7Wh{2pt)#;B|hW@_jQCl_n5 zM&2av>F|wD*W^SrSmEI*ify5@CuiJuU-!4!+H&7vnPh!Wp=g%*Vp?1b$%1dd$qc1) zh&&}--#GIh)|AQE^AY;knhbAsqNxez6MOyFcN_krFS@$nZg+puQVFJ<@oY{5BDn{3 z_))YdMe}}+;UgK))$k;o!?rfc{caB;Ul-HLScCP%qnr%s$V8hkPaJ-P81%?-N8EIuIrohk9Yd+-*DQ{-LTT1r2NvtddvT-5kZVlZr%FxSBQRqWCj| zmy~y>BD3cHj)?H~`WEclo?F$CpVpZgAS$=3myO?ySK1~Bw%L%%o)t-&M?^uhE{Uoh z<`WL1)>|4z)-e)8+!e8r*GX!N=lqCdon}WptCXlqB5rZ`}_flAh702;J`rp>C2Hhg-NjD;z8UvFT@^AA_7g(Y?+m*42S zI&VAbf}A_6aGi2}JYRl&=MW#+hEV40a7wn9@zU!jkKo%mWCu6IM*>RY2ez!fZTOfC z&ZXY9DAQwzDN+*~WQ!>!dzN8Q3r7${9tt2SM=;usiWV|>Qmi0SBu$D^l{!A$3@2+S-Fif-Wa(PSY^ zKI>L47MT^fQ~k~#hmfJ34CLJ?NO*wwBAH@9{%f!wBi&?n8GQB@XXC?747iA zmp)ocPQyL{M{g8VmZha%Eei~Y*9(b=22pVxiyt4aUB*IY72+fk5i;??ygSk<0a*mD zLwdK)o@okdHX(CPPwNTUP&pgVPc>>&imsyKp+aJ!Rr(#@A@S(%N@Y0&g%6!7$(}e_ z#N#|DBcxk!y2^TdCHtY|(dx<)4r_6BbqmHIDa~&wqh?ajQxL({pNus0!5P9i@6_$l z;GRh7-mqJ=_rll%N<85)c*i$0hiCg1m-v~ff#FZv>WGw2-uK=2-^1a-E6ib%~Z2wgAzjB zVk@{J_BWBef1V7A=N zt!;9f@7&}z<2jIzWgj@kdFt5ua)AC*SMqigFcRvEg{PpndYlOq=!M5I+;#Pt{|9^T z8P#OhtqWs8q)YE$p;tkWVjzzoARr($^oU6BA`qlRL3&eqk4TBsNG~Fhgd!jyU5Y@0 zR6!DIfDq69o;|+(WAEqt#`|M`XPh6;4;ZlEPFB{MYpuDidChC0jGaWP)spK6UfaKI zu5a;|Hc%|qdwO&2D$yai7}!1UqkZ6vZnLhjjdHV4yiWc*x2ZQ)8_&G}O7P59%{9Kq zEia#p+h-~Ai%Z`Z_=Q)Kib2I{2u=?Y+O&NxEDg+9UiuVfNiSW6ITm?Q`AB zY75!ddqSN8*s9+QUT9Z~f!uR!<961$TWgwdpfJpi^GcTmlpmBCpYZ6Ir38v35A1$L z$bNSI-fvmP%Tbol<>)u@;d`uQl9NtJmA+4zcy9sfV%mQFZ*E*Q;#~YdeUado-3}D* zCdkD$+gXKy=>q5GxVcA%5i?_KS#0g*SdzPWp!?8pfw>5*G-VMl){PJ#TN541mQ&*e z#V0|I_#ckT>1NYS@ur@?`VzRemlfKD-FLMZ{NI~DTM+nuWaV5<2V^rpSvWbkCOm^@xP7FAb!4jF@Jn_dm7nqp_282bi zB=tFwJf2pWHrZ)n{C`z`q`4@oWb61PZCx)EK1c-a5c z4Y&JSxS-OdO(4wSmH>7f8-+mikFDP$e9x8U#zkgJsSS%4 zZ68CW*{exeH*M4hWTtb5zhYhE(|KxXXNL0w?;me05eVI*gB{ZO zO2yD|Qr|BRImF#`a1;afMa_wI6>+r?pwR$F&o}P5a&(Q?yYZSvPuo*1Z^jJy%a5Cr z%j>fOldoIcjSqO)FP3-g1JV)rCn?;`eGh!u@I1;P zAZ%#hC|G58m>2gon=P^T$vimCS*0VapzeEhgXHH&@+vc`0jiIG;oMBC;?mQf`ZSgw z7w&``JCgwFG1-g67!vp%z%0`7zOhHa-<1Pk5x-S5zCqkB-CvD?<-AC^)u8h5R>DT7 ze8kirs>amq3uQBI2p)tdIrm3)v>4ATm9{3q#IKwg! zrW-A_X5O0C!r*t{bNwBDeQoloU2}P$b}v(bz4RE&HKlynuvHt=?!nk&Bin6`bKt7U zAIHKIpxRyVb;kdOd8Iti~hmTfA5bs}|7u(+WP5fQL3EP7D9b9lP zJOM$igd}od5CWbQ>Zwxg6hke|cFx@|309TuU(&iib9(tC6+NGBhb>d+&Yil1=@Xi( zmQVr_-#WoWYC|v z)l~yhU1J(R%Z!q@u&XmM+L_A%AP`~K3_FklW{a*$D3fGMa@8^4>>0~2e~TY;9bP7Z zUp3hEX8Gn4&`hhUO|+(&4C6v+&N*Lulius;zp8NZ=eEhods-siYhEDPGoE1wDy=oetfV`9ZlkyJ!P$j^d zUAOg!En3EmK(OVbLFg}dy|-DLmPlDmogZJpo!iDX$7uMl{miShGMgv!T}YigxQ+x8Jo({p8`4Sgr5= z*=BUz`Zp&x|8I-kc>zelQpI?cP7$+Gpm_f(x6Lz8%Q6NHxjEBhwI=soIwjGfgL+W< zl|9wR3SqS!4b_S_L%voylk`!yF=j~9WRR@OnvSthOtj==g=UP@ETHVtbdCG{u-@jc z_SG7Ecz`ug6b;v?s3pZ(Oau;jnzc(#tEJRbIT*q#pz{iqRmLG9{z~0IsH6YJmA8KC zY`tiK2wAcYK^eOkQ=~!!LP*eaWU+>}52OHGvcB7sanKFd5XQ1klbz}Oe!9Z`LyXY{ zJ|bzRmJ2s8ca5Z**iH`Aw;w3XHr3Bld$g(RSq?~Abm2D52Py~YAr~auyw(f{FPK_M z9(;IO#4(nRnW~EQ9;;AMuXN7!c3(O0bSCjODdeS9WtvyKDpp;|bdITr%6!oIw0tG1 zLR!G1)!LY0qaRl}@piGk;N1!MG7R0_d7dbnx-3NF$rT|))OZ;r*XZri^~P86v?&?S zHrE(`ordMh#@-DEd3@&xlmxKnx5>FPr)FVBhFBW!)vPwbSYE3yhe`x*j-Rd_99+G5z%S^FP#{Ch^au+YPT}7y7|z z{Y5-fQm*3w(72BKxlxK>XJDFhZkY$Wv$Dx(2}B?orDk3*mye{?Ve;;;`iRgbTb?1o zZs1vmHOvXPcPDW{nE0bMyYil5NSLvD{@i-QV*9M|^O)J$5sy;S2K`~uW$u;dsp}V( zTDG={w{S<;9lE(iz*&40!S9Bk-`N)sGMVTUAh~u^xD6c!W=}8++xx!zBdi`~+Uf;Q z`<>LzG86{Nf^;JEDfZ&1VbEPd5D1d~q~i{ztyBB6DyM}lmqV>ZG+`}DC6U^uT=Zdq z)T5hi3zy3IM0y@pY;RUOKy^4OEowK-eI6!)HI~t|PCYz2Ca?XbJk$I%46-JBEGn`9 z-HcZOTOu|&xtjh}FR`m6)o=YoMVHD6s{(3qiwe_*g1Lq?QfZ=#xForiz;yI$}h3ui0k4RQwo1nWGI2=^U@Wm6L}CMQ~ES z!y9+Hz)bTn1019m!ArV(l_!<~O9val`v_ zlO4oZKV^#dCRXC~bR>jqXDAEcW+ntqJ@|7oypbjqM@Wr#nG~CDWC?dUR4bVMe!(Jb zaDCd{(FCY+xu!( zh|iORt0w`Sy+t9;*9tq``I@On%?XE6Fn8RrXeMBfKwx;^pSvstrQbsL)50CnTj`U~ zYl#G1EHPuR&^OPz?qOkZ8#gjAt8TL(01J;pT4qL2FU{^(;W-tWBR+$$d3rk2-kSSsq|GXwbtxsF3wEcMB0~yAS(8e-G_FpNZ{Zo$k7hR8ExKAh zzWikILQ=P(ipiLNT1GpZlTEnakH=g=n#m+{sb2q?hhxQ0(t}HE%$ZuuHfEW2AcYgf z;#bMM^%bTT5;_S;RU;o0A5-7;n=`kKBL}agE$vpf(59P}m@zXT8DC<)4PMi%MoKnb zdr_v(Z8cgUF>E1Gsn3?Ny2AN$?i6APgdLJ1=6Qcdh2oN-hv}qb_*)N4!}3M<^ab}` z`YNu6sXz{{sGe!~*FkQGf@uk3oCrM1bc6+ymqVu3MfB2NcE4U?lhPIcO{Q!Q+o530oaZv=M}u&C z(89DrvWw;Xpb~3iR-(m|F@|~kVZOk0IV&1#*VV;);SnWHHM(b9I?l zRN3Xf(;YiMZ@YAX{tZ5h?qik2Xi3t*hNHYngiX`tbeWoxqnb)?hqf5q!tu$kQ-EgI zWm-zi?uv)IB4&`dOUb7yWU&cl-D>wdk&)RZZ_Occu}C_!dxLA{w^N+fQ$ZTn1U(CD z<@4w_<|cLpEVv_DSYQ_ZX(#Idko2|%+LFU8opSJ{ zX{pUUc#mOuyE=CV@;u+?0=dts5jSLTVJ(}A{;dOTF<=&3Hb9+@e^9_$#Ed7`1$Oei zAOg%H!Xf0D&~1^H&}aj>Yn-}+n#%p(-#alV0+PZR9ukxwj*>qADZ2~4doGId5s)5O zA9ql47+gXhQvr~J{crfbI|?2P{^4GKfIK*4OTAm>pSR3(O8QzCK~ z=qSten%On3#XT)R0(qLL7QtkJ{ntP&OO6?2G^7HLQ5lIMNkyr+Vrr?ErN_An0IaA} zeC@av-X|!sx*!P9kxVlE7CHS66^uF+vdoN<=~Tj;-KS6%W$v=ZnQ$C1RfLDA6HW>z z95*}$a7UR^|NCeFKo8CW-X(?JkVZEi0kCxbAF3RE@ZYo@p(koD|Dh@l;zE)an3#6~ zvNxm%t?`8%9)6=UEf3ynTe$y+sxTt_>Iv|-N&mIuW81a={*EOSen5bMnhtm+Uz{t2 ze_QSk)$3pDaFPX-puclsAh)d!P+K)b0c|YS)W2;!kctGa+edHwp;C^DBBM7g)=(q$ z)cYwX`aq(7ZN;|;{69Su;J+UUuCY5C5=gRh+oJLD^x7CR zO709I2uEag0{p628enVCM6cVDfE#@59l-2A7A1>-HZPLlRymt3Cq5RPe{m9 z;V?6x90W08T)UoFla*NVYp?6A=apaWukB_M=rr&Hk+q zioS~W8e&^&eo1WqLuEvw0fef+jbhjy!cOc;Gbwo)*%2P`%OY#RtkPXU=`go+07K@M1us}23I7--=QVI?dNMY4wZQe&$9B}ihm!_fAr)t z7jp|uY)iXrUgXEC$Icd&v|0(7f`33`ECJ0>>o6wL)5Bm$kj-N9mdFHSqYsAOUXrg6 zI@J2cy!1-0*Nx=TjCY*=g4@Gy4_8_x+p?Tq48E83_)d1=qR|Vkh_9i$xxT$)Xg@RK z5w^zdFGwHNY8+2$0f?2>a0Wk0KsWbnT2pa!|L&HKqy5Z)1ll<^v*c@A;k!I9$_RiL zj4(v>3!Q^I?aa*eyxy3FCsJfdrmH7w53F&CLqyPR2i{Z%mJ0jn9-ly!i(~IK=Uz7} z(@{*|ZtxjXdo~Ik6csV@X2Z99UE?aLr?}?L$D|&2IPNU4u@GuiG+=3-fag4QDf2-I znr&Yu6tusAPQbU_i6{zf8xAo`gR2?YrXeOwV}v*<=SI9yFge|rzTPxs?2}JLW6rPt zP(9iRR`bT#>%E%`nQAe9_R3bW|FWr8L@40g+wO<6;bOPlPx&Ig&)(^SY3f^h|#Q9yT~HhaSba$=_HDY4HC)fF_`dRv7V(?Ht?65 zl)eke#qqs31C=HY5UitI05ebD0G`-gcpW+ z>q+P*dvJI>N08a~4ws1#GFub|N`rPm%|!k6twT9H09Tf`)14ipV;jmSr`a@K9sQ}c z&}Yi)B&*VQ>0`F3?Ih=px1>_z&{M&G(Z*8{1WVK`@-UjD_m(V4ih2+8fjxusx=|Dm zHskvh+rCPC2!_q)mTkEAK3a>zRvWXuxrY@Z?tCLDcUgr+f)M~HD?PI1NVGldY9@hC zbtprJSHcX&ygO87V#ZP z_OvB6q$1A6k@tF%`no}4ENi3(7aL~z1wU9lw-R^Wm#TjAB#|aIJ*^_PoG^;~S}8dx zrerFW$ClyV#CC#R7sJ5sRUcvG==O%iig77U%jDM7TYdhVHxe57>qwb$tVN-DCdm@G z20Qaxc{w9apj(54XX3`p%3D?6kRWqclZa!$goFX?p#AOr%rd>7n~5 z$Qp?yAkFKiKoY^F2FUsTXV2^%n`CYu&dBP#dm|D0kcDrnTVl<9!vVODU*!2imHwj{ z;5Lf^m(Q^?wGJr?J@Bex2gDW%W*5HEXI4!tu;|VNad$1R6}dd^oyLE4INvO>EEW2^ zRV*?OW<9aGnt7@5+1$#m8VUY+k;ZSa(KfCX%8Wx$o8abApS{u(>bJ5G%qydVes+3H zq8Js|952bY6{cVD2Pq0H7TJ1&bwEez|0QwcmdX$oND?>&xpitxNK@E1JB2%%ItcSm z?cxNPw?080%D-qaxNH{4+ji+Z^Not3h)7`c{Lx;%v&>6ty)X@CvIcRr_$`U1SL57V z?RwVke6_OX>a@Y$7nb=R-dx7+vc!AGGU-r2sDto;`Nbl)lJ9K#61Yd;4(=#!M?>g4 z>D{g^isSB+kuoiw(Lm3K+bZDTZKk>>66d~ONbx^m0mgfi#6=7Mvb=QOEF&(%Xu2J) z60%+{anD&zKl>$`hv|CUGzP+g+NivCOMf)pzRka9^W^)2lmne>eHj#V(j$9)Q2Y_U z;H_D#cotJaAxP%a&U=-%y;HgQr+Wczxv6n>s!QI+vv+XG(moJe>F!t8R~@do{)+PK zt$|CwhzAdZr5zfA#74E z-HJ#PyAKwGMQM)Lim)19RPN-^CXlajH)wgCY?qd+p5L_Lnc@XGl@oQ@k1JiIG9Sqn{bHas*upKaHZIL$CsGNKdwGASiE# zREDWc(d#-9LosS8QGV&S=ithJ)lD8a`l@Bo1P&q^-u`9v@!Xp!v?Do?PzPTsLVQ@< zMlTBp$B_+*X1zs{#7pt&d%AoE0oAZ!gKmpdvziQd-_-5!~DC-a*$vcVI zcMTCcM=X*E)epN6iLci1mN3XOv9{!KQpo97`nm@8H5^}<^52H{Qr_f{p|OI(;o_Ys zZPJ~{gcA#Yv6pS=3#QTH<7EzZz6BZr%d;8t(0XJn7SInQDkM3Xpqx#b1YN>L0E3Jq zm2vf=N#c<0c%sn)-Zme~XJ6=$W5-B`5o0Vewxo4pCxL?aXn40OLM#&D{oo z<5BwtzI3QpjX#{av!CBflFPFotQ2lyWsN4KX$FT;JPf-bxgH_6{Z(Q9*jBRE^33B~40{R0TiF+MSo3Rah3LjZ z5RDa_6{-Lk(xo9C-C`e&=ZYmghZ^i~wmONI?L(0ne#n^4i$5nM!bK(V*J5LAU*M`O zp|Qv=6k{}s$0I23K8K8CZUUvWI{C?#Bq;HM1>vYSm3cFQmw1NP z!ZTW8Ejc|yB|U9_J9Y-v1vguK>`s&HYh+-l)1eO0hTmLmlFtl~4t&tHERtL#*;r?M z)68nk#zJ_N=z;94wr;@}{KNsFQOaQWpu8@_dmt)`z4y1&Iz3!8Y8haTgKLe%aK^n}&ykg8w7 zx8uNU%T;R&z>+2J0MsHsLK7b5yWNVthho4vw^i-GX%gQ!0D0RQ3sg!J> z7K;e=_c>~K1jm3yh7rQv!qEl!QKBRBh#3?~61_b{lsh~JzNA4FF}VB}Attk&A0n{Rd0~;4d1a*Cj?UHk$}DH`HF8O#&iPddK9R^AL1Moz zC{%PgTwT4A6)dZMvcovqZC=R#AmIBG|5uKBqvacY>=pew4r9-7A>$?ma}Gpegp}Xf z=o;;Q;)#S`;d4PgvlAeOQB`zvRn?Ly1}s{I3x~6A;+-LU{C#Zv=DVto*H7=C>BmT- zWf;8HVzA?{GAe3z=3={p5G`Obs}u$e1RX%PJpNFzk%jX(KVTn3ZL@w~v?mpTnLT=c zWP-kL=j)}}wwW`(HnnJ@B$v0u)Qy!W~iY0Hf;lC zocM0yz~DYEV>r!8lT~`;iZgUU(Wkae00pSo1}y@3`C}AGbgeu8S9ARb0wN`Xb~AXq zBHLq5Eb@T6;Qs5#U7ZJ2ZRbA#RRy6)T!VzO^^~&G*R!t+f_#3_J|c$+?4?AGTN0x^ z23(#hctKX^HcJJ235TKAK1F;OrWVUj3dVwHyT$@ZG%Wkw#63Aez-D^))$PcJn1>;wj;J)s?k^HM*wNbqWzDMqxr*GfV1PxL7i4=;160Uin320?_`8T4qWQ zyc+kJ*D6d7m;EI^cYv{8Zov9efCF&Lzt}=lPU_$TekdaLC1?mqZ!a5ikPSj`JxwiH zo46(pBEtr6k2k$=gS_r*ujHsJ#!3gi+dPCyxfpOAc)KmsBOyzO9b^rx^aM3n%Dpr; zK*5W_ZE7?}|JGxLXMLEDTr_v8Rz^r7kZ#~A@(N7QPvs^XjP2LR*-d~1tW?Zs@Y z5&E53>Jj*B)SGkt4BND9oYz%ZS|ke&YHq4N{X%Mpt)I>Gc(kzsl-3cTjw>5dYkkfh zAoK$-Q6r|7vHsp7(oo-2fB5&Roo-J(@z!Sqr%E5q%dRz2(ajTA5;zxx}C`IeyF7d#1leUKvFW>N`}`97MM zaoan9|9R$cUCoSc_%*BAcl4_|J(vDa-J+us#&_1AA%XL=0nCZ^07n&xkbrl7iJ%6F zl#;T)DD$pG*MRi%CW~L_v(GkB8UnqPLy$ov+qb={s1d}>;8JW)YC@+9VYr5{_Zg6z zNqn}$1RKY)dIsmI7q`Y5-ERt4Th)5n6*KhVX2uKJ?{{~g!`2JosXhKo#)7=dvf_9| zm2Odr6q^BGY%fj3?>zkzPW2g-S*u zJ841qQoosUJs|ztQyDkEBRjAj?ix#M>r^)>d6^}}n2#s=f5!Q9719lvm+VYIDAnL4 z6toH5hnSgLLVt#{kj03-#kd+s&xyGwo<2^bb@;7IbF+fp&6w5mW2`%Sca#@>R8fHV z)lv+U_8E$gw78tTol3mxO*W|_N$F4iRzx)0#>;v)z4vnYG##kamRat5d(E&lgv1(}zNn z6=o+EzE-ek*d<1GZ=>dJsXq1>RQs0_pV;ww{vwJqf=Zu1{bem*h@# zp|>rvrP~9F^+tYJ(dA?soFQiC20)&`i4*}Gsj*>qN!aB=vn8P@Z9p{)b(z$DbhqhS zBl?oZf>?}{x5lQy6Z^-9n9bQsA154}LmMrdlZ&nE;)A$6g+8ErQHhITMx<;& zY*M+Ci5P@hN@eeH#pR#U7ixqHVLwD?>(#T^mzwtTgWcM+TUoUDOK*Qqcjq`T-ZCVh z4#&=7;O@qJWVZCfAVNzDg)YpQgrSH{b;^3gO$YM|MtU8vxXUK<^LAgCsZeTE_6M}= zs(m_o<3Tm0UZi{zz@>hWTKOtzB;YD7 z@5{Ei3r!*4y@XgN5e%+&y4_(~5H@C(-GbZBuU)y-a_pGcxQSM|rZq2WtTmNB5V5XA&oxj?>z0jAWx)*KedbHkTY!8L zJCV`7s`||la_f0T4U!3-j$m~(w&t(Dn0=3gw+%p44&8c5z z-#}*X1Z<{g)2yXaN^U2Myc@oPkSVhgxn}(J_RXs-2{`OZZj5ylk*VA%vU|xf2Udm| z`#i{@|BO@NCLUz(Zp^SLehLH9mL}GaLo__)rDH)SUL#4Z#z}iS?;Mz4wWf;V?I3jF zmpi;;*&g=aVz}*W7Q2ZDiQwj&lNoT1k`|QVZeyXBJvc!juB|G}Ct9;YmHxi`QDC)} z3N>YK9}qkbvkAsg06@d^3lOh*u>|m*?CCDG@ZrcJQrPO(r2b6Aj1#g)8L|FW6(}LaMa~I@IHzgLH zb-ts>0#)hYwjaN3C09otDE2f&Zt}UWy4MJ5>*i*@q)#2#A<4;MM-tjcwyzO9Z4tK9 zWC-D7U!DuXaj8&kYC(Cdkv=hZ|6^#?ohNloF$VdC)|Byzzm5!p)ii6brHKcNgzE}9 z0ip1!PO&qH%8*K0nb)|?fIp9hC_oRRi6){WyOzYtkLDY9N;l5j#`sq~)CV&EX*jKKN@Ar2~hdms9b|cq(v;~UaB7zz z=lC;w{`C_xExdbepf?0lhbbAdUf7@~$<<%USkY=>FWia5jwn#@alKAGsx6EzIdV)55iG;FSA*o6 zg9=7B-mhe@h7?K<#(MN|Wn)aOV7T@7MkQFh1#>QVDHBdGO`UC6Ms4d7j+Ri}a$Fm; zu`g1hV0^|+P|KwNs`wBVZ#PvGnUCQsEPM+*X^jXIx27KvF}`J!!O1cxNEjW0oZfRfeU7O6FEzhq!%^4f`hbpdyoTCac(HUNPQn|F8SIgZ?G!Ns)$HtO+DwWH8e0)k_qu9>NmS@EpwBtih(TtUk0t7EEZw2%B2^ho{@p1Gt z)|tZiby-j0KROOs%>_BFqw6XiMrJ&gBl0Pi$8n5zme5;oPIV(@cz-09mn|DKPS)oIa#E zdk1$Wt=Jm#x{Yq0Xs4ux`1=YBZ3Sm-S4tl?Z(`sgN~+5rGFR#Ae{PGpD&fF{`v4db zyL|(Y&G_gW59bLx7rbOlOS*7(YFijRCA-BCd{Q>|*_$zcW?^0;fwL|^IBh$XRY%K7 zv=e(B)t&R)f!+jXjOvJG({I32ze1W?skOE(*W^hr=*!Y)<=6Bd#LEThjZMk7>>VZW z1i#{G1|KFnkpt{xgxH^kMc5i+VvFz}8dajGU*kxQ29eNnW)(JBl0MA}Dl`cxX0*M5 z&idTlI8zU(KIQWw9l-y32_;x8&ZTx~@Qvl`5h#f`TLE3{ho-ieDckVB+||C0&56^c z%>lUz4)!IAu098EXy+N98*kqL;1kUEGXQ{IDu6ZyWh*tzYzPUGwFszgs7njRpvKAr z3C0Hgh!;#B2 z7KWn9dzgHA+(#i2YuclxFK2VbZZNZ7ZV+^@du<-vrwUvm!VFA-!HlLPM_lsjZ=`UE?(Zf*AxmfV~F>vjZMb@mu(Y zPXjb1^OOtN)d74meUE5-mcUCx$@-;4+JFvHgyH?6J?KMFAADm9-h|NFIo2LI2jBWm zKvAkHgzi$lA{xYcz_W|^LS)xsRCAupPO|SadnaflBit%hb9@+4tR>KMszfIC^A6N~ zDHl&zHG6%zC*n068wJcxF?3&sEDiX=g}{vM3WTwp@~k7Mn*h9XiZ{4#B$*SseX@cPaA?1Z26w>94@-!EW0IsHRLS4E+<|3jtc z43r}vh;=WLF_3S5P$nRJl|IOk?MAZbR9rLam4kiml0SD=dckaW zWkRO;yc36{sqb!TDKxS3CIKAB{?vhb1klD624W4ZGmZrGzk;SNrNl|1)k)x{hTKx8 z$iUO*PczjN+4@%iZBf<)lub1*>22{|A6W%Vq2u^NrMZY1A*;?p|4>~6TrVbOV6%^g ze`_HCp$Vp->_1dr0{6jWNfb#W_)H}Gun5TDN3g;7Xm$Ul4Qfv*^V2<}|L_UCO7Mec zpXqlT93Sc~Rc#3DXN*%6=68_fUwa!g)7g}&RPe~aBIs`iT8IF!>i`h?j2=!8Cak%` z$+1u^V9itlU4y{Ktl{oEWLl!~AF7)f@V{-WumjL!%`G|O;|_kIl|NLbDD{Z&*K(!7 zV`j#r!GKQ09%`gi$N_p%3!HLbsW9T-e(q;~+qrOHfd2b5n*43!-;Ks!oo^QP48hAX z!zb`zw8rc35cp>69+_P8|I3W%o@90j?aN|--73GUivm56+?!sTIe(%XE~5B{DyiiT zz${fGpWHnFMyYanWN)`+SBj^;JaZZyPru z7vbMN(vMSoX=sk&6ICNn#1r~|tl~e{$Uj!`k5&Blo%0{7_{S>#v5NmVR{l9b{?lhP z`NyyLf1Hi~0jr2`AP4>{>1qaxUrdakCmXhm%YgisLO)8WKVQ_HcsIHKeGNUf)fDRQ z{a!O(U?5*z^G9JOmsj*Ms6y$qhj{1Ehk)D~Cni#8(Lka)blZ@)ysZ2J?uJUD@V2#d z08ubGr*XNCJ4jM&*SNK@C$h`C)J{~_tk*_ z$|a(h2;o$5pdqX(UZo+<|IH!yR~>Jo8oE|54mZnU!=@+8aznEK!IBY-j{LtRPcGaU zh%dhXLzPh!F#V1<;`nVDmvRzKT6>xMb}`6&w}hf8%eR<8WIB} zhy*IJEF0&~ldNA`dq>EgvPPY~^r5kidkf2DA|{UeVC8Ypc0;07T*nle%?^1>wj}V9 zZ<11%!Lb#18NQxD9OLsNEkRMjKGAMH?|XwEVwU#_VI+XPIsI(CP*%iLO%`DE@PPY{?kZ-vA^eh%`Bn_-Y9 zrf;e0RITw-GaHX4cajp+*@pd>*^K(!maI1DPD%23>wcfMM~`O zpX`va4b_K5YPBZWkgqqg#-hW#{N~fEbTZCLk|vn~ZR(pOQyegbx|h2KJS2z6U;$VI zZX^aa%=4Le#OYihWt${FxjQUSk$>hALobwq1qplLkU5oXgM?cFeK?7+kQ6CeKs}OB z7Mp34V3PXNoRaM74iSzyJb!zo`e3k8LnU5}C ze~02J&o{KL;SEL3Ad1TY2Jv{JUU{B-d2ODjPTr(a$zAOrFw_32x-FjO<+B={AF}&r z#=RQc<2&2VKUU^v7CeJ)Ph0MKOU{W#>Lwr7H&3Zt-GlzvMw8ioQE07$@$O+XkDJ2@*SZ7k)qnpPv588`F%O4S2$!sOGg9;^Eo7zUSn#jfsA<68$qvStS z_apcT3TWnUBQW2DTSwj8K=E1qusgAzr(@`1j6t16*UMFxFFcX1d$#PVLJ5Mm;h_5P74X zJW%0fAa|9;p6L^?WC6G*(t~7clx;->Z> zz3r^~Nbuo(%Nx-ul;G@bF+wjZRx^SRRz|z*#IuBxreW^2YBk)&z}z~H)rbX+NfiTF(QUMNt^D93=AhZK%~;GCG``YP#zHM?`ds~^SEY9V8RC1Rz{hI)GI`L4&LQRt#8 z5L!f_3Hk^DV)9=`xV z_4HqcPxtH{fnq}84R#d+WCYVkq0fOFXZtYM!}GvUnv$jhyq6XMSgTC=#7QH43QQ@1{;# z*enuhYG&$w!9LiUM#Z0yhiFX4s#(2%xyQRE26rz%MoNUq#|T<))`-c~ zmt0yjNC(f-;(ka!@CyWih>}GLKJqcFF#Z(o+yN+qlcx1p74Ux#D>acc`T=7|gw<>raW`?1}%6KO88zs6XhOd{I`rhwS zmQZK)qFiu8?X!=;mvYbq!)>^6%VeY63<@XPROWCsZQN|kH!$45xIJkk=FIbVR~$FA+d~3n(d3Y-W)+_)S22F+NjNorbsmgKP=+&)YkaTX`nVplME7`W z6uJ($%}h1G%mb0r(JHbsx#RA?M4iF7Nx&?jCrWUD6_FhWJ`Mp&6>;1qBvRtQ0BA-q zX81$3uW3v+H^VHRTnCbQ{}oT$0Z=mIy_t9DZ}}7fAU1mO;15*^gyee+1MDd~1CFf% zxUJ}YfMvx9q{Uu=@9w6ODq3Da_wSNYfgZqsqxjtwfX|Zv10KF-49w4ebG$_UfB4dFA4IN*c^h6cuN{3ZqycutQ*E+C9mx|Y6HeDU*`fO0!NK}2(GtoC+nxSQ@y-aAk?_`B z(sp}4W~;~UHI1_$*cQ71hQ2=2P%(?I58H`Tkh|Og`KqA7UkuU-&c`xh8tb<@3ncVA ziXmKacpF9MxMz-PuQ8^D4=nC$53oN;*FcJ#_!7`pR;+tN(qXoEpF3h-#0NV4^R$?; z<^&aPoq{Tr$?j=9oq3u4*sD!lMt!sHVV0-S62kDS6Oa?Oza6swY87ALJ%%0Efw5!m zhLhQ_;4>gV4@?$2;2Xvjfq|{n?#>^ok#X`NV1+;qd8{SSqz+%7r0`K3uKuA~SrH;^ zE|SDhXS`@Ee6U-H+)+Z2^u}?WU5U5>OhFOAgI|9I#=k4#1Wbgj55QcdV&TUp@S-6O z$}8mmMyfCFKeR?Wr^a;sC7k|;ik6D%T$-NRD%FKFV=9YKUy{fOnXQ~?4G+mEL2f88K}hyQLZ2{~tSn7O;+i#QVtS;?j%teOTv*v_Rk&^B2{H3gKTl zRKcSE_7XOS92yWIdcaF@kOhdDJ1@fQ38|kU4x;1b2BE*?XkidMSMv3yLX41PD4Jox zBWP4jF_FJ=S0cr(8)wIXY5@U;cyShZ-kWrUiQp%qV!_TMv3u)+lA~KnKLkAo(w3n5&Z{)8`m&HoVK|s%9J4!*QpGc0>Tu~-DA*2o za-8M)=I3_?PFF5{rS#8uf7KsqR%Khxye%aXl;8Sr`UfYV#f?GC@g-=sOWWG31(_$u zE_Xnp<8qo9`P?90WK|%#P<=wl05Jvb22{tqN~$A*O`0Y_PI&jPCxw&A88(4MVg1flHC~RQ zd{#HyOIfgjEbP$_sZehtfV&%T5V`@eZMLPOR{%5Gc(UV0HHoY1{33;=C#q}KqNwHZ z)VG?Br;Hvqr<1R}yr*!VuAwJG%>6pL5%FIq?(Y}ocI>tp;dlNJzY4U+s$)ZCb=x== zj?<|CO8B@DRs#2!zsJE8t z2SGy~tapIw*W$l8u$uEWlt?ni+X;v^{`Ab6wdor|m9~64_AiuUaoYF)x!lh4>Xr@2 zo{442&2_&)Mb8NMUEIaLyN`#b$M7-xS!Lep>goiA3+#fP4Zx$f5JW}|@i2i2#bo84 zc%*SzO>fJ)Shd%vZ?7e4D zlk3+$iWTY7t3;$q@6se&5fBiN-a$Y>Km-INKu8n>q$^USMnI$!dY1@+AYHn25}Gtk zr~#7rfA)JmoH=LzX3lnfF7!!1ECDZ|R8s70<{=w?ApR(#!pNK0II-j!;pvfTl4U`$@#6i+T z{6)~o2>9eRYD-f7?j!xUw$-6d@|GNBkLJo*t*CgY)uHcyJata?alt#;K;(Nz1QX1b zk2?|kuo3Mg4F5LatxMh4-x}as^X~WHm!0Pu~>0Y&7tagR5 zbOHi=$<^QfPqo-h5t(`0CdD zR7Zkc>5>7{>wMb=D<1PTVLV;WQhqTP!BOUoz`+B1T?}NV5B!git?h8i#sd zhx8f@<@a!75(DO~?*_h!)TO@p9AP+^a`z9FpdgzDtCJ&adXyr)Yt0@U1>N@TSw)(r(z9 zJb>1KK8eSP7dks`OrRd!?+}@RAgw0?+tP^eLdbws2KudMqo3nYnYTo-ahcMW_q`I5 zzVctI2=~hcul}_KcQ>91qtFPS*+Ap~W&OgV7et@XMk^K@?@)GNy7*E7RcoGaY$S(@SxGO;&U=cL^jo2M0Jj*(5RGC-%IE z%~N@dfdS%>XQJ6?qau4HeZ>xxenfYx=@Dav#mGe6?@z|B=C!1`2&gx(BzwKwX>9BE zg)WM~N^@|ef@Cn$G#Xcq$pcYmX>2trhc0p~$P1F>N~k~zR^Vm7Je*5@o?X=_5Z|m}q;>bTsHIVm zFpoJgMb7B?GgD8mIbXBAVZ~D|GJU(wNDZG^FG8mb>eT(A6;PWN)xhyzU4~98AhoFV z6=mzaR^&p8Y{!dZ3a^Y6(imcLSQ1QaL0!GOGG`B<7UW7zu?5m2Vqb|TPevj#&`IIahLMM4swW05ScF$nG0=%6}=6=4Q0XFzZb`L@Z!f4;TyLL6E0Z<)_azT{@j3lKYFd~l?u@QhceNE&xU7ntX#B}JyW zv1xB5RtncLxt^{aA)F8*C(R;nU2-WA?(`oLm4D`$Y13)2Ng2jk@xAWVc`00#m81W| zTzh(A7=vo3rW?0@26IoJXqnOG?PSCX^0U<_vU{g7$rvXZ(K5p82JsqUj``}Zd8cz` zn3u_+r^}+mtk?apQ2*1%6E|^al}T;dm@dKU%mTKAs?XTX@cd{E^(RQjm)k%*Onj_# z>%X^yJ+NnC-bW32-$vGKlrg?H*4>8B4Llq6o`3;$`%Y`PkMRtuGZg+(lOK9_>=#s_ z=$_QU0GN~bV!Y?MwcU-Vq_JTiQ(f;c>xUPkuSE2+ye#>6&gln;q}BvC zcjse%jm95e9!29nt2L;aRwjHa86PU7Z(_B~*`Q6U9xXQYPfh*wYh=}~7lgM$&6(jq zT@D7z#jzxdea8>j%gVE2y?xP3Nz<`yQ0&<)jpaXrJh^lMDY< zdBDeMcC@phMh;`q2X!iqi@scC$9IpQda+zr2I=b)X$HN&T0Qx+Wz#9ea#CT#&*?rO zLpN-fYVi$N5ry!HT78n?w zH+I!TMM!Q7gQ{QpcbIwa`O<6}HqS7j_=B6h04~WPvEXy53m(3;Mtd-MzuE9^rRI%< zf?^sKV2}GK{8s7RL><5h)Yp6PNIs%L_q+`na)RAR{I*Xjj|$KLd{LwzE`DRk$Q}<1 zF^e>MZMC3M)y_Th*B-oznWQEIAXEW}Fe2KRt-5ao6uqum2+3iJqVQk*6ld~M@a=uk zi-E|2Gp9r+mcXUB6w;GXsKI}k+CASkMk=>_)DwAkV@dz#_$N;{A#EvX3yth-ZMgUo zZtPpO=Mrb5|7rjJBgP>O6v-7i?712eL$xxT0i3VOwwD(bnBQ7KY)8@$pQsW91Ixl{ zj+ZCuZGS?2FNKS0zRBa$QZN4Mq#K$eXdEkRG60FlyVKRqnm#`KoOotr&*5Rrkk!RC zD1Ouc4{%iYO#bwnbMQB*!V`qI2d27}6N|Jy-C0?>xNwL527Ai{4D@7rHnJ??a>~&Q z&(*g{wF(@fDFIGYBwKPZMFHwXL~-`|@(`=c2=eouUE|$qLLS=b-!{#L0v=6yd)&x& zZ^{a}#v^JbFgVy z6l7GXGJ&%!G^Od#E7!j@TByl%YCvWJ49Vn9rZ15B5VYV_Vy6lkQ0s#*+kU4=i_1zm*QG=V?Pqy z(pzwseB6a5c0E$UtN}2uXL7J@`L3|3T546B@yFXs=TU8dLD>Jbxci?|#2omp>vGPd zGl$zdNL!LMZ8b8&x>5m6L&!n3+%sQ_al7>->q=nPhiL6ccFEStXIG4DkZKO2{L0bnsJ&&R3d4I7#&OWzba^bO9Z@_XgQ7mRx0{@5VC}0xo7S3diPyIth ztrV}`Mz_Ffjen6=?v(94WYUnCB4xCrNN|1VTd!jQ)1gYz@je3*h<|}nOOto)mc>>G zEuSC(J!v1-6~&$XiIY|_9d!w4%LHf3=o`q>-Vw{R`*)dhRhziQdXx&3N)KvJB`vT? z*OtrjF08X)`L7P*ZKmn`=CF$l6bzQMBwcC8{Bp21;r!4P=t5GL71FU0#2WKje5};+ z(~9uTJ``5)C=?hp5QYtcSOZbl{X*4kA;A)+nf#C)X#!*5LAt9^nU{+h0gXwll| zd)J5jHS#Z$>gGRGxKbF8WW(wos;%TW@Qg9*VK2b)V^j}ywk+ zWW452LUKn|-XFOF=Kz8-1+EnZ)YIMYPTiVJ;~HYKiBly$ha;Cf)yNyOzon(6Kl?T= zQjVR|w_Hk(E%ZyU+F?y^;L9zDCcy0P;Fj=c$K$SWDyBKlohiVvzs#Z}`eZeF_NM?A29<>hT0&HaC^7zv( z=2-9q7Iv30e=eAPs3+m+x-Yrk>y!^-_2&S`Lt=h@i5zZ5C=Lo|t*cVpI-xZ2Lp3uw z#XKp!E`ZLm>U3pXkN|h(4>)>9?8LL;BmvJg4r1dzT2rE>t);CaW%84Q zy6LT8hTz+cg~hs(FOC}x{klxNRh~qCdV1O9{+Tb%gT!HXv_~voUeNuzc%AsJz%$gS z)I%w8a_M!ugex}D_gkKF?4T_sQ!@CSUPCO4#HupabBR*`TmFmPP$kK<`)C=8q;`@S zs0DHQsqh(OBVTvnfI)*V6<>{Bk+Uh%{~pb+rzW@Im2HqFNDL!q0!BQEOSQ)FB$E|H zWGg1w11H0^$yDGyohSHxSL1$@<#Pw6)dWG5knmsz%B?{?!-<8n zKVRrfjTyu`TU6JP`G;KpefQMBts*#_ONjzw~<0uDHlg&AwAIySGc3H(kO zkNm!y1BOlzTh)2M_-Qo4q*y=4^@_Go%72dy1v-8RX1-TYKO+espvHwC0ca)B%Kz6` zx21oR{G|Sa7mWEIx-0nqf$sVz@vTcTpX-u2y-v~vvUd$2R~x+o@6pzf0ti=8pwNo9 zV*Eq34-8J+&XOS0p#Fhlb^l}D{tqUSiC9t`g*v!L00eJnu}c&fVxvwQo`35<-u)jU z;y+fS!2c}9|Fe1E|EJvR|18G;c0a=ZM8^N>6k5?GaV#B4lhglDd4Ok%ziP@71@TT@ za7L(M8Pt9}P=f4whcaNUb=;u&twaeZvowNWV?U&TcqdCbp4)+8ws z)RTxFFP|qduH;kcWTWywentQm_3tW z0!EAZA&HnA^-h4U^3S7_#XW#NCyB_&7Qd@tr>!lrL~06P>p~AFHEm6u+6w=V`_M!H zx(hi`t^mbNSK}!7!EgL8lniRQ8npNB_pBKOBSQz@x`4GM-}#pp{-LrV0riFy16e@D zu{g%*ba#|UJ<3Tjh66Es26gHHhzK|r?*DD>u|7^Vr9E=G1Pn4>W;*>H0Mxz=A(LS; z5r8Yf1!{6|3lR4|f1&XGp?dckxQnNll5xMk=_}b}a#WlBSoFGyHXGId#@D&S3=&Hr z#WdvXHZb8jgmWTW!94nMuSLZGvmkeUfZ zaBlLm0k%3NVj@eX_3`LXb1N3RzGES4k+=7D641f zp;w9CWkgpUf>C*a_w3=9TAF3?M9V0-pY^; zqLVuMCiJ!!ShE*7f$sMh;w?&FQxAx&TYkWZvw>qjpq7h$*7efJ6{IU{zn_)}Vyo`v ztX_Ek^-#0nH{Rni9^=ua7?3l4DY3`ZYo$3aLx~<^t?ys>?(qlcV#4jLe4AUgUX?T> zx?C0;`lhcB2+4afX=sLObSHr|la?Ng{Rd*C1eOddG&~aU?~Ej>UrFIAPqAbKRkj zChHA@yEt2;C^02-7hkQ~g&ayL=(l(Z*nNP0pDO$KtE#$h*Bn;E|4U!YfB}FH;WuoM?3QH ze4?*a{WS8Mg_|lzD706#YsLQ}ExsV&)QU2*WB1&dQ{Nbpv+*X)fmn~fRYGd*(+7~`SCAtF`*d+c(d&VNX85&k zRi~p45CgbE>zre%e3?z+%`qRWoQcH~mFBxkK+&kvn!*4HUbX_%|AIR;Ih&HE_1k^u zFfNNM>ta*O)=QJqUz$k+Fb@W#cHXVG!?Af5Ipc+ZwY`U}%`*j- zQte^R(gDxTS%YO*iYkREx`t(FU&XvMbqv4sraH4n+_JDRi!kw>Z;QiTxx#c%Vo ztGgCzn9P#913WvDRcG;X?5BEX4>v<&c1MA)SMfXCl^oD*yNmdWe2rk7?xiS>)ianq z9IJ#n`3H7zhb4l{(!0X+NCUwWzY5|^2S-a%?_upDM<_rA^gQXp*)7<26yC@w62Tya z+$9Mt|Gv67v*}arpdDUrlp#+newgB*bHwYd@~eveIHlyBPky%nkB_hc|8ZAq1qe7) zmzy+I$4O4XzRvk1N1~{wFILKSS96HJ7$+msxs!FfydV2G1+2WTs?q4~9-~fuhvc8@ zZ^`sV;2i-)joli>_@MxzwzY#}yz*|2f+Ug}J?~%;^L%ieNk7M{EM)OQw7?sTsJ>Zw zOt(e1RnaXq@p^Dd-812?4gWp}D(Z1X7SNrWV zW8i){Y3 z)8~@Z)}QpynrRK(($>~dhc_ORSYxn2tW; z-kJ>I4tmUzW1YA*C#J387wp#QxNyyNQ_d#|dQr1%=T3p^Fa1RvSw_4@)gUo*PHbK; z-flVG#*4lo(P4L`s`8T`c1&vMeOlO@8nd&lQ7~q2AHVmENAZT-jc0E23MYbRe~~mN z+y8!BH*#7Bh*(7z&!8;>1$&p?ecc(rfO?}TFz{XsuQ)fR0QVVzU%<8C>t%f3py zolBtRcGJ8}aJuH|KT~{l4R!yi43pmotNU1EK_BBTaf(*B-e`b1E2ITkaZ_Q!g!8J)8-#Bc4yZW16 z_aAOa6_keLF1L^(`j$}(ZzC1`<`&apG4UxUI20gxBiw=Ia{DkjChy8*kTDe`)W^UC z-xw1TE^5k1rgxYlCe-YiPutBxymn3H70_6B8A#UFk zzZg?G!j9~ryW+!w!(kS&V^@q{=k%U$W4}L`U?JOL2 zj@jJZS4nY?8yoE8eSkIBsCZZUuB*gDD2n?l2?fF^DLAof-0pw=hl;EHF9Op!FzfTI znRJsLh(@pEz+NlS@j6f=l@yjVX7(`mul@4CLdnFmYk-MX)_=odHfDosQ9RELck6;% zvkbe+QWD;bKrLCGJfQNPCV2p_8uU~-IMP}bTk4`PKbtOXJnQ0FS_TQet5`5CU-<+m zhjyyKSBErd;6IQ)f2ckRhxa%E>Bc{Q_!0sX3`NzuL1&VG=QEbXIs%vlzKZonk<&te zGEk+QXs=U)?rszQ{n5P+qIWUWpq8M0IMM@RnT5FCbz=Bo<5*cVxiM?0K4gdtqGJ;8 zd))YBp^`GL5`6c%xB*kuj_>se$z5b+cKDZhx%s=@S8Z>a%d;>Eir?v64OChN@oU^Z zliRK%0u0ELY^P`|P7Y0>reXaQhQqMG@RFaW5E800^B%4|6G$*tE`6V<|MiM1DjJKIj_aV1sxo%-XbbO!Mb2#qV z>gTu_KKdD8(tAraz$!J+vMmM3)RHyLNs7R#?Iy-s6QTgiG-#V#*AX< zU*2J*Qanx!bKd;ya=m=%V^Ra>Gi+Zr>+BUi`60b2-;b(+(*dYV7YmBe052O~dJ^g$ zRq#{IAsxZqnCa&0#`oL%+1tJ#m8K-H8ZOu>-KJXINA=z-88sgtzEP*<87Nen6gms z=ChJD#^Ey1d|efp242oryZjE2z`ZH6L-*M`{i>I37%}OWrc!r`HJfak_0lq9fvNeen+)}|GsrxKl?gsWDMuq_O;Z2hrg~^}g8+_fPF5~QVE2Du@tGXX?{O+=kG8+} zyS=L9gL5ChypCMFBjy%ZNp;$e!ePos6fPg@;7 zOVMLQIMaxWwC4(>73e^|znPP6I9Xc;6D6B=dRLg(e)3^ue)8?z39_k$B8Pdkxbk@1sjZlxCd1Z^fz##HF}pe$jk&76C6A7wi`I5 zcnG$Nan5O1C~w&_UR^!pIMmn=6dagy03{a zk^bgL^`UlG9N)As(CeY>!^a4E{p3*{pNPrbB1zh>xcnS<3FcS`V5h?l^t9()GI{7_ z(Skj)+I@9q4>3OSCi=VK7z{e$%fbVDT?lppe=|snM$b0L$Nz&~i zYe>QDfN@8wo>cveuhJ5S&HSC)1raHB_YyDO!`-@p3PpLqcjtcpn8R*E3IKWdkxsiy zJ@am#l~=l$s}QfQOAURD9JoDw(N7x&fp;=1-R3cVjo{J{l}um4zk34bgxUp^Zkh?X z9Q7~$p?c{5Wc;s{p~S~$ik-nXlHa`?X1uL=C40b-(}2MyR#DE zMY#Qp5BH0`%N??Fiv_p$dsp8;4hIAUcHJLt#F%uGu4eI|@PslgUP)~kug!j0Z>uU z2#60ee#++*Wn-ZddP~JEgr~Zucl#xBa3l%P z<4c}tTSg`|FvW0W`F1nK!=y*o_LPQXusSiWNX>ZvwTepLl7W(~Rt>oaWnblWg_?a+ z-+CWhz$t9iKyMRPvp4{&`fns|T+C>DHO6??d{ideXs?UZL-`@_gL7RfAs~(ffbfXx zz02yTOC(rxYs~%kE4(BZfp^6K(CdSzZx#7w64WC1*C_&P2H2lCOhv9qbB5~ zwpzs=lz8m7w@B~qK9ku=m21k?8JMAw?YJzL$8_`o_|O@Y5ul!UE6JyW+v|v5h4`Lu z-oQT~94iyeb33W6{K;r--;Hagx3$d21e#=MB?oFf@94Q~1o(j5e zqwC_4PFn%9p?!UEOjl=SdKnALdc2P1|7r>lV-V?WYjMs~kRS}fxPTBQ?>v6UG&g>P z`$Bwo4cK3TlZA0??LT}4@y)&ri7KNbRY@es)xVD@wx_!&y`Mnm!x6bASCILs^)9~?zH?K-DClH-c|5sNE) zb-G$wAtheLQh8JNknYHe+)4qS@dfJ|Vpve+Pnaa0GG0k!#aPOEI!cZ+w-5FOxcUTz z`>y53qT8*Wi|4kJx|#tyDKOq-=_R6Wqvy6A7Szq_%XJ6mpwKndAr|4IZcy{3W^(1r zmyYpW#auDzPfg=nb@M-JB~5L#o_0W`g!^y4FdqI;Fbd!&qV&&r?8ms9(l7=E;^{Sz zXZ2v?EU%Z;bE!`4HqV6Ohb+pbPh%{eNgxTv(Fb5=c&CpB4Y}#_?3VXu63?WsgP9|6OovvWE-yif z!n}DZ(=8L-p^?r6zW=>L^hwr>*Z>@xh!o-n+Gz67-*321=;eLqE{_*idcwJZ`G{Q& z)-x4o=(OUO7ic4bbR_7LTJr=IN-K|5h3$)Jr-8a!*(!TdHB+vfQjp*fbj(?xdyyRCM zj8J1@IAP@|Zkl9Ba2dc*c!=t_A^l$c@9iEw#Sk@5l2U=UT4jXePv89c0e|{wmRctr zr7J@{&DN1RIxDL#D%4$N8cxfwFk|LCye|VJ`K{Z$AzymsCCqr2hl;e?AjHK+y~n&V z#iBHQ-B&bE1k#Gn!N;|I`e_xXo9PsxppR0+SKH;xSQzt(NVKvO+l3qj6NI3??8~BHc!y7TzTDh2uMS^BF9l4TL?h@8f&QH=? zALQ}1r|^adj7!aKDTJGQvTvd!R5_2r#q1xSi!Nv?YPPQ?eG|ynQ1<{uw&AE1dtBNs6`~CXF zKHubI*C^T~&mji${p+IzNllh0z%V6b2fGeAiN$7V%KB@Ho&~F~h(loNI9r>2IEI*6;-Dsh00;{e|saw<$7)(xun-JJnc zS@9m7G6|GR2aRf<84Tti3($5qp!ft@;CePoH4WH@V z$>@Q{EnXttT)rKK)9v@wPJ$|3%WUDe1?D521EREmeRrT}9f9Tx zXVD$=y7}spN)bV7_m#1ZjQD0jQJ|5Ur=1DQ16dRpAciHXD4@TY=4SzB8A82&6pIhv zk(M?(RzN3XX){(fwNT9_oHHhhhqYw2q^)26lu=}vgiIvalq^;Q$Nh+e}PsK6=tBSEv&tU24;(*@On;ZGO+dyG^$M$o5|>oI|*V^T)m4EysldP-%v z_o#t6OP$yOosL^)_n&|+RKk~YfpOGIA1RU=iZP!kST%yBVXgq;7Q!kS&AA78ZlF4S zwSB|Nx?^aqapQw{m0cWh;0WO7QbD%x8I`|v63Q>wBf;EXJJ|g})XvSt(JBUU5Zg)_ zzGhwY14||q+)eihf~vH~%i$W8oqO(k^B3_mm7dGh+nm~AUJ#lR%h9NKR{=}Rtox8R$p z+e41SX}_n9vc{rkYLp)!Ca`C6w5AHNbon+lh9gw;l~OYE&#d&^svn!x-kj4NJoPER zb_3_au31tR-CC?kZD6&n%PE!??_4id6UPB-N}Lqa-=pL%5GT6{B?Acu30-`8?QHHr z6SMG_j2{Q(ms{;(ZMyE^sUDQ=6v^$MvqzGyo<4bo0%~2c(^u67d;&{?a@%CzEWs2Tg2~SmUeazGH;0OWn7&xPE<|g69H3XjhXkJaew<#C0_QqcHR-J zQ528HJ;r(wGh)7CeQag}TXIgE)YSh_U4eJHz}-JE|@he^PIJqj4F-Pi8LfI76WsGp+Q{2wQ<&uY@Aut?`Qv|S$L)Z?|eYH02@cK zRZ$p-a4llgvdo*So5yOO5g$o9qk#)N8}`+85$ju98~P4l>&XW-T0WkVJL6=8*yV+h zG*Av80TNX&!vf$bSWxzD52L0p_2U$3qPkU6V~V0|{I&zAa}+JnTVH<3F7^)B#q?qC zBhGGkO;aa=g)*keN-nGNA!$1koH`eIv%9j~Fz?IeY@THBD1P_i*u7lzIak@HD%W-5 zSC`t-UAEfXm}g9*#CWtO6+X}5-B>Bw-Z^buBV>6>ueXH}3`kkb{&3{SC9FuN3(E9u zKk{C#-XGj+cb87P?@E}gQS^2e8wOc+wdk0_wR>|^ml5=jo_kryGl~z?%zI;%4KXv) z%F*HE;+Sv5WTk-xD5HfHwH?jfkZ8NO9Ch{&J74d-r%sQJOUxMqH z;wnhzkj3#Da01IFiw7Qr_K}{pBV5a2(Gk)6eZ2!UwkwcW0Qq!r;eG7Gt28}6umMEV zF#z1_EGMB7%lkMdYT~iBPWD4$_zGEhNr&**ax_NXK`J-*SCO}HJIpRRMIEzdbnYT6 z(FX4YnvMIr-Ank)1lhF*oEjl~nwoh*8n!u>H$nqOV}5`eJV z%c48)D?ByX3B@E~ep$kB9j*NggcD22^sXRgRyu}A^$g4|I&C)gT}=@k-u{TSdNFv_ zcvWQO)BewiguLSLOoqX_a!gz^2Zl2rY0cxRKYtH)<<2dlY4IQs{myC>t?%<8#_IW zrC4G7=9eT5HKtmn-!lm>pBc-3{!8f)#)F5S^W)@xtf3n5jTpm=u|bS(Cp7Q#VuBPP z6?snE9+5fPnFeBoZ*^q?S2C7m0kmJzK5(YHEk7)F#yuXmit8vwFS0Lp(BkGZ1iBud ztSiLrGo6RDCCWjqG^W5arhhlQ2#@Sjr;YZxzFJM9yAX|O=L3jwM@UT8SYQ#8`@Gcw zv%jxzFn#0nL#+T`HAS;K{w#*{qpfL>wK(m^@2=@t08EW?p7FbLPyD>pyf;%8yatMOTwja4)y!jpylFnM4bm;(oj0`V>pSfQl0+bM>&i2jyPoygx5( zji0pLNDRT`@C4byzp$BT!_`0$QEVoEUHbpp3w9m|$K6hQPf+xzofc2f=^IEb3RaHi zq%ht9l`&y{Dr@JqPkG0!Ng#vj3vA^)5-ngU0^oXl`{%;cn)6)`}kkIQ~%~t_|&af zKvB^hbfk#i1`uK^xT@m~YA=i!Oc?^KbAhDCl>wAp4?JdN#MWD@TA~c)FIo6>7=p~> zjVYgj0!=E{xxm|BMkt9&zwsNS1W~|NdJ=rls$<{NMRvssL2U zL3{5IFV$W)J`wcpI!qHn|IiLE;H547mq;j=p61pzVuS7A$_3s8Y&<2hIB$qojRGtnpi2hm%X*zjqy z$u`?-$}-*iS%#dJt7qlq6n|eI>5ombj~_Ysa+C>R;CZ$ZdyLm`Il+t-;lQ*uqicqH zs;ibrk2l5~7sor&_)W(LU~>j|vk0Oz z{=t{r+3WbPu2j(vin&3J-&$bIqWF55VvzA&?p*Zl4w+aij|!1b-z8=69hosOZ>!GQS`_xCw7JyzFbpG83nN_`o=C^pSU#Kao|)sz59Y{^u!B0vc@9n&G9-2Az-jh4qCF!|+5-pAuE0Fb_e`Kxz@%>u)eCN{ zaH4Y|AR?fm%LIJgz4q@1b=desmw?jFFpf;tAvn1_IH=Z~=drdnD*bhz$enZkrvZ5N z6(or@fI_phoal|??1b}q!|Rc|G*!efk>-ZPtC7v~QBi`=Qr&iIsiHT8z z(^4A5<}YcsL3+Y>Sf=+|v_oxzuWfMgOt32R|d&4ArC}}3TM1Ttz}g2QsPqm zPi#N}4FWs4;mfiOT6~*R*L=FRkL`SUa*jhtV(%!unQ^J_(UERqxnJeG6b~(KCO$w_ul#@Ki|kLdi8ORbM){&Z8Ll3@;?1|^u9fc z!Fa|^HD^sfPeIT+uC`c1>HctmjWf^a32Yi%jp|~W zqeW}K$BdK{T``WFtjMSi0f^Ds4%FnMRG&_+kBE(Hgu|ZJGhpsZF5zoDx4DNU#pFLd zi)11kOk(4tM1!MH#HaNmln2O_gr=Wn2VeT-G^7&xi6fEnB&$nUe+WMp@-^;h5N)O< z6X;E490q%{A3-lu;Y|s z+NJW;h$+U~84}N}^V#M#X1X#K<6PR$rH;;w-*jAq6x$MufI>(#&otT~tVB^_>AZFU_rQXoVx{^(+01;q z{Pj$n@%4fomFuh1dt^YyWCH-25rB+IFCKuL4C$Fb18~|l3aG&?SHkg@zY~tmKjjbt*o3Bj=uE-ZC(_NQTK|XI91#cql;`V;2 zRN|;F8OJ_P;1HDVC+v?r{^|CE&kfLXc4Vba>$pn!hW_3?inQg53#?{N^iUlxNS%GR zGjS?ImMI5li3D*%)0GJveL0QOx%3s$oPKjzE=dcX4^Sgcn)D71$^dY;DbiMHbF5QG z$$mq%)Nmg>Hxdo(0A0Y*la~O?1oUuFtgxG~?@g^zLthrtTnS+FBt!XO+}Itd`gqsE zFNqb=H>q4iqNeOGrK%-ZO@kXyJ3yUqc5a#6)cGZ#8<2u~Suepmr1lH_5TDrbVR{Na z~^}cQ@?P}+L2qka%rZx&1z3nw*SU^t^nSZ{3=ljXaqQcE&-|t$yi?suap7+yCmp) zf5sn-4y(iq;0Dpj%?hk}emxKuNH7aA`iq9Np)HH4h5SxcGR=6u->|9Pq&Z$(DaWe8 ztp0)AA1ZBl5ylhoxZN;$#fYWbLqpY;Dv64obbV%AwQ z>F+pdGzxy<17A)&kC|N@9zVj8ODcDLoRbz8ulyp3)S&70SyV=KGNSHj(|!!3qCFEt z1Ey^2X`d+r6lwB@b}d2-4xFTj;>^Ydub=>+<2p~Q9pjyY_zZI3)GfwmRiK4JF4nvo z?~Wh4pwfQ-V;>g&$4BseL^E#5Fi9x-GWQ5CBp{A5G~{QqNE^7l_CtI^!M>xcG7=+B zWW3ZhmGg7*_(a@1_huh#FXsUDEa1TH)RAz}&u<9Q^imG=a1fx5Spv%+$yz*Fx8}*4 zri-(v*5Hn=R5dL*IT1Gs{5*{^dZGKvG73PgX5)ZhitUpu>fQ1)!%a2A1Q&_hn*DGZ zEgv7QpYQHvJv4W6c7js+z_mh%+6F}I&eDo+_wNL)?nL)4-#25MrUReI?|$(mYHT(( zex)IiYfoM^C(0#xpJ)JsruzWaz61a1O0)xZ=)eP@^&b#I{b$rJjHATC4INebYg%O3 z2*oknRSq94itTvyE%tVV?X_(!^4~=1t;q7xMPW+Mp6GlkqQB>$lZ8-xJW(9mkpMU7 zkR7NAF=i%s#GIs4gG_UuI^437>6%1Z=02HD*ogm%cjx6Q+AZYn!?bRAmhOvskgX}x zi0Wzd?>VN2M4J-gbPvB6egddmwp)4NDrF)5l`p(fX2>N)QKt(P@z>h=o;!SQWL{v1 zi*S;xMbt}5;>If~pI%>BoxO9d-EJngy*c&9FL%j7zsm-4qAf}!FdTjIcP5QGko%hz zN4tH7@412S6gW{#hD;BqgX3pB5u{8fx)0>@u++>(y|?&9*KT|2_-{mKce?8XWdsrD z9r>OtoIu>*?HCz)OyTM94dzm?w_3V?gQoRQ$}P}=(CPj45KV)(uKSC$-K!+^!?l!0 z;{KO!{a6DO*DO|Id&Br0`4g6=Brr-MTSkRO$}y5p5y#xxa7Oq){t;ag7(t<>`$MG# z>RKWx+j{(=60rhyX^)ELTlfJlcA6rTSq%IZHHk3q?8l?ku2h>1wd2T9x=ur0zZD%fPjF2^b!#P z0qG)90#SMiAYE!in$*ynkrs+{=~W=1iL^uk7Z9?)wa;JY%)8&2{rmnoGiTlzGQ)($ zT3Jt?b(ibD?(0q`?fuL3E_w1dvAby0u8R}&1oga9;kiawYoxB6%-~bE`O%kF9Ni~< zYD^4@LW;QEP9x1I;@YxswIF8rr?@MNSodS;#vgroT$b4BJox1s#dHBZPNIV% z9Xl**Lc1=jpfVxY8qx(_;IJkJ#Xe5}A?@ASHp%c3t>X5iO3W;`!+?>QUByc&XA3XM zn~*0N0!=ou{;Uz_XiaA+>d-j|p%@H*B#DSvIGAnHS>a#g5jAzesNJ7Y(q}Kh#Q3NB zXC-s;WK+w}-6uaef)`GLF8M92#gR?^=~?`Ts6pz&q424L%ezsw{#E7XH@fsh^$qpn0co|&RSJ7s1Hua^sbje_ZSok% z_HiGJYHRKW+oHKuJ+j%0EQ4Q8wK;CvB29<*+KZj!{qGJ0dZ!Kx480qB7bJ4s`B!Eq z=$v$u!rPeQNSal!-)lmB5M(Gjp=zU%{D!d8-&S0?%r?0%UTm|w=n_4TmW*r|@BGo6 zI*MNZ@TD>`jZE?H_LBoi;_&vVE5Xt*9=FfFwtRKKAMRx^UA{`d7$u|>LRoa>WjJ%& z8$;PEVh4!|-+RrgVu!?>#HJ$5^7L)^a#ao=oZ9*(n#X+sd=iwcLD6rG6FDJTZ!Yp2~-e%2=!*QmXpM zLAot{+-Rp>FT%xEgp-W z+x2$tA~|%EoHAQ-ob!`Szmhhd`uyxyK!uIf~={hP|!EyJz?X8~M#i;u-drYg+m zoq!kV-0)winG&MDVbi)IHjpKo3vVnb$|$e?c|A;8i#m*EHN1=M#D=&?rd1-i;a7Sb zwDFl?8rR{vz$N^-H_n@+6-+2Ur56qys=d}T%W-VAiSI2+eVSsitkeB9x6RwG2#*>DOX>Vt!|b^jJzi zoq!IxpQETEq`SN~8foF%0tx&s)-#sA@ktuzrJAc*5E_tqi;nRmdg>EP6WeJ?`uXF} zjzCtJY+Klv>8$A`-Gs8<;4Pc`W=xW75ZAsrXT(u>AVTiHxz zT!5t^`aBaR6Ry{hbjsGNwKiV-SEDRqxbsBvsh`bw~`y#?9(<r3;Dv0pA|M3+g&YF#J?p|4=g7>pkc=`a6e}U81L7`82Lx*5o#BU{Y9UmV`D8uu<6!)#bIL<=Tml-bw zUGAC=Jf_%j43+zCci28$pL-VLx8&Ysy|fnV54U1+gYD{FTl0L__vGDzG!`#gOzVsJ zi1Rg$_`@KNflpms`Vcn|{jjhQ-wSH4rz?|YRmNm6Hr>jVvoZdax_UWlV#F)5#rGXe z;)>}M>ZWOZ*NUUMgSmZ+jT5is(R?kX;-<@ehB^u$qK_pAK7A_-cE>NyO9IiHupC{Y z(C*f)0-J8IUzEykSTiu;(#__VE(Pph|BtM{o zOb=o#RLTUZZ^h9G&IG9_DW5%o_WGuoP!{WXqX0Ki!Q`dxly6~#HbWkte5kG`!W_uh{EWTO7TC3*38L|IAyA2X4}w z*-#<;OV!}2W2nG)tHX?5y(p)cv6fqvFf~SAAx06mmg4i&s6_YJQD=i_sVhQeVsd?t z{epT_Cyvuyc+M1Pq;`dltG~ld%ZxFT21=z%iJdMe^D@NT;&sZaQ1S# zoo}e5_apR*=6H3ES#t{#+vgj{v)qwl@-K5NTZk5-*bx%HYHaUk0VP?M0$mbpSObvlYX4Kxm|*DK0&Yr z#hkS!mwi(FY`NryMZJ26Z6onr`8!*ZjMXI-tBRN^m2PV{eU)}wKLe4_c^%LP%M*$; zM05Ixp>wE)Fj!NtpSw~F8|J!#ibNFY&)?bxzZjf{;UdZ>NmU`AiS(S*?&Llo5hF>G>$1l5kK9b0BM`Lvvmz#6>BdEdVl)lAbTI>@R)JDfY zIXAIPVkU{b|9BdR#qDW#JE}p$LL~`(RUD~h-}$ctIt4$njYulgL!W9twWiQcUgxV5 zU^qs143oj4>itBSVP&^-_*y&5uxvGCv<@?xa6eV4bA5gv%G>_oL0QVCwejWmggJ}? z?erSkeAX6>2QIl91D z(!3)9%oiFt8xfaC;^!F{p>nP389e8gjuC{Y%ple{EhgUMC>6Fhm)>>~#8&!A964mV z%kM>jc-$j83JSRFq%;L51)}8nrMR;LPBL=&Ht{#T3+4|jkXv2?VeA1MCHnoOxQV^I zR#)-$>Yt=~H|D*>6(LbDh~NN0-uLUUOiLV)4YOGz&XuSA!27YJb1EITi-qv1d#`!( zbP}rdpr8bK60=iFArFd(;4**4wae7WY= z5|>;Ni&ilUM|@QZ2Q$?K#AS81HTnb-eW01>3s=p2=w+rE4q2$_T@|(;eqtDsEc+v1 zQ8bla$1muH2|HEy(3?cmaR3OVnZm@5pSA!Wm#KkY#Ly5;d{k_O`?aUPy$ZBSfX7~D zFI~8+BYoW6xD)P-Ke_iPV^#kh4yoU=FLW~b?<0~9jbROBhcR-N>fOdkW-KpPVwdZb z|0!2Kge|wbQT>@eMsKFZ!jG54C1lQK@aZ^Rrs_O^Ww0;~?MNr10pU3eICb5$z8`u? zdTpP%&q>ZenNB5v!PtxLYoDG3`UuH)Ur&?Pz(VHXj*{xTK5}!6$6WT&hYv{$U@jfT z0aJl{tT1=r(1@qlwV;NmZW9&=p#2v`<9hAuJGBc6TgN8Zk1U!a@4oD0{#cTMW78xp&D^wF|UwO&$`@k~_Vj!wY?a7Tw4zjU?-TmvA^j z_(1SAOXDU^c8Q6Hm=xW*&mrvh(n~ZeftRK0<3u`*d9k_pDuxZ}WSq7^m zs&Wj11Q8ZP$km$pP$r~u#T&U?4U56L9|8U2T^K=U{qacAfT0#cYV$esQjt4(w8jtJ zD_YFs>oq@WE`TDrSOWC8gvf?U+&@CF!X(=F`W6f3 zyKhRGle=|4a!P7dor!^uoW@>iNylpA-O?P}m zU2(zXbDh50#bU=NZwqgviGk4t__Y2LP76-F`LD1F^Xc^vIV+l>H$wfq%+N@euzWjS zG)Ml!zBaB98{L3ToD9&;jLv#glTalM+;`!Yu^&gWKADO*S1=Zw$e zz%!1;!CeR_ym9QarU$@p+{l!r?NM*`7+B(TYdg~q*4J{+;?`?+M_R{xalW9J38;~2 zg$Sr_)V0t{B%2nXf~eL*Rs*>3vqZ1<&v>x}Rr0SsE5<<2K(@s8UphAyZ5A7wGwHVV zpAPXRs@+=Ivic{}{@>&+yF;OInnuHg;K-tOC|-!h>8JP=YDhXxS}MkK=9tUXEuv@D zZ~mE|1z=DuvvE|@vSg%LX2>iqA!IsTV321$nfs~+qAOK$#U{lG7IdlKUQGK|*CXQm z3l8e@&tS$g^h;b)fGS~VEw&2FCWClT71fIuIXAXg=Fa&D+G6AKh}omevqy04^Kv*i zDuqt#Oq9esP8Nsd^t9zy6a!H^4%3e&!KI>fA97}~-l?NisJao{%D-u2#_6>-B_mF| zWfwD-aT0#acplX*02c=$*H8;V$Js=O(nQUtQZY3{E;)C8TEq0PzZ~qA`adlAG&Lgi zpJk|qv<+R&O?%V7z$qg|0&52UuyG*&B~-G1>GKu4EtleZ%cZmR3P;y)HrKg|Q%g>4 z&C9-*k|KD9=A9%SL*YJG<=hXS#U*VFLI-mRt(F`YcpbzQ*b-q`{rUE-h;fc>mQ;eP zQ&+1bq#Ien^$k2lbc}CSlP{2}K16OIrv1b?I>ij`Ok!Q8wsvvqiZ)T5{cI*o`p%BG zOgA(C6O3*XMKoqAf}CFlK;C8SEpU5&JvegPK#XTx#_<97;ipF+NnK`SD_cS zNfq{&_6+-H55uvGJJnJMOeAbq@5IXKc3k=Yd1E0ri$N2ouesW&I+Ngp=M%!+eH9g5 z?mlzco>#s=qD%A?`zE{pO>)ntYRe96aWZ+ft!`E(N|b)&>#jMEJq)v3!>Cvt^t5L0 z{YCNc!0;~$+y8bE(B)#li&QfG0GJTa#OcL>8JDEKz>CDYK2f%ho;-hCS*bDKWl70A z7w7g5SgtvUo%x1b2j@;HVbLUgITNYVQbfh8|MqHsFFEMk|1DsU1e8)wFpLjLi6Y<(Ir&z zAM+(R_RIp&Wr*;AEQ)ouX=2d{+0Efn7=;2@3KN^P4sM0U1_&hvdIr<)sS0K6FXOFJ zE_i*kk9z%)YEZ-fon|$R@sNX9f%V}4=o4F_Il5Q|V4muU&hN02V*w1ts@8##J*J+F zcdGTGN-axoKRv5Q8`OFbqx>Hf7fu=i~a>0H2j z`<0Q?_o)HG;g5%#6_6JeWfdYCUe^%Sf0Llrj>=lMxCZk2&wn)y$n`Taw)=NBIvwX`rD{3|u?OdxQef?$suHBgw46-)Xd%y~! zD)acAvG8U^4TiSq=lYG?d+IXs`2r4lhGCN?pQC#ehyF;kQ~Q+j#)sF>YFbnJJ6mo=S@G-awoO# zYyZX}p)QGv$XoHSZ;0(q z7OXO@Aw^zjmnSyI$kLP4y$}!E8`I{W4EU7T44{o3qqAOsUla9Pn(&Odi)0l<*S!u; z#Kq9r4i0sRHApm>yPa^HfHR@djI=ot3%2w8_Sj|Sh)+LIWMalM()B6&8e34-x(N4r zV)!;Wv0V}9>Q{&I^N!Tb0g<>KU(WJFb&rCstd;0AF)J)?OI(rrrG}q~)2}RN8Rmxq z34+n`Qn7X@qKk=*WQ5tSM@&6Bo>?xwfPN|6{D^^BFLt`l@Q zsYP?|FnA^A&AfGQ0px4}c_yDKjtRJ_xf}21h4VDh>^+UjSq;%x)i1K!Zd;oDEj!mO zaZr*$BSkqTQ2s?SS9)v=>IROrBRp6fo4?cxWgrQ75wtr&wcE^|e3~RHFO#0rh^8RR zTMM^UnY}LR6?t61`}wuL`x3!w+Hk}lbmmWTZ30ryu8;>hcpJSg84%J-V=+*|@4jX- zUrl=H_@QCY^z(|c-Kq^w7`Nd)frRz|FD|hZXFf5~E5h%1(!;85)ScbSo}GR}Fl|X9 zgaaC-+|FzDX1LbgDZcbI{-pm>9PV%FuZFtsDwsQFeM(#*wsfk~Z^+IBlWRbk__#dN#oBN)5y4`Cw*_+39-$z=T8xl2lr>xbR zq*(78CJZkl&WvHst5SO-XP1HK<0`N_#YnA-Ulj;+2TgdrjjfhTAUE)0dc|Q<=$>=p zvZq~a&kgECS%V&f`<9&ZjTi{G%R#?3OtmF8Ao??hK}W z=~=#Wo;Hm+-d6Zv_NBK>cvq=ZMinchYT&bZ$GZK*P6xv+-B`4SV_IcE1M32uZo0`B_Ad z#no973(xT#pdiRgJv%hqk8!!s&56})^web~5xqJkQS~7UA3HZcBJWp-dD>5BqxIzo zI$v@8o&bvbK=GOjXLv~1?oo(b=kYQ#Au0j)S<0clzHX|rH#*#L%uG(lCEz9dQ2Zwo z#jBE#C&7Qx4$iK@ow{@V;<|vYUU}ioq&LDaT%Ss{dsd|vq6`P11&F%#3TZBbGC6)# zF@v!7&##^r4&8ZJw~!i2a^@mKdwErQ=a5RVOFFvwPUMf{MCaIFyjcF)z|bnan4hSt zjyf5uDdrfDnW2`9Dr@s32`;nFhpE2{*`#tbSCc2c=kuTEBHT@QmjN{cqSxk(b)&y1%!<3;iM#5&&Jc0>S(?;6H8S5~l3^$Coz@%)8e@J9n`d}AJ=}}91 z4XSc&KHSnvSWsauOy;a8EVI(3rn0fVSjj|rmFBj4Be~HfE0jfK$e&apRk~ZlKQ1(An8PZYxg(U z9{_f99ytKn9fs;))k83oe1#TdUQPBPPD6ksXg1>C9{GVFL`rP{L+k<~==B^XHHQ$z zVLoS()Jy;h3VjCv*1(-C`m z5XX&u_<2e`nkcYs>2s}~_Svr8Xxwe-!4oR&yIc`H8-9Qt^l9iYqN$_TXa3d9fzEit zxmvFV<%~;k&H*(nj#brR9gR}7d%0PUk$yS2;jJ;oev3-Idy$3NOwzP&W<;@iBE^_g zb#k)(yjp69%rpeW9HNn^W=K@1GF)g{{pQ-(8-B({Amf%+A%NAYQMD*ZuYK2 zxChtMEouF_#GUCd@V2jA1u!C*;C=d;u0j|{vV4IF=g#<}S0e4ol>ta0V=XO9N;cS&-FZ@gi*gDPx99Fvs{df-S@30~0cvYd1CUzlW z*(G6QE7|SwkgHzm$9g^3vl+ZQDA)YJn?LJ}#=V{8YsVw-Vva&NeXpXi@#n#gm^z|f zVgsM(tl6x*gvMxyE4@mAjkknLEW;(gWXT&YGVdRBrpTE;2;Yb|($`Mk8T^K>53&rh zeAVp5GmgsWVEv2YF=WARbtPJm=$07DH{|oav&&oR(7{cIsA{{BZum_o8hykiv6kfP z{v^xvk3Yu*L`4J=0A}JOeyh7G$na}BAMnebke`g#Wcz+?=LNTY#`fjn>zL+xCf{4l zS+Qqg7)>#$FZ~?~rw4@KHR|17f-`o7<$0C!aT77K&r6_>%r+xmLRd*?n3F7M`rJB+ zUNwxTl|1@vif>6Vj0vmsr1{3`3>$;QWXsJB5Y_Zk?|+$u^k+zwu2bFU#b~kEx@d`( z?ry5TzQ=drMmgh%f^w;Aqo2E4MXCF<*|EaOCy)-=*uQyV#^Wz3%@Ji9P&Hrl+SI zIccPlL^(3tD-nga@{2SU-Y7hL{ASXZIstpzql0wK4NK;m2DzUhQ1h7#_9=Cx z)pPBKgkOd}jFY`(swVD&U$s@{e(AW$|GbgAk#^}#2HmxdLN43muonjk zIb_=YGsmtEil2qBl#1>iQrzFv%|Yb|Q*U#a&+hZ-#fRS-l2Ug+{k*dl#76WwPN;Bi zs|2f}IU(+qiw@^eUXsB!_>M$EOM+KI;d|6*kc;)&&)=;sb5}OT4vc1zWLlWgal~gJ z3(vF|kALQPeH>#Rb7zJfTEEXOUhIrl=d~L9`lExA z49c2x>64_Y0W&)z^R8FR6+V^r!!t9-$5!pvC-#LoiUh64AP(75HkLeuDT*}*+jWx6;~Ld|%}d-qu- znqQB(ta--U`^JQeQIGd?jOhEQ#}_EiBf%(}3D_c5n4E)By9JA|+1`A%33cJ@hx~jK z&l#a+1Tu=Tbk6IRzQ$1TlC7Qn{z8OW-DQunr-{MG&szvb=-wozx$knY4}5n*xSnBj-h1LVC1myrG44Ef zrUI#&WInH7uw3n#@V+J8#hEm9zyVu@W7vEG0!~NRYb_4XExF zCRjgSDGtt3xSdZ+Sf5;3US5PPb}b-h76M1h_|kLqO6EL+RFU&3`}67_tJc%m;#DK{ z1a?h;o6+$Y_o~u8NbxdI+Otq;>(gz)%@(=&soYV!)Z`e#D(+^`+|rh!CUQwoi+lRU zBQtg8M!yrUHB<~(x^0v&@8xE1UR&Yy;C|B9SL*q)@JFcVEL#4=Iovd@l7pQ?>F1&$ zui0H>@&L;X^{0auECguIOx#Ta#6)zrOY04BzZ+gy23PESuc}^HM0`5E1HTDK5T(|V zVmZ1|3>c|kyo2+gd(pl7mNPHLtEIh{V0#4i`J_^!9{U zKJd#t@aD_Lp&?q~hJ)dQp%;x4vy-~0^|78)u>#@?luHp%(Ua|%y?D5%<}m&2nSJZq z#^S0&f?aKLyxgXsHl|i$Dm8cZw%vtbqsc(V$K*-FiOT&zM%~jV&GmKlVTSQJvAHV?{cu2`V=g&UY1i zr1Ge}7?;@pFzFV)K`X2OY<%fGt#BZPrGHv(uE3KCGjIKd{bPNFUqL&~4>a_NNw3wN zSD9EGV4A(l{8NWe<^_9+itS9xy2_u!8Iy|a0_+UA2a#9^XN>#w?t~-LrgPEEcpX zm2`v#>E>SmhbofmR8^xpI`BEiwE=e@s-`_D8Ch22)bI4FU98Uc=e9D{HMCi@zlBtz|FkJihlx zU*5pH4f*n6t9_1pW1+#2>;5wO!}bg6O{Lg&2n^e`ESb~Ue(URjybr6^N9_`gEI7(7 zAjnTvi$?#+B15viSh+;Ens(lb=HT3Ep$Yh11(9c7m0)Pd&D75szmJ z;+eCYxC4-YqhFan>n5`hy_<0fx1f=y zx0(YX(MMt<{GNNd%abNyv_9VQCwnEW_i*Wj%=ZuYzjjCswxp}*n|ln5O8m&tFG8g@ zoxj?r-eBrI2-9R~r-G#cCiv(`aGzD4i1=}1-a1+@>2CCSDX57^%Ab`OMSRKlEi3Po zoGYA6d$vWUH3IQGh8+YeSrq8+;l5o`A3$ivxfZW^(eZrgDPu48^pzO;pkW@YB_2bM z6uFLM&OVe{Z0=QR>)!?WO_XFH9)Ax{WQqwy&^zvMpN{~Sjl7>vz-4@i%fpuX{3;IX zl$!52g0Et7&#nGG9;DHU5g#LvwwCE<8gO+WNa_ulVc9ctAv#)6w}tqGor$p_+qjZL zr*6?5HncTtxb)@^%}?Hq*UCgpy=Q|qywoHFKb)>C&rW`XBqBI9!I3RV9UR^y7w2-) z9U`R$4bfrweQDR5-C4t?@#@m#y~DiZMOp^MFa z9dVe%+_j2k-A=8Yh8y8NvkO$XbFkYsuum-Ps5a&K`CUTQ$KDsU7@uduUP`(L%$_}k z$>C>TD`--=Yu5el9*eeWi>MiZ#KD!sy~^ilGAu+W94_|YO{76dhkK5uu-omA0j)JC z7c8ic%rFV6C9kkNwGBB)P*?O3e;yKSF)6-3+}0ammXl*A)^a^};41SM7rhlun)wG* z9;xdM&fGpm+-43^p_b^9oHs|!Bvz|lO@6o@k);c~06+W^ZYBD3CLSl_R^+}6>6i=@ zK9Jb3qg9~)6rUhxK7AeQP~KS1WxCe&aQ)$UN@*`@1&Z7W#Eu zU0nt1Wd!T5=?kU=IBa!7uYohtgGkqQ@K!yV(Y3chAw{XU-(-&V>1VXz=1pHEua7^@ z$4RbU{2^7-8|0@hS>0~V{`|>fqx&vH208jdlGep&QUBo2J}wn24wz10;*3nYc4o7^ zkawIOKp58;FZtdadL5GuVF4bmnZ)0(W$=DNo(Q*0=`BGDK@Kmm|z#2|I<|8JEmv8QPjsi_cSrE0KcGJ+&cd1Ml6fC;ZOD-!IutK}H%S8Ym`8 z^&YP@Bt@%Rf#SkI4we8HAzu0H^AQ$qF{KbI5m;hqc&omtWy-BJg(iR0-J5$^g#E@w zkw{C123YN-ET-%~)1sgi>XMv3fx}7heudZ}GP&+C6D*>saq3%>WhzIXtuv z#qkZ`z|BOmH{(5p(YIP_GapXMPbI2dP_suhdMLe^seWKL^g5+Bq~lhQ<4nRD)ExBsPUW*Pwo#b@RZmatr3VUDBy-Ayrvk+m9W=}t*Jv__T78uHh(0FS z?PSo@I4q6{?OEYZ3S*bWLZo_cy=2q34zI3>u|ISezJvU&KtRMD?FC?08l)1@4UBFU zUk`Hd{c&Ckzx1YjuEE@>u197=&=4f120`KNh|hm8Rdi9 z2USPo5~{WNJgXgm(Ci*`ZRLtJQ1K_=I#l%I!Oh;x-~=-@%)Gl7RMh;<&hh-xPjZ}a)$4N*UJvv5 zp3g~uM1o_vCi&$j_k5lsqEB_AqrGs`l^;G&j`Y=*eV+&^r+0Ts(cgEze6Jb%**T$T zxsWmuLZ%B|{VXdLr$GmF;9}&@OlU~91l_E%?Tphv%mcgl9cPKY0&(4MX7l*;Kzbjk zA^VTecW+jJ~3@TNJWNQLKNqf$c6~*5N2%A&ic2u3F8@4rZ5`=fPEsZv|x3oU;Iq&MjsuLOG zN1I=Z$wYgkXnX<6XCfV5xAm-K~_i5cgjW^M{`Mk3#8yFghGOTk+;voBbH+!ROW#=WXM_TA|5H1VzCC|>Pa>K%;BDasZp7oyIA&U?i zUKQHO`Y_1HDg;gqd5!IphdQoTFCneA^Q&+O#+?p=RgR&TKfP7+BuT7jXGVHShc(%# zYJR}`_;ue8)Ai(y)bd)D+n#Hhai|8Rej}yh^IRKPe=ob=Jlp~-GDLg9<*pQbl#FSU zIN#553v@n@sb{>sma_13T|8>)olVb2i$Y3GcM<=OKTB3iB|RIfK2Pnp`;=r~I4LZL z+4a~;#Yn;>%AYPIWX5<&6-|~*2sPwMMK1X_+`8E;ESMTl_H&*sMF-nA93gWGib-~9 zf|&jIYBTW{Tqa#zJsZA|FF5<0<|w)$>{5V$-g}Lrwy8RrsJqN_4Z}Ux?`(O6rjo}) zFB6a`!b9}yKevxfJCK1)4EC^6BUma788HtXq_M)!^3I`)9JcAfL) zc1^hVB;px@%N-mS0JkWz1_b(iw>4M@%Q;mZ8LGNvrS93uUg~4Ti}ubYqk=ZArU@UVc1SKc6!V z_p2rhac1{QqJm)?iFY5-VZ19Vi!N5u2;69w1dPtuL&yK3;89m^7X`qMX~S<9^~5fh zR_t&~MO9V%jqgEwI8J?AWBX->sIz!qhao$|p&{TZS(KzY07++mm{eWYZ%m3<=FhQA zNE6mqV0<-x@b&$Lhhj#e=@b-~rKv*v&a9rU&^Dkc;Shpu>)Acn?5a>iLkGQ0-sPb# z+xu4+Jl*-K-g2;bShkI3s;AshWZ{j{8Q$__&fVf_F)#Vhm_AcBI1`edAz`g`>)1(8TT0`XyE!YcAevJOTx=m-i)DT z;3pPqS0_Cpj`e{d!$CY8QN7bn_cfFOmI^8XHBZ~y`V)5ydpI2$!I&1^OV-3a(-oSB*0 z7IQ0$Eg$bH(cp_gGlNkO*lm%TMSQRy$^cAVSV|4Sktozszl%(HKNQ?x&1)F1E*}vp zZ`+>ybm`}gsHaWBx^1 zAl$~HZCq^o1I33pk=OrwVc`G5?-$Nh{hv=}H)ALF( z6CqKRSTB*Z@Teh$Ed1qH_A<|>cX&Zkh5YM~A1ALqXLcceUI-_DS^{yxNylo_H(jpE zSx)r~{2YAI$A0KtnRl<0%QY4mW3hPY>CfpfWByqt$2`?3QU6~PRmN;0Eu%6i_+e?- z{TGlqXWtgM3zcPpiyqsZj2oGa1(pgQEsg`7m4giw3g{Y2y*&c|;C}0uX6^v5H&V+I z%Fr+J`7BsHp|YNDGvGWRgf{c6Az1Z5`QXZ*rVcQ)_$trt%i#Z1bCp=SG{yy$V`W;3Q!WLPc=7Fn~cb~;hA)gd;P8C|pmMU=R z${Luu8;Deyxm0{w96HIzpZ>t_zvRl>XwB9ya&yjjJR!%MI-BoN#cD}JIK)9pRyI~2_b|mg znZ{3AwYj5Sk(XDRjFZn?VEvtr!V~JWAAxI9mjP3fib8&IwVh`Q^vlST&}rV8g%`Os z*U$bqpA<~^m4w>ZssP5$UVKNfA)J}Qp>^_4vz=H{cOEdxE}^jSXzFCE;&i|;cSv*fzS^il1-*Cc4-|bnCm09OX(+)Z zlGd!E4uvx&wVlgtw}Wb|`~LuGE^brL8lShGsW`Jruqxt}ErmPPXnkbQ8xX6d8Y1VV z>Ktx_iq2(*B%De2myJZvgDbtkZ0ASBCS&&{JW)sW`76p>r{UJIRBmu~UB$R%5u zLX|SlIpBCzi^bhoqD0>9>c;*4Ib4VCDGO`y`i&u=%WsAyLVBttj4RT9Ij#NEyD{;C>>01A;hGa(qW{2mXZyvE zwX8L!W!xwXGHt^1FbPk+hx-7VJmvvB&u(ZFwk&F<;Z&Y{4P>Rv9>vt{(`2L2kS7>h z(QUt_#fvF16d`w@d4^lmPfO33$xCfgyXmV3@yi_3q5K~C3YmDX#s>D;Y($KN{L{m5 zs>qQncUZI}y*e zO!MVp{Z9HI8oka1$i&;uzBOgpPEYadO!SV4SD51q>6i;d%tJ?@)EG@PRt021O$}5S zUdMY2_WHR7gxi#Ol*WzuWfk+TF~=v0IJ1i}(Qb0SvdG|Jvd!S8lS~uQP0NMs27Y@7#>^}$P9I2ZD&`B|OIPmlH;wzYzj#}7%_^}05xc@pzCqIdJO1my zT^m<-!|(a@*jh#1B|0FStbYX6EiG=No<`9>bv+aGzVS<-Ig-cQQi0d%9at#W*~ogq zAq=tyN-uH0?Q(g?d&?m_(ssI*%_;JK(FgiU1ejIRccv0VADCA;78^`I$^Bt$o>uf1 zg?HzpniE%Qs!S&VWhYtEEgq`KNP|YPsYD2^E~{`~V1+UGBfs5iP-Qb(tS_W8YUWiJ zCtN*QDf}Uh;y-zv4v}fW_AC+g^L()K?@E*+S9GvgZM(e&DYq$LtkR`!&Wf-z@ZTHt)~KrU1z7U&C79O%Vg%zAyflL z|GWK7K!;G#%dga23&MsKRf_a>p7%y+yTAR&v}B)nd*Mrz4bAni3zSx?*7p2Te74HZ z*g2SyJ;tOo=>}1}nDO92Mmp!Hf8F$gB$&=T2*CVB@dqxqy~(F1YuOZgV$9cTHT(-R zM4h&A$h%%ZWyh0L6^%6uNHvYiv;L)DRK1`VX6gWmCkfz9C;*f_!3`;&lR)1?=9v$*&w&DCRP`wei`Oxl)l(M{uG6$3VrQAw7F)g?FD zK79jOWGb>yFxS?-X#BfVt@1N(9wuZLUidNLT^nnyvb`mDl`8PenESKxbU_Suwpe7M z>Q19bU$JPzhG?M3pj>azSjq9Yk}YPn5wBk z$wm;_M7kdoa<%B|TJSz>b#Xm$z+fh0TYLoiHdMR=d$?|yyxTge!2J}z?4I*TmgF56 zgB3OF`8w#^g3;kVNqDC2`8EBOwQ69gzGcx5UC|<`Uq-B^_N7g1r>2D281HuzVrM#Fn{*S-SHM+5k#-uqGo>yE0+c{K`udA1?`qkR0?ko$)u)8_j4KbzC^edOc)uG+n|ylY0=8|r1fY*Okt z|AW0pVkBYu^TsFXo0h>gDX@47t?T#V?onP-{<357w3PE>g&1jlW8$0LF7-~;0f`m5 z3QnzI04${z1di<|L&bpuZ1$5EQus23Jk`F19~Sp|5}TA>=@$CFHP~(~@SIgM{&vEX z)aA$v5I>USfpu_*ZYa~X`>M4>V`lq=x&m)lu-a|eU_{;0w(NPXt>9NfhRo|!*3-5R z44Ni*woXNKnG<#!f=d=HcT>G?e(#{rv^yGG96L4x-h{%9h$hF96Y5rLo-LDScF?BT z=kb(NZuGwlB`^zA>+hDW-@DJJ$xy{^cBp`2(YKaY@EZp`E0L$X&?{A5Bt1_wSYbPb z$9;vtZ4?@sj-L`F+6FlCyd6!sdwB1ydX0Jx3V)DBI!vG03^YzYS#0`7aqctKv0AWl zDOA|{`z)%#WVfscZbod%{Qp>c&!DE(ux%8@Mic1*LXfUBQHs){A_5|ypj07B4=pMn zEhI|sY*0W!h;)?>flw2XrU)W65=uxA5h0MMge58ato_b>=hyl1&YAB=CJeJOv)1!G z>#o;*UH1+q0B_08Q{itK9MmhaK3PUr2Il4JrYYZ_%Dcmxo9WyK!x|1ilc1vPAmLOq z5%#GZ%HfoX$zbdneGp#;j|)*5eDnO{>sB`x7d6sW&y19)IA=NC?9dWYGF|D&;F6(x zt;et0`GBQQp{c@O#zAD^cPwB@C%OXX819s|#v$P((#vuelM4~i2Q%CtXD?*h=r8vE zQ5D8&EF5t6H48hW`5^r_nq;_w*xY6qUzya~PQbKQf!w#3hgPfcGm0~RIM+NbOP)ri z9W2eQIm5qCpz}G8{UN+@IYv3Uv&}wE*1B6I)|1$BjY)hBDjVE7JOynb&~&Njxmc<+ zqdLJ~vcBf00cH;5r>Qaan!!?A)$)5vazDx3Svc+!wd(Ti{^0Tb!c$~1Xi(#vz_2HT zaVG|`>0c9IX24Lv4w|@tSPoSKqxyl5CZv~669DQIa_)o`OiDJVh^+guaWwt(9kD>S z>iX6|W~8(A+qq|qI>t6Bcf?@j%&Jq^NRXZM1y zkDopy_*qwdH6Qq;9 zY0E7wk_^IJ+Oe*Y{wNM*7%DemUtmVd?{vJ`o(8k+ceq(H~sIZafGv>U;3Yb2LXVHM4TT zfIrg2h;W3-+JuQe$fHAJL8Jn6?}mFNhbDu^&LB?%}(u zx0jfwBuZZd>8`DIB-pgKW>e0ZM~7*=@^upEyaX|t;D40)tSt;Es8`O<>z?V|`m-LA z0NY@9#cgs@>CSBNr_1Q~#iV`1L*5QFDz9>k?JI6AZ~;bZNUI z>=zUIB{uzC_~M3jsk5#iQQK-SCu{opZAxc*u1{K@gRo%7-6Ja2($aNZr{0UiV-cZa zxLuDQhd0iFDZ&+$2-5lsRvmn=Fmq2Wf&LXa(Gg=khL2k*rgCAl_C%1z*`@WEx`EFV zO4eQ8d2MtYO@yID0g1k)-Gb13W}b$y~Upz zS^cy2!9i3-t_8`TelyW)TekkscjQ|p$*^U^MK3=g8I|2F)@Sp_QlV;EXop>1%;NRaxc#Jfrul3F1 zCE6w`iH$H=w_w!~YXy_=T(T zYB>zDjF#u(&W(6e0QR~%ibWiAbXN)uvbS)|K z(QA_4#@;s1pC>%tfaOm+gi}{X|2A?@X)o#o!QcMN^W-j=EH;^uIsZU|;xw3EU%2|~ zGKKLG!cx$|9D%P4%1d-YJWcP&|J}+L%Zb1y7_8k&2$JLOIyrOn zwSunDWgFFrQVLn^JS!mXl{Pz*xKbrFAjkv<*H03u;-u;##TMV}{Y|K6;kyggKGtZrs0VjBMj=4o$-tNPB?x%-E`K|lTC`>!v>@s?S zmtw{X7~3~rd2ys*%m4mGgkf8(a6bZ2ha|gGLKEl}%zprlYWAZqJzCrOte?db2$v$e`#Cd=Xav9OB5G#K?fn5QnLr3*I1?8?qVD z($0_7|J)dNw95O9N06@dfo97f<@OlA;+Eqjc{dswe^-Z%pK#v%$hs7jz=Q+!mF#O@ zf+^DlveDT3Ae-mUxKm%YRd7YWMozW1@cG8N7<^I=Q%t(+74k0UU{6cWR6}*kw3c=- zBIM=p19zwhw=XEyh$*S_>IbUyCC+R7___43F38lh03sB8~K`(qXqPN?l6HQi73!&NYR?up0wx*y+Y z9{PCVY)d{oVSlB_KS838|K~UV{qFyKG|I5_ob>HK?qpFHzjs|!kF_!gX#v&JdGoA)XiTbt$x1wF#N*lu z#eU$q-VZGwd?^>PA1K#Q4=zs~*ZPLDuc{Lv4e!TJ-Z9A#y;6Pk{^K^Gs&1QUVT1B? zb+Ci;f9=D&7Y2Y+N6Iknk;CBdh(*)LE(t;beX0s|DStJFP#@*M+#<3c5U1-ke`{> zkKLYElq-cOyNV4ff%#8cJ`vvNK=7W?LkxW+0}1>UBZ1 z?%EQ0$yP}{`gl+FfKOh~+#k$Wf94Po-G*)^_@T38Fw7I(fv2=nX(+qYoc@{|H7f$x%BBe%enxb93V1J_81Q;k0kI=`#iTxp~U z16wKzge`AF0G#dB#!^m%7+d_ ziZ1d3w}qkVUE&Y{%?~Bo@xHG zaOt$(!=azja#l);gfR4S5r%#fonEvyu&Bm_bE-_7;~0_4!-Fo$^h`SP^0Ys33v2B^ zwKQ&nYwDPlrwcfDWRJ`)ja{+kZO%J8*bV{1)cBuKu~+G2w)o+}*&_K8c(wZy+YCV0 zE}HxEUlJ!}SKNy>rGc?!bx;~A8Yb=cjkpHe?63H_w?>nooAqC5FWmxlU?m$LHzXPi zq@l)qsae`v%GgB9JJ&Qs#_hD#>d<``q5i2*Uor_t z&3SL5;Ak_oqaMMLB!7_H@GS%0>d+SDht&Izoot!f)}odBlIn7kZ{0p=Idq!^TBC#0 z1*Zth6;N^cQ%d=k2`Gt^0gE2yHBa;+B2=vR;<#KY{{82ES|nq0qPD0#a6<8-iMI_&-{%^?QL;qjc<0Mn`g|?l^L1Bt7yBTS@MnM z4m{5A(G5?xvFHFl#lGb+uoh1s$xv8+bhp%6!Q#xw?Ji z&&#@<))w12y_v?K$R9Z*@(u@Gni3CyWX+&wI#A4c#pOf#gY9%s)=G7}AN|=P2W*fyiWICYErL8iT}04>$xnQ{26+=9J=bI+Bj z$113cG(cbcO z7#a_`kDD4p$S5B(j}|+XZ=?S{ch~m%WPFa!InPNW4_tZRrx!c4*nNLp!9oZd$p`2% z#;H&mq6RDpc`U=ym}ZikVx&7&Scnw0*6#*O1655P65lVK@5IeuJKAi55?iJfI$E(s z8n5?X%bUOav#-JdstxsPd~C@~<#cc%NS;Z>_(@?|b|B%4LYs|5SDWoQn;CsuJL+IC z@70{(z0iF9ny2|!()J`gYW6@hU%WMH3QD(a%yPrf?vC=13n#atb9D5# z#Wqmd%|u#Tma6^Xs4wO;xfMJ}tFBq0(zI&#tz1E8XE#(;)TajuEF;Sv<64Q6r`l*fQ(4b~ zORyo)2KDIQynh_I0w#`=7KAy3yNyD!Bw{?BLnw!6P?kTVVE;9uw0Z1`$%nywAG}cA7E?syK z%KHN&&?bcPkOCn(Sd4f;yS=YTtNm-O>}Et*(1~XEfp25YE{d4v{G3^Ei5{W`$T(;! zUIFwRmVbmv#ASTNGL#p!uq4aSXJNT&^zUsU)j-b87KTxNwyCirA*!M3sYj|-zfe-S z;O1rLZ_eyvhx>M6!cYW+#Ck_`!aX_%m{Sf9L5^~Yn9^De9Sk_F_!VVybv9d z=)-xA4%n6iEQjO|(`VX?@98A8*=pPjG9DKTX!>EjZ<0C@>_^nwaF0D+U~@0@!xNEP zr)S#Esdgg(GMPGU><#NLdccL^U3$OR;q4O41LdbWkMo=<)oeRnO= z9kRr;cu}7T+Z+u6$Dhi`t=muZGd>1K2{Nyvfio}7p6r0y(HyO$x%IfgBs3XTNiMs6@s}(C+pm0 zI=+~}+fN#eFaGW)zQuV!(9GC)_Ei^5W;c?x73)e z1$L~LxBD@NqQZu|VDC>MgUSHJdpCpJD&VcHvujg^LtMm$87k~H%S1^*;ID(*ctiLsDswp9GB2%m_kHje4>vu6Zrlqz^E zV-!gD6GDDf_d4E$|7Gsj&(toeb`Sq{wj_Xee-j<3^VOWoH^@kN2QE@L0Z=jA%wia5 zg;8d6sL^dS^LM3(CqkX5F0YbZzNK)F#oZX4ya0FeD{*N(@y3Mso5i}1?z@8ahma~L z7LEPeNl?1I{bOj$T53JpvTDPg!Ar@`r*zB3#L6D*l9JKPTQ?K_(jZjww^f z^ylQovYh>XKY8}Cx%H+0tHzZ^<}6UgI>&|qYv>i|oX#?;mSxI@VER#VQ=9k`K}G{I ztF-psdm)-J-|L9XT`65`OdU$~_U4Ff1<^zdd9$o?vpS0zf-3Os4{NC)bw$Y8)?tyV6*)gw$ z>k%5hGZy0{tq9dg(vffR>TF^L|7^+k%59xTf%f@oBNbBOe{9#^!WV9OSFJxPl-kSU3_NOga%$Ei8akglWA!Rt`QW#Lt0!-Yiw-RF?;tYK zt;AuSxs6*Be?XlgK6W&{!kKaO>CxV@b-XeCdj;jwn7c(sv{J&Yri7cxQk~5OR#jG8 zibAq_!Mr{)D`pU2-W5Qsp!oq-|BwXH9Hw`j|0Cpi!ozc?;3>Zh+KOt3EOfbINg0TxLNmDXbmwGQ%5WkLJWA_J{Mc!&&8>TF1zYwsS>lXN zgg|v~F)dFyL!3cMx~)53;ZqXP)#B9Jy%DEb*ns;3VG9G$8GIb!!t3(%yQl(m-)ZRG zdUGznFzeP2C|?cUL+zcs?9?iQFz9jbPDY8&yS?x?hzQe4Fn{+eywTCzcGoY_Qv8=~ z&)<6xW)KrZqr|6xBsv5ibY>A@lT6Rx@@3J@S8!k%Y$Gg6XMik$I=SvC)x)?-h$vrd zFL~?X;;{tiICl_i+i9ll^~_&HnqhIv*$abUy6w7y7PMv*GW+QFCoTaARLC59h>x;; zO!|FKfg|jPQwC)t$}rhb?5CF8-bh-9riFPX4LS-PKpc*$p={@vcq}7MxO=z){qIo< z(|;hMZsc>ltN7NMJ0C;hBtCukOgn3rt*kvd*qUdrUpVaWps@d-fHEmEW^t~X7NlLY zE4;DCZS3vAFM>~%&%VJ)Gt*&*Pq7g!A>^4^Gckbo7QMF;e+HC33M~pPgk+A6GoMm= zMP*}SJs?)`AKcxwdfz^8(Do_vj15MW;7yh6g3wQTG% zoSHx?Fs?LE=t$=*y)aRXLM%_y>O4s{uJ;sgy|`0xa^a{(J9rz8Y$t$5=Ksv;l0Y;YB`XV#K;F%{s|FIZr;lzdA*e>~|g(RjZwRr;fby zDt>5tSu*4*ZLEE>uAtYQV-#@smv=2-I2dy{y#Xm)9~J97$@cbtYgz6Q6}UGN8y84+ zE^?t{*cQCM;-1)(=jxTE;3InOL;4{X=h;1wFb>A#&h8YWkFG}Uwy%=BWXH6*P8PSJRK>hqfts%IbikK z8$&wWU*y}E%r<8f+5m9147TY#1OgtS-6)mneiP$auylU*W^_!b1IbcTQFs1|1aGq6 z9*iph;-wA>azOO7k2~QjfwH?vS=q=*x&hQOG&gm$ zBaAiv*q(R0p2~0^K9Kq43UYL7A>UHPc2FnIa2s|t?}&+tRKL(_P@Rf6Qr4L*y|fgOHn+bs?VTFDaUlmV zbY%Jg7Z_w_F)+mtUV$dnoA{S;KaqKNVA(RTln8Z&-pTD9TY11$9Y59JRU|+;5LR65 z^38nJ)BM1Cn$l%69C%u&QCpv=Yn$MqI{@drbq6Xq7dxjUO`k>>40x+8yD<#v~ywchr~ zLX4ZWv8fEcr#Chk#OeLTj62gxz&+UJv=HEn!YhbAEn_!&BP# zye9Awm#4S8Bj*tLl|KsUotLyrDr^&7S4Q>EGG-Y%_o#qIRA21=$0N{M_rQKhvlL6Vk; z$C03IWLhM+U@w3gy5#^kY6GSi%NVnBU>yH&@8@R^F{*0qN*2_mWlD>hy&8JAU`Xy7 z)!Jk?Z}Evs_5o!dKfDVki4Z~{H8R71C-mPGLZ40+hz^QeM#!)|=_dG7^zX?<#x5HU zfu$$E{3*HAFvfimeP3v{M8?hf+qk6C&DJ2_+AF=pj|UGey}h=mkJ@0zE(o`xm%AZY zwE;v9Miwt*&roCPGwe4JwZJ(_xREkSnvt9kP2qSC`cdUXgFca)$pJCSt1cz&D+6k}J&S2R^Yb0vPVG#(0QePY?)2s1`X-UwaY`Mh7B6heTcYQ2jOL zdMv)H``W!v$7$?of^S=;_7(!{^AP6nd%yg^CI!G(wYX>pkWI%vmrz`5UeI|Kl^Z^P zEq?}h5COzi~^4uFBH41GPkz26qWE3tImp)e@ZB-ER zCiUxDI(GoQ{1`*OfhPGgJ5!jOKrG7=m~sRfBPfk?g~O$$^xbR|i<s7Z{yb zl4$Fh{6jN?HhtJj`ds^MH1D&j*ZZ5tj+hyMtLX<_Av4#&uAn7|t*-2vF^kp?pGHU> ztRf-DJ!ro;7rB9WF)nDHWbwvPL)(WQHh#UaN2_yt_?b9q zU*lq~KL8JC#~pg)vtL&*skv!pI~m>TKfIku7TrN4p!vAM$jd+q!~Ig6pJ@o?OM^=X>ZwgrrmE$J%^Dz&A)lb1_(Y+wPv-EHa^2Y{=Rlfjs1IZjF25W1GwKwEg3jV9Affcd z-E5RXKtar%A}*ooN5funR{5AgnQyEN$&Kt0`1HJ$lD|x1fArr+TvuS5`4uOFHy^Ad<2>*eJQNmz)&o%i!O(ZXysH+a4Us7k^kb6Om=GWNFja z>0p_5`Q$AzzOmOI2Oo}TwKtGjx_0ngT4)6>VHuN&79uQz_7mqkq;rUb1PvZbg&hMV zEC;b}^|mnl6S=a$Q@1an^~#wxa-%X?`On&aU1prg^|Gq>FVFN1;6$2{Cq>Y&7Zk&eGq>e_zS`9qllz`ccB&yVhG!gCk5>e)R~3 zHNdd0y8S@!2QB0qbSqf%a-?ri77I#z%h>{5^pXXs2LjP@LZ7iuNq1C6ogd6woInIj zyrZb;<{kH@L%2uJfhvj=hS(sq6_dj~jJ(`YCk4nwZvNl9;c^#a$!y;S)S6;%fqKTXQ$P+=SB85lyU~qQ#cU#(SSaSy=f7A zZUN8F9VKHSG`ZKFaM(VS5YhtZeFLAj*vaYtmq*}dscIKiQ{tKZ(H98g;Z6^$g+u1N zOI@%mun<*4^I+-CkpdxD;mzL+#PVv^@5)D!=wocL`Y2C<7s_{=E6)Z#YSPTye5dYU zt?WgpYb1?Rahd4lXK1=5dCt(5lk>fWu9L~VASCO^NExI-?OHn+ZuV3;LuZE8U~d~| ztZfYBHoZJ&ae_H{5S#VHTrmX_|DS+I{3L1)CPnZiOLIqIvON}+NY5#@$7_`l27Fn5yf58@57a_@#yXl-b zlU*EUF-v8bxNXVle90d>CYqYbHHT@9G@$n{0tUA;yI;UEu?JAw5m<4Y-4IE|7?%W!G&L^ zqNwq<4zmaIEN{5pwpJEQ=e~jZdU*Zj^8E#?VH{wX*#PiphBtyG3YiE2jZfo{KL4P| zdf*af$jCpjDw(e)oqArW@cPub6OLCteqW02jyy9|1I8cB1OUu^4_bkX6977wE8L^p zuPC*r)R5hN=+S_#lqY!gpmrG-hE9q$+oBQ}`EHG$LwJYyig!gf;cWHl-{ze1VO@L? z-}32=MyK*i@n@cFBJ%Aerx#Lhtt_sq$ zm7ytrw3~rRKuXjH;ZNWgw?BQgL{Qe-++|%u9Gzb5*k15%fgczpw!hQab~MX4{6FDs z*R08H3JU>d<%t+~x@mpecBbBI(D=OA?$L)w`xPbCe{K!cyk2l@!R3?Of`i@b#?Xz^ zd6hEUBJ}d}(lZR$dmX@VgE_~5KGtW|^A2!DIF%-TB>7&rTpjLFq=AgoWAFGakqE?t zP*q3K4-NN>?;N^uM1pm2IVTe%rY}6-%>^5e%k$%sWLrvOI)QGl1sR5kgja;rXqxFT z!qy@DD~YxN)B^XFM7Fc`y)Z~g2olsIYx zip34PnnVlt5uRYg@T#BaU6Zn4fnfsBd2fX8S>kKyFpL*y6wMHTXFokqV~79st^3yu z{-c@j*1UW-Sfp~~^7HDX>5&f)v*b`;#;GOV;9E$~Uys?(KEp5$#BpVQbH_NHL4KMh z>)3HDYK<09t#fX5v4{%}2`m>*G{&QYa)9&tNbTkyEByrztjp``&ZKV2e_&rPm??WQ zu{?10bW6q6iM%r-S1lsRN5OanmK~F_xdVoC_ZUfQ10G&0U{znr42pE2rwn90p#8~x z4Yc+fS?ms*SN|w_woy~7l~nfS&r@*&jGuIKVyAaUYlLsCavkZ~ZtsHl4lcxbLHkqV zUpH{tg{>DA$@Juu_joX3wwHyI&G?blM7)8^cyvfKz14`Rs-+jl zzBJsk**?2Rx>7oWtQ!;4c%|?ipEQYHegVcwagiP~Y~2df5CLmfL)F@Uesvb>u9LK+ z-Z72ssQPWvsZ;OWbauY>?^)Ve^~0Z#;rc9I@CA4NmnT175wyRUo4?pRC4)Y`5!1`8D`Z4vf9DP{ql2q* zF1KHjsBEu0Ks<2Fu(aL9*dyy&OQzd3c<3j-p*fsr{-~TOv!rr0L6nC((+bJLT8TNJ(isuP5g~Vy@WZ z+F5olXMa?CpV3_y?1$8#m)B}-LF-RNY)UYW!;2y?=_n2E@ZRBU69n2DE>yeWsW^?$ ztSsejkK7g=IgqU9C+#wG>pstG!txSgK8FdiQPK!>DP|CnYY22Z0#X%#AIo;iOm;yv zTTj+lPSrzl-mX2Zd~#)4@KKZTpge2Vt1hy@Wb)y&H>XtOy#79Y=xI0RT1$8*+>e=G zp-SQy?#uyz!w?=o^J$wM*g&0P3>5Bx-O5kSE#34hjCD8P=aX9@KFw1;@zE9SwwGO& zh87c++Hd1o(Fm~UNmv0Jv2Q&BF#FApf#S!w+OM02MTp1$#o2kk=0~ONjf4#NmQ0u9 zyB~kPYDAoZ(Ql*wTA^vMe|db((44aYGaP01U7(l>)|DgKB1%leEYxSXg6)kqfE{GHDB@h zmag9UnK_-}EeP>1W&}M%{#wh#afz6TI7O(Fs4=umk7E_m zXR;?!(+?^GtXylx4C3Y6$oqNm@upso;)+3Kmjd`Iqliuu{cvX~0|q%7=jDDX*suB5KGUGQhY(_KOJ_mBdOo6R5vvblI zzK6)^0e3I(7bB#HfR&BXS67I>Vit_@`|=2JXGM{49lD%@q1!>z2yHbqC_T)bF}}IM zFdl}sRBd(FyF{^uyAVljt|r31-vGp|&gPUGjI*xx)pOl}8eXqk4LmKg&zz+aY9RAs z%fqQ`5Lt5IF6R}I`J7Y173I!@{MOO}jbeb~O623Z5by4(hCkgwwOMBGe=^5MU|5n( zaMlBv)598PM3s1WxN)G*c#Li%&>$4_d?7QMQ=%uF;i)poUYd9z(DLC zTLi~^M@P_H8Bc9EMQAS=Ny#0Mss=^RpY|0IQ&6%|#U5W&C-Q>QB!jfv#%(2Wl5h6c zY78g3yi-bNKLkMh9d&#N193xY05!qJ$KVFK zHjyWrlKCpT1f+jC82Z0+gnc>8mT2ViM}wwuA7ILg;(j3unn6%g5D}#8vWM)TOj33Z zB}3iBtlc0c__v4RAcDDgD7NB2(?24l$c03S&>cXGj#()j#5}=>)N!?uDvY7VWptY4 zVaFh-ZVQa+E>9JnKADf=lOCq6}VB9aIt?Z0{V zq=-FmmJ3*#qFX{!Akf#O5IAFt52Ni;v`jh^zh%@Mw;XQHA%t2V(42%ZQV@dTM4~c^%)M!88M}S!-Og{k2%V zS+djZ{Huli?r#d{Hsb@(eV0rwEdyZE2jYNeB5wi36??#XLcs0G;8% zvMD_*J!>~bKi1>W`>iGJB9=MrI@cDP(T!H!Q~B-0(^tNb^DA>K7NO?o>xXA{TnlQMvzHkX_*IH_uhvBM@ZzF0IgI3mr3^$$UD@D zX{7b*OD7Ujm)t#!_*N|*9&xzEo3QSg!f0eFaKNm_Ur0t+1R2XIHam+{_|ot~lI#RL zS+S69ECloEV6%v;2LZDPqdJOHC#hX4w@?+9tomacWK6wt=H z^b-%6HgRM;6f&CJ9@qn6UwNd&*`l~rus57*TQhFVi8n5ez7>4$v<)t&f1?r5TBG$+ zx95IS(VHwr;ou%(Xiz%>q#PzF;$WfLSEi<=ar~FD_bNS0@u|z2>UD zrg1t)Q}w~KZ}aInichoa-xwPkVB53zOle&mjl{Tm1b`mkc!ICaBalv~)DT>BBpQL? z1I({Xv#qHiu`UVr6X9C&lkPOfTlhjpVkX#SyRR|!z&OdPd8jJY@yQ_i0`xfg9(o?a zMADJ4V;~dOC|Sl#9FULAaCbpEuQcupT9W%x)rl`SAG~a9c@vvFSJ_$MvXW%}-7@0# zM|}YT!z@ih6voeZav%HHp3 zR3Ybnm`~P}C zw`WEJYb-0Ee60`4gBJsSuN=;(`@@`R=rN?T`ipYX+A`ww)zar0tD2`ymWH>vr%@6| zZz$y5y0e-T36TMtz}uv8X6Ph^Xl91`OpwAK3)q#qId|y|@)G?jj#7LfboG2X>B5e& zixRxjtLlVyGb$W(F~M1tT$$O5Mh$0_6c;kf{ssxvJ2Xya<98pRb>?-a7o33Nq~~F=AK7(_nq%gJ9umn3|Ga4g%XNL3~7fxW@Lez>hU^eOhCS zD|2v$?b-SH`98g=YtCMlVy(x-i}p0^z{`SBxYbUUyUHdmJ?T7J}8^{n6iB14|R@UtN9>=(mK$*9&GR+KVgK(IRyU zt#L!D%}ogdOolsjtYavI>QelD!5L~0-T0OO#wNgNGOch0{MI5S;duhdqP`i+I%!Zi zLX-`%@LGmcub-l*BOx_WPj64&%{vcT`_E;nub4$9`{90sT)FQh>B#=0j(HF7pL z5pP4?G-CK7exb|JZ`dVK8WdtSR|)911+`Cfz`R6>4`KpIzcfVD=7?BFdPlszGp4!MyE6RE^WAG{=g>pyYfm5Cyt!L8zHM0D zJY~^1xVk6Ewd!%Efbug^14ZP79^bM+u*Kn=ydztp1fz4?;QGn+6+a6?y}H0zi3SPP z_AcB%;sw&TJ&qrHC$r~Ik=hhs1tH_;Sk!so6iW+vAE;XXn`f=a0GU$dfrJ4gz1;V- z6Agu=xR+nVv#*3d5`H}L?qve37ee=h#g`snfbPbh;{X1m9!p4|A1cvf%!1AXb#s^G zDjn^qpa2e#J?=JL?UH|M;f^KtleVG3;O7UhE*Net5p@(eRzYn~yt6fd7Ry+SY zuyWE3MzQU6^UwpOc%mW4%w$2=X}BUVevo~gj!5m{UGWfEE&wM_%rqt&uyKBUFEZYT zVp$)kr3$Nj9zS>wo^Pa`LcNg<+r422$3tl{g7(MBH+B zh;0A;A5Z5NJU}p~%!#px2XfQ2M@KQe!Gryb16{YGATgdY(Z|hJ32z( zo3^51*pCJ@WnOEJvGzxG-o7O|I(!L8=HYp0ZNhUw?TO>fHLw2gb;kghmKNhtEY=>2-XY zu2Q<;aiPfga-+b9`qOm}KINr0ngM+E(Cm%hy@{+FkU%ms-j4Fk&4xgpRQg+^aGjQ#j7ICQcL-P0pxIkx~h z+>2+>#8?bRCWuTK|9jk?+xHf|9-di?6rPdu5Wfj~_z)c8zxaRi19ANiNVBSjX(e+q zmq0%>6G;u^Xwo6GYrRl0yIy6X4kDG`OMx~X~E#>GrGTuldkimAOv&2k}T4&&#GTwAPv)D9Qz96>$1=l=d;NB^} zPBY&V+A@`IriSh|>*?y9axP0ttng=UZ_=n)#!M9sHr&I8N*#Oc&Q@SN>q4gsCme8_ zC=D^%TC52<+2A|n-IJCWvf7h_=vGp=4F%iwNp!;@%sgTc}tV~?5E=J4!B+|Mo~BBlY*{fuAv-3{G)PSx3xx3-UZEHR-;>+ zF|FrBN9`Y4>y0cI9`jns@5vG74{v$xkDh-Xhq+4u*_s`HS zo+KINNR~O>dV8Zv(?Z>$MC0T1_dA3a4D`gf)C<6}4}ajkPHHD1Wl7p6`k=fp733Le z6?(})h|*C~!cY2ce7yhCHf?}%@1Jpz__D0`e$V$@os94X$;Nrl?w5Y-!mqOI*x^Jp z9Fh$^W~Q_8>zqBcIY%P|uO)7*$3$jinF{Uv|_AygrUnVL-OtSCU_Zf_(60#06 z7&DgaW-McvG5_c8d5+`x@V+0O=Q!RE?*~3P#(mFyU)Ob>zw@_z!)Z&#w7W3;zxb+U z7d*uN^ckLZ2y!EMvPFu~RFQo;=9TEf(V>yFmVq^XM7q60TBXuM28 zK_c?P+MX6@D>Y>YcY~B*){&JbIP`wUis;13Ehj11^VDI@{wyLm z6A}X!1%P2)_5J=MYg2A5Xj17kEjQ;YOpALAA-zbdGLZP=Ltc`kRW)abv671j=tI@* zE!o=kL7i_!jIJ+!4JZo*w(>0SzD*EIZUmtF9^^I;HU(0otHIH!U9l@$d>Z=toMRek zY>HUl5z`W_isU8V6Ao1rz5&U7c5gneI8nksJ}mV3sjUcAQdDx9cu@^t7eaNQa-gL$ zL6ZlkYL`LN2$FI;SaMkphIRoQIx&$rS${ZEBJ0u8iH_362BGMxZm8z@wQ9HmeECpk zwYzJEVqJoZ-RBz}Vh}Rk70*&yYXL-sH<`S9l?I~7i#S#zl|mvglNj$vln5j|3u=?< ziJ?mKZMCY_jy64j(XMU<8!QG(oeb+5)0k26Rw<7(%Ny|BlJV6C=gD+JW{< zpT-n?Uhk@(2EW8leFUsz!F=2{bPc5)5`)tZ?Es|JJB)|^g+eD;mNy5AeJt!Z7Xycz z=fVnbn1KAT(Y~PEM+SLEIuzEwRWId#I9MIHjR78a0k+aF3O!ZXpuV!53x$Ztv(Dr> zSGPC3=AIg$F}}yJFVlK#RF7-^{eIT;!b=~Yefv%`LOE;wCT9b_Y&Bw(S74O2gC*r` z!zlzyDIx+Vif*-22YaG<%3{v29Ev!GL)Bp6d0U;}V%gI~J)wfC^>L^~>Qj#s_8OR1 zH4>_x#Gd1cW8R89#6J|eHJEh)0nAohQ6zk&N!MCvAgJ9!k-FU`Xx^&rEH}uppr$Jt z4RbI;F!AK={`p;acym6<;&Z0g3yj~3(ix@o)*iI3#)BEhU-pxiW}P#hR*(njacpxJ&5PyXdJX2vC`~ zV8+PQycO3ebrQjzXmFTygJ4{X;B`-G(8_4|;`_mV(p0QBg`sYm%A82@JD&DgqVnK< zY#}7y zUOY|_)rF>;Q$lbCbW8+JXWXHzeeD$T>8BaS1qZTzH6T)bHNIq^3agKKQ*R}whL&?LtY`?FIM-BN zz1px`45EVpqVNz!9@T3E+6j|;`i@8PAsRnY<{1t$7oXf%*f09wt zfZINMP+SwgntUPA@UlRRKjg8i4TJ)Y!PlZW!Z(?HL?D&(6>1n<3F=xqz(FuXU91=c zj+mVaN4s!oJ7PsDX9oX-K@59fRhP6f=Qp(adaJ2Xea!&@dz#*u9K_KI8&-1Q<35vTe9TP$i`EC*H$S z$Q^g;gYzI#>_sg|5NTL8uO5hu)=Eu6o?GNE=6(lGq)D1JKp)9++~ zCsfc;SYKfd!R~6vm`@6|E`wj%yD{4akWt9xTbcrw_Zvk4-da4OOcM2b7pSl;z@1ES z2pcYW1g-st;?YiNf~3CB-s5T=YSTG-!bc05yL;yUqx@?(MLr!XCX7lOY8 zoYI{2WG@A6_^!TD-oD9{V*$PFK?AxF0UIHE24MVxxI?MpuK}LnRM~2w|9jNtM6+~hlasbd# zc|$50#Z0195rB`lu0^e$39=247r#teB3*xLuFHEg@!8{NE}Ch1?pM1~ZDwch+IaLW zHwKj`HE|J;VHoa_gZE=8{V=Omvm86MLh;=|3J) z1Xth*u;_&L8UcYm-O_2O3NGD-I<4zdc8DWRFRjeVi&Aw#SH;Mfb!fsp9g1$xMJp$q zGL=4D9ToG*Sdi5h>=Kmg>;xr!%IOK)`?&7lZ0BsKP(4xdTjl;EHPyL?Ns`}Q-oHI# z^!l;&8+lRVjKl|%HWuDm17<(#-N~M-Ob7K#=oT`5%QJ6_!d3-t${IZt1OC3<#l}W~ zE8SY~Bw2zfD?t{IV7RqAE7x)-8LFQ??d4|qKwYQylx-Q4d2XH+Fu$8C+FIMY&%Sli z+`lh~0JRP85>^U56^2sXtI`W%pxeR6&>rRV2OU7b7Ol^$ifzU!FgWi)O3%O-LoB5@ zW|1b@79~B!J%WXcmIzT1a)(TzaUt-&gGC=a9Dr}!UDYe8cdAMs#QRhPR|y2J1^>rW zqK8=mWW6!K7pMz7h0DOz-)K7HN0YH^!B=bGrJ7{u8-NdHn*+H=oOqo+5np*M#YZ+w ztzPyML-uy5s>_hnaA`)c0sXV}#wn~9{u4S3FQR8j2hj3R14Y(lrUk&F;AQY)X);o) zikGVVd`}!+@6JDK;&nW6((~lW9G?p<@BO7NWF9*7Y=&swNd6gclv|8b0fkZ730G!D^jGNyD?%9BEjciYD^5yB<)E7UF90FSAeuctv0^lw7IO(; zW4n$0{UJ?HV=j#V#_#C}T4{pzvcMF8&Ebx*0Aku__!bNiT}w>PkQ>swt6n;jx+lKT zZg5tsCbwhd^rJKQgJO{9I+y`DP znbd0VJLk+NT3HhM1s&^mB+e44`LQy=ZPLMbtCIJ9_1!C)36i%R=wTC!|W$4mC9oKRy z7$Lw|G4{O1o+03xltxZ@0aRCNNiDc6j~Cz0>H5W;bp`#N5Y_Q|HRm^B+PU`Wr`@@c zo_V0hA#Uvjg7BaUU}#EaqW;l=0rh4!{s~2L0k~sN+!drFD;-re${AyZbLFXWRF4xv(&)L0pCy~=yG)DQjgHIBt4=< zXms!{>+rPy8G3y5Zbw%fTm=Vm==RDaX-D6eo)E@|YlG-4EfkYxGbhNLRw{;!8r2JF9eCmRd%jmu3te~YVOH`p=44rdx zmy4U;m0+y_R}oDYK({`v0_>KOTP%;U0JxmGl@vx5I_3emZuWk7lFbSMxLJegkaqEI zC^WdZ6@He+C*C`!+;`YPf{AWmzjDkf%_PQ^*x5z4VMPK|aLKU=c_x(5Cz7(~c8O|SX7 zYZ+B#0T`}+Gdk6m2eS_zNkP8<%^z)3-4rc3a_#y#?ek(S3Z@+{_m-zuN!;at*J#n# z12z-x;#_ZI;Q{sYEohKOH*7qSk? zIykL&bU0Cr2RYa7JG}=1+E-f)>%96hn;pRVz?7+Hx#aqD2XE|Qf!zzu&yi!LfMPTp z1f^EjtOwRMcQpOzqu&AR#F|IVUnB%zK6*7LG;*#SMf;=Pq1v%E+Z^EoOchqb6c{7B zkA+}AwMV+pYu>ARmX)d)SAWx7A&xy8LZ%6t)3U~X9{oTve)_!fel*`h#{|gF6)}Te zy=eZQM$oKERKq^@67bz!-pfQ!#Q};|UqBQ=>slV3LXaR9dqoCkSRX8i_!$A=HQbCEfRbp_j@JT;cGNVK_&_RrBG>I z&02hB0Gc~lP*OZx;)(TbL5-n^TwtRJkyym;M_T4`JJH9$9*|=kWIgUrHXR>{k{<_H zy>VKd#D^iluSj#f=*Zoz>OqgdM|BnPcAGnAt9;EP`93}imSmp+c5ie1V<$A1e`Bg| z^aS^aG`)LTynG7#Zc>A&XE&t^nd;b^k!Z;2#0M8I+N#pzVKg*9KKWq7)=HI)rT$)H z_R9(3$_s_)s~Y!yl(;Err4Q9(H6S0HZ7n>CM@x2n7uLBAAmZOvb(S)bBO(tVGKUSW zq1tzeEueS8DKV_T#1Q-FlqWQ-mZJ=oXPCf=WPM*x|kvLAF2eWgNi6vC@5v zZDtBf5U^Ioo@%<8xiG?24W%yE+MvW5hl*9?YcM}y;7F2GMd$oYXPL{_R%9Kd zy^Z3Z;dw}((|;OV`7eGa3^)=*>BQxL5-y>^jnGFHGdQ%g z8#kGg4^4FU9avJ;U)h?>dMQtY(26|Di=3SAEQ+k$P~s~TM4_ARCHhgICt3be)iw|#g)B0FST^wP`v_xl#JhPY83`9V`#xnCO zV$6uuGV-^pOjs_({zS9EGgURs_zGa;PU`*TuR0q7k_$kBRia1!IPr}8Y>wmMZ}a3Dd?uP3oB}41qB}d1Al~ev=Jq<(^C^*<>xl6}5bq3LHlj>YI5w z-RdGGJry3y;#Km$X-rGJo|!1F&z?w1RbH-wyabg2TNt3KEt;aN>;VUjJdN0@43Zr6 zcOl^UnsldN^G%4X(fNinn2t?m;e=z8_=r?#nP}2;-O*bQG~0A9zISc}ty$Zgo}S)5DHEg$c?iF0dU-zi zxo*+N@|1+kb7SV;A)QCgTSd=Q@3jwXdXWBCF?9?39Vk=?Ow=I$8THFv41>jJ}f?6A3#UxOjOS|4-Blf z3ie`^-p-mwzWF4IArxM?CZT=#m8~DtbiH=2hFDVal8Y~6DXv16`+%KMk|p#{w5`m3 zglac9KiCwc-2o^@k_FV?A*z~%EbHmWBkDfNp?l>;=EdYml1#D}&zXT$+~eu52iMVb zGtj>t8IS>CxknVHurWY~+&m1hWHDp)+!IJ`G~axURCRh;RGKO4?bCCNv$d(X&%3@U zfBms{!O-exu2alQRNv!>`Ey@t8s)K|nQON?|FXm)C%SKJQ*0 z_!)>qZb4mRfS3Q{VVjRHknmB}mf?O645u9&RwwxUs7^5^scsuQ)^L& z(o5A?VL$*@EicBt@Ske2_-U_dvq@Yw-4z(fOlJqau#B^Ms%u+!}`;`0m!^4 zTPwj|`EEa46Cw#wEsYmvx8Ho2L2ma&@Fo5!Df!v;i4)mx_HbHx;-_MfnZ>!y?GlPn znfYmOCV))4FuA}#3gmVH2*Q2JNmM`OFc98aduK0Av-vP2!lK{DLXG0U7}uG^=PsK1 zZje1t>J63^HF^1Z(dZgTci8uYH_@Ny5kTOo6Y?VK9`J~40p4j2%>gWGPER)6E6_7y zBr?12463Rj9VbPHk*1}KHGf)j%=5`(xMEeGlLwPZ3bK{YCm3}fI*PtBUpV6&*lM_w z6NKCf+!8B6YkM;hKsQ!PY1Mb(3Mft^$de<4&grSLNYvG`b_Z$x*RVs3ig`I1*(;}Z zFAkGB)VCj5u)~HY^+fl!e|ymYAOlTWqM}!B0+%MnK&)8P7Y94E@PgHSQo!);P5?-xFRM__0EepkEd5-Qs~+@$ zJB!wu48dgwe4008k}UeWsyz3E7{KCo;qoKc{+#nH>)e0M48Yfpt_4dPi2J|lCfHzw z4frHNHXN!32mGIYux+elXou!Cm}aJDnCoJyuGc=Q@4j<@xL#{yS&30`DV$~5`@-h% z(qF-bz*Z);%{h_hz_5K|!a32{3YKU(LzUK-w}(WkGf+(CU+dxOsGI@mTOTY!);bK7 z%z?0stPv5k+}!vbx3}4GztsAW!259x-D-jafP#bTtbT^vHZ3nyy$ME#OqUUMxY9`L z!K!!W>2Rdp>ET#t?!tyS436}i9hwyQoY!1!{ic!^`LV2P@k0FrjC`X_H5tB33EFwf z4q_vaw}8(+%_30CVi9;3%sZgqNe<&)UD_=fsm%3o)@x`EZPR(Sy?w>W=}||nP^8pE zh+V*k+Oc7cKQ;Ybr-a+f=@qD-ICbDt021pFdTt6E!co~Ps4l};?Ns>Jwxf?#3*Pbf zU}VRSo4{RMK<&Z21Ls#}%tI70meHo~T6E%KAQ40NXSM?Z^0JzDn%B}6n{ae+9-0J{ zA=7MOq~cx}N**9v&@K9as}tpbsg^OeH4z;Ka;&}}?|^)065WwRvGB_#L;NE;@;B}> zFFi`T2C2WZNp9aO~Lb)+&RAN+s&m(BjkfFWcO0PDC| z9>Z^uvp6NYfaK-p8kNG<{*T8TzZa{4@&pVnIV5zUp>y{Xeut;^KOU!5^d_`S>i@xT zz!R|R7qNSx0Fz^2LDGf=D%5E>{|z@Y^FJ9Ke+FJJRL4EA7=Vw7Og|h_*_@ZXaSNuo z^4qsxBkblyBOv#i=l$2I6CG~DX}a~x>Z*tm#}02-??V$!p~c>0v&tLOc}jB1$AYZZ z+~6yD`GMHFSEyh3@-kq{rEj!JVY`Ns?M668E>AR)Hx?)5%3~*;?PF4C)iK76HSfp{ z*Zi}fPwg_U4r9G9l|`X}^hkUKF|1hcQZeTML$4u>7%A)|!`n+^?sgI_-k%9ygvD8B z<4n(;dl;>Lv1T6cAI+z@gpB}KqnEeX#;gO^S@+nc(0dDklj*MTyfIeD-{qxe8}{ur zTEzE@AHJd8oPNZXw(vWp&*;*X=RhPAfSSuhG28$iL#79v6+v~2YBF(^Ngx0F8*Xsi z&tpztVOz)Y06|B^Pj!|*@_m2{w6AAs);>e^rgUWIV0-=WN9bL%M~-)F3hwvklazFo zwJSy^O|P;OLgQo7peAr^f)Rvy`o zpogDND1sv3!TLSFWgoVR{?0sicIAi}9W0GhVDl{E`H;3W%Xd1N0R9=f`6y&T$_bz{ z$rw-HAIgoU7Q+=|?CR^^*pL6@7coCB(#bS8vjp;~$uo zVD9lW0{?ZeAATACIv!c<63w|^@?ErO+U1mwqvr*YPta=PFFWUrh0~kU@(L578f2pN zu+#7Hr#qr@%ZZ30xMI-SwkPUe1Ou9N&y~&Vj+HEFy6nP=^R3~XhH!_6$-l4oN9#(0 zX^zCMJ5D3TPj}$F1}p++sBg4vN~oq zcf5O4CwQw^LV7mj9B`_~-*0?bcQBL*O!T~|5)t^vhy#mg>DT0Dhn58UwuZ3uj4!F# z0~U&rGOB!4zSb{mEe@PWs8aGAoe7?jB ztr|yOSd%hd`&;Nx(pH(MCC(%CnJ9S2SHY@d&|H-C`-@BT{p8BLL(Yj0oAb0atjoj> zt&*TGmK~o+oX7i29$_p6sls>X`y$X!g~ryVK<$t)iin;!1Kk#3AJa-~s2-dYbaNWS z{mh2igsh_{p!Q*frn7B{xVg9){mU;8`8ERYL=tFI9mG*Wdi}@K?Xd!$O4oLlEoU8I z;5ItG|MaH9Op%t}G+qBp36Z{UH*-=nZ@bjKP+3z*{2g=r^zGX>Ucw06vq)fJhxmFp zA7W@?-Zlx9*|BQ=-bqyDokQ@FNk)P@iI?NIl`ma<^R|=DFS%XxvB;*2HuX2<*Ts^% zmv#a4Azk6TA8cyn?|9Pj|qi6K@wW@M0(<=BH;q*#T_Es$U_gs4?Dt*lbM3;TBz zIlz9&y|qSyaa%Hk0iR!I<+9>UFa|Bexmkkb16D$q&&r8M47X?wq^x6FP_64hE%sr* z+`#KNYgNjIC@%bpv{@X|Jdf{Q*sv*gDOu3Cu^_8T z0qm-&r0jjIy0P-5l~?FTYjUu7?WaUGY3-YVTKQfoSI~m(PvJ;@XmiEM+@z*C-OxYG zcCkO?IfF4*S7*K%;>~{Lc}`bw%n%>8@1c+jqwgOvA_Xg?=Tzpe*p3s^3fvA~qud=< z#Z%8QDg(6deY^eh)9~0`aiePo<6)KnSo1N{1Is*Thn_DHufknLS^>`#H4mhMHu>cu z@6v7R^ZmKqPN^W#NwWWq$z6dstDE~0Z?BSEVPTg(M|^%LVklG4P#68_V!6*1si@gH zkJ76;6%TwAx_j<>>{5zrUwN!$sfzVvOW+X!swFZK)7zAr8|-N};5yw9k!W$8xW78{ zac#`)K|Lc;5*iQ|-5Xh(cCuKz^ZwVf;k>5Q%MGtzy}Nx$+F)SmLj!J`fmx=q52E@K z{Lz4*?4(@MM!Qb>cb8(wlEL|^M5B9BKLb>82|+TcCP|Yv=M7KAr5{3p{bM=;m-IgmaA5u&r_4#J{i(hPbUM?qJn|3*0JfV}nO^x$izE;{&%q&AFsZ^hOhF|Yi zeovF5>$x0BYqaP`^ONlsblSeN_NZETNz#2G}i+&Bx7{OUO=1%eh9jnWI z-u&ll@?+{QggHiYTm@Xtq%_exw^Xp4l(Y<%L{ZLSOdRJ_d*HmTz zqmV+KETDDw+=5NY*J?db%gQI)T^gJkq7Gt`PYLgj^RWIbV*PQ3dvt+xuCizcJEmit zR~T@017BIJR)#DYQ=SV>Rr2-ZNobU8?K0ygG+ko+$8&uzlPkdmFgUtT^F;02D)h0w zuPhzqB}BPXoSZ5UkMW_ee4IAio9PwA{=O>d`xA>juo1-M>j%Tl;o2RkUCVuW{=Qv? z!>h&9BTa_W=Vb+DVKHJ2Z{@0oP6dGLRCOHBD<0#~3%LOObc zE3=QmnroYJwq~vt5AFqQC1?g$OHlav>1m-Ff8Hiu8q^?C==2+67`AKy}0CrTn zw2=U#>Mz0*DwiFNJkP!vH$~`adj*utk?V}Fxx8D_w5`~4@a6FDsRRTNHba< z;l_>0rug_VmjepF*F5F;4lBg0bY1IkC6SyUmNF%usyk+f+KX*QzW?;nD;6z3P7rB| zzftg1%=8Gq-T?>>Qf{&mF>Oieo<3I126OJx`$GS+>}VK%|BOWfA<14voZ&ta=#+A4 zOzC&R_~Ce}i`%0oCo!v!r%;Z-h!orLcP~%<%wEQ(D4vo0j`IYV6+KMsHBVPGtqfnY z85DsVJ#{p6Pb&U%YbDI?j@h-A!dUXF*uUm+!`h3A;5;+w-~>B_r#EK@3o zEY#I7K@0`MdW5EuV{uY2d#B$LHTLk~^pR#aAH}5dB0KYgz4P+W_DDNq?L(QnCNm(w zKv<|%s*fY81rGINDhE&PooL6&H`bOn@?XB4${VMKhaPMlgtaS%xtzDzq)L?W8FP|N6YvYbuc+$qAZ)iQ?LUr1K_4qDA#+f17#^%IfyP?y6w?}lGw#FS#fhG+PMzv0H#-(~2sv65vO3L7# zDyt`?ry9b|13u_$8(qjM4RSR?1DAe6PZx0El`f}S64u5pP^SDskjmfO8v+=j85f8z zR5UHztlZxv^E!Yl+8H^mAAj_&-;s%@9b4r5!*D+POA~4VE|wF+#2VAG3+Z5Y-^)Q> zucSL?xdpftK`xU#ad9P?Z@R^rie8DONdK~Hnb?^5?XjUzT%uaM%0yVhwbpWmTR70X zPOIDDMye-$)UrZ;oD{cubt*ItBbd19PA&L=+S;?bL5=qkL9GTY4}NV#?5qqw5155RKMTADJ6m8c}gkGMB@?F*3$NTOjE#{m{v@jZ5TGP&JD_(-`80w4(ZO_fPE@`PtO0%@cJ16E=YTwVnRNBA{L3HW$vy_Kl1LR}h z`-T_*I7Ud7yFKq)wwb>#nUKmGL+w^BdzWz>pKST(b))LJ9}|v?Qaed5@F$0wRF%Vw zpms-N^WT0BJIPrXdQA?N?vh9~%4u^VEuhY)S=KbfwI_@lx)q+3OF!!vVd@g+^ho67 z#cS8f2CyvUznm{!Vc_Lhe_$z^HyPeav@WPBp2_P0tPb9Aig*%v4kljbIhtt6!*lIy z29FF3%{>kl3jjj_nBJ`&_r1(2 zHb{Jov>2%PZu2MSfp<0};CbHz7}TXIoHXld8~4qwH0#T%@sX!OvH4|VjTjLW(Ujr6 zd>k}3Nnr+9Ks6ThPI46o9js)JOE^EQ2R(ZPpvg?H3-au1a)z-PAOG4WIToB-PHU=v zjkl1YjKASJ_|~kXIiOY=FZy}*YOQqoxy8C%o0cr8q@qsr znWhcIEWGR51SW9r6@W9lq2Dzmb42q1v_z=t28LK~L{1OfI~VBnQK8-i z`r$v`-ze`6e(Wg)R`o0HsNwABAMT_8BjVU5WyaDYmVPG7yDfopjP4iXN_Q@z@^ipP zD<~wW?O*euTUP>NM2%+OrjBuY6q90&Pd;GA)#Z1tbXerBxHm#;R#$(HL5mJBP)laR zDy3(tngxU$TD6yH9=##y!$i+yhXYBdC^~ftNwJuyhfNWv+1?A*Hq#dQc`Irq zJ%xcg@GSgeaJt}4L!!np_?IRFKtRnM!}(DS5}6Qspx*`4{y)g1|@pKUmc| zGztm9l1DJ<$u@x&dp(U)pLCyxPfWkKc$yl|cY0lUbA#5k(lLc4X)`VGbZ}>+B6fNr zB1(i!2<(iZ=7gF`8C50q9TW^^=E3aL^a{imu6xP7c+m299PO$v!98M1?R)1eQO4#djT6Cfqo51dzNjCAQ2_GFl@8ER@KemF?n8QZk`)9ddzMa#P%BkiK@FY_ zbgrLv2v&X&Ki2f)_hrXNljApf1zr*^TyJ$AVZ_|SYvjmmIu_KderOoH&c$u)j35MA zQI7!^2|*|v-d)ns4fSi|lhRU_>$w!#9htSiROVyRe(8h9A64%;Jp!zG)4D)4ia?M8 z*aXwkz#DdGEBpg2&t$*!gxk#syP?w3$mix!r9J{tr82)gI%b}lo=xRCHAznOxX=#A zRe=xr2TrVNB91R5HR#8>$5LH!P3Dg#+Cpx5RYf~BT-dPY-^e9Ze{|j+xMu;mb$AiI zEC5iqr@C6faoFm-xZ+!ovDCAG=ogjz$50M3Io|i=_^`%zcC7{6M`5C9ZEXGKMG`@B z7XL&&9K^@f2PlmUW#(J@JDfh2Anwamxzt+ZLDJ~+3EnI5D>&JS$zSl&bu<-qwRuo} zgsQdQyBwHJ9A{)C)4N*m0HOJr`k^~D4M0%wk-pV<|zPN+sFji9pxef0JYLAjB;vw(0KP%SSnoen17C2mT6J5_T0og zFNIKc2;Hg&47_9z4}bwIVjnASDz9BRa&3yh<3EmGW*lE363BADDxS^JX7dq8waBo#@CMjR_W+ z)IC#3P_g1HOZ7hAcq&ls^6nPyF5hIEWiox5`DF;~t{#b%)zkjU9k74d-vn3fjG_kl z#+awwsYx~+(hQ4ITM>72@!`u@f2<~4m}Gose>UKEs0}kxb{;1tHWz<+QM`?ZYX*Ja zks}xqHLcZi=1Dc#Z{9l-?lv`9nH&_a4ZqH(Z~W?lz@Fm6fiEV7(1!3d`QXvD()i~xDb;2gu1n|x@iH_J~Z_Y2Xj z+Yo+^U6l40+Y5_O`d)Ah@{+T;mj#9^Q%JVdt_TgVwEqEu7t(lOEo1s2>usJrne5~t zeBj0X^Q!HFnuYjJDzD1O3P%%#{jX|Dn%Q_LCs3T>FKT>idYEuhUmZwnVOOoZ2*Ld| z;G=KZecE&n`@|>cd%}Cra=&R+!Vkd^BNKFvN8rN{RZ9!}pzDiI@!VcouknEbe&A-j zC|tWe^>y;aIWFGTuzLfc(x=s6{(jz9fN1 z8&}6fR!;I9nL#Nn(otjB?^>A*N~g}bs9cT{E0`9OZm`jAkHU(ANJ&z!wTKck7ihQf zRTr;E_9!OA+dDEYWWfy}Ufl_1yOd|k1O3hF!Jfr$I1fV!D7l6z?w{&Ennh{*<4!k+ zn&VRcbf!!KDx!Gah^KIP58h+tnmlh9of4TbxU%pSX7SYOwz{nu4t7&gQY1M0`jb(U zGqwMEWYiq6T+eegS%l7Ai4Js#foia>6$>_H`A#agAO~vxRAjz-q}Zj&yeKKjsLu2> zuEF}|kvDdxz2mYH)Ci8Kw-LRp-BJ{JfmKVLxJ+as1~!8sZGpDHu|)MpRG)u7Fc)Dt zkX;|;7NQ(ER~1iP-H>|jl%l8+Wa3f zrz~PXw;Kj`?(bKVvy-_`J1cS;fb~y?E>kLSd32y5`xiBc8o`~UbEHP9vpF~ZdEFv3 zgAEkx2dVgAnb!fSB#RZk@HPA2jN+_|1r`Jz%;UYcU>N=+cK|JnyI>Eb8|vu?Ok1c^ zwWHTwTZ$~`=mo-iY9lNaNBs-+)U2a1dm$jb-+!lh36uV$VHxgEj+KqcEP}mmdLu9$10-5H!}P>YG#o_ zbO*yB($f@s)|_ z?VJo#+2;>$-Rr(`iD%zW#r=2}++|>{_IFu(iV{i+%)zwdG0#E5tcj9b@!FbQ19nRB z+C@=Ixh5S{`V+Zw>-J&+ml+*X+@lU~ps(%Xr!vKL+nwsd7YA7i)U|MM4X~49NHJX? z5sraR&yC4m7$w3_0G+>(IB#!VHw+|8t!Sh*ng>-LMZi18ZROAJQ}pVa4bP%ybpZz3 zBYHtXVbx&P_047*CmqP~mB&Fn|sT zI;>aTk;B4L0T%MDzdtEpatYQV`ghm4Ev1tVDNP-%6YCjT#EA?+x+<=kE#(0|hTbt8dr7s7yX|GP+Ff39;nE|CT3xb7qZg^l;%kkSLrc_ zxsKX&-1ZLI$+8bk76L|2LIBKNxxLJwf%yHQReh&8H|UeeG{GS2Wb0v1g(126ff7$o ztx+dRpWDIbL3O{Ho2wq}Grs=9_OE^(fK%mPuho*3C91{hK2a*{~U4+Sua=n!Hhms{K4$5(=Ypj?>{+ZSG*4> zt29uyM-8HnYEDSBpeu$z`}D-<1cJDY*1!Tzp18?Yeef{#^8Hoa?lUmS8q+h;3j2PK zs!bn06`d9PIQ>g@+IISoRW{#*R3Pb%XF+%qJ({a$iL zM=efe%GgZ|@w5a_1==o+Ln@P(3)o6X1)yiado+U_8EfsBQ_${0JLI+gE$IoZ>AO+i z!fgOs`{QR5}sa?aB9@qkA@h~Zcvi23g z38^O8YaDjTN!-HhTy-?)vU)YyzRmU7k1*0jOSCLdMc_?J)K| zM^%=d6CP-}qkT(z;hgm^<$Mm+wpXxnw$X_nbKKlk>xvW`EPids?#Q+g3qs3E8nuPu3t%iIAA1!K?sq9~*&B69;A4Mx)Ey?*rg$2E< zgMHR&2ryAy!OQ50W#Ks|il#D(GFj$#5F(STU%kq4=B9t(4vvlQNQ*&Psq4LeD1FBt zrk+V9A4@T>P3V-rgZW??$3d;8!63>H@VJ~!N>Clw_D;WL zS10S!uIgSSY4`MT#noe%p%$<5&yuTelUCPClP4!@Yshv2g`4OvmMw8NjYc#uDjp4Y z^KvDvqItvUS1sEo#^dxNAqen(?rJA~-rYnLo z{r~Y4CxWUS7B9EWDzccYp zV?$M#DrR(TX1`+eCE;kRY}!MgWhmS7)qSX<OvLG=s8j3Q)t6$A(!b&Uy>c)V;DjKqr^|4ouC=6Jxi=^e z9N03D`)T(pKiu6=Pp1MYg~DAy%&_&A#L3GhiDNwRJ2@+-3Vz))WB5)kE}SFz`q^(? zM+UP^k$gCoEhD*;%SX=$rNx;LNXH5^ap{4(Kv6j7P*iu{Et+5cWntEwh*sbS$d&gG zKRv#=-|(QY;o*bA0S9II^gy$P!MrmOhzK;JCoFa&kAG zqpE)HmbAsL^smCd@Qz+ufW#01M%wte({IDnxd2av(~l+3C1k(iy}R%ERmrO|i&JyN zX3;7KU5cgAgI zck-+6t+Tfa;b+NX`!-zhVS4uhgzKv4lX#y4g58=pOn%=9b*Hx5B`JJeFGouosdvA0 zGaj@`p$eW}M~guF$43{%vw4j_^Tr3k-tBy95KZ9I??9Dt`~O)yMML&;FTo)E+@tQY zLUasajc0spo-(@b{?h4ZVS7@|z_`W-V`hPHs${B6uH?(<9)-7Lh%{hsHHl&006s!c z50>kH_%>aE$V`es6m{ejOAh-8KbDZF6%wqjS$}Toe32T>|A_b6+25{89<-144M<|J zN@_*rQWh*P&C+eY&lPy|Us*?#K^WPSRdkb(>B@4jV3ld9YLhaxqqa(0C@4Q7)%|sh zMntdc5h)YMr8iclJhro2!#NYNYk<>^mz%fj!K7`gp9sBYv2Pf63gl*ljb|kf>nrb` z*lPmAA(7}xw+;j^3qkcw&$M@h_?zD%>X`$*WuS+4V0xvcCZ1{0nDllPgF=5eA#|&Wad>%}A#M8S#cTX=*#XQ>ScR=P5skiLUy#yjE0!D?(9qXM}BM+P|ZPv`_gR&XA)<~>wOyZwen`rT;@0a>}a=mmbLvx>3-p{m9#700M%{=op$CkfI0*ZwjU@xeju?B*L38%W)t{D>`|2^_Kyk1Af+f2D9iD&w=l*;_sW0!3%e95`#j{QPb5`b=2wVNEtU59vOH z+Xp!G;7%fq=p&dM59dpv?f2$!pvDon3$C(%ZfGTea!wQyi)%l0Fnn+80HV=0@5UX%o+` zwZk>;={@N^Ci>ka^lfVaqDjAO;k`h*`OOgx_R||NF)Npu1N(o!EL5lqx%%_z^#i`9 zdbTS-ILmY-N|M_Gk>;K@h;c2O@Her?JQ~!PFw;owbTU@ja(@i_oNX#{`@My{sPfMP z3DHD=wSI$zWsb290U~DG>D(AcwMN5k&}=$hVP^LP;x9Knt(DHx)~?kZVw;H%-;Fh^&+Xfxl-jvf~j2H!$_H! zmLIJbnku|Z4IG@;EB#E_g(IGQMfO#PHvlH^RaCzOq_PxGQw=0ix8HKlXh*(|2|iPd z^vWSGCItqz1}0-+711=(?9=-9d5P;63NDG%pQhnvfz2hS>np?^Tmg#JK=83#+9ZI? z4f6v3EV~LiE-r7LnF#SH_c!BY#Yn?kUe%NZXxOY>^}9XxM&A3tP9p;_64wqzsrYXb z3}hQ>oBK7e!jp!%j`qL)2X${64s{#;|F@DY*^8J8Nw&yxXPZ)q(tT$-<~e9_nOX{LnkebjuEueB{t0em0GEWOj^#go@)U_P_Q3KrYMc(~ZP zhtB@GPGHHMg{!<#nHSA^v%Atf=TjG5M`qTl(F5}DHPSsL+R#5?WT=?^_&2Ax*I0*XQZ4`O=$4X84vPx!EV=~u%Wxw4a6W-iiG`gzoM1=-WCFYa z?=Z%3f{d(M?Hz~T?2MczRkGsD^tyhgsW~Q&uW&}JeT4N|8=vtfwnvVfm4s{RT`)qu zs~pZL^Q~>kT1;v5@cs~L@~wav{qqKwga`!fol8?L^&MmDIp_0{tARCY`(}ju4~?GL zRWB&&CMYNve`WO>jZ)EzljqvAmD_o-tg>!7^Eja~+ zhm`CxwC_X=O3#axlkQbHESnkFEWiA1{syQzPyOEjR{hUy{67w_3WmI64X1iZifxll zcBP)kwhLR;w@wS`PXiQ~&XbqBdd*%AmY&cIy?;8TyEx0u9{Cb!uhdDn0}r&S10gMW z@=L>Hozj%W_|5c5_qD&rI{cQk#wCjUo|H;i9a^+!E;f`e{dI629m0IQ?G3>gQ;8T8 zRzBUkkU760?kKZ>%&K|sH{b0eOC&m9Z;I5d77JE#YfaK@cww#T06%@A&M?=_wje0H z0wMD#M|SqaRMoINs>)j<$E2*8svoXkf3FV_qk(COeY&m8isy_0wVvI7qpMYRvSHNKU{eO>%$F|2YT{KQnBQ)(sC z_gs#@yk5>&WU4*IJ}5i05s6fZv6{$B9i2jY27V33PxExyf&;@3-N>&Tl3Bsj-c_|; zPs>+wN-+NL_(aJmD8*zMKIZZ1nj-O~@9$s->p?xh>9XFI290%YtdjYS0fGqhqj#tn ziPg=&h!b}w5-PnFK;M?ZzT?L{HS8YUn9&?5S3|#>efmTQmUrc9M_X!@R*bwh`#4VfNG z1Bx39-Gp4LJPua-3b|%xFIZLk2r#l;)AOTMQJxXEgGy|)uaUfJ31Qp6F7mGP@T4=? zVVVHOV*AQ+4WoI~RGC5D7C|IFrUWRdj&>0h1nIDt?K&E-IwnDKZagK7R*7JSLBYe<>_wC=` z?6~+lH)ZZGVP`ac*m+3Kq2Od@c2?l!(ifH1v(UdT_wFrq)c*94`pBD=Yo#KLV#BGU z6j-$@yeX;cEiS|aC~WVco8{=A=MFZHElh#bfq_d+@N|1eixAx=4RRe zP-bP0QYj@TuSKJ8Eni8vUOCpHbD9&(objG?Y|x;X#ZI@AP?u@;&EKuR3HG*=r#C=N z;V|hy5L{GS&I`JMlKDxND9RkZ{!sqrF9}NT2*N^a{C+o^gt~fvKvSO7)Hp?+$8*|d zD^k~jnLlf6yVuPertIP!*j1K_pCvWfdm~!1cOBjf>!9dnts;`cu8t2~y*QTI?^C=y z7WTQsUHximR?bRHZZ`gOhC$Y1!w<=Vy7eBn824o&g;pH19=o`G2Rk9J#uTahW~>w6 z)ZfI4j9d7wzq);&v3e!jT2HH6qcxx=S0R31dVtSA?_?Lg7h=VVQ`UMuRV0=OOJ_O7 zHMHLgUS|@2p@?%sUXmVC$G=cxUKOWk+}Qj7Yt@_M-kOPb$0(GTI5OHoH1p}EM3oP_ z?rT$igbk5n<3-MdPXyYYa=4uBBhyhj?y?#DHZCQ!q@Q{C(MX$QZF-s4wQyJ09hHh* zJ5o?b%k%8@$3TN*UX1=$xwNyZUt@dARX{KPN20!YDehWM;~3KT#hJJL;%6%S)<;oV zv&79cwZAO;^|F1`y)4hDe3@KPZ*TYVo@yQ(ceXqhN46se3}+yOxuM&N{Eq}tP&2+Y z-hrBrJx-zaQceN)cm?}B6SI?QR&A|KDIYOr9Ygt1i-LX@N8)h$0i}%m)R;aZDX<1> z|6uFxwh|W$IkN^h_jpAZv4s%n<7o_>g1dJQy~d!4=kRSs&#q#b)n6QAV#A!DRngN9 zR(-VVwaPA`4Y=PhWj6;q1;1qj_XEfpNszAb+puSYX4+&1JLw^FUA1HK#<+I4d;huK&oH!p^o) z-z&95m=Rns`qP&na*bE4Z#VqtG&!;3JiYU>HOssXln(CABFim_o<5op#vUS3DiXv? zlbpBv&Is?T8}v{=X``+kXmC8gv&D5Hu)Tro@NVa?B(7A(f_TsH142_jeu0*}wFQis ziF|Q(@H4}5PUI9FG{Rjx)|j-m72Z7Y?zHi#&qq(R4mG4BU-MEyuD>o345;RCOu%FZ zGBgTB7f+Q{(qt{Rq*Nb?e9ht!t7>OgBHBKmI(YoGWRsG|xk{u;vP#ub?}5KydE=ME z7@zaWoicK@d4^-&f$gi|<&&+j*TBh5hu;UVOnvrJ19@@-b;)qKa4{Sr)qWxb<6v;N z6gq!yd@*X%?4I4epIQK_l9ubykLeSs^+Uaw*axWmXf{Ymb5-)gXEEgb8Bqr+kfBIvz%^{5y2 z^rwsGRlYmo!oIpcG8oKIpFA!>0IABj8qob=!D|ltfU|n$Gp;Yb17q6Rbmx3*;kHBD z#QHI}-B9sUse6U7RYBs=$ufM>q-RDFp&_JeIH_&Os$0?joS}O7o-*6&t{beJqKLC- z0gHe4BYzk%2)*FYFgMx#ufbd|pBm(SbgI^av2q)gD6Pp>XS!{pYc1NSf0u>@ETI`fftaQCLgj+_p}ib*GK|j zckq3)dPuu>&8z?IkWqcFyF8UL**&UoFgPqt>C(lTdwzZ?xBpq744!UZ9MrhjlT&4T zy(%DZuv4E@SiLLdMAhyIa{+YfMOIQjA%Ud9-aGy3Q-={-xyxg{G1@2hhJV>I0IJp1 ztzSB&l4X0OInG`5uGM$**%yy{cP|r&h+dcw#s#==C_}4nww?pCuUcB*=yG%>b=E`V zpZQTuRN0NaUrt@OCI;NPWFslnazy3`0spOX@%ra28XgbQ0kkl|WyG4}wg)6duSmf7 zvoscb(`JXe$%@v>if)dXD<;F@v+rBf@0wbDw77%$5#I3+{pr*@1>3BTU(-5vZ}0O! z$MojaJ;Wi0Q=>@@ZLymlAJU&`HJ`|2{4RRR)Zfle<|>OGr+!oyO=9ZSC&4>uKin4k zr2jQQ**dp7*m!)LT`ydSL8@6L=$W7ECrB+V$z-g`D59qh3b zkh{Te3-YJ8tRI+0vHck>DdtjK%Mq@LZwb=aJF8@t_Sq42t9HcQ=I(zx_N}+v!c?ji z(9z|)F-h+uoTRZwogJK)8rxId8q@0hku{e+X0U(9X0UJz{$N1x4WU?IZmuvAD(-4;9`b8Bh zaz;Dn>oqiQa(-SQ_$4i-n3hP|VH2PvOI{q`2;wmc1D`~5mJNL^etHuw#2rx;AbFT)@6ApeeS0}MJ`HU(r zS-!@*4tQ09am0(KUk#@`nd@1Dm>+FI*08Xt71b85jGaB}cTC+5>|1~g!tAYP9vAaJ zZ>T$KaY}IL8HTXWBRAmPp4~@28mA=pyQQXN zK(-30eE}q>+SF~W42qitQPo{W&?YeGW{i1cv6BPHW;g!5GRv&Wed->DwwGv5XJ?&yu+jO6-VyLsX9X_JJ# z=@?MnpRJHH{MVoZ)xP>NRsoULwsR7_>cAppEx;rL3xy|xO)cpfir(L;J(R`IRw4y% zwka+|d}H?{C-ID0#{Rdc=O-OZo0k!dd~=AoeS|-T6@;8|?A;5X1RNhd+_SMSuu!p` z1-yheB*!k}FG-fn+MAU>c+njkvRh`qj-|kU zY)4P(cRZ71j9z;-*yh>t_Ajj%&c6`s?)FLk09mY>8<2%=q=%$+XjGzy?TlS(>q-OL z{L|K&?W*vvk$aolzW@B@P0#%3UQl1}^jZXwK7Qq;v+Ja*%RP@81=m;|l;@^k9UU?u zkV*2qMzX(#_+whLV@fdTNS|yHTk70sHTP%?FSYK}M6DLr0ChtjDY;yZjKyDM2e+lm zs7CH`+rzB_vF|j?Dh)sg7;vV}0=i~_mC=n53yP$io@{heHkv$i z`k->@+76+=D%M6H7w2Ce)D0O_w{|qFd!cCE4|^0{(HD|jc$&?xyEEm6Ri4t(k+hoa z%Y)DSgx-X{zTkR*IuJiRG<>G669}ja$nsbruI`BW8OVRZC|#k;5PqOZ>$LUQhR>LR znXSLGY=z?ZlUhQ>iikoF=h34d3lzIM)jJ0kH)UK>vZKQ7>}$dY$6S}kUIE;c822(b z_#eF7&F%koq|EJy9y03E&CQxUsTpF+(YZOH<&?lke4wV^HO#R1w}9{1-#z)si_Ws5 zF}^#dy6e$HAZ%-4iq!#6$0YX0GSIKpqokV0S@(*()EKkfX?)QC;?LMq=amYo8p}H4 zqf{R@{5w@>>lr2v&zZ$bxgFXkF2@1=ZgWpcxrL{mlO-2@VkIl6vS5RoxQi<)ZutwX0`1?UTjVIzsFj(MTvVm&J@3HJ>5J^!7Tfo zm$Y6yMr2?ui}R8++OjjJKDXVSn()#JL2f`vYie=-?Fbm2sp6#a@;gqmOgIF#b|dSn zH-w830@wcW&s2>xi*;s^-HRGdpKL+Ro{L)Adz(_VUg~C=y%qku&<$9ImXLO%MK}SC zyd1cn8R8n-FfF#a_B8{iv?4nkXkKA>B%PzKwSK#4$#s0;adK~jMDgEI-i~LuWySc$ z>r?H`zq7G}IUCqtom*6|_9P}~$ktt;O{a41jf8_m(JCCa`H+q8txL*;?E1_VNvx$h zGknsQR?_zQ=EoG=!-4#h)y@Y-y=JYGj#R=Ue`Il9uztN!ROpxW$asTDrnL07i%Zzl z1Qn{ZPUz4494STEg`>2}YF;{)1bnxV?q;wuUFTo4TzXqZ!sULeIC$@)%Tm_Ttcho_ zmrn5rvV{Swpw(wEkFtC&KxS-ZP5|%uV*4mo3BB|k6-4cIb^YOGSB^9~;PIvfdr9_J zgDKIzg7TRXAE<(jE_!^sCI><7S0`#WRC5y9>S=2a~W@q_Oc#P_axL~@UtANjN~ zwr#d3yNr5H^}Gg!+UNWJIC*_olQ>#DgnFPKaIS@JFI{GK3Dj$O$Td2{8ctvqOKKYCc|SXBI65Ht)(#aj9jxMFv*#4`JA?H1 zrw%tph6t~PFF}?-MhkKtAEP<(`zLh5XhiD$`SX#SGxY5XXKjv140WE3eDgc~j@O)Z zluAUDieF^p&5!H7jV8J(0GIBbtdePSro3b$)F{R}SA#f(tsPn)ix31VqgVcYw0)RA zMyP5FWW}J03#`uO=GnxGN?zg0H&2VmyEoUFd$Wz?^iEtnoRgyF^LS?#qGA+F!8Jb2 zU>eb{zJgN5xDpMJbu2w**0;&b_eLXI^w-?UPWRmRf$EhVkHZxXj@Pum5pwlC7>XF~ z`*S++r`s^K#(`w77PpAyFM`W*%$jXSv?ma&RR2CSmkmtz$NzOYR z?X@P9p2((bTY@ZJ_n&fN?(9qr!(Bo9Azln&!?lFkwOjD9{Xt$A=q(r!BH&Z0Zi7^i|uCBK+JG%pE7lA98fK6M4>aOJwBpau35*r>aWr; zlDbb0n~$aNc4u)id71z1K;j-29$>XF?Qy^y3`(2-prv0G@+jJ}8aQBif}BlicHCT# zTXJp$F~!}L@xFoXDwCnp&Q3`Q7AnopS!l{?s+-_UHQGn*`~~j^MoF-G>v&Va%In-i z(98lXnPgIk)PL155tcaC)NU8j+#Ou%qn4;2mG^axaU<)_2udSiHt%Of8eEAl166Sj zf~-X9IkqtZbC&frr6yd_$3h-kCgnaefO?@k7;?NRz5lFyK>u{DV*2{du7Rb8()R+g zvdaS1y(rAO3ppJRQ6Z}km76i*xE~V&WZ__nqe2f@$5(p;<@ywn3;UljVvTwbH^vXG z>)XYi?{2h1>r9R>zm+vJW_MI-Px>q#P*#0n)Qk~@Hn)wTDULOZwzkX;VGsZmC&*lk zEl~BcmQ1%pCabYEJ<^tn(8M1@{-wzMxvbjf2a+zT>QRft7VprW=(})`ULL{0fy-!O zW<*=i=E#C?((wueI#;veU~Z>#I0$xowUm&a~f*7fpkf?oU%3r5=c%jsKM zo@*?z{VNJ5er{;bU63=jZy-}Rb@}yiSSO4&lm^1@`|u`*=(1Yk+LJ$M zTLm0_7SWWE7GkcwN>l0pTKZn4BaOr?Hx{wr`K;G@nKi?e(&`Pmmc8hxP3!w^X0iWG zAK}*B-$nX}-mtL_yP9i|+hu|~i`o3MK41$}0u)kLRR?nJYFa7WO#gibaZHSCsE zb4}Tn8TGS~ITz$3lDU3RD4VsvN8?>!zi zSHE{Y{uqBnw_sZIqQN+pN`{#q#5gzeo~i&FuD$HTWD%?c6xqYqYq3*2_jb8$DIb>P zvEZPEL*B+~wRJC=uKMWjDZh}Qm-V~FQdsC-5TH#_J*!N<39gg-VIan;2bAJax=ymW zA+*glqNi7uqHzwdIw4PbYY6HmkE2?aX;4V*nR*giy!JXSO3wLv+MV@41PG;T?HjYh;fE{UnK-T6(+c&k z8V7FTdaoJ>7chymjDKKd08ocD<7%+TYl`fl`Slh?SOb&<;Bj2|b49pIPTlzRxpz;a z#7c8daWngIxKP;KfX`#cX~Y7q2cBTQhgZ{td!(dnFX#m2u%fuS^FtB!<2w86YVyTp zx-}=(P{GR0=6)r9Box&`eB;57Q?+hCYExAAs;W?bEgphvdwa?8cad1;o4iCI1%`3Z zZ1Lm{gM}JxF9aRmixkqYld=r{J`h@ATD42eRUq-&7aeKWRaqI|zYpg;F0}4IC%pl7 z-u70pZr4ZQ$hy=#Q#V9FXCteolUNJP;UeEZUCy0yQ<5yWq?>sRAQ#FoJ8nJ8F!-%% zxnC~QCEK(4w?|`3*q9p;@A`K@Q2*zZyo?#`iq0mvDZ9X^u)f;h8rMir@VY7e%=f<4 zcVD-qn}#puv4cUdgE$`;rEXrQTU;1}h91^&RoKJbUcy7&ZLuHo%Vt+XKQBke<`4=K;A3a~cTcCw~= zqv0t(;ndTNMFu+hyO!11SvxV8IgQCA)!XY5m_vJu(pn^r4C)9X0kS`|IQ#jc@32ZP zXYm(tZke-;db5q^aCxO%o$X5)B)$(TGTMhv{8@8+%Bq2sr=$JLHO4&{Gqen%168-= z=QhXEeUCeyx~I#UkiM!?obCEfrS04QHPX=kBo+O?P96IH;2rfpOY#5nwRr;=S+C#* z9pV1u?L&W@pk8kDPHef)!+)!gS$74m33L+VCvGxINduTPNPMvtAN=iZx&FG+GcdO- zTSl`u=Eb`maaL!QYj8GT4(*6xDe#44SOZKB892V(%QH;BIlyxj!Z3ErO_jb@mp zFj1k??V6|R9yYzHl+@LbfAajveF3=cyCsrIBGN9gRnCO*SZ*7uUwJiG-W*>A4PWm7FiCh1Cv*B6G19G$bWwlk%)0hsOVc@BX_xEo z2aVq%6Y4(EAk~32CS8XT&*@_c_J|)oJB2+Yk8Njk+P*zqOzWc~1Da3!YFM#bvcg`B z*zOjsl9-a&LM;HpW?*!AciW1J{QxQl4X`dLmWdsEwxVa1aHxk~rGQZQO^M}3Xhq|l z0@)w#9+{%c>Q(=u9&h>~e|9m2prKAb>HhI5zy9lWn}m0jOHmWIt9G&fy5yiogzGJq zJnH>eG4a|}LEN6PY&`v}^4$L7VD+Hbfy2;78f!a^kbpf5SrkG^sq@3)dVE7hfbW@g z$DZl^GV@cOu$)`tr|x|`yjb8j-TZV(nsvxr=}OgPqOgtIy*9*$MchR~!yi~a@N*JI z!Ecc%?ei@?O!3~WrBv-K)(}JRfqrVifj~+kz6922Fx}BU8L=Y9k<$1)r0;b7onD`` zhW2hu2q;8#vD|>Lk3UQKn%E&uPZR7m-dhLQE{P3YB~Zx4S0iELc_{5~!*@Am#&eX< zpB=7xM7{TO|3~2I^y8_o!)smDtMYP88P<8HPwTas8vvsmgccsRM-hzHTINoSGIe}@ zTbQUeGobXWv>v*>{L9U#`i6StDJjOU(yqT_Q zE(WgJEEs&Tqp@Cd&FW8V8j8r+{LV$>-uUi)lA&25f~)dlGv3AIgeIMRX3s&6ZZ)K_nL!J9 zW$#~m-CJw6-7{aq1SJr)?&0*LDXk9&Cn>bVfwrZ+Wur;Nw#laUm;{vO8So)59#GQTu6Bs82EGFS|o^ zdr)6!*JrBUaRO@}H}v~u_s_R~T`HelkY;|+FszADq1yhMpiB`{Hj1{W{N|RcJglOl z0Q78I4%a0?Qs;l59sJ|^0tC`u`^X}gbC74Lm@N(HR6GU^*=F6JXR0IQ1(Rr9q^@BgR|eDem^RA2);HA&dJNlQ0`OX2(h zD;4e8Ll7^8(yH9ixPFh^nkq`bNWl4C@es4l!c@&Wh?i7K)fVvZWIRDQb;rp)h| zlLNP|F%mAiX_7%5W9Tq}pIAy@^`6mc?14885Y|iX_V3(zLgJie-=t;h0ClXI(B5yz zCu0tM;veIGhO^Ftg&p1P@pJY*C_f|&APo}G)11yQ+iAMBB61!s^rXBU( zA)l{|2%T2G-{*bdf(!X=&0pTm+1K;$=Dg0Dr08!f5#Echwl55DrN;PTzjy`aQrM4x zX<@F6@dl-QQ<-fCNF5WyA}w`q+m46mgvmCIZS2X*$su$_tv9^}q^ZhN2+I&{cf6nT zedsb@Xk&W^wu=C~aionGL=(NZafI_^n3wr2lp})Amjw!KH0Kj|$zs zjd5t{cr6=|#g(4A(bj(CRWlLKR5yF)bnxk(OQTO@l11)|+dd4p87@S%cFrc9B`2xj=TiMrart}>tGEIM-G#}Gr8hCe4 zN@g$faLR6FnIn0G2651_!WW9%4(E>(6wMFwhY<(h0kh}lL2&Bd&mM?x#!0N?IV3Tj znX7t=%U<`UAeFB`krg#xlZuoR`LjDlZB~LZ|DfrGVRhxoM|=L z-O2?Y6QH99Y27&16*FZzu^=4z0v*C|OTcPCp4&l7c8^L5$1buWQ`Yr8ivs1N+(ath zeo9JN(yG_&%Q?U6MBA6_>=kcyqYoc~@y(;;D}oI1bgTrry=P7tdM2AnM}XDJekf`I zE(Q5*_vLO0R7MqF>nU1ops5G_-xaxy)BbAa<_SiZ|2ChP)5 zkZfE7o|#_?#WPfC1L70`MRPug|Hi`$jydk8%-HcB|Egc6T<}(kzhZMU!}&>R!)jv1J(@pD+p|d%4@|A&#y}<8PKE_94WEVZDnfMPZ~++-e+F37 zPg=Y-5wv(_*nc~o7E@q5U*U9c^DSF3TmXEGi@|jr1uA={@d{%Fu^Xd3wa}5M(i-=} zHFx78E88$MNFHB5U;mR6*q~fDg z?_25KWM`12?^Go!UO*Ou2f*U6vb<;H+m6Y&J=kM`SiRRQ@$QY(!XvE0st{CYNEy1( zot2(cI~%wfRThNLyV_p}tYS@Ydc#+|PA>J|#7q}u>g~ljAzHLs7IdbqIk&)Zn)5#p zgx0t>#2;~$8IGcuST^coQt3*I#B2cnB#CWBc;ShTLm3Jc<@?qpN0gP(vj#6D-j%nJ z)Z^|1VYUY5c*_X+HXJ-|P3FnBiQY22GLj?|)92y)Jno{Rf!XoVk6!oOWv^KOP-~rf z?mu_w!GpOM7xyIO7f{;>Mw*@@_nMk!wg)$^6c!jK4dYm@8PIWj4+6;M$c?lC+)Rl# z+dmEGH=IEd8rxJ<=run%@v}}M$kkj&Jtp*J;ALQln^iKS;29H8Giro9*mM1KzpDRl zf#ELD^96e&E(+2}+9Vb19Id&$nXPe-4e<2K9Xi_vXptV{@@MojrWRGp-&bX)LeVKh z2-bRI?2Bc}gHir$x%Z5s-ptyp@P|9cQ!a0bfQuMC)u90%xwE`X447l~)Ih%(76O&? zTA^T}b$~AnB{`>z1nblGEh`=Qw26>}Oe|U*e@+y7shyiz>}aJllob|3ue(oH^wU@f zIHjuF{4eMO>j%dKg5e~&`*=?<3Z)pMhL+J;##nRRp+I@7r=?ILhyNMnndWFc(NyE| zW^S;-UcE5#G(7M9hckwi#0|NK?JoTV+?jyFHVQ(uhDk%@;`tp@c7)+Ergq=%J(|?r z2d-&5eCpjHvh)7M9a3+1yBJb5(Q?WAXtzIOGtz zr;8-r44MMCXUZWfl-;G)ZKEVc`{*W2XeIh#FJ2NiFnGS|+}Pib60U~wg+SD{M*))B zcH@deRJIAHbb@pW(s5;xdqKc+`4L!3okt~s1PnhVWZsJSXyj|uU2)Yso_SjGyc~5uKSDGFV#$pf+PJ$PilE#)-yLE zsaJV8{FmV$?Cs1ET5;SG*!hy{2$qLJ{}Tn|lS)OCkQK_)!_ zUlj(IKvHBoORr@d2--4yh6_iQe49Hk+H4XNvzOFf7?T`QkvMrU;YUMny5k2L%SQaZ zD@_46PxLmJ47W4MFOZwZ_YS85gjb#W-+{LaxatddV)`We(f5?Ww)TcMcBT*Y>R-7k z|6JfmLp=)!Y8EFR>{BOzt=cE0D!idJCB<_?85s#qP`>3Ps=A3L*;fMPlw{d&#};>m zNIRA?_e|+ZXvZ^7^7id71CNeiy`t4=QvacAvjtdv)?EJOW^AEG|-vrNnU zf#?t_pxKnrVKa7oVI26iABa5qqcgg69+?}n^t~n6S2z1g+n8S;bRZdm4T&c@tOc_6 zC%r&U$_FIZ)}q2j_C!iqtK>?QRQs=rLq?H3_&IQ2#HDa=v2xx&Hqa5koPqH4q2#O+ z-P=8FW6POuD})ET86TdZT`CUm()iiP5O^B69mAFUb60&M;^oQxgy|U0YZk3L1m-~< zQtYpZWvx^Du!>_h+1M7%AHN$s+&$1Z=MDhgCpPu@`2D5z?qwS7bC7OhXI9SIlszgW zPiC{FZkg}@1Dy1|{=9T^S$+?>89){<(`8E-%*)*oSlCGT#aaxqa(Sz^Avj#jaBw|5 zrpCx-q~`HC&*VKpnms4??)@juk*UL#8shKM*w(|25b6;R_$R@zoz#aD27k&m0pHRj zdd9;JUBD3@crIiGYGWX)sBBxBHY?;|L9Yy3tsu&#?U*9$i&UJ&+R8L zU|boup29sXiHs=IoB!<)>Cj!I^nV|2yso4;rW$r(FF8_i=%VNS2X?x`LNMU6m;}~Z z()@R1)@3-P8o9=v_kCF+1mT zF6-m#h^QL(@(a)QH0=7fba~9yH7hPZ(rr|Ila^<%_5-%b!+9sM?PBM}C9K&>A&IVS zR>g%U@ckn5vv5liF%QUFT-zt6Y{qUc5erY0r!ZC+$gr#ECs`+7c~xs{xweCAZG{7t z>^BJUg#D&BzzGNHHgnZ)Fe$8hpsVpxDr;!oZAQC!R(QyTXf!#3B;J9128{0hc1<*B z?aUQ1L9cuL{nW)>LWCTUG2;f}8Zg&^DvTvC14F{&`NuI{EJQy-Xt_av7)KfW>RoTF zhw8O>Z(N(KQml8&#%7w@rQr<*#=QjS23v;%T1eyQKuQt)ihVq%XoaNN35CvY?Dj&_ zF6Y~@LK2x@Ag|{umqxGa*$Ln)9Bvod6i$j|dJWbbdv>__GT!K<%Uy^4sH)rlhBG*iz^N102oaiifh6nm>;NyTntEY#d(9dQjY8k{I)nKXc+hrT;kRI zRP5ympCD_)fk;WQCjTHH?c_}^fac(yeVudS)+@?I`(o&5dyn*aAk&Lr5- zanSwyYU+(Eub8BYo}!DVaCewmlt8HCpa7y}cfAnF3tBO;^GkFaMOEKQIiqUzPtDxo zxqYrGyLsAt8oV(B5HZh!n1JO-4WR&_SXVPIJB$ZtiMRRE9R!PxNowz}%)sc+_oyc+ z-`dET#}bj1k*89RQxBOWhGyqfmbo|>W!_7pMi99#*Qc2Lvp7>&6Fv^iFAt(|U<@%L zGz#_+sDhplGvxW~JJwfQ_U6mFgb>Wx7xty{RPls72~VS}fF}U-GxN9quc2{l0$94U z&cSCQ7m~P=spojFp0h*xwgKke3$ka>9vK?%B`2Gj@G4Ge8!D0bRLYf{m@f3%p>C* zgu>~hnMy1St(NAY%*FRZOLJelTYkB5SOcerLwr$H!}kVX4bCec93ku&uN&HLV?SqK z>$ko}lFjo7L>f%Jhi(4&#O3f(z+T3|8WS47?#n+9IvW}2J#46gK4(1l9zgZMeV?VCvnf8r_mSNO=yCz zA=zY39q*N?I~&-c!MlJz+G;L-fbQK=OzCes{uH%$?9!GmEzZ;#7a4IZa7e_@Yw@bf z5eM;~uUS-1CnT}$=*b7{6W~2|z8u{+@AigvU!Cr_7mYukxHV-pu{gywC|cQC%Z;F< zHRpV`qPoSc%-zr`Ju+DBGplrv?*{I0DU1|rfC2E-)|^2Wt{Q@{oPIF;_GVwSh2$27l&gX)LAVQ1c*GkKlqI68LhUgk zo^Gu|<;(f1fsyBx&ef&x$KkK)xROcqkT;IWO6HIV*ot`rL}N$xcv$Ap;>=|%=b3i) zS^XZ5DU*2L@QRwXIobM@&(y$3x2T^v&ucDU@o=cXm3c2uoxGHc zG+2|BlWnly+kLF0>SWjt><1j{lFoe?k;W8a8F6~p!^}THZvtF=)632V!c}tNg=3f5 zzOxfG^f>9MU@g;~b)J`Mr0?M~wNCaKo#;BWi(F)8)I;%S;yoJuxo_UIt9Q96bIs_o5N=kn(RZ|HYATgW%x` zL~O&FGkAY_O<2C3H5{|4bf=6ue5m^nr5kSdjxW1Wx&71^+~dsucEn?w#iuu1d96&6 zHlJ$GI&BHzK}cTy-wqcfPdz-F?HzXNh`ks|RLsTB1!DouKv_|X!WZ7gIxS>gj`^bq z?5ycexI82YgVZuo`KO_tH;s@72k}S_A%9aMh*PNP<#DCHW#m39GXB9B!CJybeOvYG zmQBb{KzDM5yO^r|x8vlyKSO!rDr^hZ9mKzLqP*bC=W^y5%j4_SHKWwWz&K+Z$i<)k zzw#r#Jj_6FpW>h+Fd$Bq0i_^vPaBs6uFf0Wpfx$rar_)0w_`aHRM-mTkswo3CrXfVGHGGH%lg$|KvEg2zq;&)+Wuc6+QJw-pOzAo4)J_W$ma#Vj>$ z7FeCE6|psd2gw+G2v;`DC5UeGKMap3fbUV*k2^yaz#gFi$W$Hc2Sf28b1@aN?l(U? z6iJOti-dh+61i&+F2#=1N_qXz8#4r4`mui?H0he)d5 z_@*px5Yipc4-xhxR@8YQBaE#GeormS!3Y@Gn(Mi5ZCxwmrfyOq-NT6T<^?}^^&26c z++4wgZNn+E(-hq{9q_nfuX5nB$w$oajNz0)uETp={Ith(5kNby8m3e>CrQBxx%AN)KYzr&{U?A zzO1|r>(|)_O~Drmcd}!;fe0n3K}Yg0Il+QEn;4iZy?ztznF<*_TKOBf^h_YsQ!U}g zCB;kTM%rCVXS_|stz}jpEsXBjzTjILT@J(j;eK=x8LdRXUoy~Zd3F=Tdpr|wq zIW^@{omg8?=82}N)l}-45#JPhW230D6z4+Ub@qE#J;Y3*)T0dJK7Qs1}5o(++<(C3;3a$~H0p z$=*)}9mWnq=lQ}hU~rp(RHM|AfvM8;?yIbfwC6U*M>k_}Zp4ZC=5bMb>C4PEc5azU zz{@-Rd)$q4zWm^rN<3q&HB{uX1>i|OS&JPOmkkHMeZ4!ZT;Nn`9Cq}w#JO}wTeRjC zjgg{r26z7H@F+U@GxpoZJp2E4n8m8J8uh(24Rr*xR(gn>20FfPPKN%MTv|OKSFtEq z#iiEk<{GDZ>G%E})@AR8B{4KJiEbGmPTRIB9eLYuXeq4Wa~Q9{_TaORr&x}VPN)X1 z>m0`RO5$#MNY9Zwp>g`{Z!CTuVOe}}^9RR924hJS#wm+Yh~K`5!LW!WtoHeVZgd4Z zypNXNRFx)=IM7+1_lpucnWa>&C2OY=t!HB81kv(+qQjbfJASA>Z5U{R(zZkR6MtMH zw8Z1EM=*ETiDbHGj2&`c(^-mlmY-(zLb^tbo4itf`8!SZ!_GzN?uxgZFrT*b%}t?_ zhcDT*19u~CJos>0IP_!H(v0k`)Sgd03=LDupmA@cOEs_A_&{dQI0qdau6i}*;*~G1 zE<8c;`HD0MRwDk;d?$+_$#N5Bu;vagUKeD9$oL)j(5Ok(c;hmtx7L2@x>$VIr*s6U z?@D{49&Rn6QoBaG-qCfhUG)xjz=P|wcMq?q1}u=pDLo8)U;Y6Hd~D@;`m8*zNu;!M z1fwR$5y-+xzkF1#Ko!x5`6pXIICF<$|3NOoQs(!1;6IE|%n&TkN2Ma={ay6q;A1xK z#?yHXRnBE2p*GaF{JMFP)H_H#0qqlq#y)=a8zyw?jNAT2X;rl|E<+8EWe)F`l*~d6 z+NSLbHQbDZ9nO$W)^vEM;?Q8Va_8;0uBU%MDmyF`_kF-!hwlL@vT0Ag@SoZn8}i+j z#ZJ=>R^$8~L8D{T=&?`cGF&fq!lZwX#B%OdY{A2{#l@wvsAARm_Soa2D1=Swl?P?l z7}l*WSF3(26nFf#w52pLHe_2IK7@~a+qA#7<+B~c%Ot`0eQ?$#_>3oKjI{WG<* z1%5l5OxH>6Fr(Kfzlm;1LWQ4bR;UrR@Rf3>qQ`orF8w;~SsIj5-_Z12tnUuZln)Z1 zPB^hYs}|rP@4b)6D+HmYAXtAhfg)-_j)PC(O1$u1_BOzuWm#1Lv|4akh|wccHQy>v zT3=*&O+P+R{dL|>kq?@eUVhxcxBB*iV?%rP+N*DHS>^X-JIe}B?vp=2t|>p9KY?|L z0O^p^ae;NIBvW1?Jj4shiG+}>5XRSToD%vf*Ruj`J4OGP5f~an*Ev9S^kHZrudu~7 zvvb>C&U#K$sJ4IqP|dwKQL48W_O_y*x!ZyT6ly8Pf)B$L7f!>T{AUa@OBTR4G#1D8IveiFO2Ow zCMlz-e2ED&#lG_vgFwu%2k4&Th}hxd!<+`lnO#RG68u${UbFGta$8q3;=&@>`tqds zyX8SfEOhGidSc99xhDCQzT;U#af^o-R}Y1J<)_$6Ho6e>J(^#;e4oX+?dI*lD)Xu2 zdeF0hpn~|>8uAC{jbc4tkqnGS`Lev{I(%jH_60URse-lf(A1H=n~~tHJ61AjYKN*E zs#tzH6cQOdY8mDexODZ-P)i^e{Civ$#urpE0zx45mNhLF!Gc$Wm0AVjF5y6$n5o5r zcuMkumoP4I87a^rHvH&k9dna}I<9!-T3}EkrITt~e(ikG=jZ?JINIcrrtL~Njj}A? zCT@Iy*6#D$$@Ab1)?GaF5CB-0gE?!6Hx`DKxS=RNj8m>`E zXWs|%BTbC;4P9WY=YG|*q}=Wq-ykLIDO8TUDw4k z;dDcG3(FmcUT_V^bo6U>_nm@#oFvGPHWB;Lw#d~2Z!uy1*NgR;29NFcJklB{q{?J` zZ?qK7Rvu(jGk$q?YHqBw?B>Yv_rclL^x-Ztq@L7;gK=Hiz*lzkr=|1+1JIGy{=tm&{TZ!eW=RbgU0jUH9#xg{HzYxfFOS_e+(y&v0!I0 z5OJh~0nsgQn7E5$1|bSil0A$0kYA~9d_T#txoy0nB_-yy$n%TJeos?R2;bBAyr_))d>O&r!EXm!Eda@Uau;GcYu=wLqr-VM!q>(q09r0g8gq$Vg&ijCnBZC$ z&#fw{G}_1XKCH#}6!Xo_yN+zS*(nTJ5wE1I2>tz3#W&oiAKMRO-TTG#tOPVhVSOA$ zeuxwE33~ua?Tf}Gbs%cdNcuAtsiHV=i#hHQI8i!P!PZ(cl$DR9*iV+368Rn!OO5}- z-g^Z#^+(~NC?FtBsZyiTM4EJ^MMSy~L8XPF0z!xZkrpH*Dgx3K1QZ06s?wx{4v{Wh zIs_pkD7_>CAwm-G{@=N0=FEM$=i$yhFZW^3_z*M9-aEgw*0;W*-}9k?d2_XB^TXfw zscE5;<@T1cIPSyNr_6TTgPoV)YK~3d=w-(OA)GK`UNVL~c;}gsHZ7nM-X=krj7@6M z7mdOKE}m_@wp0717qCt{;F{r5)#iv#;nV^B8@pwL{SuErx|nuxuzx@_m=(PE5nQX# zIRfDR zepQ>^4G07dyhY{z7M!WCIdEXsPdM0=%rQCsf@XcXa4!MolX*AL@Ze(FwEaM}koMa& zR&uXtnmdrKKuAGd0o1HhRobyHDjo(W=~_;?|9z;s&xyTWC%C*pi%jDmg&#@sC`OJK{o>XghPiZmC(WS87E?oB!Tb!yK z$}*L|o;l^BOBVz)xnUpZhKvFtqsoB&NE$sxWlD@{LE^J8-BV*{$LXeFMKFg$n|>&M z-RL~I@K5b6Cujn%b5V{_`>CXRN_v3hw}h76=9qPct%^tb>Si`hC2Q(ppWN?=pZpuy z%#%ta!Yp)AfqohTAXQ&`JHhx7qWKnxBGU;LGEgLmw9x{;tR7sNUn6_0c5EAamTVku zd{ZeHUvTgYI(uE zc#2*|k9m+S17=GWecHhz?WM9U#^8rX>a0Ps4PNhkRLLoF#@!v^#vW|Ub!5? zNXBpa#QNgXxZkh?n^bkd;L0MPi~Gm&Uw8B$bigLaNND3(1qrvXmjO;w#U=W*Y~oZ7 z38A{vqbHzKg}8ulG>#!+x#iSZ&uV^q`bptKg^Z>2Z2)=~sMXNfpqf0RITJBJwUCfJBWBzDaWaYK7 z?Q=B~n<`u9CrD%3hMpE#)EP0c>4AnguhZ3W9Cq{sJCo?YWt+c9JMh^aLgXgLUJ#gm znw4r6FXrt*Q?B2PkO9mpl;J@{w=tn>?V=#zzMsDkXE@S+OdA9DB#m-uw{iO1P7x|47{(?7cY*6pEK2|ri$|)o& zSNl7FhqQ&>$d21*r-mLWx(pfRY;x=a!^epg^a7aGNSKzkG!3M(+0d_&Ay3;x;BCqv zVSpM?DS=G0`Jwd|1rzyLGB?7bm;71Q;dRfUPi$FXidS|K>^Xl+F^&to_}NJ3KT7Ic zDfJm8fzc_Lv>qis&&N69A{^DU2pHZ#_hs? z4@~W&OBCmi`sACTU-pGF=P^BmU+BL`pCxby6hqU^?ES~Wx&o+&R@*KuFlVE>9WXq< zruX(Kj9WbkJ{>$FXZ0Lfm2gW#B$nN&B z{}*tO|F6D-V{e6`#E_Q15?1^<>I)Rr(3-GRn$f};a_81=vS z!K3Lo%pc1KAgUI513+7v!2N`LHF_>n3V`e|dq9Dl=`>&760-9cDmg3^{=WfDlrK~d z_J1tjPw*e^1pQ+vu!*1pXSxi4`^;!V7^<(+1D+iglme2jZQHB)@fTRi3EZn2B#;0t z>}oiEQhO-OVsv56Qvh2H?C@LDY^y{Qt$xG}KG#^4tQcG8GZPLa@PdC8(7^zXG}n=L z82PnGe!^>NU8VAN3>H2Ks4$LPn1fW08Zf{V`RGJz+UV{hAx_wMZ~NCGgF|b4@ssG` zs?JaA$GyS63WI855=O)k;NTO$c+G>qfopU-Q}8E167GIsX#MIj|7CJlmZV4>9yWo$ z)F=I=>HVLdFHyr{W%mfl*OuF!0Vl^7z&!674XB6rwR@R~!p=O%#j1FK` zALpi{t;OOpKnm}_)GOZ!JGqm=jW?T=Dl71%D>;2?)t$_;Qn&w8N32J6O1Sg<8swEsiXQ!`Eo`>gXXclyTansHprfwMia@31?Ti4-pjr<}!s)J%- zm5Fa5Y$+UZ)_)_gsdcHalHAjm_<7?z;|g1W9NdWsh!stYly^q$8Qr^ZrXpLws6gbkY`w?$o?>(D&#_^f;l$qX+l236UQAQhQ@Z1f?!IZWLDvbsf^l#N# zBw$~0@1}6H8Av$sb8`AcS_5%K{EuDx2*2>jTEX#IhtM}ga`1@h`NA!)-)BC#t1mmy z#-A9fg)q=Q)hHI=E~9DCCH@Y;rLO`Vc@X1M8)hi?1p^gPA$(| z$>2A~g8w#z+HI#?X5Ho$w)Es8;Lx}+HQIHp-@7hna!pgh=yDwXe?A69C9ZH3MF5uF zi(p>57329P3Pd6Zw=b4+-^v)RG%yeR;<7sGlx_1s;*&6zyT=-d9@VmB;p-gKcqpcP z3{d68GcuW{=t?x$XJmc52S^#^S>f`tolQ?JGr88yA$%!a-p9LUd_|W}bl{GhG3%od zyZ7f3x~94v3U^OVxOCel=>>M&`phnT;nPBie0J?PqNsF!zM<$0mW?qn_ZaS`Rz#(1 zuhp!8y(?#eIsxjE?o%w#3v7Ud)C@#V#FBVT%#ioU^L3@H?Vh}~gomli2slJNzlKl=)NhvE_LZ}<+Y+A;s!#4>vx>Nxc_wM;c$VhKgIi1 zLKxsA^OTRc4*JR;t*s{YpVx(Oi(H|rN<|Tg*QZ<0EoU(Lxk+H;M`x(1l5p@{0n0mm zd?l>Ud7t1mOOCXV^;AeKNi7*b|ENWi8yusbxf6j)+dHcQ> z6BMib0R`y#VX-`-87{iL=@SNa-Veuy@<)i-N>2H2ai!G_uX?4|B+kLI}&DHCJ*WxdHVfpX4{$IRUH`Hiy=Jdi{Y6NglXLsQaE9f>J zMY*j1SWmxB!`2Wv}Uh7sMoatI)1{4aiV6e-BG#AB#{;_V+x#aiBP+}H{e5)%om zL{?UTNw6v@7W`HmSc&AO?B4~pNG$*3Qref_WpWKX$g5T<+0rbb+fJxyJGTgf61NS6 zn4)#@cnQ<-HbS^KT&cXzp!DHM`Q4Ipcjw|OP5vT0#_K!5qJR{V04&5`69L}RI^O>^ zk^ldk3)nYwU54hd$?2lhg=}J}lfRm(|F3xbg+<4A34`nLMR8}nrtWyl_4RLs0~Vzd zx6(Ycnw&akVcL=wiyFzTcLPy3lCt}loN9DX_$QA8@ z@B(NQtmX38`=w_)-aG!5!`#+8=mOyXdSc0zDN*eh@Kh^bFh!8k9w*i^@pmRfo9@iO zC8Qo(ZU(4`ZEoM+4S~sMK=t#R47}@=j?wNM$OLH8`h^)d|0vMv{jZ5RTL)OTZ&qS~NTz9UZvEH+>yn-l zb$~RzIZh5(^4VM=;Sw>tMkkS#)BsB6(~|H?lp@JZ>E^idzBQ-7!tx)}gq!1A@(sKX z$9rM2ns1VWm5(Mg9FsBaI!tc546UBON1j$rv{CEQK}(Sbn{smI>9;J6GO3PLS+Fwt zXHh`|_lH)_vSM%O#ie-j^!}0gYPi|t=Alk*lFP_Vu=0h{6i;$9gN)LaCdf=I;A_{P z>tcpOOlZA(NPL&LPI)JLY)aRgjWuNm7;eGU>okB#P|L{CPBM?g#=Z=>9HKvW_tej| zCgB3OsG{^D(LLf!mO$6();N|&ie_aV+Kg%8!ExAZSQ7$O|A&=ys6@r}N^1b6Gr|`m zbe@q{j`Nvwj2U;1?KmvyhPz!8YQ|2UeGC0LpnN0Qn=4GH&XG&<>^q6a>Brk11OF=u z#YOgbhJ!=+=<+mEVmY`2Z`-{1kA=nDyLd+SIaf%|limM1^*iBGS?~-f=5p!9*JnD) zPZr?;G-Vvt>|BHh4rrUJsAJs?jc4ppGTT#T=ctaOErN^9gjtzhzz8#b-%xtQj$$6S z)uLIs?$Gow)5@zMhI?M$QY1xIdA-W4+G73b35Rn@sDz_%^pHCnSs{igh4QD}Ax6T# zMluyIkPWeyI z>X(*RU{^VoRfOzZjvam`Pg9T6u9P!Ja`|Wn6e#so0}Wb){B%7AztWD61g50HK3{vb z2~`6~N$ucB=H>6x1a=y3aiu)~Z%zVryD8iz$3EZZXCuHI zMBJ6?@=QgaB$CF?_}{OZ6u{rVj%##gi*SC)lG&5*KDYI9m~VYslgI7^|Mw=P9o&o>)t!8>b!^EH<4%hTG{kj!#5P5& zx5TDb+39kzvu7D(7)q&3w&)c{%a12h-P#nP*Qn)<;DscDZ7X@cXS;SOh&5pB)-Z_*s zt282%Q+kMqnipmpqtC3|+eY?bMTE(kJvYA1I*<8PZQskcl8 z!%h=-??I#gq-5;WtF#+dZZeKkXXpyR8IT6U@ds3t{RRRAclHh8opyc@wsBgNZ3B6K zqK<^WbTb?u`KzMqj5TN3z!odN$4MrBL6-tb2E%X(2f61P!;41g@JeKWZHh+O%{3Rk zqM#DkfaGKSX!$A5`X+=kClAY+kA3}C>Il_4x^nQ^`TTU!SZj7z>r{ptWaAJB5Nu=Q zgS`XTnZxL_G$=`!_O6KTM6+44akOb@Z2NH|RQhbw50#z!+0MQXRkrXVmlI1|6N zK>utZopsEA#|C`a2R#KqF+#W6tTpuoZ5mKs zzWMuqY<}u8bPK~vvP2mFuZ=IZ5c2?>ME9nyjQc`KQ*1`3m<2&QfV}RMH@FDEzGIDOQJFhc| z-<$&3R3xP&J;64+s+KNo@n4(53O-478??7r{#w%*t23%+@?1h*r8O`~PC+bl%=FM@JfrK`i{ zu^RW9ZdTdaml{{SYVHV+OJ(w!qB0KRyfA|kivEr}9=wC+ zUt+d~k;gj^Jh+1~FAhT+7ZE4#k0a6j2RF&g*hrm^QZ0_h*Y!6d2I4YgWQkW(Z}iNv zVT{t)1#bwk(Yp z5zb6+VfdK5z7F`|#$x@8&;{<=<_5E-2j)iC1J}m6ghQ(Y9(77i6y|3~s0}sCw#%perksZqaD=n}HJuHrSRy00bWb}%cvr9}chtXm{Z8fe;HG`9l*k07se&%9ULCA|zr!miUFy!+MZ-0Qyr=UGU&W^1-pmgpOC(ino+@ zwqkFfm-t%3Ls&N!rbSSudjJH3sRaaLy`(9UV|o{t-v#-U&>k-tSLgWOpTG`mxqBtt z4z&dFjJo0R=|A~qjxA|@9=>HohC3@LmBQqk>whng-$jj@qW3m1$J&mg`=!~)dbSk*<>{fv z#Di-9{|sJ~HqGbA8l_GaX(aDlrRaQQuZ%Ymr6oXG#=S;!B@%4vmZuJf``0y*2kzGs zkJ1ZVRZpf}D;F87v9cWvORgN%uXMnPJf(lg$9>qSoQ1kaZ$sI+Ix8wt(Exw!t+czdacK`2ZMeT^Q*`r;z*|;QD~LkjmI5 zsqcVc7>QS(csta^Ui5+jukC68YnLz#Oo1?O%^=1E~fo^GqDpVU^|hjuqi8GyY%Ez~UhWKvDx z?|%7CP{dCqe5*{{*sEclto@!>_m3q>+QOy_4$QoHY-VSI44RoYLSD|6}6M*XYj|B)~ z1pIlRMaHEYV=$)Vw6=||Nvbd6ckZNW@Vxe7lHK;nPM%D)i!Z;+eV5g>b5W81%M{Mv z4QsY^4gj`O$7m*|=xQ_`SL4yC53H0q0MlVXBu5wMq>6Uw$RrNKUHchYA^i*X2jh@Fkm40P;O4C^zHj*N(51AfH&*;DGJbrKhds81k z^3V+ekP}#>S_h6xi@=A>TSR{(@f!8^1gPbduiN{I++085EN6B~+3{FQ38Y48OPTe! zF-Qab9aMAB4&g_SDt%d$dOKS4Vi_~WQ;}_5PTuX~MQew_}9Al~g+rg9$&Ne>!&Hp6z zk9?;@Ie++7h3lrycKE!Ojhosp^EP){IdPg<5A#+S0Waowu(t7|EXnn~1B68PQ{&nb zWStEB3&+gY11ig@S)MKsbOtX^_VsadsjibllBMq~@upzkMz5bTN zZPq^z;u(XqbN7z;(9@Z1muWJKDNjAf6?8jKqDp`Zc_ewM(HRRbRXJtC)|8ld&i8I( z`u3LhpWn}Q26SZpI`Mb$+D<;iN?q$aQ|;nD?@}sVR=3+HP_@>_$+Z|{tGVp269|(Z zuNy&RPY)EevJ{yL-Fa$fD#Y@?;X1~+LI)7DR5hU917l28NeAvQj27mRNgzA%|Ft;S zQUvI)qS3hhW8`x5hyV4D?gKpJROeVmk^#Uwn)XlmQ@;xQbQEy0m`wsh6`8vWZ0 zFobtu%+Kr*YJe=|7b^!NtnnYqXmN{QE3i2Db1aJ4{PQ2n>No(n^I^VsV5q#n9>luP z8@h=8w7!}jf0Xv>KLAeYBRQi~Bnt4c*+~10)CxGY+En%(7Y^u0mEe8CkFO5M%JRJK zTUW~cFB8Tv3%P5c5=k|VV%2#q)8fbZuPwE^<@|kT>moM7qbEjN z9Cd2uu=U@IEFJFtx?4X?sIN~!pP`v7mUITp)_lv_6lV}#IE+bT)Mt+9h@;>AicG5T zR&FnBOjZ2xFxA#m?j@V!FM%Nq9@**(dyd|qS7&dDy-RjZ06EC!4gQ*bZE`XA%gvzf ziBc2pD-eF;@kAlXN`F}K&%gd#!EIJ+YZo&i^H%njR*tRIwK3`p15L&NegnkmL<4Oi zL=^vWv9nELGShH_>-eQmUK3pP&GBC2|q-TEx@i_}!ARB33w zC17KAQl(Tkf+DB^|P3 zG<@n8&x!9_?n(fr%hpO}6y?nSpb-kcBg*|%+3wZ0G{z{rI@|T@cl_2;F0%5waF&;Z zy|b|S)oP7rwBzjhTcV)dg@B{Oe=PJn7k0$1bgcaJi zrXO+CCRk^2H5673P9^jcrdmg4A+xM530Hxgzg|&Wqx8E{mj&c}nRk>ORLa;JAe1T# z;x%l9`}>wZ_kk;mg06Xh|D}VPn+4v$6^E+4iWA2t1NIre^Snrp;Qj=sswWR08 zZd*eQ&dVi^7o+dQw?ZMO-E+?i3u_OV1y9)jKB$!4s;HWeyIrrE$K&eKwnnd|L)whk zn0?ZxCu_(#{>jgg}dcs&)GwxPLjt*n2 zz&?c8xlVBPgOXC@IFz=h?ZIPSYy+ao;uBe|xunh^Es z6m|JR-Qx_Jw^JP5(v!$?9$xAn<4J~e4`V{_bMtm`pn=a)UB7#s+;Y$z~Ub3N@D?PR7zKX9IM2TV}{8CMyJ zjOW@adgD}U#+UN#eQqUaLXy7SDdWHpHceaG%%Z2~;w^=L$v$T>^*4 zda7>XuV2T=0d&EQP8_)u%a6})&y|@9Hu<#562^dS4s z+G4%$Tx_)7ARfHAwJ|jpyv?zOp$pg_4nP=RYoXak15k;;MK#HfxqnDXz9y~FU+-uP zu0DH9{sL8g!s;FkoLID}Hc1!`>(OhnB!T0F!VlANNq1?U5hRPPm2na!0-4H$hA)LL zHbe(LkPl0PosgOf6A)sW$)-s7CoSl-OT3P+c4`AKa_VMpfZA9*}GEo!95Cu+P%k9MX= zf(;?^^LlkNtjGGzSea>2dW+5zq_e!pvgjN0}PGwsq%JrYN7V_b7x6 zRobNsvbLGx_0Lodf{3FOmkk%l@yNE*B*j{XR2vsV@RPqi0}q36S%QH!RaNuJ7w-sV zBZ`YS#LU#~aB$}}+TH~Jv>LzxZa3IcZM!^=a;>6OE=uG@H60|TB$lLWCww-rT5+66 zE)9eu?g^L0|EVhod3oyR1)VR>{g-ClF+f9YOi@BAP`Ai=3FlI_REQXMy6RNHOUdwF zs|jr=_6C*k>sjt>#Yj6~F92Rl2XpL4c!<)@50G|}3``39sPh*GdUWb{XL`d0G`hbI z6!yIK%4-91YLS)0V0`uxwdBZ(?n&Y3=DpSz{9Wx{-PoR+mhM}sxODCTZwcP|(rrQO zQ;n|-N+pe})y0?I6&L>&RrNX3W4#a`b}MrrWFpJAM<(O8 zbezlu`kQ$33`+|Qi$AA-G}pY|Q?`MfL?t3FaKltma*|WQh4sz8#_~9j-9uR zj|MQZ43n#ooX`d4F)+`EwJ9C>=Xo21W$4K_UvwE@#0h=DKlsdx?+LZOu*-9@Kb%c? z=}-2O?pD;%RTOG$s}_)AUdg^Wx>dAmmR>mHM2oO((09JJ;OzT#7V3VTJHV;JGgbX! z;YU=L14v8E>w=8NmBip{n>Qncatg>o{li*IBJ*i!h+Stlcf5yN*?-2^Ea3ZjFA25g z(f%#YL^VOAsCgB(Eu+IaKme|>c2Ml}?p4$EjB!@6i=lPfZhKZ7vc-A_1Dy2WF8wT``eIp<=XQ*qDsST=yL!7affcvFdf33 z!i93A*?d7dEN?@T)EsaW9_sXo4rlJdda~)g^PLwJRnA}YzR7}j`sMgr+BVE2Q3U*a)?>T3^FXO*@2+kE+zO?y*wO(LNTT2LR5o z@WQ*Ljb=DPoN06Gn>e$cHLeVC4?4y5xl^9CwP>jOy^+ZaXC6mRXQ2$%?U$H)`YM{# z&jEOmT_7Ne#^iHCGSG77q1s^x@5TDbm@1KXDj(|c&G{(<8Q0$xy8hM`r=ONhOSP2! zmHOCnf5dg7czLm6+@KrNL! zOR}_dGQgDLpeaFZL@irl%;b-*H;kLT`$Vv8bmZk;@DO6DqY-T%&wzkrZNP&*3b6GC ziMpcznG;%>#s)?9sbG0MJF}=C4&k%wYMk)3eB@Sf_=AQiU=92XkP)6hYcZdz`X4%p|E39s6-2Yo-%8@a%+X&3aF;It*QiN%pL}p_^ z7+P7Rv*U26=i{gA&LoR%xp?c#GN&xO|2kPfIe&fnHnN2-4WVdu;ZC;k4jhT2ZqZE0 zJKyTiykq&4Qooz9*M5+Sp6ZIvno6tF8nu2i$~T_n5+#0L49i=D#h7+*z>9>RLyq+{)

@@`6j&ZeZhimLfb*oRtN%Q`we3bylN>?`yjkI_4;_8 z39WQ-L5*v2k70$jkgtaHIv# zI?aefX_6x5A*KnAG-F~)e4Ti(S6d_~|k@-3kag*o6)L%8GA- zAOR*3q81>+M(TFuT->E{0WFD=VT5Hf#i5a0?7=H2{EG_}f>1^1TQylel;i#7Ex@0yT=R0EG21bde0_;{=~J7z(PAft{;d&1c_o1kUjhy-xaRdNx)IlSi_#a@b;q#c zC5wss4Y>^WDb5ekT75a`iHSpj5}(`}JpZh0J}X7(jRCxU4^ahLoN6p@3&6o@M-anX zjD<8|6l9Z!q!s>lv`@TmhOvO#tIYCGV~ByoI;2-s!LzC%+(V&N57!xCX%xFoUB5@W8*CvwoCkt=R zC4|}-U$>UD7sbAMCYh=_hSRY@2W#!t>DnmWeXeVUM=8)k7E>f?9#?5e zWUTVuT#iB)K5E8XHngtp(a&ZLb5$T4;ceYH$oylbhZubZuq@F9xPvz+;HTIW5jYU! zCo~2Sim(WNz}7E>Uv96If;`)z0lg{%GwKSg{n}Y+82IPDwK8V?*K6?4P!9$h&?h*n zFIxhCV(Tnr@2Ab}yQKM|?fEu`x&V)a2>0!&jRw#-f1Ck^!U3q1A{N0?%mD(Z<;o)F zG~PGV1(Qe@B*9;9<%>tR7PaVVm?+J;Rgdrv3YrV_e3o2OGAI+f2^v(Jd(uh$NXEn( zD1Jl*(VB@K(y*-GjESiy;25>G32KmYFXEAE$f*p6s+aRG?h{sC#mRVLkqWzk@>%LH zf2XRiCijCWV;91`Go`&!Et9k7^FTITq++d~;x(>c4lh?Ao&+hu=4{GxzA;njo=b)@ zHjKnz{zM&@o+QyYt;#69fM*YRc4vl4p&GQ2&dl)_SY9R0wxOayi*Er&%o-gCz(i| zu{VDPtIzaaSM3Jxyx(G6(CGgz1C#Y~We0kjV?WpxSf##RF8u6VTvYqFZr)8>YOBC< zVP1#z@C@TEmN&qWkzge4i>t;Zq0b;?mgJHq*Xec>CweBW%q{xw{$l zh?Q@a|87z*)F`aTNWR^nF&miodsHst()GuEXAE8?_jB#*TMS)xbyd7yGgwsB5a%k= zYtyBBxvwWEaV*$>8$e773rX+Onx6EpZ`g=rxVV3d#2%eT-DqRH#rYw`N1U=Gwk~eC z0gFtIZab&%X|U-Ew>|@A=eYht))lsFh+qzM51}TYr-&S~ z$edyn8F&g&9YcfqWUHLlOFwVO!!6|^sZ+1ZVBFk@xXi0U$>z%kn;WI*cK0*nigzSP z5;gA>%7&uae`p;`{{2Q5M2;VlwCkb{1e@iA2!BfrJpWbTvvG_;pnLd{D|7Nm-j*B* zaZyUwI^ZPzGC3p*X2e5I4Ep=S8K9g>)jDdVC$ni~nT*q!PP1!#0ELU~&m~Hf90W<6!pG9!W6HvE`4{4-x zg$53wuv$>$UmI4`8|0pA44Xm(Xl27x(1Yj95w0Ee5*2lF1)@I(Pdbub%1S;5luq7?p{ZPZeH_>Qz?s>v1H^DMaGel zAB=ff&(s?;0@G#5j+=c2w%VmC3OBH0%ahq1bvyxbU!lmot3#jMoUY%M*>)axFMUFIa!FVeSzKFJ z1tErQRAe1H0$MzFV_HPy7dU|r2Q%dFmKL5uslbX?k0~Rt+G%dN9(#r^5}7s8;%D-+ zF0|i$z?V$a;gO6~45!HS;;`l&m{_I;BO7gij36E)YcZtfAe6l4DC;0ek*x@}Q_4Mx z?)cThQHwOrx{JzleQ%OVJ%(;UG(VYVIogf7n0udDyen{Z3IAFy1HAv(5_FUdbkQGP zLuQRE6y%23eFxHA=HEjneVa;x^MX6~da3wvj9(r*Qoqv$(#xMHEl3$oB1zqun!Zl% zrZwd*z2o~?b($BWV4(W+h=%&=4+kEmH%Yj}V2!Q0Kj>O3_GO&O`TYc+Y9 zQdRMW*?zjIP4+Hk0}DyDmTL$){lz46kGT?b2qrIJ3)DIZvDi{S0Ch?2TarOOXbHJcG>RV!Ak$CRHMg(Yppj=24#&+fcm&ibF5TJ!4B z3IsIDcJA_p!mTOlWGI4%u}FiFS1f>uD1y#TTOskk#GFD&RiaF3am3MlnnwrzM3LO) ziRA@?o%Q+M$lNRZcrVdclS;O73{xeTbagHpR z*Z6DLI=;apW?j|?OjnzMFkzr$%51A12UKDz4bO*mF0iO_5WuwJpCWw@&6`}IBC50Xvpzq)!5#u z^v2DJN|pZ@c$8(=1@{{n)F0-lC;pbambvjt_fj8g7W3&Q?ml6rhAKgmAg08D>UpvV zU6t`3r}nr(r&hrnExL~p0T0X7$_3Uh+{^J9ld*p5nZ?$kdzkr3L4(t+01x?HzsXMC z?j^-;g^|rW@NbSU|3HpgO}h&|p8B#y9B2sn)cO6(?;nP#nJ+44r`6R@{+b3HS;#H` zNqr+%j?v6K>)RAPpp&w=1?tFx#0Kw^2sSm@olkC0pPu{Bkw0Q^-|-HM|Ne#tZ|7wc zpLK`Wh}iu8NoXG zSN1T8jU|G}gr${Ooca5N#D=J64UbX$UD6kk3Gc|c`bRQxyQos7;m7Y2Vrc=&!>hZV zkKgNnHG1v`iWMud?>~fg5F^uhZg}gf4T0jb>Ys(|ylm5$0+x7B0F$vi{(Y5D>Q0A| z#H74+lDX-q9BOvP-9~D1-O_+79iW>;D-~3`%fl%$#7~X3G?%1W?{v4sF)#$$@0V)h!Nq&EhC>_w5 zD|zdCi#bIw5l;SD^H7(EEN>1?q~*r)dh;c_8J!!UoN4J~1#b3Ct`*3&XDL_hQcqK| z(8le@KD&J$KT|j#0wwLcJ&p3SqW3ay38#-4Ux+U7wPp3!M*-VmOkkq30 z4Ce+ht7y5&EiI|5{wI5vYbN9+0%L}`SeBcsPd1>%NLI5Vkr}^cmpu}?iH|pjdS{k$ z^fdF-rm9i>QPej|*Dj~{m0Sy?R=-f3j%^$OWmBw=Wg~}(d~PJ47y~zwLd<*TSHGNb zB4pQ#Wf&fYr+QIrjLeZ}7a#ACVwo!JS>-LI!~dclVoQ~U4Qv%eGp22;KRb3a&m;M} zJa`NONqH>DieYOhOoz+%=MU?b`XqWPjWQz)(|%r+J-G#c^h2z?@ND?2rpp_y-e`dT z(jE}3qS;9x-vq)*Gl29n{G1IjFXk}uwfv>&tjs8_=D9v3>${2vsb&YBLugCKVT#c$ zp4gW)gBik7PTtNHV6|w+g3mni@#CIcb5F-B*+ z6WnMu6#2h<7&a$2(Bu83uDPJJM|ZRdLVy>7 zM?#30_JEUQrtZs!!3pdl4$q-!O{^q75sPE01E*1tLRKZZS`) zAt)T*ZlZL?S`e7UxE3xwWGquKPObJR>G6W~_XR`6Jy5m2uOrVhtakRpAKQsiN9eO; zpKjoZV)$xD1XtTw0T*{5l~`K)D?+Us9}tV3H?ab4?EArrDZR~4=&hE3tHaPGYD(!R0UAo7pOMitS`Uf4`L%9ZTTH9mU=AQ8>el7_ZiyfM zyQHMTXkdZXnW%sNcEVfHSV2+#{n%q{>lC)FZmq!K?=$*1^Bax~33!Pk-vpmVIgwX7 zags=-c2O{p;j@kt2uVm0mv18bWj#nMhCeC9qqDROb?sky42Z-U1WFXe5C*fP#=h)$ z)8-&#>6%j1^*S1?noHaDnqI z#vemA-=WDWYypBQHu@A84gmms+h>cdnw>@pjBHfc$F(3DylYx-z7JPV#w59z=5p1# zY2Qy_=>1fRb4_(ZoLM15n4I%uG3I4VyF-h5NVudX5=sOowO}YFJ%Pg-NgsUE;2AIe zoU9nS#_Yc~c=hCq6|IYLO-WiyLRs}$h>evWfHM38P`9q7;DMdSJo$X@5}_+4p1{=- zMqEQ#76XpJqb-1fnCsStwDgp=a|Sm&^3HyeKu5d8plZ?G#y4q#(ZeUuPzJ#DUd*W{ z)aOhmt31T{0z6_c3$$H_>}rrFu8n|Or??7QdP@(Tt4-3h+jZkz^%3}sbn-UL1W%-* zFc{iJ{>~Jd3I`C_ZGLa*i~%=^Ab_$1q%-!Hw@J-5>IR+>#P7=Kck10>sWbTOh}9R> z&BJA<#mshyVfN$Odr9Uu$0|v_YPAD%%eX5oq z41HQLF1tx_w_X)CZ)Ts=Xy01y^MPIX$nHpu4#>-&LuoZkE&^qa(Ch1vL8>h1LFk3j3lb@$&bylIvY!-SGHi)t zi%p9ye68&P!5nd?Ng-q&4nMfTmLIS9>?rO?4mgcC|BL#?2Q7w19ho5WNYm{evz5fm zgg}KTY-Q5nR8b+nkc(!SVtBDh;jEt~=fYwiXZ-R7pId?!6lFOtBk&PmqI(wX3!c$! zXM$+&NMJF5ABZEDbm{uoVrePl%C1-I;%raSdS$1mC!}mPLo8PspL4%++?>)VyN|z} zQxN%u@Z-d5@(~e27ujHPB>>Fp5ex_LGsh5nx6U1D`ihSEf!X(Mt`WXd_)4kpPvsxp zW`8ekGnT zvQyYrYhnmv);uB`a6d8JbO;p{z1bZRC^RiX4QzP~IiH~)cg4&;2-YW`-Kr~Lbf?Dl zAj~1G+2t>{<8K>4~vH9iXXWrZX_9?QpFgLlWxi&=l zPClLyRq=nY_wMmb|NsB6jw-PbidZ?8W93v%tCZ8;T2#&~$ze{boF;6gkaGwjRL;lc zFcUIoA;~!tv#pfF*u*Sm@9X*b{`I?lx9gA3KiBp9=laKOH@DYr+g^L_`FuPc_tSP& z{=l!LGLFRbd1W!@5_>0?G?D?G63V=qKZRu1lXhOhycmx3kT~oq?mwIl zkQknr7w-s5vBamC1L=@aX&J4D?Kh^)c!RiQyg#Yo%R3@5Bp9yrq7?DUq^-!xF6Oe@%P(r&Okqy^|2$p(CxyuA}(GfH%9ll)TV9!zwY=6K!& zIPSAZxU-33Oa17|s(R%tswAIr=IF!PQxWmomqB$@!-8;yDVCgeW)Ez-gbMJRT{PG+ zQs>MmuF3=E0CGZW#|R3^F|(}H9iH;m@!B+pWXPDRxTVwxExDG$69fUH8^G)J`80)2mOd$>xL3H<9-yLwLDxj zwHqNNcjVS2oq+Su$v@VD04H}9SW&?9hK*?K*hyxB=27u{Z`dsKT!6YsOA&M9tb+2D| zkZpYI+yJKRLGj1W?@Uu#x+n>|!33zCkiV9qCcy(Y`UsF^<=7T}&()Uyi`3BRTW_|6 z!5TfEVQ2H1rod*uopfLW8s{Rn18$%PI(~8skrasA?F|z$l2|fQYGMuM@=AlW^4Px+ z6M=?$$9&hb0()buMJ(z%Rhe%RQonIM@YOIh{;wf0uA}h2sL=l9}Qf+8^>@B z3ghmhtCfXe9z;DaqAg0|(ct3`@(s&)4{pXB7B`F>!zJV9vY^cC&}m~f*fq9+A5y!9 z0k9hC#42+3`8Q=IV5P#Hb!{glKkr($2{XgvwP&tG=w0yBHVfL@lGJ09Ue)~}(Kt&kO}w_!f@$-K4^1`^#aGIf&(Rr{qaR?G{Z z5Cq~KrJRk(s1>EhgIZPkpH47bZ&}uly&DV7P9tRBSo#I$Ab^v0^w;0-1X$z73yz7ss!)K3MTSs`EvK6v`q zoCTV2QXx3nnKzU7@jkg|kKw_Jt>^~!!E0VFC+o7EW&d;;?Pn?EwwCyRb-t|D*=|^A zTVt<1rYowIyz6CcUHV>+!sVCgVm9Yy;KEoj~BZjJR+(#k<2Qknt8OyGv616irOpG z)+cQzM~reGgo;gD9)9ekD)%$dsdPLc+uS@rksjo(;=m4}+%Vz?%;Y6=U^wSA{gh1Q!rOO31wdMT-KZ3#q%*OTyH%4&Sfu zfLFL3yR&>d0@@8sb>?Q1lk)fRRFGUgPBN>49n2}2LqNVS&H|I7sCKY{`Ou}FMF&TY zUKu9fj$RX6r_vLgt{peJX(<^#-FfR`Ap>RTOO8hw>f*G+TxK3US(D(3g4#O`gbaMy z$-?=NlCbAEiS*TN<1$LYTYBE`qs@us3 z>&cK&WPPbALxL0rNzbSi-(`XZ$OskeAsJRpWCewU`Zg?A-xZ#{O06UO6L#(RE$e0r zD_e`jGd+65VdvI4oZKzjF-ol#Fbr%+Vf`{gPwSeodPq#!iBYGRETSUg-1I{op;NVT@@Z&$S>21}ivzszxF{l2Pe9C2{K&nRTz= z0qze<1`M-@jy~389DXBuOli+HXn!O7F;961AtfkW-SM4<@O`Y+F7caOt&u{GgV6~e z^2cWxE>bF`JE?-`Dp+((m6qwoBT;Uhl@U$rXis4cDa zNpPRw7Au90(Aw&sdy`a{6QtWDPXtFrqX!uY6Sz4?N^Cher;$t%Tpqht05*oGsOn;^ z9LrjbDcR^gqZ>2X(wUwd$%S2@HzYH{A-%EdZ_}8<>WU<%*rXANsRA(UI zA0tV-@yz*GdO@Wy^s|!F<7F}3)C!AqQ}h1&FJ0A5swX#a3A0d6N+b)>xhi8I+gF>U zLQ_BXDPUzUjdx%$Alu-gk@8%%Y>)egm!9dr8k>Tc$5f|f2W$I}eb+Yd)4O#}%bSLR z3-2FO#;kHhP3gEOoF`>kfEv;TmAvq@ljM`%N!+YTR=J+sC5U$0DY$qtD)Q60Jev!L zo?kb$+A`h-gZg$g7z&vPt4AjAF7SrfMp7IClR|r)?fHB?*U^+^8^icHO7v<9dRy}` zZV4F6z93Ylsn(dtCQ6?Sm@nAFxyicOGeHtDlI0BaW*A8fb45|KkHMSvL}q@>n0h>w zQ~h0UA^YxgzqWcJRQ1y3BfZQ+--nH0;UUby1U+OlPa7zbd0GCE7ql3nG+07l&M{%^ zqA|*~a(cQae#%;V&-(%unp0}DaXi&vdspJ%!?@7Tb>T#*^t;py`kA{C@$F-;3*tLy zWkZnI&Qkc)?IOvSMeLUiPCkI6{Wm}MFC>X~?18BeQ&)!8Vp!A`?%Emt;05hMZ%j>= zLCt#IvW{fBZ@m5|)&<{5g+ce{MH|cF=Zi(9Etjs}%AOzN0K-6oaJ!u>0}Lpd?=jc0 zCs^LUw_J$qW7_(HcPZQYHqQp{+5F4*Pu~2{7vVBda%{`CO$XwH?lxc^L zD`O`8p+eh{PjTQx@MP%Pe4YDB7!08D(%fW-6>SYxcC*eiGu82K_k#q!x%o{NQ>*W) zHY*re3&#i!JuUMbuXCCdedT)ob)6_&D$^YzRCl;jn|(9a0i*n_Kak1MJEU1+aUtQF z#VxwE7_zpwNMc|SLo!`Bn8+k)%q8*nVGu_2+4Frh-}Sp&aEHg4C5P@`Wnsc~L?yZ~ zLejl=16)oHN;_O=iSVsZ>A1_feYWwn7z}^+*Y!%nl0c3aDK(U6zr$Z-S(j#C#W}&fm1D3a^XAILc9d1s}25 zj+gmeTr?Ick#F#y*|)dsaAC!neRd)TPX0Jzdh+Qfxfed$dS9_(7&)H6O;Bni?_^^k z;mRz^(?A$`hz%PCF~jdTH+yAdM*|sfm*GmSD%oCp$ww5IsR3fwc`tF4PnZhfBQd>o z;dzP=r>nZIooC3J?3{sb`?MKpu{@t_MCRF#w?JuzZk>85$rsmnGw){YoqdP&!)HD) zT>g3Y(<8S?0@IqKHR|B3MSfIL{c9Rp+%t^0<9 z{BX{btH9juI88D*&Bt)`V^rlM>W49pM!Q?r(D%{?x4Z8~UB-{DK69upE5NnCmr-jS z-cZ?5-hg>QM3r4o#7uErv#Rp&E2=>5A%9ajwcJc-*GbW@wto;b2)T~^j^&YQ6hq4= z!|A_b1K~iw^vjgR-=p#T(U;7Cy|F+0_HJPdXev48Am!DHo+@r)On8YGYcpoq(xQk@ zesJ8;fEv^QD73}RVSlkQopi{N?zvF(PgFL1%0MwY?zcvWoYVE8hZFlfQS+)l`hOPn zSQ9l9$HwX{RTLVkR@!)Ju(|1c2@DDv@KCMbDAtG908Be!O5=1(T5l*`W0Qa`z zfpKf|_e}I{Rm%^OFV5M1r7*8P=SlNE0T}~st`Nqw!X>mGAWd=&=XOlHphCW?>wn$q zCV;R3DU3;-F!V788T6eFrGD4>()z99LeHyj7{XGK`?%(uc;;+`#S)d0+~PB*lIzi9 zsbSa1M8**)0*La3G*c~26!|4tdM6=s;(Ygm2S+n9E!CQP4L%L0r+0dpvT(F9mm_0F zGTp$oUlqG+yd?G4ZKeTMhoy`$zWE_LxG95vgE_M3cI{)HV`f;E!iU-n)7)$iH#zY$ zqa>q-oA=jh6RTT&u8}u?^RolMvaA8i;WbzeO7c4u1{%!HIf-QzI%I0K8z*VbRO?Lu zrU+gRdC8^bRRc0PenpEmH8Yk0_c!r4d8?!Da)DXCDaZi{?$OLSIB$?d)L^JEAFI(F zKp3-2MTqMI%wz5Dl32fG?rkY!*=t8{UOqS}*n$vAKX|~zBj_&n>@cCqt+S~i7mvZ& zjMb|Jn6*5$8VSmkpxzZxRyP!eSOTgC~~cTjeOhw&WDEVGjCIF(}V6D7-NMb zhP$)&I5o3$lMNhcXemr8_iZZ+5>H^*c4hxuNYaYd45{$Z4Iysszfh1QujzErj(*ii zxl~B&@4X*6sBWd;>EVTnDTN5V?~vbNy`;6uk~Y$|VX`D{fzl1cB$I9$UC8H}9OX%B zJx`DIk|IjRpL|nJYFv;Ml>}^UZ3=TC9R!S;Q z2n{8Wv|fMI_zIaoPtkb?6C^Un6mfSr&C?dTym(xOkkOpTBXl`i~i>j0NlD9UbTL zzu}s)R$}LHM~(!o?nlX-WeMd_Eqx>Hq>-Hsj}r7u3XMhDX6*+f%}-43vqU=9Z;Zn2 z?Y*{_LU3mRU*;9`V-u8n1Yo5>Z@*#qX_;-(nwMGC$uwytW0prU4Zk;*kLVV_lmonR+Zv>Cn6&_(J$2`QTebx#XM%R|CVewTYrrc*z)$}&rSpvCg-a!b;_#>clW6raeG z2V?TA((cM|GuCFJ(aFWHa^3sO=(wXxc`9SBNj5gA7w|z#+uN^3Os;xfeW_A==8i>z z=YZm9TD(=}`Fe*idzBA{`kT{tcqEMl=LRU(;U3TMIC3REfUHHxc0+nHuN4w{a?#4ojwi8+B`tr8QvEy!91q70TgSmLS62ovx7t zt5s@;J;j|Mp+kA9OW6vWF<4peN0%B$!-ZW#LuHc7E!|3evaMp(sGech{<^S?|H5@M zf_WsaLj4Yr^C`!_}hZnLx*;+MnOU}Iw*1ZB1V#ZxSu%-2D|7$^>cCc zAIB~e23NlvH5l+SR$lmTHya(%_|HWPq1l@vWApT78hmAJcl}Z@KK?%>J;SBvTSnat&koJy=)5+EJlTv+|0yOI^D7_v{DF-sb&kefHgC zF6B_2#SGMhe29H822~O-8Kym zW$yGeV}hjpBSgXYxl2va&+~?&i7vUaLFN=O!h~5rQuCo1RibErFud7QzZY7c%n)Yj z8>e+bMX-ikFHRJ-tT=KW3Bk~5nMvXHZwDUuDI@$0vaf%vUKEFDx3GG(L z>EmXt_S@HedM2z0xs1TsX7AOt_b2kBxHrR;x*#Adpo`pEP*$A@vf^E4DBdANhp4V# z8%aWX7pb)=6=l(TSoQXvJNx*A(s%N4?RUWp5s`>v-inZ7-`gpJ-4#GUxJYJe8FL6; zGg0ZsMN+HOx33u&qaLD2D8d*)07Y+l3BRw-aNf(VI$}?MUK`CjF?h`1cVKSU*Fz8E{m;jn_bfO& z+mjdjs{6le?ILYkVh5m-q_P9E|36}QxaV(sb87txMkLAnr9nC9N5?Kg=VLz0*@d)D>$tfwhbi;sRX zHkM8rJkw0)e{CyggLjW$;7g&GvHi#c1SpV9)#KV>QIgnXoe$7NBPER7wefY1Tc|GP z(PkIfxq)Ob0R5s?wVvf){NcXIr`z9ZpQP-7XSLrZp-CKt-uC=t5M7snLuq_V>!#GB zt#7lJpO0Owik%-s(Rghi$ z(FXS5An>utlV>7h!!J{z!l>mzS3Jkfek@SXyYIDMaQ(PeMGkz)1E|(I^~6t{h(n5D zn!EG#CN;tlbet$BrpHKOEWxjo6VvT5QACq|xbrslWE4Kg&t-m%_q+S>gU;i5v@0pH zCXLtlJWmH$s5QL2S!-DmJVA7UqtC+L<2b4fADEu(O|S`BU_X;U5!i-1;NxR_Gx-Z)=y6xrw92=G?Xiv%x>fa zZJS~Q*aa+fr`^ zT8jUa(}(LJpzbeY7ALz=R|T-6(0WL8z2JFh-9nSh6ILcoC%IsO>F>Fw8aSxml`&!S zV$}nuK6_v^K+~^J9`AX&<3_y02mAh&;mjeMroa^_6NevKg<0U{DxpkE#5DgLq#I0E z*(IDJ<`*c0dz3|Fh|!@O)9_oZ@?X(EJ_3mJSGUZ`abP)XQP-Md^^`Gj7JlYW)##_` zTEB!#_-+R_PMS9i)|+m(^Z^c%=GmJM0CVB+^JujVQ7T26YsKn~ua_%zIJmEfHDYkN z+FLTTg&_Km+6s2)SBJt2lSO%7PPfz1m)7Yy=4u)~^39KJ*^Z#b)a1Aoqw<-SqDMwnRTqwOKY$t&qK}oEEbWqsQYvj`_D+c;y zsFClzqULfYY5L_758sIUrX`;zFv=r*8Eoh5;0WKgfB7tur&{T3Drah24;a?(!+GO& zhx2i8+gC87R1bU^ho7p^;cR@FIF5)e8q;Ek1))U|C}0pWF>z7*iCQHhnB9D};Z52L z$G>3K9eFk!G9Io*#dDN_^#G6?=@1ISm3H2OX@}Zq%W>*&ZNm__58QD^W~iHNRO&~# zpq@RlhN%v!xV+aUL__sozH?E?6S(s>BmzG}!;R*ce|!qWVV_v$x`qK_z+D$i^(2jw z4BVhY-tCm@uP(>u25THqECU|M-`=(GhB6iyM&lq8a%*0%*bxY zT>|BB?vaI#cmK-bg1-O8IMR5F49tMaw*P{!^go0B3!ugT?F08{KpRP%gc8&M%mhdv z?7(Gu4s4%eNuq&f6(#&y%NSP!kFxG9S=cfu2?_a0Muo&y_G!I;o_?GDT_G`&2bef| zz_x0Lk2e5iI^&XL&T=wK+uvbs_Q0Sx6f{N`d$Rc+TzWV92-j(RrD_AdATQ2rd{lr6Pnd6G3bmseK`1kqe05RDlC z5-E$`=UricT}&OM9aD!|5pTzNl9G7%@eX!XfW>>io0} zRp=Uzv2q;!PI6upbzoZB)O6Ty#TTUYsrjMnw`WYq5ll<8R=bAc+0Uy2zUN*|uA#5& zRDy5^fdwYYNb*1A6EBF+f=y|SBPv^>2lusyQxPeY@5A=mQt)@D;R+!w^$m0Oh1~2Bq!O9b-h`e&oZ4d^5p+7QVM5fM zM)2Sn6oBQRVtM9r@~c123n9QYncyNzFwk3*T2&%{?9*-9E=GRN~lHwlSbI|Bo1+zDaR=M9_0e7L0^M_`e zz(p1t>zzM8Cp&7Hntf9nK z=wd}RN1(^(4AZ0`zD7I$04K(VIa?k(d3RA#dnTP+ZLWW{Enws$()m9>3*6D)n|U%U zU53W?B~0?Au{%D_#>SJ_^ITAUg>8FUG(6% z%AcV%jet+h_-AC2=Wyl}=9RO3V9#WOpX|OW^Xq-&gj4hVTQjQClVe(m4&LI?_3fOG=jY8%rqVh>?-8=7u|Hz7wN6>&q?Y2* zUXzxU?^?rl?I_~f8=;4cAee1hG9!wXFN#rVLkeRJ!won=sB+#QPk=RF;O6d*$8;=X z2Jw||qPK61(i@ZPD+A5&3V-$n*L+Ha%UiT3uUBm!35SEb664Hu88^___UFn1DXQlx z1dx?k8qbW5a01$h4hL963E?-I4B2mmvOJn_aoXms-TR|FOP%5J4ew-qow;8R4E-(R znD)Bpa&T3qG~_N$Z@0@q)(N=mH9W_(OOF)2Q99zUV7B2bxz_(z{DxVI&3!263El+A zVNfJ8++ku}L(~UWuhc8(*p(;oA!D=C=;Rnr6$8F8M<(OPM5ef?^KXZ$36dHwB|tlI zX8dK{!lw9IA1dI1|B7q&RaCl+#|&-V{G#G_2XmB1dM=)@@o4%IFJ%MG6*FZ!16lW3 zkk~3wpP$frJf_pO)qq8e19m&lp4yBC1(KMv#EPM?5|^yNf%sQ*p;arVf{9tFn0UXG zqme3GzTgHGW4n<@!9G(?spDUEWHqzpWu!Up4bII8>UOp&F}W^k%$2m#-)wfnW{v8k z4Y_Eia@bjFIyKqrcU|Er53gtb)wpACD#*LQy~Bwp^4HkO znoEw&(_-f3w>%cuiN8FttVTS-r}p#ST~cGXTI4+e}d3~M+jV1jgo(%z~u7FZfoVW~ImG@|_@4Us2hPk%Y{dom>B6VLgF z6fJb`^Uqc<&%cMNPonAg*nDB$>BW?#d{Lv57z$0Dco^mxZ^xR9+gO$GPPw#u*WO1l z&owp#>SKg^3<$3jA9l6Qa~ zI5r}paOI=uh;p z1P_4|0$(C&3y8dzT?;_^!fE{c=#KTF|1FjaGycDsI)3DV%01;7c$Q=dFVn1_*tTOe zK737VQ|aV?Y%gu{Vhbpk7>5xp!E++caHnxqtq7xw@^)^i+t$r-{loI#E#VB@Dc^qGVZ%vulz4~pE zUedM-Na7u67XQ^@UjrC>01h?k?0V!<1|w@YyFI}Upi~A3S!0@R0{qPU%{*U~DI5omyW? zxGf9PVRPT<$|nx!yKlZHn#6ucjLbUd^fLm3f7SWRu8DB=#cMAG>qWQ{eZ` z(b?~A!bB?NRq9XiOP_{>fflUr=k<&D1Y8}W2l51Z5BI$Fn4jL4jn6l{gLTh&XWPGW z^M#Z;pc|4lvJVQiJ`GbdRv)hro;KbPgUgzvxocJKRo$F+a8`Vz}>8JPG_z>a& zh@lw}Prq1t|U`nB~n>8b^qscXk?u5Gg6Cw2ec8zjmf}1&@%lT+a~lT<>_o=ay+cI1h|MtHsHuE`pjnyO?Mlnf(*~o3>^_fgSqd{ zQ=u<9r1()OZ5J+RZ=oTH;kHaLjVJyGs)66mH=eAt?mvdVW0z+aPB|JM`c<_|-t+X@ zss5Bcxrrvyw_Z9V7Uqq920MzC@{L zneAHVR1=SQq?1il`{wO9%A71?h=IakC+5Z}u?v$o66i94-(O2vzyqtd?2evfhrOFWkuMxT6Eum5V9dvkvg%}>sx z#pzX5P|WCFJ-=S%u0man!!f5HO049i|Kw?N?6)H@5*#s;u<*23)$n@ zM+2|OK}wWY29gSe>Pxcjy2syhlfoS z*{P42`<#UcoPY7=*Sy%D*0*jCiYOFRW?WfFiHp;>8QG>!6%SCF(_hLLEt)E8-U}60 z@Ey1yO9BBIa~Vx56*P2QzIcmqwTpSFkL2LT=z=nr{-fqWp@HPCx-V3*8(OL7?uuRN zo7;s<%ew&fvQaQ-Wou!c{>yiLGHAh-rCsiJ4&$9SSZQdWn)b}WKM3{V)+}OX-es{C z)6G8i2JeE>plhG2mstsur?!PS zbse*ZcM1$7Dli`zuA}lOU)ScAt6r-36{UKguo6!j;ZW4!euX^`SKDCc@Z(T&hX#4I z)8%{&jo7Xsk}n(%Yp5rDKtp~r7cxD_2Rt);2#pdti|T4CYR)$Zm7_DILU+kDO{Z_m z!HP-h8Itjx);Eji%V{6Rlr7lo2QQs&AuGbRT&M>cKWHm?`j|{MWdF;DWZF`7lMHsz zR-?BVz0q9Hw-hgjk011bk$bAfx&5%PJx%39!ru$cWo|`YoNruQ$H&8jxqd8oPj*RM zctn5}d8e!R3^fB<7q9(X749S5XvQsymAmzGkIu{&`&gwxpNWz$3!UVL+t+Uo^@Rxe z`1H?KA$_u%k?`rFO)jw8tT^&tIxFwm$mEI?@T^0(LG$=+kcQl`#VX=QhJa$RPXPoc zMdFqNS^2zttlZ>${(pPr@Be7aXaF!SL%49CtNa&6lc0DjZmh(@X zNi%VuN3!BAJ`qzl7eNqDS?|d$&}T=0?2sLmfBDR{$XVtkN2^x#Va;!#31e2so4i2^ z1m{6IfschScCo^oIZnR!YMpllIvYvR%jJi^Ormvjio7rHYc5pY{i;tyM(Vg=v&zz% z?W1O2{(+;m3%DNKH)LBdj33{60XvA4fb?kW|7~lIrb#(A-*m5>Dj<-uv>r%nz#l8T z){M`=pNl_syXD22DK3~Vg*|3D_ z-Yu|2F{uL1H~)$2UM)&9iMXs5{-ZhC@s5&@8JAIdR+{bC*7?w!z^%yI;@{ZJm$E|E zl9jlko9ydjEg?$nWuOVzac(^aSDk3cld2tM?#gC|Ga(6*b5i?8A;;=O8*$>*)Wt;B|f%1%a~_NQmhz1(+O zUb%3Wg=RvI#iqkzcGC*J>Cc`{6@U>UT_)BO#V=+|ZTOiCA>c8_vK^BFg}ZGM?cwDT z-*27I*u$4Z*czunzZtVFI54ARDms}$kSU^~pFfJqE$W_+p)zz-rY-dxOYf$+chzDZ zF$#oRhI`tJRUg_veDnTq0Nr+ZlZtx`lQfb>wF+|53&TyB^B>E-^fIz=cb6xbj(s=* z4R%~nZdI*j_1E|#s>hL-J^p52&XadmM$Pex<9SP~dt!FVaURlewlhojS&MENr>VDl z4eU}Gv&wOcz2=Q0*8M73hwpwdtJXj}ppGPDrPHl198mdL&P&4ik)D8@(`0bil7Q3# z+NwnfS9!(}HD+$cr~)z`8c*=Zl55ial3e7C5-ju|Z5xui6w>*)@Cf1uiIKH^<{S2N z-btoYs|4!_5Q<>sysVd6A(QioZXcWL8^k?A)kRd`W;%L{;)A>53|=^5h4xwM#aW!8 zWK~eo3uhThKKjUe{M}<#*Sz^H=SYX0InzBd;!MA1U#BS;e3u?9#&<>ifZfTai6DUp zy@MXAtkSrP{$f8AzV72!El64CXV`A5a0=MgEYBDaRhNjfU>)Ykf4%+@6;#yyL;gl3 zjcn%ed}j81(_o{DX349^W)*}d_$xaZyd&5Fm~eeFKhR%~c2fjfWoJ0Wh8mZK4KBK& z&t!hi?l+WOlj;l1>5W|U>C3g6SkyT4&Q$6^A87jb?cr#HI21=(Ys?#b7vdP#EZK_L zUBs2_O(-E|Yzf>;npFu43nC<)zWcQA+vmOBLnqH0Ilp=}|MB`Qa;;y9MD>I>!ct?j z5xUc4FX6&rhp~TO%c*+{^*7r4oWoAw}dTA`reJW zf;sFUojz^)Q?<=^P zL8EX&>Vq3eZ)wsN0|=Jj+Rs^`S>iDU1+y zn&=+pJf-i=PmdDYkRdSlTmy-X*Wk<$Ix%)7T#4$eYn?~2VPz!qeSl33mH;=M?PJA+ z#y{eyd&GtphGm^uXWw2SL@-#Qyo0^WJ#FVW5Ze9%7O-U;#qQ_eJ0xV}KCt|2T<>X@ zsr#E`1yGkPWk&|CrRrK*bkMd!wr`iFNFI1{?q_<8^_gz(zaWvz&rqXak?|GM!^7fobt*L&|$Mm>LiNi}rEr$Qj z-nsj`jF9ZiTw(|8L~`BVV!Q<%+2t(Vn3kX_6dUF{qBQL4r#|4=6sePg5G{!=e%UKF zU~t7G>xlsG{f~2og}&MIA;BYR^`mu`{@IW8tN6J6?^@sPRKPr-BD~MgBm7YW!z+|7 z8R)p0+!z=5QW|zl->S(fR1uApCBATR?5X+qWjgTj-@c!k;cXEbOdR_N^8|%Ia5V{9 zPwFOeF3$m_m>C#VCy}S71#&&M_NKfQA=GEPyes*famM06@7DXuws7FHR$At=z_>RZ zVp|KhSq9ODG9RU=lnyjB{C18bkqM2#8(h|n3GAKF#w^oOlB0-3kKo4pZlr&ufLh0dLx5$`y0`)VdivbtvP>F6|UX~%|&y$P~S z6FyN!d78EfU=Jj9b9uA?g*6)ILRbM^GV-jvkIYs(>?sa*6%i?gIP}fnQSDHGoAQFq zs(!EN=S95XSH*K{FG90!S)5qVEw`yd1m><(6B!HlVRYvXe^rmEsy@ZM*i0 z-@1ezw*%X024{FK|J-UP`l;mRGv2xI%bd5MEmVDh!9tE%di&Nh^vpIKyRti^!ooa7 zBx30%jmGl#KYgwK*70Ty@$C=Wjv2#%xv}*BV>$Zq9cH^z^3Wnra763J2n8{D&u+!C z>(OISiVPpMRx7V_II668Q9ixw+0GY~#p24!MD53hHHU^RCrI>fd@0N$dFsm&eW*8A zpSo{m;%%Xwz~EiAFuS@q(FaB3g(EKgf5bi{nWY!`7)yqxoX>E5y(fiG!5ef4iGz8o z}4Gckc4O%wG;Qh^gOPwGCOG-Z|msntWsq=`S zKOU-(-u0lTpjp4RJn2W=d)ap++d#-SiMd~ojU+L)y%d6HuVa2xCRVeaIHrXV`W=jl zD3gLWiY!9vJ9QFEkEVUfkdxzI8~;{qVrz76+Y*3_{Jdk~hRn@w1eiCS@Wm=(5M2^g zp8JyCL{hr$L=$hGsecN8pLBt~eFWIJFs905O zp98)F|Cl6%)tkYLA|_N@Pb~_ROml)MrXT8?MyB4j=6ue%k-CNPw-r}yww@5p+BMW4 zuqKVW+Ikio7(+DAWnJdsa2L+=fB6o#2v8S*;V!=A@?Q9ZP{TIK+)9U_^qSf+Nyk|6 z-=B9id=pVpXy=0tA;^yGvz!3t>Rffy_GMr!QWnC}q!#h;`=d%{7TLp&Nb+Jx)gf~G z;b5}+vpXfPEc-v}s^2)>CtIwo{UCjH=ydnArH(BbWKBU|*V|6Q>Owk^Q|Mm3>mLJv z=eHM&zk^{tY7LJN8ZsGmZB4%M-p2V^yT4f`}P%Vmy8$_ z*5 zBAPOKhrX~hqBrmdd>$po7+iBT%Bozj6>*^0JITqvM@WeUy5E89J#ivn%yub;D=N{3 ztScmm80?`7P~yl8QqMP!iGmtVaK;g7#rdiAERoPW=0c+KD{3n3GB5ALxzpr;A>(a0 zSA6Fw0U1XU4$41<9ck748va0=T@VmP9dMQ$afC-$ULBIo{%2MuHvxUYZa^yXlEM2W z>qP}!8GC(&^7u!@g@tO$J8l)vW(UzNd1hpv5cS|aq4r}N?t7z@O62d1`dp3@TSV?_ zJql(5Qmu*{I5jgm`&bZ6%)6oK;jB{{+u7*&eA01r^_BhUZ;0nr%-;8^7D7{X4Qv-o z6G(2*Z$4BbLckoLx)BiZf%#G>-}g%nE}3uS$Wu`-027rKQgS7w*1sbJIOOU^}&-{ zi9de`6UGpGgD1@1k*S53t{!}Oz;+x!9qqFk-H{B8WH6mM5+t&p8GbwIw!hTt~@MH0N#2uMz!p&Gr2=E6gCDEL4_& zB6^687zZt^IObu0r_0kG7eUsSeDSh5lJNBIs#1sR{VCT1B?Yd+%b)?ug%(fvzubNz zwxJlcy_YM1U15Og~c_6za4~LlAon?s@nV!2#cM*JmyDlY>*Hx z>hTY6@Yw0pt2lD8TYdut4(Nz=i>)AmYS7HDPtIbmTOl2=BTylnJE#K}T4nOtI8Gl8 z8Yi(y95t>cFFgtrKT|xV){-pPr6!bCoavcsEqlDM?QU=qVIA7G`puC#|ADD9O5X&$ zIhSp!?Ne=M2WNfrc=+hF^_Zd^dCpXRKqjT)8gyFjNlC-q{#dAljMFRYi$!8egB~OB z^glZ6T0$H5Q$>?|LUwUc$-?{E!luNHqJ-=J@;#7m&eIR{_z_+{vip0|t{*{o0?&}E z2x6I_fIIFsm)yH57JijgA2K2A$O*9>@vCGg)Mc+6TlJUps@La-puI=`dCK4uPuMMW z!GN^f#~fvQu(*=#90n_`EB~-|PK3jk$xfXn!DkXUvX$itc_)L^1&1ebx0(Y)#4t4k zM`$-9>%t!g8-`~{SRkbo0@^`05|9Y{%!ROQpRfCcV`jgs%};JCYbQ(%xJkdxJYunr zK}|QEmJ|LCQNI{mbIIhv+CFR|uKjZB9*1z@ajc|qNw_s@vWubV$o%bhezk`v{>R+a z)en7dqR%6Pb){n7^nxf{`S{N@`uS!t`T)$0GNod@Q`&;4?hUOjc^1w;H=}3i*gdNh zB&bkTZestgR&d{Ezkp-s24x2iB2MNOw(dJU@ZCCXA$WO`TCsD9LyOya40@|b9GIDi zX(5l3v4D>0Uqq)omx0u_G-Y-^no0avC{a9k@4KIa;Gq$FSC4Blci{x%QV;8XNFShM z5|@cZ*pYnk4NA1p=}B#pKx^9DCEY%dP@2``XKLB-F;xXMY8`pYOg?W)#JJ|3Fi7^D zdvYtWaKkCPPYYP`T^~fcJlTo()xB=JoF7A)%LiJ@kmp?IFlPMqozfsjO2XIpQZ?>H zpAyGhwT%lztw(M%aO%c+XOl*FQ6zQTJ;_xn{f5t4CJ?F;a8aZq21w?TOX{qjPkT1| zEp_L*BT{pD#052~rKx4)U6^O@t{+m~pcnVx^*yp})8wu3$)LdM{QDQYProbuzvb8b zuUR(#FFuAW41qwg6ddOybSKeB>|ef7QfCtva5W;^aQ&tv6k_{Wi`u_@rI09}4gUZ3 z%1u0AS^Y@;3|mG~Cwa|*xOCE0N`BvKLtr6-8t5t4ZB_nSFq=Irz5`?2?#+51Oi#t9|K^W1B#YhA@7 zDrm07dJ|lb^e@n`zAl;>3=d!CR8`AG|3%hbYl38t|z4vr+Ue%yh-8B8!39 z_{VVuGl~I_85i1U7tR!b0Ot49qIAV6Cijjzqd!c!GyIh^cdqm2H~ChE(&Ib1&4C3@ z>D5hnf>REgkn%3$8Y@aSj6yu&_mG54M!S1gPCA+R>i1H{2rFSu-J}`MbDQP?D`!Ln zqz<$lz5st#$Vc>nfkn#UO6dX5Dhq~uu5i7OeJhf&UTYz8< zqr@CC;}ULV*ZZjjCu?iwDHd-xMT|}c7KfxK{`4EDFYSfX*j1Qr;9i`Xgi!^ITy3V* z+{S)K7s81JS^4xOG}=#ttjt`=GLf6vOsaL=;4VFv(r!w~9v z>q~O0eE-SALSLaIom>7+ePexn{jMSB?{yq!+70*3BGu=pT3K%UACgfpKj4Kz*@(ph zJ2dD5HH!#}Gw;$Zyb~tVn>T%&QQe%Xpoj=6vpj#maTo|KQ&B^BCiQ$$vTZHH#VUe<-_44pag=HcD1S~BZv2!m9gvWJ{(_`o5 zo}vu`>l%N??k`P!PgdA)KvsB+pZb{W@_wEB%(D|dpMUOc^`%>a3@k?OShEFH$#}F~ z2ulAe^`!numh5s< zZA#zmZ2X3I3U>9|zTc}}V|l;U(R^=HZ_tHfJhzlR0ytE;im@F*m~qK1XI*RHWwImr zkK>%*jPUswvFqBy1IVGIKsnBF+a71g_QW-z6k_r!oHFJxwch$MlHgLtVuLt z1-}0UtATapJI~9^e(Eo`fSJ6Ie_EIwv34l5clO6h7yA~sbcWx)`AIe9)az8kL6iBG zpFPARVYYrYHF-8wBfc4{@l?#ERZhVpCu-8!gy*JHX3DTq^T+x6Xk(?0V`A%f?_0i_ z#*knH@r8_~ZI~$fmQr4vJLE>it)z=d&kI+sYQ4@yb4R;q#ZZbW<4TH-MeCenSR^0Arc(m6hJ&kBv<@OyeiU!i8OIxaAZjyY)iMel=tdma8YzEm8U3L7ns`){VnA2L* zet}!gasP?J{O>$=fWqX~KaR7yEI<`o^`fh=%ZbPBm%JbUrxt=UrVYlpgc<@DDDl|J zZ{Wi5-+k_u8zS6Lyjvk>ZT8WS+A1~H(tZ5`r_S3at$`&~f8KebzdIvC>gr?M!yQoUAxH zAa%+n<>I&eNt(cMcxhpcN}FY@liX^!+}dH{9w}B)tBjwU+8BP%TzR>Ch>tq?$fXUt z`ox=>*1kK>V!oGD?4=rT*o74-n(8vSD%sPLglr&-H@Py-)<9+490~E)NjzfQ=Pb!9 z4ea)AscRj*_VrhFAHPF>TEZn&t{AQZ&GVGdHBoxg9D%Tq-M$gmxDmCw@xu|MgMv0= z2&taf#CvBjX0kfjI!LUH5y0P6`7&>(YohRZtI${_P%!_`_3L;P12q2m*0#dvp>8K z(fB)6!k(HX2qjVhQQQgM4HtAmWy41+@X2r++YyiS-#13rl>=AToMsT3<`>hs6Z>E8 zGe8O?dESw#iMIWn&o^JzMLdF9-ln=m%~yJx(+7(bGvKI%uOyl2u?$sTF&~Rs$5sYM zrY8CR0gkRKB{HJM^l#m_rl)$cdS~r$g5wP0XFwP0WC6}mBpfq1D}oV60V}zjjZVJ3 zP*hu*3*@$aeN+i;ST~Rd zqTfiPPIKAN7T?3WxBcDUlX`t0Al9*U-t~D#r;aT23#9d)c;Fc#Rf)Em1@e+L7xrla z-5DJ~w`WQV*-dCZY>bP&>Ug@Y+1B~RxZp_iyWg2dFR1g($&=bY4)0o`A4wi9g(~!A z{N1VIUHGhERIx$Hkt^oDcYUPepZWgd@F0DyJ$+_UlaGh@h`ls9BT#=!I{;TiXhJ~o z<*X&v;7@S;a2*16QE={bQ#r+RB!% zqwAUmAgL2S^4ANkZBPp8N$xM5YmY36$g7&POUYSv%@BcLx zl6?P{q4>g|%^m^sV7aNg_0iG}lz4*}`A?I@Jikq9(Y^H{t1Y?fWWNoX=uXHk2Gfg` z3>hNvbr$H-Qad*IQ7i2dYEjq6@UGYnv53J+9~!`3$$JDlpGirpp?f}CtSw%RF~IZi z4eeq%P%9K-7n~Ccr3%Mr3?1^Xe2?CP{pEs`6W8O3-;OHuS9GrNbc^#_JpC-Gt z$X_SIDVAh&MlsDe4e<(s&ub+-XgM>e9y9*gq}a`bC#b6PYJ}nS#_WTy&MjWKOl>Ci z067Upo@qrl%1%aO=fzDrXU>jheN!ZqPXcGmD(j4bvykE^VCRtWbos_7tiz#m|pxN3p6Wj zSHQ{q81o6nxGDz*A8gdh?{ca=AfLp;MMA_??4WGwv`H5!C9%|`2&5~eI~Hd>@~~*s zxfq>a`CcTgVgI*TJ(m$cUjNyd8n>eo$<&$03i;`u8OBKX9(j#Yv1~C{kL}nol&L_t zdm$=crBTm%`ZY9!{peKq{qv!xwf~eKF*JOm@*95i=Ua&&BLTZl5f(JzIj(NvQD}&g zowNmukx=`FmIn;GL^_pQNoYAjamsl`=)%x_2~@5Ka65>|@oXs2ID7o++pdnIm#qsj|1QiS$&!qh zkT0c0r(NOz)PRG6J04xXtE6LETl>``azyp*6Zh~IS?b*5w?#i+riusWZHW=r7It0z zNg?YQ73h0(BX;3iQa>Q)>t)A2ba5Jt(Z1tlA?R`Vmsx&F1=ke?+4sTk!m~`v-R$vs z&P)EgjSpD+)&S4(bOLz)^(?mNq}76rZ(VD82b^(>snb z$b0z=u>+R`)=Xd$ZMOu|#?LNNYBY?Z_O*;Q1RCes5wJV0Q$LYEgUJnM^gHK&Fm0(< zm#`t8?~IJMzHciyL6p}ZPTgwRVy}?qLg6(r)uj-YkqT*7EN|a(Sjq`a-fBwhZ+Ibn z^Gd7VOYwLPdBp>!8}M-O=NrL1XHyCkS;ZQ&B9I&fm#FJbAqq@Q%`ENUVz(0WuYB!Q z-}WHkKPbrM#oc}A^5c;8nEemk`=&hciZ_OPPjHzYOK4Qfi?I^{W)-IM&Xa0^-^l3hh2-c%=M2n>DrAL zlVcq8+f~1_YU#l@Asn!s8ULt01Y}E3n0G?GaRcvs3XkF?_bz3pR$Qx3RjR6%C=re= zxH{&VrYu9&O*fQ!_uJvwH%JbL8tT%Qqiakg5vKh%fx>k=+dLw;@_Y(+uku)A?K&Za z(?sNCcY>aYY-`f+KaM~giF|orw_h`)Puz;!r5IP1pbbVUD5EO6M~av3 z_m)qZ2poEl80IhD%>OrXdc;VDKW)$D0~|w3p=%SgTq+R&{vI1s!5yuVG?Ro7XV4!r z^=Qo)xsd?%m6tz9lq=MA4wku^P47`P_ggu4@!8&|s(*0ZN__Jh-I7O`SH@Zud&* z&gACC0aST6TYtP&1o-HMCNph=Xv^@EkbB1xCA`w_?-qi8!ggX+ePv2feMwfc?tX{B zVB4GI!g zVek1Ta0`=NKg`%L>LKi~Tz9HqDqojJBl=O8#p%eY;=3}BUFu#SHRC?H-?GgIOkkXlC$EB)m(H(D4QoU(fiGNsp6s_bfm4j{;b1ca#&;1_N#RqI- z#E03#NUu6t?l1Ri{Ic>Zhe%H%^W?jHFk@I=UHe@d_GftQMxy?{r3)x3AE#4~H&v4F z*R^dY!t**tJwpxu@Tg+4^vr`^sh-Vaf!E1#BCrfG+?vJiCI^hEyy9VQe(h9u~H?`|o6zWH}Ps-FJ)!bEmv5AG`t zl8%^8n&ro+LQdwN5r2@v%nOYp56r_HZP(Y?LlvS{8qz}NO-`kK8Whe*fc&XiYo5DT znJ%=|0)8OkF?R)4QnDXomR)7p6GjPB>q?!GqN$VkCP(z=r!}0&3(0FK1S(sY?j6Sz ztaOAD+jF|sqbnz2FMZ!mUX%J5O^-CV{rOX*fxWTSAsP~PIl z!e~IBndm`A?s(1smn5oY^&CrWXJJEmb!*r43~I3y;mD=U%LCQ-1;3%@czw24 z*I$R1`>i2VB2*&QHvf|VxdJKCy}KoAk*htgAvk3tfv^UQQyv()jy@QWU0mESJL_Ii zl=$nG`DNRZxNg( zPb(x9wJ~nibnY4(a3*<`WUvn(+V!{|a~8yIRbA_QJ~^&0Cgs2P;J#hH#|BbKA-D{} zK&TyEmhcWrbZO1LIZnu3Nw%~elQ~vA5jt@nKOwAk&`LgYA#-ZdMC_E@!p)2Co=U%= z=-cJ9g*I^S>+)B3$*&|wjC$Hzjx)@@5&dDL7z(0d%TmZ^*ompFuvhV6`U*qnD9I8b za_W5ctuaq;6hp}PR6!|G2~3!9{w)MIqh(arXg-{LQI=jT#)>85Y=y@BNq_#d{Nu=3 zykaMqer-Lh|EMHV`{yF^BxVdI*(wcP8vP`p4dvsY)V6Vjz^uk2%f9b?`AIJZ8nfy? zL{be7#hfV!7>Z0>auY-b=@rvmNCPr0oxC+2x`6s15O=>SrC9BQZsbArbAhAQ=s{EC zmA_UszgssWPTU6KffIqP@@N=TU8$K5svwIdmzuD=^8Q!h(JlDirAJXKytRiAAyRKm zC`;lRGU@7>j|&{7Sp9#We(3puTT7;6duD~%ibHSF*JvtC1tR}1r--TySd2{FuF^u1 z?97aFNMjP2d{GJc1An^c#BC4mZnbG{#saINS)-KfE2h%DAvm=(9#>6v7I~oanqU6# zYma6yJ747B!9S(<;_^X+BJ4QRfG$x+-2mpWPN4bJh_{Ncv}-WzmsMjGBL+=Kze$=sVlPPP<#Sj@7`GKt8^)W*_}w-vSBT-pX> z=b;dVY31;8C5da}<~ku69_JHHiyH5LKJ~&x)J`Q0+MeHtk`!(g_RX;c6&)GNR-y%F zR=7=*e75yZkE}n~HQ!yib|;I$t(k8tU2st5$)e5QOzEV0PJ2t*EaMf#q9s~ff_0=O zNJ!CIvjztP5J#fFee}9JP^V|sw{=RfbIs!Yg~#V@F=yE$@RJx#2M1rQ0Q-!;gxcs< z!;i+%wGf4fb0U?>vJT^pcXtmIdbRyaKz!^D&3 zFqxQ%)mhpGd@BF`L(mGE9!CtFXa%j|`~+vxgcR5;%7=a%{x>5WR7ey)xMf7FL zIu2pD%MuBK5L~lS*Fq&K(h+Ps^qYACG^FIFFyx<60^UB2$IwOF5%=>o*WRrtNu>m>t|amjNB34KebdKeXWMABQnwAn=jz=~nJbo>~XTh{(ms z23spDJ>x!_(X*n3#JfM0k~=fL`C#j^*Gwrb_MMN$VRPXaqUec(8T!-0&5BFb$Q(Mt)KpvhLAoy(5-UBC?Q@SzR`-PR zJs}3bV{btp>Y|kZYoch{@GW0$KN3*3CWJJ{Go+)sTMG$mxt{>@P?HgdcT_-HGeJt_iUQ2!fCFG*W0!MyD5}zSFKeRx0 zg1~olfZEhzwP<dg(ZI#ayg@J6hFBY z4oi_xNW78o-Ki&Fd`Ez+)EK&rKBskig%UGc9P zxHD4uoo&B?D|T#9}^gbei#7EK8Eoxg}GP5$ut7s^vN%4WsH{@Wj= zaGTcgki-TRf`!?ldDBYo6~$~Vwfqq?s`|B#^FN*%!7nRZs^D{hgt2sIbEt^oYB^Vu zQvHr;{9)`=6O9!U>#M<9o9?`DwSP~|lg#zGbog1%$N4p$25ay-HV@sh6PYTZI;?wP z_bRpx21Xx^BVJUUBf-?`Y>`BZF?IZ%8iZMBM#t}KZ%$_;CK4C&2U>|-x`*cfafmS0 z=rVE1WWWo0G(q%Z5A)b$bTw~eoj%ex;gj~3j~-gO+J zlHn(up#FBSRLt%eA_=#*%8yi3Z4F5pQj}CWo$}HevW)7+yd!uSM#-FSznX1dmr3`% zY;?{XtL3y2r-3`ZJdp#w>I}dg#$^2y2d*&evKGVg6NSAme(Ke+-TQ{;+_|?L;S*1Y z{(u_cOsPvmP-Z!qZgl6W3WXt#mO`pLWW_$HgN`BZW+ru3D2hVr$m(EHxW+a8d zHP)1>w!Z1$^#1IdAtF+ffts6rrmO=P&KW=oa&4ZpFG`^lJ>gyTr3@G6zZs;?2fvIm zv$K5#Mqdn{v5E|_Qf+T7guLldj0$+H1V2ryTXJ&32HKrt0Y{j`jtfvmdrDyGaE|== z_%#A|i=3^0OuAsI=g5<0$|vbLj1}4x8U>DYY~meiYP{R|v1Vu7QjoM5rsS=O`^oWV z#r>B|_#K^`J_%eempgy*!%wLMMzRWn#;ST?p+96c!q$QchAc-ZDKmZF6q_$CAvw+X zFBYl&s<~)$b8lo1$IZFFXP(~)X{|AR5U3PBQSbIDq71a{JRl`R0L4WS1|LgOff&sh zElw{?+K}jXg#1rC%-4A)P`x5goJZb0^)VWxO(>bGOK}|zb^*BgDBYh?4cyoCU~Qz> z)u{?53Q~&D-F_rf&_sz3Q8=WfBU*$X3#=fB{D_2Uq+d)w`r)-L#}jz&>@mO+ya6>u zAm5=)=>^r)m3~_Vgz`ISo!dMv)d}1_h&f}KS;aHo4aoVg_AIyZvT8x-BuLzltft|j zT2)brS7?lvVjXu%$WA_1WKws6pq*4m@pm(h!w0TE&GSePD@u1sP{|71-fpPK&iFQ? zBS(A%vaq*?M)^pojC+uE6}wj$i{HP|77Wtau8jQg=k{qnBV?j^%-Gqp;e({^o*YSAb*X9?0^eH*_SQ6IyG^{J`?s?3XJeyoqr2t zy_o_XbGWX;lU+0`I2Th7uuSHMbx-_Q z-Y8zu680}MQ^bEeKmB6tud=3? zF~SD(FFkzZ?_f+^5hXFkeUjW^`jn)Jc6)d|z;_eFd9t?Q;?1_YYTd)S_Z?9IG08Ea z7g#X|IfFLSy3o@=Y=P0BStbZ~7YNX0OJ1X1D+elfSKm2faxYLhDLF{0NXof)QGMjR z>1m%1O$AUo^ka4xgjn{ zvg^_fT)97EbcyET^4+q`EX@f&{KkdUxb*vPP3)e7aRNF}CFmwM=t2NHAp28iCw{(HDTAQv0i>YWw+%59l3cQ9SxuO84B+E06vm4RsRVrn(0r+c8RS=fl@Gk zDqe$XLJZG7GxpQomq#gS`9)Svq@O1KR>? z_`b`R)SQcc}pX`8!=1MVdT^nJGF8wQ&vL~aYvm{>C;L%J4d|!R7ote_tiQL0;#JZpE0+JW+%H}Sc z8zB28X{=3sYc4k!lIWwG#`wPCoF@&=e0&>7!na{N>J!QKHINGXvMP2WypSd4KT8?yFjl^AnuEKl35!h;2IuTaYWPon$o%8@7E1MFDbggF>2TiaY2;$N?el9nQ?Co$rPVrDk5=EJS*}LWzA#-`CHlN5kq<&Usj0f!p>I#) z)3+>3eIF4DUcYm~A5*I%=jw#L@-;nsFj%4dOeU-dMswasYFs4lTAR{fHwu2d||HSgNFWP-7o0Y!LD69>(CsLYUq0ea}4qR$gkbsSWtYq45P%$z!J$ zqnX9W7mIUpaGW}eF#`!J6Vd1sbjfav5Df+-aN!hjZnu_EwBEeVRfn}^(^BV;&Ir4M zUu&d86@JUxo#KsYX1CbSm9@=^p~InU3N{+U@d07jIdg6dv5GkEd*0bZao9-jPyb%d zhjoeaIaCvmtI6g%Zx&)#$^ec(mk6gNxJ^R(^Ni1s9IR`%Zj_Qu(Q$SpK?yU?0kF&<(E~ZZR2yA zL)biBy)3?5#8lp@?A)L4R`)HMh+l!5Ir0!wq6@634x*B&Jomm^B=!2vpKY(ZmFxAg zRA=9lWL?<{alZp#JRXx@G%cT0#wV4<^-z~Q8*DF)ygO`Nb~x}TQxiuTux1}R3n(~o z#DmJMGMLYWcyF-I^s#_n9;SRlJ5o+a$_`nLes?ekkGSJ5nB&?X8s@uLqkccmW_6JH zgYPK}0hSn2j;#1r9XBS=M6+iZYQ?IWRv*)Bq1;rmq$Q^G8h(t_e?QCIGdSYyXSbm~ z`QNPK;?5YG%jaDI6FP(OgwD76ch?@RF!7+p{AQF1T_t{Nh|7P+wxWjH)oG%0Tj8~H zPuSiV{vKrT+md#gX3gFkU;RfYOj+B^paTjq(5Or>D#=r^OfW7+t>k2&LAd<=cdg&F1LK2@u#ni$4Z2LENtqZ^=?M?=nMR4Js%;CHY&R!>diZUaYMQ7P3F+{t@3&`*Ll)vdpm0oRi*cb#}L^_~CooolwvYMnQKdLr)9q zaa}{)kkjByk3}nihD-E}B*D%}y@NUo@F0m#d1zxG0;`@f+qr7_h}ntD8LD~zQYbXn zgW&bvrmyy-7yMG9Z~q~;2QCv(gUWB%ZCX>xy#hHD@)PFF+`ZrJx1sr`C~U%qNvD)Q zs$;d^YmdBWe_BxgAWJHJ!@j@jHQa>B!Fq=E9U{G1v8!R_vk#%RscYRaN`qAeyu&0` z!2VglFxGdJjucd5<)HUS9b2fIYxe$GenR`t39_F4H`^*?Rpsj1w-;yd+t$)Y%XRMD zx}Nf?_Nc%~U{i4yTLH!FQ*~v6IGq+s zah}B}GvrZH*Sk+C*~gZ*#h9@J{iTQ~<+(|m*nR&@BiSFTxSAS7J>n6Xp-*GO^CVLgPdvzJU zcOC0nF8Nentr6PqTR!y4g=v>)X-7-CG=R@evnZDi42l16{6ZVlJP@qe8{jpQ;AyA=i@x^IC^?;3?3$b=DGK1)q zKR*!@*eVb_gbIs$R8?4INwK)>VES&#lv+QFcyQec8Q=VNvq*HZnQr@qoM&a9s)P&I zDPKW;)grQde1^8(o;d@H4}q*g0``s&G5P*_vKgM45JRa)GjMMp@AC&!(5tMWlZ(Ca+hC zI&()|pbucrN^<>IUm84jpq5HJ$`pa7=83Ulvnhd9$Y$4fD0dqnEw@umO^wbgxyeRf z)WSwA9wy%V$^DY!VhNYkEyO0=rlMqcYSyhT98o$)Fx}RF2MV+*w#`Npl<62Ti2flD z{D!B2WM8SbM_8|uQm;>d4@#2gm6{5#=}8J`m1KYY$HA@ZNK5U`Nr^{LbtHLF?R`4p zWygwl=$aX9~OntynI7m=pW!EdOn2 zpd2#}@W_G^x|p5^-*Go6kZeO2c(|j!-D)h+EnWA-N=5PZ)jT@9-@bd6#0S=er>W(Z zC($8L1~oN7U#WUTX^UGA+m#K5)?4+%m4V8EmH1za$9fWd0#%etuafTl{v13YZ+0J< zo8J4eNe(Oms%F65;T)TC%P2(uU}4Aj%z0W%Ponz;l-rSTv+U0EiYoU!hUc532?9rf zAG8pGKUHau_uyAG|C{Beih@RM_S(-(v7^-oW0h-%3 zxp%r4K^F^^f$%(z_6EFPBTdlfjP|oRccgdqGZ7@TVYGOLmQ!0W{&%R(Ym8 zq&IJQlBo;1WNo6a&|_lD6iJd#N0zl^>Jd(xj<2s>t?p1zZadwsdQnP{b&frj6H(6; zI>A2VPBUc0L!l++ILtV<_CPHFQzZq}`;T0jTP~2U9S?C8zA`*AqG{iR5WayFa$Y{Z ze0h4Rn>KTyUnId$QUMe_Il3}X6L5Bdw>t>U1OD|zUm6orNr0V1!w__5M(P;ILu8s>ub;&u?-c+PSRXQ1bUTs`6^C7hi5%}vf zlzr|ar}src7MM!fF1~?!=W)I*2L(en>S()qZx z;yegh%eIXu0{20UgAb;%4&5a-nBK=8bP3@?LbC|5Yz_$>g(KD`U;Z1_pAriTvf}LvBDK~50?R{P%o1UuZ z**A(|$Gh20DFs#$TmyMo8rX-R5UD_cv3;u^^C%hgFTyg=%|dI3XR7 zhhqZ8f(a?Qae5mVY(rSUYxlHW?Q8?pw{a=jFERQdbQxDzLiB6Y0k)pk` zkA1QWT&-Z-EfR;;z+t_B`^TZ^zir7rQcn*}MVnGoAbI-~d;4)hYP=XDO(kA-;_hq+ zZW(j-o05vk`XAN1$;E}Ynu!Je*&a!?15&DPLGbhfdDeUIK7Pyrj6jDmX7Fy{lGuTi z5Q{6jHm$VOWW}ukEwkziLqXx8;X#z*`nnndA-*(c(U)K3m2ukiEmJP_?|Igdg!&e; zub$#We(Pnl2BmOPiqT8Y?oK_<9&8n2<+n<&5Krm~m$p@)$|(hf#U_ARrlkDKF*kf= zR|Wqx;gIobk6TYG3-Mh&1KmcrRf}DR87Ar@>xGj3UgniT#uI$PVtC=x;ibdqJ3N{J zR?GIIUnp1asbz-pXL6oA4*%-SJmE@Dq{?;iLGsuh;GQF(>s$eC^w?u-yDQ)M&B#ow z8;zI0(@(8JA(Zq-FJ9%nTaS-9$ToOtbdpMZg*%8oM^j~$d4Y?j?)rGpGW$HlH8&Kq zqvmR{*AQrvJ4tr$_W!{jCQIjR&Rc)Pn+_j*{@kGL)L3JBQi)Jjnw{UyK#3<}jcD;q z@~1a_o)HMG?W&?$B{|q%VcgC3w8C!d$z(H7GF!U>G_)0Ya2$;ec8*Wt22OOJ2%WJy z?9JIsw`zPzpGY1If)7OaA6O9S-H_q)H zpSU{#pdV8&NrpOY{b(S9O^JxTm!hwK-o^Q;Qt_GqQCzi zy!^tM+918*5cn<)OO|q4Ci2NnQTdw$h4mYk-z3QY&vZB3x1s{cm@tcfGr$Q~oeP@r zDy9Xzq+s8F&?!J|Y|htf{l`#ib#ru7TBZN8&{>_ure5UTCgQpLB5K&h`#&d#*DinvAcEdl{pYa$;E zo7vua8p`*7vl5j_uyK?M zIaU7l)DQdh?I>_Yy_v1St(~T+bilc=bFXH3o6Rd$3OGjA)Flt2Z_?zQaRR0#PR9c6`Ead43VDcUAw~U9!#77Bn-}_A63Z!iv`LUYNt4Y}VbmLJQ^mDRFAyg)%Qy zl1JB=GALXY(@94tOAcOe!Rhv1j5^a_ZA(iV@O7A8oJ=X?L)Ew%QmU%oHP;nL$6l*zNI_`)fhilD z`Ch1m`C8!REF#x}$wYaxxxq(@nzyG3KMo=CLsF|+Y*|`XYzWQ~Ww_d^nAMIv>f3wq z(&ln%(BhD&&*HQ0>v8K9s^3O4yuNGO$9~9D$e#!AvYb3M?*<~W>j(7cB2(7=PYy~~ zddY_IqC&2AA&XZ-^<(Gzx9t_PHRv_iiB8@FbnJd=YuJ2j%y+G}Jj3VElesF3Pv4E6 zp`voVbk7#h5_+q{7-5Ezu@1Va`lx!R?4uJM-eH_&PcmI@z+zd>z#(MwdN3jEb+SxAoumvF)%`72&Tck(GMoq z(yu+3NYc$UP>R<1ps+ss|M^)h-jw>a`Q3f&F3BT*R#GQdDD^48+P}ZcO+3-Em0sWZ z+}VCOM5j{!g!9yc_#d@RIePjdl(GVDfsaO+&E5Rn-ZBvZE6ROA7=@sRTDpU(+Ba;U z)AbcjBt?gfp;kjKq524z@~k_)SFAhj-~QUwZuK!vB|QBxJqB+_g?4=pO^Z0d+(#h- z@*~yl(tL^z&H6NDHR@ObovYBr`!GnFuOn4M&(gzH+IcrF>evV`wbIe`;ex+ScoS z+=MW{z?FgOYc6?S4a>})_zTFj^;3hjIPcXg{~Y(){bt(?f8n@yjs~rC>)PM2TIQ)a zODLKx2!&93#@R&n8=CzQT2c4rW`#?QVi|X~`O!D)Bjt)ZIhM{E_Ziuk9lNqJzaZoE z-ZD4|-XrkzY1-<#)W>Vzt3;&mcO2ev?hP8iIu+9FEn^BUvTBHPFF?uzl2lJojt8fl zXKDv5#$KNv!o7yf6s8|;U#1@*zCoSnBSemm=;Wr#UC+KYX0^Mv$2%#^(N6jaGi73( zk9n++eK?aI%TR-U(t76B%jcW?kyGwezRqO+A!=+4^V)`H)T<@2@e%=v({!PNjPE_3 zW#2W5(@n9b<08s?ANQUMJ5_ktcS)w%`61iWUt@fb?Aww$oH{xLoq@MaBN@i5NbKAj z$vVmIp(B2hyjz8Fg`8mO#C-t)8?r&IO;%f$Ic}^Ha^F8VIx&GdDNorw{mg1 zQA6~rt8w^*&bI+xEm3UVV_<(E(A0wwhMv%k`9vXiz;ued=VMAq04>4KNfoyNvwV_Cn!vxCaMUA=?+ zHG#tm=F@=_R<)tSQ%%m7VGm$)--n)JkLex?-$enL#m}dN4NerF0V|t%$2|lG)ta_` z4OnQ_KTanN`1=9x{+Y1KEb1YdYs2x5H%(dwNo>TauNuHV0u3KmW=g(VDUh$vpo%ZP zlK!e5VPePa8ASEYIq_1co8+A!oZ#?X_x)oom$^LqCD@u4Xul`H3wz4`hUNYPi<;2^ zQG`sHxrl&(@h#BKyYXZ9T2wgK%8`^b&aM)|(?b2j#B#$w%Oq7WHcgMCtNa%wCu7-% z&+c9U+z!I50F*d)62@CHhKAp5#8hBTp_KVVF4wKd;EPs+EFwIVif4U~DT}6P@u0h_ z&iNNuDEm4MTUrRVDujfF`~DpJFE40VC@76{mV2RDH&!mV#)WnyqtLb$L#51<<8Q`p zkD&F+moGS&k>F4F_JoV)=MDKw3EblkNshPUK$l7O;;YR$YYcSn8>*}6S{t5Izu%=8 zV0Gnh~Bdqs{w-xi7mF|UenZ@x)363fL z`nZEepZ4wBm%zq?V?o@o3pk4LCH%P)r~yB|l2acSa^Ck~9bPPkfDL-*#_fFX%c*){ zEwwP!m^%XDH_wyPt*y*PO#Mi;t!u6Q{*l8nlk5at&;p(7Lvw?cZIWKb&SzS~2c{!} z-($$|lN#8`pB?ZIEek`@L!YpRm_}n5`6Z!hC`^j(1eIGCFsMuGx+aX8-;<&$>pTy= zhOeA8SQIaUNm)O23B!%wPv3gTQ5mt%U|iR_%2(n9jZvsyUbR9>F3l)f%6jdz_>aSi zn6Ff^I>-CD)H(jgwIZIrk{6zj7i$#W(oB*j^gvCf zuDoc*5bJ)ySd_aWRMmLF@ZG7~sw;L}D-)}^yM6#}gU#i$FW_lf(GOG-voT`$F~pk* z{iCR%ww%-iv_^SbI8r#H)OmW=SFCR-tM<^^Iv3-}AH1<^yPbr`96JUa+3k{tT2HPJ z_rvZ2mdheCCP(EHF;!BGEtw3bVmp!#xA&XIY}<^jq4gqy!@^2C+a0RZ%M;6ATxDOk z7e25=f_l%qhG*h8Anhpj;ov)^zB?W}L!Z{Xca_H#%*72|A@c)e@5x;}ZZ-(fBG!F6$p4j(P=-1pm^Z$- zKO|HluO7j3;D@2CtR`(;vdO8}IMB~3$=!eYo#u~9kDMBE#7x^S%qQ@QYKW4+jNu;# z`|$A+v{-El*%pvzhW0^pv~`i+kj!|YU{A*oCn~q6QR7c(zI^%sve0%|@lDR^)$F|o zzbZB)b1h3d`pbs zGbSg4NWNm8`K`A5dKb7d^EZ3?h#niHGAeNuPVeXdM)qx{TinRF0{&4Z*gqae6&kx@ zjh*4bD5MRo>VvX+{YQf14}_e3v$OXFT-rVN7YEMbA!3RyyaJ$CxFp>Os%#zhC~Ce- z;uIazIDU`YS#w<1yxeugDc6a7AP5;SQD6Jw6fadIp{Jql$4H{BjGp4WK-07q_A3lH zyz37^dn&fgT^r_IR)ap*uUTGwCG7CJ` zin8y-NWU0*oj72WwMaG(BT>!E$%g`fjn(~Z-#1S!Pg<$W;Z_I0(Ko{)VmK+m9fF)s%s=F#sXfHim0FNf(FmgXC+|lOmeBC&{A8}(7ObaPN;v~a!cLHC>+zvH9 z=P7FDyV)h*tD)~b;4w<5sIRrq9B*}sUC6{r~}zp^I3Onky+NgWu9!B zFOVzvvbiBKA2#3)#XSK0 zIj^BTQ=kaNYOFX9NLtlcfIV}53+r9OXq8uV;F|L5#<-7Jq9eBUk`nB8EX^86!mMC?e~8r;VMn?yDw_2;9yFpmo%ZBS^gnaU0^ ze0sqM(UlBtkp#jI|q!uXs>RV~m_+G3E^iE$2junCQM23XYgJiI#^8h3hRo-SRNb}-GjP`QGk__)Y9Q(E!`6CCdQ z16fu2C)s2FNoVk%<#s_|sDWDOvF+S*sUjC3UQyk14>amWDbca2YzRCgZw<{-|y+g3dec7{HlxnJ!pbnuAx7&0TyOu0nW| zMfm;LXS3k;pkI;jz*$qJFfr$;E=OX^uWex)X5l8eSYP|v1e(Mu*znbuTJ zUvXmnk1t;?X7RY z9g)wLb>z0@q@?IxQPE|E?ZSCt4|YGM4dBw=9e_f{Ir2wE81Ad;;^un&!&G}3+2~}b z{dRUW*~Mkr?fdt>^e)b70iTkzq~)6m>@fClVcnmewPUm%OIi$pwtZzp*EnZn)(wi8 zo^NbO8Tm@?4am~syTeI4(ZETc={jLt;MCJl%H-V-;$liWFHI@LiP}f znCw}`6f<@vdk7(9>}z&2mNA%_e(!$2bMLv|`_K1t&i$NwKlk_bM`z|7uV%))-(Iih z^YMHvQ@y0i)^Xc<>`uct4kX{FS*6s0ol|w`$sx0zw?*zF5)F@K^9XFbJY2EfF-i#3Yo*9*f8R;}f?gBQZ5Z@E zz3a!J2+nKWnHy!R-7^~c)FqS7VmkVSwFM3@bxHA!IsJL@ zIsBNa-?95p>AaXG<71^ie#k8oCrKhdBV^z_BK*Dud`tK9 zxL%`Gbw;?Jv`E|%ea9>O_KL%`&j(6PTGx}^AmJt1^V~idjM30;tqbn-9lU2l&4{mN z9z3OBwjq9Mk`uT&*0-7Iph*^(I90Y(MU{vm7oVl8&&`C>I)|V`x41ObAAi0k_&6%6 zt10u!chvfJy;8G3g<4?4qa9da>a-s_NO^s)GhS%?9Y?=~xq94k%$9moNoQ`zEl~CE zU$xs$S@uAY)NGm}o5&-{n%&f$ zLW7v>Vab~Q!tT8Fq%H0|@00Mzk)_Jnd5SlgV*n;F)9o;`*{gwXf9Y*rf3wAxSmPF5 zXl-FqEpgQ9wkueyqDSiny;PRa`6J0UT%mfcBE{{~yZE7on(f*cGXW=D)bi$W^@JFI z=L*jF3R!2Kr^2U7+UJiAuc21Cyq*ThQwMjWLR7&hv#)0ovpOxI$rULyub8gCV!A0> zLU4FlIa!HV1%Pe6aJGJ!O%^Fi#6C7!+NDAf5)Ga=xO4nj1@VJ?>UaYB0}Z!l_dJ7| zi^L1C9{t*^q|>kr6;SJTms-j!7+bQhebX<17FzoW4ktNV!=>@w%Azxo-VWbv@e}oz ztD@_-?fQ034kk$D1UPrOLD)Sy<Bpk+Np3=}7WZF%y@7uxg~XZMLgRsd}DW!krb5Qy*X1 zWQ+$L1Wusy(w^{YJzAVz>@>WF{v(XZzWL*OQf@5WMAt7_>7B2Nk0m*@<3=?b+ZSQK z^ETr^PRzLSsX)LkUDkCv(7LfEuP1_nfCwlBdlPFr7(ctZf)fQ|vwX1U*Vhc;Qy(Tb zy$k?q*iFEdEM{<}EK{Pm`?r`|v)8BhkPh+F+rkVkjW8(WCnE zcwSc#X4}f&Wzusivj!~MXeBlL{=g_=8VJ9V8xdGjGzu+RzGmmm=be$;H|NV!CMvBK z(!XNb4@UjSi)|~gIldBp#i-x)Lr&G>sgOJyN2TE7?yuBE_ivJdXZ6kmbx)T6eC~~S z4q^mMIM|&HvEFy!Jx`ap=rpqFbnmx-*U z%7)&X;O{R>r^CLSyUB^MHy#IJG}e5bf&9*Z<}^LBXe>_?Ztcy1#><`C$Lrg&hl?-r z+oJ0Ou1HWji_d#-(cdxY5>w~R@g$2|Lj8scwWLvM4?b-8ySQW<`P3w8%IsPhUDgz~ z+e-~F#{=a_#s8K=@t4S-Ak8Im0VXsvDZ-`#iE?&{3M`=lIfV>yF|?v03?)a7vE0v& ztvIWhpQa=0kXcwz<67j9i9)WZT&M$USPkynggk)GMxu5C4v<=e?3XNTU~fOMfj6_q zUVf{7bS#X<$e!)t>pT$!EnR~oaHmeS+%CAbV$$eKo>ns5vmEsdMVZ3ZI~i9W#437C zvpY4N3DJLH(5EgUh`t9Dkm|vfJ;}crC7+y66lTLfgq}j18?0u_ipF8G2P>MdpLgOd zaVFXf&qL11c(8;3@h(*^)aN@JaR{~`T6m*!1JFoM8|9|CRYGy#*p@QqSIbdDD93_u z>q~at2Dd>fQc8T7vq=_(f@2vNuw>&kkcccl7j*~9RrJXsrbqtZbvhlmBZv^PtuJJ63+<^Ux+RW(X zzMWC}dI>5gz7*l&h^Y&ns)`lbG)zttId?*}+B3mQ8{b9LA>|O@(-F}@BBT>Po6?3d zrg9T?JevqEy9j(*u9L$L;{H`<1M{!tCCq}yF;{lm9%bZC$&8~^42uSp3Ic?RwM(Pt zxFsc5$O&6Q?At$^KqHbf*fz=Z- z^FT@c1KlhJD|2P03)YjLk45;6b^z+;(=(ItL9z{a$xvCu$saC09L9>F%Y4E^ZZa7h z0j}mBkfunK%>ZORAG+$gMIm3m zw$l~m2!0Zzp%F|9?hY!a4qBa}d{jA0?Nm8V0u!`btm(ZWp*W5l0MxmTcgv9|DC&^` z&@+u}Rhcc4ueK+9uvquC+Ju6$8H;i>LE4npAO|=%8BPd9{tUywX_tnA&J3f)HB3WW z_s82_?^Zlp_J@@>7Bn*@e?ZQasa2^}%zE#%E*!4)850Uxwbl03E5`GA@(Xlt4|9NcqHFZSb4r%Lv9VSO9 z@`f-i`SZ89&w!Ph6zkjGIzkF!O{`lsEv>ZKOCAXJ?Nz)5{Ez)a1zr*(p3ZY8!s7I` zq|0=a(6~CcG_#u->8>B)*1#-SdK1WgI8HEbi9fs4z=$`BcSk{!jQt+!^{;82E@e1V z-_m+NCZVXE!QkHNYSD+hNSa508_~(=1}-RIp4Ql)(B}pN&FDyX$k*6UrZ|K-#%R;0 zL{7+QKCw@f*RZ$07<^%P5waR^AQUuE{DBOjybq!yRZdYeiteS#o3Y17l6Cy}K7dgd zs|O%Rloyb@opTK0?kv=DE7IY51Z%13Eq);Ou@b?7!a6Z?>kuk-Z#R%$#M+!Fln;+v z<5MyZxErBUfq{Q=Zo3V(OSsjK$<-0K=)?17P-IJ|^#(en0`gXCW#0AXLGadb_U*~f zpb<`hPsmrf3ul(mzOrp8TUR^D3JqTml)e?BpNPkh<&3`yzC7;C+0hiCnv{7HGGk0W z8$paCIZpFxv<+W}3b*d_CD2;9Btkg>!Oh%0OyolB#IjN>r_Q}5X^D-A%p9&Bk?L0M zAh>}~U>OH*??tim>z!@L8qho8B=_*dItv>dIbN3r=LRtKp zCc`cBDjT-9qQM9+62Nyo_LGtgjKe_sgzjw>dV)`AuH@UIjwzj>U8|~rS%zbYmTD@| z`MJ)gl;%~OanIlJ9XUUhn?3hBqa@vU7=nGze_D;L{7ZuB`6O+D<8fCKaua@RKe~JT zZT#c+Nk5Ez5OhITI@}~?N#NO^7|}Y5g3!9VL-UI3XC>a*j>h@Awbutl!W(Ziu#%Ol z2dHAyemI*r5(A9UOHC(op9EmO8I?m?!jmFVEcliw& z+EB7*2q+>l8TH~K9{9aAPCmE&w%s}~s>RePmacE9tX(}Vq&7UlP*`=%K;K=$W5GSd zV7^jzRBI#K`KH0(BsSo^3J*yWe|84d&d=&H!Ab&8M+}iwvzGc@YVy-P;rWYYI8!y> zq@ZuZd~K)3rP$yHMT+$xwUsp+1o4AZ7@C>r4(zhKs24Td9!x7=G=kcz6pxOM?ouwhhiken`-3R%+l_wz|4$u%U}*+}>v_ zd7#F2>f2tLmUE^@uXv561!oxSAba2o_Z<_;!9mcnS@@@e%fKIo%8sSk{>H=aJIrCb zslK={N=v10W$HtxAQrz;iBd6;!|| zD&e~!MN}7IGAJ{Cc`!>m!+_eWTNw~>{d3a9mo+{CyXJ z+ogh%TPo9B=**36f}a7byS4KcgQUJgWS!Y_wvOrJK;ctHZ(<}G<>b%E`y$Wh@Oa9g zFx%8h+AaU69UY#j$v9cPD4)rbl43}*?Cjf}s=Av(5s~?+y5YT#GM%QP=sTWN)+S!q z)aDzyCn$LBb1(aLP(1LqB+@}C^yiFq^U7)EGy5A&#q@!yE&UDkA0#rwj1|G9#cXDV zU%sW8*M>U#@|;_!#2iUcW4aSA@Y-liTynF<_yBkCndA|*&pk`DBPBZ?sI(twSye`? zsqkAs!zCGUW)?2;nlsT|g>w~gqFrDy=WgfAX>H4LoWc)Hr5h6Y$f-+CLOR;rmh*`Cw6+Gr^79XDpG#9{xSSV8Cwjg*?r@M>i| zXr7GcHwJMa@uyH@;V z=!W|AHvjRSNM}L$M2mJ-vu<#{3s@!#X*Usj-sbk5bL%(2xuxTY%O`uk8;@YQf6+Yd z#nDe0hs_aCk?4$*Eh=o4S0Ota$CfM2))D!4R|Pb8P8STw3>*`e)}VP@#xG|w>~`+s z7Ce~0T)qW@y9hIzID%+?7Jqyxx@d(T|7zLS%xA^O)Pf#V?pza*A#o9wo2m7aAEYdF zqiRb;m~UzEWbY&6Iv5KjqYh3uXio(gGxYKi*@P2_kE;fu^m!?yy0A7EXEfKT?YeJM7Sw&e<{Uq_O9+WJ6mIr54rmD|+n!&~Gw zQnl4TwxRHY+R=4s6EjqV{Z>xW@h4)0RQn6TF~f~Pl6cN2GW{&?!_O+0f&8N1eGqpl z(Qqhbal*aE@lj}p0ehxW9(2CqNs(QY$&aFx3s-uiuwqFS#K-PCH<9S!R5_wg$AX2W z9wVA%-WWFU<UG~jZKZK z(+W-)thSr zVaFQZU%OlnB`Q@TOXgn3U%lh$mP7YUhtA!tQ?sFCvU2ae^1%%1^KU=1m$@|} znpA!u*f+xpM%UX0p2F|S4@$4q~8h<$r+Dx_C_;i(NvtKv75RMB6Cd`>mC=~TLwRw^kID?ripzXjglK)$sFheX`kN_dS&^v2MKAs0{ zK#5xb-an>EBw+@Y&2s4)O>kN_Y4?DO{FIU*Qx9t4yD_k>qu0#+I+}q`7$2ACS+b}t z{n{i8;8f}jycK*`wfXw)=tmFZ^TW!JuCLX#*4BEKKD6^!Mb|-L)_6T^9D*x|OR^P2 zy9{F5kTL4p*!h(6TQd>edbC$BaY4ioUFF5|#>h_)lkhke74pSQ`Ad4-1VRg#4cbu$ z{Ch)F+c=D`N|NP`cDmo13q1Dqv z;dfm<`4h>0a}YgbY%J${1rSeVnmf;42uZpvpmFKdEd^r(1=<3wkdps|CHx5C}z&(v`iU`*t+nMQG%NEJeX1Hu6@7SsWp*bjo( zpp&h@jR|VO(mN@}za&emL=d^wWz3agwE|4nIdHBTDY#CB1&)Ey(K>|7j|+Fc#2n<_ zr)s!ZF%p7e?I{H%z$`V*D9!#1qpHET9c>z~h1N|%SODEFJ*ivK#{%fO@-*ny9x z(n%8lNAs}NI-#P)ec={fQQcVaRpSw3ESDmuRo^r7u_lfey^1ubS-k=tyYP=EnzLpMA=E zk;#{=#WpwfiKQf)k}5kugiLs7*2~n_Zsd*$j#Z~6Su@t;CZl!%WeoN8Z+#CsJ|5&K z3FIKDf11*kWVEt$B`T`C^=}Xu5dNN}?SwQ_e}*V?JLt3)>=wR+l;qf+J>;ZN0FOI z^9M%Ih}rn&-UuN6L>5BlgyTgtZ@E0(;Z-E=&#XK+kV{ud!#YRJ`RPM6zZP@~Greaz zT>$kYA_>e9AV)?=;uENZ_Y;VJJ=OOVd$u|pX zPShFp2FObBy7)Y7tEQ~x<>8&!I|G`Pm;qUyD_&=h`KI#M zoFqFfu0gy(sBF?Z2D{ZCBoNPFVr8{dgUQ6?L*oMIJKTh70C8NZcJdJD>fl0D-+8r* z;mV;GjD$CtXCl^OXF!plD=RXG6wa8@E?kTAZ6iP4 zCPuw=P;{#h*ZmO@6L$&l`gr!*x$|mzviahmihUOb9k4ifPr?L~9b1hV$L*WfY;CHn zQR3z@+ld!Q8-h|Y#vG|C^3Xh8ZiV;w@oEjLDtm7R#i^(R`6=DZ2FV(Lv!SG*GT{1w z+T%&mqOP6OK956&mp>^rv+LwamKip_GukX__spGCJ_R3Tw!rfP27G{`%q#vV6t+0Y zA}PC4U26B;Td1lpy#8?tYoqFj|80L(JN4lOFJ~S+)^F z0ggvGvLJfZ%ld?qia-Sc{<4&;>CRF3VOg%&-WPh&IBv|0wx0c>A+zw;+X{yY%yljeyn`&^tA2Gh5y?OnjJ(vKJM#c<|033Zh-RxIH9sQwg>7snk*# zGT|{iCveVlQFPf9K2_XsEmI>69*kwWoT6HzY2Pp?nDArA(f4^dzmH&_@(CfMECUm6 zld5J~^%q$Nm>X9NL!*<7-PTNVhtet#>fdJ_s>cFX7E#_#s4XfQE(#MFgxSo1aK9rO zqUv0;#+=N0Uy?1nPlNONAzlG~r_r^>u1c=c3+D_Q3{=0+J~NlyK;R$hohB8VQu6KU z`;}#97>&M?Xqyt~>*dwILy$3_I*K9jy_kv=$?NhG+mqEEQYX})*jCV}v?U2aFrO}H zH4Wm2nm1QYGTPKc^=bhAhAPd)Ja6aT)NdoXb$Eo|=-(W{`^7H+CeMTx)DyIQ30UGBh-? zAI;ONO~icgHwWvq#lGIVBqK)pc@EXb9}GwLt|{SZS5;0EjGIv$q~=DB#e~)zYm%*L z;`%wBC&01wekj(N=+5zo7V_-WW@iQ4`er;(JpLGNQ29Z++_N#={sP8FJx#FV6GK;j z%dbrVl%`NKL3GX8mMHPfyDv)3pV6K}`3}IRih@AsvI@-iAXX^7S=!LK>9|ee#skxk zw%`*-Z--4WMZ_FQ8#sI3>T=kP!7e8w4^QIjf|ns)IOjVL3iFYc4Mw?W<3I=HPf0Fd zvmsdRn%6K&PjuNAi+=zKQK3zpP7R}~KpmT-r|RJrRh23i`uU_G;*+D?r);;785h0k z!!@JtH_}`$VixhUB~4oEQv3KZkQO*jZu2uV3yF?BsV%gsET1P0cWhR&Q{E8JI6utr z=z`(9nl@XbTRdrdfTb^v9Z$Mv(4w7|{W{H}C#Z*Uf^Bc2Y|hNuEo#Z)p}U8hlBu7o zTO$x|Y47!Ii2y?wwPV#~t>zb2!v2EWO_{P`b~zm2zf z3dunPAlDdZ+qX$)XAq|sgIIv5+qoHsIjGT-I2B_wXxKTtnjFS&2ywJwWjyveHL zs$DeY2cTc~tFKYrI*)?q<%_>;jfmgyC1 zR?-9)ctho~wl9<__%S$!`p8JGG|{BxRr|F&oR8BxHBvpF-%6Tuf3ye5*40G%`pajp z)mEg?Zf^jjalYzlP^b#Sw+0qdXGQqudSgRZ1lt(N%48vYwx!tJsjszY%*;aT)B7rI z7Mf&R*3PfNh}ZnV*DS_x)rmth0nVpzR;TU4I+GIfa_pZ-9cgnT1q`5mP)VqW^F$FEP%#WtO#C>TW_Z) zLMi!Z?DJH<%PF%{*ylk)sx$Nmm5IqVkkeZ)lT;_|0Fh&7zbr;&X@Bv>inV;Y{7&GC zTHDx=ua3++6mp!HxI95LdK@@HAUXr+c!&-8bc&x{n(=oTkAAH+!5A1)G{sjaa#wJ8 zp1E1Hs~E_}IeBn%u(b>u1G^@)>6pmwMIHV0CCT=Kefxuvb1T*b!w9^Q(zt5#P|(?W zAi;;(5(?3^58!1Ds5i2d8+>)eD5M*ROSqJE=4e8xT+9=!RFEJP;EkT0e2D`L2}GHh zmx36;j*lt_!Cs8C9!vgWDc{Um+026GVu!8^eW*HClpq!pxMX0UrKOJ<4JfEb_hC0_ zw|`RUW+~OS1mEd}yyIkh#wM@frW6gojK|o*?~aJnth|pKHW=`N7e`C zW~Vm7a;`heEnJ#ZzVal(`SIs70<`nIKvr6mzPom^siv&6S%;Eay7^?$HtiSD0Tl*# zhXn~JW^CKT;oBRoR*sg;FX~i*=yCn&4L0-9R$uq&HAmf{id$8!AYeGosK#+7l*|C)5s)cVIO3gN3Oc-&N z_w`NLY=u-l-4L2$6^ljNt3Ps#+N&rFQ!(Uo)bn%%OGxW2xLhvVuJ$68rLNDiK$=sU9v@lNb{>Eb)RFQ2AgHJX{2S^god73?Fi2YndRw*mOBDyjD< zkM%VyO^i~p9|9d`3NgLRz^Q(P%;JqlZmj80C`r&wI8#oSSO3`W3t1FC`#&t_Fzni)mEdGXsnxzLyDS+QW2)U`9oFw z6K(G2VYd1)tfKu5B0-aidM&63>Q(%CP&t2N?<8P>aPoQap(M5SVd#(1)8^-wYIPE=8&RR z9%Atf2)dngrMCkx z|8rIv&a=9d!WvnNLDjZS~oepAXFaOI2n%l4G{^Sm!mm+~KE{A4RF z5R&j2uEiId8?7cM*EpRVwEo14ENGb=86^%=GV7#O-ZD;9d&4_Xn8;E&$&BLN3I63lSu+`PH zclN^P3JcK_g);s6MF^6N-{e093FqqONfxIiM?MIED+V-b7~5mKxeMOD(0ae_0B$B zx$j0tjfZ}1G`Nj8ZXV#}zZbljy1Imi7?3E0_(nLL7gC+~@ zckT7*ODfKo@?@KfR8vzPS6gRFAPE9U7Kj%~w-0Us&PM){EgSLN!)*{oXd17`bpPbT zYW&KI(eP0&Wa$$XAW)9%aoZrNZC~ni=}ykb!X16e#t3!e)^E-l4T8yF#WEETvssSl3#JK zuKbjdrE=z#eJXZp$GS4qd=aRxt&c1lxrhNz?<2!! zFU{S#$i@_4mB*uNjr@4Yo!A?&H1~lQ?Qj1Yh+omX?VQ;zqpO~t+q4jkrtIe^p zQRy>f2(jLax0tS4$MNGIVEM^TeN-N2$P|K+qy-P(uLvsvq9P4oc<9>^hnHq2X^w%T2qqr=~YMJ8xHo55J78`c_uJkAVQOiEL0JG zQ$ZGS-ldYmh+_`T$Pq4c`d!v>;;DYMt8t^x1<$9ErBFbX?TQ%HZUJ12s_}^{6Q_Zz z9x{cGsmUd#;Eq$X{EoNOl=!&!Yzi$GwR|>h^E|Ux9zLxGGc_B~(U{QGtQgPOfoy5( z)}Gw13&Gmg`2j}O1$Yo8hJ%0Kd%%^DkG&Vf1aDOHhJ;z4+|~;fE`cP$+0BDKOe&Cl7yqk|cgIC^@+)@dz2Mbpj%JP2zO<$Q%aXY|Bb z-awyZ!8;50vus}j;!hCTwUXCB*3_fz2a1!_uW&SP{AaM|OhlCOl^VtY7up#MUk_ZX zKVoDZF(Ng-Xm+ROxqyQSF0jmR&D8-4i{r)s!@Eucut7tdP~kEs0nSy;dH`NPGWJR6C?NSL%ze+sCYdCctUk#k z5^^-kuaBR;v}rHCn|yr>8w>|qbHQXu8z>kUukk`zdYS}N7mj{}KORo1lawvl^6P$D z^zr1~2WB_MFB#u;_+c!YR-j^*fT^ywFi!-dQ{p;&CWa0$%=y@s)B!N&f}-lTBkPooLauDO8K~%NKzfk*Jr* zSa~X4@k}o>skFJJft|#RM>X}`-A!EK)(x-ZxJC-su62IglNSK7XP>^8sa9>0{8im< ztRvL}X0SzE9ONg)^#LtcH~%z$aGg@|l2O@h$uP9vhPVt^eZlUz4&2wB;kre7VQv_} zYAfm2l!;k2nHv@x%O3-G>!o_F2?g>aw_UZxy}#)MiG!+9eY_j-)C;5`Ym&&FPMCJF zWD;Sw+3eW$ys=0IGcl?_|8%;}y+<#@&h_=4|(=RpVDz;qf3o?_V@=2<|#nLd}LY z8lQ;;!ZPK%Y;r6u^}q!dPWyo=2sh%eyb>^B{iK-=*YMF#Zlz;2h{h!5&fqFr#{3}z zaPhXlEa1Z$4Xa!-rw*irBZ?+niJ9E!zNgG#PsY8S^G*pmx3&{u38&tt-HLQ2hG`^Z z^F4T$KJ42p4=`R;F2U{%QTcz6W?TJHk>W7v4^I0*ksLW!JH^1p1{x-_7un+&`e{s( z(6bW_)89Z-1ytaEePOE(>@Y2pp>T$VYvYS4pUli$sn>xlwMbMd>m|Fc1jh6Q<=ChXqk`kPSCT%}qfPHY8SIJNS0&j^3P2}0p?HaY7Y&fVk|hK#yX0X%z8%0N3C@}Q)qy_tq|JWvLBRma2@K=Z2nyg ztw}xCT-aNH)5WXaq#z^0`*91_V#Rf@ga(`1Q*hbFQ-nAO@yB;bZ`2QYBHhrA>s!bU z_)dE(bHE3S%D3KLU5*Y#1NZzq*l!)%H*Wn6L}{X^1C022%p|=Nl?%iKlYmLu$)tCU zV%J3B!uKQAjIE#2cBXUNIog3P+XpqeNJdSfsr3ZoDaX&hXo71Z!nR|DHA(y@@=hd| zj!al}e^?1Mh`N%2q4)b-QF;AT@a?Dfp6I%;2_}oHaLJ1s^}ZGUSwl-i&BT+hh${ig zZT^!jqNvDyc(bM#QbjvGJ}lKi6tY%;$=Co@EMUHQqa;8W%!FMsvhVWdjDys*le4Io zAdK+p+k9#lp9F~(-?KI28=3m3MltS%vbRiSzjIn-Sii#KdIGjhP;bV+S%4xJKr@UtlmTwcMJ5Nj8s*x;KM^ z4dG7wqgUH!5y7dAa{NbBIAKzHYoa<@mXId7ZVFNao%GclqT+fZ_nu&fDZ9{-@6)fR z9zVh*r@hYkI+xcwSUcXX5ikkf*|0HNv0Q^}jqMLmPZ2&?sm>yBY{-_cDb~j%CB(D* zET^MPFvZ43_ufW{j49x9F1+$jBXu=TV&JVCAt3b0I29q1ts!Pmb~GBx>cl!iN>oB> zsDtHJ^iOthoUt-pyCz=ZKAQac)T~6V+-?5bWB{BDNnZxhk_GaCcq34GZ_`rgnwR7% zB+IGEa4DxD z+-rG`3FfUBvzD5e3J=WC`>p1)b$A|imddYsI3dBg%~`Ght#{vc9l_?&eQM%{8z}M( ziXJUN0_#1K0XHVFZ*!y!r*)I}!@hE`$Z)vpJe!|0hgxZx-KbN~2?FPaWc;A=esxV{WbTT_(CN~o?4lGsUdZUFH$03Aj*P3}J@8DSp; z{o$qlfP;>vSTnUPlNY{Hsj+9i4WJB3lg(HngqjLJIOjwj2b^v8fV$^VNPTJ}eDg>s zwHD|@7YD3lt-7YLdo+yz0S_&FjpdUN})2n)-{za5A!ZyZkd?@&limLLBfa0|->X3P(Olr9cuoI<^ez5@G|osU%O%T`z9881OGA5 zjH37~{-O~LdWqc(Ao)@OE@A%$fQ2yfr=tN_TMn`O>VKMUw-x`-aQY4WPjZYfz_}U( zZP%z9Hk@3JD+Qu=s}=qzY2uHrV=lo?CG&44^^R;%+aRpJXqIaUvOy_OW~!zn`TSYn zGZ)1CqS-zm@L^>DWbh9zRh!b=69!Pc5!D%EYHdC|1yD94_EflbDd~u8d1{;7dFo>) zz>VA=H26IG&vl^sGu^EI-LFLpSl0r3gP=9_)Jfo**9!w6Q2aVp5{+tHC@^w~5|jkK#v z5w)T3cC=+!fASLhCYa&YJr8L@@0#9B?~l<|AlGg?&~?f;>>^-s;f86iOwt`dxAbM9 z%P<^?R_%b)6`U#dS*8HR$X(= zRe0WOT8Jr7oHUxFaw;3^9CQSxMu|`k@SK82u)w!>|9Z{3jQVc!d7}R92uz$`j zWyY`6YS@+Ik+8dG)$=18R(S`iE`ZO?&(D%9y;F#@l%G; zf@6}m`~`Gk=F(}mQ@yrvzUjKML%j1OtjoY_+k@Ip79UT|j(`SRqQ9qS8_sfkaeDx~ zL&a21sj(l>ADn>6k%VXXL$T~II6-5K zaWS?@J!j+mr%R3cl$wnhiPUuA{R3T?;%H%tW_+sdTdVmFeN;-YDuBe0wwM)MC&XbRaw8 z0s0$y5lt#}tX8xMajr`?%v949YGz3Wc<#m9o_8p*)RV%i6wWY|3O`9561jw**x#kH z(q~8vQ&~m4BV|_ho?DV3po|Jk6xm94&H7jJW`9vAK& zQS&Wzl`SKZ_j2R|Hwi7AlI*a#>Ap)4xX-$9va%qQ$_P6LD9)|JRZinUp{vRP?)kV$ zQhAG9&CRMIfN6a?D>ZQYsbKhmr53vbE5!EcsC)RNb&4$XzECF+{$Bs(whn2_!F1V2`ph{sKO0ZpL3EfNW0_OJN9c`K zM>7N_$0&JiO{9l&t(yb2Sv63uSFX42xU#!8o&&Xl;0~t4hFkp{KavbTn4791mr;zX z6+ZhjwrRPSX!XJsPz_T+=Bfa7X45K#W)(mqCb_>aSmGC@*#@%yT`m z!xr^Z4_g(O5V3mdD~w%r*hJj2Oh~B!P2UI6R0Erq~bD&xby| zPY{kMm3V?si`U`sXUYhG2(23Fs|8$eZs_7EIfdl2c4DY^U0BrM*M-ta#7U$z^~e<8 zkilb|nsjyDaMfB%ZUfrYwR0|R1Q@QEthqF~Jsh~B(m-OzTeovtuigX#acL>3I)tBb z$cKiBRKp)BWvG`%M4Vq#$R?Lpu@iFhZVapb^Q_x^zkxt{(nI93bvdLN!MQSYW(?Xj zz3Ggzs&;qkvAz3U?Ar?Gb1@<6qTJ_NmZ z7{BwhrAp_hNmaxe-?_}`lGV$fOz3}1b`HDC*<`#OQN5qnxlZ#v^9ZG^js+Rim3f1t zKma$>&JAIOtwf}{BAhZ(pzZda$UF3f$36AILnF01t-}vlrP)Rz5cWb z2siqXidR|pTWkyu6g)w5k7nw}6MZ+_I~i|Ss~tlH@*+ul2nlK%Xw!qA5!lTHe1Fm8 za>{PU|8`)~Ue5p|_u~j^f-{Ejb9HBSg9#={>YfJiK*LbI5oo1@!cwcY?-r9XtmeH8 zI=r7!F7#+;*>9=$W$G&IY>bA^Rt5)w$dAy3i04cny`WwJ0i23M?`puq2zJanotGR7 zi)`bx)0Hu?1?LGXJ;}!D-%7HCwQgu7c#G{ldY6$ssi`vhHorc}csPx?S*8MbC51p% zxUD1Lj*u7}wX&cAnd9_{6k#~%Rn_94qoMn;FKlo=@$}m#lHL%uSE#hkx9;^JbAStA z{DC92D^mE)`M>8yPQIl4XF$ce%^$vgv{0QkrPDp&J5LH}w~RGAHQ)tE zCHzuFLSO&T)dk(g01?=QB-LpEiGBAG9+CDl;=EQmT~U3?+lyBfo(TrzpZt2H!M0u1 z`i*tD{2B<)In~>$7q`0~moYgjnR65?LMqitTxT%pEWD!wse zEpGqnGS}@JKNk@vfI2ql=m_~P2~h}5>1|z^W^{cp;DAr4=wsbnHxVn5c}ec-I@u5@ zvt(P1&R`Kpd)C*^lfq|X*v=c)`n}b-P@BC0)vZ_6y=n9l*C z*5b`tUyxTAJ9@kQC)XkfR=;Rw1)!b1GuH7)WeylPe?_&q&lUIDNc9fpJ6O1SAUvh= z&S*#G5SMMheLsWKTs+~jo?V1D@%HN1A7w>!WvQlN6l4{8M83x=1)}?RYh(rnj~23} z!(Dv)igS@z?|j69UC;|?>@fBFAs4&ngZX%Sj zY;a#XIK%X$Sv}_{Rut|Ak4KyW0sj1rnO8~v6PMzFeCbG;jWcQ`PJK%iTbg3gB57O( z#Hr3F*K_YNxX`hV9D7UzW_O4+OMJo9#^?B#ASUu-*ga*{ncGN48E1b*YZF8zZtt4k zu1>&_=${9_XllSYnXUz#5vb@+{JU+J5<>-p7c<$E>^Sst<#5Uai=GWjiMtlI248-5^KEJNSC`!BFXwPZf#k-srOO5qk#xqxbG6fa{2cpSOfO zW0I_-jlGp#uJ4H7Sr(r#>;~dW1Hy+k7cw=^KI>=(+ax4~CL}#7o-8;uP@wzHSj^^B zlW19#revbJ_;|&x|Htg8tDg^xzkg32-k(`e{+Abje`3Dge<)k;GXMpVx8cSA8>#sJ z($5dW0sbB^@E;5Zc*A`q8=D!w^Z1YK>;G5Q98T!|KIJ={(EW+0&%Zh$Je<(|kxSJwaLstBgo{GLWLVRuGz$jE(wI?=H)J$WDx z4E3>;7?~iq-(^1zUUd7P_%+=B6f}-)Mr>X~P(mL8Y=|W;BNBg+V$GH8hPk;vvkCnD zS}J7#I`|0QsRwez9&puZ|Dq|{Xc^o$odWF%V)4)PC~=w6zi0+FTKvfae{>~)kv4#E`nb zmYDWm*!%yiGvq({I!7o9zZG)NxTX3-o4#55n$8f48!gm z!6Sp-|M8_efB-AeG=dVVtoVy&*e!muauL{*g|QLv)k%s3pt-r$w^#d{m*-3^JpNDo znmU(%qAdQRVS;~Yf)g1Z0-J5Fz4=tohvmiWsDq?ee|D34ILZEN0iK6o=HJ@LLoo9% zlahJ}W*&l>e^JT7A((jxX8!HO_$xggf|>u=0Uv^yhhXNPC^t9+Gyi`IfF6RG&;JIB z9)g)y-v8qf@}GQu2xhYVy%#+MGyg&W>LHlu=eIA0DhhXL*m+u(?FXbt4EPYNQMOO=M)2i znRVS;e{vUjC^P?SnL-~jGY^@We>*Y$N{@$N=HEKO-;al2=AV2F{VV3thcffOH>QU& z^FKF>;9mtw55dg8H>UqGGqdABnXEvFZyl$n(%0ge$7J z4G_K@&-sf+1`Th1i=E1`-iG)hNTwv95lAy5M^jX1;roI&(g8nqKfpym+7DGn1)N|b zv-g3VARx^N?+By}S)YV&O_6J}vHGCtuakIv)V_ReFIkLy{1;8PI6r;?Lb@_c;Tiu) zRvQi}fXbhFNRL+HC{guDp)Zxc`T}_l#)M5}Akw8t6;yhc z-U8XEfOM&$N2E9D5(J4#@6wT~0@8`}4nio>J4g{oXwoF186bpnKWCgV&UxI=c=vwC zcgFei?VtIP`(F22bIoFsB^lz`%%}0c^@ggq!tORnPi8Md^c>#Fm zDW;zAUWoxQ+48u+IGlXFMsOmVWzsvI+R3=USUgn2$O-4&r=R^uo&UZ^hG&*-avs-& z`X@NWIVJX+&`6I6&0y7n6)Q)YJv+;}5cV6zbE;O54mInW?sHKm^u2}_5uTVrHJY%l z(PEuivcp1oUnd=@JN&BZ?L%`w6M+ofBM-rUZ`QkF^qctQ7tWiYN(VI6bk04ss@k=g3+QM?4U6U* z77xsgkkH}&GQ#?1rHvN-LX%ESe8|}_Rtw~~e?~?bFy?kaqyxv4gYEZDbvL@9c!3t6 zLE{K*JkB_`+V7nA01fo?wBoxU+K7Y>+A(axyEHoCW6UWRMCb=Si_jx|{~*g+{4WC6 zJW$w>AGrd>8!iFG4TNNN&7Xks@6S(B!wc%pHWQLgu#gKPG6X&RpbN1s9tB2oTZ4bZ z0KLDkcUIg*Pm4kAB6GP|p4ngLT)Hg4+diEF7bqXT--mjh1Inn*n)A%|O>iy)98-c- zyC4E-fKg^aLraIbBT?-*BUs)AQ4Tj;EE)I~8bLrs_0S%8TO>8BKYU7k?d16+@I!i} zee*7C=7NYpW~?1(X-<-O7QZ<{U?|-AenF&;$CU!@$RJVR6EyG)vWeqoz~>PHjeu8| zc5~pdf+sN_<8hHDx~H7t?f6Hq`2j+SuINBrAzr=iCGdZ`o_1Vu8<2ZB=L4lJ09nAl zJH_<>afZQY?drYLM>#+$s9XEc{eoyyd9`ij)EgM1)0qr>9IhmAL4-0w#}i=5@IOxu z!G2 zhpB-aFspE2X+P&4s@lPEZ?W}h=Y~1ZG&fyblIs~EkvA5OHF(K-L3H}qU$lXc1q|^| zi^TEgiD^&o2MjJE1!$c7Q9=+%=Y_|%v~T6_Uk6&Vvu?hgD54?;sP`#1DTzC{hryrLr4(|aVLn=qEb|+ zp2)H<%=9xHLptJ5XoT9g&-4M_Hx=;_m7HUv0MPVRQ$TmZWUc>s4QV+3E(?f-i`a02Uv<4x}J}UWFkKlrM;KfdohU0UYZ)p{D{o?6zDk z?x7^d$)Wq%1rd;>U{w3}F;rwk2{ns0UK#{)2Y22B$(20#(n&k63H*PKz-Lm=ZuaF@ zDu;g`Fi`jL?_=2h^f6jw;O8IS;Qw>T@&7&mODTHO=TNUJ?|vJw5B%Q-uvzqRpz7=` z+sMC<_s?^7(=u)0)ju#m`@$?~mhNW~Gj_k{LUc!qnoo-H)?bEy9f(E8nHxjyI@# z!{BoBfZ67*{nbYu@)@TTx}=^wR4FfTW#JOYG30$CF$U-5();vIofb*i>au2HF*Qj6 ziX+|o;Qa7cf!gV7a%$AL4%AQY42Ommr7JvN?C^RiXixtf8O8_OvKAs_v7m>AR&_k# zdwd3Oyh@TD>!=rC8UJg`ZNhWllvmv2g5<{uw*r+a>)Nr>6}QUov^3o`E@gA4_tNzA z`MQ1e(S$0Cs%4%>UkOlQr9_V#S+0+h$_>v^{2Z-|l1x27^{QfKbaIE3t9 zjryQ|l}Z`Gdj@7a;!Z!;ub;f-7i!XOCt7-L{a{X>eR?5hCgH%#3g7hY{ElZhxspKI z6_Gpr985@Fiw7dir^g&;&*a!D!3J@z`viE|xiUKGeWlXK>>1QMVb?- z_w{2$wgsoR9B}sT+0UA^ME?jUHmgMuG(De!@sqq)Bt$+(B}C5{WCQFdKCHIVjR zIr>%K!SQWl1-eVJ45tgck8>|{lxbtbq&B;wzS|uMKc|#FR#aVI?AZiG%iZxe#tT`++(^vu}8r5YBbNZoNkMn~n`vWsO8uEwF=_VN#ZFh=Ru1yMZUILW+$B>4HI)eE98XG;VyRC7L% z2Ya;F^&F>bgWBHSyTyYzk2>YflCaQ8i|XgHH(3!EuAynj@gEF$T2CoqZJ2QeHU|3L zngz|U0EzapAryk_d$a<$-onYuFRC2jI~m@8@Y$!RylrCrYVX%S$0?MYGEs)bPb|mVr<$W zdGV-Ixfz$2N<;3%Cy@CDI(P%e%m z;DV@kYbdRQ`s$WUYR1ddV8x-`^TC_XQ?W~n{z>00HQt(cW>#F@ONBhNcq?mu&6{?8}0^R5!AUw?i;@))uuKm(nYFxrT@ z+%Gdh)Y##A)JxLCc||yqrC|77A?}Uf9!-bCdE*myb;(D&q2uYn?kPH;8sn$1!c>nO z2`OLhH(yK9Mb;&%vP})Hl#7hdY>k_=`(9;ji(&h{8x0ttZu1Y3PX?FfISHJ zc0D68r$NdE`R6WsNLBZ#W@GlQXjI(I*3xE~vJ~(vRo~Yh7NBcZTi5MspXF|&!hLin z4%Eg4l)@Cc@0prz(K$Ai4x`$JUV1X7c|;ASzpcBpKq_o^RU z>zU$dRE(P3W7255TP|-ASFYyEdlF3D;eOddo1uk;t4bSGiKK9W2Q;bMc_e&X_YfcM zP<_M<$~Z*;9PJx(y!V*oE#0nD#LnJ|pdyLtctvfifkBdfjZ1(qPyf{3GejYci~H>` zR+B}Cc~+$F%3#%nJ$+?!ucQTNo43xcF%Ah5B%Dwkz)|8{0f% z3tkUk5Ofg*p~w`CEVSE|( zu{E;gZIdF>d6zCB(}>liFhUq@H_-G94M^tZe&1D0e*ssNV^*(_J|~gUim$EjZH6bx z%nTFhm|;O)ADax@6t7=aR6aLFul@)yQORG^%^`oMGOOU6jc}vB@;cB6v(dF>n&M6^ zyB7u<&(V%33>k7j`cIQfn?Ao=X0fT?UB>Mk7gEwgngJkHO-rV6bM$l`az1FS^D9a%-1alb^ zTCNr$SyP3CN=FML^H^|p+Ed6WNP!udo{r&q3W__gE|YzR(leJF8_PC8)0w4~jAfw6 zl<8SzE_cnn_aVM8&B5RG14R%dI+Zz4H{aev|RK{&UQtixQ%2C-}kQF&<%oi5A-(!=BjrAZlS^lR5c>W#CHDS4o>(>Hab2AmT4HmqCo)hf_9ld^?6Y(M zh1h&SS41~DBypU}OMLYX!aEWV&QaGliDB1^>arc}SZj#1_x12dxjxmijG5Z%G%D5$ zawxrk)xDqNip~7ffcju z3MrBuBf3>V=X@`*yzgrszo|-H(sNC7j+$k>Bc|})N}z7oH&L1fHM(A$JT69>H@JA? z8)KJbC6fl=(C(X>msJ9iRXtw;gIF2a`y$$A!icE$-@G>v^Z(}R5G_iSgx(7xRwWQl z%?MkFe956PRTImhS2teqA=5Z+-XLJ0F56nu2QQ6Oh1N19nqrwA9$u&KN_%AmkE;q7kS!ZMDrzaaEq!7V` zu{>mYNdw0dtRSPVXmXJ}jhB8&iE3tahy}MluU-FM<%Yc#7$=zIsdk!_2`m57=*|dt z(f^uDWdq9;M%rVC1=Xkp_|pGl4#~Ftom{h3YG@ja!00Vl@e*@n


OF(k~z$vpn%D58mJYyIcIzfxRH2C#{2hKa5&Ul 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

uWp!=y9&Q*y- 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 WWMs; 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 TX7>m$AxQ0xMca~$%o}&3Bd4@(jjRvh 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/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 Ils`uQ596dA5&!@I diff --git a/assets/macos-gatekeeper-allow.jpg b/assets/macos-gatekeeper-allow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9128eb313f429805e0f5d51abb1d0453a0601d9d GIT binary patch literal 459540 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

dh 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 ~7sYoB2v{EkoqX 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

    2qS}yXz67%dPp;ec!0#0Wn;{*`YlxUOnwgnQ~ zGSh~rnv5!2Sez!Eax%$t>MKXBUB_wvCU!OyedD3@+5;Zf+kC60zw+hV1OI-T5^Isy zKky5~E4wJQ*p5!l;0TIXA*hqX}ZHw8s; zRoCexRhZ|eoU5vBdeCpPtB8FDWt?~`=S;6K5ss}cKYJ@JqWN5q_+>?srW^W- z8M8mV6B@D=1g?_+@B#b)kj%5p)n_$lw!31w1gZd>3v0`4h>ohpuB{pkkry%I*sz5| zIIFw~ANf<9&1mgU5=@5OkJV+-z*tWSaN{tj{ct5YeS`_p8iZZi^}bh>p;CN8YUb(I-dQCvKjW6P z6jRoITFJNw2JvI^avyhEaQ@q&_|Fc--#5YD3Fx!p%kLBrJYz|};tzvu$o_p;{kEwS zfkNn>uZ0rUnvF*{AH9)~mhCu>e2v_e9`_f(xQ@|H;+$IE`c90Y?1EY-XOVXXeDY8L z!)hMxI9hR_85}(^mV>Bq0@ddDi}!rp?S?wvhQ$|LbVeG5x}OUErCI=a)0+@L-3U$+ zS`>5ssby?rd8AT}=!UXB_9H@daIAIp=o(nIwrC3N-7y{G6wJ1;z*q+j__ zbYrFW{0k5BB30*}HJaUQd?Itx%gmdE`K8N(EO~9)^TRm9n=e{~@%;n|yb{*d62<<+ zg+$>bt9CG#D(D%F>ME z%yr9TcB(?-nn0Vv5%A`kHQsU=9!xho(>SQYX#o4IzSm2g%}!Q?qpqd}IEP+>4tCU- zI&P@}L10|Xv{kDWpz-7flHanKDfPkDZLMy@`h!b;LXxkDS zz`MwipmGS?TJY#U$Y@s!(`~H|nCNe7ZCx=^oQB2{PQiB{?>w}(s<*2oF#Ew)7(XJm zogE-UaO zzc*!QL{!qNg89vc>WZ?7>+Z$#S-zE<%;>y`pmVKgFLw|0DT;1L$R#}^j4$S7DqOA7 zzd(N%!Tg~BOmsHJpAmFHQx(S?5@*`DvmlpjjI>6TOvEsFcd z+-`hfTUY5U6RI$*UEulFtrX}7pT=k#i4)`4K}7NDf

    &fhy7npob$#+<;aIMocU zGn!BPo?&>RgE7bY0~FNHw~~}|v@`d)RC+#E08ep83GM_YY35cctB=4kS^CR$@7_9? z;73k?iJ%2i9M77v--s6i2=+d`ZSnk=+(o8_COR9drHKLIdU?6ne^IzmYgryaaJYyY z!m9^bx!b&t9N03FtrefE3K*3M`ib%wg7*%D?U7NW^OPm*4zJB#q7X(AorPhJkxgdB z=!78m!g;-idKT?_e9n&{{>q>=NJK1khS5oV25yo!iK8ZPzj)eWr5 z0Y{t00dmET%FXA_)cOHXalTX(zaXrK-j2m!PCuPN$JK2s1+Uqd&i-X|h7S3#I7g!G zT_EvY59;RGXM4re_Zt}co5)$8Yx$Gi!O9ix>AGZHW$t4!N+~J)@bF}w?eT^C^Q?L6 zuCIZ=%DP2Wr%R%39&Or1q}wAH{(f%@&Q3ZoI3bRMK^l8O+^~k{_XvHbfgsrl6?@u> zol;+PQSo@MSA^H2CogLe6GrMMN)->@+V!e0;sPsqB!Jh>CAC9=P8MXeV8$fQP`VeT zT;R)|ko_$G50$oB_tC~od3gPc?^5oz-2f{|)y)@aS=T_jXZkbL@0E2dC;+O+1Iv+= zp`2ES+Gr7nPUfn$sm&zbT5$OIG4;k!lXXe*iOKOk{ft~NJhM3$<@vHM$@wR)#zzoI zoz8>E{{KGWW3cHmI)2|yHpcMu2m**O*jtDPvv~L#UXO^!pi_tYSUHzG;<5K(HCS8Q z0YB*eVPEnUlKI%^7*jlBtxtW>6rZ5m=RacL=9d!|32j$u_~IFOgDOh!>Q90l68tkibp)@>SK-b*#VepCWrC^`z-iVAEXIZw zRQ1@`ZTX=#bx9vJ4H>)Oqx-!+BSgmCb!>b>Y~RJY@-MJ=5J`Z%b( z$g~0Q+P;mY@HXjnB<2!dV{mExs(YJ*5q#L|(;(|3Cymip_`AKM7K^_^s3nVfVU$0W zDLS6f-zMs0P^+;0Y+b(CoZ#>)FvTV26CH|4bf8i|yTb(zqf=h%=~UO&X6qb1NHzgj zg$e=r!%*QpQ}*u%gq!(mcjJ^OG?Ulth(&hG`R;D9WjyKWR$741l~eCI6%N?>`wP=~ z@mS{LCe%>x(9itBvdN-D4GSnu`-z>X=)g2_gkWspad-KLNO2L5BZA%$3rmk!e|S%T zCL+;xHUuUEwHguPjr{mu$4+Vy`GL_br`GPaH^rOpQa%OVrNk>`&ev018heV(Dl^#Z z?`@+QzUEtBWwSM)QnHH){&(k+z^LhDW-W`*UZu~T>wt|ef)ZFmnFW8Q1!e|z98kDR z3pBkqgsUk5Bi-&I6uh}UZV!#%nT|b@F!00AGTvW zk(ES*f`^HJI0t#;DgGHP)+_*SfjhYvkvGS()t4(QH&?OX<=k6$mu~jcrySx^#kMgH z1#C|v0kxGFZVVd|$0xj-6!g16x!?34aX}W%R;GhE<3eD$YJDu{pw*`g7vEz-`e;V| zGa5W;a^hd|hZkKAgK{kq$1bv^@0HDz-@BM;vaP1ouVi_t>sCRVyZ^E0?Z%&a&i%0F zmDe_3(e3$?@+(5@%ZEkIIGMGW9AcWU;J%MntQmpw z@Fjh)YTG_-s|;{gU3g!;hhJWbshlEaoG>QKrY;+E7=pve;OZtPfSOxAVR|)2hVj(k zo^E2$RLq}+j1v=B8hj{_!f{I{q4o5NCf*`PYk~YtV;W*b$n`#!Z1#)Nka?4Ww)z2! zdW?O3cLz)+oPO1E1*qBo4N~Xzg6fWxbK|?~b*>X43;Ku~8eT#<5lr;@M0);GO zM$Ri08Uqo48N0=z9$YAX-J;$1=w-+K5~Fh!F}%M5N#Psf73v;(pP=gYPWGL~^*MS2 z?U%nFw@x<l!`%)Zj6j?cMJ=fV*qKtH?oIA8FdGx{Wt~*6)%CiTZFMoBknSC3o zB}<*v?*v;G#AP+k0xW8-3&finssQ)rX6!P{I0fdz{L__)#YtGErQZ35n0CCJPPZ#H zjnHPCr+vQq?T3l(@{u0YDem_dVVDF$P0X@0sNt%Y*8=*tVK`eLfaPlLU9}7XK#_+Q zg2NO;QcKa{vdg!PlyWmm@1Difj`%!wp6s9fcKYeJi%UEA!L{rLc2xSeZwi=G()cqt zBW^|`*J#Eon(BpN>UI%ozo)XpGZ}TuYV3`j>I|lRa+Egw+Bj|x8;&fB%VhYtod9@JmBYy0+2)Iz(el~BfXtwEri z&-z?!Npcn9eEYP!^3QUcBD3nS`1~rjJI`L6tR{)1vd)>EHE_507w3$G^c*~b5kBNr zeY!$L+S1Ybc|zjl`dQ`VucR1I5Hz>^lxx_2Z5R&`^iid7&V@jBdr4>3f!YE9g7`EL zggllIw=8B)9`5@vPukx(Ozv#XkErr&$vS+$Ha_H*HKBd#{nT@NZQ=By`o1UiD2CPP1XHw%wQV$}DGwe|OTp35 z+_EZ{Hr>4x4le!NABHD!=3sQm4zvY4^IU03ZfmHbr!c9pfkX+EPME<~BjW3Go4Ud2 zZ3JTEZk&2DFY%An(U!+c+p9BH%ZIu7?E;|zPBvJjxR6dA*s*_`IF8X&*8bXIl17u7 z;TyKdZnuSo4{qKyx9fESrhEdLmlz@EdoF)8HUCm^;_O<@DHEb4X5`M-VHxU}&AWvr z^9mVfP1QT4<)u9)%=@RN^Lo8$wW+qFVRnz3_9;kSVW%5!Xa+$ZOiy0SDt+rm>V>;4 zPxLV92-4giu(&*J5pxxELS%60YQ8YgN#Z;IFjU35+~R=a6B+b6U_9+Bvqrmp5p)n7 z@AQ|?YT#ArFWAi7scCA=Qp=GJ+|JSMX_Oo8TjEeH_bzY5iuCX|1?Si-Dr&w})pq4y0nH*?wYde*8T- zUIk_+^5G0Q-adm(Tb4A`fyMfd1jSa}NO|kyex|~*a`FD@sG?IPTAeCZ!sh1XC8~~T zK6)2YidOC4x}=(vMD=>x@g8M6CTayK9n!1CX4R3)6xlxu_A7m7&vKNj0{)hQ!D~kB z)ov|u-KT=%I9+Z`2S&VeU#6p;WaPU31cKK!C@mpPf}Y-YOXK2K>73IUf2=d;%qux( zLLE%(FY#>wEw0zJV?45zAO#LDYgyFz47&dBbueUxl67^WFY-ILy>uj^TruGe?N^HG z{dhr_MS_`9ywjj(ijL6EKm0wMUvV5HPkpC(6|aijW*5KW+R%7Cj3#OBBAc?*%~};6 z$K4DUvgd5{6=q!M_4POqH?86BFbws*@yp_DU2DmtPeWqbeUVzLqXY=pe!6C;$=3yl z+eh)=vmB$>kxWY0X3GH}8-yFU@S=C}@u!@A*I*Oc5gFMeoq}n!mi+u0lo;z((?EWo zJK2L9ON%kea*4||ErPacDt3TY)~KSGsN0G)Qg=1Xx}Y8^>bE3vPu~}JK5Ju_ckWeu zA+1a=q%^lD$V9W`l>XZV^NZFwPHmP)zKWl^a!P#m+mLJnt|mRB{u8J%phiJX7`ROL zOM|1Yxu<-1HQ31;7^HQ(X$-Ds_x2rZgID#c>s@1zrS6AE=x^Wh_DC8UZ&0s(QEv9R z0^!(3iqo3{p+Ta2`vFi66Jfz3J>PJV6X_6KsU6f(;dDh+ET{>M%x5LNugTU&FQ(;A z6x63!YmQFee+L&O3=jatk_2tI?TkuC@~C6COw6bu$Yx@Br>(b622*%_ZC2EKixmmaPd<2oYMQZ7RZcfP zd(N8XhDgdEF3eP~LObJiJGaFI^glQ#=EZTrcY(!e%& zB5d`-X>mSuYOceC1+=MSK~mZ|62s2gIWjPDY>I5zy$>I5S@CRGDdi`pDNiDL-xfv;58VHV9rc z+mw=XaklA*$8+(Pk4wykY@UHMwacR=!Kn!oBYf58ELg{U({3-WgUB8r9}~SPI46Io zhFceQ^=B@a2Fz+nGuE6&D+?{s(A>8oHe{!mod0p_i=T35ClDuGd6FxJX1}R zn$=mcj@`vj0S^cjV0uVM2c$l^b1DiE(;)($B--jYos{S6eF~p5w!kReC{`2>R_xQS z%~ZElN}u?-RW!t)xw;ywcX?i`eARiRd(;Bjc|YJ_WV?xQ&nY*Zw1vK*6yD{_|H}Tn zlVS-~%9Ai!$x-o4}q>A9~C4hY@m2Gkn5sBYv?|6@a9 zGVtH7w>E=bDOX|r8-l>P;p*z8Mo^dv3WR|#3uCr-TT94z!iq9+V3ol&~ItVi@^CVE?b)lSkLruN24F4m>q{Gq+Z9XaCbL2k9-N11|DV zAh`!*CuO$fU)$Wt#ve##Tv4Uy4(^yoK+h?oE8`Zg>=u z>mLn1N}DCt_39!xc?0di8(Mq9HM2(5)@}bjCRnN!P6>iR|FnG%D&^RvvGJWpRzG0| zTaIQQ|J#NAZ`YIpA3a(i`GrJK&LY>j=Ju{#E>Zl+GvC7OFv=Xxj6o7j|JxPLk=`Nni`!U+9D5ht|0A&b?>w@3#)lfJ(}3(n|DB?_ zZll_79hv7jt}l_pH~x%l^gn|)b{ZedGu?57+`0O}#O90MH7 zlivKd>z_rdO|SoU4K`*qBNHt`7QjzU9ia_E6HRVVV{8Via|hS<1e?CQ_~Ctfr@G$} zi<8f1u|FLg6TWs_qHJ!>hJVDq9t@$G{wR7HW_ z5bD0FU7p)pHS4OwwiV`;?s`G0eV4zAMW%wEVWNt&yqd=NAwz@Ol5UWi+&jBM|7p%0 zJ~jO_w(uFJ*vi1%|A+gYN>X=OYcJMKS?&ql)#_JZJ>+h+S!@5EmWce8EqOKUb4$@k zZ05I+MCgA4AMM|P1}n>L@Q@ygx3|I|m1SswfS=&NFSzT$308|6Dd zbXhra5fMc%D;w_#i3nB6_BY55(f2*{?%aOUnM85-q_e{yXu$5K{hb35;?rJlT|3^I z$vpc02j^gzqZ&XfvK_T8ILsVhNIVi)7BEWwHa%OxaELfy+;$4uZd+!n`1SnA5wd%a zP36@p^T8qb%3-c(y9l&Bd*(pK$}mZH^?+FNRb z+9gI&Rl8y)X{iwsC4wY$j2yysZsIbE8W{~cqw96JBZm;8%`dV3ZIgi6_*lhV%AY$e1z^v1}8{_K{FjlvcBYPd& zxcaJ3dB)~+AP5FfFkiJ9~K%GIEiSyBucft5TD zki)H=DdhuW39h-l>240)6w`R70QgAP&r1OJr;n{>ZUGwSnq|}2-JE^d=0ObKdN^NB zy1u}8uY_FU+cxI@zh&>I1XAdg9^uvvs{kDL2XmZjQTlGVI$(Em9&Ohj2Kg}n5H*n` z)^q*nq_U))G#4GO#z z{^skbWD-4u1|~Q5XfiKQBzq-O9TB|xio^hM$CdR1>Wr=yQeQL%U?AO8c?WDJaT{8@ z>j{*g%s^SHwd}k@23xBVb}(qn%fo*egYJQd1ErW+M;^;)+7U(ffN*L61wTo>Gt#&b zAcR6qJ;>>uA0{!f-|JLedw2ETC1r1a&`rIlq?`@wZ++drD>%quM2!aC3Hji&foMNc z;92=b+<+m7Qj_T#0N0s@Y}fv3@W#)_+T9&*Qsm%S68_L+X{Z;ITH5iT!r<#mW90x= z`_)7(UCUb=BU#R-d8Cr5R&--?p7PLS?pW1SGWk7V-P&vP?I zjqf3TAWf!0e%7a9N=U-{NCc;$3`}*ZO9`q-h%HO_j#*1krt4K~YBu`2ivL;&^i~)v z9|^4Z43W;6(Rle%&Lmz3FhdaRItzVAg~w50v?Ev)e$$$bcC->qG!VCFY}j(pLZwy(-R&uTR1D0W-7FzVoZ&aeowN^6lHTzR643wD*p zNPxen}@H&71|AFX|@w!Ep^l3x*95c{&f%dhaWg^%oJ=t%ZA zYG%U>Ye-B(`t-v4wy@cu=GMU4nobBDh2BFZcd7Vwvo>+@^q0{kD5zxMo&Hd=QSC4y zcyaXO!77-C1!4J3oyrhZ6<+g9{^tR{ozNXMsgiLiS;Y;@Q&dlyU+v7R;wWu%I)lT_88Q{ba`ja%hjP9F}BfOb*O>i}(ODCy&MR8R2O`7_)t4 zJv%aK-c`Y6!%5&>2~;|3YZt3)tUcjh^nwTA7Ia^&Xzf&Qz;r2kS@Q2JWIv+1;zry& zH`g%VjC#Eq&`*{0l_`pZ$N|Z;h&=1w?vpSR5WBkMMVQ)TEi5vWBH4FV2QAe`u1T%H zty>n4l=f-xbq37+5%Cxp5_N3^c@@F?3`J*|YIH4fP%gzA-Ng?W(*eTeWF|W;r%PP3 zC7>J{YB8mPuF)7hNjMPD$_w02#!MHbP<@|ks>F>-IFFK1gUC5zzZZL#6#Wb>3*Rk5 z*QGtEq`N^G6g3(z+iId%FgGpq-8ePbH&ZyH<--i`!0Loj!r@B^YQ?MaTn%$2a|#S;zXD<36X8ZJ1inP=bEm$q%2%UynQz zs(n=IOAK}m-gDy?hYYq}SxwY-T)B;R3>ASIBo1(~&cg?B2cL(ern~VPQ(Zb>#6{9i zH|>lhbv6)dbNY=TSWw?)h3JH)8=I8qEq$yuf?)p~!CPbTT&LkZ zPD|$~b#Fu2MFN>6OC01NfPGRoY5q!Oje}$1+M-d}lK#uv#}iHoJepOx%_+H^nspOx zt};TO+G`=B@L9LIT2GZ(^P|Y-BkKAx#n7BGxV1RRsiYA1+3(Y9gNy3rUGrat^ zs(Lrq50`Z)2;%LBX|0A~zirFvekY94bcwt)#0uO~K^PR+pON ztg6{X9MLhQ94E!OoDxS;2uEx&&l92o)%26LUa<-bm?{uSi!~cACYCxfQRd@MQrld; z-m0wAv5Oly&GSCX{~o)o{kfrUaeNKNKdUcFdEiXrq6|9H;t9y-j^~^y8P5Q}4|Jju zz`Wyf^Y@yIMkUetD|)K@&mLRWw?X+cw}pOY>X26d@aR=&MVDhCa~AlBgbX|!`aZ8px#UP{eL6lF~;^MqT`TDN&hpxPUm{t|| zD38$^d3IR{X3 zAHG{@_2jjT08vI72Xcf710vgEycTvRwcC%@s7g|-VUFSwUX{@8C+If#^Brtew_Bny z%ZX?ivUNt&aVln38Bpd7>@GzCf9^E3nfm!mXu~mh;ii6f;SxLT4|#r2LUjr*zK+AX z(ss;%)g&ym3hH|o^XWN|Q%DFj96lPcd45==>$7)_(Zy!5hiT6we8cCGE}EXEPFm7C zz}=E`wt#Z@d`_cLoiM`B@?Q@cSKtfwSEfLfIp;#r@qjWgRt3v*Z7-w*2(;J|Gj2Wp zhlcqzOkm}GO5f}FA1}Jmv<4!ri*52D&1}W`9PI&s*lypO(0BPy4;658GHmqL5z*Mv zEO4L1xoQC>9;>&du~SNHlV1H$muw)TGMJZxyBo2DC#lIHI9MeUw%(M?RW1+)sJM40hh${ z)8+clYgHl@vb!5T9?I(R>S)&#>H(Ew=r^daL7QCigOP+Szk z;jiXymZP?z`#7SJ7V44-zJM8>DK56IG=Ey-`r^#U?5~|c6ir$#S)X_1-54)jh*DNd z(ejwQFdDuxYHB>WS8HcezqAzACO)N`g$<7lV~hR4#hx^OHkZ=L@T)rm zg$RwF=Bc_e44A&tsSf~8XM7p2HQq6_tfwKcNPHp8d|?BJj>B0>nnyquOaJ&C6*N{h ztW=4n8fb3ZFtSjXH;E`;A4$^ZpctFzP(AFP(o(sr2W`SWhWWZWc9g%lTddT0F`w|u zP3JRSQN7h$yVGm;0ePO9#^Ca5A_MvWh7?S7#9^Jhs#2hqbp6NUGve~&<-La)H&BAV zY;9KULzrg??91oQY1nb!DpyQHoH0BPA3ZAX+et2vqk#y66fW#ok|X>A#R`qL=G^p@ z{8T@sffV}f=mq&m?yB^y7G>*HF>pLz_eur!R>sBl+RNKQuW&F;#>*YU%P>V6f-p`n zQ%8!@U1?u{8?#Fi>bTvfoW1(CbWRk_ORKi)_yb>rfQ!@xzmM>DrhOhc&8?p*v*6!7 z)s-Ahb|c236JfFVP2>rf8S+-U#WDx9{Wc&XQ-y&LO}QQ*=O%DC2PdUC@;S5Lw<({H z8KZX`Dmuz~7)dzTdPd+dB0ze)fPwF9heeT}JlVo_2BWNnH)#bd*zmC-5dKXkTz8u4 zC5mz)HJzGTwP~}&NdFfnQF;IQn6Xvt2UqjGLl*fHR}8HgFKk7uZN<^^wXk8&LDHZ zi37f3s_c(lOLd*o9!Z`_91Ai3R{P5~A@C)Z_1TNezSTc>^X@Sd6diTKNAd49ZlL#L^4So$+n;_A>Tm`YUHeh9Viwy+!zx7c>?{9pyt=W zNb@cqG<*~n^ojzaKBU156uR=3i3bUWytKHxQ}cJ8$&#l~+;mgJ_|QP#$0|63o9azT z?mg1GZlbvm&er*BvC+nU_nqZ^6b z22{BSAKEg2?>G9?c@D9oIbZpN8vwq*jY>|<1u;#PKRx*v9}qs`5o^>(v~-FAQ*qsk z2jsiVMKkUF1C8uGl1Q$ALi#U472QzwWNH;Id!{rX5-jujz%nW-=n6;l%fb7#Tyz-f zbNw5oNrk?#b9wik{9yNx^Z#@wLZnWV-^xna)n4&dzOe)(fifKsrR;TNgN0dR#e80S zt1)>9>{-dXN?}ptw}V`(pY(}Jef=R?TTgymm?lrl@Jp8pO@+jP&E)-B#ighQH0$zp zRFi6op)CC#!13#lA@85H8-JQ*oqK++OU=^NpX*wuKSz}1~fH}@PVp55)$O_y2gsdO{O3a>+oEAdt1Z8ne_O6fMc#p zTU#>xn{RVV3y=AR+ZH(ypNIZZBxmlPdV&F%it%!~vt z=UO^peLI(OzC&`I{FaWQYBd5Z!!eloFGnB`+9@%+8MyE6Q@O zr9RCGi*r}50JkhD#LD4cXMxin&PMF^mwM&RRCCK^AcP8jWBnxQ%Jt%wdiqU91Dx023!??#7Bsh>wvuHQCTHy=7|uQwcB~hJ zucnpl*iXaU2-@mHs_4gEOBx=x2HdnuI<9rNo=F){exG3JR#Pif{-}g%{_RFq<3VIz z!>s9}qh$4&_R=-qB3DdZzPi)y)Jm5Wa|pqqYSGh@Jd`3(htRRNcF#R9>rTk(xO(HC_p@AY2kX8HYJu>@6AAk#spmsNX|UJGCAYC zgnujB0yc00253?&Ij#?J&d`9yYfj6i{2?Hc4V4yXh`ZnG45h(G4yZ(c`&vp%#~1_Q zP-Q$1;Rl5A|`!mvu3?Z4wHjh9sXTn^EEHyec;#Uj}$*M zCnQ<-te*kyli_3WM_S3Nl!sjcjpHQDqN9cOV`6Gwi-=F>OiP)owA})0p!!HFbBJ$O zJ>=_yN6?pK_+J*rQ~f&y7smnMC%Hh4%rRQNwT}N2Kry`;Fb8?~9l%NuT;Y$V=;HWg z_KoQGXq9>bmlQzY7h=)(f74$1;R%@j=TfQOIr zZb{CWepdb9AELb4$f#cH!?kS$L_UG$B%dA$6t5A9z5+PxevT=ZKQ=R7teDfxo)5kI z%l3Dx!RB0Z&XIv#g9mQ}Jje1NX1yC-9uN5GPTjUK=41K)^Rn~WMqv+A!Zn!dbkLiM z7^4}`E5KEjDR_A09fL?iT)qohqJzc``St$KM>c!Rveq>~ST&L{G7dM6+%QB<&F-54 zoo^;B3xKJYw-QVI1bGi}iM=}_wC>N7$&z8K0|CG3-X(hSK)xeCGR5u=26kG$Ox@h# z7LDn+Tycc3^J+qo|5Z%}c>^c!AYS<^4cWIOGER>v>5;-qwUcvdRT8UfKWTFqkkF~> zL$WtpaU7R!e~`YB$Ef~&oDX>3LUVJzzdyX{`$j3q+M&hokA1ON1izcFJb|n(Z(0@Z z>Z*nKvqOCkMKIeLyfpNUVx#$02?^g0ok3^q0jJ{$FsqK?NyJdp$M?#LNgk=wEf8Qha!vyUfgabPez7Rt7a$_Bfb6|L_ zg~Mbv-)Yl^u(T+D47vS(*R~y0Guhse=eHz40M;FZTRwoxk_$RllwF~-=X2rQQkrH32F(#UjL91PxGU0K&MxEFy=I z*1&==2iQhBr^&A!%I#J-ylb8BAO z`VYvN!l~k7b#_tB6?BxE`j_wnT>= zUS-e^SP`)lD>?7oEzUIid`ehn$B7?0Jlfwhk=f|KX_B>3&{hrJuzwI4Bkwv?KI_pG z+2~ndx^<2;e(X-aJcaO4oy(=N0W+KoZI*!U1wD^+-ASVdQl$EqM5kcFa_KNg)pCHA zhzhC%8eoSYaj0Pnc;osCu2|^l)n6G>DSS*Y^Qjl)fz2Ol1NV&?;xTH@q9+n?>SCk2 zih!QUOf9>%kWtBi?K4E;H_obFUKuF+Za`z2clqF1o4wU-)cbkw8;lSC|LQSMt52hk;5HA{< z0QjQU7Bu?S{h%9xNtdV9j~NDzdVG^xwWKTKTVeCgf?MBzH|K@v>f+RJv58x|X1ol* znS!QdO6QXsi1tkiv-Bl)c0^mJ(iI6@=b+tI6L5a|rSV#rT`9C`SRCZ}Y4-l4KI`LL z+#>0{;lCn>_A_A>YaJdU2Uc$L^;)BOpdQGJTtvfKhT-)9NeOil8xfJfy!6|X?>orX z4aK%$T}La%9u?utq6kUh<2UB#u->N+w2Y-2V;)%uG-qxr&X1f|G4y~wbb+;G*ymmn z8s>k=b9VgKt4{)_`Ngtc>cx5RHGD;i``M2KJ3Pn*RoSGwi))CmWoG-bYZ+(=8V`gm zYbZ{xS$Y+yjBEgA~Hcjl!+xXlF3s`pziu-^{Wi~|%r97K%@JA(EHcQ_R)09v;&>{9gKr*pD#$se<8=52ew(*xD{Nq^ z8k#S>TDW)~?a5a$X&bK)h8M}KbPRr;VwVpd4)@pKdtK@n^1591-TRV-cY*JDv&}T* zTU+?g%gN5C0C&wzt%fvP8|3YhoW$9g?v`?r_k&}L1^5q51pgX_QKYJDnZAr90pSX_ zi`yC?Tv&WW<0o<^)EmaFQT;oI?5fWL4J?YflCl4C`D&|}YGSuP8d)HKK!*A)UsNe> zWt2CKTHmIvMAv?m2?M4~3C|dFS1Jc)L{S+k;oHr6^;=*8HNzS5B*net2ax;38~P`1|*5(brU; zF9$Whf9cp4UhO!0X29M%?zN*RCs9d*wW^7{vk+NoUb-fNV3*sG&TE|&b5t{-@J~9( zxWw#QF$-dVj`@(aPk4DU1Uh&pBf3x7-y7+xnk%Pp*{X z9u(L9&PAH4)u%H)O=M)6#`1*?Lxt~`okxuQb42=#tdim<;a}{z*P@Z6&0|-8MAKt0 zObMWk(6B(@PAcjdXw#0(zQd#nH3r^2c$5dz_1Q_-v*uc7@^nu=Y)sJ(+EP^)CaLx+ zE@7WssYbqB(vdplT2zFpI-<#n#nEd zd2;OmJ1>w%=-jVfcp_BHTDRlbiThQzoZ=#?Rbdo=yx1nz)LnI~N89+(7 zt2IQwX++so3u~55|9qzoU3ge*mDTSQT&@uGS{%IFd)Le^*vc_ArA5)PVLo&V*flaQ zC?DU0PZ#iE$bti?)EfbwXPTyF0qyY%EugrPou0z9VrrxgldJrXe4hcySTs!gk1}PA z4qQ*yvC^1yb2ZzStz}u7;-G{wEtD{NYS8n{>^|rMt-BYOU)Kn5&3@0?x@<%{mit`F z!EMhFZd_>7#wwo8uvZqVptN6RR8ey2+@L#TQ{)=puEIsT>a-PFNcT+p(a(iN@X?BP zdY7aN{@6cPOjj7(YHJ!F&W(N@`kCs;v{-O2srYUryTbLWn`C*c0P|sFbSe9HpJi(K zUG!theQa)-+S_E@poCj<(%#+dyurqSz_vA*@+^Z}EZN6jDQV8;ZUWs zvp6+g$06G*K*8$Z#Y3XJ`Ma)4i|i$)?{~nE46#y;YC#NkC%O&-96?D@*MlCoY|44S zISs#a3h443iwQK+XJ;Zsr5(?8;dk3i<*6#d>V(^`#*Q;xa}bcwp_3P*5UxxsUuk*P z?VBFij|ilJ$n$+iJ83Xs{N+Mg$o#QnA6zEAXa>VuhXV^RrKb$%MB7p=?VLLsninif zo6hA?=9IPx33|zaK~gQb685_{uNbM^lp4u?E%o!H$&3!mtA6Q-7B!(h%>$mdZ%S@y zKMYkwFWsgpMx}L5hnNN{&h|b$)D+0f5LNwrx19aX6B~QK@fQzLUWiF~gp>e=0I!RM z*R=<1%}VA5-GtL?<#per-C3xN(JZ$u4oZ9PR{m%aFMo9i9gm51{q!O3%NpP*TfhN- z3Dsg=md{KO2LYLtZ;DCgUmVyRg^GX3mE>mK&K=Zr$DVAmt&n)61q_-_G zO$ncJ=5}DY<-;WxlUK_5Ur!1}z%*AL)D+q*h^x|d$rV2Lwx1F!NN;E%Kbpc){6y}*l#n4)gFTo5cs?Pxt&Ke+7 zfZZfm^UxARM@XlzYOYWxfR2A5M3; zd|`8*eDliTeAVzui+9;gIR&J}e548kg$RP+-ch|Ms%dm5awOp!y2u~F?VGnVICHd( zDuON>Q?mE_`({Hn;8(uGe9Jc5tct5KqoZGooio4tr_5@d2%=gl7(mjOo0myC$N$nT z$kzR+R0MAwkl@Fa&=T#lh9wFl<2xm>OYFI(Q?=nSCqB?jO~hp!;ku^gq6m4y>pDU@j3Q~PYNKKFk>V{AVvHEaEhTH zPStfFfVd~bGjZvTcScAoQj2~i1nS+v_w;wYsqe2reBX<}KfSRPZ;`x!SU7>!PcEAz z{6aqMa0jf{GL|k*b+A%w)e4~yd!W*xdU0LF64e#pcl)tdn3nwI-6=YN*fDY0eCh#9 zKgU9_-oTO^U#Qx7TRE`F&2*czx9;0#&W&dGd>Js|GB`!Y)$h&>oLilUia4FzhKVAVdB0QO#Njx{$fWVEc2(;w!(qx zrgqL@zW=u8_;g4pN(&$e=UcSEBWlW(apUch%}gcWPDR5^Xr{-8%)Vtni1DyH<)BTX zkvy_8fWb>yTLUl)}=3#q4EaczY+kuXe%~~IB zQ=EEo{<5^7NPgW-Fa)L;<~0@6W$1=mbasQT*^&?9_MNxFj^|mMhQb!LdU2@4I=Z-b zS#9=cpC(l^@3iAgf@1;hYMsz9{8m#_YkSs6^YfUu=G$x#-jPc-Y5DbMFGu}K`Ye6Y z|EuQiVS)Jo>H2+kZ`PM>S=n<#3p(uAU&Rfc?eLG{w0MytKX@j-GO6D}SYbb4yx6hR z6-xhibGFDMfqEY#vM!Dka*qc!VBB`e7;vJ|NLGcB4RPc zu@UX24gW>)P`n6(@c@D^H`sMmWGBJ!9L@hv53p0&8!v()?ih-Y8H^+U$Ous2gnBz0 zpQ5YVj@-YH)-&CYAvh4=s^8?8d+fA{x|nl7+)j-wT?TjQ8uE~h z+veSWw|S2@&4KWO81hd;t~YMrzWKw@-#o_G4Z|oynm(1xOUy5lyi88I7wsm1+(k{O z$66DpDDt4^_`Wy~1v8=u;(}RsHaAbG1R=BTpDhOSV@IRPw$DhYD!E_N!y*EZa|1-G zE2EGpN56pVII z{v%7rGKqMus&5XziPheV3Qxk;%)={{Lep;#zS6~1*-^(sY-?@5nOi9rOe+Xg41}VZ z(!j%M3i3l`B_9~6PeQue&&Ztf{gRUdY}JnKVNtU%4oVDuppy71bY%=F$?HYr>bI0) za-&&q0So}Z9&6u4|CYx2Frj^eiyCyK&P0bnOzH0uZb2#{Q{ehuVtn^X6{rOgM+f2x zMU8318|z)-d3N&=B2L0E98v~|eKJJj#Q(flhB$*kW_ec>z0JfUB( z$od(5qf1h;Uw3a~h5W}}(WpQpcr$B0=7PP3oP+76e4#$ZMiA1YN?7f+O8*yNZpFV0=*ls&PAYRC%g-TfIGG>e7%97^7U zV_UpVbQn;MdqEcwev+wh86evQ8J{DZcG!lURtxJkro*WiyHPY)C;WP-;ijmJ)J{1V?BZ5G`7( zp`V^!3w>{+jQPqYIttO3zq-gf!uP7Cn(=#X>YT*J?K=6D-`3YMyyb@-3KMfg(xiz) zR{S!;)35nk-&cL=F&k<(kvBHh@d~#!Pc6!{c|C20iD@;_uXmd>k#BdA-Kmb2Ec}k> zaRUBx0gw}>(gOSqy50Y%AXw-QcKRvoYa`#LFc!+X_t`56jD<+z0@J|2;H}vUa z_Y!yR8f5O6+DQA$a;yqP_QB<8ij6Rs24+p)SWKMw^g&6O{P;+ax5=w^hz?@GZOJTi4VCXH> z78Z{sa9BG{ev9iSwZz>OD89oJ+o4?f@~i#)rGLhDJ92XG8i`Hd7L55mzbVNSmNEP1{I;@%0tbfzEe6`%r^;Ldd9%KnH zr>@k$BgbzWuCu;7%rQFDW5xa672M_4Um8Gl4LcSj0^kc56~p*ISA4L7Oa<|qCebgK zLc6eox>|`JfaD2vMRCDCU5}O@dvoeJ0Kqc8X=FrM^V>{6mn-GEXGE>wCVc& ziC>{h?D|zO9X3#M5&{uoS&iy3AY;dyUzE-3|nwp;2>AM!~ zVHc`&_e2Hk0kjmKcRbg|pgcew@6>`@Q)@f4GMMBz);FOXXPI_Ca)e zHC!-aPxN-QSa?srfz6!9jMhV>=z{#!cNu)`R~4Qi**>NBUAzHaG9GG)DQW%`5QeWi zeM2<(=Bcyy%C3bxdH#7Vqr+xqw6w~#DSHXmGIJF9TL9t{B1Uqr-!iKrsa?Co?%NH! z>*&H=dVW=1p=L$x3U7YlyO*UOpJZx1NSoBJ)bG|BK`2IyseyM7ht0;Bbv`8QtrI6{+jM9UV4xi>LOj&=rS75pU z!2Dtk4>Fe5_9Ta2!%D9GJPE{qxuNlg8Ph5g9=tJLF;s>Sx&^fl@!EZqDRG+)h)zge ze(Nu|#9+4p@oQo)z%$RAC#$0XEE8Qe@+ z8F0&DS!Qqs&+fT6MlpRRX;pxF1n4n=*n?+EY>1UXv?UOi^-7IRjx!}Y%gj!rd=b&UFDBUshBwH}TZk_f(5*dU!_Ed3eR|HCd z@;)i_c{UWow>KRB>=6it zVr%uZ=^X%C|9^7J(SyI*_YrURP8jpqvJ|CU{r~K$)3f`_g4{&bAdch}>;K0i|Nm?= z*y!K+t`?%|{3cDH*n&v$YMt>n3i3s6jKY|WdlQ`&a& z=`R^QRhWYdJACwP#8`n|m!@46b{F{Q|KmCbUa?MMf@t2gg9PhD&l?oE=a?_Og;OxC z>ZNtHu?wGG;+ELViZYId=1lW_qH_g8>umj`Z`m{J=)kn{yoYW@9{jO~$bfTCc1f?B zHORy4@E8oaJFN)$^gz%R_5>Zj-^%aAJ?}OTz1Mg>3cc`@ix|$o57QVFbL!=Hu#)^n zd2>EAbgEar>M-`vQeE?@i&cO!yc+Gd9^+hgsBm)wE>Bn+pY4H&k!^cvC_L4u51MWI z!@F@XF~_5ic3%2W>KVbk0eWYX%be#IYWhJsRH5O8qZEfLE6^a{N1OvamYgf6zH0E0 z?Dfr+au`4!0I``&7F7H4yko$X`&)jXf|VId&mgxABYOmieR4SXdoibfT&_rdEq7N2iKD&(75_KZF}Pc;LztQ<}UEF+lnVAk#Y=3Ud;P<%ETe z(ISOMNgKhDysNe&r5W+E8%F2sbVkUNc;@BOuZP#>Abi2oVq$!kl{|eNBP-f=GC<@z zk~qNLcwPg<={Ex`+2tW!#?$(z#QtoO=j$TGje49;9Y1!zQ5l(f6BxQvaC|+P`(~f% zy9Gc^H|xq=S{3co0#fVlgx=H-by;>^ax+UXyYlM6r{>m{>}w9$GAYJC6;&lCeLwHe z6wd)(LQ-4NPJKz?pg0K`)WbVR^O~(It!wxB9hduE!)xrEtHsBp3oZdU0HP0Z`r z8vEUS=(z*xV@9PqTkM1WE?zPO8HiCd)-^?T=tMql8)~^=%Nm9jg$5Dy=BsZzpUs%w z+1_gvJ~;+fk9oPI+GEH@R5EZM>D|88YY*$1a53?Abxs;zrFrXA7-^0aHQ8i<&ZJh# z?pO0a4E>2}k^(C63AHEP~n@F>^K zhC-5VxN8XAG|@Kuwno9k#6(prTE|v+EP5?1wC1etQVDL=)yd~sh79_4q#IgUPVVYm zXJMx}R0B&#B9hKM*ryvguYsC79{dhXn1&7ACp-1=a=xuqV9#!u>~JcdFGE&*`}rb8 z+0LB2$9*N}on1DrTdGSACPb?xKXBF+>{mSj&FXR5+LdVx(1pEP$04(s4VIEFabx5A z6?{KM^JSU4%a@R9m!8qQ_<-bey10(gv2;H^c*FhUS9#<64Y=HhL=`>G4R50SZz9kb zIhoq3x0w#0@# zWAK}+kF&TyPnR7@(g+{&Y4}!>e4u!~We+@tY_w_@&)Z zKWipC^;_S(mUVFX1zxy!GR1d45kQ6EG{AleATvM+To>Ku4{^xeF+r%);9Wk;*|P%y zl^v4R12t#y5P;3#X4iJ-g?yQG4ck9&nE#?a9Q|c6@>(ot0hhpmoq7YaF!1pu<}i&! zwsQKYWHjgtJndhWIDLlUz0pr4IG=B&r@xFIe^Ci^c*81n32`BJCRbBXOH=EmSzwee z(6UZ94cofKyo-_$UdiT?m$of7*@$wq<+{@S0iNaJag`#-`9wuvqId03uEF7cCvLi7 zqdu?d*HRhVwVnZaMxN_q67wN*Na9-mG`X=~lJyTf+GQ-E>#~Kg52z5`S4nmb!R91N?qr&4}aRGCj&@gAqfOxY^OjlG)CjF8a%| zJ1f-fM$fpWcJjnw?&M@f0nu>bKC0E)fu4ZK)j6^^4^c})6cpofx{Hus`D{Bgr^Pho2m9x{Qb#};)K-$;-&3NQ z{FS$>0v&}=eTKlxG~2Nsq#WoLq`q5w`*qb?gYglxQVZ;+`QbQ(2w)(^9Pn!F?jK7B zvB6v_X#sdSl9phSs@$h6%Z5opA~tWJx_q5_RF+FHFuj$s?WP@W!Qt6!Myf4rezv!Z zl!5EJGM7CKIvMZ&vIKZN^VB3Wy3;$1k3Hx>WPAUZD9nCJqq%-MY?;T;P;Rorp(4st zym`X2_fVsH^K9By*W~5N;!lc!fzo&T&(r6n@LLgb$|F+^bN7PxL+C{X-eaj{8;>S4 ze4h3@_KoCJRh!rczou|nwKSq_rm5yyDb^cL<}xHQ6gulIxNU{rKm0gTEF&iEDS#2O zbsztkm+vxRkv+_P*KT8;ajKn$B`l{)lFiY$RBAqDvHS(Z@WwK89;gNyS`?G)6*QZl zS)giShdkZ@bf@Lxqt|%)DuXprgRddSI-=sKHOW(a_-CSno>MDZod~2EMrb z@qpA`27Pdge6V#)v8G0UVw5n|e>j7i>uGMi1(z6kVe=M}$$8?8rYFkv0-kcH-e~q7 zqD}Oy=AYNzdW~>S8id+0)nRbV12bl%63GVfGr1HtV1)u#u_Ug}`U13Z3lug&pjp1K zAHP2--O`@0vz034)m428+xgOU=;`1*oCnck37oXjjQB2jIFMxbB5cvfiWVPUXH*Qc zc;{BI>_G-4z&t_=-D!hZkw=+r?wgUmgmdHhrP<;62A6Y4k7C?%n$kcM#1Qt0bbZS^ zIqKx;oClscJbBe7e8t%hLeeq4yAR-*&LS{7SkyUqKd3P= z0j@}gcX`NBD;VYBs_f4FE29#NAAsf!ftSPr6Xj+e5Ev#SVx3sibj((L+~2I5zIR|Z z@3N6g8IncqEQ@sej6DaCWGzxP&>|O2>qkj`-VwE+U_XPnHN4W6kP_Gx+Gr*0 za&w~V|n45QIyk_b$zk;~B z{jHzdK@Z__G+!)JnIh4Mw*ZDH=$APQAA?_Ag{3YVpn&IoK5y@{a>OsTEp6N#3XcIt z2VPlV03Twd2EoHrrOg9^?|AYi-ufv}36?LyzsU7n0}O8gg5s~W7mp^wJ?w7hS^`x9 zFV>BTJJvFr+~W|K#~#HuxyH{@`yvHr>zi{XjRGJ)r}1frk2atRd>_MtH+kc+kJJNf z+ItWWtj|5q_EGw`-mX^6sWIF2VxcvcRdBrPlXt7{)-!x^J4Dm{Gts&k>E@rJdP}Df zb-fs9&p=+uhjpE+Q=!=|vP3y??wut~V2Wz<@8<5Xqio_O2fXHt@LZ|?)vk+R;D-%) z3fLM2eAX3=>j))B2e7?Wf?cm&FSyn)p%N|}9(Y#I;nS=wb!<4+gC`JlEcgEx{clIqd<51$vYG(+AWha??i^C_S;84ehk!!3Um3IZeA>_ zcaGk#bv4(h8p#PSK4Ye)8(w+89^PA~wh4V~tZ!Q>e>a;y?!%&P)4SKxI%X63+A(dt zYf}O8v9IlZ|;wYy0W=;g5iyX-;aSe|>MSe%EpXidH zi!sWWBDD6E$fp1qpq-kM&_iOXLZ^Rz0>0o;E796pe{84KU4AyS=k2c9ws-43gQEA6 zvusmIT9Ja3w)y_umUxX;z;+SB1N?c3@d__aSIg^qehb)`!fS|Jv(29B4l9~fKD7On)wy#7OxR~ed?m@YV;OhS zdiry*gtU?bc75)`Yz33^msZm5#iiZbWB4SlG^ZY0(z2N6l~+(W|Jmc1e%x};a=Pn5 zU!ZD#*5Y55$F%a#OB~H)-(*96aKY7>7E9L+1JB`^wTqtOz`k&Uto9{yfz^X@EQVW|M=*hpEP+@)9JoMQfNPq#%fj|M_qPkOsVC{rl(u;ho=c~PgRww5v=`Y{6^9@oNMJl-UdoxGl2rz?A_HNB)++{r|4zy@lR+=S#BYEL zRL`zQ%S@9<3k70r&o`*^c5aJSn+{X9M;ig@cNUuLjCaFAtgiI`nv-?iJ&Lk)&pnft z7HUp=FMFki!XL6=a`r{=Ycj@vnD3YVEwp2JsQ}TO``2nyZWHBgpaN~-7Fyr^38uo_ z+0U-_e3ea~ZJ*^#wzIL$fcl}gNM!qav?AnK>i8;r-W5&fGo{v3*0$^@gDa-JY@oS$ z!nb4!=pVB6izZ-=rj@AgK{m8zUDqur#;1E(1BYv%Z`Ekk`pfnE;t_6y3JtR})6c=p zcg;#YqRNtwjr~#expW>;YA|KCxBCqJ>J;+^pd?77HLj#95|GLCGkAoUI~4NI#I}Zq zk!vS;ck?Kals~%ND2pm~nOpQ1IB5P3HlY+ZyX9i?B+pS0bO8|7#+hzSL3{?R09LxU z0L1S${r>mx{8Iy{rgI4zsf%u&nAEvuVHFQWKB0WI} zk(4w0o%!>A-&!-@njbT3e#|dc7OaP4XYc2^@9VxwJ0mDYcF=jT1{Ew|RkpUA3WmBE z4uI+a)MxfQpkk9=XLMH>iVp^CI+hUH;d}g*GEKBef2ZfTehjZ z*h;HI2kdWW74PYQKCPKDzOUYmdIzFjtG=25B+e`49zhNy8D>DT+dO+vdVz6+sVJ|2ukt)?cXI4b4P|!I=G6%tRsHRQPHLc~a@CTDYt-YZc zE~fH$XvO@Z;Yr(&e~q}y>z*c=5FDnlRc#k-?0{HL9PnK54m*XOE(;K5O1j z8AiX$kYirMQao~E&^V9XSiMX1a5wZk)M_J;-Yc{dK25Z8gWeMzY^Mdayt>SzyJ=sWfW%~3f{N4bW6w>Qvv-rMyGg%H0bZa%gD zM`>Hg==o3C)nVvM-yLEKNq}*`U{d-4{^dQjn#qN?f%J61#Y*Ff-4PtL>ye59w#L?61e$7HJX6DbR@Nlk& z>BV~4E}fY+Xqq5-7X>v4r_HABNMfiA0IZGlLv%xGS-V^7D?BlZfKaY#OeYi=vX}Mk zio#?JsoMvuxR|D4u+apQtE4PWac9fdm((z&xH9dQ%)9^lX=LA(wn-&_JD~QAq$jU{ zfRPW@gp7+pay0V1W?vwP4Vr?9ssFj<1Mhy9ju{aFJU))tbAxVztjU+ItPhHAEsfsa zE?kPgV7FA%06g0bf72r<{P^Vt$hJH~k*UI}(GzCsQBQv&fuk?b*KDv9S}Wx!!u~_B zsWjfIc0yI^)n`{1>>=b?Q1b`ZpD?ALxCSR}s9w^%QZM1_-a}Odl+C9o^xLYGf3o1Z zg0|5*ijELl8+QvgIR&*VK~-`uE6b;?6-Sm8%pR%&*LmiszHiU0QO*!Z^{pJVsL-m- zDE~~{{#Nsy~5g^=o3Yy!1 z%;RW9^i;+fpibeWgzK|%J1+s&2FMufiIv@RaCIQj8Sw7_Q)1Q+f7AW#YN?V%zP=Ts zRo;~Kcvgen=iwIny&Z+<3GondOzD?xHy;X*sqj^&D)ImP&Ei`NTqBr=E;u^SK1#^~A8X*}7cT zga^>rEY%@Y!uIr@C$8{}C|C`h_OnVjzRO$=Ajcr9s41sk4&1>@q2q@G4sN7qBs5t= z%}>P^oHgIrDItnkBmo9gNF#IcXzNc2_FsB(Xi-^SELZ0lx@8YB>*;#GVvp*EY-8p1 zezTV^y^Hrx&>m6FCo*mWN1_&+lleE@f*MX!r(dJ~hZ>#CgjUjVWS(uMIcYSjfe3u2 zO<_kxTom>cPn@E%%Fh&$?2!l~KEaf^5$0kEPq|Mb(z7CvI67O^52o0F=`x3@2r*a= zEr9-pR1}ZkBjnIx$MlZ-w5cZ`L@~WMZhEMNkt#jN{wFTVpoOZ~`H{P{2oWE&{pqW2 zSMKg8wv~xweL${!c7*gSzfoq~LVU+H#J&dmj7H{q#;;OyUN?fodsw6dRx?xBHpnQ@I%+_n@jxjyv*r(3lCEOZy8H#r^_=Q(nD7`@Zb}$yDR6@ zWMcIs;M^2(9&Rt8wyU{_Vvj2ug)%K;rIV_ruCG*iw8*w!##4Tw6`!v(G{=k1S2}|W z@GAXJpp{KNravu@_HPDBne*Nij0$=&6P93gG&!YN>7ycJmM!^qUG97NuJ?6!%FCYb zZv`%kuf1KK3MSgjSQZl`;Y8d%#0ONA#WL@d!BQVKM%iJfcRn{cZCi#j+M`YF zorY1|YICxRHOM?%EJB?sj0Z@No+RP7&+1yJ>TgxUbp!Q_tj1N)NVCL8_5QaOcU4?o zjsUp(S{icdVA7`K#G|dpL>S_X8CiSNFRv1*W`4$SgO7 ztj<2&Tdg5l0ix8-xP2FoVJ%osX(ws!}?uYUs-rROM*5);6kZ6Dg0u*;EnC<>2Rw zevT%!#hRIF&Hqz4;=SOU+Pe(2V-yP~o)pQmtis3CoUPTB`+y6x-Vmk1e1feB9EMX{ z02n7g2NQ#aRC8ifeFdva8>3ZY6Et`IO;VU+ zRZg5TJ0n!onXfjb3)zFF+VT~@z1v%s^wjB)6-+F$)}JY`i`N5vZ=69RK!|0n&?u%% zsA4j9t}u<*ArG(vwL-F>6($#RsPlI`1zy}jckyp4OFN7p|L)|W_Y+95k%&{jmTT3M z!0tSPFyT9N!_P2(Qs|ZgH3N?jptSS>l*RFRLm1PpVx#l&bdVE9XaneBZe>7gevSGQ z*LSkR^S&xewLMFvAp0RLg>_`}x1c7^9!-R}_gqBFwjo0wv;7wm-3QD{;+f(hZIv6I zc%f{?;y%eHzrK5rxr~MN`eR(Iw?7Q@-cv%Ef}vfx+@r7cr07wAvYDR*nKL1##x`z? z_W+RTwrWIZPjkcax{#&)I$LU*$^FrBew}dCR-LN*BW>f6k;>l4TyTGyI9{StG~4Dq zq2y2P9>!86ZYoK)Q_MOaS5JY3%1u^|+^jg|nq`eLYtm}W4SrlwDQw6en>21^P2S1` zwOg35exmPvx?Z8G&Z&4!)WEd7-Jbb{jxX78l69U^Eb5bAqwU7_O2Kg*wx6l-5$lW}15?dDeEt)}Hq zD`WES(h!q={dsQwUaAsiWD9D&^RjcI7a2eUSFbqKJmt|r@jeZ?{5O+KkEfpKT^7=b zAcNx(x3K`CB1k{%of}Qmxf^;;-v4A_8y;2}phKX#5{4tmA8~e1q_BvRvAqM3r@F(lJOx>ZJ zZSLD*vtrZVM#BTv1#!i4avGH{ixhlNTWiD`mg5UR`cWH`~02X!MzOJw^dB z1vzNHrVmnMI(ew-(QHX}S8i&{5#Jvd0FM`bB=WV2}mHS9PgS(f0DKuQ%OAs6J0&>%=TO!<-wa=&!`soarm81~p>X$|tQdqayvr z=f?}it3bh;iszdWQDP}_$S+wBA66A@-muW% zm2~zAdREyi)b)Hb3-C!G4O`*olA;yh`fQ<2YD$G$iAyiabQ|5bA2Q*xz6D3?PjjM` zY)!mNlUY@LOY*>_&A=A{eg~jAcN*aE85yuqbhc^vG^_)`1w#^>TZrb|u)~$ZB$end z04RB_5$$?yVzW$yb}h z8ih^I(Uz!G5c}*a%DfZhuy+#^4XR!48hr~9>Ac|;Rue@wPs~2FlT_?zjD;8pLnd`( zh>LcH&ExwLo))imq+>0k7q^upwOge+u#rnYC5?V)R#`t;AGAmcFwRBUoh^G?WpxuB zXEV7mYG|3X-Wg^b?Do&f%AZ^n`)|6dp3kO~Jg-kS+2eW4ili)(bCbx|hU68kixrjF z`>aIv6_{Z!xIT4@L0O||0m_E!Oa&k$!^4gHl{WJ1@iie zq&PaGED@DpSxMD^J&_wk`&hp9no6o(*O0||CVm2Wpsw4{UcS6xKgb&+YFy%P7Syq1 zoO0n&l;ulPwGz}|9!h4odR4qi8PJFU``|^^$JDOfC`mc`Fp*~#LqZe*14!30kB^91 za6{j2>?d;xfOfyMaEHhv=;g{c5maz|;Xxvep?KH4ImayB<=W?#^ra1G`3_v0%?VbI z?7Yl;MDJ!u)AuAU2l>wptq>AACB3w!i`}5Xp~RxNT(!LNP1m!8{rI)`(iU88R+G`k z6i!}cY5ON1r02!#G7yuvmgtrN@LS0NQ_@c>QzN9Y=YTvxsTCj$lROS;GQTU=fBSVDrLgJSj%kc9>F+fB- z2oJshSEHVPL%n4#4x~<8%ZUwAJdN*ZW9NhhhO<8?oNz3>bhqHNteY0um5TiX z(M!-Or7!;b6iy==9f6)^NS+G2`5UPV-UtAQVnV;!ntu6WY~*@HMK{NQwMUrDE* zL0)J{_YXWr(YdOdeMHKiXgL3XiG+!`W9M0$iZNK&0a3pYVx!gBXR<*L(?P>uR9L=D zc)oGeCuUi2#+v3jXzebBtpocRa!Nn(Zu4yDF>I3YEIiXLwi+f(vZRqh)q(681QljXEg$9i`La(IX z34?D#!-O(~nY^CeT7Sm&e;rh%&TIG5aPyE^+SiKFvVoP&`$Efeu8-QXho$4YVn=RN zm(w2&LjM7KV(8aX6A(h1xW+s53CcVk)Hv|w-(x=B`z$N6a3mZWt`{wYA?eCkPB!Vv z_iDMKN@hob65BPKpfl54CMtJCYSl)))$%+PoPhfT=pEf3us(u4LEMOMlANqu>t93|5}AxZq0Xvj?*EtyB!5hvx{){L#lIL_->`i;2aw=J(M4OG5lQ7 zkAT!N@-J$;Ot}ol9&Iz~kLT-~V_6!H!w@z~BO`-{rky~=6E@Gj*gk=72MDRJ`1A+7 zI4TRZf|$BI932rKp3G2}d;O1CbX#+_RA~2!E^^^bI-&mz{@XzL0O|I4=hucO0Aj{p z*n&J8SOlRz+l>VOAZP44+s`yzJf96PoRhR2o!A}@b4YDO==Y^Ow)`?H-QN`aQ~u#zDpR%#3?53dkfx}c54cybJd$Ycdr5m2c;#f}`Z&OIM_>(}Rd^Hv( z09GU5iPg=DwW%EWW)|`cyC|MHr<*o<;$j%}T^Mib+Mk4~Yeh0C4-Q27Pv6kqUz=_p zm>4*WXh`V@bH`wiA9oE*!4+NWqZg_3^Xmfv5DZyjV%xJcv8FIrQc$L&22<0fIo6wk@cO3iYW}`=Lw!TRr2fG6!O4IIN3P%=-_9){2z|K ze%v&{!=L`Jo3#T=>F}LoLEbLzWv#|l;`SW<5406;v+qn7?Ti8(k5JXH^7a3LrTXVw z3j*_7@BQ`9?aIet{1AFP>5Psx@t0J1Y>G)JPEb_i=w+2&$%V%o2wSQylbt6 z5753(vPoWKpE5I)&pVU>UzKe&*{ncA$$KQw=Vp@SRP&sX=v0+iwB|HgV;p6CtH3xT zJ?Zippd-u}yV~TN?<;tyTegKny^;l}ACgNEWnQUHeIhrY%|9VNLEmx7L2eZWxt&{PdQNjFYlI9agX_1uy2)>HkxqhFVx zd-s75rTuZ1UG^MpwfJk7;%cp{(>V=H8q<`BhV*CwQ=-^|A$Mr``d-}6hdU|vf*me@ zX?b_7!D->%y235Y;4l2AlRWYoMELJ3WoFHuuL~uU9bWcS7HBoLZU=LkWv+>Qt!gZp z2nlK3e9E*+TWgXrR2b0j@CgXa64ghEVG{`HWq|Vab8FUumGK6xHR`^A-DuI@|1;(G z|L$wS0Usol6>yOH@3H&WLRA0zmG50Ll8DSb9xY(u^1uHdO^NS222b@WAAnJg!iTRP zn=QxLg6VrjRF+SHz}caCM&k0ET`}7U`9mZ1w=uX8gIsH2-vt1Q$ zmD%dnULO_Om0N{z|F!TK3k9?-y0|^SBHFKf>~Dt<$V=t_VclYe@gX}LcuP3?qQ&Re z^;?}kT>pDjLt54@6S&9z2|!ZYitqpBF&ptu$A7Py`rq$4*dXCJ@IlkU|7%syf4}36 zWPwvT9*0*B|9jd0k6a3j1Koykm-(E28DQQ+ney~h68Q9jPKD}JXO6ZRl59osLx(8TlstiNwxgw7 zCM;F$gu*T+eZ9bf(?hAFv}depo!h}Ex=Ekw3Hm1##ED~ChX>>6Xj<8K>;#fVc!M>C z3RPU>ZGGz0x?fh8Ha0iC;o-7Ie_z!Wf-3)YNXsovLx!U^Lika`DaBV`K*5?Lv2XJ3h7V@ak8!QQ^)PVPmL9-tnZOd7q z;>;9(vqCxJtL(gO6I$6X=zR<&9d2^ceb-Br7EnG+rmwc@;G3eWWPvjw+E$H^*0>3T z&C{_S=bkxeSSRVm9(5F~iaeBVx|}nWn*fD3$KrVZhqL{>=X9f_698DUd(kGU`<-}dEUFOT_Ew%m$R-NQY&Dl zAd;3#H=^mv_vg?c;yN<^tGzT=^=x|`2f=#xmoxOABeJTpeh`o}wOWyqWZaw@rcE)bB$ zf&cpVm_5>4=KJmn_s!Cdc#B&KZd7AWlo{=Z_s9{|2?Ke zIuGhHxScGPq0E~s34X35G}zNi<08|Wdeskojx9A2JoC`wq46<&ut=v4vRfQ+ z6Q;i1Hrll-;0+N{dBFVYPWf5w3zs3kzq0!FhU#KBrT>dtviXM>sxD`L{?xDVF4k5X zqg=FG0bP8tR}nYlWl4g!sb@l0fBm)J3?Vrtv;Fgd7 ztJ35B|LPC^->6MEs=3aEgq z5OmBbn~y@Ce@QjRK`wX&WNp;V!=Syns+o`;TSC;kE~MHrG*04IT_ zrp_V%i*#{IO7^F#(7jH~imAP8$>F)b=5)IzKkK!%Z7!# zqXb+c(b<6KRANz|h8yKTjVY|0|g0jd#HnnP}`YT%EPZ&I9g|}1>0yCbM~W(gxZKoBJoBA`#m)2VjZ#z0Q&!aES4@n%ZaWDmkXsQ(DtyF3Q5$Jz= zcDVqX)Lr~;D(LJ!IN=CLU=i}+up=P&H|F9Q>T!CKt{bBjP<8e3U+Q?M>NL7?=wya& z{YGf*-E(W>-Bg4_;_#;oIaq>yymc?pYcjufJlfgzyv|JS#l}tJMjLsvn^jtmifyge zwknOTx4PL$_gQqa{HR$PJy}NgKBdV?DUw9?Xzq(2A-bHN~ws85efM{t#ZDL`0hYACea zMcn1K$yECEQea@IBi&fha9luTOWCxW*df~jh2E}c_4?nEwNSlECyC!vp%`-STO zu4O+VMELZGrmwQQmnxTqP}(P*I=v5nDj3>u6;`Ys7qVN$idN4m!##^fs4-2)kv;|f zMCsUKL$bdx%u+YpQc`TXrW&hDDjhp`I{}o!snC+mu!W)1d9?WsC*$$$XN5lpq3!Y* zxzsoZTWOxz$8}QyN}UiBp0}lxV0k--0^`gV!BW=}g1j}Y%dU@P`DAWdVx3nqYud~3 z^m3DBw7$TU&b>89ziKy*m`7Rmv*T+y?N`ay4IUK~ph|JEAz&VCcm-V@Narl2OZ6Sa z;&-`y)EvQa%wTsS`q^%G+i6UN3TK&j7$JV8KI7hallWn4vy;61<}W3`T+*pX*>htt zKfo6wphpF)A{;;xaRKHn>Hi*U2KR~r^CZw&Qwrf8Fay;4yCt`MoT``a@r8{X;X>Og z_i}7nXR)s@&7Lq>Brw@yc)KF$E7iJUCGf0z@6Cm}PU@QT^ZWS`8Anf}Dg-Jy~v`86fmUx#G=$TE}> z-#`LjC?rj|3II;F3c zmIk@1%Jp6;4EEU{x6aajpc54pd462?#*1(<_IoEb=PE;ssQ{e5U9e(WNE0QyDwN`6 z&nnQn%mgN$Z&b~NU}?(m%jJtq_2~ns%PAd`&(P*J+9!A7qT`5lz4{!x=Fm1$L)D4( zRNi@%Si}VvW}JcpkZzEXub3V}YDOKxI%NjCzpmFtaV2fUWb&G&7ZG5j*wT&MJ7 zK$~EPVV`HbrE75&Ny##T2eSH~8{Ldh7B=}$C)L^g6JtU^<#TSr-E_ED%riQ)%zqf+E63O(s=Ms^-@we&%jm ziaR`gLbdVt93JZbBKw^Y<1$kO@Ib)OWq`?4BH&h!RhcVGK?oR9j?uXs#dhNw55{=n zf^nh))v*&Q!;V7+Q_))?LW#+NW|PqsZ*@PunbT)C%fuDyfnJSB3f88>+C(%hd%s6J zPRauZ(!@}JK&53e%qk<<&ay~H{tJpR6Miq#W7r(f8QUAD6!kp8(H^k)bCsr|-R9pj}~h7SA;m9TxA8rlu)jh$l6 zZ$L>{XmFr+$q*^oN8-fY`0A}1Rjzpd1I^F*crmAW3M?v6l&cRF%wJF&_p$`Sl*MR&FMG?Ccsd+q??DfjbKQ3X`rKD9Y zpRcVgLYcYAt?AjZ0|;%3Z7XGY0C9^RN_tvv&IjY_LtLi#$61-;nx=w&9pJb&4#T9& zo3&7x>9am+CP)vhx#c;NYv_f`GBE}T_uPl0ude*ZM!o1aL6s$i=wGe_cY*ledaPQu zqE}JFvm(YbCX^0kn81dJ18>34lJAmM39m_%BD@R8xqT07bWX;hVG3(n>Eb9J=lgV8 zNwv7VG*fXu&wo2H!j?qd%cC)X7?9L+Oz-+8x-JC}&u!spm^16bdSHNb8a8Sli98M4 z{nnOfo7hcQy3*E8IPgEcgqP?TDJtK1`O{%(wK`q=baSNAIT<0hx({z}efi#J)>dq6 z6CkH@*~rY^QKOU<+mbCO06_D#)$7J@D-EoRZB1RKrQ|CN&r@Gk+{YIhtB^K~oja$k z(`kjnmiZaU3%Z|klUh+HYjScBgTNCG5X$DcNV)@f<;Yy=yh`YtXKXM?7@BiLYw#oj zArby~v4=FgC#)@qY0RgPt*XV<8Ee_r*PilhYR~+-Dw>OXyyX12kn2lCC*k zYov9h;F&Lv^^}e{dY+zv7tVPhr#)N@&5moH%PIstSujXAQPn?K{nlk3Vh%&P?Iz<|IQoiak8{6RFKT+o7NA2ZRR;7N@;ua3-FQmp zHgR?zd}2cj$e#Sh2cbiaau2Pi{rq+n@+`2q*U_cvq=iI7O5K8a;-|cyL2@fM1#f78 zx4x;01xdve$_OTYgKAi&XHo+K-d49>mh+Z3oy_$3DjG_xue7O!Io9@CB|kTu_A#9d zb~j7Ds3N5)oh6;0p7AsNrCptui@f40~fI5J){pR(S&(_@fcsmYyJDRx_cVnmfk3_1sX1CWg&c)%N5SS*V* z-Qc0ySzL3W`Q@F9M+Hs$wwuxg+@%HQelNNX{DLwh*k6Ij5#UVg)<$|QzXhLD#ws#& z*i$(EU`Py3p!1~9(j|hbqH<5?ElN|U!m)y{63Udz5Z zm7mK*fpFpBiDa+w#JuufWg;!#m-wyII9Bp)Cn?c1*kB1wve9f<_UV5{>gfh^be#w zpvm$nBMQfd(%10Kb0O&92sgLlCTwV~aVTNkHs4*R<>n{OYC)r^s&hJS2HAR;hMuWh`&jUjwW%?0QssnE z>-}G0F9yrblvWYfv?r6Suc@tS@vfmoTFi466Wc;9GQ!k8&=V403j`k>Z2tm}Za(F% z3yKw1#AW5*-CKT?>Z5s^j$~Fvno>Zt%=?+bv(D4qC4q4ru6HL z-$6xZyeF>KEo|oIovf=rE&A=H`P#J(e19!&KZ;~N6SWfCZyp6{gbOWGOo7On<+|0D zp3x|T8vO$$kgT4-e1%bu$KFA%bZBfe=L#odg)y)Ui_XZ+-jH_>{hv$*Z9DA$5?Z~2 zt;Iel@g5Fr6LqdI=+H_}w*IlkIcA(MP|UGoU1b(5o|& zHIiL2Et7Y>k)F^Pnj_b?>;let|2^%UR(`NV;A_o)kLeKX$e@$3QR2@Q^%yvk$iq(u z^}cg*SRDQc!~H!4S4HE!ktaiK?QVNJ4x_X7Lk_b?CHhK6{iMFUnYMQzKOP7q@+2?? zS5o5&1*qn|kXSXO5LU3*hi?)3@3Hg25}`Jfshk8wz)Pehr9UF{zQ>fxLBfWY^1bHw zAyAL#hKuQ*NB=4R-nqh0(_4VURFe)g-4IsMeh5{vm1;-}OeKN(Zj+S-B5(u9^Gw6y znVBKV&TqULK`qxd&E4O>(*EOgP{sCvH@H5{)_!>M$M0vN1rOC^xb!t%5m1gPN*zA6 zS=SjNZXJm)8!PV&znt_llGWZTm(@l!UDT1Y%3r)M3hd+}UyXhn`_9{Umv0Kx%o%St zXP5n!OVI!QD?&Tcu=AA!E=yatp=*6FbpxrzP7Ge=Kz8dM;ON&m-XPtOvAB8(bWBzm z6P{y0-R;$dl6aCa%1lFVHvlob;JUHIKLO1Dx%=~ozk;RfBS%bJj7NSApsLg3b6=tI zR#Ru%X}0it?tgG$%+xQlG4+$eNGPGl40!aUve!@?4_XP(ac|omAA>)r6!T-g#ZwmqqbAmT)Zcg2p$+i9zmF;$BG8ycxqk<5<-d~cQ zxj5;rE-LS`cd)qfv$E0{rD-%}tt{zhW3iJL;COYySGw~jLg&KGcSc|L2h4kq5`F;& z?tq@@5K>3Myn!o{z+$js%Uj9+aqJ#bRp#An`FX~b6yQ3a}q5wJXq_>5$GOaX= z$db3L!xDngwz3MUJklg?CyPP}{qB8@=kE2BuDfHn$5qR-16@dpO*n`XuF3iUco*uJ z3&VnBVwg9nr2xFckpu{8rbqE@2Zu@9D#_9`66tsEoKS;L-OPFetj4gTsX6OM^!$@{ zH@zz*?Dlhd!n~HH=aU^%i)MOmXUV-NpGZWD)s;>X6z$AZHePsbmT^t)bePbK*Sj*pvjEs0GM8W0~f8bNrOUuCoWaoaei+JB#=%uu?C zN;D{Z1q|1Fku!Eg#UduZHgM-9VXqPUT3I!XXlO{Y!1_k*> z<&R2*f6-l`?an*hfZ+Du0wcIV-yeVV#%omPgo|L6yWRyTa3XZ&7#36@p~0 zPy4B&7zt+i#kT(T>Vlqu>QPlnuPpNA^m8%teb$W~iIFcF60oZyt?p$mHmP_6zfVq) z92Zm9nO}D;2>Wipj@VAqqR4D>QRi)_Wv_QIY)(WMxB=87zMRD71)54#`YL^j`n%8e zs1Rf|wMNie{(QfBqn=B9$n@-*(Zp0^VlWWbh3as^6<{M)mS@PcK2}<%Eexhrvte}6HS;P`#9KtwLBfuTi;geY>U}gmaL{`l`(IQTxPyiS)}z;u-xRIUlY-0_OeZ{j5%eL?&bOTO#2Pj zK(EOoFYLR+>l03C#mO)y)vg|Z2;%X>hvX&?R zo;`88>v103Bk!-&7{Y-&!jvH9Q<^i_s}D-eJ-3PcMdM%g03HoRy;l1bD7 z3%_n075tUU6OENGqZ{IZjF-mpQua`T5c~4f0l#i@lTI#rxKXU6w%gkSO4;p&%(Sm4 zWl`GMXTcO`H-bMLlM1QB^yZlZq0rRDX-u^Xlr7CFQkw494a6U|J%N-)1<1TviP^Q7 zQnlX?cWV8JtM~nR?|oYrRYl^QI)U;fS5(TN6`r=H;ccIqr{<=u|1rs!r=-%EGQ*4|+RxcU0O$2Q8e z$HGEzKYWdvHGeiK0PgHbcB=^bU>JzYm+~HLw7LK)3ZohFz4#TYs0-d_~cjDeb@fd>Z@swMVX9HQ}`1>r|fa%5h2eau_fS=OcW7t2?>p)ma@W95`D;;mq0ku62x2(q;1B50z5!KEK7>#q z*YlL*rXfcojI(1wj#kX47g58pMlCsq_wJp{7(@j|#>;$E!;ueMu771{((ecPdKWiO z_?kx_s9?VsS^P-2T`uv!*=b?uY=dDy;K@5Ze-cHHx4L**juHC6_0Ds}K(}`0d7&_c zl2$^(GfZ{vhe}pI8{MI_MD{M_T-KMpm8y`tiRBEW>MPGh*}HgczW6m@O|ac_dAL{; zEuEp;G3Zq{XQVfO+Iw*;!`SKBZ^@O)eoiHxt=ZfP%Tjs%_@gEx1wCYHvH7iDQZ3!b z9-ea(lRYt1Yh63_YhW|WRe}_t@u98h^QP6?OCO7e=!MPzmG&*(t?){w3q0|JZ7C!c zZ`V78&R?a&`)jntt@sTZ?6m?g2hVlSA_guU@)xLIoQ2e{!gTIcGG1Aw+-g4F^iaH4&(qP~!6v{eA7x=#1=s|Y)UcG! zVi3B-H93$~?-R-^!gTG1Ucc(K=ugST=d8W1iHxU6t4tPX^jXmwH_Y6as28VcT2&$f*x$o%()ynyLl=jbv@(fu#^#Y`XEC4Uj2PWNCgsF z=A_U&Ao2xUsO<$`o9ox;O;g}m{+>7q%*NU&%+l{WLoc~M9Q-t=L<~{ zbxTv+xoEtR^Ww}7(KPYmhQMEX@9YMUk~$VpQeCO*dgpO}0heFRp=u!^wu7{}w>RdE zDvY2f`62k{#?B_!wlbQ3jpf%j#AwbEtGEk9A04Z9Zyq9>%* zjqCpDJ(WCDd-as<)i;x`|N2MbpB1|~N7ha($^0~Ie4^h~h3-=2>NI0oLBE65)-8xZ zm2Ie1C}bbOuiQM>67pNn#9vvMzwN8cd#4{y*K{1ni9qT%^ETbapUB`A3_L5_-i-oQS_OsD2rKbMMp>7iI$P01D9ie?zpGLolWU` zBPVE|&S81%;+usO*|!&781Q^rVcQ&F;^D8#VI0KW9%%}#=yPBBM(0I_=}vKI2_S(n z@qbP^-N+rl^)`jLs;HB2viGmme`2NEttp<-2goFvR>#N()ciacuMN65T4wy2TlS!( z5PGe(HTr>heOKK;pw??%)V*|@m*c{I#dZ_Ws-+_|#B(ZSx8^nc##ozsnAXl}sH_Wq z^65FY;3QO%d%97V1HUBIX%bKdD-^HTpAbN;aeAw#DbIc0oBZ2^)!oq|%iSLUv4yrA z$;83M$!B!j$mY>NQi!Lwbk)ilWvkgXETyQzG5%UhZG!4vA#LeNyzlC*g$L^jg(1YI zj0}1K8N=rcm!kWnFE|KN4RR{`s(r_Unrr@$FDxi0`vgf~YL9>$9)9!RaDcA9wgXkd6#@ zA#_UoT9Tsd6nRwQ;@s+{uy@OPbKznmc`9tT`rPRy!DPQ?OpdsrlFq=Bw9i9LAnncs z>c+&BHpJ!!?t&iTI`L$*$YqW5)^BCL-xQIV5fhXcnY0aIZ&bMdIc6Gh7X$Y!#P=;- zqBJc>>41TlCh=r*Jca&DSLqC(Y*JPm*Dl|EYiZWvN9BtD%PyJsgN4o1GnIX`7hH{Q zSOP1VzZ}r#eqC@3fAqEP@@WLai0+Vn5WfhfbB22F2GXe(rR7D3bc#JtVMk`_)P968m?l zV7eXjBu-rC{<-O~q5qA&_Y7*PVc&i6Q9(sS=|w;V1gX+Hu>k@C(mN3mDG?A52oQ+U zdz7k3QF^FSLJf&@0TCe->4d11fFzUH(+m3(JkMoHiV|zZ?22~1pnb$4Oq+PoTe2ZNek)kF%_A zm2Qc{W9>U+!`7cL_UnGK3}ZxrvJLMOXij2O*0bF;ZdIKIdAuGu@mi|cHM1x*;cl&< z=T8EEwM{Bca119Z8j$tnU*U4zb_#QqT$Td>;uug^?_Q#09fEuqzviHr=|&- z66Zo6AjWNnnTuubh{~oUi;igD37FPO3ayYzM_VR2O+_Zokvm$}SiVe{>v^B}zhSli zee4tr2!uqYMeP1E?OE8m{g*?V30*b75B7uPzYJ+l+&YzaA|1*UBr%@!W4tE1(MkQ( z)mdO}8h$x1;=+legRT!IGfu`4;tO{l^vf*AF!2mvpq87rMLRv^ay&b&mk=uf8vBRA z4Euo=qp_lB2@PGW|J`HNVFol~KOMZblJoE10W(rVIQ5rH(JSWzcsP*LKb?V(iqI0} zfo09&|N0*8UXht*@b180rfbH4Wv|pe*XgB@g9gNZ zIWeg58Fm%HxCgwQY1SK(teEX>cai@*aVGpf9(C2kL*9s`#Ua4G^#3}sERA24SkCzgak9*KAnR?Ia>u1)ggGJDunq!Xrdc`kpe zJ%S-HIlFDrl)L(81YV}+iX^<D>k*2grFYC6Jz3J5P8>sXd_zm$@5UfHZV zl5iTL5x~YsTH3zhU93^3&8CXQ zOW(w3$@+sI^M#EEboZ@~$gnxd8bCB=*BGwk^0m-C82Pt)we4ln1@PSjvH~zeccQxG z#H%-`lvLOArHr01G5_XmX;Nv`8h-x7@?td&n z73e5A=NJ_Hx>+Y%+RX;(ZpZX32oCaORozCpC<&f+J*+1WFwTsUT6&-E+ELkouYG*` z`|WIzv(N^gyS38$9r*c1Mn{j!5I{znhTd*(vvQOHXt|7)Ls$<`x6iF8a0X8|H z8P22v?BluW{kjX@EwidfzjRg^+gTXp;9Q#3Sy7JA0Li&)4P-D<1fa&&f(2o$6dlnO zN^&X8<@>c6&W+)pP;fu#&dpcfipytpU*GFDzJrCxF?4tVnwox?o=T)%U$F?AbbVZrRR|2VRzjyI*wmYOxnG;8$#} zeP=R(4gTks2>VGs3fz*jIY>T~ZR%tl;3*Iws~jZ=eg>NH>2o~}^E;-h5ov>eY9+o0 z9X=hdKz-kjFvS<2$2s$I9X$alp)~na#H*A&`uhQVB$y3H$H*k zd{6Dv+g;{dtmFG#Z)TK3Y-)c6V=|)tGI z`F=M=M4}__|&{8?QeZ0pJwo}98Fyi+{_ z)p6HThZdT7|D0S#?(;KU)QXO#k|59NyMrR8oy;tUf0>9A9RKind=H{Q_zm~M%Ie?- zbWe*8!_8cPb&uUYbWAKbd*$`OwdJ(t2)M+b(CBALW{)qQy*>Zv+|94mSfJHKvWLgT_ZK!?F1Xno9Rc)@;lfn6 zK6ypC^|<6(L;vL((*=FjQ1GhWk#NpDBLS*JrJct7@ns88F@|e%5B85>fbXA07mzMCVXT==hA)CWIIW^`1N_7R_~8o zNIGbSCff^r_SE(Ay~*^Nccf814?fKx5v3Q3hx&%yF^_>gcOektjsut;Q;psGD7}6U z0Q%qf2nbxDRNx&fbzuXI<{mbGQ%knN6=ex)h)(*5rfYB&_pb+3uC0AmZ2u%R^qKog z)CulA;LZo9DexDOM>?$uKjS%%z2azA(zQWdYTJI&jJNr?LaUb-@@1|S}6Z#_5?`}&0$pNqv4zX1nVB7l1Gr~(4 z?Gu?r;IS##<$S=J1$c`2Qq#TbWNCcM)=9E7kl6V8DGy7cA#@}_pR%PsEk)=OebXNIlFkX_m@ob zyrn90E9RV%LQps_HLeLqgFOYp=o}~GtVbp6E;aes&>p)rOn~!KDIy%yB$1hTCo-jY zc`4e#te2fftZ{?Wgy6?a9eqGXNAjX3|J3dann)W?x}{xjJr)ZdTYosW{*cC2$Cv~n zxeAziogKh2JvmbYvjlo15F>gmn2@#gvpuK^s)rVmc!`GK8_zq;ZH#XO57yw=-;2Y~H=-zqsX=l5kNlzTV6L)?QMtTI zSFbx#nUB}|El>vHQv276`E45RAoCtSEu$9*b5xmj3giKkO+rG7n}y$EyN{lF1;Bt}i}W`56D8(LfA zGClOvwUxiqOzi~yMVPBQ#W1o!&Vjz4uAJpi#SyM7g&DRyDNl?egq5}+xb~Nr8o`i1a zP1>xL-K*HEvCuJv5Ie|XL%v(9ZkyLi9oEwmwfBK+xgjPOm8d@p}cjpnye&1*kfara(hfiv@XWmoF0j+BWf4OhQ?E=A)-Ym^~H|IEbLWABbJo1rIk#ADZe{u}UuKmlD&#m6i ztm%d*4dV{DeELCqnZ5b+QO$#p=~eGUtDwcljry5~0#=L$47cDhDDeFj-(5imzJwK9 ziKbNU+b*&LZ(JGG`E}%Q-If;B=C#OrD1nj>#Bo)ieXfaNrk$Xs`jG)9Vw2xyIZJOv z*V)_A&D^uZ#dGI z94k^Z*k5zep2fI{B+}q@ha>j4KNB(-dLCvsKHkap(6i~Y`j~6mJ6)w;V>gt4cIB4^ zG%MEV93ZO>k}&VLsL1>UopW5(b~nnpIsUVhAAc3FIej!ysh=KIz7;_Dwz?h%G^q84zC+BGswGD-u0RvzE(p^%c(4GQU&6vX^FB zy~i?^2MX91^=IqyHal9i+2(E=;eKM%{e4nRELgKT0Rh!=sYtWgD>$T%5606(^0PQrOt0*L}8C|j9RJnWW^02d@oW9`!l@Q#0zJiNb|>?&bDZ=^tOGlym)BN zczWmUz-2kRybDn6z@y;LG;f-5dN^m)*~_n@|1wQe2J23fDF4K{-<r4rSf+_wOF7 zNwoZ^Z_(4ks(~{-SNXe`D__F;drIkzJ1I~8bnv*kK)vu0`FwYprcrq}b|#SAmb0bT zOP0sqx941YZ+OF`sxXq#2Wl$pS(^FF^cmC)j)VF8tQhCtB`j?cDrq-JN}ifQ`E<0f zBzkI7&>Znd8%GDO{LsR$w0R{=r%03zN0b6CwHCwn@X4HDK9Z({UqRoOH#8tCQQ%W?LD( z(xG`|TPHz@oZ+pQsyW>&sC${nUK z!8o~&AQNc0hiDsvSJ_g{;=`A`QN+gu+}2<#yP>`ObFC=DKCGXlsoA@cK2wX_c%?St zc;8g2`rYjObzH3E-nGtH++zI^J zyvMho29&bzG&bhK5}7-iAn^O6T&Yix zo2WrJ%MXh=Q;hwOFJh4AFIzr)YB5d%I>&;2#gsRPxrCZ5SE;~$Q&g#$e5w4Rtc|nn zoGa!VBL8^X#r;Vpz$!ei=P9-&h@8r^G&4wuiq%Ik!10Zf&6Fb?MVS6!&uqqx@_}kb z9_}r;oYSYt5*1XHxSx9c`>h~RSEN@DOkaB-m2cV*hI}E69xIv$M(F&wR;=q(<3MEo#{Dr6X zeXJ*{bxK}ls}^=!%X!{QKrX}2r+n_~bXYX7`l`hhI86k;yo{jXcRyR5`b!|z1=l$# zF5O14_q(jiewTezkuF3$kf$1I3|jHDo|{Q6pFjDKiY!QR@Yq`rqORz z^eub2iQfsRa(VxuvR^KRz-^J`+0X4|ZILp8{B|=fX+$|U-Lu^qfO8q$OfQr^AcPyS zwGL@k8Oe4!Pt`aE?nykPe}d@%fsrZ+R3TgMmq&PQC(+_816tUGBj4!C(&miX4PgN+ zfN`XBuc!lKSeYQL)ax66B)jLG@3IZ+_uD+Qi_C;wrZ-`ivkQUJ$Qv#kITgv(LPrKI z3BoKg25hHYgeK7n%Wu(Iq-}HZX2wROcq@(raOeXQcQ(RO8trG2^KZ-Ub?Q>qXXI9S zwpezH3kZ833fGU$!wZe@y+75pP0rbQ*zy`>$%uYN={H2z#Cd}{s(+cnhRCL5^N;}B zM$hpA#Zrx-qSzZo*4*x+tUFfDk85)mN1SVTawh27^mmcck=?VPZZ=+h>O0WZ)ngsT zcNadg&Quvd83r!)5x&QEg#!g5>EVf+%CaGV0a5bi3lm`<1@ScL@EVB+&7mHssNaz6 zG{+9F&fPvZ$r2lPgrIRjmOZv?%b|A%Gz)t`(IV_GzzkNtcSzTDBJEn9=OiUK3dt?y-w=(IdXCP!(f`v}#O7wo z6=a_aXO4{QjR50<%Ofp<3mcmyleG)Ak>ylYAcM#Qd6-cp>wuPq7yBz-F!z4pt3WEk zata11g)ZIu?t}JVE+iD!-X@rwG{Ws|-S;{wdtcInoE!a^yl`jn_ugzNrdaFZv;`eR zA~4P+lR!OK(nCa|h#<@W58^gFmW3ALTjF%N0Y9nc0uh`XDittMKOY|#1d4l0>Yb%& z-eW6XTEC4bKO9kQETQW(f!CPCK$vVtE3sjM{*-#-IFbfx zrhkAxV*^v-NQH?oXA&o6D!Ervq8CuPhbZU~%-FnqYP~fJU!girIXUaO`BzhoW=W27 zeg2?K@w1xmcsNfTV8kPFpn60XXvT77X%m=V{GdG{2X`FnL39N(R+joesj3dbi4_je zyIs>hNGQD9MP!_&rfENTUTtU5M7B5kp|O(pY{sJV(!E^kvUi8P59C;|xbjtp(fH?_ z`8VW!&iUKjpX|m}@wwK6Ec~Hfk8cFDNEr6Zst?_$s2ExoGwPp835^(#p7bs$TDTwX zBPD5c6bOD=@Mo?$0(Tq{Iacrwy-ZgErU>MLRq#uWUM?4biu58)Zqki>z0j-N1)B(C zD_NW&TG-$9sMs)6eT$oytZz?;dbFnSq))N8N4%%Jp_gIZmu4k^Vm1#ZTQLW-BMoe+ z9IHclKykAsHMQ?QEYRf1!LM}7%blDTHrn&1fvBr$tNPl(EU-*AKTpib*=cSuxixpn z*lcK9;nkPV#BJJoNqT^(S!d8)iLUXFQNXU^a>G$=vv7&GxVfp3cgfRvfh^58NcACY z8gYMb{=6V6LF)id&K?c-orRSjU|XMdlsm-G@;0G zIe5gyjWF??M4#%%6IUCe9imO!T1@Up+XXorNpdW!r)eFRK0LL<{Hyop3-`EK8J*4= znbXhOlY56OODZ0F7+Jep-29cfA#S8tHc^zBZeqW{wR`s1RtkHHi0d_bBd%tVo1T=M zk!}tfQN{)D=~e+ft~a!RF@_dn8pN@WJ)_e~^AYG+*HJ;>9?=G(TXScIPvSLPfe}bsQ;Df7UMdFIJEuP7`)?>P;10(e z^M9H8g2?B?7GM%0g_{JD8R#uvUD~TIk#S6K3(y?fUg4!j&?NpcO+BYdIQY4D4O3~|5Xe~^EfkZ(^;G&LS*tc4Dq`~-|grHeoB0WyGTn)Evy`-1Rl!2+|v zv`FfUtWDRf4|Ko7JZ?1`+cEf7RV5vCaz}p!ck;teH@-pj?STg&KTizQxH&mqfR5^f zlOiJXLAvh-e6-IQ5;FcoVtQJ2G>>g$CtHu)%8~udI_jg5g*B1oNxtKLdQVY@kTrma zII)YYq0NG)7yT*bF_S?u=iNSwT(l)I`ePVy{5O9W6_+(+iv?B``;u|>!XZH~3N43C zhuMd$G)>c4Z^DmUuF&tsW;JvK_gVw3TCe=?u7Qa{-RE>M2uqk=J?O=;7x~Du>~I@*VY`w&hHMLc4GN!6qs!8MA|=ES z%4zycefD64+9Mi+zu9%Iy}A;v^yn|s5Vpu08jtS_+wI5iehogc;*GX`CiE<+Sv5u> zFviuE(}??17SNrsk7k@Gs@vBfFH1rNKX zvN~fBD&0YQSnYv3@_Dt?IRojY)!c-%M~(xv&UDA z>6d`sr*u8rbN$!TZmHgnX&?}$id=*0NsEQ2hyURSA11zETM-j#VD&8eWcx(@QNtIq z0+Zq81bg;`joea3BRmMX5u)e$;k4a^cr}lpBt5&@!R*y(KZ`e zWhJjTsknFEP|6NoW!`s}JnVI>*a1hgo-%fi< zVr~qtzFn?|)cKuQ;n{!IS764?arcDUiSZlB$}abN-K|vrIQxGM%SnAY0b|9FoD=67Oj zhAZJJ>jy`N;*b8otO=oL^N&=edl_bFBiceG!zF#sbJ&!?Vs)H{iyx3pS9@6nQP@nT;c#MrXlk^eb;dL_JMo%RXQs>Q6a5cJjY-qqT+ScTWk=-9iQ#f^P`>oLe|6rvTdJZrw8NG9QcA~m|wJVA*+j!!TnF;hFKZriixqC z2a4B}$ED@0jUFl8|2EDm2rEc(dV@1*UZT-n9!bFw*;^|89S{<+M-`*i(U?99=Vs0} z)LwsdterFVqv`hP;c^?V;TJY<2t&dKUH>k0n;e(U1C#&kF)MAfYQ&9BWm_z0XIiuTobSm?!`DC z9zt4-ef)m&S&CwH?Ra{9({_Nl1x)6?o1pbcWrReK&+%=q+2G;|h^ASO`~|PsZ!M?i z9TrR3wx(z0X8))v>a^W_*2LkI)!O=x23wbNgNOnezw6A^c9n*4hM{S%g*p#XpLUXT z5K*Rum!JQjrXBZ+KIA<=e*fST9~-h|C#q8a0$x7%>p;}YKwEjt)kSAd#))zKr?^=& zrq15xkjU^6%DTh^^M@K`X7%1{YX9gUrMI2(km|yrdb)NLVr0j9-7;obXVjWH(ZZ0& z(L7k9v~?iJn$EtugsYi@p&l#ueILcm(<}MiEd69|oO>Ty>?N*mN9%vboo=KFBqHw} zv25NL9#Ub{9-P^0@k+tj{dJjI0N(A_hpy5NCU<_;Yi?02;=Mn?*yJm|RQyGxzoG=s z3t2MLq5NeMN4=CRO8Rt8vgqaia3i2VR7UI3z+a}K^bUdlJZk<(#*(ljC+;Lk)_yp* zzE_`o547k3a?%hPZ%f*EF_tVD*&sqo*vsD*o?Y6z4re`#bU!*?qDTW7U?j|n-tY&N z@1{S|H2M!riPZN~Ah?6D5XN8Y);hVGV%p9g_aZqsjo>s{gmUk|!ZmRZ#(hd!eC{x$ zlNWTSc9)YFpMlP3uCXf~3oN(WeTtI~nU+C&tgx9BpKw?;Z>2`B3UZb< zj{ll&pDZdx*CJ~z^TgI-V#`w5dkW9c3R54Nis>0Pl+vMaGM6b-U9u&6$9 z%J%36<2&PQGgX3Y=|l~8t9B-;wp#=4vpgR|PuVn%(2HG9&oL+Q3*W3qr&N9!kYc{| zEQuiiyr&-=bn`zlX5dYy3v&FpYf7h6uT4L=d2QfK2U$P5n3`k$;@-LBRd3DC`LA3| z0(V&WYH1g)mD;O+Gd0e6r<%2OATMu*?l0l;oyu-0o&_cw437aTb8s1`ft|*gD?b~* zEZ+;CmG1*Lm3RBi%;5)mKnVgNJtKbh)N)BjKu%USH+>WAQ%ahvCuN%_)CkH^EayId#-M%H}yHy%MiMiC-NUEmrz`6E7oJ8~W>;h?f zdHHnWz1yb-1BH-H9mXjf88T*<<9M#UZ*DN09qBe+Nl#h`%FJ1zJn)s((uyFWjwv8D z&#H$%z)pS2l&KKQE?*^oZPi`l^QUvIsCb;WWA9YS@-K%+U;rY`BE?5M+V7^Tp!k; ztl#KAx8oH)hM;#|&$P*(yJOgi21a`!RJfxEzd863fCsE!O#<3*&Ju)!q-t&JA}Y)0 zrN;7ZS@23r8VtMwu+SD%QmdN6cw=NaK35mcF{B_q57+;>b4Ap!MFG;(|p~`tR7b97t77FgBavcy>W-b8{hw zcn~CQZXsjQg3GmXzX_(suP!8uNtA?Q?ul17+TQ`I*y!j#msh1e053b#cEM z@1>hu`*50Sv8=lFKKi0kiuEhgB)S!~6o?CT)b}NfC4rVreiEdwUA~u{Zx$Lk-OD2^ zNj5TE?i5~`r&=;Ns@As8pL99q?0J@P+b2V^J&Wt9c0G9L@MG?xeiF1a%KCV0M6jQr zqgc!RDg5#yN=58_6A69^C{lbntK<3<(Q2x*(6tp6US5WnZdplKD@v+A&1HV;|MeCT zPtu}n(@Qb`M5Oif2Q&V*_5K4-X0p|!`??2hKd%7PW%cq%!}fqAqzS#!nhQDNY$U6SctdbG zv5dI1<1nu^fR^Z?xxSh1$ad_tw_#W5xl@dg>UAX~ms;38a8{|g?80N^Z*{#`Q{|(2 zzwyySD$vwC9YmES_|>nA@HNTW;;(neeJQk$-V&A~-P^q^@%A8_2U)i+42$7TBIM`GPf_%7Y6}IjRF7k|$VmnVYC?t1jz8#Iy0CET`+B6*xNkLzbxzr}b7hR?ia)N{ zPnEjob<)hot)5uKPtxFip)erY0-rw)%q1dZMLrI&l$3}HdhWJctITG)G9>+D zifHiz#DVl&>s)4^$>PzNjw=s}n0dhNqEF`9#l4C!I1KFd05!0ALGBQ6J; zB5q_ktk7(16xnlX&eyVfhT#F|dSul#o??lGV?DjP5+uizn?Vvu!_O_gHaZQ6eoUO~ z=6@O#*Y|yeAsmod*whF2TqVbFd42zUM-AllF9A@OARgWf0X44nin79X4VDWNexmQ~ zY%#wluw5VhdJ8l$GrQ5w)ter->-`KWuPt3C^-Q-!V?e${CHjt_9g=T@In~NcYc5{MdsdFf5Ja+JXWn zOXxtCG~@&Ip;rq>yQFeTAw3u9*N$9w=Is_% zB4HEDCS?U=&G6evHOuwnQ=b%l+fW@OM1rWHO1Rz2zLEa?3st2ad>zk+%rI1uu0l>w zXjdo^|6r%NN}{*c&a@i!+}=j9$=UE37B48Gts*u@vGgAJt#EXYuFFWMan**C7KN3m zK`*`YsM}IG&2mp<8CD<4Kbb7sO_?}sYNb6t7g(vF}{^oFjs@Gd3!)4 z4MZ~Wn+;qZol}t(IWN%%Iy)Dd&VCW5!K>N%lV>>Fb^?7vOaYocbuU4=#lqgsyWRbR z%~GY}%dJBLZmPmS-i!C&e9Lo;I*q~0CX5J%IKwYdAKCP#s|69ohKI@EDsf$;j z$n4@dItY`BQHijW@Ai*T=S=d~{g#XOyNIdj`BB-LI4hOroM*=A7$0aqSp{`{PJJ-* z+q!u8#;*x+6%*JAJws*1xnUX3J~7TN(LQA*Q9T*O{ywoJyLssWG2e6TnsGL#?fyAa zLEMSU^-+1++R0u&i3Ahh#tyUdhANXa zXcJ(w@60A(uh`qtF5koEMv&;jCJVDr50>Rwzf=*YtkY`FEiuLPZ&qgkPZ!Qfu zU5cIgxm#}N-V}#d#?I7rhru^zypC_d45;tbVtirX(3Ki_e0NoQ-e2CZ;t_8Z+ea5i#E;CRl^;ND{0uNo8Q1SZy?d0zyDMq-D`|#5&%4D6efD}X%ipZ88#2r4WBJmI zOXZJ5C;E8Z53}U&b-X>|NCN*f_d~-+y#ZzsKYD$jY?Yc-m z?kU6yn|cmzpReTBFY_+f%N1v?OFV1f&Q|<#DE#a%6O?jU#7$#kb z#Ku#n*`BKoDjwY@>;y=+MHa8qP5gP`#99!ssqpCLL_`J9kjJL;=g|#tN4bDJU^!a^ z(AqwU?1QsTg(I503)z27M+$7jM{Y|nF6Ha}{_aCgAN)xLS(0Wqeev2c!^LLtV51*eE95~TKF&YMR?ee)+&6) zMHK%W*zLUhmr3@kV%v54sE_Hzub+0{OM#z_V%0i{t#yfotGrvr%k29DYjwG+dy?T! zA#wBeJuqj}hp9yRUG9Xc?sxg>O|@%x5SMUY6%@v$_+LPbsgkq=dXp|cFx4)i*JG5R z!dmbKI|rIoJFsgVWb&p9xKQxVJ5q;lwIny{`IJDWf`@vLG0}17nJxYYfh*98rSahK zg*4HnCPcSvCfH6mLeyOGNj#8Sp;yaU_WJ$;ZRbHA#0! zv$+8%5N)mH1>o%F8N>Ar9b}$TzcPamK+oigQWJ(HlxSDRsW)t*eygmDoN??XlC?gA zNx9SkXKE;3VP%00Xh#WApLY9L_6lFXSuhrAwT+9{r^dSi!{J51_rdeFpO4gh$L(Yv z+%p?Xi&V{m(FO0kUpz&t!(yFNA5d8Z=qg)?>>%V_zpRU#xdi~tcAsp@I^JyY*hCC0 zQ6)f|(xebuY8UAak0#l_Ny-lWojisEiZG?h7CYNRwR%NRSxd=v&x(?}FUq#W;o+QKAybX> zM(qo$i6Tyz@jAUy+H6|<>HL*p(&?pWtj8$zM)_t9KxzR2YCr>uJ9YS2f~FY1%$tT5 z0=PA~9??y7cvqNsuSYjVjuf;-`B-jH)v*4l(!1(eLmN6I`Ch0!7HxNQ`_Syb-*}txhA!GZ#{dl1BpdkyUg-k5J!h}Hvz>wV8bAUQlql|Pnp zqIvpVqH)qcMhbdk`E)s8ex%?Wca_ydZ6?6@m7QlTH)`#`*1~IS-o?pt5TX!wv@+!= z__nUb{cDjVLy?{Uda`hmQbR7^I*X48?>icesll9Qd@ba~Xs=H@=*1R%ltWd*R69u{ zrD?Ciasu?TXCq76X^4;zR-Lc1zR4Uyn-I!LhG03xmI@3yPG6(HR0dFB28ccYiL~iV zG8(0LOzEnqG!@??UoS;cw73`vzz_w!Z3 zx{uBrP)zCZDxf(~|LiJ_ix(*N-c{4A>FJRmm=;wDk2pos`|kTkC4#rtq{y4>42`#7 zj75l;l7Ywj4xpCxFwUnPIU75t?>e=Qf50T)Z&mHI3jA}m2VXUOGh4%$`?X4D>$1CM zR^yqc>XlaYDpyToI(?Hr8Se3WY_a!qwsTPZiWnRO6sygIZ)Yji1EQy>#L>0e-3cl``?;Q;aq_*XTNrgLTia zUm#mP+T3Yj&Pl)hS<8WR`04l)$5u2WYQ%~e^*Z1NefP7l5P2YGf9Wy}GzpZMZLrYg z1<_syn~y7SW{Cu}&oU9qKDd(4Jig&Lh{XDVL!d5bdSMLj*s@0N3n8E z&g!UMch-}&z!&_n^n;71)C0(Yn$)WNaX)Ub@`&fMzqY~KWwJ%j_tG2gZN37v3XckS z(z~pY;t5rzshV#qlZvc7gdMM!1&qgC2rj>Tou@R+K2jJZ`K@HaU%j&a1F^rlv*}uA zp18%sl03&e=j{?Og6hXOtAGPI4RjLp6xe})T_nrBT9Lot;3C#?G}kEVpeHUC_0%&Jw_u3sicQ2n3NQSdtiyzHGi{S)oi$RK z9wYiC>t^#+d}cWX9KUkpWe8qfLy9J}RBxq^AP>u&_t_i=9{g3N=ll;o{1Tis+Bir? zJLh_uPekaMJ-l#VxWsCIT=Lm5985ZWQjFcw^3`|t4;8Ni3(Zz-E(MO3?^gfbH*N;R zXZvl%I1Zl@DXOF z%M54ek?m>kW(O_MpjY^_x=PnG8ZCg)Wl zQg;ChB92$}@G@O0ba^X*TdISb&fDY}rkSZT;B;RF7u9PGzt%%0wYG-#ch`R}Igj2+ z)oPkW6%>Z#)cMGma0F|I72f`F9$wH{d2}8I5VD!v%KZjfbdL5bif_MG7_b4w`BTHf z_D3C19KNnUpSh(YM!V4Py`0BE8ccx7AjEYabS!(T22t*Bw#$NDXQ-S- ziU-%`FYI+<*}-qYa23Bk(Dw+g1tJmAL%X4dfbUYLbO)V}#|-VIb$rSfO82&Z0@PIR z$vPBj&i2JV4m`(Hi(*rbhek| zH(^%;jHHCNh)3t{7;Zr9l$*?(TgJjdv1nCsnPN<29NJkJv=xopoA(*mDNI*9h?EbzeX?_)yKoxF z;UpOUwAI&6kVGgNJ^#YKX~RFx#fRkkX>%L=Wy*QWzN=&VKrEJU+(x^lK@YPvd$R@t zlWQ#>NSuwHk9_r9D?++-F@lB2P}%tIb9ePn`7hJG024@r8vWW|rs1Rd!+3w;vnR`R z==H?!JCUfw7ff3N5|_hX0w=FJw7ng;MElDW{HeAbaex47o}>LEj6B|Jb})!YT3afZ zhKqhJs~P@{qotWzpS0*C@RLM(9s`HYpAEU^7X*#=*rtv?4x($kV?8D#f0bPO0>dlIzZ<}4# zRyb^buFo<5rP(mD+xhVZ*`lVu~4bKIIVHaBMDIT%rE{%1x zQ&a-pkw3_c6M!v##BZrydWT|ze2$dB$*c%BI4ycT?|yl~(1YoMNvOgpjx$}CFNQ_0 zd6gB6fb`!2?a>GSBuRewU%It!nK3}Mx-^$Gj*PQE!fa}B{!5GdKIaJu_TO3&Gg}D-aZ=819 zUp2l;Fb=089={TjTB|z{nGJZkqMGKqhdzx6W8Mhd-Ffc6 z5AQ^#`|5g+m*T+<*eD<;AVObsECG#Bo_$sk*7e-Qid?;dMACtAC;Z|ta|WJGZm+Av zD@--392#KNIF38W#gxjhRdkYB`Nm1B2@DCqE4+Le9t9Er3N>b$9jS1DO-F9uS@%-W zfNVisMX@ZU#hnr!0kXujc1&?!f9F%&))F;RI^k0m<(chG?EzL3`$%jccVK%Waum#` zD+Hah1GXL`yNyMW&`6Uj3AG<4w*MP@?;X{|!?laz&w`2=r3wO3iYQe&((*&43z6Q0 zh=>p(AV>)AfU9UNG}oTHFQM82%$&<1cHF{gc=};cYNP-?|t9%-F3fn);a&2 zwZ65&S~FqRWHQ+^^X$E!{X8|C(oREwcKe0gUdvAhu+O(FL>oURxJBFEFquwXH8{8s z#!tJcqZHpk7Si&i$SU@5fKE@*YZ#JRC~!1~{zo;9@Rpp_YIn=rzT7FqUR{;+m%(1{ z`ulskW{6CxSHb8cZ-bZgL5b$}`0~49UtAqH{(L8drv?|9(tWIoy>nInGd;AYvD4)7 ztnbW#?TxBoFNQ>^XP*qb9G-b|H{ZX%MSsf4;qpDh{FXG643;o@`t1+^upYt^A9G{W zEU8%w!)l%D$6xN>!&QKY?!c5tFE&X>jgHBK+cxTCQ4j;#$^(iUH*=Y?{;N{GMo%l6 zBL+F*F_p2ImlI+IdHSXn6Qzifs&v3%6=c{juzT?QoL3G0N~d{XWyDGy3~1Ve2>B zS*TaO2|}kxmNM)NAEZ>l@5lJZ6_N46;i=PD5{DhteZ_>9u_G7Lw(rGCq(xE+pS50| zq;EKRE^$I{G*I7Cr{fLkz7KVnIR21_#<(at^%Xhx9dCFc!}ZW2T*ma8b>X>QG~CSe zS6QCQrR||bSRQ!q>cy;#Rv>mH&N|H{S?Uyx~RcVHB%iuh~ z$LU~$>gXd4gOC~ixN^pRU+3RuY3+4rEm3TnOIuH5CO_eK~FdH zsq&5P*w_H6HsU-&)5OrVwx*^wa`yB)A=RBxYRQdLXZ1^7>TyZw#}&o#9D+STS7CY_ z@#C|zg&p|BeN>beC6pX3qA=79^O`y6ANMCYIXlT&ivGZ(kG2etz8pv?K&}?tUlQ|9 z+b#r#)80E@q@g=Oq?nm(@+oD+c^{JN>`$18N{%yxi);#cBLAKi-Wv~@5hBQ*H%`(H z8T?QWeC?v?cAZvEhAb7|iTP_8C!i%vqmcB6QDR|%Ei~~nPzs)B zNpPyz)i((70YQuJ$8z3TRC189G?~*h`ap763*rAMTW_YJp+Pv(Oymd;olNC+Z}j&asL(;AxMU+ z+W7D$Wx;vSN5X(+bVMcnKI0nQXeOEHxO0`RaA3jsn+|%n$T~-Nq9J-i(pYH}M+oOH zJ@kygAj8DRJ>R*VVyAp0Zt9%Qq_kd;aI@v4V~*j3CqMWk9lO&)$58OZR?<#uDD)KF zha!TZar{kVy?l>^7$tX3aRIR**!(HOh&q*4;HaE@I-WNw!NIz{K6h!KHD&T3<43)*$E9AbKP2| z@WocJ0LHmdj@oo!`3L7;kZQ;(iAIK{cISexs?(0fbO2w~;S>FVljzd9NVE6L-SO_O z1wT_xH+mcx<3-cEOVI6VICpe(V@6ap=mY}r;>)%` z+5!}fb74j=xJD>>y(n&3LLOk{iBu33e=WaMJX-rOJrghe& zP|O9nMSBWb4{Cse{|QeCGo%)-a#FxQZtS3GpjDGRI}f5+1Fg{u(UqRjSd*0VK>2`_ zD0ZE1S9MBwWW6k*wl?#12pZ=4wew%?I244H9U2iS{QM& z)@E02N{Bj@O4IBihDT?)geyaXe0%^wOh=-Ufs{brQ-^y4sm^C-Ryj;ybkRd&WP^`R z_n@R@-Rc+zFP#Zb=shi=_Ep#6TM$o#uHr`zKG)+f&1@g3{DiUNri%o3JJAWSn%xA3 zJr&Z1;bomc5nClkwM=O~(+2SYn(HfiO-(e&b1kEHeIJnNQ@sb*Ij|t0FbpkQT=tU@ zbJCZ7E;koV-0w8a#ar$GS4sqC5iJCHAS=0JMh`uZ4pU@eTKSQ#l<0UGxMOGP2<>!T zxNPB$UO}EvBMQq`c<*#ov1dkO{=nS&bGOEb>7LphEL^i|%+I5E%=qdf@6C7DpgHh9 zLElWzU2mF?sbElpZ1?1*g#Sp{!J=D5`r6n#=27Gg3Og6BH47n|@F(0!|7SaSaStohjfx6nU?>d0p$#t4jOImK~W9r(n z!*EUFU%Q6TQ)*Ev(rTmWgj>TGQ*T!WWoo`&JyF$gIjr>cUM%bB2jJFNuy3IW@etZTc6%Ch3x3K{-NYGC#I1{J~=82143VGgR9CTrYyl z;E6Oy-zp!PnS-e`_cPV8my~oLv#YK)^}rStsz``uRb>UjSBAZO41#u-zwhk;{HNLf z;pSNJ-CP9xeSK}FW9V~nqqn&yM6SHcR0RoDnh4k?8BVYp=-cQYdKGb~sOX()4x!4S zpblD}XlY@Xt24p%1Fzzg$CJqUw&TOgoC%Tu+J1@J<&mCR|%Xx=bMuTM&E8G49B z+uPBTkIFKA;$71rT#km7CA|+bqt>tm#FC<96X`TaB@Q5)mw>Bcx*?T|DAmRmx&kFW zSx&W<_Uz>(-Pkdv#{%C|Yi{6n&+rEvSc1F?w5 zdmMih&Q@p3IaV`!+aTxd=?hS(qFoqxR=*m^7~@_Z3u{8ipSfO1cek(iRx>HOF>{&c z{$D>i$*(cT(H^)&%%Sz}-45LI+>6v|)2q45`q7I%45gyJHu++ttEI`nvHk0BbbWPl zcg>~MUTWP6vR&S7Tc^uJmEl|maUPJEa-dJv6b(Herp~F-pmhObK}D4#-J|rimix=4 zof;oVbmvAt5trO{>tUTjzVQrmf2qTbM-b)JJ9P|=%gSC&AtLj@9=m92RR*3@UsfJ* zsUd@Axvv!tK|TM3#IiLT#3Z7G=2pK6ab)(A#_}Qx_NGLH_%W_aW#WQAlNopKm>Q_` zm?>op|7zbbq1jR-J8-e!69Kh=!aVK>J+)nI)AHE*1_zPD>KFKg(X5l=6I^o0*4+nB5Ii7XCAWinvS{2v-`dOn_RHs43-riev`i-K!1n3mW?bAxV`adj zt(I1iXhr{h867EJ_jK2?)cMlw1n$&o7u_lVj-G;e!j72$IkqM0Wh>ZMEn>nYviQ^` zw?uny#Ot3{3L01XmHH~r84JNWzgeXG?9i&yNmRdLg?)Wp6_TYPy#h*td@Nj*rh-;8 z0FgW1vFBRS?(>>RQd@x!4-)Q`_iedpr=~9679h&BZHDQ6SX4;ATlCEk-@8p0L8Lwy z7m-5>Ey(uFA?ux*bu>9`In8@^EF((K9T@vf@xmJ{GppZSx%7th`VmZo3Vl86 zCq2Wu`gwVy=s}ov=Je(r87cK*Egey$ag8%Suc@X@$30gA-s|v=8o|d2KnwnTGhzAq z|3W4U8uCd1p#eG^V&^vWv_MRZx6TEI1zI@#MZJ-hIXEutrZVs7GjOA#T(+D}n;dnX zsCh|G;`S((!7W>(N@HIYKDstb-2-M2eFSiEd~;a`;-1&njRciUo3m~N@9>Y=98wh` zF-zecV-YAky5<{}>e}HfNq3~+{sPd~Zu>g_gzQfFG?=%#QWsH-P0KMcY)!g0kUjKT zcFG?Sd?=GRMIoA)JcDB@>B$EO5;;t6Oy@L!S~X`NPjXMvo;_{$QHxoZt3jVdW=`C# z&C{$A{=F}tMl48d44&r@@71&51TVN`5y4#Gi2DeGJ$zTcMejb<8?%G zToCxY)|FY+5h{VRAp?vbXwbI z)@!YXdbk~S2>f?V-AAf>a+vOHQJ8+lP+t&Q2Y3G=8JYoA?LT$a@tC{Gv&NS{XB@=a z?;$&919szE*~3KW5~@!TZf?|gE82W99NQbNJmx?^F53%sQ77zbs(tM%vS&ZX&ems7 zT$x%vyXR>Gc2qA68OjP8)GKzZ3fY3*Jcxn7WSg2wSzzCpyu!Uj(pJ&@5v z^sjW6$`g8-BA@Yyef7(Tef^_+IgWIRKo|;!28u!_C@nwnS7Ex?NgF0>E~4 z%o?3@z1qFwTe#dq%g! zB5Pu3ZIGYw9_Srsh{vHlXS%xW4vyzOwG)Z8)OnlQ=?jVpAe9QcI`XHx+p7qsCZwmp z%(3bDO*J_ygDeNu*ERL>tZzMQg@96L?R)# zUSbvMJxVNQ)y|tQPHKnd!xoa=^f4dE=Vt$<*OT&;m0BrlucAC6CAT!t6&5K#HwQ zVR`%7fF740e+9~wwSJg(ZAZK1sK?D5g)NcJF)ZC3i7zs+*U?zm2K|grWznLHWU7E%Vyn2 zHgx>FRGebs!PKr%O+lY07S%E+C6o|?mJ0v-gfOS(S8d20uy;5FUs>jKO%maQkZf%U zWW-rphJA6g->!q=p;jh(2J@lrG$6jwF2gAbD#NohMWk~*67ND@8u#t4V${$|5sqG0>qU`O!Vy#0xR(N4eFT$>dSHnM5LcDBKzP9Lpx)+^i= ziWhMK>g!u8>WfVEt%?k3CYga8lP$o!I^BMV(T?BXC5)BpIb?>DHd(e~WBUzw6)e8L z8s@V+%;;CPQ~+H%E)4mG&<|Y3xL|2xzKPZYBqTj zZEApm;xjp)QOyzrIq6{wNv^_F_yz6PshWC|kF&qwB z40r=G7^gE>O_+DT*?1suQ}(uy_+KH`nai2w6=IDdzR}-Cb$e5U54rodP4J)m;5FHU zNEk$WB1(U~^r?2z845PLT~<{c3i|uY{fYAI$MqG#ng(K<=et@j4_DNzno(Fr4r?Zh zQ?mp2u=8*1>hGFaM=nSwfxfV_jVwdXlAT_6S87dwGjN8T0g;6utTs_9V`dW5a+B@^9C%9d%F9f-Vs5~e!arUgOw(#=P#x)cnFScLf zKQXg;YU7+;iJgYs*Iuo!zG+v7;g1PX2FjD3$PndMbN%P~j7>czKH!N^_AtXrnv1IK z)o!7&vsaHRQtw=`oWE|q>+XMl&TKB=)akV}x-?K`k)cb@BuBvnlzq66svTs5LDZ#R z84i=9iSB}(t}Ya9DVof7@xu#$7kj~rx`DA3koOY>(&pD^Rhn_{U3}+IsbG>qjFy`u`(+6oBh8f|fSQmT@P1#XoVRhZ zufX#x@#B|?mYj~ZPJz#_d&I7le%|tr&k%lCAfsB<}p=som2sWsKR)Tu9v(VZqW{UV^|8@3(v}42u_VXSL1-OoO-v-VFo@Z&#<+?x$y<1z$O?DpIf2 z)_ag%=YRFnQx3jha-K)xWXdq*Cj}d@8${S=vQb>35wU#dL{s2*W<+BJjfD24F39x$ z;*{WixXUP0_*hL23y(?c81hF|=G0v+_mcZsX{zrKs`>V@V;Qt=(5 zt=xkQTiRh5FnGf-k?Fins1{cC4Dxd4sD>7i-R$p1hOAlLX}UT1i9bl8U?jOMD_BPq z-60l4VP|!$%*_x^%VEBfz0lA=5D+dnTXI85Z^145 zt*v2aj4&jA3mZ)fm8K3Z;zKPq*H?d03y+HubXkDG|~}dO(9FHD9B^&eUf3%m|AJdkWthopi5j zB&=ECIVT25M2_2}b$-Ia-VkqNI;A2)v7)79#B2(n%K|1>fAS7VcZb1?b; zH8gQ@&iTzIayuOSn@wNaFKuLzOq*jL1RkfZ-`y<>@mq3Z-yv)`I64kem0lB6j0Bn2 zV2np@p-EHRqcbVHoy4(}wVcul za+J8UI9>Q%{R($4@zq`5^%*$i)YjHA=CiRh`?I&ZF@T6+1!iO%H5v|J3P?XK91lRg zI6zYh&|%h5r+#}kgE@u-tjbOuD?gGR#1bXQZ_9>un~&d06z;A~Eq0cT#(b#xYIZN3 z#VKNH!@E}|zw$x$B4beItBDfd1Bo}UQ@$GYiRT)L|4i-EN->q-E!R+9gr%tZ1T~?` z-WXd<0@Ntn-vG7Z`u|Gd>%XUdaGLTvjy7K_v0p}sjBIPO+Y<^L6Ct*P*tO~wvz~y@ z_GRlU`4LM*>myIa74x2$fmHTkR87{&hJ))2r2~;P+F9lGL=l0={zTl&#nc0E&m+fz zVVz2(&ftGyZUO+p*Ku zwsOc$B|7f9@i|l!JLUH z%iI}>=x?85`|1WPWqM~MrE9Jz6VanFKmqzPd8M0%T?z4FNRX-J&Gvp`$*i-O*kfaV zbKj}u=`KJfuPV{qZ*N}Gamr@5t{L&J5@3CaGuX&@OP@SA0&t;4h88SGn?YVPKHOyK zTUuUJ+G?{(8D6FnOTwD*md}pT;x=JS4Qo2WFzjy{yrKYU?d;hMB(vX zUep7M2%vCte6Fe3=O{7(bC#}S_;iBWnYUyrx%2nysLcv3h;JU*kmpJhQ32L6=M1^4 zR3`41%&l&DDEDsuTEB2a=j|NB+E?M#8x!|)Bpg?@^f~ySUGSv@JG?dCVYz0ZYTU)Z%SHA={TjNpg1V?#2QS&?)ZzzIPb)E8P|g zJ}YQohRYDJ(-Apx@Bm9qwHs=cr=#Q*t%ing%p{*Dbtul6r4>2#y2F2X%4Ce5$3`q*+$g&!s+@2AmfF z&O1IkukY55{U!NlppGBVUf1c(V+2xxtEjWwoKasugGHsUcTIs(Zm>ShuN$3YT^_vH z*WpOiqtvy@OAH-;x~~$NkNO6#!9=7uW}#!ig3T92Uqx^569W3GV=K-m<=4LRoOPhK zmVfk0yIViBZFhC>v)IM-rV^lejs;ufs&(qfTo5ueIkFqr-JWpa!wmljg^kkfB4}1g?%TwWD-RyLgn4FLVzzj^D+2=7*BSJb@6<| z^)4#=nR{-g`c@lXc-3KGut$Dk@yXm7hO;^ByL&H~E{1uw4a_Kl*|dTl&uETqO2oJ4 zY9SKcJIj8ir0yu4$1p zGzu#Uvn}juBtMheRsC_xS$rsQG|NY}?c)2&;Y*)}BeG50xTCEZmT}EjXPr+6NIlq- z4ZeGq%#fOvPwKy8L&z0Pxbv~ZVWamvl*633#0U9~KFWFxGr)_uMt43^1J4a3%RIp8_{hF|XZeV#%LJ0oyX4x+c{A>h2P}`Db(8>U*h%zCTifc;3U?3`kF* zOZo*6dg$$+0jyCK6;q%z?~EhJK~9D9|Fr$*5SAKdc@4eXjN4Z!t^E)0Kx)7TK^r%e zU`6|AODou0poV$F7V|GF)_)#QhYx_soe0-3p#Bw=bHKwm*PJ_xAJICW4+~Qnxm~+c z>vMQXO;9KHu8v(j#01;lw%4py0f(j?Iv@D+kRk8BIY2_XM1QlBe2NiB*9`ED{IHA?qQ89a zP<8)H=4wK#08OUo!<@x912SVB_E*_h3G%`k@SvKmb_Ue}{kLC(X`EJqqBy}R@f?KUe8$~3`D;MKI8eW^%i|JWUZW57^*_=+4kxMvy z;+~TQ!hTKkLWFp2#C`2U;%Bs_z(3Y0KjS$1XEgmn${I)duL_~@oN@u57u4@*E5U-& z=|R2r`NwVB9_5C{tsYzz1V|tgfH`iBhbfC`?ZKuC7 zrRDdYVz%FtsajGjMdX9l)2cIj2!gQbVqwJrsW826Pe$y@YA+5@`pQ-V>u&X;B@*2`%8Ze$F>c7%nqK7OYvAzC_GJYjf2mKVykWqkF&@iIH&QaId+= zMeLg%h6i=lXS-i?+m48Gc5V#I-4DwwQZzf4I_#x=Rr^ry*o93CHiHKc>alac<*5X- zOCD#ZJOQd<0MmjcSn&a}dmN6Qd4U2x93VDAs9weBjief99%Rf|WEGhqR=gj`40>Qe zOYMxo3EY=;US`$`I!|T{5c~U!H;~tZNn>MdG{S|=z2TA=Eq91+FU1eSbPC3nvadwZ zAT7JVE)l>kU!kRt%oi-lr-0Yq)S33hL|b!VZ8XIgkc{q?-ic*!R zvjY9mO2)aLMw95T4`F(9g|Jr-!%8Z16(8@Xu6{rHsVJ1?gx1(kL*GZo&K_Q!I&-*z zH8NcFo2|g%6{-)+eF(_3fd@U)thAU5vhkdKw7{7ga9uz`w15uCH9!gv!_Wtxc4oM~ z3@N)0Y0O=(n=Wr`*gRqw^w%}Xy0^d~4pn?WE+nui^*qj1=(Sw$EeIVWoH)3|Fu8*+ zL>iD;mqK3RaT;oX(FZE{Bkog1SK0+;U5A|z;JgPlUtWV$Qqi}3?^ID!yMpES#HuFc zf3sb+SoYEOKfqmTzDJ!?>F>G5WdhqJ`Z%eO3KvUOHxo<* z6*4wxbRfepyfeSs5u=lt3lxmLplPdkUPV+t?rd9o3l&?(xV6nahGleswy+w7k*(tW z-W>RJV)%8xCQ_-*Z?>mnk4rKVTF;jj_Skcl)%CPL9Nih*Ui~?^NxXY#|6n0Pr+y#B z`%oyAJAf)1ou|Owuf9uroiZX?l1ok?u zkQcsyd3nCsr@L&1lImO2zJw+1O-2iuC6GZ8LAFo< z$kqJqK^QeuGI};vsm-;sZGaW3FLucgz6~f3x|fw~W-``f>NI!n~*? ztl*=a!McMm4SO#yE=TNu2=_e3dLP585*d~2&{s6nj zKuiI)-n8Qh=gFLRf3pqiZ=q#Hlj#re&+U3iVJZo(tm|qmI=FFxLg3I1y72oK=`n?j zHf!=EZhL6T(4+@2wg3(>VJv1JxQ-!Bb4>d^$9Bfo*(3J@+S!%K3XN;$v`>>!3wv8T zD(hhC0PIYR=x(R|?l6`Hr~-e7JitA_!E$CO07go(H})4VWru-iion=qvDV+ULw176 zbwOxNS!EDCk#$}iH>>OeG&7gDDAxY zkKY}{Lj7~U2EWdSq!1rZA@tSbk|Lo$57vu(%hwxTd|g@B6nKo_u3V46=U~3;e?Wy3h1~>=OvD);}?T zk3v&ji_jbI3t1O}V$oYS7XX!NJhuEdFL&-bqH76_^gsf717$-^ z>|cT?7`6X`4s$=d%VcY-$9M6$lkOzGKFJ&yd zLLe42ySmEirFDQPy=uu?S`LNphP^iE1#`~+W>YD+4qVQ!fB4tk$JRM+?IBeSR~|22 zh3q^Wj=#7DJbMka+wj1UN#?HafhyyMuuroxdQB{Jb@>AZwYAO6YQ2zDhY|okesEaH7+9Rb6;^cJ8dqSb*llnCMlN%}hh(7Te^hvMrK-kk|7sh?+ zr{=ulv~^BnzZCQMuSI&bD=E9#00751fw+rb{B#BGbfUEZ_>=VHPQN{kwfG`q!u-9{YwS4EAtAw9@y_)8|NV$rPv&)nl4Yc<7@@T%Q$<`ER zT`bFE?(93vyQAFzfQ)6ziDh8u2wk``M>Sz6z=Ujn*!;u@F?1IP@Esq00rCE2s^!GK zt=u(;TZRsX^x6##S;<~q|K&d;xRW-rjg+c&HWA49&Gu%A=<@ltft%&lwRT4G!Vg>X zI~D1u^@Gn4833UBzkvZHh1s4KW7&G|t<<_n@bbRR7dRZPowhGQ{>*551Kccgp@*?= zR~PMU6~V)iFCz)G9hTGbb&B(8?LBSZ7fQ)`*gYvW(mNAlJs`A&CnM5 z-JC8tqc4Bg?lZdv@UQ^#s5lM^2)uhV1~b|$)zTqqR#?RSo9#M(7nrHo?hJ-~W=S{E z9K$yz0M2P&DW-s0`{p5TMXK~*anRkZ0R35mzyxAot70B&Ev&@S9mT8xahV^a5`vRR zUmsz)4;@7T_mlQZ1y6LD|^ zdK}-|m^k)aR$iXLO(AJ|sdf1-P8)?5wv_ca3f~(VpX6FC`zcEK2=U`E)h zyl6qo`JyIOj7@cU-5$VZ?na11H1OKl&ZG#5HKSHE6ig`c$nOZFOwN*>ZagOO2Q9m19kC5-DohxhN zchu+@?!Mi%al25R@~A%QTt|yk)u%$|M76$tNUUBcxjB$&5XUeWp!av&35vN^tMOr} zM_nb|)k=H`S7cl0c)~!yK}EFWdyKe?oQHAVT%j6XeG`sEomjGZ zN=UAjVJV?{A?*JB<&K8*H0qr7**d)$?3wmiS?}4yek9Vk$Kz#S7bt!Lj zb1BEuOAqW=-|N8D?p?byy_W2KSX8yF1_Xq>ajQXkr+%y6+go08Smo?_Wmk(4dGGw( zfW$W!YLmWv)xx=L-rqwhQDi_|^TWQ^yt!KWPh5XR$-QWq=unm2Kzapl(7*Cn_S#R< zNQ(vRTQ+Bh3eu9}b(xuzdV-l-T#{%RyrJ>-qi1(B{iM9yc<)&^f3KC*fyCq@BG`fCd_Wre3M%$r=R$@<;7z!*M&*lF z-quW2_m}Iu^K5jKX+7Oi9hJ`bB5T~|-{&L1HB;agYvgy-?@xJ0u!A&vZ)&MA{LKhM zm~!6qinq^&Crh@rhi{*8{1Gc`Iy|%&JJ-z?1Sr*+=^-U}Jx@6HNh~8Y_nSP~i5dn; za$1hlxZ}Dr_*f$QlNu=J;)`EIRtDv=@-q|Mr|wg?mjbyx`Kpv(Hk<$K<&cu*7K%TA z0j`6lH(?V)yN&xOi;RbvfjM4?B-}y(^rf&0#(2u9vh`6^+Ni*l{V{@cQ`FIJC&o%s%g_xpUybE)Pu^3)?cxjlyUStm+bav|WkzmgD`g4sA zNcbbNVr2g6JRW@R;~^t4jT8)so;Qt-@gJs{#pf?i9c!ZLF~7wZPsAezETosSvVPqo zoc(-oYxCw{@5vr;*xWnK^XFT`-)BVxj+CmmV%!|-oN+(dNrTAwg`P%mR1JueVNKnA zDo*e(HI?rQk$mp8WmTWF)OWqV$WEJCHgu;~E7&9D%8{ctJ*RF_?pNrOdNQ@=^Juvy z>*ss1S$H<1_lttx%8zBe!tBCOK$iX}tey zeNc`_o}>R!NUi=)C9oikeCGiwf=g@_STkV5%R0%w z+44jhjF-07=^K&2ocX@OFQsC_!al#}ijDI_gd}k#ZxS=v|AUi}hR&^>`fj4~>P1a=S=8T|Lugf^ z|JTQQqFjX$@2m4{T1Cn=wxq4zIlP*+Nb^)mld4|e%XT;WAmo0cAj<;qU-#cKxTJab z$yX{eyGg0tMM(%JppOiDz%CqG%2J<<*G_6 zI~?7qVNd`1ss8*hT7>8A3*93T$1g-i9RGR7;6mj0vDNG=vkO9xHVUJ^E)P)>8n0Zp zQu6k}O2H*0Oq2_9MJ-cIuerWUHd1|cmKBC194MiZrY@UsXHt_63jn4Q)_&#CtJAM7 z!>(yr21VU+KSpssRhuU0*PM4F##cy9QbNGExoj9$Di3rxZ{fx>WS9fa_v3_*&gbIIs7?&(VJAm?ciEi+#l+rmsJP|kj4qQ!c z!&ffu2PJ->;h(LB)%QL_iE<=!Ez%qfqU@9>Elgf(8RYfyIHnLuW}_Dg_wHdYVbt}E znTh`pfc2nr&^T5?pxx4(meb_t^BWXI)28He2Dh)K3ws)cQ)5Wmz?7@r9le@!8V{{X zOmiERByQOL0y|M9yO;{EB--xi!9r;MibP(7iOrPg+2WLYu2*UXw48+aRBMQl@!lqT z$p?o4DHrcW^A%Tv z8=aR`EdTwXiCu}OfZ6~{yQ-Zc>SRSZ1#=1srr5keBkBxjJx_xXaz!tWiYn#N$(^?5 zOAz1r{{Gsr4g-<|5Yr*ob2I^Fpa zkYejn4P?}1+~XznW^3b&d8>P6DCYF~>P@Ork^;{s5YJVC-iU0+lQ^sHLCWQkuXJly z7YXD1qh0E^Bu?2ls#`@teOMoVHt6u5$iFsQGNAgWB{V-axF%V=OY@&8>``9vE_h3vzRjvUEzxLR!HxOlm+f(ZUKZ<6~n%qZPO zsa1Gjqxm=6rQRXykJoBVUe7$fRdrsEIP&NkE}3%0W0dJ%Gg%$#wW~7c>*QEaTZP+hElA!8VnY6ji2>#n zZt?%YGd2M{<=(YTEto6^pAAC26Y6~F9=d(IH50(ank>veQHF;OOI1~!G(*X z>50|zEp>!J938E_B|PRm84l3|p!I(rWxv@9S2cgL{RNb$&)N^a0moo{zD4KcwrKj) zEx7OQ9m@fr5FLVk`Bnz9xC;d^RPsx_I%|Pq;GF}<7BUK%YS5(7vEt2{HH|zdpP=C=|8sm=W@xP9OuwJ6zK6M z(X#+^^Ua-Ms(yyA?@qW9B_@VpNu@B~R0^c;LSbCLJglhq8vw=ft@z`H=hLvqkBF&5 zD#=uPOiWA{x(XY~KzsSjPlc(`qY%OqB=jt)lch*lQ_fnLI3zuf7)9~Lv!5h2Xr>)v z!F0Hh-*#v4-9FKfGs>%gQUw*-1!kvLjJtZ3%QPOtxs)6~A%4ZEyEyu$Pm1Bs`;W|D zgI5c8im9fUp7n7@piLjG{4q0=UO1DRUxB>e(|Ql)Nwj|DEJsc4ZP)ZxNmMeq|A^!A zqa_YD(2m+*uOe%7+F5=k)+D#qF>!xKkaMZ{0TMQ88Ml0VR`E+!szZvs~N+ zx*g9Aw!Z9NMbeDIEoJeo#hrI_V`G~5O)h*Jb;_kkTRG_0cyrBqxWJ41^vi{CX>tw$ z_6*D6{)9)5CxCM92q*9M*}$M0AS-;y%DU|2yo1|jp`ZGld(e<69g6&r8~oGoT{I6| zBHjL+l!1^3M^n?8k`fN3Gi>5xOEGPbalAhDQkFXf%4bLNL@75$Yepr_*-%KG%TmYq zMqhrsTC;JC^f=IZTu9kwt#ZwMPi)7SYUM0T&(6%*67_<#?|{lR8uJV;<%<`?^KUMV ze;Z4oY#R!xys$udqwSArn+2~jA2X#7z`qDHwN9D0yM4a}Ju8e)0t?9S8ANwkF60Sr zT{J^xcUUcm7=J31dhsqM19#jiG1%U!kRMFJ=YwPS3anuqZ2}^46(=Z<9`!E#C{r;w zN$8|Vs=N688Yz^}xLthmad}+gyDLLE$0rHd1gP$hM?k{O8Z{D6`SX$%PmdL^Hj;hG zxJvS>{qqX)YH{>hB87f@A9v+BuqoTW{Z;hNkRvNlqG5kid! z-JzpRXmYWXaJet@ou;GJ118^Mr>2NY_W4OB(j>=H=~qgIN4}?=KgG6J=FU%@KOCo+ zbgJh;eHesmVLZ?^b56hs5NYV0_A7ZEg50kUu0Obf2g#U9N=X&grs;%yER^yuG1gh* zkFNUqK=%zzhhH8G6YZ;fEp*KNwWYDuM`YO-mHdc8dr3Sm?1zxh=@hkO4DO*eGlUYYQC1Krj_4){FleK<@w?r%cXku*Uegz>{h*G zSAA5JyxzQjlb`u*=5M_p3zT?Aj^$a=aeJpJWs_Fxn%d0io@~a@aq}?$gsNQ?^5M_J$vmII4Vrlb93DV6Ho;5Bf{fdv9>gVNayoj><_Uc24 zWw!a7N8dtIXB4#p-O}$pdhIRV4pr!rv=N*?cg`k_JvQ&i#N-t-`4o$|ofp2v93AGV z#d^js21GwEo~rnmNKn*Ysk|b7_#)f$_6rG>{}*lV9nSXu{|oEXnnh8Rv_;i!jZ#9o zP!vV&)lxOmP_=?cT8i4OSrk>PMx^$Z5~HY1RLmqs?ZgZspZone*Y7^p_ndRz_doYJ zeTIUYp73z%9D@h`oKyWRWm0Oh$gbI_S?@ z*-X#vNpXVBV|;mySM>%jo8APOZEh*XOi$E=$NR~b>&v|NrllMiF6 zhArL&pCVv=@L4a++N3JiSaqeJ?qQtnTz#{uoW^F(SEha{?Dr3!RpT!bkw2YWi)prY zm#PB>F2ZDgpBQ@{8MEdX>ZQjR z#XdW}?-uR-W!F+H-glbeh(%9u_#z8l>x-0~B$G_bp4TSp#2KN!x{_N~=KA`a4nk6f zrEZS5>cFR6cug{a>ghE`*(o$PlQxaH(e|c9z4n~4fbs!O3t0XPW6PP}VqGwt@M0*4 zusJv0A^!o%MZIV*I0m#3J=RfbUf}z{M7M@EUFh{3pXb46A1wsysYKj5mno1OhD7$d z`LjJSQm$@)V&{3JH2gru%l1Q0BpbKd`_ET>I2Td~Ru7*2=xzf;aZ}#O57dZL<`P_~ zqh)`MOJaJxFBjwoNL?(K^0|3Xz|Z^v6Ok!6m?>i*Wr`cqL-yxo-q{-GdfnpPR(G(F zh8Ia@b5_$s>%kHBJi;u-zAic`=vVBov;=*f%(>tH?e{8T?(O3p5E}o6wzaY+oa>v; zAN!v6pv^;e;k{l&Zs!ZF?TdTPzxKaL3r!HyNkZyH==DyQEa`w3;U`v=zK4l|HY>(Q zj@-dnBZT2S#8k@&K_@XIBQZ6d-ZV9{jzPjy1}V+Sh8{*Pnn4bU3AXr!MXW5w!y$05 zR<4oP6;sl}$>@7RH$BdTxMAb^NNH6?z_39YF-iTyi;(+q3US=BnkJ6IlpjYSf5|bww zLGNq{@U^+oFMud8Sbzk03jQy#D;KNT)o^B2>0Kt3wJnvIf_Knmj=JA`o|(O~U}ir3 zwl`eh*Oe$5l+4`LNzs}Ltoc}>OKdzyS2D5JRq8G1m946{pnsoyonQ`|mDZmBVeZ*f zl0|ihSVx!=*KO`MnoXcwWQ-s6l)^Udr&QM){CrC>_ zg!nlZ+AC%22@xpEYs)50g)f(v2b0Zur9Rs}{#In3Yl4W?HF(p{m2m9T2Dw<7&90hp zCMG8u1CHSNkgEgkSupWr{m|O)t_?h^?4(5k!9Kw^zw0C6G zhQ-h`Fs;_K-R@KI4m~Z66~Qyg)PrBl>kh2PZ_XWOwy<$DH*lye{^%TOVLoL-D7)I0 zkG0LvaN7fz5zLji(Hf_6FB7$7G~;+%ZIP&})4iU%p|RVn$A(|Ii_i$A87(sZgU7Y@h`) z+D61o{xm+c>&JLqdY%r7kVffGM)Volir5=uJH(DUf7MX2CNh; zye&$$bUlt87K1MtYhC;|@9lpaa{hn3mW~?*hz5t`RS+XeZw>mNuLu#H0F&w;CZ#jL z>ZIZAe;Q$ynf!-ECfkb70V9lX12cu9Nl2{aP&&y(z!ni3$q*a?LrI+64(_jMJP)b7 zP1X?+U`XEIeN3S(bmXu9)ivczk0X=n=9&t%DwT9(8uZmj>x;vfn*ACFo7T$$U%d9Y zp3ywT!c<~9q>{zWE0bKG|Jb>*V$p7%RlYoOLrLfjLH}6dgf(SU`K30#rEEOljL=2CE&d0AQ5>W-rWPNfmSth){T+LQ_u@CCTegpz_WB zECr=}tLLlFSNcmri}ZLU^q!QccAFF-%@a_0a8vTjd!&3@+?kM8$BNQ%Ib0XN&56ho zBObxefSIHg>4u}5XRRS0FtWpNn=qGfCg62Cv5PfdqOn-4yZ;4w~MdT9MmvNzkObu9CG^#9FiWLq+{_Z>gQ{R(dI{ARB?y+h_}B z6At6lJqHSzyty`UWI{tO0nUI?bbjjD0pltNfpLn;n_s)vHPIHHu3^)vDP9wI7-VU4 zWB-9s!-$yA%i6}Y+w}*WycywACqANf@g4qx$~uh8OdvF^lFsd)o$$HQ14|jaTxei*cmky`BJ=$GXS0XH+AY z_o*%=9_^n>Z`xP>{#5uOs(dK%uaqnFNFO0~B$7%Bwx1;Phso0GCJm_biM^y7u+dl& zt`k3jkHx*^eMvn@OE{N$O~Z zn>0L_NyYs`v8rCzA0#~g-p1Z!yEv?x4WRgjvq08V#H6xN_SxEY(0Rf^0*IHcdSvtg zO{}?VS1`JxToc;fV1hfx>F@2GcRSU?zu4XTn*b;7acOB!k6BW(^Roxir$S0O&Rq#P zCwhefJ^cRPPLjrP8bj_l{xOxW+KYnf@E5U#<--r=Y6r#ZJewL&v9RvJzJ`SF39DVW zRK+{eyoo0gfmR{_amt>^-TPP>?9`FPJd}s`wLH^@X_r6UcQ%4#)zNk~`ia_1QmhN< zf)fnCQ}Y8(lz_68GtN-PFA|Giw(xr)SwVVJo6^6fiy0lgGf%Um!jxlL^$4ap zpPz}F&1jBu@djvS2_EOp$kJ54Jcmm!$IjWKaGz&9pL5(K#?%BL$_MkvhHyfFoKs}h zgZ7I7Kv>#KgnWAy#cScq>ohyvx?zW|VBRC4fBoN%sXA@5P`u_@TBRW^HLoyD26>6f zYE5(K^wL%^nIY_1>4YoSn%Q=HE6;{!P9$F$nVKJ80L5vFz&D9m^(x^lSK$xZ?b*hk zP1!uAUQZ0lo9e#c_>DB-L5%d26%)NVW37Zw%(1$f{?OTpro&Cq;e8!zVe~=avByac zL=x45rR8K*bwzGGuAxpZ0XnniURxan&Eg6KEkoXCKZ-v?_iokUCq{g@!^!Q)%j>|C zpu;7Djr?(;Vkrd@zODLPu~hknZUf3X>-^kGkHX%i^g-K!I6w-yz)AR@#E?1eKr+<$ z3^1F@3Lj3aG>z~66(5HkN(VELfOyGtW_4i&vdRF}p?3cQ)DH+|mrOq+Tz8B*b~+et z@R;`T+t~&dWq=_D)VX3Z7xf0f2+gTt@(*N`-@4_RU>Z#KUa zBjF2eiNth4cL!xAZI+v}3$px!!}3;uc6fPi`(St6Z0s>@VG*fo~IgEg9kysGpb4Gf{s@ ztQZ+DXF)D=-1tZgn0o@+-)z3l&%fV4Fw4ScJ{DL^Yu}=cSleX{cByjTY3Rn{etk z`u{$Flc^8FxNq%n{Js>slOuiP3|w9W;t7Nw$UqAdJpTO+dVF%SJ)6B+$x-BK1~EQr z7kh#rl-R$FQ3_^$JZkkh^^<&pF2}w*<9Zc9TR-oH+T*VHhiUjdW(C4HmrU}lqp?$+ z+En=OWE;{BsPfh$>+^3M)+`8NvDK7X)65?>Gxt2jitCIuzk$x=J2?zNw0@hpBYZQ6 zP7-Ukz>1MY*8CL!v$a=dk5?A|#H@J4-YgFVAiCA5p>AHxLl_y7t4WLcJH*{z+n4Y$ z0mV8p{jbyF9>n8H+>1s#$Ztx`%xex~gxsYb)2|%o0%q53`LtbX5V7q(Y>h5KJ=Nae z@kzig?=O3ikuPzg2GEJr4VEdbzRkV*@axH+ClTgY3+&vFf0#H>M4sa!nI@R$*ml;>W<$tfu#h1ph<>Uk?8N+NWK@BJ^oZe+aCK91inrG;jW*LI=+DUv2FLU9m0CIYb66t+HAf$M{4{$?~*;9{twf=mYB2|o?8@Bikab) zIQgr;%l{zE#Kl(B;Dqh2=Tz>v6<&>M>sy}w5!p^O?6T1(CxuNO_r+bOJI`pL(g5XC zuT0j|?B*qRD?bUg{Hj8um7S74+1$wRbpY9vJ``g>{}mhl&kqgU-iv>j5)+QI{$X0H zLr4JHgZ;ahzoHBlgE{EzB=jMz+l0*>zK+ z@Q35=fTW;ocls{;b>uUE6<<+krz;ctdpuBlBUyMlvv)$KO}+8xp{rA4^1v1iIw{&!sm^-C<9HQ z^J|xEdjk#@h^R@4Ekk#v8mdkGX{E5UIqf_q> z`*c3Pj^hqLTc&>Ee!i`;UP73efMI3)ipoR__bNb7!r7fNp+tnYfZ#z47U&VTn{{d| zSk|S`hSlgi{l)lBvmqI$DLI68j!+SJG*^vm@R&5~c?w$UXS?`w6vJ>A2tPIDz6iU=G+X!&nTS_RA@ z{MxA;FL&OQ&bC{7-p+|jF;1h%IcF`}vM00xII7ENj*bGcG@C!FVBtPolk-i4~Jo1fj26<<}rfAgOnWel_vQ=IjqDt;GAk=6*M%cJ>5(q9(d_Ep;Q ziJ;}(*z5!6zb)u}i{J4gt-BC6e-6a92-EoayNv#L635rDM0Wmr6whdg%dE(sWp?tq zg)VjoHexcmdyKNJNk8yit!_LrLwDeF_td{%KQb)y*zj;}a9Bv)OQS5|JkKKlMlf+R zlz`8~&}B!Oe9JIzs|`TLi54R-DAj%4O~bIYUELQccK2n~@I<8{f29$9b*rj`KAD(d zmA}G(u1w!>3_|3gVmmk|gyteTPz27_boQ+10tAezFg>}VmUQU+ZDKin-~&SAxeIT2 z;dh&BkwUPJ1~Ihch>d>Sz00VS7IkVtGgYV)d4swcb-g;fT~mJCSN_5(r;`)3z>;;{ zWJSN8{A1)*Zuo#vd05`ot2MM^9IMTXxJyabSPngN9o~b3iAxt=-ihFwtGs+h?~Ag& zWWQInV%Fu(1-ezXX`d7+Kid?y@C2PfTJ8T*JZ6QxFy6WtmnFYq_M_VDjEM+mEbqw>{mREJ8fD-WVma481OZgLw z!Px8U4V87SBMSs9m2uwY=$75>#;*Z@&sLKzRi0QI`$FV2<*v;?Oy~ddc@{3G_;GP^ z9tg7U_zhj_Ep?47bl;Z|vBG4T^!uUQvI&hH4iqDh0FdmLp~(biB}URbiI6bBR|#xE zj`STk>xR76LK`We)pTk+Xjk!{xjKs8P-YF1)x!H!W$U@h$!@$%T}tR-g{g7&JFp`s z*P%zE_Q+ohO`7bSL!=03#B+BVO6-dDh4yLV+OZtF$G&n+eNF~8u8ZMYEB*SDpFF_5 zHx_D=*X8*ZWw_Q`P5|{b^A8ZMNQ_ISHUfEkI!AYDM{}L2PTvr!v|O@8`pCc2&9^E~ z;;Qh|2L;n*#SNl357>OVGq=(;B8h+4C;$*z{G1W%@%l`AN{NsrmGR+d86^8)o3Pim zS|4B38A|oSE0k7V8wLq_cRs(lv(#Ee{_N~9aZrpSz2@o{kL!`N$P!RJW&@jcZ?nl+ zlS$g@R|oQm`)B5TDu0{nX1p}3^S?=4i301S6#u|EN*5z^dc!)qgp!L#sq*r1JPOH| z%q+H1G%3%v>{E2HV+`FP9|0bEMZZOy<)wMH0`jr=uu{|RTFPPxS(Z=O)N(|$lFex( zmw#P()EXfKLp#-5uIEObxP0rNi_X)?Fbdjp0eWu!H`HH#W#X(bLj}IM7ey-}ZN`AO zHlH<7jiPrst8I0hIVY;7<$BKHCm#>_WH=5hx1{D-YMqHq(3KgfdJ^eTSXZCmHm`XS zVbe_Gq$U!_J7v|itEue!xB(t3c*5kv=EjuHanqFFkIt0tzt#fRP=ATtNl|^_+;t9m zXmn$4L$Z~|Z1hf1ul#pio8~1F4a%hwd)R(UV9Y}pliHT}+smYIJ$E<#WA5(M!> zI@O%4{7q+#c`dPEnNUmTYb5!m6g_t?vg)RK?!~ytDzjS3%f5KgL{+>BmlI97e&eS? z%KfJ?F>))uV4<EJ*!K5~Lb@3mG^u74_p zBCk@7+Bq-k&y}EGZjI~Jq;_q)D@+k|O0-;Lr#>yu5@dcn!bgpKNd5h2{{nb>&-D0v zlM^7Sepx}o%^ibu05Prs-bdIQC%B(^oQRR^;@V*et2jPtVDMe%J z4Cg3-YS-{(T3?DvawQWDNS9HjZM0O}>)tm_DFJxrkltG@3NPu#?bKe%<*`)?+B!`G zQF(zF-DZY!T+-nRYj>1f*v+wOt5)qV6EpxI ze;HiV)OY#ESw1ipjR(Z*xpLU=Hu?oK?&)@=*I^19=U$9drKadA^M#LF#G5oWDs)vq zCT2-y@o`8lWM3B7ag?SEvKJ|W2TRTgi)ztuM>heLaa?E~yo5B{&T~Oiyir7IR0*cv zu2R!b-R+$>K&-Q#5U;nwY;L3&tsTh&q#Hg!nWm4>P-#WQWS?l^W^_B8Yhj@Yk#C2c z&Bo{x*^MlMVagwN&u#RVdk1I~cN9(D^W>gzOIL4&a-=8Ng88rC39Is?fan(+acXB1 zDTOA~@wH%z5lN*3s6m0+k$Lb_r0L&>+2Olz3!ENvL^Zp=-Vho;ZZ)P<&L9k}$(Cgh-~O^m`tlp% z;UB4k;+QFlK)+q)LSizQ%$`fpaf5UFkxraAS9#mF+nbFVSvl*uZ)pl@(wCucN-gvA9E(555DFtacPb zENPQFf2{vASl)LIZE86RgH;)bi#Eh9_PFj^?$8{Ynz^kAf9|D~E4eUrr?pg60PYJx8om&_>mOV{`2%rN%V#r84ae7(`03iT=4L;8;Kv=nTkQ z3P?Ra-uVPad>(qZ7Tn_SAzYgI++c609_|2(}EK#G|D`=co?v+J+h z{~eGfokZ=5gwaKbFbliP#NRbFpJcu{KCpG@DNuiOmnlKe5qDkE9E(%KcAP7sLBPSPKMzkUy4T)VflHEYnYOX6+dTyE#Ma4x=<%&f{E!OeSb!& zdiEvXq~%(kn@NbSN@*U(`CDV)b)+}^OA%H6gWkudtLf#oJ_KLZ7^lllmqaIdKSqd( zd_f9h2dZydiMWZSXv>S0Z9Q6h^LwKs?eo4&dqPm6foW*=&tqx2>hF>VJfF$biy1!D zOH*dJ>xwviKCe)f~pG^Nz#=xu3-1e8H-lme$JLV^T@qP2Ud(nm!iL@z$?K^Jl!t*mX!*utyS4_w`PFfZP7P`4L%GInd6KTSGh{MkL0Y}o`a zL3%-Hhi^1i3;(sOEz{TYxOm8PXFW#bKsbnMr+g9CgF$ApG-n!3U6c=FB4Vy6lgG!#Qa|L38FWHoFQn8nb5T zC{2ykJvRIDRDIV}tzi4v9P;ooLw;-XYoBtcJ0bh{1Ly+0KhSmXRQ#*B=G9-&v7PXK zOz{f<68x8P!pTU#0y4ysqqd?L+Ef>Y9`>k!2nJI67DW%Y{v1C5Mzwj%O#sB;MeRhH z(>Zz{14a_Qg-4bDFfH5l*%zz*54%aEuIy|Hec&6*A!hH<@#kG5fhKJ<&tbc=_wANX8Md*)zonD3k~%*cY~>@TfYN&{MQjkM zX3OiKe&q1;!uea8I;{FO<36mGRCD8fw@(XjbmqbGon-cMDH)O8fy4(_;0HX7R z?g8Sw)gZtHH9{>uvg~<`OhZjAc|h1^)(#?+pCZozkG?y?l>zc&*AzSTZ7F>m_(nLR zvy0*S+twd+p+yGeAR@TWc?t1!gf98!6{CMVu}kEj=26szkFVcePI-2h{mTXa7d``B zNJUyL89I63iQZj3x;XQqHl&ldK38M-p* z%5HKiS!#DG{rlp$ytu07%gm(u{2sK6zaPRcpi4kb-;B6!DijrFp_k9XnL435nbx0o zJXx^rIz6ql{?bq6I?y;?A-e-N(lp}Y=VD!2Q-?**DQ7Zww#c_M)qafs#ONb6L))!i z{SrDu4!Y7`ZwI6ut9DN#Q7OjMVuD&+4~^W}y~b`TCH3i7HFu;c~azbKBXV8$rkeB}O^1A+ zwwZLT<+ka4?!{f!XSk4?eiDzL=#6j_Jsq;Q9shAplb41f3UQv6Xg7-FZz#34&kc}G zjquUnLX$^#BSLgSprNvD6&0HjlUZGEN*tu!Y)B3miUIDWBl3*`?p3mIc$)mjsokgebUU|NCT~}<; z#yzWXxjKXXbBjf~8sBmmzKUxITxpmMtBzGMR9CULwf+$3df>!;<_a_v&l@3>^{ zze|i-+EmhTF#0j#t3R!|GH!_Hc2jEX&Xqi~^ZJov+WHh_^>V0_$jx9KrO|JD&>+w8 z+On_C#x|*0g5zt~hu1NHI9pD+5iYm$L2&waLu_^L#oCl%WwvdZewMB(?yk4e1f($i z68<{%B7O!!$*OeDQ;G1+SalQUA?WNKJrYeBvnLVmI(>BhJo)%<|A}bb!?0>$pc|x0| zIV`@-0XpN8GrocT^niOisN+k!Jr)*)sIDZC4)8Pa_c9H$A%l$$L;Gv*Fwu|+=v;8c zuZ#bR)>BSI%%VOZl^l)gO+o>eD~En|lzbR}T#vb?4y=cSDcP(k$*b(3~i_odlf-$L!7-(dOgaCJhwY%0^$VsK{;4 z{_MM|>D%Tp*fc@=ODX-b@0ylb+F5^-j8Mnu(%p5?-a}f;Vce(h%Uhi9|6!_>4M9&Q zw{sAZ?5~6iO z5nZQ{L9MxI>ydU!#y)&X_>P=XUDn=}6Y10TS9%+>d+3g&CL_+^tEwS);y8Ju@^QAo zVHvS{)hpHeVw$TJ(v%^uBP;Qv~2mt{V&Va=f7ieC{FK}y+A`p*W2fA z`P^`Xhrag?d}3Uvmm+d$&0g4fxFn{yI*IHcSEx2-`*gTSZ1l^uS*jhCA19(l)5qAr zT}UFK!NlOeRqYXFS{3?Xn_Q~i*xZr9`>igvifF$SxyOpHvTmDOiE5vL9(+Eox^5E> zw6ONo7=xm}Ci6}S3@*xnQbn2Y{a%1syB9+%q23?MXSCrF2EHyt zRl6U+l$8@U)TEYi?}P|Up`W#T^jUDhig!Pp;emphD|IGgwSs< zNo2hV*kA2KnIFqg>PPA4DP3x$$JhB!WWStxknE%g=HTFU8~W*Zi>~~IEOPcHMlCt% zhgZ_g-ArBe@q82A?^UUDsE1&C3(#foqwWeBJA|K6@@u^F`=8`;g+jH@1WP&5UzB*h z%p0BA6&?MG7%eS1YhJe^d)?aYOk_O}F{4lE0w_lzD#&rrrD}&0aEm9jA|iy~P4oeibH6RCt$nOMPRR&9&PVN)s9t|0q2evT6+MsC9^Vl@x`1G% zl^F>3h*L70~QJ>sCu;V{W2IVJ* zJ7{3-Jo}4M-OfXDccc zbpe1bi(G=LR zI}?37(M<+_D5Ry7p67qcuqBFcej&WaKm3u!GK405=k6Yjs^^2y~|w7_Ic5xo{u2 z2S3>3KaN1T$d8mZ6s{PS~ z^qW--Y|62!$)<7K#>%a%1yfsAxG=kx15||}TH}#1LSY=K>VUXQ)1k1=WyO|t5N(Vi zQJ0%c7~F$&BT^*Si~$G-`Rg6M{leH0+gO*?GwmU61LZ+^d4AV&+S^=HJUUO?-m?P^ zR|(*IdL9Y+i#$P{4R7)!&ho%}+eEGoZphHRC<7#wFb{+3Q2qK{{y7M+F9PHs=QWPRfv7yA+G^@-EcT^*B#L)uHWqTG%Fh4s zNRvUZ7kOHbaMU)Vk!pa)_)J1OhGm{*b)JnQ<^>sDnCWv(h}O7!?=O(G2`tGbs6l(? zz#o{2Y|Cb4+@?E>ApFT*l#!0~g)W>hqWq-TNv%BnlCOgkmxm$ifx|HWi6W4^bGPL3~4Br>q$|D8n;@Ye|KZa zu&VK`zevMn$PY~WEH8o+ASqb_uJ05`8z=(Y%|oV#Yb?Sh^2y%-p%sKp{`Ah(sQ}Ms{RZIjny=f+IwZ%K=xU^#=w3 z8GeAu{D`JO7G@UP#j zdTdKB%uSlh_qE2#z* z&|nFzaz*X&T4O5%@V>khK(}&QuZUY+}q|5{aN;+TnM>t-!KM9U7w*?CZKcOGu5gQo>doj9);K=y%6_ z3@jKdz^&F97MfU#_#mB&D!LbY9KF%PjQ~+!))w7P^vF^lrEAO`-=Az-{=V)D6pHsz z_+dBu*MbX%aUUo0Y8Ay45b{TNkUiMTfMII3Lxnv3A_VxqYp#buDT%mQUgSBIR{01N z2Uv>~&#@Ai+V1(`u;BZaTl&nWLYk)q{&}4d8z%jgytVIE$#&21FKR~&CqyqjBR#fu(Mw_4r-${zZWzcEQdG)Z2ilQzvdm+?EJ&@ z$9}|~!oJ^bV2ZoVP^_S4<}<1xxMjMu7qNz_0QFkY{g!E{FY228(n@rAN@Mcc_4#K@ z-Ax)t%!ucB$K#4JW%dVcE&SET5#)vl(gMEOzo^4AxU`ED?9$fY7fN)KRcgf4uBa{S zy(>1#6KZvWdb^x79Oo)6acHT@zN9pJ4%Mm#z6xAZ)j_(haBbH#&)#E7_L}t9-$oh1 z{Bb^@$f1dDQuj;0Z5Mn-tD1NcKM$W`bD}%is2SaL>&I_%#4kNYhWqlhlKQbD{_ zWzutJe|e`M@uLo<$(rsU^$BS4%%O6(kOS-@dOR6p51h=UrTqx(Tm(R{!2lO7TrE2q z>*_gSL(Fp!1cT;)?F&M3gziPXcJzqK87B{R%Kfvl*8&=9g4_;C59`}YZ%9s#%aytH zTjbCu+Ay2}?tw_Rs3W)l{~%Sm4RxT^C@S!Upix{4Xw~Fz!$NiD@T}XmT9>Fi&aSWEtMlnV5}a0VB0 zbe8hSC|2bOU6W$_4^vfDHdo74Y@3l#O=Lm*2ep>UIZw8~S+}YvB8iyK+9k?0l?BVz zeP68aPqQkFn?9x%r+s1g{fj7Iod?*FBcK|I2w9}WvbFDiEB3vhb|KYg zF9#k#tQzq#x}fD#Fk_?P(r(e2K}vBsk@!7wG>6Sfc!zH zs9w#QY*#TiF5%=<&`DZ(`hK;2U9lu%u31;*37v06LjT|-7m zBjgsu9s5VR9VG%}!~PrKMm~~*g8;2MCtaRsGiiqeGVJ+gSZOvCY-^1s59N3Uje)Y~ z|LJziS(fzoyWipRY}jr^CGzrTRYtA7@)vZ7 z4!pGSIiu+8j+&XX)YGk6&9zamj=XFA{fjR8mYA<~^_CtBA?){sc>G_ELu#|c;>Jnx z>(juS^XqX+lM2QaDMTY5>CmrJ>|-hRM^W~((H!B-0JY9jy-F1 z%p0+-hiF(quj0)R)^LXyO%NdM2Kv-92<vw4aDac)7r)#4=T%A&&&v^%bF-x)kiJ3G(_lNQ;p+f)r6&j>SNtM?IGzF zGB<9FxNj6zEiC%C#M%*hwKESd&Sm&n+pVNmR5Y0HjP27cda>--xmZXQb3Bq4p-E-b zQ%!-cYYHsStkqU$nOoTCCbYL;NvZ^iFTIKkgJ+YR5dot;q13yp~p2NJDjY4IX&luH9dLY ziS7J>230BMYUUXW13@J#pC0(iEO`hjU^Jb$*#*0}t+=7p4D#+Iq)>6Yt1py$A`H|;sgZEYh{RtN!C1J<}F`|Cot2-p&%4+NKWb_&o4 zj;m2bU|I_CXY_!|E#84mDH}_~%_xL!0M*nA7DR%LP3@;NXw>3_g&t*exRIu9^cq_X zOm$PAz&P~+Hgdeb|CZf7SNacAQp0N7Xr`9LUQH51Nr|2>ho^So<+HX;s7^rkK9BesvYM4 zVB}#ZG@i}&cSF~EPpe6v&Lx~$vE4GO(p-hcZgq7{UQ~N)5L<>VoSk>#?QgFGbEzy9 znNHFc-I+fSa6`XV#?NK&cUIh8G3-&HvroQDrGW@1H>H-1nqm}1WXKWMQlk`Pd;+}E zAQznBGGv+d5~b!EW{?h7c+rjNI=!b;U#I6tyO6K)?`A2Mn;h;APQX6N6~r_^>@AqM z*rG&3N{lJpUt$^n#>OXg!7hgHnB-*|?2AYO^M0%x8Qf9FcZZvFj#Lk{9KF1p_^9`1WHPl?+OpkI7puduYfeQuX-k6(%a8eZQUOm0&(>%j6`MP|wP1$}|3$*WiI_YU?m0IZ zY!u~^oNTWI$k~b{Io!6${q1;MF!`!B9Blj4H|dtDig~4ZQItVFXmKDKRWaUXDB9T@ z1?D9I7vNj(j8+DPr#BuXbP!P&s%N9X0!k6$vg??FOIG!`Os+e4Zn z9Y=E^jN|5V+pT=$<MlzLl3JCnhm4M!eeT_PfFHtB^AgR{(Ns?3gWf zuE$(3qf|zfUrw6Q@7lsG9N4{jSek#iG1~2EHYYxB>Bh*{%EyNK@$LP(CcR~uSeftW zV1L}$?n+q949HulQRH}RkseIHz3(^7bWc+0#-A%>-cJs( z{XB(tOpDIn&l4l68pb0`C&6l+BEkM4pXCoi zi{&HwezRb?X})!=^_yh1s^a^4uZzy>@kE#Gm{I&_L$o(KxDv<`1JME+8!lfm~ zUEp_#)iSbr^(Qo10uS8L&W(Q?TcQq^SGOH~0GDde!s^aNUpi-?wPuxg(6oFbW*~UK z)*s(mIX%(1JG2Z7gf2FS;}||-MK~M@KxC*=bSK{%zEavgDV8Gb~Ef0u} zSZ?Gc%yj!f^$R_LB%7v*@t{fAvH1~UHne%;%Dhh}3St-xBvBck{`h-H=XJROTkPqt z2d2f3N{1ftCO6eH-I%+nZaEj)r*eqOE?h8tWKDX69?I3pa(!-*e($2*@S_iCG4xi~ zB^lPn*U>3pmI#kW`+*F>U8Kdbwmo$qm#!n=kzkoP zl~uSBl=TLhNtd_dv8@FxR?GtfUY_*%rNcbkpaE!`FMN(#%cL4}j{)63R&W3$O_UZvt;Yl6=+<>kLc8%3?G z%pOVt1qg*;J^qS%F%T`nO98-=6YeQ$fCaGtWAM;-JW@&b3KjBRbfoEi%LOVX#5beQ z`+mYnmCbyVoHQx40}_LK!1He4^*0a8HfW^Hyb4#-M{mx*bM5 zEEM#>{impYfUOHq-+GYeTrOqt{B#Cewf>pngGiDMxjW`=fhOw_QVUl3k2wWjx4673 z5zq@@J19@-v3+)W{K7u1VEgY&>=^RxRW9v;2{!lQiL?T+b z=w$V7>f5G!dE%XQhX#$(;(KVhB4Xc3oIy?Mg$K94?T??fwy`@)2OxuxBx|^I>nvzz zAOyv}Qv%~`1)XfUOw<2K@JMq-w8QjG!qc9Z zFLBJfgO8qAeK32Xf9nbp6OjDHbSlA!$$~O_bQf_K!i!WzSkq#zE85U-B+jVnX5~m3 z9_u?OKCa~W2G0)663T+YWW?o zaNne+BFS-nI;WR@;z&VpMwM(YicVadsJT`fF>{`h0uvGF1{fU7VWfC2T6m3IK!I!r zw#Qhj6$FGZ9i@0b4S>FKbtRH#T``Ee!u^qX^6086)b4*4Je zUs;FbyVnT%ha#`Jvlc>A%5|IzXyKH#@-cGk$p?HnZ;<+SI=v^@wh#RFU!71uKkA2cx#(r`p-}aZ`(TI`vItpGM`z|HLoCA0vhJ(}vNPsz##si_Ahz3pGsLdK3We zP16I)Qbg0EU&YP>>jDyn9yOJyiq;a zBz;_7JT=8t?IjZXEcNBw$zS=%?rU4cI@el8hgyVCt@^&KmW35GH>&ehU$&A`IO~HE zxti>QWlzjseC_wywcZO})6gTK8;8aehQfR2IHLWPLOU&~Xc@~_o$(sY8&-y8++GGN zNx9b(X<3^ziI|qS9n}ZiMT?qJaR0<1bO14x-^CIs&_0^~d(_s=u^1m~m+oCC&M&r> z(z|i;9FU~fwXExKMWSthCRwpdgORs1=%Bf4I^^T*WA|Z%3~)q0FD(Kx``>}t%rrJe z2rhJFG+51|9+a1QPM_g6qiA1m&hF>1qp&gXwuo4!WyJ7wd6x)C#Xr>B5T@@q==l0i z1O`L35MQyZkhy#v49-Y61~r;J_v-Rd^#4WPn}p{6z~vjXeZxgz#^J12}d$cz?eZ;@wz9DbmyWrDX zI3=vvwwB*M&d#JhP5@LgbL1-oMyagTj{P2eZp7cFN1JFuJDG%Q^%dgSdrf19wmOQ% zTjHtbci^h6kPwp|oMI4k8s@v>r?Kl6A8fK9(sGnQiT6vzZdtQWT)JzrTdns17P-S? z!iVJa0qE)X*XkWiR!VmR-G(Y^rqy7^phO+nhdY9s%GP8E2F+nDh=>Djf)Cez?4t)2 zF%!jUCrx5#YLw3!cR-o8)D*V!sVf7OoW5&74kknf4fXxdUI|XWjsnr zZ{`m1C7rXZF0U%;m6Jm24ItFxpQFj93>KOcT99iIDKiC}wN;lbvxz*klZv=jtN=rc zhm6=L%ZK#iI7}G&eJa8JUF0UjraS`QH{5XP%Op5SPvch4&dB~@dO+)c?~X-7L{ng7 zU43$Zc8T(--&8|x>T5hLiSZh+DOkeErt!Pt&dOlKx0`XaNK-}$N>IN zZ&yGonL*RQ{q1|`57U|$>wuQJun3^q#ZFme`iwt z?W7H`63O|%Q~eHXbK?d-ux{01&<^MSc))I9xcT&H2#gnrTf99-4gUU*ETDn;{}6@6 z%nbP9`210+&nzu+)bnb8n9@K1XOoNzd-#Vb=k+Sp5=J)l-4$>8D`7-4;U5Y20|~2w z#}(D>uZTPUMe=1UWJCeOl)()e5NEvXkH!7R z9y<$G@n|&TC>Q8xu7g0mK?SEy0`RH6d^1IdPYX`_(v)13#+&Zc1C2mXI28WXX#e`} z{&(m9V=q9>SE2a~>Nr>%^dV?XOzl6mZd@Sp5~XgVlV%}&Ol>JE1u}q>?p}m5UN(Ug zmR4P^9!8!s6j_3%X*a|(;#Hr)sGlC7*BDJVwil(E;Y3rY`3ihKi%I~^LVuTB2u99_ zPrXBLm>~aZ9&oq))x5V^?0=X(Jd6c|ENE6>MOwgAw=aMN3rqmJXqK!EHm`~WL$7hb z`KZ=kjk>%?)1cJ;VX8$mgS~cd;Xe+G%eeW>e|m~%f!4U?{v&Y|4l@ zVdRnKXcW1b9JWP2?F){DTl#at0_3QKrNpXMI9718RIwe$4taZ@>h_91PGu(b=Ek`9 z`aRC2>XNQ|lboIPS2ffy*JaK!7nd1v-j4l=PmkKQCdUcCUepVtZW$l>!^9)eVwyYC zYct30{daZFOuLb`2i3f{JbTyLYt1pn#tXw{I*=lIu*>0!V3I)0)$1(W#~L9COUu-CyN6w?Lj~e7Yj(JPlfe>$ofv;uDvEYro+{k5nXI^}Ha)y|Gb!$2 z7TCflT=bs32R5V;;;9?S3mUec+_KlKq``BseUmSKjBJ|hi4%_dZ7{w(Yp80JYhA6k z8;J-;YEqvj#LZm~gXKQXg+zirVY!!msW)1ng|An3<5>au+9%*?(s9By8}6e1FGyTO z{~>W103jmB$}{brQdyq87uhJKRm^y~m4u(!|q6w7QcbN-~KT6(>H%KL@VEOoXzW$!^z zj$C(M_IdsG-@|v(P3pY#ezgs~cIB41(yb7c;oZw}zNSET*I8x)VVJ#w<>slY2Y)<- zXU&(T)b6G9aJ>qCG(C=2CaM1{5vcmD5ccYD-(?X{9jmJ+^6GJaFr95!gO3<%ZJo7l z@bVYRDbcGav~A5$l&d2rBjXdF7AEFlHS91$$njS{@%yiS1oe$)E1?ikKo4cHr^C-v zxFkQfB$kACI%l{D8<@VosFy6}zHSpfn&u>D2#dTc%tK)p;Z`43ls@pfP%Xq@y-hO7 z*ao>^R^V~T**d9l8?KmSKU$IL(`x?B$EQTu!Rrfu*Rs&4^;=hqlOeapXGP<T{DjQs?UF=X~wS0cG-QC5Pee9)s8B0 zCmd-KoL7GyoRvxp;jKJu=PVHY5*u*n=|~xN|00i#S?f6y4o;C>!K1XB;D%2}8j%6n zSd(;+Mf2Ff-~7=?H8Op(sy@K`Y|p_a97*7d?21x?BxhnTW?>igW?SdTaVKX(w=)jW z9s?SaYSS9^(Qe7w`<|9W1|4*^^1GOYvSpmE*DvT4?K%Y=$2kc*;l&Z)=4t3) z>o7iMYkPH$Iy%_F8kCcC;Hh+rou#5=jrG}lPc0RSaZ%`p#lX4-;B61?9x@&%or)Oj z_F}!nlW1t0?IjT6EC1oK=dl#+hS9k2S+HE4Gi+9CH8ls^hof*-x{DR0IhVM-#y4=TIT15GNIVXX3rNvZ2bu zH)~$8t&g$k4$hBVZ}agje=~xX?RHF&xRRHZ=AaW0;zRx$ko>O6kL5%hG$xG2~TuTqX3o+X# zSsUG$5C{seN>Q$|eZEx2XONy_8F|zF_}p0~v-P-g3Tub+=@I|Tht_x>pUP17^Dcv% z+)<;WizBC#G4b0~kG7VTp4mL|DAo9p^YdCNi`;2PNo6#&M=&CXFc{bcfY{J+0da)4(}IK%xYaLNWS*;ru& zf3z+)K-0gWk_VLo3krv-?@Y_rPB#h6vaI(W=ncX4_%@7Shp^xGNFZZjmh2B_3&=Dd zGQd|{O8?;>gvA)TZ-y%D&KhFB;F(xOW4eJ4?7ZANR{+jZv^y@=MItZDNI`kX$ditPA3}x%sJ{NqNHy zT?R@^riHc1>Kg^UEuOM+Yw?aSUa00V54Znl5tf+9D1_YP#xT>qziUYj=#w-)%Dp;8Pd~Ay@*Kx;?V_L% z$7hOlV#7-Hl__KT??vCpomne=_j5_GJ8lt`Oa%tzL2Y^|2zU7&B+#_*4cfUuZY}Cx z&BncUeV-{QX7`tjpVxBD+{#;}ymGU;*2*%0Un3jh_bOR(-=9}m^dT-P zEpsh@_h808>d|DRHtE1#8lwicvbYP538@6ch+6gFcuU-A(lI$6H=N$RbR|+T>|?Uv zDo^^9Bx&S^sz|HxLo`PhK1(1GZe8Fsk|m@gX%S@&Pb$N{-;Ppa*N-N~#4oVCok6O` z1dQB6A4!~^C%PWHTIs%KLEfo=$Mpihf z^JFb9?p}+tJEbgLRTo+um*mMG+l4sS*ZsrU;pJPKqfdW<lSt=k4e%Yb~iL`WXjh49myal%@7h_ud#UtWG zPDJ_NC!1woE%EgCFn@m->#eG0w6@0R-GFwLU4;+f`xM=>@FcC%efMAW6y44Zw>>5+ zAQu}PR6rzCtq@$|lGG?APwOJYHYzO!N+^LsXp^ejpbdPv$PiC+F!O1jDM*dYhzq3&ve%P9u_NJ9n*G5M1uGGMaQGl^SM1j+$rh))y7#@LXaEk zaQ2|q_9l*+wAB{p^AwKomD1ZM93814_QU>w7Mic^VGqV}TwmJLHd*hkmFTN|;ukte z@mxA3Fit&^W-$B&j|Y2GTGP9t@7+!x z>m9t94!e9v4&9uCdi&OToAc>mWj`!osjhvcu1rUA$tE~+xv#ivJpB0ex|pFxQWule z@tSmh7t1sIbd@#qrwM}8c9{>9z!;hp=}mEMc{FHYq%wb*y-u5CZ?RY1(qBZrXi`I6E3({f%>;a4sT1pgcR5Vr>yr=aT;0 z(BEnTz;hZ^zVU4p%w4H2@q85QgD&vg&3o^Xt)s-R8N2d6FwiYm`~E-$d7)~kS5!sb zc;MQQo@at+te|V_Nppqy4?Q7Lt06Lb&jK5>#-PtitD7ZmRw<*?mLrv#1|b3tAyN{c z5EUy{US2NXEhBTMCPlBhveNX_sP+aA5j$Y zFdt#^>8f~UV4(C+&nIp()i2{#S7VqCx((>0q{*lZ`Ce9vt1K2e?=EgV92Zd?Y3jsF zm=Kci{d!jfskEy4`xd$F#@%1qbC2)j(J|a&lekjw$iEv}Z$E;Ce>xpCuDb_HWc=s} z-pnu0R1aw^pno6NKFz7ajZGHxVLltdG-`D6cl~LVI~3Rp*HOI}Gx*8^{ zD%OZRv-)-m1UTB%E3={lXP=ePF@{z3Q60X@`gX`K0$*7;YOIa6aHWgEG4@TlC7xpu zB}`Se0fx)EYpZ7A_gYHcMQuIKsg1EOJUf2i_2K>b(e7q3Y3k-?QT6&#D~x5r2gH3L z-xtWxX&jHkxP1_|qe+^goSUW92~aIhc<#107BD^erky|O*vZ9Y5D{w`F3SAOEAc~= zUsRN}zQ+({Z+Cm~2*DEEvlsr4xIO=~-?gf!LVNcagK#FJ%+#<3D5QvLKh&c`){8yR zBu>t+j(H{{U4j)i(z{xHu}3aqQwH2=oVWvFvs+Pg@o){h=`4;d%d+33 zJUKbcowDNbbl4NqxZ(MZoKX=Lj+LoTym!XEF^`~gK_eR?pqy-Jv%&Y|eOVq65)z>? z(}CoiC-gZ>SEJ$_>iu7;99(T1X9yX5=W0iv37-z|9zQktpJ!|CV0+*IZ%>a+ zVQD$7|Mw1Mzme?iCU+Vaz^mb_BaDVtT-ObgVSGP+vqLy~Iza z%+f}90UfQZq3Mxif1%$?66^o7&!yE z^7!X7fvt-{=|qzkAL9htjCteq(~Kq4`Y-quSRb`Ei2h~wNSKY zUl!#J%#T}IFAzqE50$|`st4TKcPCGiy^of+^|v;x&HM{5+rbF{oXJOg_f(KJdv^5`f)f|~6p;PJAMzWXu`*f-!Ws!=tPu!B5ZcbW1+(7O!xMvxH6ct3< zTmPYCX<2cu2;0b9da_{8(aHUd-sZ36Pp+-H6=u{{XyKp0J$ZI3Gg_ZAlCw^LU@^@q z;(l&y--F_8l@y0lOTuhSr+>>IS>mb$cXV^BW@ZQKA*|X#N0jG`nn;Zhd zbROt1!oA@QUyfFaZ_A^-U;Oyi;TqfsvDz`&Td#TOEHy%+N=hHTb&Y>HyzE#{eoPdN zT3c09UXwMra-_tkBsY;Y-Rn&jYxHB%gTz)1g~G0rpVZ6r1*1yij+Q+;jQw2RC)i)( zb&~ve%ObA(6HltBpTD><_iLvN@@Db;EswnA<@=kGL%trch15ut6xEwFY7Bg8Y&GMN zLH*J*c}rGb)+Ko}bPok^6ZMpOR*!W`v>QlNKmQ5>#pcFqACj&qndt$K^F=IIeeiC8 zQ|30Kq+V|xKGFy$eglsP2ShjF-0C9!Cy%}CHu$|(ukG-YW_m_V)`qB9P0fIL8Sg=Q za1mplo+E(S`@^Iz!GVVX7U+w+X1-G6h^6Y3?7BJxzJJxhE59NOH@7~AULOrZ0|)HV zAEwQXX$=_xC$Eymr7Dn2_|QwB-n%IHW*M(+=iy?SR>X~ zG#PMR&kcPnZ9gAEUe`9q2()p=?$U!2^-Y3n zC?vZy>m{(>O}|x9m3gVOF{@qo^F<+?^A6pUWl;+~JiNLcPo#xkrt1yurgi5L;OnxN z$z<0UEIS8ss#9RU6@ezc6^FvKcvk8aeH~^3D#V)f@ zR!EbiNxg`W$HZX`AyLI{SBx(P^Y&vsQ3@G>!a^KQMbtiqAt!m z+cS_ex0V_yoPDQ>k39EcSzi(cfN?5|JUmFDTGINDj(SxwwLZ)r2kA`xmrut;J$uc` zt^*m)sx`?eVV*_EI<7UFCLbR5t{pMY+*w3CUX#54x*C1qVYJYd_^){L~94NjqB@pIn%F;NkKSZew@XidS^OHCh0n`1SsG8&6xFx0cN=E+$8= zhF|PQl;A$%Xk2p6)J?BEq!snsE+(vr<7ZCb*r5dXB{vfGg{jYk&`3jK=b3Lvi3{dX z<*yfu>Ww(s(_7ERNNf+}Ky>DrFQaP<2hu@0BI_ujuT$YioA_E?Nicz{$S*|vYU$l% z4b`cwECu0Tlg&B7<4@_R?};kkDOqcpuv#3>Cu@XF@80N3Tn-hy)^ z`E{tCj|(OFz8INr(1o?KpJ>w!!)&UX1)@1;E#+)(xo+Zh3$0yt480IPz=i)>dGW{qVjC!R=2!gRtfi z*W7($6l9y%bur+wfhcX&vSR=f<;eFRDO!%|=%~$1`x%R>y>usW-IwWj@4|gUS(Q7Q zQ|N<1*(ABB3itu4oL(Ki!Gt6=zd;u_tYDor(Ri9UsV{ox+1TLN$&|&dt=a1(()Wm2 zD=Y5a#1x&qaZ_^O?}R<^j{{BT?VK1xXu*4D*%tr4DX_Ug@nC-pDFYiL={M?> zU}};U)F;|QMZ{19wNt0gUr{5iOCK^xpZU&jT7ZB>wGch}k2eNDQ>8F2-f0H6+|$c* zj9X)j;6PzF7}_8cnks=f$KezY+A;Y*OlIB?;H0sc0X1JdB!GS=HA>@|mN~n0l_D4E zoAYs`0-%Q}O!}LCq4Wr7s9INFsIalNNhve*2=P!-eN=Abt6{Dp>NRwFO;_(74Q@}7 zq(GeJ8SgbZDkQHOD?a~74eI8MuEb z=M0sm0uy-J3BWd^Kw|h`z0MXTEIoso zZ5d)8575q&73T0(ctDWID1W-c;C6wgig26`Fp6gb`<07dMgmqlb0TRBbdvmgAH#sY z(5rKzPgi5MrmIUct{bcTVyh9-daG$N%@7qf$na{5zBl+`ed{7Zc0=j+_{#4`t@H95 zR*5!bNPGpv^1Y(X4Gj?to?QYaVYdNIwnDsEj-g4DuXlCQ!UG6P;sAvg!|e)96%bCO zqp4N^w^7;19|TkkXaz6TZw7o8ZrJXC_bcZ_o!l4q3M_ETJ=VQH4 zb4O-yD@yf%AGkF2o5g(IW@^}uW(io&gnbrz0()}uj_`9> zM;YogCGf@~u#sw!oPU@me3#Y+LYWyaccG5VZWw)(THV6{r`ia%6b%~X zr?r1xdWv?md=RtzE3Wb4Mu`ZC<@ihQAbE0 zW)5$OxhQ!2^1{AEN>f zqd2Gbc?GNU8ip=$>|$TxNY@xILBfYySv*aX;sy|_!QTB$2jTe0_q4xraGAn^IiP7) zpmq2Q^u`Sh9xw-^hk!T$+cQDadVM)m&mQn5LF2eQ448}lbRe045e_O zqQUv_X}#UO_B4cQVdH%&P)Q!Ba|QOzWIo$l1J<=Vm+9wP2aQ}=!dH7kra0m};W%#o z4OI?^CJn3yw;oLsJpSlhuz&h<+U$I^jqT}!H^5h)GPqAOGfh33V?t>SXv$%zSDuVuuqhK5lJV4F zxi&2VD~%VGmRAcbL;TFn()I(6`}^hyG+=LMn$SZ@6&t;am2=uhz}gyXpmlKGhnk*2 zIM`tsHqbXdBmw|s3q73_Kds~3g@PEX<2|3`X3Gfe*bUqpy6ahKffxf`MA{rmz|8vy;Y6K|N0O~9> zAp4I__5Y(&*>n^Mw<}-?RD%~Gx9c}U-TeuYUAT77Lj&k8kRr6%o`u+iZ|>o}A(PI( z3&#grGo%TiO_nu%cfUx26r_V!sn3IH7s&VjFx8~g;%i9Sh}5?t@7oOeH^i@R z7Jv~2l`|r_Lp39~fK=UZn@bNFq9*h20ns5O4jw!BQ_#j0I^q!u9sgH#6#>CXPjlG! z#o$T3Jm@k0B#bJ_(FSfUrOsvyobOdGT=PEx3VMCx+MoKiW@+ zNIjzEQN08P^5)QvTv4TOyVi94H-~w&)Oo6El1fT-AGH!;1v0cayV_4nk~_pviurqj^!LP7?u0R*elQ>Mhrgl&=Rj4F z+}_i$YGbPJ(yRyUUHrh+ZFJaM8C{dhPuD+%znuOwyiq``_vzvCR1wK|ThmO-ipLS@ zo(A_RtkFcVi*t(U(IzQ*0?ZfnrJVFNRC;}ejC|69-%O*PfZU~qNBpJy&c~-NOpdZk zop?qvR%{`;cNOX8N)Fe&RK9pgU!q_r$-2P!{)1@x*V^bUReJr#kBYq>0Vg==Sjr$h z%dX^igA~QWXziBFR`jJW{3}CkqbUhe*GIAN7aK+HJ(?q#4LJ%7Mbr?k42s9yHQ^L% zssu*zD)W-6c{>ph}-Bg6&%9dW9&m3 zkgJzV$cT10OVfoPt@%Mk217FMMpsUFFRug`>fVjlq4Y(6+W*~a%xc4rP5aC9F>RFh zU3q$#owtG<#1hw2LpRwM-|F(9Ph#s~!IQ7|2evNW>$4>GN~vY-APBs(1 zXC0&)#N#xOii1nUm*7Zp!-ZXBAW?SxCdu*I z8`wg;)HDvp3=|H|s=xA>p2c8OUC0Q9-|$N^lxwjF4T?M{zCe#fW6Ela-7)Edr5mQ* zibn(M?fZq2>YW~I_iQ?+=T@m?h4{sNMuCIA|KogFsL8BX$`Efxs2+x|zxw7SO0)9d z3TzU#4bB|?g>3hB=dp1w&DSBIghXaC^>=Eg_}$TzA*=Grl7PmMJt=?q**hrtesMKX z@`WoK3%h=HfgEdD6L9H`UEua8zCNKF93TqLVR}gL-srs*`FM+@X5LP?Jw4XTRkLuRgx8ErvNV)?`5+^|>=(gZH3PnIu}S4sOs z7{udl;|q=lp|jMFfS*a~4^!P;E^xNrvYEcU$$kpt2|55Yi?k-_?%N+8=rwk5pFD!- zQnf`RdN=+HavQcwtbP-EyWwJeZ`D_6sXk9fh7@^_Zc=)EHKY?; zrG1!16UxdyemOnjDY0+UCmQ&fN7N5K=l70mUp9B}^0%G#Mzl_{Qn(j%h}Y`SFOaH* zo;C;ARUMtWzjxS9-F8F~(3Ld{lshOm2ZIOpjVi)Tncx=ggLT1bA47@Ei_i(iIJ{Ck zs-kNn)`7Hw6-w@|c=(6O#XXCYoIBmo>eWflB_cTH>k>PblKl-UZOprg$uHotMoM@t zLq9+1q<*D%>7g6T(q%m&ryh$XbUhY*B$oL-YX%H}oL~X782pP2geA z-yikg01^Ku|E^g!VZckZK(5q$PHD%#zm?=8eQ9#*mrVgUfBydBv7eR2@A1hR>HrQ7 zY71Z?uUh55WAEmh&eYcZ{A<6SF5hoGGYcEe;x7-1yF)EsK^JuF9QG=Cxp2bu4&&6P z?|NISC+J9UM`wC-cSG)z@jE^cFA3f&0aak3xoU=X^dF|!Z?bwbbO=H2w9+ewQnjGFnkPzL_p?~`Al_-TgT{|fnNrmy`knR_|v|6>#T?+Z@= zUfqrVvb5q`$y*TK?+=qBiPkjdi@-F>_|$uWmjS!p%ryBi9#Ds{2;8#9yC|u~4Vvn~BT~zS)rkM!h~ocAMXpj4jk=a19+V48 zn|{T<6^*-g^UM6Awn#Qpcel0_)0G1@lL%(qR1U0St{L%Zp^W+wd4rr;`UN;j_^EL| z0bOUROu4#;kT=95+eQzazZ}uS2pRwNp!)aGQ=m1&^>roO zExf<%tCqU0K{98|C%#9Lx$sigH}#^~F+HDn9WAA6rF1(t?fynwEB7uTjZ}h7+L-eJ zmzrkBmmf%jX)E$Q{ISbFyrx-MgHi_N*ELdwMF){^56&ka7Hve}7DzLCI&>E|7N!$v zM+ppT5Su=`$bMeR!o)*NI-gN5cUc)^TqD>q6M9`Hi*Q_PuvMd2K$D+9`6QBzTe$Erj zo_{r8Cxos}x;~7S_?CfReath+b0Vsx7}I7f*h59j6u4WrnYFsOynJmPSAwiR!RK02 z7_~NI>Fk*8KU@mZqrMVOz+J1D&-K9s=%*SiNxm%9>LU`PJ!=iEs=Z4yN`?9uL0PAE zH75h4T_O=C9a_{WE1Bta(Lj>Z0FFh2!udFq%pmGVF_MWgfsK~7`I;hkD2P(BfjCik z>~-}=ie`p96|$;%aGBZSBe}A{GpF)r%w>2Yg0ty3;8D>m&SWM*Pm%glnKvq)9p0Q! z0ccN~FTxYO#Vt&C?|$+uenc-fXlSUE`TXXU4yu6$Y_DfS-MCu=kUK8&sulgGLeLWN z-I3FRy5);m$42Eeszpg&0@4jH^%{&ydvkiB4pYR6?Hi-AYp?rYmEI6Nm<1dn$LksksD4E8m&cPK4r=oK zEk850KTNl7Wh`o=Nv}`Y#TA$caYK&+KJqqLvnP7}7)&&fo06bQp)#OryA^AMoVn-a zluJ)PmPZH$MJBdH2$!|4OIj8gMhAtkMiIM*?r-N$T}&72TL0}oH7O-vY@TUFyK9CR zOzniIqBhM4SZKK|Rg=Vqn8Lqs<&g9+6bLH%T+Q{$v0!jh;Kk;IXun+douSPIrM>gt z>FHM!%+6rjadE)Vv{^=wnV0J#Um#DwqZ>rrW-kSe6US7e{CStNySy@W$5_uJue^8Y zs7H=x2I>qIw|=}{diQi1EhJjw^mNmStxCVz89QCV4}V2$4sOJ7fo|A<6pa%nl7s5gv|k0M4sQn1)319Aot%2k9d1&*RyF^fNn;I ze$U*gO~q!V?JN%i%fx-<@0!kXji66=!8H=R%97AeCiU1|o2_bgCJIUZSeKInCdHyp47)|thYtv?pu(f~8LRyu-5N>z& z%~^Z7_jJm$u(WzCpWsPq^vMBICqaW9we++^Is%6Q=5gz~!jp}n?H z_^ZOTR~YHbOb_%w3ufhPl=`i%9)eImQ(zA+=(1muTT`~ypG~FT0efRrUSw^-&Omj@ z+hPZf%a)K5t(Q8rDKL|Cq2)oQP^WI^C7W?g6Xfl@hyD5kO}Nn#Rf|n{Ut!nZ&t=BT z!Po&*m;}yjgOQhyyRylMz{3Cc&xIHh^gOfDf34(xH+x^}=)B}5S}^?`aNazfWM}A& zV}f_KIvH2f-007L|K)0%mws7CxM_o5{;TMPKTP=irCpx4 zC1^K-brKZ6G>gnRTTqw=lSFd^xj#P)2V9H-ycqulD@hj7xy-ht+;29iIS%_Ftul%{an^yt_ z6fD}{75IF7eOF6G>l*575?*?U*VLrw=ROi`i2M3jd2gFs&5%S;MRh(7;DT>bxu=z0 zX(t9!9h@lpm%N@P+6i38amHKSm)hA9JRah3#5=udW(4-ccaDR^2
    YyJoY@c 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

    Rj#K$Z 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 pGF7YB>kzuTlRn0^AB=;q9nd_Bp%7+PM literal 0 HcmV?d00001 From ba96f11f90090902c75354c29407a5b3ef1febf7 Mon Sep 17 00:00:00 2001 From: BeaconCat Date: Sat, 28 Mar 2026 18:02:13 +0800 Subject: [PATCH 13/55] docs: update WeCom channel docs and README provider/channel tables - Rewrite docs/channels/wecom/README.md and README.zh.md with unified 3-option setup guide (Web UI QR / CLI QR / manual config), full config table with defaults and env vars, runtime behavior details, and migration notes from legacy wecom_bot/wecom_app/wecom_aibot - Add assets/wecom-qr-binding.jpg screenshot for Web UI QR binding flow - Remove obsolete docs/channels/wecom/wecom_bot/, wecom_app/, wecom_aibot/ subdirectories (18 files, all language variants) - Update Channels table in all 8 READMEs: replace 3 legacy WeCom rows with single unified WeCom row; zh README links to README.zh.md, others link to README.md - Add Xiaomi MiMo (mimo/) to Providers table in all 8 READMEs Co-Authored-By: Claude Opus 4.6 --- README.fr.md | 5 +- README.id.md | 5 +- README.it.md | 5 +- README.ja.md | 5 +- README.md | 5 +- README.pt-br.md | 5 +- README.vi.md | 5 +- README.zh.md | 5 +- assets/wecom-qr-binding.jpg | Bin 0 -> 238989 bytes docs/channels/wecom/README.md | 124 ++++++++---- docs/channels/wecom/README.zh.md | 140 ++++++++----- docs/channels/wecom/wecom_aibot/README.fr.md | 118 ----------- docs/channels/wecom/wecom_aibot/README.ja.md | 118 ----------- docs/channels/wecom/wecom_aibot/README.md | 118 ----------- .../wecom/wecom_aibot/README.pt-br.md | 118 ----------- docs/channels/wecom/wecom_aibot/README.vi.md | 118 ----------- docs/channels/wecom/wecom_aibot/README.zh.md | 185 ------------------ docs/channels/wecom/wecom_app/README.fr.md | 47 ----- docs/channels/wecom/wecom_app/README.ja.md | 47 ----- docs/channels/wecom/wecom_app/README.md | 47 ----- docs/channels/wecom/wecom_app/README.pt-br.md | 47 ----- docs/channels/wecom/wecom_app/README.vi.md | 47 ----- docs/channels/wecom/wecom_app/README.zh.md | 47 ----- docs/channels/wecom/wecom_bot/README.fr.md | 41 ---- docs/channels/wecom/wecom_bot/README.ja.md | 41 ---- docs/channels/wecom/wecom_bot/README.md | 41 ---- docs/channels/wecom/wecom_bot/README.pt-br.md | 41 ---- docs/channels/wecom/wecom_bot/README.vi.md | 41 ---- docs/channels/wecom/wecom_bot/README.zh.md | 41 ---- 29 files changed, 192 insertions(+), 1415 deletions(-) create mode 100644 assets/wecom-qr-binding.jpg delete mode 100644 docs/channels/wecom/wecom_aibot/README.fr.md delete mode 100644 docs/channels/wecom/wecom_aibot/README.ja.md delete mode 100644 docs/channels/wecom/wecom_aibot/README.md delete mode 100644 docs/channels/wecom/wecom_aibot/README.pt-br.md delete mode 100644 docs/channels/wecom/wecom_aibot/README.vi.md delete mode 100644 docs/channels/wecom/wecom_aibot/README.zh.md delete mode 100644 docs/channels/wecom/wecom_app/README.fr.md delete mode 100644 docs/channels/wecom/wecom_app/README.ja.md delete mode 100644 docs/channels/wecom/wecom_app/README.md delete mode 100644 docs/channels/wecom/wecom_app/README.pt-br.md delete mode 100644 docs/channels/wecom/wecom_app/README.vi.md delete mode 100644 docs/channels/wecom/wecom_app/README.zh.md delete mode 100644 docs/channels/wecom/wecom_bot/README.fr.md delete mode 100644 docs/channels/wecom/wecom_bot/README.ja.md delete mode 100644 docs/channels/wecom/wecom_bot/README.md delete mode 100644 docs/channels/wecom/wecom_bot/README.pt-br.md delete mode 100644 docs/channels/wecom/wecom_bot/README.vi.md delete mode 100644 docs/channels/wecom/wecom_bot/README.zh.md diff --git a/README.fr.md b/README.fr.md index 727f67c2c..f9812bd64 100644 --- a/README.fr.md +++ b/README.fr.md @@ -394,6 +394,7 @@ PicoClaw supporte plus de 30 providers LLM via la configuration `model_list`. Ut | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Requise | Modèles hébergés NVIDIA | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Requise | Inférence rapide | | [Novita AI](https://novita.ai/) | `novita/` | Requise | Divers modèles open | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Requise | Modèles MiMo | | [Ollama](https://ollama.com/) | `ollama/` | Non requise | Modèles locaux, auto-hébergé | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Non requise | Déploiement local, compatible OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variable | Proxy pour 100+ providers | @@ -450,9 +451,7 @@ Parlez à votre PicoClaw via plus de 17 plateformes de messagerie : | **DingTalk** | Moyen (identifiants client) | Stream | [Guide](docs/channels/dingtalk/README.fr.md) | | **Feishu / Lark** | Moyen (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.fr.md) | | **LINE** | Moyen (identifiants + webhook) | Webhook | [Guide](docs/channels/line/README.fr.md) | -| **WeCom Bot** | Moyen (URL webhook) | Webhook | [Guide](docs/channels/wecom/wecom_bot/README.fr.md) | -| **WeCom App** | Moyen (identifiants corp) | Webhook | [Guide](docs/channels/wecom/wecom_app/README.fr.md) | -| **WeCom AI Bot** | Moyen (token + clé AES) | WebSocket / Webhook | [Guide](docs/channels/wecom/wecom_aibot/README.fr.md) | +| **WeCom** | Facile (QR login ou manuel) | WebSocket | [Guide](docs/channels/wecom/README.md) | | **IRC** | Moyen (serveur + pseudo) | Protocole IRC | [Guide](docs/fr/chat-apps.md#irc) | | **OneBot** | Moyen (URL WebSocket) | OneBot v11 | [Guide](docs/channels/onebot/README.fr.md) | | **MaixCam** | Facile (activer) | Socket TCP | [Guide](docs/channels/maixcam/README.fr.md) | diff --git a/README.id.md b/README.id.md index 20713314f..4ef999ac2 100644 --- a/README.id.md +++ b/README.id.md @@ -390,6 +390,7 @@ PicoClaw mendukung 30+ provider LLM melalui konfigurasi `model_list`. Gunakan fo | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model yang di-host NVIDIA | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferensi cepat | | [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Berbagai model open | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo | | [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model lokal, self-hosted | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deploy lokal, kompatibel OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Bervariasi | Proxy untuk 100+ provider | @@ -446,9 +447,7 @@ Bicara dengan PicoClaw Anda melalui 17+ platform pesan: | **DingTalk** | Sedang (client credentials) | Stream | [Panduan](docs/channels/dingtalk/README.md) | | **Feishu / Lark** | Sedang (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) | | **LINE** | Sedang (credentials + webhook) | Webhook | [Panduan](docs/channels/line/README.md) | -| **WeCom Bot** | Sedang (webhook URL) | Webhook | [Panduan](docs/channels/wecom/wecom_bot/README.md) | -| **WeCom App** | Sedang (corp credentials) | Webhook | [Panduan](docs/channels/wecom/wecom_app/README.md) | -| **WeCom AI Bot** | Sedang (token + AES key) | WebSocket / Webhook | [Panduan](docs/channels/wecom/wecom_aibot/README.md) | +| **WeCom** | Mudah (login QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) | | **IRC** | Sedang (server + nick) | IRC protocol | [Panduan](docs/chat-apps.md#irc) | | **OneBot** | Sedang (WebSocket URL) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) | | **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) | diff --git a/README.it.md b/README.it.md index 5c75dab3f..b715ea33a 100644 --- a/README.it.md +++ b/README.it.md @@ -390,6 +390,7 @@ PicoClaw supporta 30+ provider LLM tramite la configurazione `model_list`. Usa i | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Richiesta | Modelli ospitati NVIDIA | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Richiesta | Inferenza veloce | | [Novita AI](https://novita.ai/) | `novita/` | Richiesta | Vari modelli open | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Richiesta | Modelli MiMo | | [Ollama](https://ollama.com/) | `ollama/` | Non necessaria | Modelli locali, self-hosted | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Non necessaria | Deploy locale, compatibile OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variabile | Proxy per 100+ provider | @@ -446,9 +447,7 @@ Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica: | **DingTalk** | Medio (credenziali client) | Stream | [Guida](docs/channels/dingtalk/README.md) | | **Feishu / Lark** | Medio (App ID + Secret) | WebSocket/SDK | [Guida](docs/channels/feishu/README.md) | | **LINE** | Medio (credenziali + webhook) | Webhook | [Guida](docs/channels/line/README.md) | -| **WeCom Bot** | Medio (webhook URL) | Webhook | [Guida](docs/channels/wecom/wecom_bot/README.md) | -| **WeCom App** | Medio (credenziali aziendali) | Webhook | [Guida](docs/channels/wecom/wecom_app/README.md) | -| **WeCom AI Bot** | Medio (token + AES key) | WebSocket / Webhook | [Guida](docs/channels/wecom/wecom_aibot/README.md) | +| **WeCom** | Facile (login QR o manuale) | WebSocket | [Guida](docs/channels/wecom/README.md) | | **IRC** | Medio (server + nick) | Protocollo IRC | [Guida](docs/chat-apps.md#irc) | | **OneBot** | Medio (WebSocket URL) | OneBot v11 | [Guida](docs/channels/onebot/README.md) | | **MaixCam** | Facile (abilita) | TCP socket | [Guida](docs/channels/maixcam/README.md) | diff --git a/README.ja.md b/README.ja.md index cccba8517..6b2bfc2b3 100644 --- a/README.ja.md +++ b/README.ja.md @@ -390,6 +390,7 @@ PicoClaw は `model_list` 設定を通じて 30 以上の LLM Provider をサポ | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必須 | NVIDIA ホスティングモデル | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必須 | 高速推論 | | [Novita AI](https://novita.ai/) | `novita/` | 必須 | 各種オープンモデル | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必須 | MiMo モデル | | [Ollama](https://ollama.com/) | `ollama/` | 不要 | ローカルモデル、セルフホスト | | [vLLM](https://docs.vllm.ai/) | `vllm/` | 不要 | ローカルデプロイ、OpenAI 互換 | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 場合による | 100 以上の Provider のプロキシ | @@ -446,9 +447,7 @@ Provider の完全な設定詳細は [Provider とモデル](docs/ja/providers.m | **DingTalk** | 中級(クライアント認証情報) | Stream | [ガイド](docs/channels/dingtalk/README.ja.md) | | **Feishu / Lark** | 中級(App ID + Secret) | WebSocket/SDK | [ガイド](docs/channels/feishu/README.ja.md) | | **LINE** | 中級(認証情報 + webhook) | Webhook | [ガイド](docs/channels/line/README.ja.md) | -| **WeCom Bot** | 中級(webhook URL) | Webhook | [ガイド](docs/channels/wecom/wecom_bot/README.ja.md) | -| **WeCom App** | 中級(corp 認証情報) | Webhook | [ガイド](docs/channels/wecom/wecom_app/README.ja.md) | -| **WeCom AI Bot** | 中級(トークン + AES キー) | WebSocket / Webhook | [ガイド](docs/channels/wecom/wecom_aibot/README.ja.md) | +| **WeCom** | 簡単(QR ログインまたは手動) | WebSocket | [ガイド](docs/channels/wecom/README.md) | | **IRC** | 中級(サーバー + nick) | IRC protocol | [ガイド](docs/ja/chat-apps.md#irc) | | **OneBot** | 中級(WebSocket URL) | OneBot v11 | [ガイド](docs/channels/onebot/README.ja.md) | | **MaixCam** | 簡単(有効化) | TCP socket | [ガイド](docs/channels/maixcam/README.ja.md) | diff --git a/README.md b/README.md index bdefaeb18..31ae19eb4 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,7 @@ PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Required | NVIDIA hosted models | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Required | Fast inference | | [Novita AI](https://novita.ai/) | `novita/` | Required | Various open models | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Required | MiMo models | | [Ollama](https://ollama.com/) | `ollama/` | Not needed | Local models, self-hosted | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Not needed | Local deployment, OpenAI-compatible | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varies | Proxy for 100+ providers | @@ -452,9 +453,7 @@ Talk to your PicoClaw through 17+ messaging platforms: | **DingTalk** | Medium (client credentials) | Stream | [Guide](docs/channels/dingtalk/README.md) | | **Feishu / Lark** | Medium (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.md) | | **LINE** | Medium (credentials + webhook) | Webhook | [Guide](docs/channels/line/README.md) | -| **WeCom Bot** | Medium (webhook URL) | Webhook | [Guide](docs/channels/wecom/wecom_bot/README.md) | -| **WeCom App** | Medium (corp credentials) | Webhook | [Guide](docs/channels/wecom/wecom_app/README.md) | -| **WeCom AI Bot** | Medium (token + AES key) | WebSocket / Webhook | [Guide](docs/channels/wecom/wecom_aibot/README.md) | +| **WeCom** | Easy (QR login or manual) | WebSocket | [Guide](docs/channels/wecom/README.md) | | **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/chat-apps.md#irc) | | **OneBot** | Medium (WebSocket URL) | OneBot v11 | [Guide](docs/channels/onebot/README.md) | | **MaixCam** | Easy (enable) | TCP socket | [Guide](docs/channels/maixcam/README.md) | diff --git a/README.pt-br.md b/README.pt-br.md index 40f5f3d4f..a3024e264 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -390,6 +390,7 @@ O PicoClaw suporta mais de 30 providers de LLM através da configuração `model | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Obrigatória | Modelos hospedados pela NVIDIA | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Obrigatória | Inferência rápida | | [Novita AI](https://novita.ai/) | `novita/` | Obrigatória | Vários modelos abertos | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Obrigatória | Modelos MiMo | | [Ollama](https://ollama.com/) | `ollama/` | Não necessária | Modelos locais, self-hosted | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Não necessária | Implantação local, compatível com OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varia | Proxy para 100+ providers | @@ -446,9 +447,7 @@ Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens: | **DingTalk** | Médio (credenciais do cliente) | Stream | [Guia](docs/channels/dingtalk/README.pt-br.md) | | **Feishu / Lark** | Médio (App ID + Secret) | WebSocket/SDK | [Guia](docs/channels/feishu/README.pt-br.md) | | **LINE** | Médio (credenciais + webhook) | Webhook | [Guia](docs/channels/line/README.pt-br.md) | -| **WeCom Bot** | Médio (webhook URL) | Webhook | [Guia](docs/channels/wecom/wecom_bot/README.pt-br.md) | -| **WeCom App** | Médio (credenciais corporativas) | Webhook | [Guia](docs/channels/wecom/wecom_app/README.pt-br.md) | -| **WeCom AI Bot** | Médio (token + chave AES) | WebSocket / Webhook | [Guia](docs/channels/wecom/wecom_aibot/README.pt-br.md) | +| **WeCom** | Fácil (login QR ou manual) | WebSocket | [Guia](docs/channels/wecom/README.md) | | **IRC** | Médio (servidor + nick) | Protocolo IRC | [Guia](docs/pt-br/chat-apps.md#irc) | | **OneBot** | Médio (WebSocket URL) | OneBot v11 | [Guia](docs/channels/onebot/README.pt-br.md) | | **MaixCam** | Fácil (habilitar) | TCP socket | [Guia](docs/channels/maixcam/README.pt-br.md) | diff --git a/README.vi.md b/README.vi.md index a345e99fc..7274167c3 100644 --- a/README.vi.md +++ b/README.vi.md @@ -390,6 +390,7 @@ PicoClaw hỗ trợ 30+ Provider LLM thông qua cấu hình `model_list`. Sử d | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Bắt buộc | Mô hình do NVIDIA lưu trữ | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Bắt buộc | Suy luận nhanh | | [Novita AI](https://novita.ai/) | `novita/` | Bắt buộc | Nhiều mô hình mở | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Bắt buộc | Mô hình MiMo | | [Ollama](https://ollama.com/) | `ollama/` | Không cần | Mô hình cục bộ, tự lưu trữ | | [vLLM](https://docs.vllm.ai/) | `vllm/` | Không cần | Triển khai cục bộ, tương thích OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Tùy | Proxy cho 100+ provider | @@ -446,9 +447,7 @@ Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin: | **DingTalk** | Trung bình (client credentials) | Stream | [Hướng dẫn](docs/channels/dingtalk/README.vi.md) | | **Feishu / Lark** | Trung bình (App ID + Secret) | WebSocket/SDK | [Hướng dẫn](docs/channels/feishu/README.vi.md) | | **LINE** | Trung bình (credentials + webhook) | Webhook | [Hướng dẫn](docs/channels/line/README.vi.md) | -| **WeCom Bot** | Trung bình (webhook URL) | Webhook | [Hướng dẫn](docs/channels/wecom/wecom_bot/README.vi.md) | -| **WeCom App** | Trung bình (corp credentials) | Webhook | [Hướng dẫn](docs/channels/wecom/wecom_app/README.vi.md) | -| **WeCom AI Bot** | Trung bình (token + AES key) | WebSocket / Webhook | [Hướng dẫn](docs/channels/wecom/wecom_aibot/README.vi.md) | +| **WeCom** | Dễ (đăng nhập QR hoặc thủ công) | WebSocket | [Hướng dẫn](docs/channels/wecom/README.md) | | **IRC** | Trung bình (server + nick) | IRC protocol | [Hướng dẫn](docs/vi/chat-apps.md#irc) | | **OneBot** | Trung bình (WebSocket URL) | OneBot v11 | [Hướng dẫn](docs/channels/onebot/README.vi.md) | | **MaixCam** | Dễ (bật) | TCP socket | [Hướng dẫn](docs/channels/maixcam/README.vi.md) | diff --git a/README.zh.md b/README.zh.md index 5e1e3c394..021f1e28c 100644 --- a/README.zh.md +++ b/README.zh.md @@ -390,6 +390,7 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模 | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必填 | NVIDIA 托管模型 | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必填 | 快速推理 | | [Novita AI](https://novita.ai/) | `novita/` | 必填 | 多种开源模型 | +| [小米 MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必填 | MiMo 系列模型 | | [Ollama](https://ollama.com/) | `ollama/` | 无需 | 本地模型,自托管 | | [vLLM](https://docs.vllm.ai/) | `vllm/` | 无需 | 本地部署,兼容 OpenAI | | [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 视情况 | 100+ Provider 代理 | @@ -446,9 +447,7 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模 | **钉钉** | 中等(client credentials) | Stream | [指南](docs/channels/dingtalk/README.zh.md) | | **飞书 / Lark** | 中等(App ID + Secret) | WebSocket/SDK | [指南](docs/channels/feishu/README.zh.md) | | **LINE** | 中等(credentials + webhook) | Webhook | [指南](docs/channels/line/README.zh.md) | -| **企业微信机器人** | 中等(webhook URL) | Webhook | [指南](docs/channels/wecom/wecom_bot/README.zh.md) | -| **企业微信应用** | 中等(corp credentials) | Webhook | [指南](docs/channels/wecom/wecom_app/README.zh.md) | -| **企业微信 AI 机器人** | 中等(token + AES key) | WebSocket / Webhook | [指南](docs/channels/wecom/wecom_aibot/README.zh.md) | +| **企业微信** | 简单(扫码登录或手动配置) | WebSocket | [指南](docs/channels/wecom/README.zh.md) | | **IRC** | 中等(server + nick) | IRC 协议 | [指南](docs/zh/chat-apps.md#irc) | | **OneBot** | 中等(WebSocket URL) | OneBot v11 | [指南](docs/channels/onebot/README.zh.md) | | **MaixCam** | 简单(启用即可) | TCP socket | [指南](docs/channels/maixcam/README.zh.md) | diff --git a/assets/wecom-qr-binding.jpg b/assets/wecom-qr-binding.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4768d0d716e05d8df8d1f2519eb9d2da97a5e082 GIT binary patch literal 238989 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�!-zwg(Mh!~Y7HI9gMuz(aJ8Fc^w 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 z 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%0DT1n~R-fCZ5ED<#(;&1-SH4XYm?~s6DXg`(ndC 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 literal 0 HcmV?d00001 diff --git a/docs/channels/wecom/README.md b/docs/channels/wecom/README.md index ecdfbc47b..e99f6540d 100644 --- a/docs/channels/wecom/README.md +++ b/docs/channels/wecom/README.md @@ -2,8 +2,10 @@ # WeCom -PicoClaw now exposes WeCom as a single `channels.wecom` channel built on the official WeCom AI Bot WebSocket API. -This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one configuration model. +PicoClaw exposes WeCom as a single `channels.wecom` channel built on the official WeCom AI Bot WebSocket API. +This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one unified configuration model. + +> No public webhook callback URL is required. PicoClaw opens an outbound WebSocket connection to WeCom. ## What This Channel Supports @@ -11,14 +13,22 @@ This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one - Channel-side streaming replies over WeCom's AI Bot protocol - Incoming text, voice, image, file, video, and mixed messages - Outbound text and media replies (`image`, `file`, `voice`, `video`) -- QR-based CLI onboarding with `picoclaw auth wecom` +- QR-based onboarding via Web UI or CLI - Shared allowlist and `reasoning_channel_id` routing -> No public webhook callback URL is required for this channel. PicoClaw opens an outbound WebSocket connection to WeCom. +--- ## Quick Start -### Option 1: QR Login From CLI +### Option 1: Web UI QR Binding (Recommended) + +Open the Web UI, navigate to **Channels → WeCom**, and click the QR binding button. Scan the QR code with WeCom and confirm in the app — credentials are saved automatically. + +

    +WeCom QR binding in Web UI +

    + +### Option 2: CLI QR Login Run: @@ -26,16 +36,23 @@ Run: picoclaw auth wecom ``` -The command prints a QR code in the terminal, waits for confirmation in WeCom, and then writes the resulting -`bot_id` and `secret` into `channels.wecom`. +The command: +1. Requests a QR code from WeCom and prints it in the terminal +2. Also prints a **QR Code Link** you can open in a browser if the terminal QR is hard to scan +3. Polls for confirmation — after scanning, you must also **confirm the login inside the WeCom app** +4. On success, writes `bot_id` and `secret` into `channels.wecom` and saves the config -Use `--timeout` if you want to wait longer: +The default timeout is **5 minutes**. Use `--timeout` to extend it: ```bash picoclaw auth wecom --timeout 10m ``` -### Option 2: Configure Manually +> ⚠️ Scanning the QR code is not enough — you must also tap **Confirm** inside the WeCom app, otherwise the command will time out. + +### Option 3: Configure Manually + +If you already have a `bot_id` and `secret` from the WeCom AI Bot platform, configure directly: ```json { @@ -53,52 +70,79 @@ picoclaw auth wecom --timeout 10m } ``` +--- + ## Configuration -| Field | Type | Required | Description | -| ----- | ---- | -------- | ----------- | -| `enabled` | bool | No | Enables the WeCom channel. | -| `bot_id` | string | Yes | WeCom AI Bot identifier. Required when the channel is enabled. | -| `secret` | string | Yes | WeCom AI Bot secret. Required when the channel is enabled. | -| `websocket_url` | string | No | WebSocket endpoint. Defaults to `wss://openws.work.weixin.qq.com`. | -| `send_thinking_message` | bool | No | Sends an initial `Processing...` chunk before the final streamed reply. Defaults to `true`. | -| `allow_from` | array | No | Sender allowlist. Empty means allow all senders. | -| `reasoning_channel_id` | string | No | Optional destination for reasoning/thinking output. | +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `enabled` | bool | `false` | Enable the WeCom channel. | +| `bot_id` | string | — | WeCom AI Bot identifier. Required when enabled. | +| `secret` | string | — | WeCom AI Bot secret. Stored encrypted in `.security.yml`. Required when enabled. | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | WeCom WebSocket endpoint. | +| `send_thinking_message` | bool | `true` | Send a `Processing...` message before the streamed reply begins. | +| `allow_from` | array | `[]` | Sender allowlist. Empty means allow all senders. | +| `reasoning_channel_id` | string | `""` | Optional chat ID to route reasoning/thinking output to a separate conversation. | + +### Environment Variables + +All fields can be overridden via environment variables with the prefix `PICOCLAW_CHANNELS_WECOM_`: + +| Environment Variable | Corresponding Field | +| -------------------- | ------------------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- ## Runtime Behavior -- PicoClaw keeps the active WeCom turn so normal replies can continue the same stream when possible. -- If streaming is no longer available, replies fall back to active push delivery to the resolved chat route. -- Incoming media is downloaded into the media store before being handed to the agent. -- Outbound media is uploaded to WeCom in temporary chunks and then sent as a regular media message. +- PicoClaw maintains an active WeCom turn so streaming replies can continue on the same stream when possible. +- Streaming replies have a maximum duration of **5.5 minutes** and a minimum send interval of **500ms**. +- If streaming is no longer available, replies fall back to active push delivery. +- Chat route associations expire after **30 minutes** of inactivity. +- Incoming media is downloaded into the local media store before being passed to the agent. +- Outbound media is uploaded to WeCom as a temporary file and then sent as a media message. +- Duplicate messages are detected and suppressed (ring buffer of last 1000 message IDs). -## Migration Notes +--- -This branch removes the old multi-channel WeCom model. +## Migration from Legacy WeCom Config -| Previous config | Now | -| --------------- | --- | -| `channels.wecom` webhook bot | Replace with `channels.wecom` using `bot_id` + `secret`. | -| `channels.wecom_app` | Remove it and use `channels.wecom`. | -| `channels.wecom_aibot` | Move the config to `channels.wecom`. | -| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | No longer used by the WeCom channel. | -| `corp_id`, `corp_secret`, `agent_id` | No longer used by the WeCom channel. | -| `welcome_message`, `processing_message`, `max_steps` under WeCom | No longer part of the WeCom channel config. | +| Previous config | Migration | +| --------------- | --------- | +| `channels.wecom` (webhook bot) | Replace with `channels.wecom` using `bot_id` + `secret`. | +| `channels.wecom_app` | Remove. Use `channels.wecom` instead. | +| `channels.wecom_aibot` | Move `bot_id` and `secret` to `channels.wecom`. | +| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | No longer used. Remove from config. | +| `corp_id`, `corp_secret`, `agent_id` | No longer used. Remove from config. | +| `welcome_message`, `processing_message`, `max_steps` | No longer part of the WeCom channel config. | + +--- ## Troubleshooting -### `picoclaw auth wecom` times out +### QR binding times out -- Re-run with a larger `--timeout`. -- Make sure the QR code was confirmed inside WeCom, not only scanned. +- After scanning the QR code, you must also **confirm the login inside the WeCom app**. Scanning alone is not enough. +- Re-run with a larger `--timeout`: `picoclaw auth wecom --timeout 10m` +- If the QR code in the terminal is hard to scan, use the **QR Code Link** printed below it to open in a browser. + +### QR code expired + +- The QR code has a limited validity. Re-run `picoclaw auth wecom` to get a fresh one. ### WebSocket connection fails -- Verify `bot_id` and `secret`. -- Confirm the host can reach `wss://openws.work.weixin.qq.com`. +- Verify `bot_id` and `secret` are correct. +- Confirm the host can reach `wss://openws.work.weixin.qq.com` (outbound WebSocket, no inbound port needed). ### Replies do not arrive -- Check whether `allow_from` blocks the sender. -- Check launcher or startup validation for missing `channels.wecom.bot_id` / `channels.wecom.secret`. - +- Check whether `allow_from` is blocking the sender. +- Check that `channels.wecom.bot_id` and `channels.wecom.secret` are set and non-empty. diff --git a/docs/channels/wecom/README.zh.md b/docs/channels/wecom/README.zh.md index 6b4a5e495..2134b94b5 100644 --- a/docs/channels/wecom/README.zh.md +++ b/docs/channels/wecom/README.zh.md @@ -1,24 +1,34 @@ > 返回 [README](../../../README.zh.md) -# 企业微信 +# 企业微信(WeCom) -PicoClaw 现在将企业微信统一为一个 `channels.wecom` 渠道,并基于企业微信官方 AI Bot WebSocket 协议实现。 -这取代了旧的 `wecom`、`wecom_app`、`wecom_aibot` 三套配置模型。 +PicoClaw 将企业微信整合为单一的 `channels.wecom` 渠道,基于腾讯官方企业微信 AI Bot WebSocket API 实现。 +原有的 `wecom`、`wecom_app`、`wecom_aibot` 三个独立渠道已合并为统一配置模型。 -## 当前渠道能力 +> 本渠道无需公网 Webhook 回调地址。PicoClaw 主动向企业微信建立出站 WebSocket 连接。 -- 支持私聊和群聊 -- 支持企业微信侧流式回复 -- 支持接收文本、语音、图片、文件、视频和 mixed 消息 -- 支持发送文本与媒体消息(`image`、`file`、`voice`、`video`) -- 支持通过 `picoclaw auth wecom` 扫码写入配置 -- 支持统一白名单与 `reasoning_channel_id` +## 支持的功能 -> 这个渠道不再需要公网 webhook 回调地址。PicoClaw 会主动向企业微信发起 WebSocket 连接。 +- 单聊和群聊消息收发 +- 基于企业微信 AI Bot 协议的流式回复 +- 接收文本、语音、图片、文件、视频及混合消息 +- 发送文本及媒体消息(`image`、`file`、`voice`、`video`) +- 通过 Web UI 或 CLI 扫码绑定 +- 发送者白名单和 `reasoning_channel_id` 路由 + +--- ## 快速开始 -### 方式 1:命令行扫码登录 +### 方式一:Web UI 扫码绑定(推荐) + +打开 Web UI,进入 **Channels → WeCom**,点击扫码绑定按钮。用企业微信扫码并在 App 内确认,凭据自动保存。 + +

    +Web UI 企业微信扫码绑定 +

    + +### 方式二:CLI 扫码登录 运行: @@ -26,16 +36,23 @@ PicoClaw 现在将企业微信统一为一个 `channels.wecom` 渠道,并基 picoclaw auth wecom ``` -该命令会在终端打印二维码,等待你在企业微信中确认,然后把生成的 `bot_id` 和 `secret` 写入 -`channels.wecom`。 +命令执行流程: +1. 向企业微信请求二维码并在终端打印 +2. 同时打印一个**二维码链接**,终端二维码不清晰时可在浏览器中打开 +3. 轮询确认状态——扫码后还需要在**企业微信 App 内点击确认** +4. 成功后将 `bot_id` 和 `secret` 写入 `channels.wecom` 并保存配置 -如果需要更长等待时间,可以加 `--timeout`: +默认超时为 **5 分钟**,可通过 `--timeout` 延长: ```bash picoclaw auth wecom --timeout 10m ``` -### 方式 2:手动配置 +> ⚠️ 仅扫描二维码还不够——必须在企业微信 App 内点击**确认**,否则命令会超时。 + +### 方式三:手动配置 + +如果已有企业微信 AI Bot 的 `bot_id` 和 `secret`,可直接配置: ```json { @@ -53,52 +70,79 @@ picoclaw auth wecom --timeout 10m } ``` -## 配置字段 +--- -| 字段 | 类型 | 必填 | 说明 | -| ---- | ---- | ---- | ---- | -| `enabled` | bool | 否 | 是否启用企业微信渠道。 | -| `bot_id` | string | 是 | 企业微信 AI Bot 标识。渠道启用时必填。 | -| `secret` | string | 是 | 企业微信 AI Bot 密钥。渠道启用时必填。 | -| `websocket_url` | string | 否 | WebSocket 地址,默认 `wss://openws.work.weixin.qq.com`。 | -| `send_thinking_message` | bool | 否 | 是否在流式最终回复前先发送一段 `Processing...` 开场消息,默认 `true`。 | -| `allow_from` | array | 否 | 发送者白名单;空数组表示允许所有发送者。 | -| `reasoning_channel_id` | string | 否 | 可选的 reasoning/thinking 输出目标。 | +## 配置项说明 + +| 字段 | 类型 | 默认值 | 说明 | +| ---- | ---- | ------ | ---- | +| `enabled` | bool | `false` | 启用企业微信渠道。 | +| `bot_id` | string | — | 企业微信 AI Bot 标识符。启用时必填。 | +| `secret` | string | — | 企业微信 AI Bot 密钥。加密存储于 `.security.yml`。启用时必填。 | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | 企业微信 WebSocket 端点。 | +| `send_thinking_message` | bool | `true` | 在流式回复开始前发送"处理中..."提示消息。 | +| `allow_from` | array | `[]` | 发送者白名单。为空时允许所有人。 | +| `reasoning_channel_id` | string | `""` | 可选,将推理/思考内容路由到指定会话 ID。 | + +### 环境变量 + +所有字段均可通过 `PICOCLAW_CHANNELS_WECOM_` 前缀的环境变量覆盖: + +| 环境变量 | 对应字段 | +| -------- | -------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- ## 运行时行为 -- PicoClaw 会保留当前会话对应的企业微信 turn,优先继续同一个流式回复。 -- 如果流式上下文已经失效,回复会自动回退到主动推送消息。 -- 收到的媒体会先下载到 media store,再交给 Agent 处理。 -- 发出的媒体会先按分片上传到企业微信,再作为普通媒体消息发送。 +- PicoClaw 维护活跃的企业微信 Turn,流式回复尽可能在同一流上继续。 +- 流式回复最大持续时长为 **5.5 分钟**,最小发送间隔为 **500ms**。 +- 流式不可用时,回复降级为主动推送。 +- 会话路由关联在 **30 分钟**无活动后过期。 +- 接收到的媒体文件先下载到本地媒体存储,再传递给 Agent。 +- 发送媒体时先上传为企业微信临时文件,再作为媒体消息发送。 +- 自动检测并过滤重复消息(环形缓冲区,最多记录 1000 条消息 ID)。 -## 迁移说明 +--- -这个分支移除了旧的多通道企业微信模型。 +## 从旧版企业微信配置迁移 -| 旧配置 | 现在怎么做 | -| ------ | ---------- | -| `channels.wecom` webhook 机器人 | 改为使用 `bot_id` + `secret` 的 `channels.wecom`。 | -| `channels.wecom_app` | 删除,统一迁移到 `channels.wecom`。 | -| `channels.wecom_aibot` | 配置迁移到 `channels.wecom`。 | -| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 企业微信渠道不再使用这些字段。 | -| `corp_id`、`corp_secret`、`agent_id` | 企业微信渠道不再使用这些字段。 | -| 企业微信下的 `welcome_message`、`processing_message`、`max_steps` | 不再属于企业微信渠道配置。 | +| 旧配置 | 迁移方式 | +| ------ | -------- | +| `channels.wecom`(Webhook 机器人) | 改用 `channels.wecom`,填写 `bot_id` + `secret`。 | +| `channels.wecom_app` | 删除,改用 `channels.wecom`。 | +| `channels.wecom_aibot` | 将 `bot_id` 和 `secret` 移至 `channels.wecom`。 | +| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 已废弃,从配置中删除。 | +| `corp_id`、`corp_secret`、`agent_id` | 已废弃,从配置中删除。 | +| `welcome_message`、`processing_message`、`max_steps` | 已不属于企业微信渠道配置,删除即可。 | + +--- ## 常见问题 -### `picoclaw auth wecom` 超时 +### 扫码绑定超时 -- 用更大的 `--timeout` 重新执行。 -- 确认是在企业微信里完成了确认,而不只是扫描二维码。 +- 扫码后必须在**企业微信 App 内点击确认**,仅扫码不够。 +- 使用更长的超时重试:`picoclaw auth wecom --timeout 10m` +- 终端二维码不清晰时,使用命令打印的**二维码链接**在浏览器中打开。 + +### 二维码已过期 + +- 二维码有效期有限,重新运行 `picoclaw auth wecom` 获取新二维码。 ### WebSocket 连接失败 - 检查 `bot_id` 和 `secret` 是否正确。 -- 确认运行环境可以访问 `wss://openws.work.weixin.qq.com`。 +- 确认设备可以访问 `wss://openws.work.weixin.qq.com`(出站 WebSocket,无需开放入站端口)。 -### 消息没有回到企业微信 - -- 检查 `allow_from` 是否拦截了发送者。 -- 检查启动日志或 launcher 校验,确认 `channels.wecom.bot_id` / `channels.wecom.secret` 已填写。 +### 收不到回复 +- 检查 `allow_from` 是否屏蔽了发送者。 +- 确认 `channels.wecom.bot_id` 和 `channels.wecom.secret` 已填写且非空。 diff --git a/docs/channels/wecom/wecom_aibot/README.fr.md b/docs/channels/wecom/wecom_aibot/README.fr.md deleted file mode 100644 index 8020dd7b0..000000000 --- a/docs/channels/wecom/wecom_aibot/README.fr.md +++ /dev/null @@ -1,118 +0,0 @@ -> Retour au [README](../../../../README.fr.md) - -# WeCom AI Bot - -Le WeCom AI Bot est une méthode d'intégration de conversation IA officiellement fournie par WeCom. Il prend en charge les conversations privées et de groupe, intègre un protocole de réponse en streaming et supporte l'envoi proactif de la réponse finale via `response_url` en cas de dépassement de délai. - -## Comparaison avec les autres canaux WeCom - -| Fonctionnalité | WeCom Bot | WeCom App | **WeCom AI Bot** | -|----------------|-----------|-----------|-----------------| -| Chat privé | ✅ | ✅ | ✅ | -| Chat de groupe | ✅ | ❌ | ✅ | -| Sortie en streaming | ❌ | ❌ | ✅ | -| Push proactif en cas de timeout | ❌ | ✅ | ✅ | -| Complexité de configuration | Faible | Élevée | Moyenne | - -## Configuration - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -| Champ | Type | Requis | Description | -| ---------------- | ------ | ------ | -------------------------------------------------- | -| token | string | Oui | Jeton de vérification du callback, configuré sur la page de gestion de l'AI Bot | -| encoding_aes_key | string | Oui | Clé AES de 43 caractères, générée aléatoirement sur la page de gestion de l'AI Bot | -| webhook_path | string | Non | Chemin du webhook (par défaut : /webhook/wecom-aibot) | -| allow_from | array | Non | Liste blanche d'ID utilisateurs ; un tableau vide autorise tous les utilisateurs | -| welcome_message | string | Non | Message de bienvenue envoyé à l'ouverture du chat ; laisser vide pour désactiver | -| reply_timeout | int | Non | Délai de réponse en secondes (par défaut : 5) | -| max_steps | int | Non | Nombre maximum d'étapes d'exécution de l'agent (par défaut : 10) | - -## Procédure de configuration - -1. Connectez-vous à la [console d'administration WeCom](https://work.weixin.qq.com/wework_admin) -2. Accédez à « Gestion des applications » → « AI Bot », puis créez ou sélectionnez un AI Bot -3. Sur la page de configuration de l'AI Bot, renseignez les informations de « Réception des messages » : - - **URL** : `http://:18790/webhook/wecom-aibot` - - **Token** : Généré aléatoirement ou personnalisé - - **EncodingAESKey** : Cliquez sur « Générer aléatoirement » pour obtenir une clé de 43 caractères -4. Saisissez le Token et l'EncodingAESKey dans le fichier de configuration PicoClaw, démarrez le service, puis revenez à la console d'administration pour enregistrer (WeCom enverra une requête de vérification) - -> [!TIP] -> Le serveur doit être accessible par les serveurs WeCom. Si vous êtes sur un intranet ou en développement local, utilisez [ngrok](https://ngrok.com) ou frp pour le tunneling. - -## Protocole de réponse en streaming - -Le WeCom AI Bot utilise un protocole de « pull en streaming », différent de la réponse unique d'un webhook standard : - -``` -L'utilisateur envoie un message - │ - ▼ -PicoClaw retourne immédiatement {finish: false} (l'agent commence le traitement) - │ - ▼ -WeCom effectue un pull environ toutes les 1 seconde avec {msgtype: "stream", stream: {id: "..."}} - │ - ├─ Agent non terminé → retourne {finish: false} (continuer à attendre) - │ - └─ Agent terminé → retourne {finish: true, content: "contenu de la réponse"} -``` - -**Gestion du timeout** (tâche dépassant 30 secondes) : - -Si le traitement de l'agent dépasse environ 30 secondes (la fenêtre de polling maximale de WeCom est de 6 minutes), PicoClaw va : - -1. Fermer immédiatement le stream et afficher à l'utilisateur : « ⏳ 正在处理中,请稍候,结果将稍后发送。 » -2. L'agent continue de s'exécuter en arrière-plan -3. Une fois l'agent terminé, la réponse finale est envoyée proactivement à l'utilisateur via le `response_url` inclus dans le message - -> `response_url` est émis par WeCom, valable 1 heure, utilisable une seule fois, sans chiffrement requis — il suffit de POSTer directement le corps du message markdown. - -## Message de bienvenue - -Lorsque `welcome_message` est configuré, PicoClaw répond automatiquement avec ce message lorsqu'un utilisateur ouvre la fenêtre de chat avec l'AI Bot (événement `enter_chat`). Laisser vide pour ignorer silencieusement. - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## FAQ - -### Échec de la vérification de l'URL de callback - -- Vérifiez que le pare-feu du serveur autorise le port concerné (par défaut 18790) -- Vérifiez que `token` et `encoding_aes_key` sont correctement renseignés -- Consultez les logs PicoClaw pour voir si une requête GET de WeCom a été reçue - -### Les messages ne reçoivent pas de réponse - -- Vérifiez que `allow_from` ne restreint pas accidentellement l'expéditeur -- Recherchez `context canceled` ou des erreurs d'agent dans les logs -- Vérifiez que la configuration de l'agent (ex. `model_name`) est correcte - -### Pas de push final reçu pour les tâches longues - -- Vérifiez que le callback du message inclut `response_url` (uniquement supporté par la nouvelle version du WeCom AI Bot) -- Vérifiez que le serveur peut effectuer des requêtes sortantes (nécessite un POST vers `response_url`) -- Consultez les logs pour les mots-clés `response_url mode` et `Sending reply via response_url` - -## Références - -- [Documentation d'intégration WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719) -- [Description du protocole de réponse en streaming](https://developer.work.weixin.qq.com/document/path/100719) -- [Réponse proactive via response_url](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_aibot/README.ja.md b/docs/channels/wecom/wecom_aibot/README.ja.md deleted file mode 100644 index 210caffb4..000000000 --- a/docs/channels/wecom/wecom_aibot/README.ja.md +++ /dev/null @@ -1,118 +0,0 @@ -> [README](../../../../README.ja.md) に戻る - -# 企業WeChat AIボット - -企業WeChat AIボット(AI Bot)は、企業WeChatが公式に提供するAI会話連携方式です。プライベートチャットとグループチャットの両方をサポートし、ストリーミングレスポンスプロトコルを内蔵しており、タイムアウト後に `response_url` を通じて最終返信をプッシュする機能もサポートしています。 - -## 他のWeCom チャンネルとの比較 - -| 機能 | WeCom Bot | WeCom App | **WeCom AI Bot** | -|------|-----------|-----------|-----------------| -| プライベートチャット | ✅ | ✅ | ✅ | -| グループチャット | ✅ | ❌ | ✅ | -| ストリーミング出力 | ❌ | ❌ | ✅ | -| タイムアウト時のプッシュ | ❌ | ✅ | ✅ | -| 設定の複雑さ | 低 | 高 | 中 | - -## 設定 - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -| フィールド | 型 | 必須 | 説明 | -| ---------------- | ------ | ---- | -------------------------------------------------- | -| token | string | はい | コールバック検証トークン。AIボット管理ページで設定 | -| encoding_aes_key | string | はい | 43文字のAESキー。AIボット管理ページでランダム生成 | -| webhook_path | string | いいえ | Webhookパス(デフォルト:/webhook/wecom-aibot) | -| allow_from | array | いいえ | ユーザーIDの許可リスト。空配列は全ユーザーを許可 | -| welcome_message | string | いいえ | ユーザーがチャットを開いたときに送信するウェルカムメッセージ。空白の場合は送信しない | -| reply_timeout | int | いいえ | 返信タイムアウト(秒、デフォルト:5) | -| max_steps | int | いいえ | エージェントの最大実行ステップ数(デフォルト:10) | - -## セットアップ手順 - -1. [企業WeChat管理コンソール](https://work.weixin.qq.com/wework_admin) にログイン -2. 「アプリ管理」→「AIボット」に進み、AIボットを作成または選択 -3. AIボット設定ページで「メッセージ受信」情報を入力: - - **URL**:`http://:18790/webhook/wecom-aibot` - - **Token**:ランダム生成またはカスタム - - **EncodingAESKey**:「ランダム生成」をクリックして43文字のキーを取得 -4. TokenとEncodingAESKeyをPicoClawの設定ファイルに入力し、サービスを起動してから管理コンソールに戻って保存(企業WeChatが検証リクエストを送信します) - -> [!TIP] -> サーバーは企業WeChatのサーバーからアクセス可能である必要があります。イントラネットやローカル開発環境の場合は、[ngrok](https://ngrok.com) またはfrpを使用してトンネリングしてください。 - -## ストリーミングレスポンスプロトコル - -WeCom AIボットは「ストリーミングプル」プロトコルを使用しており、通常のWebhookの一回限りの返信とは異なります: - -``` -ユーザーがメッセージを送信 - │ - ▼ -PicoClawが即座に {finish: false} を返す(エージェントが処理開始) - │ - ▼ -企業WeChatが約1秒ごとに {msgtype: "stream", stream: {id: "..."}} でプル - │ - ├─ エージェント未完了 → {finish: false} を返す(待機継続) - │ - └─ エージェント完了 → {finish: true, content: "返信内容"} を返す -``` - -**タイムアウト処理**(タスクが30秒を超える場合): - -エージェントの処理時間が約30秒を超えた場合(企業WeChatの最大ポーリングウィンドウは6分)、PicoClawは: - -1. 即座にストリームを閉じ、ユーザーに「⏳ 正在处理中,请稍候,结果将稍后发送。」と表示 -2. エージェントはバックグラウンドで処理を継続 -3. エージェント完了後、メッセージに含まれる `response_url` を通じて最終返信をユーザーにプッシュ - -> `response_url` は企業WeChatが発行し、有効期限は1時間、使用は1回限りで、暗号化不要。マークダウンメッセージ本文をそのままPOSTするだけです。 - -## ウェルカムメッセージ - -`welcome_message` を設定すると、ユーザーがAIボットとのチャットウィンドウを開いたとき(`enter_chat` イベント)に、PicoClawが自動的にそのメッセージを返信します。空白の場合は無視されます。 - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## よくある質問 - -### コールバックURL検証の失敗 - -- サーバーのファイアウォールで該当ポートが開放されているか確認(デフォルト18790) -- `token` と `encoding_aes_key` が正しく入力されているか確認 -- PicoClawのログに企業WeChatからのGETリクエストが届いているか確認 - -### メッセージに返信がない - -- `allow_from` が誤って送信者を制限していないか確認 -- ログに `context canceled` またはエージェントエラーが出ていないか確認 -- エージェント設定(`model_name` など)が正しいか確認 - -### 長時間タスクで最終プッシュが届かない - -- メッセージコールバックに `response_url` が含まれているか確認(新バージョンの企業WeChat AIボットのみ対応) -- サーバーが外部ネットワークへのアウトバウンドリクエストを送信できるか確認(`response_url` へのPOSTが必要) -- ログのキーワード `response_url mode` と `Sending reply via response_url` を確認 - -## 参考ドキュメント - -- [企業WeChat AIボット連携ドキュメント](https://developer.work.weixin.qq.com/document/path/100719) -- [ストリーミングレスポンスプロトコルの説明](https://developer.work.weixin.qq.com/document/path/100719) -- [response_url によるプロアクティブ返信](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_aibot/README.md b/docs/channels/wecom/wecom_aibot/README.md deleted file mode 100644 index 31d831617..000000000 --- a/docs/channels/wecom/wecom_aibot/README.md +++ /dev/null @@ -1,118 +0,0 @@ -> Back to [README](../../../../README.md) - -# WeCom AI Bot - -The WeCom AI Bot is an official AI conversation integration provided by WeCom. It supports both private and group chats, has a built-in streaming response protocol, and supports proactively pushing the final reply via `response_url` after a timeout. - -## Comparison with Other WeCom Channels - -| Feature | WeCom Bot | WeCom App | **WeCom AI Bot** | -|---------|-----------|-----------|-----------------| -| Private Chat | ✅ | ✅ | ✅ | -| Group Chat | ✅ | ❌ | ✅ | -| Streaming Output | ❌ | ❌ | ✅ | -| Proactive Push on Timeout | ❌ | ✅ | ✅ | -| Configuration Complexity | Low | High | Medium | - -## Configuration - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -| Field | Type | Required | Description | -| ---------------- | ------ | -------- | -------------------------------------------------- | -| token | string | Yes | Callback verification token, configured on the AI Bot management page | -| encoding_aes_key | string | Yes | 43-character AES key, randomly generated on the AI Bot management page | -| webhook_path | string | No | Webhook path (default: /webhook/wecom-aibot) | -| allow_from | array | No | User ID allowlist; empty array allows all users | -| welcome_message | string | No | Welcome message sent when a user opens the chat; leave empty to disable | -| reply_timeout | int | No | Reply timeout in seconds (default: 5) | -| max_steps | int | No | Maximum agent execution steps (default: 10) | - -## Setup - -1. Log in to the [WeCom Admin Console](https://work.weixin.qq.com/wework_admin) -2. Go to "App Management" → "AI Bot", then create or select an AI Bot -3. On the AI Bot configuration page, fill in the "Message Reception" details: - - **URL**: `http://:18790/webhook/wecom-aibot` - - **Token**: Randomly generated or custom - - **EncodingAESKey**: Click "Random Generate" to get a 43-character key -4. Enter the Token and EncodingAESKey into the PicoClaw config file, start the service, then return to the admin console to save (WeCom will send a verification request) - -> [!TIP] -> The server must be accessible by WeCom's servers. If you are on an intranet or developing locally, use [ngrok](https://ngrok.com) or frp for tunneling. - -## Streaming Response Protocol - -WeCom AI Bot uses a "streaming pull" protocol, which differs from the one-shot reply of a standard webhook: - -``` -User sends a message - │ - ▼ -PicoClaw immediately returns {finish: false} (Agent starts processing) - │ - ▼ -WeCom pulls approximately every 1 second with {msgtype: "stream", stream: {id: "..."}} - │ - ├─ Agent not done → returns {finish: false} (keep waiting) - │ - └─ Agent done → returns {finish: true, content: "reply content"} -``` - -**Timeout Handling** (task exceeds 30 seconds): - -If the Agent takes longer than approximately 30 seconds (WeCom's maximum polling window is 6 minutes), PicoClaw will: - -1. Immediately close the stream and show the user: "⏳ 正在处理中,请稍候,结果将稍后发送。" -2. The Agent continues running in the background -3. Once the Agent finishes, the final reply is proactively pushed to the user via the `response_url` included in the message - -> `response_url` is issued by WeCom, valid for 1 hour, can only be used once, requires no encryption — just POST the markdown message body directly. - -## Welcome Message - -When `welcome_message` is configured, PicoClaw will automatically reply with it when a user opens the chat window with the AI Bot (`enter_chat` event). Leave it empty to silently ignore the event. - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## FAQ - -### Callback URL Verification Failed - -- Confirm the server firewall has the relevant port open (default 18790) -- Confirm `token` and `encoding_aes_key` are entered correctly -- Check PicoClaw logs to see if a GET request from WeCom was received - -### Messages Not Getting a Reply - -- Check whether `allow_from` is accidentally restricting the sender -- Look for `context canceled` or Agent errors in the logs -- Confirm the Agent configuration (e.g., `model_name`) is correct - -### No Final Push Received for Long-Running Tasks - -- Confirm the message callback includes `response_url` (only supported by the newer WeCom AI Bot) -- Confirm the server can make outbound requests (needs to POST to `response_url`) -- Check logs for keywords `response_url mode` and `Sending reply via response_url` - -## Reference - -- [WeCom AI Bot Integration Docs](https://developer.work.weixin.qq.com/document/path/100719) -- [Streaming Response Protocol](https://developer.work.weixin.qq.com/document/path/100719) -- [Proactive Reply via response_url](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_aibot/README.pt-br.md b/docs/channels/wecom/wecom_aibot/README.pt-br.md deleted file mode 100644 index 1ab735c41..000000000 --- a/docs/channels/wecom/wecom_aibot/README.pt-br.md +++ /dev/null @@ -1,118 +0,0 @@ -> Voltar ao [README](../../../../README.pt-br.md) - -# WeCom AI Bot - -O WeCom AI Bot é uma forma oficial de integração de conversas com IA fornecida pelo WeCom. Suporta conversas privadas e em grupo, possui um protocolo de resposta em streaming integrado e suporta o envio proativo da resposta final via `response_url` após um timeout. - -## Comparação com outros canais WeCom - -| Recurso | WeCom Bot | WeCom App | **WeCom AI Bot** | -|---------|-----------|-----------|-----------------| -| Chat privado | ✅ | ✅ | ✅ | -| Chat em grupo | ✅ | ❌ | ✅ | -| Saída em streaming | ❌ | ❌ | ✅ | -| Push proativo em timeout | ❌ | ✅ | ✅ | -| Complexidade de configuração | Baixa | Alta | Média | - -## Configuração - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -| Campo | Tipo | Obrigatório | Descrição | -| ---------------- | ------ | ----------- | -------------------------------------------------- | -| token | string | Sim | Token de verificação de callback, configurado na página de gerenciamento do AI Bot | -| encoding_aes_key | string | Sim | Chave AES de 43 caracteres, gerada aleatoriamente na página de gerenciamento do AI Bot | -| webhook_path | string | Não | Caminho do webhook (padrão: /webhook/wecom-aibot) | -| allow_from | array | Não | Lista de permissão de IDs de usuários; array vazio permite todos os usuários | -| welcome_message | string | Não | Mensagem de boas-vindas enviada quando o usuário abre o chat; deixe vazio para desativar | -| reply_timeout | int | Não | Timeout de resposta em segundos (padrão: 5) | -| max_steps | int | Não | Número máximo de etapas de execução do agente (padrão: 10) | - -## Configuração passo a passo - -1. Faça login no [Console de Administração do WeCom](https://work.weixin.qq.com/wework_admin) -2. Acesse "Gerenciamento de Apps" → "AI Bot", depois crie ou selecione um AI Bot -3. Na página de configuração do AI Bot, preencha as informações de "Recebimento de Mensagens": - - **URL**: `http://:18790/webhook/wecom-aibot` - - **Token**: Gerado aleatoriamente ou personalizado - - **EncodingAESKey**: Clique em "Gerar Aleatoriamente" para obter uma chave de 43 caracteres -4. Insira o Token e o EncodingAESKey no arquivo de configuração do PicoClaw, inicie o serviço e volte ao console de administração para salvar (o WeCom enviará uma requisição de verificação) - -> [!TIP] -> O servidor precisa ser acessível pelos servidores do WeCom. Se estiver em uma intranet ou desenvolvendo localmente, use [ngrok](https://ngrok.com) ou frp para tunelamento. - -## Protocolo de resposta em streaming - -O WeCom AI Bot usa um protocolo de "pull em streaming", diferente da resposta única de um webhook padrão: - -``` -Usuário envia uma mensagem - │ - ▼ -PicoClaw retorna imediatamente {finish: false} (Agente começa a processar) - │ - ▼ -WeCom faz pull aproximadamente a cada 1 segundo com {msgtype: "stream", stream: {id: "..."}} - │ - ├─ Agente não concluído → retorna {finish: false} (continuar aguardando) - │ - └─ Agente concluído → retorna {finish: true, content: "conteúdo da resposta"} -``` - -**Tratamento de timeout** (tarefa excede 30 segundos): - -Se o processamento do agente demorar mais de aproximadamente 30 segundos (a janela máxima de polling do WeCom é de 6 minutos), o PicoClaw irá: - -1. Fechar imediatamente o stream e exibir ao usuário: "⏳ 正在处理中,请稍候,结果将稍后发送。" -2. O agente continua executando em segundo plano -3. Após a conclusão do agente, a resposta final é enviada proativamente ao usuário via `response_url` incluído na mensagem - -> `response_url` é emitido pelo WeCom, válido por 1 hora, pode ser usado apenas uma vez, sem necessidade de criptografia — basta fazer um POST com o corpo da mensagem em markdown diretamente. - -## Mensagem de boas-vindas - -Quando `welcome_message` está configurado, o PicoClaw responde automaticamente com essa mensagem quando um usuário abre a janela de chat com o AI Bot (evento `enter_chat`). Deixe vazio para ignorar silenciosamente. - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## Perguntas frequentes - -### Falha na verificação da URL de callback - -- Confirme que o firewall do servidor tem a porta correspondente aberta (padrão 18790) -- Confirme que `token` e `encoding_aes_key` estão preenchidos corretamente -- Verifique os logs do PicoClaw para ver se uma requisição GET do WeCom foi recebida - -### Mensagens sem resposta - -- Verifique se `allow_from` está restringindo acidentalmente o remetente -- Procure por `context canceled` ou erros do agente nos logs -- Confirme que a configuração do agente (ex.: `model_name`) está correta - -### Nenhum push final recebido para tarefas longas - -- Confirme que o callback da mensagem inclui `response_url` (suportado apenas pelo novo WeCom AI Bot) -- Confirme que o servidor consegue fazer requisições de saída (precisa fazer POST para `response_url`) -- Verifique nos logs as palavras-chave `response_url mode` e `Sending reply via response_url` - -## Referências - -- [Documentação de integração do WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719) -- [Descrição do protocolo de resposta em streaming](https://developer.work.weixin.qq.com/document/path/100719) -- [Resposta proativa via response_url](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_aibot/README.vi.md b/docs/channels/wecom/wecom_aibot/README.vi.md deleted file mode 100644 index cb6586e6e..000000000 --- a/docs/channels/wecom/wecom_aibot/README.vi.md +++ /dev/null @@ -1,118 +0,0 @@ -> Quay lại [README](../../../../README.vi.md) - -# WeCom AI Bot - -WeCom AI Bot là phương thức tích hợp hội thoại AI chính thức do WeCom cung cấp. Hỗ trợ cả chat riêng tư và chat nhóm, tích hợp giao thức phản hồi streaming, và hỗ trợ chủ động đẩy phản hồi cuối cùng qua `response_url` sau khi hết thời gian chờ. - -## So sánh với các kênh WeCom khác - -| Tính năng | WeCom Bot | WeCom App | **WeCom AI Bot** | -|-----------|-----------|-----------|-----------------| -| Chat riêng tư | ✅ | ✅ | ✅ | -| Chat nhóm | ✅ | ❌ | ✅ | -| Đầu ra streaming | ❌ | ❌ | ✅ | -| Đẩy chủ động khi timeout | ❌ | ✅ | ✅ | -| Độ phức tạp cấu hình | Thấp | Cao | Trung bình | - -## Cấu hình - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -| Trường | Kiểu | Bắt buộc | Mô tả | -| ---------------- | ------ | --------- | -------------------------------------------------- | -| token | string | Có | Token xác minh callback, cấu hình trên trang quản lý AI Bot | -| encoding_aes_key | string | Có | Khóa AES 43 ký tự, được tạo ngẫu nhiên trên trang quản lý AI Bot | -| webhook_path | string | Không | Đường dẫn webhook (mặc định: /webhook/wecom-aibot) | -| allow_from | array | Không | Danh sách cho phép ID người dùng; mảng rỗng cho phép tất cả người dùng | -| welcome_message | string | Không | Tin nhắn chào mừng gửi khi người dùng mở chat; để trống để tắt | -| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây (mặc định: 5) | -| max_steps | int | Không | Số bước thực thi tối đa của agent (mặc định: 10) | - -## Hướng dẫn thiết lập - -1. Đăng nhập vào [Bảng điều khiển quản trị WeCom](https://work.weixin.qq.com/wework_admin) -2. Vào "Quản lý ứng dụng" → "AI Bot", sau đó tạo hoặc chọn một AI Bot -3. Trên trang cấu hình AI Bot, điền thông tin "Nhận tin nhắn": - - **URL**: `http://:18790/webhook/wecom-aibot` - - **Token**: Tạo ngẫu nhiên hoặc tùy chỉnh - - **EncodingAESKey**: Nhấp "Tạo ngẫu nhiên" để lấy khóa 43 ký tự -4. Nhập Token và EncodingAESKey vào file cấu hình PicoClaw, khởi động dịch vụ rồi quay lại bảng điều khiển quản trị để lưu (WeCom sẽ gửi yêu cầu xác minh) - -> [!TIP] -> Máy chủ cần có thể truy cập được từ các máy chủ WeCom. Nếu bạn đang ở mạng nội bộ hoặc phát triển cục bộ, hãy sử dụng [ngrok](https://ngrok.com) hoặc frp để tạo tunnel. - -## Giao thức phản hồi streaming - -WeCom AI Bot sử dụng giao thức "pull streaming", khác với phản hồi một lần của webhook thông thường: - -``` -Người dùng gửi tin nhắn - │ - ▼ -PicoClaw trả về ngay {finish: false} (Agent bắt đầu xử lý) - │ - ▼ -WeCom pull khoảng mỗi 1 giây với {msgtype: "stream", stream: {id: "..."}} - │ - ├─ Agent chưa xong → trả về {finish: false} (tiếp tục chờ) - │ - └─ Agent xong → trả về {finish: true, content: "nội dung phản hồi"} -``` - -**Xử lý timeout** (tác vụ vượt quá 30 giây): - -Nếu thời gian xử lý của agent vượt quá khoảng 30 giây (cửa sổ polling tối đa của WeCom là 6 phút), PicoClaw sẽ: - -1. Đóng stream ngay lập tức và hiển thị cho người dùng: "⏳ 正在处理中,请稍候,结果将稍后发送。" -2. Agent tiếp tục chạy ở nền -3. Sau khi agent hoàn thành, phản hồi cuối cùng được chủ động đẩy đến người dùng qua `response_url` có trong tin nhắn - -> `response_url` do WeCom cấp, có hiệu lực 1 giờ, chỉ dùng được một lần, không cần mã hóa — chỉ cần POST trực tiếp nội dung tin nhắn markdown. - -## Tin nhắn chào mừng - -Khi `welcome_message` được cấu hình, PicoClaw sẽ tự động phản hồi bằng tin nhắn đó khi người dùng mở cửa sổ chat với AI Bot (sự kiện `enter_chat`). Để trống để bỏ qua im lặng. - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## Câu hỏi thường gặp - -### Xác minh URL callback thất bại - -- Xác nhận tường lửa máy chủ đã mở cổng tương ứng (mặc định 18790) -- Xác nhận `token` và `encoding_aes_key` được điền đúng -- Kiểm tra log PicoClaw xem có nhận được yêu cầu GET từ WeCom không - -### Tin nhắn không nhận được phản hồi - -- Kiểm tra xem `allow_from` có vô tình hạn chế người gửi không -- Tìm `context canceled` hoặc lỗi agent trong log -- Xác nhận cấu hình agent (ví dụ: `model_name`) là đúng - -### Không nhận được push cuối cùng cho tác vụ dài - -- Xác nhận callback tin nhắn có chứa `response_url` (chỉ hỗ trợ bởi WeCom AI Bot phiên bản mới) -- Xác nhận máy chủ có thể thực hiện yêu cầu ra ngoài (cần POST đến `response_url`) -- Kiểm tra log với từ khóa `response_url mode` và `Sending reply via response_url` - -## Tài liệu tham khảo - -- [Tài liệu tích hợp WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719) -- [Mô tả giao thức phản hồi streaming](https://developer.work.weixin.qq.com/document/path/100719) -- [Phản hồi chủ động qua response_url](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_aibot/README.zh.md b/docs/channels/wecom/wecom_aibot/README.zh.md deleted file mode 100644 index 9da5ee1b9..000000000 --- a/docs/channels/wecom/wecom_aibot/README.zh.md +++ /dev/null @@ -1,185 +0,0 @@ -> 返回 [README](../../../../README.zh.md) - -# 企业微信智能机器人 (AI Bot) - -企业微信智能机器人(AI Bot)是企业微信官方提供的 AI 对话接入方式,支持私聊与群聊,内置流式响应协议。PicoClaw 当前同时支持两种接入模式: - -- WebSocket 长连接模式:使用 `bot_id` + `secret`,优先级更高,推荐使用 -- Webhook 短连接模式:使用 `token` + `encoding_aes_key`,兼容传统回调,并支持超时后通过 `response_url` 主动推送最终回复 - -## 与其他 WeCom 通道的对比 - -| 特性 | WeCom Bot | WeCom App | **WeCom AI Bot** | -|------|-----------|-----------|-----------------| -| 私聊 | ✅ | ✅ | ✅ | -| 群聊 | ✅ | ❌ | ✅ | -| 流式输出 | ❌ | ❌ | ✅ | -| 超时主动推送 | ❌ | ✅ | ✅ | -| 配置复杂度 | 低 | 高 | 中 | - -## 配置 - -### WebSocket 长连接模式(推荐) - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "bot_id": "YOUR_BOT_ID", - "secret": "YOUR_SECRET", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "max_steps": 10 - } - } -} -``` - -### Webhook 短连接模式 - -```json -{ - "channels": { - "wecom_aibot": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-aibot", - "allow_from": [], - "welcome_message": "你好!有什么可以帮助你的吗?", - "processing_message": "⏳ Processing, please wait. The results will be sent shortly.", - "max_steps": 10 - } - } -} -``` - -### WebSocket 模式字段 - -| 字段 | 类型 | 必填 | 描述 | -|--------|--------|------|--------------------------------------------| -| bot_id | string | 是 | AI Bot 的唯一标识,在 AI Bot 管理页面配置 | -| secret | string | 是 | AI Bot 的密钥,在 AI Bot 管理页面配置 | - -### Webhook 模式字段 - -| 字段 | 类型 | 必填 | 描述 | -|------------------|--------|------|----------------------------------------------| -| token | string | 是 | 回调验证令牌,在 AI Bot 管理页面配置 | -| encoding_aes_key | string | 是 | 43 字符 AES 密钥,在 AI Bot 管理页面随机生成 | -| webhook_path | string | 否 | Webhook 路径,默认 `/webhook/wecom-aibot` | -| processing_message | string | 否 | 流式超时后返回给用户的提示语 | - -### 通用字段 - -| 字段 | 类型 | 必填 | 描述 | -|-----------------|--------|------|------------------------------------------| -| allow_from | array | 否 | 用户 ID 白名单,空数组表示允许所有用户 | -| welcome_message | string | 否 | 用户进入聊天时发送的欢迎语,留空则不发送 | -| reply_timeout | int | 否 | 回复超时时间(秒,默认:5) | -| max_steps | int | 否 | Agent 最大执行步骤数(默认:10) | - -## 模式选择 - -- 当 `bot_id` 和 `secret` 同时存在时,PicoClaw 会优先使用 WebSocket 长连接模式 -- 否则,当 `token` 和 `encoding_aes_key` 同时存在时,PicoClaw 会使用 Webhook 短连接模式 - -## 设置流程 - -### WebSocket 长连接模式 - -1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin) -2. 进入"应用管理" → "智能机器人",创建或选择一个 AI Bot -3. 在 AI Bot 配置页面,配置 Bot 的名称、头像等信息,获取 `Bot ID` 和 `Secret` -4. 在 PicoClaw 配置文件中添加上述配置,重启 PicoClaw - -### Webhook 短连接模式 - -1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin) -2. 进入"应用管理" → "智能机器人",创建或选择一个 AI Bot -3. 在 AI Bot 配置页面,填写"消息接收"信息: - - **URL**:`http://:18790/webhook/wecom-aibot` - - **Token**:随机生成或自定义 - - **EncodingAESKey**:点击"随机生成",得到 43 字符密钥 -4. 将 Token 和 EncodingAESKey 填入 PicoClaw 配置文件,启动服务后回到管理后台保存 - -> [!TIP] -> 服务器需要能被企业微信服务器访问。如在内网或本地开发,可使用 [ngrok](https://ngrok.com) 或 frp 做内网穿透。 - -## Webhook 模式的流式响应协议 - -Webhook 模式使用"流式拉取"协议,区别于普通 Webhook 的一次性回复: - -``` -用户发消息 - │ - ▼ -PicoClaw 立即返回 {finish: false}(Agent 开始处理) - │ - ▼ -企业微信每隔约 1 秒拉取一次 {msgtype: "stream", stream: {id: "..."}} - │ - ├─ Agent 未完成 → 返回 {finish: false}(继续等待) - │ - └─ Agent 完成 → 返回 {finish: true, content: "回答内容"} -``` - -**超时处理**(任务超过约 30 秒): - -若 Agent 处理时间超过轮询窗口,PicoClaw 会: - -1. 立即关闭流,向用户显示 `processing_message` 提示语 -2. Agent 继续在后台运行 -3. Agent 完成后,通过消息中携带的 `response_url` 将最终回复主动推送给用户 - -> `response_url` 由企业微信颁发,有效期 1 小时,只可使用一次,无需加密,直接 POST markdown 消息体即可。 - -## 超时提示语 - -配置 `processing_message` 后,当 Webhook 模式的流式轮询超时并切换到 `response_url` 主动推送模式时,PicoClaw 会先返回这段提示语来结束当前流。 - -```json -"processing_message": "⏳ Processing, please wait. The results will be sent shortly." -``` - -## 欢迎语 - -配置 `welcome_message` 后,当用户打开与 AI Bot 的聊天窗口时(`enter_chat` 事件),PicoClaw 会自动回复该欢迎语。留空则静默忽略。 - -```json -"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?" -``` - -## 常见问题 - -### WebSocket 模式无法连接 - -- 检查 `bot_id` 和 `secret` 是否填写正确 -- 查看日志中是否有 WebSocket 连接或鉴权失败信息 -- 确认服务器可以访问企业微信长连接接口 - -### 回调 URL 验证失败 - - -- 确认 `token` 与 `encoding_aes_key` 填写正确 -- 确认服务器防火墙已开放对应端口 -- 检查 PicoClaw 日志是否收到了来自企业微信的验证请求 - -### 消息没有回复 - -- 检查 `allow_from` 是否意外限制了发送者 -- 查看日志中是否出现 `context canceled` 或 Agent 错误 -- 确认 Agent 配置(`model_name` 等)正确 - -### 超长任务没有收到最终推送 - -- 确认消息回调中携带了 `response_url` -- 确认服务器能主动访问外网 -- 查看日志关键词 `response_url mode` 和 `Sending reply via response_url` - -## 参考文档 - -- [企业微信 AI Bot 接入文档](https://developer.work.weixin.qq.com/document/path/101463) -- [流式响应协议说明](https://developer.work.weixin.qq.com/document/path/100719) -- [response_url 主动回复](https://developer.work.weixin.qq.com/document/path/101138) diff --git a/docs/channels/wecom/wecom_app/README.fr.md b/docs/channels/wecom/wecom_app/README.fr.md deleted file mode 100644 index f95426497..000000000 --- a/docs/channels/wecom/wecom_app/README.fr.md +++ /dev/null @@ -1,47 +0,0 @@ -> Retour au [README](../../../../README.fr.md) - -# Application interne WeCom - -Une application interne WeCom est une application créée par une entreprise au sein de WeCom, principalement destinée à un usage interne. Grâce aux applications internes WeCom, les entreprises peuvent assurer une communication et une collaboration efficaces avec leurs employés, améliorant ainsi la productivité. - -## Configuration - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Champ | Type | Requis | Description | -| ---------------- | ------ | ------ | ---------------------------------------- | -| corp_id | string | Oui | ID de l'entreprise | -| corp_secret | string | Oui | Secret de l'application | -| agent_id | int | Oui | ID de l'agent de l'application | -| token | string | Oui | Jeton de vérification du callback | -| encoding_aes_key | string | Oui | Clé AES de 43 caractères | -| webhook_path | string | Non | Chemin du webhook (par défaut : /webhook/wecom-app) | -| allow_from | array | Non | Liste blanche d'ID utilisateurs | -| reply_timeout | int | Non | Délai de réponse en secondes | - -## Procédure de configuration - -1. Connectez-vous à la [console d'administration WeCom](https://work.weixin.qq.com/) -2. Accédez à « Gestion des applications » -> « Créer une application » -3. Obtenez l'ID d'entreprise (CorpID) et le Secret de l'application -4. Configurez « Réception des messages » dans les paramètres de l'application pour obtenir le Token et l'EncodingAESKey -5. Définissez l'URL de callback sur `http://:/webhook/wecom-app` -6. Saisissez le CorpID, le Secret, l'AgentID et les autres informations dans le fichier de configuration - - Remarque : PicoClaw utilise désormais un serveur HTTP Gateway partagé pour recevoir les callbacks webhook de tous les canaux. L'adresse d'écoute par défaut est 127.0.0.1:18790. Pour recevoir des callbacks depuis l'internet public, configurez un reverse proxy de votre domaine externe vers le Gateway (port par défaut 18790). diff --git a/docs/channels/wecom/wecom_app/README.ja.md b/docs/channels/wecom/wecom_app/README.ja.md deleted file mode 100644 index 4bd5a7101..000000000 --- a/docs/channels/wecom/wecom_app/README.ja.md +++ /dev/null @@ -1,47 +0,0 @@ -> [README](../../../../README.ja.md) に戻る - -# 企業WeChat 自社開発アプリ - -企業WeChat 自社開発アプリとは、企業が企業WeChat内で作成するアプリケーションで、主に社内利用を目的としています。企業WeChat 自社開発アプリを通じて、企業は従業員との効率的なコミュニケーションと協業を実現し、業務効率を向上させることができます。 - -## 設定 - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| フィールド | 型 | 必須 | 説明 | -| ---------------- | ------ | ---- | ---------------------------------------- | -| corp_id | string | はい | 企業ID | -| corp_secret | string | はい | アプリケーションシークレット | -| agent_id | int | はい | アプリケーションエージェントID | -| token | string | はい | コールバック検証トークン | -| encoding_aes_key | string | はい | 43文字のAESキー | -| webhook_path | string | いいえ | Webhookパス(デフォルト:/webhook/wecom-app) | -| allow_from | array | いいえ | ユーザーIDの許可リスト | -| reply_timeout | int | いいえ | 返信タイムアウト(秒) | - -## セットアップ手順 - -1. [企業WeChat管理コンソール](https://work.weixin.qq.com/) にログイン -2. 「アプリ管理」→「アプリを作成」に進む -3. 企業ID(CorpID)とアプリのSecretを取得 -4. アプリ設定で「メッセージ受信」を設定し、TokenとEncodingAESKeyを取得 -5. コールバックURLを `http://:/webhook/wecom-app` に設定 -6. CorpID、Secret、AgentIDなどの情報を設定ファイルに入力 - - 注意:PicoClawは現在、すべてのチャンネルのwebhookコールバックを受信するために共有のGateway HTTPサーバーを使用しています。デフォルトのリスニングアドレスは127.0.0.1:18790です。公共インターネットからコールバックを受信するには、外部ドメインをGateway(デフォルトポート18790)にリバースプロキシしてください。 diff --git a/docs/channels/wecom/wecom_app/README.md b/docs/channels/wecom/wecom_app/README.md deleted file mode 100644 index 4397f805a..000000000 --- a/docs/channels/wecom/wecom_app/README.md +++ /dev/null @@ -1,47 +0,0 @@ -> Back to [README](../../../../README.md) - -# WeCom Internal App - -A WeCom Internal App is an application created by an enterprise within WeCom, primarily intended for internal use. Through WeCom Internal Apps, enterprises can achieve efficient communication and collaboration with employees, improving productivity. - -## Configuration - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Field | Type | Required | Description | -| ---------------- | ------ | -------- | ---------------------------------------- | -| corp_id | string | Yes | Enterprise ID | -| corp_secret | string | Yes | Application secret | -| agent_id | int | Yes | Application agent ID | -| token | string | Yes | Callback verification token | -| encoding_aes_key | string | Yes | 43-character AES key | -| webhook_path | string | No | Webhook path (default: /webhook/wecom-app) | -| allow_from | array | No | User ID allowlist | -| reply_timeout | int | No | Reply timeout in seconds | - -## Setup - -1. Log in to the [WeCom Admin Console](https://work.weixin.qq.com/) -2. Go to "App Management" -> "Create App" -3. Obtain the Enterprise ID (CorpID) and App Secret -4. Configure "Receive Messages" in the app settings to get the Token and EncodingAESKey -5. Set the callback URL to `http://:/webhook/wecom-app` -6. Enter the CorpID, Secret, AgentID, and other details into the config file - - Note: PicoClaw now uses a shared Gateway HTTP server to receive webhook callbacks for all channels. The default listening address is 127.0.0.1:18790. To receive callbacks from the public internet, reverse-proxy your external domain to the Gateway (default port 18790). diff --git a/docs/channels/wecom/wecom_app/README.pt-br.md b/docs/channels/wecom/wecom_app/README.pt-br.md deleted file mode 100644 index bd0538ed0..000000000 --- a/docs/channels/wecom/wecom_app/README.pt-br.md +++ /dev/null @@ -1,47 +0,0 @@ -> Voltar ao [README](../../../../README.pt-br.md) - -# App Interno WeCom - -Um App Interno WeCom é um aplicativo criado por uma empresa dentro do WeCom, destinado principalmente ao uso interno. Por meio dos Apps Internos WeCom, as empresas podem alcançar comunicação e colaboração eficientes com os funcionários, melhorando a produtividade. - -## Configuração - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Campo | Tipo | Obrigatório | Descrição | -| ---------------- | ------ | ----------- | ---------------------------------------- | -| corp_id | string | Sim | ID da empresa | -| corp_secret | string | Sim | Segredo da aplicação | -| agent_id | int | Sim | ID do agente da aplicação | -| token | string | Sim | Token de verificação de callback | -| encoding_aes_key | string | Sim | Chave AES de 43 caracteres | -| webhook_path | string | Não | Caminho do webhook (padrão: /webhook/wecom-app) | -| allow_from | array | Não | Lista de permissão de IDs de usuários | -| reply_timeout | int | Não | Timeout de resposta em segundos | - -## Configuração passo a passo - -1. Faça login no [Console de Administração do WeCom](https://work.weixin.qq.com/) -2. Acesse "Gerenciamento de Apps" -> "Criar App" -3. Obtenha o ID da Empresa (CorpID) e o Secret do App -4. Configure "Receber Mensagens" nas configurações do app para obter o Token e o EncodingAESKey -5. Defina a URL de callback como `http://:/webhook/wecom-app` -6. Insira o CorpID, Secret, AgentID e outras informações no arquivo de configuração - - Nota: O PicoClaw agora usa um servidor HTTP Gateway compartilhado para receber callbacks de webhook de todos os canais. O endereço de escuta padrão é 127.0.0.1:18790. Para receber callbacks da internet pública, configure um reverse proxy do seu domínio externo para o Gateway (porta padrão 18790). diff --git a/docs/channels/wecom/wecom_app/README.vi.md b/docs/channels/wecom/wecom_app/README.vi.md deleted file mode 100644 index f713f9501..000000000 --- a/docs/channels/wecom/wecom_app/README.vi.md +++ /dev/null @@ -1,47 +0,0 @@ -> Quay lại [README](../../../../README.vi.md) - -# Ứng dụng nội bộ WeCom - -Ứng dụng nội bộ WeCom là ứng dụng được doanh nghiệp tạo ra trong WeCom, chủ yếu dùng cho mục đích nội bộ. Thông qua ứng dụng nội bộ WeCom, doanh nghiệp có thể thực hiện giao tiếp và cộng tác hiệu quả với nhân viên, nâng cao hiệu suất làm việc. - -## Cấu hình - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Trường | Kiểu | Bắt buộc | Mô tả | -| ---------------- | ------ | --------- | ---------------------------------------- | -| corp_id | string | Có | ID doanh nghiệp | -| corp_secret | string | Có | Secret của ứng dụng | -| agent_id | int | Có | ID agent của ứng dụng | -| token | string | Có | Token xác minh callback | -| encoding_aes_key | string | Có | Khóa AES 43 ký tự | -| webhook_path | string | Không | Đường dẫn webhook (mặc định: /webhook/wecom-app) | -| allow_from | array | Không | Danh sách cho phép ID người dùng | -| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây | - -## Hướng dẫn thiết lập - -1. Đăng nhập vào [Bảng điều khiển quản trị WeCom](https://work.weixin.qq.com/) -2. Vào "Quản lý ứng dụng" -> "Tạo ứng dụng" -3. Lấy ID doanh nghiệp (CorpID) và Secret của ứng dụng -4. Cấu hình "Nhận tin nhắn" trong cài đặt ứng dụng để lấy Token và EncodingAESKey -5. Đặt URL callback thành `http://:/webhook/wecom-app` -6. Nhập CorpID, Secret, AgentID và các thông tin khác vào file cấu hình - - Lưu ý: PicoClaw hiện sử dụng máy chủ HTTP Gateway dùng chung để nhận callback webhook cho tất cả các kênh. Địa chỉ lắng nghe mặc định là 127.0.0.1:18790. Để nhận callback từ internet công cộng, hãy cấu hình reverse proxy từ tên miền bên ngoài của bạn đến Gateway (cổng mặc định 18790). diff --git a/docs/channels/wecom/wecom_app/README.zh.md b/docs/channels/wecom/wecom_app/README.zh.md deleted file mode 100644 index 81268692d..000000000 --- a/docs/channels/wecom/wecom_app/README.zh.md +++ /dev/null @@ -1,47 +0,0 @@ -> 返回 [README](../../../../README.zh.md) - -# 企业微信自建应用 - -企业微信自建应用是指企业在企业微信中创建的应用,主要用于企业内部使用。通过企业微信自建应用,企业可以实现与员工的高效沟通和协作,提高工作效率。 - -## 配置 - -```json -{ - "channels": { - "wecom_app": { - "enabled": true, - "corp_id": "wwxxxxxxxxxxxxxxxx", - "corp_secret": "YOUR_CORP_SECRET", - "agent_id": 1000002, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_path": "/webhook/wecom-app", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| 字段 | 类型 | 必填 | 描述 | -| ---------------- | ------ | ---- | ---------------------------------------- | -| corp_id | string | 是 | 企业 ID | -| corp_secret | string | 是 | 应用程序密钥 | -| agent_id | int | 是 | 应用程序代理 ID | -| token | string | 是 | 回调验证令牌 | -| encoding_aes_key | string | 是 | 43 字符 AES 密钥 | -| webhook_path | string | 否 | Webhook 路径(默认:/webhook/wecom-app) | -| allow_from | array | 否 | 用户 ID 白名单 | -| reply_timeout | int | 否 | 回复超时时间(秒) | - -## 设置流程 - -1. 登录 [企业微信管理后台](https://work.weixin.qq.com/) -2. 进入“应用管理” -> “创建应用” -3. 获取企业 ID (CorpID) 和应用 Secret -4. 在应用设置中配置“接收消息”,获取 Token 和 EncodingAESKey -5. 设置回调 URL 为 `http://:/webhook/wecom-app` -6. 将 CorpID, Secret, AgentID 等信息填入配置文件 - - 注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。 diff --git a/docs/channels/wecom/wecom_bot/README.fr.md b/docs/channels/wecom/wecom_bot/README.fr.md deleted file mode 100644 index fa3caeb37..000000000 --- a/docs/channels/wecom/wecom_bot/README.fr.md +++ /dev/null @@ -1,41 +0,0 @@ -> Retour au [README](../../../../README.fr.md) - -# WeCom Bot - -Le WeCom Bot est une méthode d'intégration rapide fournie par WeCom, permettant de recevoir des messages via une URL Webhook. - -## Configuration - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Champ | Type | Requis | Description | -| ---------------- | ------ | ------ | -------------------------------------------- | -| token | string | Oui | Jeton de vérification de signature | -| encoding_aes_key | string | Oui | Clé AES de 43 caractères utilisée pour le déchiffrement | -| webhook_url | string | Oui | URL Webhook du bot de groupe WeCom utilisée pour envoyer les réponses | -| webhook_path | string | Non | Chemin de l'endpoint webhook (par défaut : /webhook/wecom) | -| allow_from | array | Non | Liste blanche d'ID utilisateurs (vide = autoriser tous les utilisateurs) | -| reply_timeout | int | Non | Délai de réponse en secondes (par défaut : 5) | - -## Procédure de configuration - -1. Ajouter un bot à un groupe WeCom -2. Obtenir l'URL Webhook -3. (Pour recevoir des messages) Configurer l'adresse API de réception des messages (URL de callback), le Token et l'EncodingAESKey sur la page de configuration du bot -4. Saisir les informations pertinentes dans le fichier de configuration - - Remarque : PicoClaw utilise désormais un serveur HTTP Gateway partagé pour recevoir les callbacks webhook de tous les canaux. L'adresse d'écoute par défaut est 127.0.0.1:18790. Pour recevoir des callbacks depuis l'internet public, configurez un reverse proxy de votre domaine externe vers le Gateway (port par défaut 18790). diff --git a/docs/channels/wecom/wecom_bot/README.ja.md b/docs/channels/wecom/wecom_bot/README.ja.md deleted file mode 100644 index c932c6b4f..000000000 --- a/docs/channels/wecom/wecom_bot/README.ja.md +++ /dev/null @@ -1,41 +0,0 @@ -> [README](../../../../README.ja.md) に戻る - -# 企業WeChat ボット - -企業WeChat ボットは、企業WeChatが提供するWebhook URLを通じてメッセージを受信できる迅速な連携方式です。 - -## 設定 - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| フィールド | 型 | 必須 | 説明 | -| ---------------- | ------ | ---- | -------------------------------------------- | -| token | string | はい | 署名検証トークン | -| encoding_aes_key | string | はい | 復号化に使用する43文字のAESキー | -| webhook_url | string | はい | 返信送信に使用する企業WeChatグループボットのWebhook URL | -| webhook_path | string | いいえ | Webhookエンドポイントパス(デフォルト:/webhook/wecom) | -| allow_from | array | いいえ | ユーザーIDの許可リスト(空 = 全ユーザーを許可) | -| reply_timeout | int | いいえ | 返信タイムアウト(秒、デフォルト:5) | - -## セットアップ手順 - -1. 企業WeChatグループにボットを追加 -2. Webhook URLを取得 -3. (メッセージを受信する場合)ボット設定ページでメッセージ受信APIアドレス(コールバックURL)、Token、EncodingAESKeyを設定 -4. 関連情報を設定ファイルに入力 - - 注意:PicoClawは現在、すべてのチャンネルのwebhookコールバックを受信するために共有のGateway HTTPサーバーを使用しています。デフォルトのリスニングアドレスは127.0.0.1:18790です。公共インターネットからコールバックを受信するには、外部ドメインをGateway(デフォルトポート18790)にリバースプロキシしてください。 diff --git a/docs/channels/wecom/wecom_bot/README.md b/docs/channels/wecom/wecom_bot/README.md deleted file mode 100644 index 2600a6a6b..000000000 --- a/docs/channels/wecom/wecom_bot/README.md +++ /dev/null @@ -1,41 +0,0 @@ -> Back to [README](../../../../README.md) - -# WeCom Bot - -WeCom Bot is a quick integration method provided by WeCom that can receive messages via a Webhook URL. - -## Configuration - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Field | Type | Required | Description | -| ---------------- | ------ | -------- | -------------------------------------------- | -| token | string | Yes | Signature verification token | -| encoding_aes_key | string | Yes | 43-character AES key used for decryption | -| webhook_url | string | Yes | WeCom group bot webhook URL used to send replies | -| webhook_path | string | No | Webhook endpoint path (default: /webhook/wecom) | -| allow_from | array | No | User ID allowlist (empty = allow all users) | -| reply_timeout | int | No | Reply timeout in seconds (default: 5) | - -## Setup - -1. Add a bot to a WeCom group -2. Obtain the Webhook URL -3. (To receive messages) Configure the message receiving API address (callback URL), Token, and EncodingAESKey on the bot configuration page -4. Enter the relevant information into the config file - - Note: PicoClaw now uses a shared Gateway HTTP server to receive webhook callbacks for all channels. The default listening address is 127.0.0.1:18790. To receive callbacks from the public internet, reverse-proxy your external domain to the Gateway (default port 18790). diff --git a/docs/channels/wecom/wecom_bot/README.pt-br.md b/docs/channels/wecom/wecom_bot/README.pt-br.md deleted file mode 100644 index 4b3af1404..000000000 --- a/docs/channels/wecom/wecom_bot/README.pt-br.md +++ /dev/null @@ -1,41 +0,0 @@ -> Voltar ao [README](../../../../README.pt-br.md) - -# WeCom Bot - -O WeCom Bot é um método de integração rápida fornecido pelo WeCom que pode receber mensagens via URL de Webhook. - -## Configuração - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Campo | Tipo | Obrigatório | Descrição | -| ---------------- | ------ | ----------- | -------------------------------------------- | -| token | string | Sim | Token de verificação de assinatura | -| encoding_aes_key | string | Sim | Chave AES de 43 caracteres usada para descriptografia | -| webhook_url | string | Sim | URL do webhook do bot de grupo WeCom usada para enviar respostas | -| webhook_path | string | Não | Caminho do endpoint webhook (padrão: /webhook/wecom) | -| allow_from | array | Não | Lista de permissão de IDs de usuários (vazio = permitir todos) | -| reply_timeout | int | Não | Timeout de resposta em segundos (padrão: 5) | - -## Configuração passo a passo - -1. Adicione um bot a um grupo WeCom -2. Obtenha a URL do Webhook -3. (Para receber mensagens) Configure o endereço da API de recebimento de mensagens (URL de callback), Token e EncodingAESKey na página de configuração do bot -4. Insira as informações relevantes no arquivo de configuração - - Nota: O PicoClaw agora usa um servidor HTTP Gateway compartilhado para receber callbacks de webhook de todos os canais. O endereço de escuta padrão é 127.0.0.1:18790. Para receber callbacks da internet pública, configure um reverse proxy do seu domínio externo para o Gateway (porta padrão 18790). diff --git a/docs/channels/wecom/wecom_bot/README.vi.md b/docs/channels/wecom/wecom_bot/README.vi.md deleted file mode 100644 index aab4b46cd..000000000 --- a/docs/channels/wecom/wecom_bot/README.vi.md +++ /dev/null @@ -1,41 +0,0 @@ -> Quay lại [README](../../../../README.vi.md) - -# WeCom Bot - -WeCom Bot là phương thức tích hợp nhanh do WeCom cung cấp, có thể nhận tin nhắn qua URL Webhook. - -## Cấu hình - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| Trường | Kiểu | Bắt buộc | Mô tả | -| ---------------- | ------ | --------- | -------------------------------------------- | -| token | string | Có | Token xác minh chữ ký | -| encoding_aes_key | string | Có | Khóa AES 43 ký tự dùng để giải mã | -| webhook_url | string | Có | URL webhook của bot nhóm WeCom dùng để gửi phản hồi | -| webhook_path | string | Không | Đường dẫn endpoint webhook (mặc định: /webhook/wecom) | -| allow_from | array | Không | Danh sách cho phép ID người dùng (rỗng = cho phép tất cả) | -| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây (mặc định: 5) | - -## Hướng dẫn thiết lập - -1. Thêm bot vào một nhóm WeCom -2. Lấy URL Webhook -3. (Để nhận tin nhắn) Cấu hình địa chỉ API nhận tin nhắn (URL callback), Token và EncodingAESKey trên trang cấu hình bot -4. Nhập thông tin liên quan vào file cấu hình - - Lưu ý: PicoClaw hiện sử dụng máy chủ HTTP Gateway dùng chung để nhận callback webhook cho tất cả các kênh. Địa chỉ lắng nghe mặc định là 127.0.0.1:18790. Để nhận callback từ internet công cộng, hãy cấu hình reverse proxy từ tên miền bên ngoài của bạn đến Gateway (cổng mặc định 18790). diff --git a/docs/channels/wecom/wecom_bot/README.zh.md b/docs/channels/wecom/wecom_bot/README.zh.md deleted file mode 100644 index 016fcf973..000000000 --- a/docs/channels/wecom/wecom_bot/README.zh.md +++ /dev/null @@ -1,41 +0,0 @@ -> 返回 [README](../../../../README.zh.md) - -# 企业微信机器人 - -企业微信机器人是企业微信提供的一种快速接入方式,可以通过 Webhook URL 接收消息。 - -## 配置 - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "token": "YOUR_TOKEN", - "encoding_aes_key": "YOUR_ENCODING_AES_KEY", - "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", - "webhook_path": "/webhook/wecom", - "allow_from": [], - "reply_timeout": 5 - } - } -} -``` - -| 字段 | 类型 | 必填 | 描述 | -| ---------------- | ------ | ---- | -------------------------------------------- | -| token | string | 是 | 签名验证代币 | -| encoding_aes_key | string | 是 | 用于解密的 43 字符 AES 密钥 | -| webhook_url | string | 是 | 用于发送回复的企业微信群聊机器人 Webhook URL | -| webhook_path | string | 否 | Webhook 端点路径(默认:/webhook/wecom) | -| allow_from | array | 否 | 用户 ID 白名单(空值 = 允许所有用户) | -| reply_timeout | int | 否 | 回复超时时间(单位:秒,默认值:5) | - -## 设置流程 - -1. 在企业微信群中添加机器人 -2. 获取 Webhook URL -3. (如需接收消息) 在机器人配置页面设置接收消息的 API 地址(回调地址)以及 Token 和 EncodingAESKey -4. 将相关信息填入配置文件 - - 注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。 From 0e13f6bdece88bea92590fa2ea7a5e6c03b18d20 Mon Sep 17 00:00:00 2001 From: Hua Audio Date: Sat, 28 Mar 2026 11:18:01 +0100 Subject: [PATCH 14/55] fix/wechat-new-protocol (#2106) * fix/wechat-new-protocol * fix cdn download logic --- pkg/channels/weixin/api.go | 94 +++++++--------- pkg/channels/weixin/auth.go | 24 +++- pkg/channels/weixin/media.go | 173 ++++++++++++++++++++++++----- pkg/channels/weixin/types.go | 13 ++- pkg/channels/weixin/weixin_test.go | 114 ++++++++++++++++++- 5 files changed, 331 insertions(+), 87 deletions(-) diff --git a/pkg/channels/weixin/api.go b/pkg/channels/weixin/api.go index 7f9b3b5c6..6dc52790e 100644 --- a/pkg/channels/weixin/api.go +++ b/pkg/channels/weixin/api.go @@ -12,6 +12,14 @@ import ( "net/http" "net/url" "path" + "strconv" +) + +const ( + weixinChannelVersion = "2.1.1" + weixinIlinkAppID = "bot" + // 2.1.1 encoded as 0x00MMNNPP => 0x00020101 => 131329 + weixinClientVersion = 131329 ) type ApiClient struct { @@ -80,13 +88,9 @@ func (c *ApiClient) post(ctx context.Context, endpoint string, body any, respons } req.Header.Set("Content-Type", "application/json") - if endpoint == "ilink/bot/get_bot_qrcode" || endpoint == "ilink/bot/get_qrcode_status" { - // QR routes have different headers sometimes, but let's stick to base ones - if endpoint == "ilink/bot/get_qrcode_status" { - // Use direct map assignment to send exact header name the Tencent API expects - req.Header["iLink-App-ClientVersion"] = []string{"1"} - } - } else { + req.Header["iLink-App-Id"] = []string{weixinIlinkAppID} + req.Header["iLink-App-ClientVersion"] = []string{strconv.Itoa(weixinClientVersion)} + if endpoint != "ilink/bot/get_bot_qrcode" && endpoint != "ilink/bot/get_qrcode_status" { req.Header["AuthorizationType"] = []string{"ilink_bot_token"} req.Header["X-WECHAT-UIN"] = []string{randomWechatUIN()} if c.Token != "" { @@ -119,7 +123,7 @@ func (c *ApiClient) post(ctx context.Context, endpoint string, body any, respons } func (c *ApiClient) GetUpdates(ctx context.Context, req GetUpdatesReq) (*GetUpdatesResp, error) { - req.BaseInfo = BaseInfo{ChannelVersion: "1.0.2"} + req.BaseInfo = BaseInfo{ChannelVersion: weixinChannelVersion} var resp GetUpdatesResp err := c.post(ctx, "ilink/bot/getupdates", req, &resp) if err != nil { @@ -129,7 +133,7 @@ func (c *ApiClient) GetUpdates(ctx context.Context, req GetUpdatesReq) (*GetUpda } func (c *ApiClient) SendMessage(ctx context.Context, req SendMessageReq) (*SendMessageResp, error) { - req.BaseInfo = BaseInfo{ChannelVersion: "1.0.2"} + req.BaseInfo = BaseInfo{ChannelVersion: weixinChannelVersion} var resp SendMessageResp if err := c.post(ctx, "ilink/bot/sendmessage", req, &resp); err != nil { return nil, err @@ -138,7 +142,7 @@ func (c *ApiClient) SendMessage(ctx context.Context, req SendMessageReq) (*SendM } func (c *ApiClient) GetUploadUrl(ctx context.Context, req GetUploadUrlReq) (*GetUploadUrlResp, error) { - req.BaseInfo = BaseInfo{ChannelVersion: "1.0.2"} + req.BaseInfo = BaseInfo{ChannelVersion: weixinChannelVersion} var resp GetUploadUrlResp err := c.post(ctx, "ilink/bot/getuploadurl", req, &resp) if err != nil { @@ -148,7 +152,7 @@ func (c *ApiClient) GetUploadUrl(ctx context.Context, req GetUploadUrlReq) (*Get } func (c *ApiClient) GetConfig(ctx context.Context, req GetConfigReq) (*GetConfigResp, error) { - req.BaseInfo = BaseInfo{ChannelVersion: "1.0.2"} + req.BaseInfo = BaseInfo{ChannelVersion: weixinChannelVersion} var resp GetConfigResp if err := c.post(ctx, "ilink/bot/getconfig", req, &resp); err != nil { return nil, err @@ -157,7 +161,7 @@ func (c *ApiClient) GetConfig(ctx context.Context, req GetConfigReq) (*GetConfig } func (c *ApiClient) SendTyping(ctx context.Context, req SendTypingReq) (*SendTypingResp, error) { - req.BaseInfo = BaseInfo{ChannelVersion: "1.0.2"} + req.BaseInfo = BaseInfo{ChannelVersion: weixinChannelVersion} var resp SendTypingResp if err := c.post(ctx, "ilink/bot/sendtyping", req, &resp); err != nil { return nil, err @@ -165,38 +169,51 @@ func (c *ApiClient) SendTyping(ctx context.Context, req SendTypingReq) (*SendTyp return &resp, nil } -func (c *ApiClient) GetQRCode(ctx context.Context, botType string) (*QRCodeResponse, error) { - // get_bot_qrcode is GET, not POST +func (c *ApiClient) getQR(ctx context.Context, endpoint string, query map[string]string, respObj any) error { u, err := url.Parse(c.BaseURL) if err != nil { - return nil, err + return err } - u.Path = path.Join(u.Path, "ilink/bot/get_bot_qrcode") + u.Path = path.Join(u.Path, endpoint) q := u.Query() - q.Set("bot_type", botType) + for key, value := range query { + q.Set(key, value) + } u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { - return nil, err + return err } + req.Header["iLink-App-Id"] = []string{weixinIlinkAppID} + req.Header["iLink-App-ClientVersion"] = []string{strconv.Itoa(weixinClientVersion)} resp, err := c.HttpClient.Do(req) if err != nil { - return nil, err + return err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + return err } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("get_bot_qrcode failed: %d %s", resp.StatusCode, string(respBody)) + return fmt.Errorf("%s failed: %d %s", endpoint, resp.StatusCode, string(respBody)) + } + if err := json.Unmarshal(respBody, respObj); err != nil { + return err } + return nil +} + +func (c *ApiClient) GetQRCode(ctx context.Context, botType string) (*QRCodeResponse, error) { + // get_bot_qrcode is GET, not POST var qrcodeResp QRCodeResponse - if err := json.Unmarshal(respBody, &qrcodeResp); err != nil { + if err := c.getQR(ctx, "ilink/bot/get_bot_qrcode", map[string]string{ + "bot_type": botType, + }, &qrcodeResp); err != nil { return nil, err } return &qrcodeResp, nil @@ -204,37 +221,10 @@ func (c *ApiClient) GetQRCode(ctx context.Context, botType string) (*QRCodeRespo func (c *ApiClient) GetQRCodeStatus(ctx context.Context, qrcode string) (*StatusResponse, error) { // get_qrcode_status is GET - u, err := url.Parse(c.BaseURL) - if err != nil { - return nil, err - } - u.Path = path.Join(u.Path, "ilink/bot/get_qrcode_status") - q := u.Query() - q.Set("qrcode", qrcode) - u.RawQuery = q.Encode() - - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) - if err != nil { - return nil, err - } - req.Header["iLink-App-ClientVersion"] = []string{"1"} - - resp, err := c.HttpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("get_qrcode_status failed: %d %s", resp.StatusCode, string(respBody)) - } - var statusResp StatusResponse - if err := json.Unmarshal(respBody, &statusResp); err != nil { + if err := c.getQR(ctx, "ilink/bot/get_qrcode_status", map[string]string{ + "qrcode": qrcode, + }, &statusResp); err != nil { return nil, err } return &statusResp, nil diff --git a/pkg/channels/weixin/auth.go b/pkg/channels/weixin/auth.go index 52ec2a6df..0a0e597c1 100644 --- a/pkg/channels/weixin/auth.go +++ b/pkg/channels/weixin/auth.go @@ -40,6 +40,7 @@ func PerformLoginInteractive( if err != nil { return "", "", "", "", fmt.Errorf("failed to create api client: %w", err) } + pollAPI := api logger.InfoC("weixin", "Requesting Weixin QR code...") qrResp, err := api.GetQRCode(ctx, opts.BotType) @@ -76,7 +77,7 @@ func PerformLoginInteractive( case <-timeoutCtx.Done(): return "", "", "", "", fmt.Errorf("login timeout") case <-pollTicker.C: - statusResp, err := api.GetQRCodeStatus(timeoutCtx, qrResp.Qrcode) + statusResp, err := pollAPI.GetQRCodeStatus(timeoutCtx, qrResp.Qrcode) if err != nil { // Long poll timeout or temporary error continue @@ -99,6 +100,27 @@ func PerformLoginInteractive( }) return statusResp.BotToken, statusResp.IlinkUserID, statusResp.IlinkBotID, statusResp.Baseurl, nil + case "scaned_but_redirect": + if statusResp.RedirectHost == "" { + logger.WarnC( + "weixin", + "scaned_but_redirect received without redirect_host; continuing on current host", + ) + continue + } + nextBaseURL := "https://" + statusResp.RedirectHost + "/" + nextAPI, nextErr := NewApiClient(nextBaseURL, "", opts.Proxy) + if nextErr != nil { + logger.WarnCF("weixin", "Failed to switch QR polling host", map[string]any{ + "redirect_host": statusResp.RedirectHost, + "error": nextErr.Error(), + }) + continue + } + pollAPI = nextAPI + logger.InfoCF("weixin", "Switched QR polling host", map[string]any{ + "redirect_host": statusResp.RedirectHost, + }) case "expired": return "", "", "", "", fmt.Errorf("qrcode expired, please try again") default: diff --git a/pkg/channels/weixin/media.go b/pkg/channels/weixin/media.go index 72af27438..4da7f0db9 100644 --- a/pkg/channels/weixin/media.go +++ b/pkg/channels/weixin/media.go @@ -34,6 +34,8 @@ const ( weixinMediaMaxBytes = 100 << 20 weixinTypingKeepAlive = 5 * time.Second weixinUploadRetryMax = 3 + weixinDownloadRetryMax = 2 + weixinDownloadRetryDelay = 300 * time.Millisecond weixinVoiceTranscodeTimeout = 15 * time.Second ) @@ -163,49 +165,108 @@ func buildCDNDownloadURL(base, encryptedQueryParam string) string { "/download?encrypted_query_param=" + url.QueryEscape(encryptedQueryParam) } +func shouldRetryCDNDownload(statusCode int) bool { + // statusCode=0 represents transport/build errors from the HTTP client. + return statusCode == 0 || statusCode >= 500 || statusCode == http.StatusTooManyRequests +} + func buildCDNUploadURL(base, uploadParam, filekey string) string { return strings.TrimRight(base, "/") + "/upload?encrypted_query_param=" + url.QueryEscape(uploadParam) + "&filekey=" + url.QueryEscape(filekey) } -func (c *WeixinChannel) downloadCDNBuffer(ctx context.Context, encryptedQueryParam string) ([]byte, error) { - req, err := http.NewRequestWithContext( - ctx, - http.MethodGet, - buildCDNDownloadURL(c.cdnBaseURL(), encryptedQueryParam), - nil, - ) +func uniqCDNURLs(urls []string) []string { + seen := make(map[string]struct{}, len(urls)) + out := make([]string, 0, len(urls)) + for _, raw := range urls { + u := strings.TrimSpace(raw) + if u == "" { + continue + } + if _, ok := seen[u]; ok { + continue + } + seen[u] = struct{}{} + out = append(out, u) + } + return out +} + +func (c *WeixinChannel) downloadCDNBufferOnce(ctx context.Context, downloadURL string) ([]byte, int, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) if err != nil { - return nil, err + return nil, 0, err } resp, err := c.api.HttpClient.Do(req) if err != nil { - return nil, err + return nil, 0, err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) - return nil, fmt.Errorf("cdn download HTTP %d: %s", resp.StatusCode, string(body)) + return nil, resp.StatusCode, fmt.Errorf("cdn download HTTP %d: %s", resp.StatusCode, string(body)) } data, err := io.ReadAll(io.LimitReader(resp.Body, weixinMediaMaxBytes+1)) if err != nil { - return nil, err + return nil, resp.StatusCode, err } if len(data) > weixinMediaMaxBytes { - return nil, fmt.Errorf("cdn media too large: %d bytes", len(data)) + return nil, resp.StatusCode, fmt.Errorf("cdn media too large: %d bytes", len(data)) } - return data, nil + return data, resp.StatusCode, nil +} + +func (c *WeixinChannel) downloadCDNBuffer( + ctx context.Context, + encryptedQueryParam, + fullURL string, +) ([]byte, error) { + candidates := uniqCDNURLs([]string{ + strings.TrimSpace(fullURL), + func() string { + if strings.TrimSpace(encryptedQueryParam) == "" { + return "" + } + return buildCDNDownloadURL(c.cdnBaseURL(), encryptedQueryParam) + }(), + }) + if len(candidates) == 0 { + return nil, fmt.Errorf("missing CDN download URL") + } + + var lastErr error + for _, downloadURL := range candidates { + for attempt := 1; attempt <= weixinDownloadRetryMax; attempt++ { + data, statusCode, err := c.downloadCDNBufferOnce(ctx, downloadURL) + if err == nil { + return data, nil + } + lastErr = fmt.Errorf("%w (attempt=%d url=%s)", err, attempt, downloadURL) + if !shouldRetryCDNDownload(statusCode) { + break + } + if attempt < weixinDownloadRetryMax { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(weixinDownloadRetryDelay): + } + } + } + } + return nil, lastErr } func (c *WeixinChannel) downloadAndDecryptCDNBuffer( ctx context.Context, encryptedQueryParam string, + fullURL string, key []byte, ) ([]byte, error) { - data, err := c.downloadCDNBuffer(ctx, encryptedQueryParam) + data, err := c.downloadCDNBuffer(ctx, encryptedQueryParam, fullURL) if err != nil { return nil, err } @@ -215,6 +276,33 @@ func (c *WeixinChannel) downloadAndDecryptCDNBuffer( return decryptAESECB(data, key) } +func (c *WeixinChannel) downloadImageBuffer( + ctx context.Context, + img *ImageItem, + key []byte, +) ([]byte, error) { + if img == nil { + return nil, fmt.Errorf("image item is nil") + } + if img.Media != nil { + data, err := c.downloadAndDecryptCDNBuffer(ctx, img.Media.EncryptQueryParam, img.Media.FullURL, key) + if err == nil { + return data, nil + } + if img.ThumbMedia == nil { + return nil, fmt.Errorf("image download failed: %w", err) + } + } + if img.ThumbMedia != nil { + data, err := c.downloadAndDecryptCDNBuffer(ctx, img.ThumbMedia.EncryptQueryParam, img.ThumbMedia.FullURL, key) + if err == nil { + return data, nil + } + return nil, fmt.Errorf("image download failed: %w", err) + } + return nil, fmt.Errorf("image media is nil") +} + func detectMediaMetadata(data []byte, fallbackName, fallbackContentType string) (string, string) { contentType := strings.TrimSpace(fallbackContentType) ext := filepath.Ext(fallbackName) @@ -310,15 +398,18 @@ func isDownloadableMediaItem(item *MessageItem) bool { switch item.Type { case MessageItemTypeImage: - return item.ImageItem != nil && item.ImageItem.Media != nil && item.ImageItem.Media.EncryptQueryParam != "" + return item.ImageItem != nil && item.ImageItem.Media != nil && + (item.ImageItem.Media.EncryptQueryParam != "" || item.ImageItem.Media.FullURL != "") case MessageItemTypeVideo: - return item.VideoItem != nil && item.VideoItem.Media != nil && item.VideoItem.Media.EncryptQueryParam != "" + return item.VideoItem != nil && item.VideoItem.Media != nil && + (item.VideoItem.Media.EncryptQueryParam != "" || item.VideoItem.Media.FullURL != "") case MessageItemTypeFile: - return item.FileItem != nil && item.FileItem.Media != nil && item.FileItem.Media.EncryptQueryParam != "" + return item.FileItem != nil && item.FileItem.Media != nil && + (item.FileItem.Media.EncryptQueryParam != "" || item.FileItem.Media.FullURL != "") case MessageItemTypeVoice: return item.VoiceItem != nil && item.VoiceItem.Media != nil && - item.VoiceItem.Media.EncryptQueryParam != "" && + (item.VoiceItem.Media.EncryptQueryParam != "" || item.VoiceItem.Media.FullURL != "") && strings.TrimSpace(item.VoiceItem.Text) == "" default: return false @@ -434,16 +525,20 @@ func (c *WeixinChannel) downloadMediaFromItem( switch item.Type { case MessageItemTypeImage: + if item.ImageItem == nil { + return "", fmt.Errorf("image media is nil") + } key, ok, err := imageAESKey(item.ImageItem) if err != nil { return "", err } - data, err := c.downloadAndDecryptCDNBuffer(ctx, item.ImageItem.Media.EncryptQueryParam, func() []byte { + decryptKey := func() []byte { if ok { return key } return nil - }()) + }() + data, err := c.downloadImageBuffer(ctx, item.ImageItem, decryptKey) if err != nil { return "", err } @@ -454,7 +549,12 @@ func (c *WeixinChannel) downloadMediaFromItem( if err != nil { return "", err } - silk, err := c.downloadAndDecryptCDNBuffer(ctx, item.VoiceItem.Media.EncryptQueryParam, key) + silk, err := c.downloadAndDecryptCDNBuffer( + ctx, + item.VoiceItem.Media.EncryptQueryParam, + item.VoiceItem.Media.FullURL, + key, + ) if err != nil { return "", err } @@ -468,7 +568,12 @@ func (c *WeixinChannel) downloadMediaFromItem( if err != nil { return "", err } - data, err := c.downloadAndDecryptCDNBuffer(ctx, item.FileItem.Media.EncryptQueryParam, key) + data, err := c.downloadAndDecryptCDNBuffer( + ctx, + item.FileItem.Media.EncryptQueryParam, + item.FileItem.Media.FullURL, + key, + ) if err != nil { return "", err } @@ -484,7 +589,12 @@ func (c *WeixinChannel) downloadMediaFromItem( if err != nil { return "", err } - data, err := c.downloadAndDecryptCDNBuffer(ctx, item.VideoItem.Media.EncryptQueryParam, key) + data, err := c.downloadAndDecryptCDNBuffer( + ctx, + item.VideoItem.Media.EncryptQueryParam, + item.VideoItem.Media.FullURL, + key, + ) if err != nil { return "", err } @@ -701,11 +811,13 @@ func (c *WeixinChannel) uploadLocalFile( } return nil, fmt.Errorf("getuploadurl failed: ret=%d errcode=%d errmsg=%s", resp.Ret, resp.Errcode, resp.Errmsg) } - if strings.TrimSpace(resp.UploadParam) == "" { - return nil, fmt.Errorf("getuploadurl returned empty upload_param") + uploadParam := strings.TrimSpace(resp.UploadParam) + uploadFullURL := strings.TrimSpace(resp.UploadFullURL) + if uploadParam == "" && uploadFullURL == "" { + return nil, fmt.Errorf("getuploadurl returned no upload URL") } - downloadParam, err := c.uploadBufferToCDN(ctx, data, resp.UploadParam, filekey, aesKey) + downloadParam, err := c.uploadBufferToCDN(ctx, data, uploadParam, uploadFullURL, filekey, aesKey) if err != nil { return nil, err } @@ -723,6 +835,7 @@ func (c *WeixinChannel) uploadBufferToCDN( ctx context.Context, plaintext []byte, uploadParam, + uploadFullURL, filekey string, aesKey []byte, ) (string, error) { @@ -731,7 +844,13 @@ func (c *WeixinChannel) uploadBufferToCDN( return "", err } - uploadURL := buildCDNUploadURL(c.cdnBaseURL(), uploadParam, filekey) + uploadURL := strings.TrimSpace(uploadFullURL) + if uploadURL == "" { + if strings.TrimSpace(uploadParam) == "" { + return "", fmt.Errorf("missing CDN upload URL") + } + uploadURL = buildCDNUploadURL(c.cdnBaseURL(), uploadParam, filekey) + } var lastErr error for attempt := 1; attempt <= weixinUploadRetryMax; attempt++ { diff --git a/pkg/channels/weixin/types.go b/pkg/channels/weixin/types.go index 74c6e63c3..f2c03894f 100644 --- a/pkg/channels/weixin/types.go +++ b/pkg/channels/weixin/types.go @@ -38,6 +38,7 @@ type GetUploadUrlResp struct { APIStatus UploadParam string `json:"upload_param,omitempty"` ThumbUploadParam string `json:"thumb_upload_param,omitempty"` + UploadFullURL string `json:"upload_full_url,omitempty"` } const ( @@ -69,6 +70,7 @@ type CDNMedia struct { EncryptQueryParam string `json:"encrypt_query_param,omitempty"` AesKey string `json:"aes_key,omitempty"` // base64 encoded EncryptType int `json:"encrypt_type,omitempty"` + FullURL string `json:"full_url,omitempty"` } type ImageItem struct { @@ -202,9 +204,10 @@ type QRCodeResponse struct { } type StatusResponse struct { - Status string `json:"status"` // "wait", "scaned", "confirmed", "expired" - BotToken string `json:"bot_token,omitempty"` - IlinkBotID string `json:"ilink_bot_id,omitempty"` - Baseurl string `json:"baseurl,omitempty"` - IlinkUserID string `json:"ilink_user_id,omitempty"` + Status string `json:"status"` // "wait", "scaned", "confirmed", "expired", "scaned_but_redirect" + BotToken string `json:"bot_token,omitempty"` + IlinkBotID string `json:"ilink_bot_id,omitempty"` + Baseurl string `json:"baseurl,omitempty"` + IlinkUserID string `json:"ilink_user_id,omitempty"` + RedirectHost string `json:"redirect_host,omitempty"` } diff --git a/pkg/channels/weixin/weixin_test.go b/pkg/channels/weixin/weixin_test.go index 62984c965..b41b930db 100644 --- a/pkg/channels/weixin/weixin_test.go +++ b/pkg/channels/weixin/weixin_test.go @@ -72,7 +72,7 @@ func TestDownloadAndDecryptCDNBuffer(t *testing.T) { typingCache: make(map[string]typingTicketCacheEntry), } - got, err := ch.downloadAndDecryptCDNBuffer(context.Background(), "token", key) + got, err := ch.downloadAndDecryptCDNBuffer(context.Background(), "token", "", key) if err != nil { t.Fatalf("downloadAndDecryptCDNBuffer() error = %v", err) } @@ -81,6 +81,116 @@ func TestDownloadAndDecryptCDNBuffer(t *testing.T) { } } +func TestDownloadAndDecryptCDNBufferUsesFullURLWhenProvided(t *testing.T) { + key := []byte("1234567890abcdef") + plaintext := []byte("hello weixin") + ciphertext, err := encryptAESECB(plaintext, key) + if err != nil { + t.Fatalf("encryptAESECB() error = %v", err) + } + + fullURLAttempts := 0 + ch := &WeixinChannel{ + api: &ApiClient{ + HttpClient: &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + if r.URL.String() == "https://full.example.com/download" { + fullURLAttempts++ + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(ciphertext)), + Header: make(http.Header), + }, nil + } + t.Fatalf("unexpected fallback request: %s", r.URL.String()) + return nil, nil + })}, + }, + config: config.WeixinConfig{ + CDNBaseURL: "https://cdn.example.com", + }, + typingCache: make(map[string]typingTicketCacheEntry), + } + + got, err := ch.downloadAndDecryptCDNBuffer(context.Background(), "token", "https://full.example.com/download", key) + if err != nil { + t.Fatalf("downloadAndDecryptCDNBuffer() error = %v", err) + } + if !bytes.Equal(got, plaintext) { + t.Fatalf("downloadAndDecryptCDNBuffer() = %q, want %q", got, plaintext) + } + if fullURLAttempts == 0 { + t.Fatalf("fullURLAttempts = %d, want > 0", fullURLAttempts) + } +} + +func TestDownloadAndDecryptCDNBufferFallsBackToConstructedURLWhenFullURLFails(t *testing.T) { + key := []byte("1234567890abcdef") + plaintext := []byte("hello weixin") + ciphertext, err := encryptAESECB(plaintext, key) + if err != nil { + t.Fatalf("encryptAESECB() error = %v", err) + } + + fullURLAttempts := 0 + constructedAttempts := 0 + ch := &WeixinChannel{ + api: &ApiClient{ + HttpClient: &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + if r.URL.String() == "https://full.example.com/download?encrypted_query_param=token&taskid=123" { + fullURLAttempts++ + return &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(bytes.NewReader(nil)), + Header: make(http.Header), + }, nil + } + if r.URL.String() != "https://cdn.example.com/download?encrypted_query_param=token" { + t.Fatalf("unexpected fallback request: %s", r.URL.String()) + } + constructedAttempts++ + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(ciphertext)), + Header: make(http.Header), + }, nil + })}, + }, + config: config.WeixinConfig{ + CDNBaseURL: "https://cdn.example.com", + }, + typingCache: make(map[string]typingTicketCacheEntry), + } + + got, err := ch.downloadAndDecryptCDNBuffer( + context.Background(), + "token", + "https://full.example.com/download?encrypted_query_param=token&taskid=123", + key, + ) + if err != nil { + t.Fatalf("downloadAndDecryptCDNBuffer() error = %v", err) + } + if !bytes.Equal(got, plaintext) { + t.Fatalf("downloadAndDecryptCDNBuffer() = %q, want %q", got, plaintext) + } + if fullURLAttempts == 0 { + t.Fatalf("fullURLAttempts = %d, want > 0", fullURLAttempts) + } + if constructedAttempts == 0 { + t.Fatalf("constructedAttempts = %d, want > 0", constructedAttempts) + } +} + +func TestBuildCDNDownloadURLEscapesOpaqueToken(t *testing.T) { + token := "MFcCAQAESzBJAgEAAgSieMV9AgM9CcwCBEoKPqICBGnHZB0EJDk4OWY5YWU0LTc4OGItNGQ5Ni1iMjZhLWU4YjhlMmEwOWVkZgIEIR0IAgIBAAQFAExUPQA%3D" + + got := buildCDNDownloadURL("https://cdn.example.com", token) + + if got != "https://cdn.example.com/download?encrypted_query_param=MFcCAQAESzBJAgEAAgSieMV9AgM9CcwCBEoKPqICBGnHZB0EJDk4OWY5YWU0LTc4OGItNGQ5Ni1iMjZhLWU4YjhlMmEwOWVkZgIEIR0IAgIBAAQFAExUPQA%253D" { + t.Fatalf("buildCDNDownloadURL() = %q", got) + } +} + func TestUploadBufferToCDN(t *testing.T) { key := []byte("1234567890abcdef") plaintext := []byte("upload me") @@ -120,7 +230,7 @@ func TestUploadBufferToCDN(t *testing.T) { typingCache: make(map[string]typingTicketCacheEntry), } - got, err := ch.uploadBufferToCDN(context.Background(), plaintext, "upload-param", "file-key", key) + got, err := ch.uploadBufferToCDN(context.Background(), plaintext, "upload-param", "", "file-key", key) if err != nil { t.Fatalf("uploadBufferToCDN() error = %v", err) } From b6951b6925b313e3ccb3daa63d7d39ded276f9ad Mon Sep 17 00:00:00 2001 From: Alix-007 Date: Sat, 28 Mar 2026 18:26:10 +0800 Subject: [PATCH 15/55] fix(dingtalk): honor mention-only groups and strip leading mentions (#2054) * fix(dingtalk): honor @mention flag in mention-only groups * fix(dingtalk): strip leading mentions in group payloads --------- Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com> --- pkg/channels/dingtalk/dingtalk.go | 70 ++++++++++--- pkg/channels/dingtalk/dingtalk_test.go | 131 +++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 pkg/channels/dingtalk/dingtalk_test.go diff --git a/pkg/channels/dingtalk/dingtalk.go b/pkg/channels/dingtalk/dingtalk.go index 273e2b020..450dcce54 100644 --- a/pkg/channels/dingtalk/dingtalk.go +++ b/pkg/channels/dingtalk/dingtalk.go @@ -6,6 +6,7 @@ package dingtalk import ( "context" "fmt" + "strings" "sync" "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" @@ -135,13 +136,17 @@ func (c *DingTalkChannel) onChatBotMessageReceived( ctx context.Context, data *chatbot.BotCallbackDataModel, ) ([]byte, error) { + if data == nil { + return nil, nil + } + // Extract message content from Text field - content := data.Text.Content + content := strings.TrimSpace(data.Text.Content) if content == "" { // Try to extract from Content interface{} if Text is empty if contentMap, ok := data.Content.(map[string]any); ok { if textContent, ok := contentMap["content"].(string); ok { - content = textContent + content = strings.TrimSpace(textContent) } } } @@ -150,12 +155,19 @@ func (c *DingTalkChannel) onChatBotMessageReceived( return nil, nil // Ignore empty messages } - senderID := data.SenderStaffId - senderNick := data.SenderNick - chatID := senderID - if data.ConversationType != "1" { - // For group chats - chatID = data.ConversationId + senderID := strings.TrimSpace(data.SenderStaffId) + if senderID == "" { + senderID = strings.TrimSpace(data.SenderId) + } + senderNick := strings.TrimSpace(data.SenderNick) + + chatID := strings.TrimSpace(data.ConversationId) + if chatID == "" && data.ConversationType == "1" { + // Fallback for direct chats when conversation_id is absent. + chatID = senderID + } + if chatID == "" { + return nil, nil } // Store the session webhook for this chat so we can reply later @@ -171,11 +183,19 @@ func (c *DingTalkChannel) onChatBotMessageReceived( var peer bus.Peer if data.ConversationType == "1" { - peer = bus.Peer{Kind: "direct", ID: senderID} + peerID := senderID + if peerID == "" { + peerID = chatID + } + peer = bus.Peer{Kind: "direct", ID: peerID} } else { peer = bus.Peer{Kind: "group", ID: data.ConversationId} + isMentioned := data.IsInAtList + if isMentioned { + content = stripLeadingAtMentions(content) + } // In group chats, apply unified group trigger filtering - respond, cleaned := c.ShouldRespondInGroup(false, content) + respond, cleaned := c.ShouldRespondInGroup(isMentioned, content) if !respond { return nil, nil } @@ -189,10 +209,18 @@ func (c *DingTalkChannel) onChatBotMessageReceived( }) // Build sender info + platformID := senderID + if platformID == "" { + platformID = chatID + } + resolvedSenderID := senderID + if resolvedSenderID == "" { + resolvedSenderID = platformID + } sender := bus.SenderInfo{ Platform: "dingtalk", - PlatformID: senderID, - CanonicalID: identity.BuildCanonicalID("dingtalk", senderID), + PlatformID: platformID, + CanonicalID: identity.BuildCanonicalID("dingtalk", platformID), DisplayName: senderNick, } @@ -201,7 +229,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived( } // Handle the message through the base channel - c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata, sender) + c.HandleMessage(ctx, peer, "", resolvedSenderID, chatID, content, nil, metadata, sender) // Return nil to indicate we've handled the message asynchronously // The response will be sent through the message bus @@ -229,3 +257,19 @@ func (c *DingTalkChannel) SendDirectReply(ctx context.Context, sessionWebhook, c return nil } + +func stripLeadingAtMentions(content string) string { + fields := strings.Fields(content) + if len(fields) == 0 { + return "" + } + + i := 0 + for i < len(fields) && strings.HasPrefix(fields[i], "@") { + i++ + } + if i == 0 { + return strings.TrimSpace(content) + } + return strings.Join(fields[i:], " ") +} diff --git a/pkg/channels/dingtalk/dingtalk_test.go b/pkg/channels/dingtalk/dingtalk_test.go new file mode 100644 index 000000000..3b517aef4 --- /dev/null +++ b/pkg/channels/dingtalk/dingtalk_test.go @@ -0,0 +1,131 @@ +package dingtalk + +import ( + "context" + "testing" + "time" + + "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" + + "github.com/sipeed/picoclaw/pkg/bus" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newTestDingTalkChannel(t *testing.T, cfg config.DingTalkConfig) (*DingTalkChannel, *bus.MessageBus) { + t.Helper() + + if cfg.ClientID == "" { + cfg.ClientID = "test-client-id" + } + if cfg.ClientSecret() == "" { + cfg.SetClientSecret("test-client-secret") + } + + msgBus := bus.NewMessageBus() + ch, err := NewDingTalkChannel(cfg, msgBus) + if err != nil { + t.Fatalf("new channel: %v", err) + } + return ch, msgBus +} + +func mustReceiveInbound(t *testing.T, msgBus *bus.MessageBus) bus.InboundMessage { + t.Helper() + select { + case msg := <-msgBus.InboundChan(): + return msg + case <-time.After(time.Second): + t.Fatal("expected inbound message") + return bus.InboundMessage{} + } +} + +func TestOnChatBotMessageReceived_GroupMentionOnlyUsesIsInAtListAndStripsMention(t *testing.T) { + ch, msgBus := newTestDingTalkChannel(t, config.DingTalkConfig{ + GroupTrigger: config.GroupTriggerConfig{MentionOnly: true}, + }) + + _, err := ch.onChatBotMessageReceived(context.Background(), &chatbot.BotCallbackDataModel{ + Text: chatbot.BotCallbackDataTextModel{Content: " @bot /help "}, + SenderStaffId: "staff-123", + SenderNick: "Alice", + ConversationType: "2", + ConversationId: "group-abc", + SessionWebhook: "https://example.com/webhook", + IsInAtList: true, + }) + if err != nil { + t.Fatalf("handler returned error: %v", err) + } + + inbound := mustReceiveInbound(t, msgBus) + if inbound.Channel != "dingtalk" { + t.Fatalf("channel=%q", inbound.Channel) + } + if inbound.ChatID != "group-abc" { + t.Fatalf("chat_id=%q", inbound.ChatID) + } + if inbound.Peer.Kind != "group" || inbound.Peer.ID != "group-abc" { + t.Fatalf("peer=%+v", inbound.Peer) + } + if inbound.Content != "/help" { + t.Fatalf("content=%q", inbound.Content) + } +} + +func TestOnChatBotMessageReceived_DirectFallbackSenderIDUsesConversationID(t *testing.T) { + ch, msgBus := newTestDingTalkChannel(t, config.DingTalkConfig{}) + + _, err := ch.onChatBotMessageReceived(context.Background(), &chatbot.BotCallbackDataModel{ + Text: chatbot.BotCallbackDataTextModel{Content: "ping"}, + SenderStaffId: "", + SenderId: "openid-user-42", + SenderNick: "Bob", + ConversationType: "1", + ConversationId: "conv-direct-42", + SessionWebhook: "https://example.com/webhook-direct", + }) + if err != nil { + t.Fatalf("handler returned error: %v", err) + } + + inbound := mustReceiveInbound(t, msgBus) + if inbound.ChatID != "conv-direct-42" { + t.Fatalf("chat_id=%q", inbound.ChatID) + } + if inbound.Peer.Kind != "direct" || inbound.Peer.ID != "openid-user-42" { + t.Fatalf("peer=%+v", inbound.Peer) + } + if inbound.SenderID != "dingtalk:openid-user-42" { + t.Fatalf("sender_id=%q", inbound.SenderID) + } + + if _, ok := ch.sessionWebhooks.Load("conv-direct-42"); !ok { + t.Fatal("expected session webhook keyed by conversation_id") + } + if _, ok := ch.sessionWebhooks.Load(""); ok { + t.Fatal("unexpected empty chat_id webhook key") + } +} + +func TestStripLeadingAtMentions(t *testing.T) { + tests := []struct { + name string + input string + wantOut string + }{ + {name: "single mention and command", input: "@bot /help", wantOut: "/help"}, + {name: "multiple mentions", input: "@bot @alice /new", wantOut: "/new"}, + {name: "no mention", input: "/help", wantOut: "/help"}, + {name: "mention only", input: "@bot", wantOut: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := stripLeadingAtMentions(tt.input) + if got != tt.wantOut { + t.Fatalf("stripLeadingAtMentions(%q)=%q want=%q", tt.input, got, tt.wantOut) + } + }) + } +} From 836cbc3066d6af5ac9e25e2a8ccd6e89f06b670e Mon Sep 17 00:00:00 2001 From: BeaconCat Date: Sat, 28 Mar 2026 18:46:21 +0800 Subject: [PATCH 16/55] docs: add Malay README and docs, add v0.2.4 news to all languages - Add README.my.md (full Malay translation from English, including macOS guide, MiMo provider, unified WeCom row, all sections) - Add docs/my/ (chat-apps, configuration, debug, docker, spawn-tasks, troubleshooting) from upstream PR #1770 - Add [Malay](README.my.md) language link to all 8 existing READMEs - Add v0.2.4 news entry to all 9 READMEs (en/zh/fr/ja/pt-br/vi/id/it/my) - Move 2026-02-26 20K Stars entry into Earlier news in all READMEs Co-Authored-By: Claude Opus 4.6 --- README.fr.md | 10 +- README.id.md | 10 +- README.it.md | 10 +- README.ja.md | 12 +- README.md | 8 +- README.my.md | 599 +++++++++++++++++++++++++++++++++++++ README.pt-br.md | 8 +- README.vi.md | 8 +- README.zh.md | 8 +- docs/my/chat-apps.md | 431 ++++++++++++++++++++++++++ docs/my/configuration.md | 216 +++++++++++++ docs/my/debug.md | 33 ++ docs/my/docker.md | 166 ++++++++++ docs/my/spawn-tasks.md | 61 ++++ docs/my/troubleshooting.md | 43 +++ 15 files changed, 1594 insertions(+), 29 deletions(-) create mode 100644 README.my.md create mode 100644 docs/my/chat-apps.md create mode 100644 docs/my/configuration.md create mode 100644 docs/my/debug.md create mode 100644 docs/my/docker.md create mode 100644 docs/my/spawn-tasks.md create mode 100644 docs/my/troubleshooting.md diff --git a/README.fr.md b/README.fr.md index f9812bd64..4fd326102 100644 --- a/README.fr.md +++ b/README.fr.md @@ -18,7 +18,7 @@
    Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | **Français** | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md) +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | **Français** | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -57,17 +57,19 @@ ## 📢 Actualités +2026-03-25 🚀 **v0.2.4 publiée !** Refonte de l'architecture Agent (SubTurn, Hooks, Steering, EventBus), intégration WeChat/WeCom, renforcement de la sécurité (.security.yml, filtrage des données sensibles), nouveaux providers (AWS Bedrock, Azure, Xiaomi MiMo), et 35 corrections de bugs. PicoClaw a atteint **26K Stars** ! + 2026-03-17 🚀 **v0.2.3 publiée !** Interface system tray (Windows & Linux), requête de statut des sous-agents (`spawn_status`), rechargement à chaud expérimental du Gateway, sécurisation Cron, et 2 correctifs de sécurité. PicoClaw a atteint **25K Stars** ! -2026-03-09 🎉 **v0.2.1 — La plus grande mise à jour à ce jour !** Support du protocole MCP, 4 nouveaux channels (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux providers (Kimi/Minimax/Avian), pipeline vision, stockage mémoire JSONL, routage de modèles. +2026-03-09 🎉 **v0.2.1 — Plus grande mise à jour à ce jour !** Support du protocole MCP, 4 nouveaux channels (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux providers (Kimi/Minimax/Avian), pipeline vision, stockage mémoire JSONL, routage de modèles. 2026-02-28 📦 **v0.2.0** publiée avec support Docker Compose et Web UI Launcher. -2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles. -
    Actualités précédentes... +2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles. + 2026-02-16 🎉 PicoClaw dépasse 12K Stars en une semaine ! Rôles de mainteneurs communautaires et [Roadmap](ROADMAP.md) officiellement lancés. 2026-02-13 🎉 PicoClaw dépasse 5000 Stars en 4 jours ! Roadmap du projet et groupes de développeurs en cours. diff --git a/README.id.md b/README.id.md index 4ef999ac2..d88f5eb32 100644 --- a/README.id.md +++ b/README.id.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [English](README.md) | **Bahasa Indonesia** +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Malay](README.my.md) | [English](README.md) | **Bahasa Indonesia** @@ -56,17 +56,19 @@ ## 📢 Berita +2026-03-25 🚀 **v0.2.4 Dirilis!** Perombakan arsitektur Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keamanan (.security.yml, penyaringan data sensitif), provider baru (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 perbaikan bug. PicoClaw telah mencapai **26K Stars**! + 2026-03-17 🚀 **v0.2.3 Dirilis!** UI system tray (Windows & Linux), pelacakan status sub-agent (`spawn_status`), eksperimental Gateway hot-reload, gerbang keamanan Cron, dan 2 perbaikan keamanan. PicoClaw telah mencapai **25K Stars**! -2026-03-09 🎉 **v0.2.1 — Update terbesar sejauh ini!** Dukungan protokol MCP, 4 channel baru (Matrix/IRC/WeCom/Discord Proxy), 3 provider baru (Kimi/Minimax/Avian), pipeline vision, penyimpanan memori JSONL, routing model. +2026-03-09 🎉 **v0.2.1 — Pembaruan terbesar sejauh ini!** Dukungan protokol MCP, 4 channel baru (Matrix/IRC/WeCom/Discord Proxy), 3 provider baru (Kimi/Minimax/Avian), pipeline visi, penyimpanan memori JSONL, perutean model. 2026-02-28 📦 **v0.2.0** dirilis dengan dukungan Docker Compose dan Web UI Launcher. -2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif. -
    Berita sebelumnya... +2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif. + 2026-02-16 🎉 PicoClaw menembus 12K Stars dalam satu minggu! Peran maintainer komunitas dan [Roadmap](ROADMAP.md) resmi diluncurkan. 2026-02-13 🎉 PicoClaw menembus 5000 Stars dalam 4 hari! Roadmap proyek dan grup pengembang sedang dalam proses. diff --git a/README.it.md b/README.it.md index b715ea33a..5ed82f15d 100644 --- a/README.it.md +++ b/README.it.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **Italiano** | [Bahasa Indonesia](README.id.md) | [English](README.md) +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **Italiano** | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -56,17 +56,19 @@ ## 📢 Novità +2026-03-25 🚀 **v0.2.4 rilasciata!** Revisione dell'architettura Agent (SubTurn, Hooks, Steering, EventBus), integrazione WeChat/WeCom, rafforzamento della sicurezza (.security.yml, filtraggio dati sensibili), nuovi provider (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correzioni di bug. PicoClaw raggiunge **26K Stars**! + 2026-03-17 🚀 **v0.2.3 rilasciata!** Interfaccia system tray (Windows & Linux), query sullo stato dei sub-agent (`spawn_status`), hot-reload sperimentale del Gateway, gate di sicurezza per Cron e 2 correzioni di sicurezza. PicoClaw raggiunge **25K Stars**! -2026-03-09 🎉 **v0.2.1 — Il più grande aggiornamento di sempre!** Supporto al protocollo MCP, 4 nuovi canali (Matrix/IRC/WeCom/Discord Proxy), 3 nuovi provider (Kimi/Minimax/Avian), pipeline di visione, store di memoria JSONL e routing dei modelli. +2026-03-09 🎉 **v0.2.1 — Il più grande aggiornamento di sempre!** Supporto al protocollo MCP, 4 nuovi canali (Matrix/IRC/WeCom/Discord Proxy), 3 nuovi provider (Kimi/Minimax/Avian), pipeline visiva, archivio memoria JSONL, routing dei modelli. 2026-02-28 📦 **v0.2.0** rilasciata con supporto Docker Compose e Web UI Launcher. -2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive. -
    Notizie precedenti... +2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive. + 2026-02-16 🎉 PicoClaw supera 12K stelle in una settimana! Ruoli di maintainer della community e [Roadmap](ROADMAP.md) pubblicati ufficialmente. 2026-02-13 🎉 PicoClaw supera 5000 stelle in 4 giorni! Roadmap del progetto e gruppi sviluppatori in fase di avvio. diff --git a/README.ja.md b/README.ja.md index 6b2bfc2b3..47d43eb56 100644 --- a/README.ja.md +++ b/README.ja.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | **日本語** | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md) +[中文](README.zh.md) | **日本語** | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -56,17 +56,19 @@ ## 📢 ニュース +2026-03-25 🚀 **v0.2.4 リリース!** Agent アーキテクチャ全面刷新(SubTurn、Hooks、Steering、EventBus)、WeChat/WeCom 統合、セキュリティ強化(.security.yml、機密データフィルタリング)、新プロバイダー(AWS Bedrock、Azure、Xiaomi MiMo)、35 件のバグ修正。PicoClaw **26K ⭐** 達成! + 2026-03-17 🚀 **v0.2.3 リリース!** システムトレイ UI(Windows & Linux)、サブエージェントステータス追跡(`spawn_status`)、実験的 Gateway ホットリロード、cron セキュリティゲート、セキュリティ修正 2 件。PicoClaw **25K ⭐** 達成! -2026-03-09 🎉 **v0.2.1 — 史上最大のアップデート!** MCP プロトコル対応、4 つの新 Channel(Matrix/IRC/WeCom/Discord Proxy)、3 つの新 Provider(Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。 +2026-03-09 🎉 **v0.2.1 — 最大のアップデート!** MCP プロトコルサポート、4 つの新チャンネル (Matrix/IRC/WeCom/Discord Proxy)、3 つの新プロバイダー (Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。 -2026-02-28 📦 **v0.2.0** リリース — Docker Compose 対応と Web UI Launcher。 - -2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!Channel 自動オーケストレーションとケイパビリティインターフェースが実装されました。 +2026-02-28 📦 **v0.2.0** リリース — Docker Compose と Web UI Launcher サポート。
    過去のニュース... +2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!Channel 自動オーケストレーションとケイパビリティインターフェースが実装されました。 + 2026-02-16 🎉 PicoClaw が 1 週間で 12K スター達成!コミュニティメンテナーの役割と[ロードマップ](ROADMAP.md)が正式に公開されました。 2026-02-13 🎉 PicoClaw が 4 日間で 5000 スター達成!プロジェクトロードマップと開発者グループの準備が進行中。 diff --git a/README.md b/README.md index 31ae19eb4..e963fb1cf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | **English** +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | **English** @@ -56,17 +56,19 @@ ## 📢 News +2026-03-25 🚀 **v0.2.4 Released!** Agent architecture overhaul (SubTurn, Hooks, Steering, EventBus), WeChat/WeCom integration, security hardening (.security.yml, sensitive data filtering), new providers (AWS Bedrock, Azure, Xiaomi MiMo), and 35 bug fixes. PicoClaw has reached **26K Stars**! + 2026-03-17 🚀 **v0.2.3 Released!** System tray UI (Windows & Linux), sub-agent status query (`spawn_status`), experimental Gateway hot-reload, Cron security gating, and 2 security fixes. PicoClaw has reached **25K Stars**! 2026-03-09 🎉 **v0.2.1 — Biggest update yet!** MCP protocol support, 4 new channels (Matrix/IRC/WeCom/Discord Proxy), 3 new providers (Kimi/Minimax/Avian), vision pipeline, JSONL memory store, model routing. 2026-02-28 📦 **v0.2.0** released with Docker Compose and Web UI Launcher support. -2026-02-26 🎉 PicoClaw hits **20K Stars** in just 17 days! Channel auto-orchestration and capability interfaces are live. -
    Earlier news... +2026-02-26 🎉 PicoClaw hits **20K Stars** in just 17 days! Channel auto-orchestration and capability interfaces are live. + 2026-02-16 🎉 PicoClaw breaks 12K Stars in one week! Community maintainer roles and [Roadmap](ROADMAP.md) officially launched. 2026-02-13 🎉 PicoClaw breaks 5000 Stars in 4 days! Project roadmap and developer groups in progress. diff --git a/README.my.md b/README.my.md new file mode 100644 index 000000000..2f1a38942 --- /dev/null +++ b/README.my.md @@ -0,0 +1,599 @@ +
    +PicoClaw + +

    PicoClaw: Pembantu AI Ultra-Cekap dalam Go

    + +

    Perkakasan $10 · RAM 10MB · Boot ms · Jom, PicoClaw!

    +

    + Go + Hardware + License +
    + Website + Docs + Wiki +
    + Twitter + + Discord +

    + +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | **Malay** | [English](README.md) + +
    + +--- + +> **PicoClaw** adalah projek sumber terbuka bebas yang dilancarkan oleh [Sipeed](https://sipeed.com), ditulis sepenuhnya dalam **Go** dari awal — bukan cabang OpenClaw, NanoBot, atau projek lain. + +**PicoClaw** adalah pembantu AI peribadi ultra-ringan yang terinspirasi oleh [NanoBot](https://github.com/HKUDS/nanobot). Ia dibina semula dari awal dalam **Go** melalui proses "self-bootstrapping" — AI Agent itu sendiri yang memacu migrasi seni bina dan pengoptimuman kod. + +**Berjalan pada perkakasan $10 dengan RAM <10MB** — 99% lebih sedikit memori daripada OpenClaw dan 98% lebih murah daripada Mac mini! + + + + + + +
    +

    + +

    +
    +

    + +

    +
    + +> [!CAUTION] +> **Notis Keselamatan** +> +> * **TIADA KRIPTO:** PicoClaw **tidak** mengeluarkan sebarang token atau mata wang kripto rasmi. Semua tuntutan di `pump.fun` atau platform dagangan lain adalah **penipuan**. +> * **DOMAIN RASMI:** Satu-satunya laman web rasmi ialah **[picoclaw.io](https://picoclaw.io)**, dan laman web syarikat ialah **[sipeed.com](https://sipeed.com)** +> * **BERHATI-HATI:** Banyak domain `.ai/.org/.com/.net/...` telah didaftarkan oleh pihak ketiga. Jangan percayai mereka. +> * **NOTA:** PicoClaw dalam pembangunan pesat awal. Mungkin terdapat isu keselamatan yang belum diselesaikan. Jangan deploy ke pengeluaran sebelum v1.0. + + +## 📢 Berita + +2026-03-25 🚀 **v0.2.4 Dikeluarkan!** Penstrukturan semula seni bina Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keselamatan (.security.yml, penapisan data sensitif), penyedia baharu (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 pembetulan pepijat. PicoClaw mencapai **26K Stars**! + +2026-03-17 🚀 **v0.2.3 Dikeluarkan!** UI dulang sistem (Windows & Linux), pertanyaan status sub-agent (`spawn_status`), muat semula panas Gateway eksperimental, kawalan keselamatan Cron, dan 2 pembetulan keselamatan. PicoClaw mencapai **25K Stars**! + +2026-03-09 🎉 **v0.2.1 — Kemas kini terbesar setakat ini!** Sokongan protokol MCP, 4 saluran baharu (Matrix/IRC/WeCom/Discord Proxy), 3 penyedia baharu (Kimi/Minimax/Avian), saluran paip visi, storan memori JSONL, penghalaan model. + +2026-02-28 📦 **v0.2.0** dikeluarkan dengan sokongan Docker Compose dan Pelancar Web UI. + +
    +Berita terdahulu... + +2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi saluran automatik dan antara muka keupayaan kini aktif. + +2026-02-16 🎉 PicoClaw melepasi 12K Stars dalam seminggu! Peranan penyelenggara komuniti dan [Peta Jalan](ROADMAP.md) dilancarkan secara rasmi. + +2026-02-13 🎉 PicoClaw melepasi 5000 Stars dalam 4 hari! Peta jalan projek dan kumpulan pembangun sedang dalam proses. + +2026-02-09 🎉 **PicoClaw Dikeluarkan!** Dibina dalam 1 hari untuk membawa AI Agent ke perkakasan $10 dengan RAM <10MB. Jom, PicoClaw! + +
    + +## ✨ Ciri-ciri + +🪶 **Ultra-ringan**: Jejak memori teras <10MB — 99% lebih kecil daripada OpenClaw.* + +💰 **Kos minimum**: Cukup cekap untuk berjalan pada perkakasan $10 — 98% lebih murah daripada Mac mini. + +⚡️ **Boot kilat**: 400x lebih pantas. Boot dalam <1s walaupun pada pemproses teras tunggal 0.6GHz. + +🌍 **Benar-benar mudah alih**: Binari tunggal merentasi seni bina RISC-V, ARM, MIPS, dan x86. + +🤖 **Dibantu AI**: Pelaksanaan Go tulen — 95% kod teras dijana oleh Agent dan diperhalusi melalui semakan manusia. + +🔌 **Sokongan MCP**: Integrasi [Model Context Protocol](https://modelcontextprotocol.io/) natif. + +👁️ **Saluran paip visi**: Hantar imej dan fail terus ke Agent — pengekodan base64 automatik untuk LLM multimodal. + +🧠 **Penghalaan pintar**: Penghalaan model berasaskan peraturan — pertanyaan mudah ke model ringan, menjimatkan kos API. + +_*Binaan terkini mungkin menggunakan 10-20MB disebabkan penggabungan PR yang pesat. Pengoptimuman sumber dirancang. Perbandingan kelajuan boot berdasarkan penanda aras teras tunggal 0.8GHz (lihat jadual di bawah)._ + +
    + +| | OpenClaw | NanoBot | **PicoClaw** | +| ------------------------------ | ------------- | ------------------------ | -------------------------------------- | +| **Bahasa** | TypeScript | Python | **Go** | +| **RAM** | >1GB | >100MB | **< 10MB*** | +| **Masa Boot** (teras 0.8GHz) | >500s | >30s | **<1s** | +| **Kos** | Mac Mini $599 | Kebanyakan papan Linux ~$50 | **Mana-mana papan Linux dari $10** | + +PicoClaw + +
    + +> **[Senarai Keserasian Perkakasan](docs/hardware-compatibility.md)** — Lihat semua papan yang diuji, dari RISC-V $5 hingga Raspberry Pi hingga telefon Android. + +

    +Keserasian Perkakasan PicoClaw +

    + +## 🦾 Demonstrasi + +### 🛠️ Aliran Kerja Pembantu Standard + + + + + + + + + + + + + + + + + +

    Mod Jurutera Full-Stack

    Pengelogan & Perancangan

    Carian Web & Pembelajaran

    Bangun · Deploy · SkalaJadual · Automatik · IngatTemui · Wawasan · Trend
    + +### 🐜 Deployment Jejak Rendah yang Inovatif + +PicoClaw boleh digunakan pada hampir mana-mana peranti Linux! + +- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) untuk pembantu rumah minimal +- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html) untuk operasi pelayan automatik +- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) untuk pengawasan pintar + + + +🌟 Lebih Banyak Kes Deployment Menanti! + + +## 📦 Pemasangan + +### Muat turun dari picoclaw.io (Disyorkan) + +Lawati **[picoclaw.io](https://picoclaw.io)** — laman web rasmi mengesan platform anda secara automatik dan menyediakan muat turun satu klik. + +### Muat turun binari pra-kompil + +Muat turun binari untuk platform anda dari halaman [GitHub Releases](https://github.com/sipeed/picoclaw/releases). + +### Bina dari sumber (untuk pembangunan) + +```bash +git clone https://github.com/sipeed/picoclaw.git +cd picoclaw +make deps + +# Bina binari teras +make build + +# Bina Pelancar Web UI (diperlukan untuk mod WebUI) +make build-launcher + +# Bina untuk pelbagai platform +make build-all + +# Bina untuk Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64) +make build-pi-zero + +# Bina dan pasang +make install +``` + +**Raspberry Pi Zero 2 W:** Gunakan binari yang sepadan dengan OS anda: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Atau jalankan `make build-pi-zero` untuk membina kedua-duanya. + +## 🚀 Panduan Permulaan Pantas + +### 🌐 Pelancar WebUI (Disyorkan untuk Desktop) + +Pelancar WebUI menyediakan antara muka berasaskan pelayar untuk konfigurasi dan sembang. Ini adalah cara termudah untuk bermula — tiada pengetahuan baris arahan diperlukan. + +**Pilihan 1: Klik dua kali (Desktop)** + +Selepas memuat turun dari [picoclaw.io](https://picoclaw.io), klik dua kali `picoclaw-launcher` (atau `picoclaw-launcher.exe` pada Windows). Pelayar anda akan dibuka secara automatik di `http://localhost:18800`. + +**Pilihan 2: Baris arahan** + +```bash +picoclaw-launcher +# Buka http://localhost:18800 dalam pelayar anda +``` + +> [!TIP] +> **Akses jauh / Docker / VM:** Tambah bendera `-public` untuk mendengar pada semua antara muka: +> ```bash +> picoclaw-launcher -public +> ``` + +

    +Pelancar WebUI +

    + +**Memulakan:** Buka WebUI, kemudian: **1)** Konfigurasikan Penyedia (tambah kunci API LLM) -> **2)** Konfigurasikan Saluran (cth. Telegram) -> **3)** Mulakan Gateway -> **4)** Sembang! + +Untuk dokumentasi WebUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io). + +
    +Docker (alternatif) + +```bash +# 1. Klon repo ini +git clone https://github.com/sipeed/picoclaw.git +cd picoclaw + +# 2. Jalankan pertama kali — jana docker/data/config.json secara automatik kemudian keluar +docker compose -f docker/docker-compose.yml --profile launcher up + +# 3. Tetapkan kunci API anda +vim docker/data/config.json + +# 4. Mulakan +docker compose -f docker/docker-compose.yml --profile launcher up -d +# Buka http://localhost:18800 +``` + +> **Pengguna Docker / VM:** Gateway mendengar pada `127.0.0.1` secara lalai. Tetapkan `PICOCLAW_GATEWAY_HOST=0.0.0.0` atau gunakan bendera `-public` untuk membolehkan akses dari hos. + +```bash +# Semak log +docker compose -f docker/docker-compose.yml logs -f + +# Henti +docker compose -f docker/docker-compose.yml --profile launcher down + +# Kemas kini +docker compose -f docker/docker-compose.yml pull +docker compose -f docker/docker-compose.yml --profile launcher up -d +``` + +
    + + +
    +macOS — Amaran Keselamatan Pelancaran Pertama + +macOS mungkin menyekat `picoclaw-launcher` pada pelancaran pertama kerana ia dimuat turun dari internet dan tidak disahkan melalui Mac App Store. + +**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat amaran keselamatan: + +

    +Amaran macOS Gatekeeper +

    + +> *"picoclaw-launcher" Tidak Dibuka — Apple tidak dapat mengesahkan "picoclaw-launcher" bebas daripada perisian hasad yang mungkin membahayakan Mac anda atau menjejaskan privasi anda.* + +**Langkah 2:** Buka **Tetapan Sistem** → **Privasi & Keselamatan** → tatal ke bawah ke bahagian **Keselamatan** → klik **Buka Juga** → sahkan dengan mengklik **Buka Juga** dalam dialog. + +

    +macOS Privasi & Keselamatan — Buka Juga +

    + +Selepas langkah sekali ini, `picoclaw-launcher` akan dibuka secara normal pada pelancaran seterusnya. + +
    + +### 💻 Pelancar TUI (Disyorkan untuk Headless / SSH) + +Pelancar TUI menyediakan antara muka terminal lengkap untuk konfigurasi dan pengurusan. Sesuai untuk pelayan, Raspberry Pi, dan persekitaran tanpa kepala lain. + +```bash +picoclaw-launcher-tui +``` + +

    +Pelancar TUI +

    + +**Memulakan:** + +Gunakan menu TUI untuk: **1)** Konfigurasikan Penyedia -> **2)** Konfigurasikan Saluran -> **3)** Mulakan Gateway -> **4)** Sembang! + +Untuk dokumentasi TUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io). + +### 📱 Android + +Berikan telefon lama anda kehidupan baru! Jadikannya Pembantu AI pintar dengan PicoClaw. + +**Pilihan 1: Termux (tersedia sekarang)** + +1. Pasang [Termux](https://github.com/termux/termux-app) (muat turun dari [GitHub Releases](https://github.com/termux/termux-app/releases), atau cari di F-Droid / Google Play) +2. Jalankan arahan berikut: + +```bash +# Muat turun keluaran terkini +wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz +tar xzf picoclaw_Linux_arm64.tar.gz +pkg install proot +termux-chroot ./picoclaw onboard # chroot menyediakan susun atur sistem fail Linux standard +``` + +Kemudian ikuti bahagian Pelancar Terminal di bawah untuk melengkapkan konfigurasi. + +PicoClaw pada Termux + +**Pilihan 2: APK (akan datang)** + +APK Android bebas dengan WebUI terbina dalam sedang dalam pembangunan. Nantikan! + +
    +Pelancar Terminal (untuk persekitaran terhad sumber) + +Untuk persekitaran minimal di mana hanya binari teras `picoclaw` tersedia (tiada UI Pelancar), anda boleh mengkonfigurasi semua melalui baris arahan dan fail konfigurasi JSON. + +**1. Mulakan** + +```bash +picoclaw onboard +``` + +Ini mencipta `~/.picoclaw/config.json` dan direktori ruang kerja. + +**2. Konfigurasikan** (`~/.picoclaw/config.json`) + +```json +{ + "agents": { + "defaults": { + "model_name": "gpt-5.4" + } + }, + "model_list": [ + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4" + } + ] +} +``` + +> Lihat `config/config.example.json` dalam repo untuk templat konfigurasi lengkap. Nota: kunci API kini disimpan dalam `.security.yml`, bukan `config.json`. + +**3. Sembang** + +```bash +picoclaw agent -m "Apa itu 2+2?" + +# Mod interaktif +picoclaw agent + +# Mulakan gateway untuk integrasi aplikasi sembang +picoclaw gateway +``` + +
    + + +## 🔌 Penyedia (LLM) + +PicoClaw menyokong 30+ penyedia LLM melalui konfigurasi `model_list`. Gunakan format `protokol/model`: + +| Penyedia | Protokol | Kunci API | Nota | +|----------|----------|-----------|------| +| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Diperlukan | GPT-5.4, GPT-4o, o3, dll. | +| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Diperlukan | Claude Opus 4.6, Sonnet 4.6, dll. | +| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Diperlukan | Gemini 3 Flash, 2.5 Pro, dll. | +| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Diperlukan | 200+ model, API bersatu | +| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Diperlukan | GLM-4.7, GLM-5, dll. | +| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Diperlukan | DeepSeek-V3, DeepSeek-R1 | +| [Volcengine](https://console.volcengine.com) | `volcengine/` | Diperlukan | Doubao, model Ark | +| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Diperlukan | Qwen3, Qwen-Max, dll. | +| [Groq](https://console.groq.com/keys) | `groq/` | Diperlukan | Inferens pantas (Llama, Mixtral) | +| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Diperlukan | Model Kimi | +| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Diperlukan | Model MiniMax | +| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Diperlukan | Mistral Large, Codestral | +| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model hos NVIDIA | +| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferens pantas | +| [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Pelbagai model terbuka | +| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo | +| [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model tempatan, self-hosted | +| [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deployment tempatan, serasi OpenAI | +| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Berbeza | Proksi untuk 100+ penyedia | +| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Diperlukan | Deployment Azure perusahaan | +| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Log masuk kod peranti | +| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI | +| [AWS Bedrock](https://console.aws.amazon.com/bedrock)* | `bedrock/` | Kelayakan AWS | Claude, Llama, Mistral pada AWS | + +> \* AWS Bedrock memerlukan tag binaan: `go build -tags bedrock`. Tetapkan `api_base` kepada nama rantau (cth. `us-east-1`) untuk resolusi endpoint automatik merentasi semua partition AWS. Apabila menggunakan URL endpoint penuh, anda juga perlu mengkonfigurasi `AWS_REGION` melalui pemboleh ubah persekitaran. + +
    +Deployment tempatan (Ollama, vLLM, dll.) + +**Ollama:** +```json +{ + "model_list": [ + { + "model_name": "local-llama", + "model": "ollama/llama3.1:8b", + "api_base": "http://localhost:11434/v1" + } + ] +} +``` + +**vLLM:** +```json +{ + "model_list": [ + { + "model_name": "local-vllm", + "model": "vllm/your-model", + "api_base": "http://localhost:8000/v1" + } + ] +} +``` + +Untuk butiran konfigurasi penyedia penuh, lihat [Penyedia & Model](docs/providers.md). + +
    + + +## 💬 Saluran (Aplikasi Sembang) + +Bercakap dengan PicoClaw anda melalui 17+ platform pemesejan: + +| Saluran | Persediaan | Protokol | Dok | +|---------|-----------|----------|-----| +| **Telegram** | Mudah (token bot) | Long polling | [Panduan](docs/channels/telegram/README.md) | +| **Discord** | Mudah (token bot + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) | +| **WhatsApp** | Mudah (imbas QR atau URL jambatan) | Natif / Jambatan | [Panduan](docs/chat-apps.md#whatsapp) | +| **Weixin** | Mudah (imbas QR natif) | iLink API | [Panduan](docs/chat-apps.md#weixin) | +| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) | +| **Slack** | Mudah (token bot + app) | Socket Mode | [Panduan](docs/channels/slack/README.md) | +| **Matrix** | Sederhana (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) | +| **DingTalk** | Sederhana (kelayakan klien) | Stream | [Panduan](docs/channels/dingtalk/README.md) | +| **Feishu / Lark** | Sederhana (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) | +| **LINE** | Sederhana (kelayakan + webhook) | Webhook | [Panduan](docs/channels/line/README.md) | +| **WeCom** | Mudah (log masuk QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) | +| **IRC** | Sederhana (pelayan + nick) | Protokol IRC | [Panduan](docs/chat-apps.md#irc) | +| **OneBot** | Sederhana (URL WebSocket) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) | +| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) | +| **Pico** | Mudah (aktifkan) | Protokol natif | Terbina dalam | +| **Pico Client** | Mudah (URL WebSocket) | WebSocket | Terbina dalam | + +> Semua saluran berasaskan webhook berkongsi satu pelayan HTTP Gateway (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP yang dikongsi. + +Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](docs/my/chat-apps.md). + +## 🔧 Alat + +### 🔍 Carian Web + +PicoClaw boleh mencari web untuk menyediakan maklumat terkini. Konfigurasikan dalam `tools.web`: + +| Enjin Carian | Kunci API | Peringkat Percuma | Pautan | +|-------------|-----------|-------------------|--------| +| DuckDuckGo | Tidak perlu | Tanpa had | Sandaran terbina dalam | +| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Diperlukan | 1000 pertanyaan/hari | Dikuasai AI, dioptimumkan untuk China | +| [Tavily](https://tavily.com) | Diperlukan | 1000 pertanyaan/bulan | Dioptimumkan untuk AI Agent | +| [Brave Search](https://brave.com/search/api) | Diperlukan | 2000 pertanyaan/bulan | Pantas dan peribadi | +| [Perplexity](https://www.perplexity.ai) | Diperlukan | Berbayar | Carian dikuasai AI | +| [SearXNG](https://github.com/searxng/searxng) | Tidak perlu | Self-hosted | Enjin metasearch percuma | +| [GLM Search](https://open.bigmodel.cn/) | Diperlukan | Berbeza | Carian web Zhipu | + +### ⚙️ Alat Lain + +PicoClaw menyertakan alat terbina dalam untuk operasi fail, pelaksanaan kod, penjadualan, dan banyak lagi. Lihat [Konfigurasi Alat](docs/tools_configuration.md) untuk butiran. + +## 🎯 Kemahiran + +Kemahiran adalah keupayaan modular yang melanjutkan Agent anda. Ia dimuatkan dari fail `SKILL.md` dalam ruang kerja anda. + +**Pasang kemahiran dari ClawHub:** + +```bash +picoclaw skills search "web scraping" +picoclaw skills install +``` + +**Konfigurasikan token ClawHub** (pilihan, untuk had kadar lebih tinggi): + +Tambah ke `config.json` anda: +```json +{ + "tools": { + "skills": { + "registries": { + "clawhub": { + "auth_token": "your-clawhub-token" + } + } + } + } +} +``` + +Untuk butiran lanjut, lihat [Konfigurasi Alat - Kemahiran](docs/tools_configuration.md#skills-tool). + +## 🔗 MCP (Protokol Konteks Model) + +PicoClaw menyokong [MCP](https://modelcontextprotocol.io/) secara natif — sambungkan mana-mana pelayan MCP untuk melanjutkan keupayaan Agent anda dengan alat dan sumber data luaran. + +```json +{ + "tools": { + "mcp": { + "enabled": true, + "servers": { + "filesystem": { + "enabled": true, + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] + } + } + } + } +} +``` + +Untuk konfigurasi MCP penuh (pengangkutan stdio, SSE, HTTP, Penemuan Alat), lihat [Konfigurasi Alat - MCP](docs/tools_configuration.md#mcp-tool). + +## ClawdChat Sertai Rangkaian Sosial Agent + +Sambungkan PicoClaw ke Rangkaian Sosial Agent dengan menghantar satu mesej melalui CLI atau mana-mana Aplikasi Sembang yang disepadukan. + +**Baca `https://clawdchat.ai/skill.md` dan ikuti arahan untuk menyertai [ClawdChat.ai](https://clawdchat.ai)** + +## 🖥️ Rujukan CLI + +| Arahan | Penerangan | +| ------ | ---------- | +| `picoclaw onboard` | Mulakan konfigurasi & ruang kerja | +| `picoclaw auth weixin` | Sambungkan akaun WeChat melalui QR | +| `picoclaw agent -m "..."` | Sembang dengan agent | +| `picoclaw agent` | Mod sembang interaktif | +| `picoclaw gateway` | Mulakan gateway | +| `picoclaw status` | Tunjukkan status | +| `picoclaw version` | Tunjukkan maklumat versi | +| `picoclaw model` | Lihat atau tukar model lalai | +| `picoclaw cron list` | Senaraikan semua kerja berjadual | +| `picoclaw cron add ...` | Tambah kerja berjadual | +| `picoclaw cron disable` | Lumpuhkan kerja berjadual | +| `picoclaw cron remove` | Buang kerja berjadual | +| `picoclaw skills list` | Senaraikan kemahiran yang dipasang | +| `picoclaw skills install` | Pasang kemahiran | +| `picoclaw migrate` | Migrasi data dari versi lama | +| `picoclaw auth login` | Sahkan dengan penyedia | + +### ⏰ Tugasan Berjadual / Peringatan + +PicoClaw menyokong peringatan berjadual dan tugasan berulang melalui alat `cron`: + +* **Peringatan sekali**: "Ingatkan saya dalam 10 minit" -> pencetus sekali selepas 10 minit +* **Tugasan berulang**: "Ingatkan saya setiap 2 jam" -> pencetus setiap 2 jam +* **Ungkapan Cron**: "Ingatkan saya pada pukul 9 pagi setiap hari" -> menggunakan ungkapan cron + +## 📚 Dokumentasi + +Untuk panduan terperinci melebihi README ini: + +| Topik | Penerangan | +|-------|------------| +| [Docker & Permulaan Pantas](docs/my/docker.md) | Persediaan Docker Compose, mod Launcher/Agent | +| [Aplikasi Sembang](docs/my/chat-apps.md) | Panduan persediaan 17+ saluran | +| [Konfigurasi](docs/my/configuration.md) | Pemboleh ubah persekitaran, susun atur ruang kerja | +| [Penyedia & Model](docs/providers.md) | 30+ penyedia LLM, penghalaan model | +| [Spawn & Tugasan Async](docs/my/spawn-tasks.md) | Tugasan pantas, tugasan panjang dengan spawn | +| [Penyelesaian Masalah](docs/my/troubleshooting.md) | Isu biasa dan penyelesaian | +| [Konfigurasi Alat](docs/tools_configuration.md) | Aktif/nyahaktif alat, dasar exec, MCP, Kemahiran | +| [Keserasian Perkakasan](docs/hardware-compatibility.md) | Papan yang diuji, keperluan minimum | + +## 🤝 Sumbangan & Peta Jalan + +PR dialu-alukan! Kod sumber sengaja dibuat kecil dan mudah dibaca. + +Lihat [Peta Jalan Komuniti](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan. + +Kumpulan pembangun sedang dibina, sertai selepas PR pertama anda digabungkan! + +Kumpulan Pengguna: + +Discord: + +WeChat: +Kod QR kumpulan WeChat diff --git a/README.pt-br.md b/README.pt-br.md index a3024e264..42ce5cc95 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | **Português** | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md) +[中文](README.zh.md) | [日本語](README.ja.md) | **Português** | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -56,17 +56,19 @@ ## 📢 Novidades +2026-03-25 🚀 **v0.2.4 Lançada!** Reformulação da arquitetura Agent (SubTurn, Hooks, Steering, EventBus), integração WeChat/WeCom, fortalecimento de segurança (.security.yml, filtragem de dados sensíveis), novos providers (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correções de bugs. O PicoClaw atingiu **26K Stars**! + 2026-03-17 🚀 **v0.2.3 Lançada!** UI na bandeja do sistema (Windows e Linux), consulta de status de sub-agent (`spawn_status`), hot-reload experimental do Gateway, controle de segurança do Cron e 2 correções de segurança. O PicoClaw atingiu **25K Stars**! 2026-03-09 🎉 **v0.2.1 — Maior atualização até agora!** Suporte ao protocolo MCP, 4 novos channels (Matrix/IRC/WeCom/Discord Proxy), 3 novos providers (Kimi/Minimax/Avian), pipeline de visão, armazenamento de memória JSONL, roteamento de modelos. 2026-02-28 📦 **v0.2.0** lançada com suporte a Docker Compose e Web UI Launcher. -2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis. -
    Notícias anteriores... +2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis. + 2026-02-16 🎉 O PicoClaw ultrapassa 12K Stars em uma semana! Funções de mantenedor da comunidade e [Roadmap](ROADMAP.md) lançados oficialmente. 2026-02-13 🎉 O PicoClaw ultrapassa 5000 Stars em 4 dias! Roadmap do projeto e grupos de desenvolvedores em andamento. diff --git a/README.vi.md b/README.vi.md index 7274167c3..5ca39d99d 100644 --- a/README.vi.md +++ b/README.vi.md @@ -18,7 +18,7 @@ Discord

    -[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | **Tiếng Việt** | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md) +[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | **Tiếng Việt** | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -56,17 +56,19 @@ ## 📢 Tin tức +2026-03-25 🚀 **v0.2.4 đã phát hành!** Tái cấu trúc kiến trúc Agent (SubTurn, Hooks, Steering, EventBus), tích hợp WeChat/WeCom, tăng cường bảo mật (.security.yml, lọc dữ liệu nhạy cảm), provider mới (AWS Bedrock, Azure, Xiaomi MiMo) và 35 bản vá lỗi. PicoClaw đã đạt **26K Stars**! + 2026-03-17 🚀 **v0.2.3 đã phát hành!** Giao diện system tray (Windows & Linux), truy vấn trạng thái sub-agent (`spawn_status`), thử nghiệm Gateway hot-reload, bảo mật Cron, và 2 bản vá bảo mật. PicoClaw đã đạt **25K Stars**! 2026-03-09 🎉 **v0.2.1 — Bản cập nhật lớn nhất từ trước đến nay!** Hỗ trợ giao thức MCP, 4 Channel mới (Matrix/IRC/WeCom/Discord Proxy), 3 Provider mới (Kimi/Minimax/Avian), pipeline thị giác, bộ nhớ JSONL, định tuyến mô hình. 2026-02-28 📦 **v0.2.0** phát hành với hỗ trợ Docker Compose và Web UI Launcher. -2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động. -
    Tin tức trước đó... +2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động. + 2026-02-16 🎉 PicoClaw vượt 12K Stars trong một tuần! Vai trò người duy trì cộng đồng và [Lộ trình](ROADMAP.md) chính thức ra mắt. 2026-02-13 🎉 PicoClaw vượt 5000 Stars trong 4 ngày! Lộ trình dự án và nhóm nhà phát triển đang được xây dựng. diff --git a/README.zh.md b/README.zh.md index 021f1e28c..7ca20b1ed 100644 --- a/README.zh.md +++ b/README.zh.md @@ -18,7 +18,7 @@ Discord

    -**中文** | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md) +**中文** | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md) @@ -56,17 +56,19 @@ ## 📢 新闻 +2026-03-25 🚀 **v0.2.4 发布!** Agent 架构全面重构(SubTurn、Hook、Steering、EventBus)、微信/企业微信深度集成、安全体系升级(.security.yml、敏感数据过滤)、新增 Provider(AWS Bedrock、Azure、小米 MiMo),以及 35 项 Bug 修复。PicoClaw 已达 **26K ⭐**! + 2026-03-17 🚀 **v0.2.3 发布!** 系统托盘 UI(Windows & Linux)、子 Agent 状态查询 (`spawn_status`)、实验性 Gateway 热重载、Cron 安全门控,以及 2 项安全修复。PicoClaw 已达 **25K ⭐**! 2026-03-09 🎉 **v0.2.1 — 史上最大更新!** MCP 协议支持、4 个新频道 (Matrix/IRC/WeCom/Discord Proxy)、3 个新 Provider (Kimi/Minimax/Avian)、视觉管线、JSONL 记忆存储、模型路由。 2026-02-28 📦 **v0.2.0** 发布,支持 Docker Compose 和 Web UI 启动器。 -2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。 -
    更早的新闻... +2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。 + 2026-02-16 🎉 PicoClaw 一周内突破 12K Stars!社区维护者角色和 [路线图](ROADMAP.md) 正式发布。 2026-02-13 🎉 PicoClaw 4 天内突破 5000 Stars!项目路线图和开发者群组筹建中。 diff --git a/docs/my/chat-apps.md b/docs/my/chat-apps.md new file mode 100644 index 000000000..35a35a7cc --- /dev/null +++ b/docs/my/chat-apps.md @@ -0,0 +1,431 @@ +# 💬 Konfigurasi Aplikasi Sembang + +> Kembali ke [README](../../README.my.md) + +## 💬 Aplikasi Sembang + +Berbual dengan picoclaw anda melalui Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MaixCam, atau Pico (protokol asli) + +> **Nota**: Semua saluran berasaskan webhook (LINE, WeCom, dan sebagainya) diservis pada satu pelayan HTTP Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Tiada port khusus per saluran untuk dikonfigurasikan. Nota: Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP webhook yang dikongsi. + +| Saluran | Penyediaan | +| ---------------- | ------------------------------------------ | +| **Telegram** | Mudah (hanya token) | +| **Discord** | Mudah (token bot + intents) | +| **WhatsApp** | Mudah (asli: imbas QR; atau bridge URL) | +| **Matrix** | Sederhana (homeserver + access token bot) | +| **QQ** | Mudah (AppID + AppSecret) | +| **DingTalk** | Sederhana (kelayakan aplikasi) | +| **LINE** | Sederhana (kelayakan + webhook URL) | +| **WeCom AI Bot** | Sederhana (Token + kunci AES) | +| **Feishu** | Sederhana (App ID + Secret, mod WebSocket) | +| **Slack** | Sederhana (Bot token + App token) | +| **IRC** | Sederhana (pelayan + konfigurasi TLS) | +| **OneBot** | Sederhana (QQ melalui protokol OneBot) | +| **MaixCam** | Mudah (integrasi perkakasan Sipeed) | +| **Pico** | Protokol PicoClaw asli | + +
    +Telegram (Disyorkan) + +**1. Cipta bot** + +* Buka Telegram, cari `@BotFather` +* Hantar `/newbot`, ikut arahan +* Salin token + +**2. Konfigurasi** + +```json +{ + "channels": { + "telegram": { + "enabled": true, + "token": "YOUR_BOT_TOKEN", + "allow_from": ["YOUR_USER_ID"], + "use_markdown_v2": false, + } + } +} +``` + +> Dapatkan user ID anda daripada `@userinfobot` di Telegram. + +**3. Jalankan** + +```bash +picoclaw gateway +``` + +**4. Menu arahan Telegram (auto-register semasa startup)** + +PicoClaw kini menyimpan definisi arahan dalam satu registry bersama. Semasa startup, Telegram akan mendaftarkan arahan bot yang disokong secara automatik (contohnya `/start`, `/help`, `/show`, `/list`) supaya menu arahan dan tingkah laku runtime sentiasa selari. +Pendaftaran menu arahan Telegram kekal sebagai UX penemuan setempat saluran; pelaksanaan arahan generik dikendalikan secara berpusat dalam gelung agen melalui commands executor. + +Jika pendaftaran arahan gagal (ralat sementara rangkaian/API), saluran tetap akan bermula dan PicoClaw akan mencuba semula pendaftaran di latar belakang. + +**4. Pemformatan Lanjutan** +Anda boleh menetapkan `use_markdown_v2: true` untuk mengaktifkan pilihan pemformatan yang lebih maju. Ini membolehkan bot menggunakan keseluruhan set ciri Telegram MarkdownV2, termasuk gaya bersarang, spoiler, dan blok lebar tetap tersuai. + +
    + +
    +Discord + +**1. Cipta bot** + +* Pergi ke +* Cipta aplikasi → Bot → Add Bot +* Salin token bot + +**2. Aktifkan intents** + +* Dalam tetapan Bot, aktifkan **MESSAGE CONTENT INTENT** +* (Pilihan) Aktifkan **SERVER MEMBERS INTENT** jika anda bercadang menggunakan allow list berasaskan data ahli + +**3. Dapatkan User ID anda** +* Discord Settings → Advanced → aktifkan **Developer Mode** +* Klik kanan avatar anda → **Copy User ID** + +**4. Konfigurasi** + +```json +{ + "channels": { + "discord": { + "enabled": true, + "token": "YOUR_BOT_TOKEN", + "allow_from": ["YOUR_USER_ID"] + } + } +} +``` + +**5. Jemput bot** + +* OAuth2 → URL Generator +* Scopes: `bot` +* Bot Permissions: `Send Messages`, `Read Message History` +* Buka URL jemputan yang dijana dan tambahkan bot ke pelayan anda + +**Pilihan: Mod trigger kumpulan** + +Secara lalai bot membalas semua mesej dalam saluran pelayan. Untuk mengehadkan balasan kepada @mention sahaja, tambah: + +```json +{ + "channels": { + "discord": { + "group_trigger": { "mention_only": true } + } + } +} +``` + +Anda juga boleh mencetuskan dengan awalan kata kunci (contohnya `!bot`): + +```json +{ + "channels": { + "discord": { + "group_trigger": { "prefixes": ["!bot"] } + } + } +} +``` + +**6. Jalankan** + +```bash +picoclaw gateway +``` + +
    + +
    +WhatsApp (asli melalui whatsmeow) + +PicoClaw boleh menyambung ke WhatsApp dalam dua cara: + +- **Asli (disyorkan):** Dalam proses menggunakan [whatsmeow](https://github.com/tulir/whatsmeow). Tiada bridge berasingan. Tetapkan `"use_native": true` dan biarkan `bridge_url` kosong. Pada larian pertama, imbas kod QR dengan WhatsApp (Linked Devices). Sesi disimpan di bawah workspace anda (contohnya `workspace/whatsapp/`). Saluran asli ini adalah **pilihan** untuk memastikan binari lalai kekal kecil; bina dengan `-tags whatsapp_native` (contohnya `make build-whatsapp-native` atau `go build -tags whatsapp_native ./cmd/...`). +- **Bridge:** Sambung ke bridge WebSocket luaran. Tetapkan `bridge_url` (contohnya `ws://localhost:3001`) dan biarkan `use_native` sebagai false. + +**Konfigurasi (asli)** + +```json +{ + "channels": { + "whatsapp": { + "enabled": true, + "use_native": true, + "session_store_path": "", + "allow_from": [] + } + } +} +``` + +Jika `session_store_path` kosong, sesi akan disimpan dalam `/whatsapp/`. Jalankan `picoclaw gateway`; pada larian pertama, imbas kod QR yang dipaparkan dalam terminal menggunakan WhatsApp → Linked Devices. + +
    + +
    +QQ + +**1. Cipta bot** + +- Pergi ke [QQ Open Platform](https://q.qq.com/#) +- Cipta aplikasi → Dapatkan **AppID** dan **AppSecret** + +**2. Konfigurasi** + +```json +{ + "channels": { + "qq": { + "enabled": true, + "app_id": "YOUR_APP_ID", + "app_secret": "YOUR_APP_SECRET", + "allow_from": [] + } + } +} +``` + +> Tetapkan `allow_from` kepada kosong untuk membenarkan semua pengguna, atau nyatakan nombor QQ untuk mengehadkan akses. + +**3. Jalankan** + +```bash +picoclaw gateway +``` + +
    + +
    +DingTalk + +**1. Cipta bot** + +* Pergi ke [Open Platform](https://open.dingtalk.com/) +* Cipta aplikasi dalaman +* Salin Client ID dan Client Secret + +**2. Konfigurasi** + +```json +{ + "channels": { + "dingtalk": { + "enabled": true, + "client_id": "YOUR_CLIENT_ID", + "client_secret": "YOUR_CLIENT_SECRET", + "allow_from": [] + } + } +} +``` + +> Tetapkan `allow_from` kepada kosong untuk membenarkan semua pengguna, atau nyatakan user ID DingTalk untuk mengehadkan akses. + +**3. Jalankan** + +```bash +picoclaw gateway +``` +
    + +
    +Matrix + +**1. Sediakan akaun bot** + +* Gunakan homeserver pilihan anda (contohnya `https://matrix.org` atau self-hosted) +* Cipta pengguna bot dan dapatkan access tokennya + +**2. Konfigurasi** + +```json +{ + "channels": { + "matrix": { + "enabled": true, + "homeserver": "https://matrix.org", + "user_id": "@your-bot:matrix.org", + "access_token": "YOUR_MATRIX_ACCESS_TOKEN", + "allow_from": [] + } + } +} +``` + +**3. Jalankan** + +```bash +picoclaw gateway +``` + +Untuk pilihan penuh (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), lihat [Panduan Konfigurasi Saluran Matrix](docs/channels/matrix/README.md). + +
    + +
    +LINE + +**1. Cipta Akaun Rasmi LINE** + +- Pergi ke [LINE Developers Console](https://developers.line.biz/) +- Cipta provider → Cipta saluran Messaging API +- Salin **Channel Secret** dan **Channel Access Token** + +**2. Konfigurasi** + +```json +{ + "channels": { + "line": { + "enabled": true, + "channel_secret": "YOUR_CHANNEL_SECRET", + "channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN", + "webhook_path": "/webhook/line", + "allow_from": [] + } + } +} +``` + +> Webhook LINE diservis pada pelayan Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). + +**3. Tetapkan Webhook URL** + +LINE memerlukan HTTPS untuk webhook. Gunakan reverse proxy atau tunnel: + +```bash +# Contoh dengan ngrok (port lalai gateway ialah 18790) +ngrok http 18790 +``` + +Kemudian tetapkan Webhook URL dalam LINE Developers Console kepada `https://your-domain/webhook/line` dan aktifkan **Use webhook**. + +**4. Jalankan** + +```bash +picoclaw gateway +``` + +> Dalam sembang kumpulan, bot hanya membalas apabila @disebut. Balasan akan memetik mesej asal. + +
    + +
    +WeCom (企业微信) + +PicoClaw menyokong tiga jenis integrasi WeCom: + +**Pilihan 1: WeCom Bot (Bot)** - Penyediaan lebih mudah, menyokong sembang kumpulan +**Pilihan 2: WeCom App (Custom App)** - Lebih banyak ciri, pemesejan proaktif, sembang peribadi sahaja +**Pilihan 3: WeCom AI Bot (AI Bot)** - AI Bot rasmi, balasan streaming, menyokong sembang kumpulan & peribadi + +Lihat [Panduan Konfigurasi WeCom AI Bot](docs/channels/wecom/wecom_aibot/README.zh.md) untuk arahan penyediaan terperinci. + +**Quick Setup - WeCom Bot:** + +**1. Cipta bot** + +* Pergi ke WeCom Admin Console → Group Chat → Add Group Bot +* Salin webhook URL (format: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`) + +**2. Konfigurasi** + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "token": "YOUR_TOKEN", + "encoding_aes_key": "YOUR_ENCODING_AES_KEY", + "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY", + "webhook_path": "/webhook/wecom", + "allow_from": [] + } + } +} +``` + +> Webhook WeCom diservis pada pelayan Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). + +**Quick Setup - WeCom App:** + +**1. Cipta aplikasi** + +* Pergi ke WeCom Admin Console → App Management → Create App +* Salin **AgentId** dan **Secret** +* Pergi ke halaman "My Company", salin **CorpID** + +**2. Konfigurasi penerimaan mesej** + +* Dalam butiran aplikasi, klik "Receive Message" → "Set API" +* Tetapkan URL kepada `http://your-server:18790/webhook/wecom-app` +* Jana **Token** dan **EncodingAESKey** + +**3. Konfigurasi** + +```json +{ + "channels": { + "wecom_app": { + "enabled": true, + "corp_id": "wwxxxxxxxxxxxxxxxx", + "corp_secret": "YOUR_CORP_SECRET", + "agent_id": 1000002, + "token": "YOUR_TOKEN", + "encoding_aes_key": "YOUR_ENCODING_AES_KEY", + "webhook_path": "/webhook/wecom-app", + "allow_from": [] + } + } +} +``` + +**4. Jalankan** + +```bash +picoclaw gateway +``` + +> **Nota**: Callback webhook WeCom diservis pada port Gateway (lalai 18790). Gunakan reverse proxy untuk HTTPS. + +**Quick Setup - WeCom AI Bot:** + +**1. Cipta AI Bot** + +* Pergi ke WeCom Admin Console → App Management → AI Bot +* Dalam tetapan AI Bot, konfigurasikan callback URL: `http://your-server:18791/webhook/wecom-aibot` +* Salin **Token** dan klik "Random Generate" untuk **EncodingAESKey** + +**2. Konfigurasi** + +```json +{ + "channels": { + "wecom_aibot": { + "enabled": true, + "token": "YOUR_TOKEN", + "encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY", + "webhook_path": "/webhook/wecom-aibot", + "allow_from": [], + "welcome_message": "Hello! How can I help you?" + } + } +} +``` + +**3. Jalankan** + +```bash +picoclaw gateway +``` + +> **Nota**: WeCom AI Bot menggunakan protokol streaming pull — tiada isu timeout balasan. Tugasan panjang (>30 saat) akan bertukar secara automatik kepada penghantaran push `response_url`. + +
    diff --git a/docs/my/configuration.md b/docs/my/configuration.md new file mode 100644 index 000000000..f798bd9bd --- /dev/null +++ b/docs/my/configuration.md @@ -0,0 +1,216 @@ +# ⚙️ Panduan Konfigurasi + +> Kembali ke [README](../../README.my.md) + +## ⚙️ Konfigurasi + +Fail konfigurasi: `~/.picoclaw/config.json` + +### Pemboleh Ubah Persekitaran + +Anda boleh menggantikan laluan lalai menggunakan pemboleh ubah persekitaran. Ini berguna untuk pemasangan mudah alih, deployment dalam container, atau menjalankan picoclaw sebagai system service. Pemboleh ubah ini saling bebas dan mengawal laluan yang berbeza. + +| Pemboleh Ubah | Penerangan | Laluan Lalai | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `PICOCLAW_CONFIG` | Menindih laluan ke fail konfigurasi. Ini memberitahu picoclaw secara terus fail `config.json` yang perlu dimuatkan, dengan mengabaikan lokasi lain. | `~/.picoclaw/config.json` | +| `PICOCLAW_HOME` | Menindih direktori root untuk data picoclaw. Ini mengubah lokasi lalai bagi `workspace` dan direktori data lain. | `~/.picoclaw` | + +**Contoh:** + +```bash +# Jalankan picoclaw menggunakan fail config tertentu +# Laluan workspace akan dibaca daripada fail config tersebut +PICOCLAW_CONFIG=/etc/picoclaw/production.json picoclaw gateway + +# Jalankan picoclaw dengan semua data disimpan di /opt/picoclaw +# Config akan dimuatkan dari lalai ~/.picoclaw/config.json +# Workspace akan dicipta di /opt/picoclaw/workspace +PICOCLAW_HOME=/opt/picoclaw picoclaw agent + +# Gunakan kedua-duanya untuk setup yang disesuaikan sepenuhnya +PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway +``` + +### Susun Atur Workspace + +PicoClaw menyimpan data dalam workspace yang dikonfigurasikan (lalai: `~/.picoclaw/workspace`): + +``` +~/.picoclaw/workspace/ +├── sessions/ # Sesi perbualan dan sejarah +├── memory/ # Memori jangka panjang (MEMORY.md) +├── state/ # Keadaan persisten (saluran terakhir, dll.) +├── cron/ # Pangkalan data job berjadual +├── skills/ # Skill tersuai +├── AGENTS.md # Panduan tingkah laku agen +├── HEARTBEAT.md # Prompt tugasan berkala (disemak setiap 30 minit) +├── IDENTITY.md # Identiti agen +├── SOUL.md # Jiwa agen +└── USER.md # Keutamaan pengguna +``` + +### Sumber Skill + +Secara lalai, skill dimuatkan daripada: + +1. `~/.picoclaw/workspace/skills` (workspace) +2. `~/.picoclaw/skills` (global) +3. `/skills` (builtin) + +Untuk setup lanjutan/ujian, anda boleh menindih root builtin skills dengan: + +```bash +export PICOCLAW_BUILTIN_SKILLS=/path/to/skills +``` + +### Polisi Pelaksanaan Arahan Bersepadu + +- Generic slash command dilaksanakan melalui satu laluan dalam `pkg/agent/loop.go` melalui `commands.Executor`. +- Adapter saluran tidak lagi menggunakan generic command secara setempat; ia memajukan teks masuk ke laluan bus/agent. Telegram masih auto-register arahan yang disokong semasa startup. +- Slash command yang tidak dikenali (contohnya `/foo`) akan diteruskan ke pemprosesan LLM biasa. +- Arahan yang didaftarkan tetapi tidak disokong pada saluran semasa (contohnya `/show` di WhatsApp) akan memulangkan ralat yang jelas kepada pengguna dan menghentikan pemprosesan lanjut. + +### 🔒 Security Sandbox + +PicoClaw berjalan dalam persekitaran bersandbox secara lalai. Agen hanya boleh mengakses fail dan melaksanakan arahan dalam workspace yang dikonfigurasikan. + +#### Konfigurasi Lalai + +```json +{ + "agents": { + "defaults": { + "workspace": "~/.picoclaw/workspace", + "restrict_to_workspace": true + } + } +} +``` + +| Option | Default | Description | +| ----------------------- | ----------------------- | ----------------------------------------- | +| `workspace` | `~/.picoclaw/workspace` | Direktori kerja untuk agen | +| `restrict_to_workspace` | `true` | Hadkan akses fail/arahan kepada workspace | + +#### Tools yang Dilindungi + +Apabila `restrict_to_workspace: true`, tools berikut disandboxkan: + +| Tool | Fungsi | Sekatan | +| ------------- | ----------------- | ----------------------------------- | +| `read_file` | Baca fail | Hanya fail dalam workspace | +| `write_file` | Tulis fail | Hanya fail dalam workspace | +| `list_dir` | Senarai direktori | Hanya direktori dalam workspace | +| `edit_file` | Edit fail | Hanya fail dalam workspace | +| `append_file` | Tambah ke fail | Hanya fail dalam workspace | +| `exec` | Jalankan arahan | Laluan arahan mesti dalam workspace | + +#### Perlindungan Exec Tambahan + +Walaupun dengan `restrict_to_workspace: false`, tool `exec` menyekat arahan berbahaya berikut: + +* `rm -rf`, `del /f`, `rmdir /s` — Pemadaman pukal +* `format`, `mkfs`, `diskpart` — Pemformatan cakera +* `dd if=` — Pengimejan cakera +* Menulis ke `/dev/sd[a-z]` — Tulis terus ke cakera +* `shutdown`, `reboot`, `poweroff` — Penutupan sistem +* Fork bomb `:(){ :|:& };:` + +### Kawalan Akses Fail + +| Kunci Config | Jenis | Lalai | Penerangan | +| ------------------------- | -------- | ----- | --------------------------------------------------------------- | +| `tools.allow_read_paths` | string[] | `[]` | Laluan tambahan yang dibenarkan untuk dibaca di luar workspace | +| `tools.allow_write_paths` | string[] | `[]` | Laluan tambahan yang dibenarkan untuk ditulis di luar workspace | + +### Keselamatan Exec + +| Kunci Config | Jenis | Lalai | Penerangan | +| ---------------------------------- | -------- | ------- | ------------------------------------------------------------ | +| `tools.exec.allow_remote` | bool | `false` | Benarkan tool exec dari saluran jauh (Telegram/Discord dll.) | +| `tools.exec.enable_deny_patterns` | bool | `true` | Aktifkan pemintasan arahan berbahaya | +| `tools.exec.custom_deny_patterns` | string[] | `[]` | Corak regex tersuai untuk disekat | +| `tools.exec.custom_allow_patterns` | string[] | `[]` | Corak regex tersuai untuk dibenarkan | + +> **Nota Keselamatan:** Perlindungan symlink diaktifkan secara lalai — semua laluan fail akan diselesaikan melalui `filepath.EvalSymlinks` sebelum dipadankan dengan whitelist, bagi mengelakkan serangan melarikan diri melalui symlink. + +#### Had yang Diketahui: Proses Anak Daripada Build Tools + +Pengawal keselamatan exec hanya memeriksa baris arahan yang PicoClaw lancarkan secara terus. Ia tidak memeriksa secara rekursif proses anak yang dilancarkan oleh tools pembangun yang dibenarkan seperti `make`, `go run`, `cargo`, `npm run`, atau skrip build tersuai. + +Ini bermakna arahan peringkat atas masih boleh mengkompil atau melancarkan binari lain selepas ia melepasi semakan awal pengawal. Dalam amalan, anggap build script, Makefile, package script, dan binari terjana sebagai kod boleh laksana yang memerlukan tahap semakan yang sama seperti arahan shell terus. + +Untuk persekitaran yang lebih berisiko: + +* Semak build script sebelum pelaksanaan. +* Utamakan kelulusan/semakan manual untuk aliran kerja compile-and-run. +* Jalankan PicoClaw dalam container atau VM jika anda memerlukan pengasingan yang lebih kuat daripada pengawal terbina dalam. + +#### Contoh Ralat + +``` +[ERROR] tool: Tool execution failed +{tool=exec, error=Command blocked by safety guard (path outside working dir)} +``` + +``` +[ERROR] tool: Tool execution failed +{tool=exec, error=Command blocked by safety guard (dangerous pattern detected)} +``` + +#### Menyahaktifkan Sekatan (Risiko Keselamatan) + +Jika anda perlu membenarkan agen mengakses laluan di luar workspace: + +**Kaedah 1: Fail config** + +```json +{ + "agents": { + "defaults": { + "restrict_to_workspace": false + } + } +} +``` + +**Kaedah 2: Pemboleh ubah persekitaran** + +```bash +export PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE=false +``` + +> ⚠️ **Amaran**: Menyahaktifkan sekatan ini membenarkan agen mengakses mana-mana laluan pada sistem anda. Gunakan dengan berhati-hati hanya dalam persekitaran terkawal. + +#### Ketekalan Sempadan Keselamatan + +Tetapan `restrict_to_workspace` digunakan secara konsisten merentas semua laluan pelaksanaan: + +| Execution Path | Security Boundary | +| ---------------- | --------------------------- | +| Main Agent | `restrict_to_workspace` ✅ | +| Subagent / Spawn | Inherits same restriction ✅ | +| Heartbeat tasks | Inherits same restriction ✅ | + +Semua laluan berkongsi sekatan workspace yang sama — tiada cara untuk memintas sempadan keselamatan melalui subagent atau tugasan berjadual. + +### Heartbeat (Tugasan Berkala) + +PicoClaw boleh melaksanakan tugasan berkala secara automatik. Cipta fail `HEARTBEAT.md` dalam workspace anda: + +```markdown +# Periodic Tasks + +- Check my email for important messages +- Review my calendar for upcoming events +- Check the weather forecast +``` + +Agen akan membaca fail ini setiap 30 minit (boleh dikonfigurasi) dan melaksanakan sebarang tugasan menggunakan tools yang tersedia. + +#### Tugasan Async dengan Spawn + +Untuk tugasan yang berjalan lama (carian web, panggilan API), gunakan tool `spawn` untuk mencipta **subagent**: + +```markdown +# Periodic Tasks diff --git a/docs/my/debug.md b/docs/my/debug.md new file mode 100644 index 000000000..6ab28365e --- /dev/null +++ b/docs/my/debug.md @@ -0,0 +1,33 @@ +# Penyahpepijatan PicoClaw + +PicoClaw melakukan pelbagai interaksi kompleks di sebalik tabir untuk setiap permintaan yang diterimanya, daripada menghala mesej dan menilai kerumitan, hinggalah melaksanakan tools dan menyesuaikan diri dengan kegagalan model. Keupayaan melihat dengan tepat apa yang sedang berlaku sangat penting, bukan sahaja untuk menyelesaikan masalah, malah untuk benar-benar memahami cara agen ini beroperasi. +## Memulakan PicoClaw dalam Mod Debug + +Untuk mendapatkan maklumat terperinci tentang apa yang sedang dilakukan oleh agen (permintaan LLM, panggilan tool, penghalaan mesej), anda boleh memulakan gateway PicoClaw dengan flag debug: + +```bash +picoclaw gateway --debug +# or +picoclaw gateway -d +``` + +Dalam mod ini, sistem akan memformat log dengan lebih terperinci dan memaparkan pratonton system prompt serta hasil pelaksanaan tool. + +## Menyahaktifkan Pemotongan Log (Log Penuh) + +Secara lalai, PicoClaw memotong rentetan yang sangat panjang (seperti *System Prompt* atau hasil output JSON yang besar) dalam log debug supaya konsol kekal mudah dibaca. + +Jika anda perlu memeriksa output penuh sesuatu arahan atau payload tepat yang dihantar kepada model LLM, anda boleh menggunakan flag `--no-truncate`. + +**Nota:** Flag ini *hanya* berfungsi apabila digabungkan dengan mod `--debug`. + +```bash +picoclaw gateway --debug --no-truncate + +``` + +Apabila flag ini aktif, fungsi pemotongan global dinyahaktifkan. Ini sangat berguna untuk: + +* Mengesahkan sintaks tepat mesej yang dihantar kepada penyedia. +* Membaca output lengkap daripada tools seperti `exec`, `web_fetch`, atau `read_file`. +* Menyahpepijat sejarah sesi yang disimpan dalam memori. diff --git a/docs/my/docker.md b/docs/my/docker.md new file mode 100644 index 000000000..8fe1aed8c --- /dev/null +++ b/docs/my/docker.md @@ -0,0 +1,166 @@ +# 🐳 Panduan Docker & Quick Start + +> Kembali ke [README](../../README.my.md) + +## 🐳 Docker Compose + +Anda juga boleh menjalankan PicoClaw menggunakan Docker Compose tanpa memasang apa-apa secara setempat. + +```bash +# 1. Clone repo ini +git clone https://github.com/sipeed/picoclaw.git +cd picoclaw + +# 2. Larian pertama — jana docker/data/config.json secara automatik kemudian keluar +docker compose -f docker/docker-compose.yml --profile gateway up +# Container akan memaparkan "First-run setup complete." dan berhenti. + +# 3. Tetapkan kunci API anda +vim docker/data/config.json # Tetapkan API key penyedia, token bot, dan sebagainya. + +# 4. Mula +docker compose -f docker/docker-compose.yml --profile gateway up -d +``` + +> [!TIP] +> **Pengguna Docker**: Secara lalai, Gateway mendengar pada `127.0.0.1` yang tidak boleh diakses dari host. Jika anda perlu mengakses health endpoint atau mendedahkan port, tetapkan `PICOCLAW_GATEWAY_HOST=0.0.0.0` dalam persekitaran anda atau kemas kini `config.json`. + +```bash +# 5. Semak log +docker compose -f docker/docker-compose.yml logs -f picoclaw-gateway + +# 6. Hentikan +docker compose -f docker/docker-compose.yml --profile gateway down +``` + +### Mod Launcher (Konsol Web) + +Imej `launcher` merangkumi ketiga-tiga binari (`picoclaw`, `picoclaw-launcher`, `picoclaw-launcher-tui`) dan memulakan konsol web secara lalai, yang menyediakan UI berasaskan pelayar untuk konfigurasi dan sembang. + +```bash +docker compose -f docker/docker-compose.yml --profile launcher up -d +``` + +Buka http://localhost:18800 dalam pelayar anda. Launcher mengurus proses gateway secara automatik. + +> [!WARNING] +> Konsol web belum menyokong autentikasi. Elakkan mendedahkannya ke internet awam. + +### Mod Agent (One-shot) + +```bash +# Tanyakan soalan +docker compose -f docker/docker-compose.yml run --rm picoclaw-agent -m "What is 2+2?" + +# Mod interaktif +docker compose -f docker/docker-compose.yml run --rm picoclaw-agent +``` + +### Kemas kini + +```bash +docker compose -f docker/docker-compose.yml pull +docker compose -f docker/docker-compose.yml --profile gateway up -d +``` + +### 🚀 Quick Start + +> [!TIP] +> Tetapkan API Key anda dalam `~/.picoclaw/config.json`. Dapatkan API Key: [Volcengine (CodingPlan)](https://www.volcengine.com/activity/codingplan?utm_campaign=PicoClaw&utm_content=PicoClaw&utm_medium=devrel&utm_source=OWO&utm_term=PicoClaw) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Carian web adalah pilihan — dapatkan [Tavily API](https://tavily.com) percuma (1000 pertanyaan percuma/bulan) atau [Brave Search API](https://brave.com/search/api) (2000 pertanyaan percuma/bulan). + +**1. Inisialisasi** + +```bash +picoclaw onboard +``` + +**2. Konfigurasi** (`~/.picoclaw/config.json`) + +```json +{ + "agents": { + "defaults": { + "workspace": "~/.picoclaw/workspace", + "model_name": "gpt-5.4", + "max_tokens": 8192, + "temperature": 0.7, + "max_tool_iterations": 20 + } + }, + "model_list": [ + { + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key", + "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "your-api-key", + "request_timeout": 300 + }, + { + "model_name": "claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4.6", + "api_key": "your-anthropic-key" + } + ], + "tools": { + "web": { + "enabled": true, + "fetch_limit_bytes": 10485760, + "format": "plaintext", + "brave": { + "enabled": false, + "api_key": "YOUR_BRAVE_API_KEY", + "max_results": 5 + }, + "tavily": { + "enabled": false, + "api_key": "YOUR_TAVILY_API_KEY", + "max_results": 5 + }, + "duckduckgo": { + "enabled": true, + "max_results": 5 + }, + "perplexity": { + "enabled": false, + "api_key": "YOUR_PERPLEXITY_API_KEY", + "max_results": 5 + }, + "searxng": { + "enabled": false, + "base_url": "http://your-searxng-instance:8888", + "max_results": 5 + } + } + } +} +``` + +> **Baharu**: Format konfigurasi `model_list` membolehkan penambahan penyedia tanpa perubahan kod. Lihat [Konfigurasi Model](#konfigurasi-model-model_list) untuk butiran. +> `request_timeout` adalah pilihan dan menggunakan saat. Jika diabaikan atau ditetapkan kepada `<= 0`, PicoClaw menggunakan timeout lalai (120s). + +**3. Dapatkan API Key** + +* **Penyedia LLM**: [OpenRouter](https://openrouter.ai/keys) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) · [Anthropic](https://console.anthropic.com) · [OpenAI](https://platform.openai.com) · [Gemini](https://aistudio.google.com/api-keys) +* **Carian Web** (pilihan): + * [Brave Search](https://brave.com/search/api) - Berbayar ($5/1000 pertanyaan, ~$5-6/bulan) + * [Perplexity](https://www.perplexity.ai) - Carian berkuasa AI dengan antara muka sembang + * [SearXNG](https://github.com/searxng/searxng) - Enjin meta-carian hos kendiri (percuma, tidak perlu API key) + * [Tavily](https://tavily.com) - Dioptimumkan untuk AI Agents (1000 permintaan/bulan) + * DuckDuckGo - Fallback terbina dalam (tidak memerlukan API key) + +> **Nota**: Lihat `config.example.json` untuk templat konfigurasi penuh. + +**4. Sembang** + +```bash +picoclaw agent -m "What is 2+2?" +``` + +Itu sahaja! Anda kini mempunyai pembantu AI yang berfungsi dalam masa 2 minit. + +--- diff --git a/docs/my/spawn-tasks.md b/docs/my/spawn-tasks.md new file mode 100644 index 000000000..c0c3e8f92 --- /dev/null +++ b/docs/my/spawn-tasks.md @@ -0,0 +1,61 @@ +# 🔄 Spawn & Tugasan Async + +> Kembali ke [README](../../README.my.md) + +## Tugasan Cepat (balas terus) + +- Laporkan masa semasa + +## Tugasan Panjang (guna spawn untuk async) + +- Cari berita AI di web dan ringkaskan +- Semak e-mel dan laporkan mesej penting +``` + +**Tingkah laku utama:** + +| Feature | Description | +| ----------------------- | --------------------------------------------------------- | +| **spawn** | Mencipta sub-agen async, tidak menyekat heartbeat | +| **Independent context** | Sub-agen mempunyai konteks sendiri, tiada sejarah sesi | +| **message tool** | Sub-agen berkomunikasi terus dengan pengguna melalui message tool | +| **Non-blocking** | Selepas spawn, heartbeat terus ke tugasan seterusnya | + +#### Cara Komunikasi Sub-agen Berfungsi + +``` +Heartbeat dicetuskan + ↓ +Agen membaca HEARTBEAT.md + ↓ +Untuk tugasan panjang: spawn sub-agen + ↓ ↓ +Terus ke tugasan seterusnya Sub-agen bekerja secara bebas + ↓ ↓ +Semua tugasan selesai Sub-agen menggunakan tool "message" + ↓ ↓ +Balas HEARTBEAT_OK Pengguna menerima hasil secara terus +``` + +Sub-agen mempunyai akses kepada tools (message, web_search, dan sebagainya) dan boleh berkomunikasi dengan pengguna secara bebas tanpa melalui agen utama. + +**Konfigurasi:** + +```json +{ + "heartbeat": { + "enabled": true, + "interval": 30 + } +} +``` + +| Option | Default | Description | +| ---------- | ------- | ---------------------------------------- | +| `enabled` | `true` | Hidupkan/matikan heartbeat | +| `interval` | `30` | Selang semakan dalam minit (minimum: 5) | + +**Pemboleh ubah persekitaran:** + +* `PICOCLAW_HEARTBEAT_ENABLED=false` untuk nyahaktifkan +* `PICOCLAW_HEARTBEAT_INTERVAL=60` untuk menukar selang diff --git a/docs/my/troubleshooting.md b/docs/my/troubleshooting.md new file mode 100644 index 000000000..c9d987ab4 --- /dev/null +++ b/docs/my/troubleshooting.md @@ -0,0 +1,43 @@ +# Penyelesaian Masalah + +## "model ... not found in model_list" atau OpenRouter "free is not a valid model ID" + +**Gejala:** Anda akan melihat salah satu daripada mesej berikut: + +- `Error creating provider: model "openrouter/free" not found in model_list` +- OpenRouter memulangkan 400: `"free is not a valid model ID"` + +**Punca:** Medan `model` dalam entri `model_list` anda ialah nilai yang dihantar ke API. Untuk OpenRouter, anda mesti menggunakan ID model **penuh**, bukan bentuk singkatan. + +- **Salah:** `"model": "free"` → OpenRouter menerima `free` dan menolaknya. +- **Betul:** `"model": "openrouter/free"` → OpenRouter menerima `openrouter/free` (routing auto free-tier). + +**Penyelesaian:** Dalam `~/.picoclaw/config.json` (atau laluan config anda): + +1. **agents.defaults.model** mesti sepadan dengan `model_name` dalam `model_list` (contohnya `"openrouter-free"`). +2. Medan **model** bagi entri tersebut mesti merupakan ID model OpenRouter yang sah, contohnya: + - `"openrouter/free"` – auto free-tier + - `"google/gemini-2.0-flash-exp:free"` + - `"meta-llama/llama-3.1-8b-instruct:free"` + +Example snippet: + +```json +{ + "agents": { + "defaults": { + "model": "openrouter-free" + } + }, + "model_list": [ + { + "model_name": "openrouter-free", + "model": "openrouter/free", + "api_key": "sk-or-v1-YOUR_OPENROUTER_KEY", + "api_base": "https://openrouter.ai/api/v1" + } + ] +} +``` + +Dapatkan kunci anda di [OpenRouter Keys](https://openrouter.ai/keys). From 62d40a02d45362ed634d6258245e9a4de03238df Mon Sep 17 00:00:00 2001 From: Guoguo <16666742+imguoguo@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:58:09 +0800 Subject: [PATCH 17/55] fix: resolve typecheck errors in loop_test.go and dingtalk_test.go (#2122) - loop_test.go: replace undefined WithSecurity/SecurityConfig/ModelSecurityEntry with direct APIKeys field using SimpleSecureStrings() - dingtalk_test.go: use ClientSecret.String() and ClientSecret.Set() instead of non-existent ClientSecret() and SetClientSecret() methods Co-authored-by: Claude Opus 4.6 (1M context) --- pkg/agent/loop_test.go | 8 ++------ pkg/channels/dingtalk/dingtalk_test.go | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 4fd916d33..25d20c689 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -1783,20 +1783,16 @@ func TestProcessMessage_ModelRoutingUsesLightProvider(t *testing.T) { ModelName: "gemini-main", Model: "gemini/gemini-2.5-flash", APIBase: heavyServer.URL, + APIKeys: config.SimpleSecureStrings("heavy-key"), }, { ModelName: "qwen-light", Model: "ollama/qwen2.5:0.5b", APIBase: lightServer.URL, + APIKeys: config.SimpleSecureStrings("light-key"), }, }, } - cfg.WithSecurity(&config.SecurityConfig{ - ModelList: map[string]config.ModelSecurityEntry{ - "gemini-main": {APIKeys: []string{"heavy-key"}}, - "qwen-light": {APIKeys: []string{"light-key"}}, - }, - }) msgBus := bus.NewMessageBus() provider, _, err := providers.CreateProvider(cfg) diff --git a/pkg/channels/dingtalk/dingtalk_test.go b/pkg/channels/dingtalk/dingtalk_test.go index 3b517aef4..437616456 100644 --- a/pkg/channels/dingtalk/dingtalk_test.go +++ b/pkg/channels/dingtalk/dingtalk_test.go @@ -17,8 +17,8 @@ func newTestDingTalkChannel(t *testing.T, cfg config.DingTalkConfig) (*DingTalkC if cfg.ClientID == "" { cfg.ClientID = "test-client-id" } - if cfg.ClientSecret() == "" { - cfg.SetClientSecret("test-client-secret") + if cfg.ClientSecret.String() == "" { + cfg.ClientSecret.Set("test-client-secret") } msgBus := bus.NewMessageBus() From 465ca0361c022568620483143a2c9e8a436fe09d Mon Sep 17 00:00:00 2001 From: Guoguo Date: Sat, 28 Mar 2026 03:58:52 -0700 Subject: [PATCH 18/55] docs(wecom): add fr/ja/pt-br/vi translations for unified WeCom channel docs Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/channels/wecom/README.fr.md | 148 ++++++++++++++++++++++++++++ docs/channels/wecom/README.ja.md | 148 ++++++++++++++++++++++++++++ docs/channels/wecom/README.pt-br.md | 148 ++++++++++++++++++++++++++++ docs/channels/wecom/README.vi.md | 148 ++++++++++++++++++++++++++++ 4 files changed, 592 insertions(+) create mode 100644 docs/channels/wecom/README.fr.md create mode 100644 docs/channels/wecom/README.ja.md create mode 100644 docs/channels/wecom/README.pt-br.md create mode 100644 docs/channels/wecom/README.vi.md diff --git a/docs/channels/wecom/README.fr.md b/docs/channels/wecom/README.fr.md new file mode 100644 index 000000000..8f6cfe285 --- /dev/null +++ b/docs/channels/wecom/README.fr.md @@ -0,0 +1,148 @@ +> Retour au [README](../../../README.fr.md) + +# WeCom + +PicoClaw expose WeCom en tant que canal unique `channels.wecom`, basé sur l'API WebSocket officielle WeCom AI Bot. +Ce canal remplace l'ancienne séparation `wecom`, `wecom_app` et `wecom_aibot` par un modèle de configuration unifié. + +> Aucune URL de callback webhook publique n'est requise. PicoClaw établit une connexion WebSocket sortante vers WeCom. + +## Fonctionnalités prises en charge + +- Chat privé et chat de groupe +- Réponses en streaming côté canal via le protocole WeCom AI Bot +- Messages entrants : texte, voix, image, fichier, vidéo et messages mixtes +- Réponses sortantes : texte et médias (`image`, `file`, `voice`, `video`) +- Onboarding par QR code via l'interface Web ou le CLI +- Liste blanche partagée et routage `reasoning_channel_id` + +--- + +## Démarrage rapide + +### Option 1 : Liaison QR via l'interface Web (recommandé) + +Ouvrez l'interface Web, accédez à **Channels → WeCom** et cliquez sur le bouton de liaison QR. Scannez le QR code avec WeCom et confirmez dans l'application — les identifiants sont enregistrés automatiquement. + +

    +Liaison QR WeCom dans l'interface Web +

    + +### Option 2 : Connexion QR via le CLI + +Exécutez : + +```bash +picoclaw auth wecom +``` + +La commande : +1. Demande un QR code à WeCom et l'affiche dans le terminal +2. Affiche également un **lien QR code** que vous pouvez ouvrir dans un navigateur si le QR du terminal est difficile à scanner +3. Attend la confirmation — après le scan, vous devez également **confirmer la connexion dans l'application WeCom** +4. En cas de succès, écrit `bot_id` et `secret` dans `channels.wecom` et sauvegarde la configuration + +Le délai d'expiration par défaut est de **5 minutes**. Utilisez `--timeout` pour l'étendre : + +```bash +picoclaw auth wecom --timeout 10m +``` + +> ⚠️ Scanner le QR code ne suffit pas — vous devez également appuyer sur **Confirmer** dans l'application WeCom, sinon la commande expirera. + +### Option 3 : Configuration manuelle + +Si vous disposez déjà d'un `bot_id` et d'un `secret` depuis la plateforme WeCom AI Bot, configurez directement : + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "bot_id": "YOUR_BOT_ID", + "secret": "YOUR_SECRET", + "websocket_url": "wss://openws.work.weixin.qq.com", + "send_thinking_message": true, + "allow_from": [], + "reasoning_channel_id": "" + } + } +} +``` + +--- + +## Configuration + +| Champ | Type | Défaut | Description | +| ----- | ---- | ------ | ----------- | +| `enabled` | bool | `false` | Activer le canal WeCom. | +| `bot_id` | string | — | Identifiant WeCom AI Bot. Requis lorsque le canal est activé. | +| `secret` | string | — | Secret WeCom AI Bot. Stocké chiffré dans `.security.yml`. Requis lorsque le canal est activé. | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Point de terminaison WebSocket WeCom. | +| `send_thinking_message` | bool | `true` | Envoyer un message `Processing...` avant le début de la réponse en streaming. | +| `allow_from` | array | `[]` | Liste blanche des expéditeurs. Vide signifie autoriser tous les expéditeurs. | +| `reasoning_channel_id` | string | `""` | ID de chat optionnel pour router la sortie de raisonnement vers une conversation séparée. | + +### Variables d'environnement + +Tous les champs peuvent être remplacés par des variables d'environnement avec le préfixe `PICOCLAW_CHANNELS_WECOM_` : + +| Variable d'environnement | Champ correspondant | +| ------------------------ | ------------------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- + +## Comportement à l'exécution + +- PicoClaw maintient un tour WeCom actif pour que les réponses en streaming puissent continuer sur le même flux lorsque c'est possible. +- Les réponses en streaming ont une durée maximale de **5,5 minutes** et un intervalle d'envoi minimum de **500 ms**. +- Si le streaming n'est plus disponible, les réponses basculent vers la livraison par push actif. +- Les associations de routes de chat expirent après **30 minutes** d'inactivité. +- Les médias entrants sont téléchargés dans le stockage média local avant d'être transmis à l'agent. +- Les médias sortants sont uploadés vers WeCom en tant que fichier temporaire, puis envoyés comme message média. +- Les messages en double sont détectés et supprimés (tampon circulaire des 1000 derniers identifiants de messages). + +--- + +## Migration depuis l'ancienne configuration WeCom + +| Configuration précédente | Migration | +| ------------------------ | --------- | +| `channels.wecom` (bot webhook) | Remplacer par `channels.wecom` avec `bot_id` + `secret`. | +| `channels.wecom_app` | Supprimer. Utiliser `channels.wecom` à la place. | +| `channels.wecom_aibot` | Déplacer `bot_id` et `secret` vers `channels.wecom`. | +| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Plus utilisés. Supprimer de la configuration. | +| `corp_id`, `corp_secret`, `agent_id` | Plus utilisés. Supprimer de la configuration. | +| `welcome_message`, `processing_message`, `max_steps` | Ne font plus partie de la configuration du canal WeCom. | + +--- + +## Dépannage + +### La liaison QR expire + +- Après avoir scanné le QR code, vous devez également **confirmer la connexion dans l'application WeCom**. Le scan seul ne suffit pas. +- Relancez avec un `--timeout` plus long : `picoclaw auth wecom --timeout 10m` +- Si le QR code dans le terminal est difficile à scanner, utilisez le **lien QR code** affiché en dessous pour l'ouvrir dans un navigateur. + +### QR code expiré + +- Le QR code a une durée de validité limitée. Relancez `picoclaw auth wecom` pour en obtenir un nouveau. + +### Échec de la connexion WebSocket + +- Vérifiez que `bot_id` et `secret` sont corrects. +- Confirmez que l'hôte peut atteindre `wss://openws.work.weixin.qq.com` (WebSocket sortant, aucun port entrant nécessaire). + +### Les réponses n'arrivent pas + +- Vérifiez si `allow_from` bloque l'expéditeur. +- Vérifiez que `channels.wecom.bot_id` et `channels.wecom.secret` sont définis et non vides. diff --git a/docs/channels/wecom/README.ja.md b/docs/channels/wecom/README.ja.md new file mode 100644 index 000000000..34b785ba5 --- /dev/null +++ b/docs/channels/wecom/README.ja.md @@ -0,0 +1,148 @@ +> [README](../../../README.ja.md) に戻る + +# WeCom + +PicoClaw は WeCom を公式 WeCom AI Bot WebSocket API に基づく単一の `channels.wecom` チャンネルとして公開します。 +従来の `wecom`、`wecom_app`、`wecom_aibot` の分割を統一された設定モデルに置き換えました。 + +> パブリックな Webhook コールバック URL は不要です。PicoClaw は WeCom へのアウトバウンド WebSocket 接続を確立します。 + +## サポートされる機能 + +- ダイレクトチャットとグループチャット +- WeCom AI Bot プロトコルによるチャンネル側ストリーミング返信 +- テキスト、音声、画像、ファイル、動画、ミックスメッセージの受信 +- テキストおよびメディア返信の送信(`image`、`file`、`voice`、`video`) +- Web UI または CLI による QR コードオンボーディング +- 共有許可リストと `reasoning_channel_id` ルーティング + +--- + +## クイックスタート + +### オプション 1:Web UI QR バインディング(推奨) + +Web UI を開き、**Channels → WeCom** に移動して、QR バインディングボタンをクリックします。WeCom で QR コードをスキャンし、アプリ内で確認すると、認証情報が自動的に保存されます。 + +

    +Web UI での WeCom QR バインディング +

    + +### オプション 2:CLI QR ログイン + +実行: + +```bash +picoclaw auth wecom +``` + +コマンドの動作: +1. WeCom に QR コードをリクエストし、ターミナルに表示します +2. ターミナルの QR コードがスキャンしにくい場合に備え、ブラウザで開ける **QR コードリンク** も表示します +3. 確認をポーリングします — スキャン後、**WeCom アプリ内でログインを確認** する必要があります +4. 成功すると、`bot_id` と `secret` を `channels.wecom` に書き込み、設定を保存します + +デフォルトのタイムアウトは **5 分** です。`--timeout` で延長できます: + +```bash +picoclaw auth wecom --timeout 10m +``` + +> ⚠️ QR コードのスキャンだけでは不十分です — WeCom アプリ内で **確認** をタップする必要があります。そうしないとコマンドがタイムアウトします。 + +### オプション 3:手動設定 + +WeCom AI Bot プラットフォームから `bot_id` と `secret` を既にお持ちの場合、直接設定できます: + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "bot_id": "YOUR_BOT_ID", + "secret": "YOUR_SECRET", + "websocket_url": "wss://openws.work.weixin.qq.com", + "send_thinking_message": true, + "allow_from": [], + "reasoning_channel_id": "" + } + } +} +``` + +--- + +## 設定 + +| フィールド | 型 | デフォルト | 説明 | +| ---------- | -- | ---------- | ---- | +| `enabled` | bool | `false` | WeCom チャンネルを有効にする。 | +| `bot_id` | string | — | WeCom AI Bot 識別子。有効時に必須。 | +| `secret` | string | — | WeCom AI Bot シークレット。`.security.yml` に暗号化して保存。有効時に必須。 | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | WeCom WebSocket エンドポイント。 | +| `send_thinking_message` | bool | `true` | ストリーミング返信の開始前に `Processing...` メッセージを送信する。 | +| `allow_from` | array | `[]` | 送信者許可リスト。空の場合はすべての送信者を許可。 | +| `reasoning_channel_id` | string | `""` | 推論・思考出力を別の会話にルーティングするためのオプションのチャット ID。 | + +### 環境変数 + +すべてのフィールドは `PICOCLAW_CHANNELS_WECOM_` プレフィックスの環境変数で上書きできます: + +| 環境変数 | 対応フィールド | +| -------- | -------------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- + +## ランタイム動作 + +- PicoClaw はアクティブな WeCom ターンを維持し、可能な限り同じストリームでストリーミング返信を継続します。 +- ストリーミング返信の最大持続時間は **5.5 分**、最小送信間隔は **500ms** です。 +- ストリーミングが利用できなくなった場合、返信はアクティブプッシュ配信にフォールバックします。 +- チャットルートの関連付けは **30 分** の非アクティブ後に期限切れになります。 +- 受信メディアはエージェントに渡される前にローカルメディアストアにダウンロードされます。 +- 送信メディアは WeCom に一時ファイルとしてアップロードされ、メディアメッセージとして送信されます。 +- 重複メッセージは検出され抑制されます(最新 1000 件のメッセージ ID のリングバッファ)。 + +--- + +## レガシー WeCom 設定からの移行 + +| 以前の設定 | 移行方法 | +| ---------- | -------- | +| `channels.wecom`(Webhook ボット) | `bot_id` + `secret` を使用する `channels.wecom` に置き換える。 | +| `channels.wecom_app` | 削除して `channels.wecom` を使用する。 | +| `channels.wecom_aibot` | `bot_id` と `secret` を `channels.wecom` に移動する。 | +| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 使用されなくなりました。設定から削除してください。 | +| `corp_id`、`corp_secret`、`agent_id` | 使用されなくなりました。設定から削除してください。 | +| `welcome_message`、`processing_message`、`max_steps` | WeCom チャンネル設定の一部ではなくなりました。 | + +--- + +## トラブルシューティング + +### QR バインディングがタイムアウトする + +- QR コードをスキャンした後、**WeCom アプリ内でログインを確認** する必要があります。スキャンだけでは不十分です。 +- より長い `--timeout` で再実行してください:`picoclaw auth wecom --timeout 10m` +- ターミナルの QR コードがスキャンしにくい場合は、その下に表示される **QR コードリンク** を使用してブラウザで開いてください。 + +### QR コードの有効期限切れ + +- QR コードには有効期限があります。`picoclaw auth wecom` を再実行して新しいものを取得してください。 + +### WebSocket 接続の失敗 + +- `bot_id` と `secret` が正しいことを確認してください。 +- ホストが `wss://openws.work.weixin.qq.com` に到達できることを確認してください(アウトバウンド WebSocket、インバウンドポートは不要)。 + +### 返信が届かない + +- `allow_from` が送信者をブロックしていないか確認してください。 +- `channels.wecom.bot_id` と `channels.wecom.secret` が設定されており、空でないことを確認してください。 diff --git a/docs/channels/wecom/README.pt-br.md b/docs/channels/wecom/README.pt-br.md new file mode 100644 index 000000000..5d8cf10f0 --- /dev/null +++ b/docs/channels/wecom/README.pt-br.md @@ -0,0 +1,148 @@ +> Voltar ao [README](../../../README.pt-br.md) + +# WeCom + +O PicoClaw expõe o WeCom como um único canal `channels.wecom`, construído sobre a API WebSocket oficial do WeCom AI Bot. +Isso substitui a antiga separação `wecom`, `wecom_app` e `wecom_aibot` por um modelo de configuração unificado. + +> Nenhuma URL de callback webhook pública é necessária. O PicoClaw estabelece uma conexão WebSocket de saída para o WeCom. + +## Funcionalidades Suportadas + +- Chat direto e chat em grupo +- Respostas em streaming pelo protocolo WeCom AI Bot +- Mensagens recebidas: texto, voz, imagem, arquivo, vídeo e mensagens mistas +- Respostas enviadas: texto e mídia (`image`, `file`, `voice`, `video`) +- Onboarding por QR code via Web UI ou CLI +- Lista de permissões compartilhada e roteamento `reasoning_channel_id` + +--- + +## Início Rápido + +### Opção 1: Vinculação QR via Web UI (Recomendado) + +Abra a Web UI, navegue até **Channels → WeCom** e clique no botão de vinculação QR. Escaneie o QR code com o WeCom e confirme no aplicativo — as credenciais são salvas automaticamente. + +

    +Vinculação QR do WeCom na Web UI +

    + +### Opção 2: Login QR via CLI + +Execute: + +```bash +picoclaw auth wecom +``` + +O comando: +1. Solicita um QR code ao WeCom e o exibe no terminal +2. Também exibe um **Link do QR Code** que você pode abrir no navegador se o QR do terminal for difícil de escanear +3. Aguarda a confirmação — após escanear, você também deve **confirmar o login dentro do aplicativo WeCom** +4. Em caso de sucesso, grava `bot_id` e `secret` em `channels.wecom` e salva a configuração + +O timeout padrão é de **5 minutos**. Use `--timeout` para estendê-lo: + +```bash +picoclaw auth wecom --timeout 10m +``` + +> ⚠️ Escanear o QR code não é suficiente — você também deve tocar em **Confirmar** dentro do aplicativo WeCom, caso contrário o comando expirará. + +### Opção 3: Configuração Manual + +Se você já possui um `bot_id` e `secret` da plataforma WeCom AI Bot, configure diretamente: + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "bot_id": "YOUR_BOT_ID", + "secret": "YOUR_SECRET", + "websocket_url": "wss://openws.work.weixin.qq.com", + "send_thinking_message": true, + "allow_from": [], + "reasoning_channel_id": "" + } + } +} +``` + +--- + +## Configuração + +| Campo | Tipo | Padrão | Descrição | +| ----- | ---- | ------ | --------- | +| `enabled` | bool | `false` | Ativar o canal WeCom. | +| `bot_id` | string | — | Identificador do WeCom AI Bot. Obrigatório quando ativado. | +| `secret` | string | — | Secret do WeCom AI Bot. Armazenado criptografado em `.security.yml`. Obrigatório quando ativado. | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Endpoint WebSocket do WeCom. | +| `send_thinking_message` | bool | `true` | Enviar uma mensagem `Processing...` antes do início da resposta em streaming. | +| `allow_from` | array | `[]` | Lista de permissões de remetentes. Vazio significa permitir todos os remetentes. | +| `reasoning_channel_id` | string | `""` | ID de chat opcional para rotear a saída de raciocínio para uma conversa separada. | + +### Variáveis de Ambiente + +Todos os campos podem ser substituídos via variáveis de ambiente com o prefixo `PICOCLAW_CHANNELS_WECOM_`: + +| Variável de Ambiente | Campo Correspondente | +| -------------------- | -------------------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- + +## Comportamento em Tempo de Execução + +- O PicoClaw mantém um turno WeCom ativo para que as respostas em streaming possam continuar no mesmo fluxo quando possível. +- As respostas em streaming têm uma duração máxima de **5,5 minutos** e um intervalo mínimo de envio de **500ms**. +- Se o streaming não estiver mais disponível, as respostas recorrem à entrega por push ativo. +- As associações de rotas de chat expiram após **30 minutos** de inatividade. +- A mídia recebida é baixada para o armazenamento de mídia local antes de ser passada ao agente. +- A mídia enviada é carregada para o WeCom como um arquivo temporário e então enviada como uma mensagem de mídia. +- Mensagens duplicadas são detectadas e suprimidas (buffer circular dos últimos 1000 IDs de mensagens). + +--- + +## Migração da Configuração Legada do WeCom + +| Configuração anterior | Migração | +| --------------------- | -------- | +| `channels.wecom` (bot webhook) | Substituir por `channels.wecom` usando `bot_id` + `secret`. | +| `channels.wecom_app` | Remover. Usar `channels.wecom` no lugar. | +| `channels.wecom_aibot` | Mover `bot_id` e `secret` para `channels.wecom`. | +| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Não mais utilizados. Remover da configuração. | +| `corp_id`, `corp_secret`, `agent_id` | Não mais utilizados. Remover da configuração. | +| `welcome_message`, `processing_message`, `max_steps` | Não fazem mais parte da configuração do canal WeCom. | + +--- + +## Solução de Problemas + +### A vinculação QR expira + +- Após escanear o QR code, você também deve **confirmar o login dentro do aplicativo WeCom**. Escanear sozinho não é suficiente. +- Execute novamente com um `--timeout` maior: `picoclaw auth wecom --timeout 10m` +- Se o QR code no terminal for difícil de escanear, use o **Link do QR Code** exibido abaixo dele para abrir no navegador. + +### QR code expirado + +- O QR code tem validade limitada. Execute novamente `picoclaw auth wecom` para obter um novo. + +### Falha na conexão WebSocket + +- Verifique se `bot_id` e `secret` estão corretos. +- Confirme que o host pode alcançar `wss://openws.work.weixin.qq.com` (WebSocket de saída, nenhuma porta de entrada necessária). + +### As respostas não chegam + +- Verifique se `allow_from` está bloqueando o remetente. +- Verifique se `channels.wecom.bot_id` e `channels.wecom.secret` estão definidos e não vazios. diff --git a/docs/channels/wecom/README.vi.md b/docs/channels/wecom/README.vi.md new file mode 100644 index 000000000..caffb3465 --- /dev/null +++ b/docs/channels/wecom/README.vi.md @@ -0,0 +1,148 @@ +> Quay lại [README](../../../README.vi.md) + +# WeCom + +PicoClaw cung cấp WeCom dưới dạng một kênh duy nhất `channels.wecom`, được xây dựng trên API WebSocket chính thức của WeCom AI Bot. +Điều này thay thế việc phân tách cũ `wecom`, `wecom_app` và `wecom_aibot` bằng một mô hình cấu hình thống nhất. + +> Không cần URL callback webhook công khai. PicoClaw thiết lập kết nối WebSocket đi ra tới WeCom. + +## Tính năng được hỗ trợ + +- Chat trực tiếp và chat nhóm +- Phản hồi streaming qua giao thức WeCom AI Bot +- Nhận tin nhắn văn bản, giọng nói, hình ảnh, tệp, video và tin nhắn hỗn hợp +- Gửi phản hồi văn bản và phương tiện (`image`, `file`, `voice`, `video`) +- Đăng ký qua mã QR bằng Web UI hoặc CLI +- Danh sách cho phép chung và định tuyến `reasoning_channel_id` + +--- + +## Bắt đầu nhanh + +### Tùy chọn 1: Liên kết QR qua Web UI (Khuyến nghị) + +Mở Web UI, điều hướng đến **Channels → WeCom** và nhấp vào nút liên kết QR. Quét mã QR bằng WeCom và xác nhận trong ứng dụng — thông tin đăng nhập được lưu tự động. + +

    +Liên kết QR WeCom trong Web UI +

    + +### Tùy chọn 2: Đăng nhập QR qua CLI + +Chạy: + +```bash +picoclaw auth wecom +``` + +Lệnh thực hiện: +1. Yêu cầu mã QR từ WeCom và hiển thị trong terminal +2. Đồng thời in ra một **Liên kết mã QR** mà bạn có thể mở trong trình duyệt nếu mã QR trên terminal khó quét +3. Chờ xác nhận — sau khi quét, bạn cũng phải **xác nhận đăng nhập trong ứng dụng WeCom** +4. Khi thành công, ghi `bot_id` và `secret` vào `channels.wecom` và lưu cấu hình + +Thời gian chờ mặc định là **5 phút**. Sử dụng `--timeout` để kéo dài: + +```bash +picoclaw auth wecom --timeout 10m +``` + +> ⚠️ Quét mã QR là chưa đủ — bạn cũng phải nhấn **Xác nhận** trong ứng dụng WeCom, nếu không lệnh sẽ hết thời gian chờ. + +### Tùy chọn 3: Cấu hình thủ công + +Nếu bạn đã có `bot_id` và `secret` từ nền tảng WeCom AI Bot, hãy cấu hình trực tiếp: + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "bot_id": "YOUR_BOT_ID", + "secret": "YOUR_SECRET", + "websocket_url": "wss://openws.work.weixin.qq.com", + "send_thinking_message": true, + "allow_from": [], + "reasoning_channel_id": "" + } + } +} +``` + +--- + +## Cấu hình + +| Trường | Kiểu | Mặc định | Mô tả | +| ------ | ---- | -------- | ----- | +| `enabled` | bool | `false` | Kích hoạt kênh WeCom. | +| `bot_id` | string | — | Mã định danh WeCom AI Bot. Bắt buộc khi được kích hoạt. | +| `secret` | string | — | Secret của WeCom AI Bot. Được lưu mã hóa trong `.security.yml`. Bắt buộc khi được kích hoạt. | +| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Điểm cuối WebSocket của WeCom. | +| `send_thinking_message` | bool | `true` | Gửi tin nhắn `Processing...` trước khi phản hồi streaming bắt đầu. | +| `allow_from` | array | `[]` | Danh sách cho phép người gửi. Để trống nghĩa là cho phép tất cả. | +| `reasoning_channel_id` | string | `""` | ID chat tùy chọn để định tuyến đầu ra suy luận đến một cuộc hội thoại riêng. | + +### Biến môi trường + +Tất cả các trường có thể được ghi đè bằng biến môi trường với tiền tố `PICOCLAW_CHANNELS_WECOM_`: + +| Biến môi trường | Trường tương ứng | +| ---------------- | ---------------- | +| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` | +| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` | +| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` | +| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` | +| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` | +| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` | +| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` | + +--- + +## Hành vi khi chạy + +- PicoClaw duy trì một lượt WeCom đang hoạt động để phản hồi streaming có thể tiếp tục trên cùng một luồng khi có thể. +- Phản hồi streaming có thời lượng tối đa **5,5 phút** và khoảng cách gửi tối thiểu **500ms**. +- Nếu streaming không còn khả dụng, phản hồi sẽ chuyển sang gửi push chủ động. +- Các liên kết tuyến chat hết hạn sau **30 phút** không hoạt động. +- Phương tiện nhận được sẽ được tải xuống bộ lưu trữ phương tiện cục bộ trước khi chuyển cho agent. +- Phương tiện gửi đi được tải lên WeCom dưới dạng tệp tạm thời, sau đó gửi dưới dạng tin nhắn phương tiện. +- Tin nhắn trùng lặp được phát hiện và loại bỏ (bộ đệm vòng của 1000 ID tin nhắn gần nhất). + +--- + +## Di chuyển từ cấu hình WeCom cũ + +| Cấu hình trước đây | Di chuyển | +| ------------------- | --------- | +| `channels.wecom` (bot webhook) | Thay thế bằng `channels.wecom` sử dụng `bot_id` + `secret`. | +| `channels.wecom_app` | Xóa. Sử dụng `channels.wecom` thay thế. | +| `channels.wecom_aibot` | Di chuyển `bot_id` và `secret` sang `channels.wecom`. | +| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Không còn sử dụng. Xóa khỏi cấu hình. | +| `corp_id`, `corp_secret`, `agent_id` | Không còn sử dụng. Xóa khỏi cấu hình. | +| `welcome_message`, `processing_message`, `max_steps` | Không còn là một phần của cấu hình kênh WeCom. | + +--- + +## Khắc phục sự cố + +### Liên kết QR hết thời gian chờ + +- Sau khi quét mã QR, bạn cũng phải **xác nhận đăng nhập trong ứng dụng WeCom**. Chỉ quét là chưa đủ. +- Chạy lại với `--timeout` lớn hơn: `picoclaw auth wecom --timeout 10m` +- Nếu mã QR trên terminal khó quét, hãy sử dụng **Liên kết mã QR** được in bên dưới để mở trong trình duyệt. + +### Mã QR đã hết hạn + +- Mã QR có thời hạn hiệu lực giới hạn. Chạy lại `picoclaw auth wecom` để lấy mã mới. + +### Kết nối WebSocket thất bại + +- Kiểm tra xem `bot_id` và `secret` có chính xác không. +- Xác nhận máy chủ có thể kết nối đến `wss://openws.work.weixin.qq.com` (WebSocket đi ra, không cần cổng đến). + +### Phản hồi không đến + +- Kiểm tra xem `allow_from` có đang chặn người gửi không. +- Kiểm tra rằng `channels.wecom.bot_id` và `channels.wecom.secret` đã được thiết lập và không trống. From 11dec0c80ad784ba8c74d1ae4ea2f603ea13432a Mon Sep 17 00:00:00 2001 From: champly Date: Sat, 28 Mar 2026 20:23:14 +0800 Subject: [PATCH 19/55] fix(weixin): persist context tokens to disk to survive restarts (#2124) --- pkg/channels/weixin/state.go | 46 ++++++++++++++++++++--- pkg/channels/weixin/weixin.go | 69 +++++++++++++++++++++++++++++------ 2 files changed, 97 insertions(+), 18 deletions(-) diff --git a/pkg/channels/weixin/state.go b/pkg/channels/weixin/state.go index 2d1b9f4a6..c3ff23584 100644 --- a/pkg/channels/weixin/state.go +++ b/pkg/channels/weixin/state.go @@ -36,6 +36,10 @@ type syncCursorFile struct { GetUpdatesBuf string `json:"get_updates_buf"` } +type contextTokensFile struct { + Tokens map[string]string `json:"tokens"` +} + func picoclawHomeDir() string { if home := os.Getenv(config.EnvHome); home != "" { return home @@ -44,14 +48,21 @@ func picoclawHomeDir() string { return filepath.Join(userHome, ".picoclaw") } -func buildWeixinSyncBufPath(cfg config.WeixinConfig) string { - key := "default" +func genWeixinAccountKey(cfg config.WeixinConfig) string { token := strings.TrimSpace(cfg.Token.String()) - if token != "" { - sum := sha256.Sum256([]byte(strings.TrimSpace(cfg.BaseURL) + "|" + token)) - key = hex.EncodeToString(sum[:8]) + if token == "" { + return "default" } - return filepath.Join(picoclawHomeDir(), "channels", "weixin", "sync", key+".json") + sum := sha256.Sum256([]byte(strings.TrimSpace(cfg.BaseURL) + "|" + token)) + return hex.EncodeToString(sum[:8]) +} + +func buildWeixinSyncBufPath(cfg config.WeixinConfig) string { + return filepath.Join(picoclawHomeDir(), "channels", "weixin", "sync", genWeixinAccountKey(cfg)+".json") +} + +func buildWeixinContextTokensPath(cfg config.WeixinConfig) string { + return filepath.Join(picoclawHomeDir(), "channels", "weixin", "context-tokens", genWeixinAccountKey(cfg)+".json") } func loadGetUpdatesBuf(path string) (string, error) { @@ -79,6 +90,29 @@ func saveGetUpdatesBuf(path, cursor string) error { return fileutil.WriteFileAtomic(path, data, 0o600) } +func loadContextTokens(path string) (map[string]string, error) { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + var decoded contextTokensFile + if err := json.Unmarshal(data, &decoded); err != nil { + return nil, err + } + return decoded.Tokens, nil +} + +func saveContextTokens(path string, tokens map[string]string) error { + data, err := json.Marshal(contextTokensFile{Tokens: tokens}) + if err != nil { + return err + } + return fileutil.WriteFileAtomic(path, data, 0o600) +} + func (c *WeixinChannel) cdnBaseURL() string { if base := strings.TrimSpace(c.config.CDNBaseURL); base != "" { return strings.TrimRight(base, "/") diff --git a/pkg/channels/weixin/weixin.go b/pkg/channels/weixin/weixin.go index fdafb02c2..65fabe399 100644 --- a/pkg/channels/weixin/weixin.go +++ b/pkg/channels/weixin/weixin.go @@ -26,12 +26,13 @@ type WeixinChannel struct { bus *bus.MessageBus // contextTokens stores the last context_token per user (from_user_id → context_token). // This is required by the iLink API to associate replies with the right chat session. - contextTokens sync.Map - typingMu sync.Mutex - typingCache map[string]typingTicketCacheEntry - pauseMu sync.Mutex - pauseUntil time.Time - syncBufPath string + contextTokens sync.Map + typingMu sync.Mutex + typingCache map[string]typingTicketCacheEntry + pauseMu sync.Mutex + pauseUntil time.Time + syncBufPath string + contextTokensPath string } func init() { @@ -57,12 +58,13 @@ func NewWeixinChannel(cfg config.WeixinConfig, messageBus *bus.MessageBus) (*Wei ) return &WeixinChannel{ - BaseChannel: base, - api: api, - config: cfg, - bus: messageBus, - typingCache: make(map[string]typingTicketCacheEntry), - syncBufPath: buildWeixinSyncBufPath(cfg), + BaseChannel: base, + api: api, + config: cfg, + bus: messageBus, + typingCache: make(map[string]typingTicketCacheEntry), + syncBufPath: buildWeixinSyncBufPath(cfg), + contextTokensPath: buildWeixinContextTokensPath(cfg), }, nil } @@ -70,11 +72,53 @@ func (c *WeixinChannel) Start(ctx context.Context) error { logger.InfoC("weixin", "Starting Weixin channel") c.ctx, c.cancel = context.WithCancel(ctx) c.SetRunning(true) + c.restoreContextTokens() go c.pollLoop(c.ctx) logger.InfoC("weixin", "Weixin channel started") return nil } +// restoreContextTokens loads persisted context tokens from disk into memory. +func (c *WeixinChannel) restoreContextTokens() { + tokens, err := loadContextTokens(c.contextTokensPath) + if err != nil { + logger.WarnCF("weixin", "Failed to load persisted context tokens", map[string]any{ + "path": c.contextTokensPath, + "error": err.Error(), + }) + return + } + if len(tokens) == 0 { + return + } + for userID, token := range tokens { + c.contextTokens.Store(userID, token) + } + logger.InfoCF("weixin", "Restored context tokens from disk", map[string]any{ + "path": c.contextTokensPath, + "count": len(tokens), + }) +} + +// persistContextTokens saves all in-memory context tokens to disk. +func (c *WeixinChannel) persistContextTokens() { + tokens := make(map[string]string) + c.contextTokens.Range(func(k, v any) bool { + if userID, ok := k.(string); ok { + if token, ok := v.(string); ok { + tokens[userID] = token + } + } + return true + }) + if err := saveContextTokens(c.contextTokensPath, tokens); err != nil { + logger.WarnCF("weixin", "Failed to persist context tokens", map[string]any{ + "path": c.contextTokensPath, + "error": err.Error(), + }) + } +} + func (c *WeixinChannel) Stop(ctx context.Context) error { logger.InfoC("weixin", "Stopping Weixin channel") c.SetRunning(false) @@ -307,6 +351,7 @@ func (c *WeixinChannel) handleInboundMessage(ctx context.Context, msg WeixinMess // Store context_token for outbound reply association if msg.ContextToken != "" { c.contextTokens.Store(fromUserID, msg.ContextToken) + c.persistContextTokens() } c.HandleMessage(ctx, peer, messageID, fromUserID, fromUserID, content, mediaRefs, metadata, sender) From 026a1339c7f18a7406807beccc3d09d7d43b9a34 Mon Sep 17 00:00:00 2001 From: Mauro Date: Sat, 28 Mar 2026 13:28:24 +0100 Subject: [PATCH 20/55] simplified duplicated code (#1935) --- pkg/auth/oauth.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index 4667e3d81..2bf719dd4 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -545,13 +545,11 @@ func parseTokenResponse(body []byte, provider string) (*AuthCredential, error) { AuthMethod: "oauth", } - if accountID := extractAccountID(tokenResp.IDToken); accountID != "" { - cred.AccountID = accountID - } else if accountID := extractAccountID(tokenResp.AccessToken); accountID != "" { - cred.AccountID = accountID - } else if accountID := extractAccountID(tokenResp.IDToken); accountID != "" { - // Recent OpenAI OAuth responses may only include chatgpt_account_id in id_token claims. - cred.AccountID = accountID + // Recent OpenAI OAuth responses may only include chatgpt_account_id in id_token claims. + if id := extractAccountID(tokenResp.IDToken); id != "" { + cred.AccountID = id + } else if id := extractAccountID(tokenResp.AccessToken); id != "" { + cred.AccountID = id } return cred, nil From 1809d04905baf1738e262186d84195fc61ac8d1f Mon Sep 17 00:00:00 2001 From: Kunal Karmakar <5303824+kunalk16@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:20:24 +0530 Subject: [PATCH 21/55] chore(provider): use openai responses api for azure openai endpoints (#2110) Migrate Azure OpenAI provider from legacy Chat Completions API to the OpenAI Responses API. - Switch API endpoint from `/openai/deployments/{deployment}/chat/completions` to `/openai/v1/responses` - Change auth header from `Api-Key` to `Authorization: Bearer` - Use `responses.ResponseNewParams` SDK types for request construction - Extract shared Responses API utilities into `openai_responses_common` package - Deduplicate 178 lines from codex_provider.go by reusing shared package - Add 593 lines of comprehensive test coverage for the shared package Closes #2111 Co-Authored-By: Claude Opus 4.6 --- pkg/providers/azure/provider.go | 58 +- pkg/providers/azure/provider_test.go | 248 ++++++-- pkg/providers/codex_provider.go | 190 +----- pkg/providers/codex_provider_test.go | 6 +- .../responses_common.go | 291 +++++++++ .../responses_common_test.go | 593 ++++++++++++++++++ 6 files changed, 1126 insertions(+), 260 deletions(-) create mode 100644 pkg/providers/openai_responses_common/responses_common.go create mode 100644 pkg/providers/openai_responses_common/responses_common_test.go diff --git a/pkg/providers/azure/provider.go b/pkg/providers/azure/provider.go index e0ddbbde4..9d29a90cd 100644 --- a/pkg/providers/azure/provider.go +++ b/pkg/providers/azure/provider.go @@ -10,7 +10,11 @@ import ( "strings" "time" + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/responses" + "github.com/sipeed/picoclaw/pkg/providers/common" + orc "github.com/sipeed/picoclaw/pkg/providers/openai_responses_common" "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" ) @@ -21,14 +25,12 @@ type ( ) const ( - // azureAPIVersion is the Azure OpenAI API version used for all requests. - azureAPIVersion = "2024-10-21" defaultRequestTimeout = common.DefaultRequestTimeout ) // Provider implements the LLM provider interface for Azure OpenAI endpoints. -// It handles Azure-specific authentication (api-key header), URL construction -// (deployment-based), and request body formatting (max_completion_tokens, no model field). +// It handles Azure-specific authentication (Bearer token), URL construction +// (Responses API), and request/response formatting. type Provider struct { apiKey string apiBase string @@ -72,8 +74,8 @@ func NewProviderWithTimeout(apiKey, apiBase, proxy string, requestTimeoutSeconds ) } -// Chat sends a chat completion request to the Azure OpenAI endpoint. -// The model parameter is used as the Azure deployment name in the URL. +// Chat sends a request to the Azure OpenAI Responses API endpoint. +// The model parameter is passed in the request body. func (p *Provider) Chat( ctx context.Context, messages []Message, @@ -85,34 +87,43 @@ func (p *Provider) Chat( return nil, fmt.Errorf("Azure API base not configured") } - // model is the deployment name for Azure OpenAI - deployment := model - - // Build Azure-specific URL safely using url.JoinPath and query encoding - // to prevent path traversal or query injection via deployment names. - base, err := url.JoinPath(p.apiBase, "openai/deployments", deployment, "chat/completions") + requestURL, err := url.JoinPath(p.apiBase, "openai/v1/responses") if err != nil { return nil, fmt.Errorf("failed to build Azure request URL: %w", err) } - requestURL := base + "?api-version=" + azureAPIVersion - // Build request body — no "model" field (Azure infers from deployment URL) - requestBody := map[string]any{ - "messages": common.SerializeMessages(messages), + input, instructions := orc.TranslateMessages(messages) + + requestBody := responses.ResponseNewParams{ + Model: model, + Input: responses.ResponseNewParamsInputUnion{ + OfInputItemList: input, + }, + Store: openai.Opt(false), + } + + if instructions != "" { + requestBody.Instructions = openai.Opt(instructions) } if len(tools) > 0 { - requestBody["tools"] = tools - requestBody["tool_choice"] = "auto" + enableWebSearch, _ := options["native_search"].(bool) + requestBody.Tools = orc.TranslateTools(tools, enableWebSearch) + requestBody.ToolChoice = responses.ResponseNewParamsToolChoiceUnion{ + OfToolChoiceMode: openai.Opt(responses.ToolChoiceOptionsAuto), + } } - // Azure OpenAI always uses max_completion_tokens if maxTokens, ok := common.AsInt(options["max_tokens"]); ok { - requestBody["max_completion_tokens"] = maxTokens + requestBody.MaxOutputTokens = openai.Opt(int64(maxTokens)) } if temperature, ok := common.AsFloat(options["temperature"]); ok { - requestBody["temperature"] = temperature + requestBody.Temperature = openai.Opt(temperature) + } + + if cacheKey, ok := options["prompt_cache_key"].(string); ok && cacheKey != "" { + requestBody.PromptCacheKey = openai.Opt(cacheKey) } jsonData, err := json.Marshal(requestBody) @@ -125,10 +136,9 @@ func (p *Provider) Chat( return nil, fmt.Errorf("failed to create request: %w", err) } - // Azure uses api-key header instead of Authorization: Bearer req.Header.Set("Content-Type", "application/json") if p.apiKey != "" { - req.Header.Set("Api-Key", p.apiKey) + req.Header.Set("Authorization", "Bearer "+p.apiKey) } resp, err := p.httpClient.Do(req) @@ -141,7 +151,7 @@ func (p *Provider) Chat( return nil, common.HandleErrorResponse(resp, p.apiBase) } - return common.ReadAndParseResponse(resp, p.apiBase) + return orc.ParseResponseBody(resp.Body) } // GetDefaultModel returns an empty string as Azure deployments are user-configured. diff --git a/pkg/providers/azure/provider_test.go b/pkg/providers/azure/provider_test.go index 531b81296..e57d68057 100644 --- a/pkg/providers/azure/provider_test.go +++ b/pkg/providers/azure/provider_test.go @@ -6,17 +6,31 @@ import ( "net/http/httptest" "testing" "time" + + "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" ) -// writeValidResponse writes a minimal valid Azure OpenAI chat completion response. +// writeValidResponse writes a minimal valid Responses API response. func writeValidResponse(w http.ResponseWriter) { resp := map[string]any{ - "choices": []map[string]any{ + "id": "resp_test", + "object": "response", + "status": "completed", + "output": []map[string]any{ { - "message": map[string]any{"content": "ok"}, - "finish_reason": "stop", + "type": "message", + "content": []map[string]any{ + {"type": "output_text", "text": "ok"}, + }, }, }, + "usage": map[string]any{ + "input_tokens": 5, + "output_tokens": 2, + "total_tokens": 7, + "input_tokens_details": map[string]any{"cached_tokens": 0}, + "output_tokens_details": map[string]any{"reasoning_tokens": 0}, + }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) @@ -24,11 +38,9 @@ func writeValidResponse(w http.ResponseWriter) { func TestProviderChat_AzureURLConstruction(t *testing.T) { var capturedPath string - var capturedAPIVersion string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedPath = r.URL.Path - capturedAPIVersion = r.URL.Query().Get("api-version") writeValidResponse(w) })) defer server.Close() @@ -39,22 +51,19 @@ func TestProviderChat_AzureURLConstruction(t *testing.T) { t.Fatalf("Chat() error = %v", err) } - wantPath := "/openai/deployments/my-gpt5-deployment/chat/completions" + wantPath := "/openai/v1/responses" if capturedPath != wantPath { t.Errorf("URL path = %q, want %q", capturedPath, wantPath) } - if capturedAPIVersion != azureAPIVersion { - t.Errorf("api-version = %q, want %q", capturedAPIVersion, azureAPIVersion) - } } func TestProviderChat_AzureAuthHeader(t *testing.T) { - var capturedAPIKey string var capturedAuth string + var capturedAPIKey string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - capturedAPIKey = r.Header.Get("Api-Key") capturedAuth = r.Header.Get("Authorization") + capturedAPIKey = r.Header.Get("Api-Key") writeValidResponse(w) })) defer server.Close() @@ -65,15 +74,15 @@ func TestProviderChat_AzureAuthHeader(t *testing.T) { t.Fatalf("Chat() error = %v", err) } - if capturedAPIKey != "test-azure-key" { - t.Errorf("api-key header = %q, want %q", capturedAPIKey, "test-azure-key") + if capturedAuth != "Bearer test-azure-key" { + t.Errorf("Authorization header = %q, want %q", capturedAuth, "Bearer test-azure-key") } - if capturedAuth != "" { - t.Errorf("Authorization header should be empty, got %q", capturedAuth) + if capturedAPIKey != "" { + t.Errorf("Api-Key header should be empty, got %q", capturedAPIKey) } } -func TestProviderChat_AzureOmitsModelFromBody(t *testing.T) { +func TestProviderChat_AzureRequestBodyContainsModel(t *testing.T) { var requestBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -83,17 +92,17 @@ func TestProviderChat_AzureOmitsModelFromBody(t *testing.T) { defer server.Close() p := NewProvider("test-key", server.URL, "") - _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "deployment", nil) + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "my-deployment", nil) if err != nil { t.Fatalf("Chat() error = %v", err) } - if _, exists := requestBody["model"]; exists { - t.Error("request body should not contain 'model' field for Azure OpenAI") + if requestBody["model"] != "my-deployment" { + t.Errorf("model = %v, want %q", requestBody["model"], "my-deployment") } } -func TestProviderChat_AzureUsesMaxCompletionTokens(t *testing.T) { +func TestProviderChat_AzureUsesMaxOutputTokens(t *testing.T) { var requestBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -114,12 +123,35 @@ func TestProviderChat_AzureUsesMaxCompletionTokens(t *testing.T) { t.Fatalf("Chat() error = %v", err) } - if _, exists := requestBody["max_completion_tokens"]; !exists { - t.Error("request body should contain 'max_completion_tokens'") + if requestBody["max_output_tokens"] == nil { + t.Error("request body should contain 'max_output_tokens'") } if _, exists := requestBody["max_tokens"]; exists { t.Error("request body should not contain 'max_tokens'") } + if _, exists := requestBody["max_completion_tokens"]; exists { + t.Error("request body should not contain 'max_completion_tokens'") + } +} + +func TestProviderChat_AzureStoreIsFalse(t *testing.T) { + var requestBody map[string]any + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + json.NewDecoder(r.Body).Decode(&requestBody) + writeValidResponse(w) + })) + defer server.Close() + + p := NewProvider("test-key", server.URL, "") + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "deployment", nil) + if err != nil { + t.Fatalf("Chat() error = %v", err) + } + + if requestBody["store"] != false { + t.Errorf("store = %v, want false", requestBody["store"]) + } } func TestProviderChat_AzureHTTPError(t *testing.T) { @@ -135,27 +167,66 @@ func TestProviderChat_AzureHTTPError(t *testing.T) { } } +func TestProviderChat_AzureParseTextOutput(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := map[string]any{ + "id": "resp_1", + "object": "response", + "status": "completed", + "output": []map[string]any{ + { + "type": "message", + "content": []map[string]any{ + {"type": "output_text", "text": "Hello there!"}, + }, + }, + }, + "usage": map[string]any{ + "input_tokens": 10, "output_tokens": 5, "total_tokens": 15, + "input_tokens_details": map[string]any{"cached_tokens": 0}, + "output_tokens_details": map[string]any{"reasoning_tokens": 0}, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer server.Close() + + p := NewProvider("test-key", server.URL, "") + out, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "deployment", nil) + if err != nil { + t.Fatalf("Chat() error = %v", err) + } + if out.Content != "Hello there!" { + t.Errorf("Content = %q, want %q", out.Content, "Hello there!") + } + if out.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", out.FinishReason, "stop") + } + if out.Usage.TotalTokens != 15 { + t.Errorf("TotalTokens = %d, want 15", out.Usage.TotalTokens) + } +} + func TestProviderChat_AzureParseToolCalls(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := map[string]any{ - "choices": []map[string]any{ + "id": "resp_2", + "object": "response", + "status": "completed", + "output": []map[string]any{ { - "message": map[string]any{ - "content": "", - "tool_calls": []map[string]any{ - { - "id": "call_1", - "type": "function", - "function": map[string]any{ - "name": "get_weather", - "arguments": `{"city":"Seattle"}`, - }, - }, - }, - }, - "finish_reason": "tool_calls", + "type": "function_call", + "call_id": "call_1", + "name": "get_weather", + "arguments": `{"city":"Seattle"}`, }, }, + "usage": map[string]any{ + "input_tokens": 10, "output_tokens": 8, "total_tokens": 18, + "input_tokens_details": map[string]any{"cached_tokens": 0}, + "output_tokens_details": map[string]any{"reasoning_tokens": 0}, + }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) @@ -167,13 +238,15 @@ func TestProviderChat_AzureParseToolCalls(t *testing.T) { if err != nil { t.Fatalf("Chat() error = %v", err) } - if len(out.ToolCalls) != 1 { t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls)) } if out.ToolCalls[0].Name != "get_weather" { t.Errorf("ToolCalls[0].Name = %q, want %q", out.ToolCalls[0].Name, "get_weather") } + if out.FinishReason != "tool_calls" { + t.Errorf("FinishReason = %q, want %q", out.FinishReason, "tool_calls") + } } func TestProvider_AzureEmptyAPIBase(t *testing.T) { @@ -205,28 +278,103 @@ func TestProvider_AzureNewProviderWithTimeout(t *testing.T) { } } -func TestProviderChat_AzureDeploymentNameEscaped(t *testing.T) { - var capturedPath string +func TestProviderChat_AzureNativeWebSearchInjection(t *testing.T) { + var requestBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - capturedPath = r.URL.RawPath // use RawPath to see percent-encoding - if capturedPath == "" { - capturedPath = r.URL.Path - } + json.NewDecoder(r.Body).Decode(&requestBody) writeValidResponse(w) })) defer server.Close() + tools := []ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "web_search", + Description: "local web search", + Parameters: map[string]any{"type": "object"}, + }, + }, + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "read_file", + Description: "read a file", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + p := NewProvider("test-key", server.URL, "") - // Deployment name with characters that could cause path injection - _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "my deploy/../../admin", nil) + // With native_search=true: user-defined web_search should be replaced by built-in + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, tools, "deployment", + map[string]any{"native_search": true}) if err != nil { t.Fatalf("Chat() error = %v", err) } - // The slash and special chars in the deployment name must be escaped, not treated as path separators - if capturedPath == "/openai/deployments/my deploy/../../admin/chat/completions" { - t.Fatal("deployment name was interpolated without escaping — path injection possible") + toolsAny, ok := requestBody["tools"].([]any) + if !ok { + t.Fatal("request body should contain 'tools' array") + } + if len(toolsAny) != 2 { + t.Fatalf("len(tools) = %d, want 2 (read_file + web_search builtin)", len(toolsAny)) + } + + // First tool should be read_file (user-defined web_search was skipped) + firstTool, _ := toolsAny[0].(map[string]any) + if firstTool["name"] != "read_file" { + t.Errorf("first tool name = %v, want %q", firstTool["name"], "read_file") + } + + // Second tool should be built-in web_search + secondTool, _ := toolsAny[1].(map[string]any) + if secondTool["type"] != "web_search" { + t.Errorf("second tool type = %v, want %q", secondTool["type"], "web_search") + } +} + +func TestProviderChat_AzureNoNativeWebSearch(t *testing.T) { + var requestBody map[string]any + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + json.NewDecoder(r.Body).Decode(&requestBody) + writeValidResponse(w) + })) + defer server.Close() + + tools := []ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "web_search", + Description: "local web search", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + + p := NewProvider("test-key", server.URL, "") + + // Without native_search: user-defined web_search should be kept as-is + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, tools, "deployment", nil) + if err != nil { + t.Fatalf("Chat() error = %v", err) + } + + toolsAny, ok := requestBody["tools"].([]any) + if !ok { + t.Fatal("request body should contain 'tools' array") + } + if len(toolsAny) != 1 { + t.Fatalf("len(tools) = %d, want 1", len(toolsAny)) + } + + // Should be the user-defined function tool, not built-in + tool, _ := toolsAny[0].(map[string]any) + if tool["type"] != "function" { + t.Errorf("tool type = %v, want %q", tool["type"], "function") } } diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go index 4a6d61a4b..d968215cc 100644 --- a/pkg/providers/codex_provider.go +++ b/pkg/providers/codex_provider.go @@ -2,7 +2,6 @@ package providers import ( "context" - "encoding/json" "errors" "fmt" "strings" @@ -13,6 +12,7 @@ import ( "github.com/sipeed/picoclaw/pkg/auth" "github.com/sipeed/picoclaw/pkg/logger" + orc "github.com/sipeed/picoclaw/pkg/providers/openai_responses_common" ) const ( @@ -96,7 +96,7 @@ func (p *CodexProvider) Chat( } // Respect tools.web.prefer_native: only inject native search when the agent - // loop requested it (options["native_search"]), so prefer_native: false + // loop passes options["native_search"]=true, so prefer_native=false means no injection. useNativeSearch := p.enableWebSearch && (options["native_search"] == true) params := buildCodexParams(messages, tools, resolvedModel, options, useNativeSearch) @@ -153,7 +153,7 @@ func (p *CodexProvider) Chat( return nil, fmt.Errorf("codex API call: stream ended without completed response") } - return parseCodexResponse(resp), nil + return orc.ParseResponseFromStruct(resp), nil } func (p *CodexProvider) GetDefaultModel() string { @@ -209,89 +209,14 @@ func resolveCodexModel(model string) (string, string) { func buildCodexParams( messages []Message, tools []ToolDefinition, model string, options map[string]any, enableWebSearch bool, ) responses.ResponseNewParams { - var inputItems responses.ResponseInputParam - var instructions string - - for _, msg := range messages { - switch msg.Role { - case "system": - // Use the full concatenated system prompt (static + dynamic + summary) - // as instructions. This keeps behavior consistent with Anthropic and - // OpenAI-compat adapters where the complete system context lives in - // one place. Prefix caching is handled by prompt_cache_key below, - // not by splitting content across instructions vs input messages. - instructions = msg.Content - case "user": - if msg.ToolCallID != "" { - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ - CallID: msg.ToolCallID, - Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{ - OfString: openai.Opt(msg.Content), - }, - }, - }) - } else { - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfMessage: &responses.EasyInputMessageParam{ - Role: responses.EasyInputMessageRoleUser, - Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, - }, - }) - } - case "assistant": - if len(msg.ToolCalls) > 0 { - if msg.Content != "" { - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfMessage: &responses.EasyInputMessageParam{ - Role: responses.EasyInputMessageRoleAssistant, - Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, - }, - }) - } - for _, tc := range msg.ToolCalls { - name, args, ok := resolveCodexToolCall(tc) - if !ok { - logger.WarnCF("provider.codex", "Skipping invalid tool call in history", map[string]any{ - "call_id": tc.ID, - }) - continue - } - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfFunctionCall: &responses.ResponseFunctionToolCallParam{ - CallID: tc.ID, - Name: name, - Arguments: args, - }, - }) - } - } else { - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfMessage: &responses.EasyInputMessageParam{ - Role: responses.EasyInputMessageRoleAssistant, - Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, - }, - }) - } - case "tool": - inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ - OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ - CallID: msg.ToolCallID, - Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{ - OfString: openai.Opt(msg.Content), - }, - }, - }) - } - } + inputItems, instructions := orc.TranslateMessages(messages) params := responses.ResponseNewParams{ Model: model, Input: responses.ResponseNewParamsInputUnion{ OfInputItemList: inputItems, }, - Instructions: openai.Opt(instructions), - Store: openai.Opt(false), + Store: openai.Opt(false), } if instructions != "" { @@ -309,115 +234,12 @@ func buildCodexParams( } if len(tools) > 0 || enableWebSearch { - params.Tools = translateToolsForCodex(tools, enableWebSearch) + params.Tools = orc.TranslateTools(tools, enableWebSearch) } return params } -func resolveCodexToolCall(tc ToolCall) (name string, arguments string, ok bool) { - name = tc.Name - if name == "" && tc.Function != nil { - name = tc.Function.Name - } - if name == "" { - return "", "", false - } - - if len(tc.Arguments) > 0 { - argsJSON, err := json.Marshal(tc.Arguments) - if err != nil { - return "", "", false - } - return name, string(argsJSON), true - } - - if tc.Function != nil && tc.Function.Arguments != "" { - return name, tc.Function.Arguments, true - } - - return name, "{}", true -} - -func translateToolsForCodex(tools []ToolDefinition, enableWebSearch bool) []responses.ToolUnionParam { - capHint := len(tools) - if enableWebSearch { - capHint++ - } - result := make([]responses.ToolUnionParam, 0, capHint) - for _, t := range tools { - if t.Type != "function" { - continue - } - if enableWebSearch && strings.EqualFold(t.Function.Name, "web_search") { - continue - } - ft := responses.FunctionToolParam{ - Name: t.Function.Name, - Parameters: t.Function.Parameters, - Strict: openai.Opt(false), - } - if t.Function.Description != "" { - ft.Description = openai.Opt(t.Function.Description) - } - result = append(result, responses.ToolUnionParam{OfFunction: &ft}) - } - if enableWebSearch { - result = append(result, responses.ToolParamOfWebSearch(responses.WebSearchToolTypeWebSearch)) - } - return result -} - -func parseCodexResponse(resp *responses.Response) *LLMResponse { - var content strings.Builder - var toolCalls []ToolCall - - for _, item := range resp.Output { - switch item.Type { - case "message": - for _, c := range item.Content { - if c.Type == "output_text" { - content.WriteString(c.Text) - } - } - case "function_call": - var args map[string]any - if err := json.Unmarshal([]byte(item.Arguments), &args); err != nil { - args = map[string]any{"raw": item.Arguments} - } - toolCalls = append(toolCalls, ToolCall{ - ID: item.CallID, - Name: item.Name, - Arguments: args, - }) - } - } - - finishReason := "stop" - if len(toolCalls) > 0 { - finishReason = "tool_calls" - } - if resp.Status == "incomplete" { - finishReason = "length" - } - - var usage *UsageInfo - if resp.Usage.TotalTokens > 0 { - usage = &UsageInfo{ - PromptTokens: int(resp.Usage.InputTokens), - CompletionTokens: int(resp.Usage.OutputTokens), - TotalTokens: int(resp.Usage.TotalTokens), - } - } - - return &LLMResponse{ - Content: content.String(), - ToolCalls: toolCalls, - FinishReason: finishReason, - Usage: usage, - } -} - func createCodexTokenSource() func() (string, string, error) { return func() (string, string, error) { cred, err := auth.GetCredential("openai") diff --git a/pkg/providers/codex_provider_test.go b/pkg/providers/codex_provider_test.go index 3a0da5e3b..ad5748e0c 100644 --- a/pkg/providers/codex_provider_test.go +++ b/pkg/providers/codex_provider_test.go @@ -10,6 +10,8 @@ import ( "github.com/openai/openai-go/v3" openaiopt "github.com/openai/openai-go/v3/option" "github.com/openai/openai-go/v3/responses" + + orc "github.com/sipeed/picoclaw/pkg/providers/openai_responses_common" ) func TestBuildCodexParams_BasicMessage(t *testing.T) { @@ -225,7 +227,7 @@ func TestParseCodexResponse_TextOutput(t *testing.T) { t.Fatalf("unmarshal: %v", err) } - result := parseCodexResponse(&resp) + result := orc.ParseResponseFromStruct(&resp) if result.Content != "Hello there!" { t.Errorf("Content = %q, want %q", result.Content, "Hello there!") } @@ -266,7 +268,7 @@ func TestParseCodexResponse_FunctionCall(t *testing.T) { t.Fatalf("unmarshal: %v", err) } - result := parseCodexResponse(&resp) + result := orc.ParseResponseFromStruct(&resp) if len(result.ToolCalls) != 1 { t.Fatalf("len(ToolCalls) = %d, want 1", len(result.ToolCalls)) } diff --git a/pkg/providers/openai_responses_common/responses_common.go b/pkg/providers/openai_responses_common/responses_common.go new file mode 100644 index 000000000..29133a51e --- /dev/null +++ b/pkg/providers/openai_responses_common/responses_common.go @@ -0,0 +1,291 @@ +// Package openai_responses_common provides shared utilities for providers +// that use the OpenAI Responses API (e.g., Azure, Codex). +package openai_responses_common + +import ( + "encoding/json" + "io" + "strings" + + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/responses" + + "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" +) + +// TranslateMessages converts internal Message entries to the OpenAI Responses API +// input format. System messages are extracted as instructions (returned separately), +// user/assistant/tool messages become ResponseInputItemUnionParam entries. +// Supports multipart media (images, audio). +func TranslateMessages(messages []protocoltypes.Message) (input responses.ResponseInputParam, instructions string) { + input = make(responses.ResponseInputParam, 0, len(messages)) + + for _, msg := range messages { + switch msg.Role { + case "system": + instructions = msg.Content + case "user": + if msg.ToolCallID != "" { + input = append(input, responses.ResponseInputItemUnionParam{ + OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ + CallID: msg.ToolCallID, + Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{ + OfString: openai.Opt(msg.Content), + }, + }, + }) + } else if len(msg.Media) > 0 { + content := BuildMultipartContent(msg.Content, msg.Media) + input = append(input, responses.ResponseInputItemUnionParam{ + OfInputMessage: &responses.ResponseInputItemMessageParam{ + Role: "user", + Content: content, + }, + }) + } else { + input = append(input, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleUser, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + case "assistant": + if len(msg.ToolCalls) > 0 { + if msg.Content != "" { + input = append(input, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleAssistant, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + for _, tc := range msg.ToolCalls { + name, args, ok := ResolveToolCall(tc) + if !ok { + continue + } + input = append(input, responses.ResponseInputItemUnionParam{ + OfFunctionCall: &responses.ResponseFunctionToolCallParam{ + CallID: tc.ID, + Name: name, + Arguments: args, + }, + }) + } + } else { + input = append(input, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleAssistant, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + case "tool": + input = append(input, responses.ResponseInputItemUnionParam{ + OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ + CallID: msg.ToolCallID, + Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{ + OfString: openai.Opt(msg.Content), + }, + }, + }) + } + } + + return input, instructions +} + +// BuildMultipartContent constructs a ResponseInputMessageContentListParam from +// text content and media URLs (data:image/... and data:audio/... URIs). +func BuildMultipartContent(text string, media []string) responses.ResponseInputMessageContentListParam { + parts := make(responses.ResponseInputMessageContentListParam, 0, 1+len(media)) + + if text != "" { + parts = append(parts, responses.ResponseInputContentUnionParam{ + OfInputText: &responses.ResponseInputTextParam{ + Text: text, + }, + }) + } + + for _, mediaURL := range media { + if strings.HasPrefix(mediaURL, "data:image/") { + parts = append(parts, responses.ResponseInputContentUnionParam{ + OfInputImage: &responses.ResponseInputImageParam{ + ImageURL: openai.Opt(mediaURL), + Detail: responses.ResponseInputImageDetailAuto, + }, + }) + } else if strings.HasPrefix(mediaURL, "data:audio/") { + if format, data, ok := ParseDataAudioURL(mediaURL); ok { + parts = append(parts, responses.ResponseInputContentUnionParam{ + OfInputFile: &responses.ResponseInputFileParam{ + FileData: openai.Opt(data), + Filename: openai.Opt("audio." + format), + }, + }) + } + } + } + + return parts +} + +// ParseDataAudioURL extracts the format and base64 data from a data:audio/... URL. +func ParseDataAudioURL(mediaURL string) (format, data string, ok bool) { + if !strings.HasPrefix(mediaURL, "data:audio/") { + return "", "", false + } + payload := strings.TrimPrefix(mediaURL, "data:audio/") + meta, data, found := strings.Cut(payload, ",") + if !found { + return "", "", false + } + format, _, _ = strings.Cut(meta, ";") + format = strings.TrimSpace(format) + data = strings.TrimSpace(data) + if format == "" || data == "" { + return "", "", false + } + return format, data, true +} + +// ResolveToolCall extracts the function name and JSON arguments string from a ToolCall. +// Returns ok=false if the tool call has no name or if arguments fail to marshal. +func ResolveToolCall(tc protocoltypes.ToolCall) (name string, arguments string, ok bool) { + name = tc.Name + if name == "" && tc.Function != nil { + name = tc.Function.Name + } + if name == "" { + return "", "", false + } + + if len(tc.Arguments) > 0 { + argsJSON, err := json.Marshal(tc.Arguments) + if err != nil { + return "", "", false + } + return name, string(argsJSON), true + } + + if tc.Function != nil && tc.Function.Arguments != "" { + return name, tc.Function.Arguments, true + } + + return name, "{}", true +} + +// TranslateTools converts internal ToolDefinition entries to the OpenAI Responses API +// tool format. If enableWebSearch is true, a web_search tool is appended and any +// user-defined tool named "web_search" is skipped to avoid duplicates. +func TranslateTools(tools []protocoltypes.ToolDefinition, enableWebSearch bool) []responses.ToolUnionParam { + capHint := len(tools) + if enableWebSearch { + capHint++ + } + result := make([]responses.ToolUnionParam, 0, capHint) + + for _, t := range tools { + if t.Type != "function" { + continue + } + if enableWebSearch && strings.EqualFold(t.Function.Name, "web_search") { + continue + } + ft := responses.FunctionToolParam{ + Name: t.Function.Name, + Parameters: t.Function.Parameters, + Strict: openai.Opt(false), + } + if t.Function.Description != "" { + ft.Description = openai.Opt(t.Function.Description) + } + result = append(result, responses.ToolUnionParam{OfFunction: &ft}) + } + + if enableWebSearch { + result = append(result, responses.ToolParamOfWebSearch(responses.WebSearchToolTypeWebSearch)) + } + + return result +} + +// ParseResponseBody parses an OpenAI Responses API JSON body into an LLMResponse. +// Handles output item types: "message" (output_text + refusal), "function_call", and "reasoning". +func ParseResponseBody(body io.Reader) (*protocoltypes.LLMResponse, error) { + var apiResp responses.Response + if err := json.NewDecoder(body).Decode(&apiResp); err != nil { + return nil, err + } + + return parseResponse(&apiResp), nil +} + +// ParseResponseFromStruct converts a decoded responses.Response into an LLMResponse. +// Used by providers that receive the Response struct directly (e.g., via streaming SDK). +func ParseResponseFromStruct(resp *responses.Response) *protocoltypes.LLMResponse { + return parseResponse(resp) +} + +// parseResponse is the shared implementation for extracting LLMResponse fields +// from a decoded responses.Response. +func parseResponse(apiResp *responses.Response) *protocoltypes.LLMResponse { + var content strings.Builder + var reasoningContent strings.Builder + var toolCalls []protocoltypes.ToolCall + + for _, item := range apiResp.Output { + switch item.Type { + case "message": + for _, c := range item.Content { + switch c.Type { + case "output_text": + content.WriteString(c.Text) + case "refusal": + content.WriteString(c.Refusal) + } + } + case "function_call": + var args map[string]any + if err := json.Unmarshal([]byte(item.Arguments), &args); err != nil { + args = map[string]any{"raw": item.Arguments} + } + toolCalls = append(toolCalls, protocoltypes.ToolCall{ + ID: item.CallID, + Name: item.Name, + Arguments: args, + }) + case "reasoning": + for _, s := range item.Summary { + reasoningContent.WriteString(s.Text) + } + } + } + + finishReason := "stop" + if len(toolCalls) > 0 { + finishReason = "tool_calls" + } + if apiResp.Status == "incomplete" { + finishReason = "length" + } + + var usage *protocoltypes.UsageInfo + if apiResp.Usage.TotalTokens > 0 { + usage = &protocoltypes.UsageInfo{ + PromptTokens: int(apiResp.Usage.InputTokens), + CompletionTokens: int(apiResp.Usage.OutputTokens), + TotalTokens: int(apiResp.Usage.TotalTokens), + } + } + + return &protocoltypes.LLMResponse{ + Content: content.String(), + ReasoningContent: reasoningContent.String(), + ToolCalls: toolCalls, + FinishReason: finishReason, + Usage: usage, + } +} diff --git a/pkg/providers/openai_responses_common/responses_common_test.go b/pkg/providers/openai_responses_common/responses_common_test.go new file mode 100644 index 000000000..be10e8427 --- /dev/null +++ b/pkg/providers/openai_responses_common/responses_common_test.go @@ -0,0 +1,593 @@ +package openai_responses_common + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" +) + +// --- TranslateMessages tests --- + +func TestTranslateMessages_SystemExtractedAsInstructions(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "system", Content: "You are helpful"}, + {Role: "user", Content: "Hi"}, + } + input, instructions := TranslateMessages(msgs) + if instructions != "You are helpful" { + t.Errorf("instructions = %q, want %q", instructions, "You are helpful") + } + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfMessage == nil { + t.Fatal("expected user message item") + } +} + +func TestTranslateMessages_UserTextMessage(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "user", Content: "Hello"}, + } + input, instructions := TranslateMessages(msgs) + if instructions != "" { + t.Errorf("instructions = %q, want empty", instructions) + } + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfMessage == nil { + t.Fatal("expected EasyInputMessage") + } + if string(input[0].OfMessage.Role) != "user" { + t.Errorf("role = %q, want %q", input[0].OfMessage.Role, "user") + } +} + +func TestTranslateMessages_UserWithToolCallID(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "user", Content: `{"temp":72}`, ToolCallID: "call_1"}, + } + input, _ := TranslateMessages(msgs) + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfFunctionCallOutput == nil { + t.Fatal("expected FunctionCallOutput for user with ToolCallID") + } + if input[0].OfFunctionCallOutput.CallID != "call_1" { + t.Errorf("CallID = %q, want %q", input[0].OfFunctionCallOutput.CallID, "call_1") + } +} + +func TestTranslateMessages_UserWithMedia(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "user", Content: "Describe this", Media: []string{"data:image/png;base64,abc123"}}, + } + input, _ := TranslateMessages(msgs) + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfInputMessage == nil { + t.Fatal("expected InputMessage for multipart content") + } + if input[0].OfInputMessage.Role != "user" { + t.Errorf("role = %q, want %q", input[0].OfInputMessage.Role, "user") + } +} + +func TestTranslateMessages_AssistantWithToolCalls(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "user", Content: "Weather?"}, + { + Role: "assistant", + Content: "Let me check", + ToolCalls: []protocoltypes.ToolCall{ + {ID: "call_1", Name: "get_weather", Arguments: map[string]any{"city": "SF"}}, + }, + }, + {Role: "tool", Content: `{"temp":72}`, ToolCallID: "call_1"}, + } + input, _ := TranslateMessages(msgs) + // user + assistant text + function_call + tool output = 4 items + if len(input) != 4 { + t.Fatalf("len(input) = %d, want 4", len(input)) + } + // item[1] = assistant text + if input[1].OfMessage == nil { + t.Fatal("expected assistant text message") + } + // item[2] = function call + if input[2].OfFunctionCall == nil { + t.Fatal("expected function call") + } + if input[2].OfFunctionCall.Name != "get_weather" { + t.Errorf("function name = %q, want %q", input[2].OfFunctionCall.Name, "get_weather") + } + // item[3] = tool output + if input[3].OfFunctionCallOutput == nil { + t.Fatal("expected function call output") + } +} + +func TestTranslateMessages_AssistantWithoutToolCalls(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "assistant", Content: "Sure thing"}, + } + input, _ := TranslateMessages(msgs) + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfMessage == nil { + t.Fatal("expected EasyInputMessage for assistant without tool calls") + } +} + +func TestTranslateMessages_ToolMessage(t *testing.T) { + msgs := []protocoltypes.Message{ + {Role: "tool", Content: "result data", ToolCallID: "call_99"}, + } + input, _ := TranslateMessages(msgs) + if len(input) != 1 { + t.Fatalf("len(input) = %d, want 1", len(input)) + } + if input[0].OfFunctionCallOutput == nil { + t.Fatal("expected FunctionCallOutput") + } + if input[0].OfFunctionCallOutput.CallID != "call_99" { + t.Errorf("CallID = %q, want %q", input[0].OfFunctionCallOutput.CallID, "call_99") + } +} + +// --- ResolveToolCall tests --- + +func TestResolveToolCall_FromNameAndArguments(t *testing.T) { + tc := protocoltypes.ToolCall{ + Name: "get_weather", + Arguments: map[string]any{"city": "SF"}, + } + name, args, ok := ResolveToolCall(tc) + if !ok { + t.Fatal("expected ok=true") + } + if name != "get_weather" { + t.Errorf("name = %q, want %q", name, "get_weather") + } + if !strings.Contains(args, "SF") { + t.Errorf("args = %q, want to contain SF", args) + } +} + +func TestResolveToolCall_FromFunctionField(t *testing.T) { + tc := protocoltypes.ToolCall{ + ID: "call_1", + Function: &protocoltypes.FunctionCall{ + Name: "read_file", + Arguments: `{"path":"README.md"}`, + }, + } + name, args, ok := ResolveToolCall(tc) + if !ok { + t.Fatal("expected ok=true") + } + if name != "read_file" { + t.Errorf("name = %q, want %q", name, "read_file") + } + if args != `{"path":"README.md"}` { + t.Errorf("args = %q, want %q", args, `{"path":"README.md"}`) + } +} + +func TestResolveToolCall_EmptyName(t *testing.T) { + tc := protocoltypes.ToolCall{} + _, _, ok := ResolveToolCall(tc) + if ok { + t.Error("expected ok=false for empty tool call") + } +} + +func TestResolveToolCall_NoArgsFallsBackToEmptyObject(t *testing.T) { + tc := protocoltypes.ToolCall{Name: "do_something"} + name, args, ok := ResolveToolCall(tc) + if !ok { + t.Fatal("expected ok=true") + } + if name != "do_something" { + t.Errorf("name = %q, want %q", name, "do_something") + } + if args != "{}" { + t.Errorf("args = %q, want %q", args, "{}") + } +} + +// --- TranslateTools tests --- + +func TestTranslateTools_FunctionTools(t *testing.T) { + tools := []protocoltypes.ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "get_weather", + Description: "Get weather", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + result := TranslateTools(tools, false) + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + if result[0].OfFunction == nil { + t.Fatal("expected function tool") + } + if result[0].OfFunction.Name != "get_weather" { + t.Errorf("name = %q, want %q", result[0].OfFunction.Name, "get_weather") + } +} + +func TestTranslateTools_SkipsNonFunction(t *testing.T) { + tools := []protocoltypes.ToolDefinition{ + {Type: "not_function"}, + } + result := TranslateTools(tools, false) + if len(result) != 0 { + t.Errorf("len(result) = %d, want 0", len(result)) + } +} + +func TestTranslateTools_WebSearchAppended(t *testing.T) { + result := TranslateTools(nil, true) + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + if result[0].OfWebSearch == nil { + t.Fatal("expected web_search tool") + } +} + +func TestTranslateTools_WebSearchReplacesUserDefined(t *testing.T) { + tools := []protocoltypes.ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "web_search", + Parameters: map[string]any{"type": "object"}, + }, + }, + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "read_file", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + result := TranslateTools(tools, true) + if len(result) != 2 { + t.Fatalf("len(result) = %d, want 2", len(result)) + } + if result[0].OfFunction == nil || result[0].OfFunction.Name != "read_file" { + t.Errorf("first tool should be read_file, got %v", result[0]) + } + if result[1].OfWebSearch == nil { + t.Error("second tool should be web_search") + } +} + +func TestTranslateTools_DescriptionOmittedWhenEmpty(t *testing.T) { + tools := []protocoltypes.ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "no_desc", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + result := TranslateTools(tools, false) + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + if result[0].OfFunction.Description.Valid() { + t.Error("Description should not be set when empty") + } +} + +// --- ParseResponseBody tests --- + +func TestParseResponseBody_TextOutput(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_123", + "object": "response", + "status": "completed", + "output": [ + { + "type": "message", + "content": [{"type": "output_text", "text": "Hello!"}] + } + ], + "usage": { + "input_tokens": 10, + "output_tokens": 5, + "total_tokens": 15, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0} + } + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("ParseResponseBody error: %v", err) + } + if result.Content != "Hello!" { + t.Errorf("Content = %q, want %q", result.Content, "Hello!") + } + if result.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "stop") + } + if result.Usage.TotalTokens != 15 { + t.Errorf("TotalTokens = %d, want 15", result.Usage.TotalTokens) + } +} + +func TestParseResponseBody_FunctionCall(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_456", + "object": "response", + "status": "completed", + "output": [ + { + "type": "function_call", + "call_id": "call_abc", + "name": "get_weather", + "arguments": "{\"city\":\"SF\"}" + } + ], + "usage": { + "input_tokens": 10, + "output_tokens": 8, + "total_tokens": 18, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0} + } + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("ParseResponseBody error: %v", err) + } + if len(result.ToolCalls) != 1 { + t.Fatalf("len(ToolCalls) = %d, want 1", len(result.ToolCalls)) + } + if result.ToolCalls[0].Name != "get_weather" { + t.Errorf("Name = %q, want %q", result.ToolCalls[0].Name, "get_weather") + } + if result.ToolCalls[0].ID != "call_abc" { + t.Errorf("ID = %q, want %q", result.ToolCalls[0].ID, "call_abc") + } + if result.FinishReason != "tool_calls" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "tool_calls") + } +} + +func TestParseResponseBody_Reasoning(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_789", + "object": "response", + "status": "completed", + "output": [ + { + "type": "reasoning", + "id": "rs_1", + "summary": [{"type": "summary_text", "text": "Thinking about it..."}] + }, + { + "type": "message", + "content": [{"type": "output_text", "text": "The answer is 42."}] + } + ], + "usage": { + "input_tokens": 10, + "output_tokens": 20, + "total_tokens": 30, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 10} + } + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("ParseResponseBody error: %v", err) + } + if result.Content != "The answer is 42." { + t.Errorf("Content = %q, want %q", result.Content, "The answer is 42.") + } + if result.ReasoningContent != "Thinking about it..." { + t.Errorf("ReasoningContent = %q, want %q", result.ReasoningContent, "Thinking about it...") + } +} + +func TestParseResponseBody_Refusal(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_ref", + "object": "response", + "status": "completed", + "output": [ + { + "type": "message", + "content": [{"type": "refusal", "refusal": "I cannot help with that."}] + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 5, + "total_tokens": 10, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0} + } + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("ParseResponseBody error: %v", err) + } + if result.Content != "I cannot help with that." { + t.Errorf("Content = %q, want %q", result.Content, "I cannot help with that.") + } +} + +func TestParseResponseBody_IncompleteStatus(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_inc", + "object": "response", + "status": "incomplete", + "output": [ + { + "type": "message", + "content": [{"type": "output_text", "text": "partial"}] + } + ], + "usage": {"input_tokens": 5, "output_tokens": 2, "total_tokens": 7, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0}} + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("error: %v", err) + } + if result.FinishReason != "length" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "length") + } +} + +func TestParseResponseBody_FailedStatus(t *testing.T) { + body := strings.NewReader(`{ + "id": "resp_fail", + "object": "response", + "status": "failed", + "output": [], + "usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0}} + }`) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("error: %v", err) + } + // failed/canceled statuses are not specially mapped; they fall through to "stop" + if result.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "stop") + } +} + +// --- ParseDataAudioURL tests --- + +func TestParseDataAudioURL_Valid(t *testing.T) { + format, data, ok := ParseDataAudioURL("data:audio/mp3;base64,SGVsbG8=") + if !ok { + t.Fatal("expected ok=true") + } + if format != "mp3" { + t.Errorf("format = %q, want %q", format, "mp3") + } + if data != "SGVsbG8=" { + t.Errorf("data = %q, want %q", data, "SGVsbG8=") + } +} + +func TestParseDataAudioURL_NotAudio(t *testing.T) { + _, _, ok := ParseDataAudioURL("data:image/png;base64,abc") + if ok { + t.Error("expected ok=false for non-audio URL") + } +} + +func TestParseDataAudioURL_MalformedNoComma(t *testing.T) { + _, _, ok := ParseDataAudioURL("data:audio/mp3;base64") + if ok { + t.Error("expected ok=false for malformed URL") + } +} + +func TestParseDataAudioURL_EmptyData(t *testing.T) { + _, _, ok := ParseDataAudioURL("data:audio/mp3;base64,") + if ok { + t.Error("expected ok=false for empty data") + } +} + +// --- BuildMultipartContent tests --- + +func TestBuildMultipartContent_TextOnly(t *testing.T) { + parts := BuildMultipartContent("hello", nil) + if len(parts) != 1 { + t.Fatalf("len(parts) = %d, want 1", len(parts)) + } + if parts[0].OfInputText == nil { + t.Fatal("expected text part") + } +} + +func TestBuildMultipartContent_TextAndImage(t *testing.T) { + parts := BuildMultipartContent("describe", []string{"data:image/png;base64,abc"}) + if len(parts) != 2 { + t.Fatalf("len(parts) = %d, want 2", len(parts)) + } + if parts[0].OfInputText == nil { + t.Error("first part should be text") + } + if parts[1].OfInputImage == nil { + t.Error("second part should be image") + } +} + +func TestBuildMultipartContent_AudioFile(t *testing.T) { + parts := BuildMultipartContent("", []string{"data:audio/wav;base64,AAAA"}) + if len(parts) != 1 { + t.Fatalf("len(parts) = %d, want 1", len(parts)) + } + if parts[0].OfInputFile == nil { + t.Fatal("expected file part for audio") + } +} + +func TestBuildMultipartContent_EmptyTextSkipped(t *testing.T) { + parts := BuildMultipartContent("", []string{"data:image/png;base64,abc"}) + if len(parts) != 1 { + t.Fatalf("len(parts) = %d, want 1", len(parts)) + } + if parts[0].OfInputImage == nil { + t.Error("should only have image part") + } +} + +// --- JSON serialization sanity checks --- + +func TestTranslateTools_SerializesToJSON(t *testing.T) { + tools := []protocoltypes.ToolDefinition{ + { + Type: "function", + Function: protocoltypes.ToolFunctionDefinition{ + Name: "test_tool", + Description: "A test", + Parameters: map[string]any{"type": "object"}, + }, + }, + } + result := TranslateTools(tools, true) + data, err := json.Marshal(result) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + s := string(data) + if !strings.Contains(s, "test_tool") { + t.Errorf("JSON should contain test_tool, got: %s", s) + } + if !strings.Contains(s, "web_search") { + t.Errorf("JSON should contain web_search, got: %s", s) + } +} From e23eda53655c43998b14a1658f2fec709e61d2f6 Mon Sep 17 00:00:00 2001 From: Kunal Karmakar Date: Sat, 28 Mar 2026 13:33:48 +0000 Subject: [PATCH 22/55] Update tests and error cases handling --- pkg/providers/azure/provider.go | 3 +- pkg/providers/azure/provider_test.go | 37 +++++++++++ .../responses_common.go | 7 +- .../responses_common_test.go | 64 +++++++++++++------ 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/pkg/providers/azure/provider.go b/pkg/providers/azure/provider.go index 9d29a90cd..429b26798 100644 --- a/pkg/providers/azure/provider.go +++ b/pkg/providers/azure/provider.go @@ -26,6 +26,7 @@ type ( const ( defaultRequestTimeout = common.DefaultRequestTimeout + responsesAPIPath = "openai/v1/responses" ) // Provider implements the LLM provider interface for Azure OpenAI endpoints. @@ -87,7 +88,7 @@ func (p *Provider) Chat( return nil, fmt.Errorf("Azure API base not configured") } - requestURL, err := url.JoinPath(p.apiBase, "openai/v1/responses") + requestURL, err := url.JoinPath(p.apiBase, responsesAPIPath) if err != nil { return nil, fmt.Errorf("failed to build Azure request URL: %w", err) } diff --git a/pkg/providers/azure/provider_test.go b/pkg/providers/azure/provider_test.go index e57d68057..b3752ea50 100644 --- a/pkg/providers/azure/provider_test.go +++ b/pkg/providers/azure/provider_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -167,6 +168,42 @@ func TestProviderChat_AzureHTTPError(t *testing.T) { } } +func TestProviderChat_AzureRateLimitError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusTooManyRequests) + w.Write([]byte(`{"error":{"message":"Rate limit exceeded","type":"rate_limit_error"}}`)) + })) + defer server.Close() + + p := NewProvider("test-key", server.URL, "") + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "deployment", nil) + if err == nil { + t.Fatal("expected error for 429, got nil") + } + if !strings.Contains(err.Error(), "429") { + t.Errorf("error should contain status code 429, got: %v", err) + } +} + +func TestProviderChat_AzureServerError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"error":{"message":"Internal server error","type":"server_error"}}`)) + })) + defer server.Close() + + p := NewProvider("test-key", server.URL, "") + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "deployment", nil) + if err == nil { + t.Fatal("expected error for 500, got nil") + } + if !strings.Contains(err.Error(), "500") { + t.Errorf("error should contain status code 500, got: %v", err) + } +} + func TestProviderChat_AzureParseTextOutput(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := map[string]any{ diff --git a/pkg/providers/openai_responses_common/responses_common.go b/pkg/providers/openai_responses_common/responses_common.go index 29133a51e..839471f69 100644 --- a/pkg/providers/openai_responses_common/responses_common.go +++ b/pkg/providers/openai_responses_common/responses_common.go @@ -268,8 +268,13 @@ func parseResponse(apiResp *responses.Response) *protocoltypes.LLMResponse { if len(toolCalls) > 0 { finishReason = "tool_calls" } - if apiResp.Status == "incomplete" { + switch apiResp.Status { + case responses.ResponseStatusIncomplete: finishReason = "length" + case responses.ResponseStatusFailed: + finishReason = "error" + case responses.ResponseStatusCancelled: + finishReason = "canceled" } var usage *protocoltypes.UsageInfo diff --git a/pkg/providers/openai_responses_common/responses_common_test.go b/pkg/providers/openai_responses_common/responses_common_test.go index be10e8427..0d41190b1 100644 --- a/pkg/providers/openai_responses_common/responses_common_test.go +++ b/pkg/providers/openai_responses_common/responses_common_test.go @@ -2,9 +2,12 @@ package openai_responses_common import ( "encoding/json" + "fmt" "strings" "testing" + "github.com/openai/openai-go/v3/responses" + "github.com/sipeed/picoclaw/pkg/providers/protocoltypes" ) @@ -298,10 +301,10 @@ func TestTranslateTools_DescriptionOmittedWhenEmpty(t *testing.T) { // --- ParseResponseBody tests --- func TestParseResponseBody_TextOutput(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_123", "object": "response", - "status": "completed", + "status": "%s", "output": [ { "type": "message", @@ -315,7 +318,7 @@ func TestParseResponseBody_TextOutput(t *testing.T) { "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 0} } - }`) + }`, string(responses.ResponseStatusCompleted))) result, err := ParseResponseBody(body) if err != nil { @@ -333,10 +336,10 @@ func TestParseResponseBody_TextOutput(t *testing.T) { } func TestParseResponseBody_FunctionCall(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_456", "object": "response", - "status": "completed", + "status": "%s", "output": [ { "type": "function_call", @@ -352,7 +355,7 @@ func TestParseResponseBody_FunctionCall(t *testing.T) { "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 0} } - }`) + }`, string(responses.ResponseStatusCompleted))) result, err := ParseResponseBody(body) if err != nil { @@ -373,10 +376,10 @@ func TestParseResponseBody_FunctionCall(t *testing.T) { } func TestParseResponseBody_Reasoning(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_789", "object": "response", - "status": "completed", + "status": "%s", "output": [ { "type": "reasoning", @@ -395,7 +398,7 @@ func TestParseResponseBody_Reasoning(t *testing.T) { "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 10} } - }`) + }`, string(responses.ResponseStatusCompleted))) result, err := ParseResponseBody(body) if err != nil { @@ -410,10 +413,10 @@ func TestParseResponseBody_Reasoning(t *testing.T) { } func TestParseResponseBody_Refusal(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_ref", "object": "response", - "status": "completed", + "status": "%s", "output": [ { "type": "message", @@ -427,7 +430,7 @@ func TestParseResponseBody_Refusal(t *testing.T) { "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 0} } - }`) + }`, string(responses.ResponseStatusCompleted))) result, err := ParseResponseBody(body) if err != nil { @@ -439,10 +442,10 @@ func TestParseResponseBody_Refusal(t *testing.T) { } func TestParseResponseBody_IncompleteStatus(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_inc", "object": "response", - "status": "incomplete", + "status": "%s", "output": [ { "type": "message", @@ -452,7 +455,7 @@ func TestParseResponseBody_IncompleteStatus(t *testing.T) { "usage": {"input_tokens": 5, "output_tokens": 2, "total_tokens": 7, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 0}} - }`) + }`, string(responses.ResponseStatusIncomplete))) result, err := ParseResponseBody(body) if err != nil { @@ -464,23 +467,42 @@ func TestParseResponseBody_IncompleteStatus(t *testing.T) { } func TestParseResponseBody_FailedStatus(t *testing.T) { - body := strings.NewReader(`{ + body := strings.NewReader(fmt.Sprintf(`{ "id": "resp_fail", "object": "response", - "status": "failed", + "status": "%s", "output": [], "usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 0}} - }`) + }`, string(responses.ResponseStatusFailed))) result, err := ParseResponseBody(body) if err != nil { t.Fatalf("error: %v", err) } - // failed/canceled statuses are not specially mapped; they fall through to "stop" - if result.FinishReason != "stop" { - t.Errorf("FinishReason = %q, want %q", result.FinishReason, "stop") + if result.FinishReason != "error" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "error") + } +} + +func TestParseResponseBody_CanceledStatus(t *testing.T) { + body := strings.NewReader(fmt.Sprintf(`{ + "id": "resp_cancel", + "object": "response", + "status": "%s", + "output": [], + "usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0}} + }`, string(responses.ResponseStatusCancelled))) + + result, err := ParseResponseBody(body) + if err != nil { + t.Fatalf("error: %v", err) + } + if result.FinishReason != "canceled" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "canceled") } } From 27f638e909a2f91245f34deab72838f1b78471dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=86=E6=9C=88?= <2835601846@qq.com> Date: Sat, 28 Mar 2026 22:13:50 +0800 Subject: [PATCH 23/55] fix: unified restart required (#1978) Unified restart-required detection and notification mechanism so that model, tool, and configuration changes all follow the same signature-based comparison logic. --- web/backend/api/gateway.go | 118 +++++++++-- web/backend/api/gateway_test.go | 185 ++++++++++++++++++ .../src/components/config/config-page.tsx | 2 + .../src/components/config/raw-config-page.tsx | 2 + .../src/components/tools/tools-page.tsx | 2 + web/frontend/src/i18n/locales/en.json | 2 +- web/frontend/src/i18n/locales/zh.json | 2 +- 7 files changed, 295 insertions(+), 18 deletions(-) diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index 808475cb6..2621b722b 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -25,13 +25,14 @@ import ( // gateway holds the state for the managed gateway process. var gateway = struct { - mu sync.Mutex - cmd *exec.Cmd - owned bool // true if we started the process, false if we attached to an existing one - bootDefaultModel string - runtimeStatus string - startupDeadline time.Time - logs *LogBuffer + mu sync.Mutex + cmd *exec.Cmd + owned bool // true if we started the process, false if we attached to an existing one + bootDefaultModel string + bootConfigSignature string + runtimeStatus string + startupDeadline time.Time + logs *LogBuffer }{ runtimeStatus: "stopped", logs: NewLogBuffer(200), @@ -177,14 +178,93 @@ func lookupModelConfig(cfg *config.Config, modelName string) *config.ModelConfig return modelCfg } -func gatewayRestartRequired(configDefaultModel, bootDefaultModel, gatewayStatus string) bool { +func computeConfigSignature(cfg *config.Config) string { + if cfg == nil { + return "" + } + var parts []string + defaultModel := strings.TrimSpace(cfg.Agents.Defaults.GetModelName()) + if defaultModel != "" { + parts = append(parts, "model:"+defaultModel) + } + toolSignatures := []string{} + if cfg.Tools.ReadFile.Enabled { + toolSignatures = append(toolSignatures, "read_file") + } + if cfg.Tools.WriteFile.Enabled { + toolSignatures = append(toolSignatures, "write_file") + } + if cfg.Tools.ListDir.Enabled { + toolSignatures = append(toolSignatures, "list_dir") + } + if cfg.Tools.EditFile.Enabled { + toolSignatures = append(toolSignatures, "edit_file") + } + if cfg.Tools.AppendFile.Enabled { + toolSignatures = append(toolSignatures, "append_file") + } + if cfg.Tools.Exec.Enabled { + toolSignatures = append(toolSignatures, "exec") + } + if cfg.Tools.Cron.Enabled { + toolSignatures = append(toolSignatures, "cron") + } + if cfg.Tools.Web.Enabled { + toolSignatures = append(toolSignatures, "web") + } + if cfg.Tools.WebFetch.Enabled { + toolSignatures = append(toolSignatures, "web_fetch") + } + if cfg.Tools.Message.Enabled { + toolSignatures = append(toolSignatures, "message") + } + if cfg.Tools.SendFile.Enabled { + toolSignatures = append(toolSignatures, "send_file") + } + if cfg.Tools.FindSkills.Enabled { + toolSignatures = append(toolSignatures, "find_skills") + } + if cfg.Tools.InstallSkill.Enabled { + toolSignatures = append(toolSignatures, "install_skill") + } + if cfg.Tools.Spawn.Enabled { + toolSignatures = append(toolSignatures, "spawn") + } + if cfg.Tools.SpawnStatus.Enabled { + toolSignatures = append(toolSignatures, "spawn_status") + } + if cfg.Tools.I2C.Enabled { + toolSignatures = append(toolSignatures, "i2c") + } + if cfg.Tools.SPI.Enabled { + toolSignatures = append(toolSignatures, "spi") + } + if cfg.Tools.MCP.Enabled { + toolSignatures = append(toolSignatures, "mcp") + } + if cfg.Tools.MCP.Discovery.Enabled { + toolSignatures = append(toolSignatures, "mcp_discovery") + } + if cfg.Tools.MCP.Discovery.UseRegex { + toolSignatures = append(toolSignatures, "mcp_discovery_regex") + } + if cfg.Tools.MCP.Discovery.UseBM25 { + toolSignatures = append(toolSignatures, "mcp_discovery_bm25") + } + if len(toolSignatures) > 0 { + parts = append(parts, "tools:"+strings.Join(toolSignatures, ",")) + } + return strings.Join(parts, ";") +} + +func gatewayRestartRequiredBySignature(bootSignature, currentSignature, gatewayStatus string) bool { if gatewayStatus != "running" { return false } - if strings.TrimSpace(configDefaultModel) == "" || strings.TrimSpace(bootDefaultModel) == "" { + if bootSignature == "" || currentSignature == "" { return false } - return configDefaultModel != bootDefaultModel + return bootSignature != currentSignature } func isCmdProcessAliveLocked(cmd *exec.Cmd) bool { @@ -228,10 +308,11 @@ func attachToGatewayProcessLocked(pid int, cfg *config.Config) error { gateway.owned = false // We didn't start this process setGatewayRuntimeStatusLocked("running") - // Update bootDefaultModel from config + // Update bootDefaultModel and bootConfigSignature from config if cfg != nil { defaultModelName := strings.TrimSpace(cfg.Agents.Defaults.GetModelName()) gateway.bootDefaultModel = defaultModelName + gateway.bootConfigSignature = computeConfigSignature(cfg) } logger.InfoC("gateway", fmt.Sprintf("Attached to gateway process (PID: %d)", pid)) @@ -419,6 +500,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int gateway.cmd = cmd gateway.owned = true // We started this process gateway.bootDefaultModel = defaultModelName + gateway.bootConfigSignature = computeConfigSignature(cfg) setGatewayRuntimeStatusLocked(initialStatus) pid = cmd.Process.Pid logger.InfoC("gateway", fmt.Sprintf("Started picoclaw gateway (PID: %d) from %s", pid, execPath)) @@ -439,6 +521,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int if gateway.cmd == cmd { gateway.cmd = nil gateway.bootDefaultModel = "" + gateway.bootConfigSignature = "" if gateway.runtimeStatus != "restarting" { setGatewayRuntimeStatusLocked("stopped") } @@ -713,7 +796,7 @@ func (h *Handler) handleGatewayStatus(w http.ResponseWriter, r *http.Request) { func (h *Handler) gatewayStatusData() map[string]any { data := map[string]any{} - configDefaultModel := "" + var configDefaultModel string cfg, cfgErr := config.LoadConfig(h.configPath) if cfgErr == nil && cfg != nil { configDefaultModel = strings.TrimSpace(cfg.Agents.Defaults.GetModelName()) @@ -784,11 +867,14 @@ func (h *Handler) gatewayStatusData() map[string]any { } } - bootDefaultModel, _ := data["boot_default_model"].(string) gatewayStatus, _ := data["gateway_status"].(string) - data["gateway_restart_required"] = gatewayRestartRequired( - configDefaultModel, - bootDefaultModel, + currentConfigSignature := computeConfigSignature(cfg) + gateway.mu.Lock() + bootConfigSignature := gateway.bootConfigSignature + gateway.mu.Unlock() + data["gateway_restart_required"] = gatewayRestartRequiredBySignature( + bootConfigSignature, + currentConfigSignature, gatewayStatus, ) diff --git a/web/backend/api/gateway_test.go b/web/backend/api/gateway_test.go index a5ba2bad2..42f0ab66c 100644 --- a/web/backend/api/gateway_test.go +++ b/web/backend/api/gateway_test.go @@ -77,6 +77,7 @@ func resetGatewayTestState(t *testing.T) { gateway.mu.Lock() gateway.cmd = nil gateway.bootDefaultModel = "" + gateway.bootConfigSignature = "" setGatewayRuntimeStatusLocked("stopped") gateway.mu.Unlock() }) @@ -502,9 +503,11 @@ func TestGatewayStatusRequiresRestartAfterDefaultModelChange(t *testing.T) { t.Fatalf("FindProcess() error = %v", err) } + bootSignature := computeConfigSignature(cfg) gateway.mu.Lock() gateway.cmd = &exec.Cmd{Process: process} gateway.bootDefaultModel = cfg.ModelList[0].ModelName + gateway.bootConfigSignature = bootSignature setGatewayRuntimeStatusLocked("running") gateway.mu.Unlock() @@ -548,6 +551,188 @@ func TestGatewayStatusRequiresRestartAfterDefaultModelChange(t *testing.T) { } } +func TestGatewayStatusRequiresRestartAfterToolChange(t *testing.T) { + resetGatewayTestState(t) + + configPath := filepath.Join(t.TempDir(), "config.json") + cfg := config.DefaultConfig() + cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName + cfg.ModelList[0].SetAPIKey("test-key") + cfg.Tools.WriteFile.Enabled = true + if err := config.SaveConfig(configPath, cfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + process, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess() error = %v", err) + } + + bootSignature := computeConfigSignature(cfg) + gateway.mu.Lock() + gateway.cmd = &exec.Cmd{Process: process} + gateway.bootDefaultModel = cfg.ModelList[0].ModelName + gateway.bootConfigSignature = bootSignature + setGatewayRuntimeStatusLocked("running") + gateway.mu.Unlock() + + updatedCfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + updatedCfg.Tools.WriteFile.Enabled = false + if err := config.SaveConfig(configPath, updatedCfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + gatewayHealthGet = func(string, time.Duration) (*http.Response, error) { + return mockGatewayHealthResponse(http.StatusOK, os.Getpid()), nil + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil) + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + + if got := body["gateway_status"]; got != "running" { + t.Fatalf("gateway_status = %#v, want %q", got, "running") + } + if got := body["gateway_restart_required"]; got != true { + t.Fatalf("gateway_restart_required = %#v, want true", got) + } +} + +func TestGatewayStatusNoRestartRequiredForNonSensitiveChanges(t *testing.T) { + resetGatewayTestState(t) + + configPath := filepath.Join(t.TempDir(), "config.json") + cfg := config.DefaultConfig() + cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName + cfg.ModelList[0].SetAPIKey("test-key") + cfg.Agents.Defaults.MaxTokens = 1000 + if err := config.SaveConfig(configPath, cfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + process, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess() error = %v", err) + } + + bootSignature := computeConfigSignature(cfg) + gateway.mu.Lock() + gateway.cmd = &exec.Cmd{Process: process} + gateway.bootDefaultModel = cfg.ModelList[0].ModelName + gateway.bootConfigSignature = bootSignature + setGatewayRuntimeStatusLocked("running") + gateway.mu.Unlock() + + updatedCfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + updatedCfg.Agents.Defaults.MaxTokens = 2000 + if err := config.SaveConfig(configPath, updatedCfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + gatewayHealthGet = func(string, time.Duration) (*http.Response, error) { + return mockGatewayHealthResponse(http.StatusOK, os.Getpid()), nil + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil) + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + + if got := body["gateway_status"]; got != "running" { + t.Fatalf("gateway_status = %#v, want %q", got, "running") + } + if got := body["gateway_restart_required"]; got != false { + t.Fatalf("gateway_restart_required = %#v, want false", got) + } +} + +func TestGatewayStatusNoRestartRequiredWhenNotRunning(t *testing.T) { + resetGatewayTestState(t) + + configPath := filepath.Join(t.TempDir(), "config.json") + cfg := config.DefaultConfig() + cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName + cfg.ModelList[0].SetAPIKey("test-key") + if err := config.SaveConfig(configPath, cfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + gateway.mu.Lock() + gateway.cmd = nil + gateway.bootDefaultModel = "" + gateway.bootConfigSignature = "" + setGatewayRuntimeStatusLocked("stopped") + gateway.mu.Unlock() + + updatedCfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + updatedCfg.Agents.Defaults.ModelName = "different-model" + if err := config.SaveConfig(configPath, updatedCfg); err != nil { + t.Fatalf("SaveConfig() error = %v", err) + } + + gatewayHealthGet = func(string, time.Duration) (*http.Response, error) { + return nil, errors.New("no gateway running") + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil) + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + + if got := body["gateway_status"]; got != "stopped" { + t.Fatalf("gateway_status = %#v, want %q", got, "stopped") + } + if got := body["gateway_restart_required"]; got != false { + t.Fatalf("gateway_restart_required = %#v, want false", got) + } +} + func TestGatewayStatusReturnsErrorAfterStartupWindowExpires(t *testing.T) { resetGatewayTestState(t) diff --git a/web/frontend/src/components/config/config-page.tsx b/web/frontend/src/components/config/config-page.tsx index 46f62f426..664e75440 100644 --- a/web/frontend/src/components/config/config-page.tsx +++ b/web/frontend/src/components/config/config-page.tsx @@ -32,6 +32,7 @@ import { } from "@/components/config/form-model" import { PageHeader } from "@/components/page-header" import { Button } from "@/components/ui/button" +import { refreshGatewayState } from "@/store/gateway" export function ConfigPage() { const { t } = useTranslation() @@ -281,6 +282,7 @@ export function ConfigPage() { } toast.success(t("pages.config.save_success")) + void refreshGatewayState({ force: true }) } catch (err) { toast.error( err instanceof Error ? err.message : t("pages.config.save_error"), diff --git a/web/frontend/src/components/config/raw-config-page.tsx b/web/frontend/src/components/config/raw-config-page.tsx index e40cc7301..56a922fe6 100644 --- a/web/frontend/src/components/config/raw-config-page.tsx +++ b/web/frontend/src/components/config/raw-config-page.tsx @@ -19,6 +19,7 @@ import { } from "@/components/ui/alert-dialog" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" +import { refreshGatewayState } from "@/store/gateway" export function RawConfigPage() { const { t } = useTranslation() @@ -56,6 +57,7 @@ export function RawConfigPage() { } catch { queryClient.invalidateQueries({ queryKey: ["config"] }) } + void refreshGatewayState({ force: true }) }, onError: () => { toast.error(t("pages.config.save_error")) diff --git a/web/frontend/src/components/tools/tools-page.tsx b/web/frontend/src/components/tools/tools-page.tsx index 05aa42122..6a521a565 100644 --- a/web/frontend/src/components/tools/tools-page.tsx +++ b/web/frontend/src/components/tools/tools-page.tsx @@ -14,6 +14,7 @@ import { CardTitle, } from "@/components/ui/card" import { cn } from "@/lib/utils" +import { refreshGatewayState } from "@/store/gateway" export function ToolsPage() { const { t } = useTranslation() @@ -33,6 +34,7 @@ export function ToolsPage() { : t("pages.agent.tools.disable_success"), ) void queryClient.invalidateQueries({ queryKey: ["tools"] }) + void refreshGatewayState({ force: true }) }, onError: (err) => { toast.error( diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index a7c60f893..25608fe93 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -66,7 +66,7 @@ "restarting": "Restarting Gateway...", "stopping": "Stopping Gateway..." }, - "restartRequired": "Model changes require a gateway restart to take effect." + "restartRequired": "Configuration changes require a gateway restart to take effect." } }, "common": { diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index 9dde090f8..346822407 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -66,7 +66,7 @@ "restarting": "服务重启中...", "stopping": "服务停止中..." }, - "restartRequired": "切换默认模型后需要重启服务才能生效。" + "restartRequired": "配置变更后需要重启服务才能生效。" } }, "common": { From 43095543ab28d45b3fd48112caa42dec95001dfb Mon Sep 17 00:00:00 2001 From: imalasong Date: Sat, 28 Mar 2026 23:36:49 +0800 Subject: [PATCH 24/55] fix(feishu): skip empty random_reaction_emoji entries Feishu returns 231001 when emoji_type is empty. Config slices like ["", "Pin"] could randomly select an empty string; filter and trim entries and fall back to Pin when none remain. Made-with: Cursor --- pkg/channels/feishu/feishu_64.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/channels/feishu/feishu_64.go b/pkg/channels/feishu/feishu_64.go index 5c57cfb02..d0c351119 100644 --- a/pkg/channels/feishu/feishu_64.go +++ b/pkg/channels/feishu/feishu_64.go @@ -245,15 +245,18 @@ func (c *FeishuChannel) SendPlaceholder(ctx context.Context, chatID string) (str // ReactToMessage implements channels.ReactionCapable. // Adds a reaction (randomly chosen from config) and returns an undo function to remove it. func (c *FeishuChannel) ReactToMessage(ctx context.Context, chatID, messageID string) (func(), error) { - // Get emoji list from config - emojiList := c.config.RandomReactionEmoji - var chosenEmoji string - if len(emojiList) == 0 { - // Default to "Pin" if no config - chosenEmoji = "Pin" - } else { - idx := rand.Intn(len(emojiList)) - chosenEmoji = emojiList[idx] + // Get emoji list from config (Feishu emoji_type keys, e.g. Pin, THUMBSUP). + // Ignore empty entries so a list like ["", "Pin"] does not randomly pick "" (API 231001). + var candidates []string + for _, e := range c.config.RandomReactionEmoji { + e = strings.TrimSpace(e) + if e != "" { + candidates = append(candidates, e) + } + } + chosenEmoji := "Pin" + if len(candidates) > 0 { + chosenEmoji = candidates[rand.Intn(len(candidates))] } req := larkim.NewCreateMessageReactionReqBuilder(). From 0bb561548f099a60b588f0aaef30615af45aad05 Mon Sep 17 00:00:00 2001 From: Cytown Date: Sun, 29 Mar 2026 01:14:39 +0800 Subject: [PATCH 25/55] add pid file for gateway running and auth token for /reload and pico channel --- cmd/picoclaw-launcher-tui/ui/gateway.go | 72 ++---- cmd/picoclaw/internal/helpers.go | 6 +- pkg/agent/context.go | 10 +- pkg/auth/store.go | 7 +- pkg/channels/weixin/state.go | 6 +- pkg/config/config.go | 7 +- pkg/config/defaults.go | 12 +- pkg/config/envkeys.go | 20 ++ pkg/gateway/gateway.go | 40 ++- pkg/health/server.go | 36 ++- pkg/health/server_test.go | 11 +- pkg/migrate/internal/common.go | 11 +- pkg/pid/pidfile.go | 159 ++++++++++++ pkg/pid/pidfile_test.go | 253 +++++++++++++++++++ pkg/pid/pidfile_unix.go | 22 ++ pkg/pid/pidfile_windows.go | 42 ++++ web/backend/api/config.go | 6 + web/backend/api/gateway.go | 310 +++++++++++++++--------- web/backend/api/gateway_test.go | 3 - web/backend/api/pico.go | 61 ++++- web/backend/api/pico_test.go | 14 ++ web/backend/api/skills.go | 9 +- web/backend/middleware/middleware.go | 11 + web/backend/utils/runtime.go | 8 +- 24 files changed, 876 insertions(+), 260 deletions(-) create mode 100644 pkg/pid/pidfile.go create mode 100644 pkg/pid/pidfile_test.go create mode 100644 pkg/pid/pidfile_unix.go create mode 100644 pkg/pid/pidfile_windows.go diff --git a/cmd/picoclaw-launcher-tui/ui/gateway.go b/cmd/picoclaw-launcher-tui/ui/gateway.go index 1138c12db..781204bf2 100644 --- a/cmd/picoclaw-launcher-tui/ui/gateway.go +++ b/cmd/picoclaw-launcher-tui/ui/gateway.go @@ -7,9 +7,7 @@ package ui import ( "fmt" - "os" "os/exec" - "path/filepath" "runtime" "strconv" "strings" @@ -17,61 +15,30 @@ import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" -) -const pidFileName = "gateway.pid" + "github.com/sipeed/picoclaw/pkg/config" + ppid "github.com/sipeed/picoclaw/pkg/pid" +) type gatewayStatus struct { running bool pid int + version string } -func getPidPath() string { - home, err := os.UserHomeDir() - if err != nil { - home = "." - } - return filepath.Join(home, ".picoclaw", pidFileName) -} - -func isProcessRunning(pid int) bool { - if runtime.GOOS == "windows" { - cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid)) - output, err := cmd.Output() - if err != nil { - return false - } - return strings.Contains(string(output), strconv.Itoa(pid)) - } else if runtime.GOOS == "darwin" { - cmd := exec.Command("ps", "aux") - output, err := cmd.Output() - if err != nil { - return false - } - return strings.Contains(string(output), fmt.Sprintf(" %d ", pid)) - } - // Linux - _, err := os.Stat(fmt.Sprintf("/proc/%d", pid)) - return err == nil +func picoHome() string { + return config.GetHome() } func getGatewayStatus() gatewayStatus { - pidPath := getPidPath() - data, err := os.ReadFile(pidPath) - if err != nil { - return gatewayStatus{running: false} - } - pid, err := strconv.Atoi(strings.TrimSpace(string(data))) - if err != nil { - return gatewayStatus{running: false} - } - if !isProcessRunning(pid) { - os.Remove(pidPath) + data := ppid.ReadPidFileWithCheck(picoHome()) + if data == nil { return gatewayStatus{running: false} } return gatewayStatus{ running: true, - pid: pid, + pid: data.PID, + version: data.Version, } } @@ -81,13 +48,12 @@ func startGateway() error { return fmt.Errorf("gateway is already running (PID: %d)", status.pid) } - pidPath := getPidPath() var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("cmd", "/C", "start /B picoclaw gateway > NUL 2>&1") } else { - cmd = exec.Command("sh", "-c", "nohup picoclaw gateway > /dev/null 2>&1 & echo $! > "+pidPath) + cmd = exec.Command("sh", "-c", "nohup picoclaw gateway > /dev/null 2>&1 &") } err := cmd.Start() @@ -116,9 +82,8 @@ func startGateway() error { if line == "" { continue } - pid, err := strconv.Atoi(line) + _, err := strconv.Atoi(line) if err == nil { - os.WriteFile(pidPath, []byte(strconv.Itoa(pid)), 0o600) break } } @@ -141,21 +106,20 @@ func stopGateway() error { if runtime.GOOS == "windows" { err = exec.Command("taskkill", "/F", "/PID", strconv.Itoa(status.pid)).Run() } else { - err = exec.Command("kill", "-9", strconv.Itoa(status.pid)).Run() + err = exec.Command("kill", strconv.Itoa(status.pid)).Run() } if err != nil { return err } - // 多次尝试确认进程已停止 + // Wait for process to stop (ReadPidFileWithCheck cleans up stale pid file) for i := 0; i < 5; i++ { - if !isProcessRunning(status.pid) { + if !getGatewayStatus().running { break } time.Sleep(200 * time.Millisecond) } - os.Remove(getPidPath()) return nil } @@ -217,7 +181,11 @@ func (a *App) newGatewayPage() tview.Primitive { updateStatus = func() { status := getGatewayStatus() if status.running { - statusTV.SetText(fmt.Sprintf("[#39ff14::b]GATEWAY RUNNING[-]\n\nPID: %d", status.pid)) + versionInfo := "" + if status.version != "" { + versionInfo = fmt.Sprintf("\nVersion: %s", status.version) + } + statusTV.SetText(fmt.Sprintf("[#39ff14::b]GATEWAY RUNNING[-]\n\nPID: %d%s", status.pid, versionInfo)) buttons.SetItemText(0, " [gray]START[white] ", "") buttons.SetItemText(1, " [red]STOP[white] ", "") } else { diff --git a/cmd/picoclaw/internal/helpers.go b/cmd/picoclaw/internal/helpers.go index 17de88ccb..afe5074a7 100644 --- a/cmd/picoclaw/internal/helpers.go +++ b/cmd/picoclaw/internal/helpers.go @@ -14,11 +14,7 @@ const Logo = pkg.Logo // GetPicoclawHome returns the picoclaw home directory. // Priority: $PICOCLAW_HOME > ~/.picoclaw func GetPicoclawHome() string { - if home := os.Getenv(config.EnvHome); home != "" { - return home - } - home, _ := os.UserHomeDir() - return filepath.Join(home, pkg.DefaultPicoClawHome) + return config.GetHome() } func GetConfigPath() string { diff --git a/pkg/agent/context.go b/pkg/agent/context.go index c3fcc9fff..b5c68650a 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -12,7 +12,6 @@ import ( "sync" "time" - "github.com/sipeed/picoclaw/pkg" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/providers" @@ -59,14 +58,7 @@ func (cb *ContextBuilder) WithSplitOnMarker(enabled bool) *ContextBuilder { } func getGlobalConfigDir() string { - if home := os.Getenv(config.EnvHome); home != "" { - return home - } - home, err := os.UserHomeDir() - if err != nil { - return "" - } - return filepath.Join(home, pkg.DefaultPicoClawHome) + return config.GetHome() } func NewContextBuilder(workspace string) *ContextBuilder { diff --git a/pkg/auth/store.go b/pkg/auth/store.go index 8a878d553..dfea11df4 100644 --- a/pkg/auth/store.go +++ b/pkg/auth/store.go @@ -6,7 +6,6 @@ import ( "path/filepath" "time" - "github.com/sipeed/picoclaw/pkg" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/fileutil" ) @@ -41,11 +40,7 @@ func (c *AuthCredential) NeedsRefresh() bool { } func authFilePath() string { - if home := os.Getenv(config.EnvHome); home != "" { - return filepath.Join(home, "auth.json") - } - home, _ := os.UserHomeDir() - return filepath.Join(home, pkg.DefaultPicoClawHome, "auth.json") + return filepath.Join(config.GetHome(), "auth.json") } func LoadStore() (*AuthStore, error) { diff --git a/pkg/channels/weixin/state.go b/pkg/channels/weixin/state.go index 2d1b9f4a6..854a4ab53 100644 --- a/pkg/channels/weixin/state.go +++ b/pkg/channels/weixin/state.go @@ -37,11 +37,7 @@ type syncCursorFile struct { } func picoclawHomeDir() string { - if home := os.Getenv(config.EnvHome); home != "" { - return home - } - userHome, _ := os.UserHomeDir() - return filepath.Join(userHome, ".picoclaw") + return config.GetHome() } func buildWeixinSyncBufPath(cfg config.WeixinConfig) string { diff --git a/pkg/config/config.go b/pkg/config/config.go index 533f45a44..e7326625c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1081,12 +1081,7 @@ func LoadConfig(path string) (*Config, error) { // Ensure Workspace has a default if not set if cfg.Agents.Defaults.Workspace == "" { - homePath, _ := os.UserHomeDir() - if picoclawHome := os.Getenv(EnvHome); picoclawHome != "" { - homePath = picoclawHome - } else if homePath != "" { - homePath = filepath.Join(homePath, pkg.DefaultPicoClawHome) - } + homePath := GetHome() cfg.Agents.Defaults.Workspace = filepath.Join(homePath, pkg.WorkspaceName) } diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index bc4ab0649..bded97fcd 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -6,7 +6,6 @@ package config import ( - "os" "path/filepath" "github.com/sipeed/picoclaw/pkg" @@ -14,16 +13,7 @@ import ( // DefaultConfig returns the default configuration for PicoClaw. func DefaultConfig() *Config { - // Determine the base path for the workspace. - // Priority: $PICOCLAW_HOME > ~/.picoclaw - var homePath string - if picoclawHome := os.Getenv(EnvHome); picoclawHome != "" { - homePath = picoclawHome - } else { - userHome, _ := os.UserHomeDir() - homePath = filepath.Join(userHome, pkg.DefaultPicoClawHome) - } - workspacePath := filepath.Join(homePath, pkg.WorkspaceName) + workspacePath := filepath.Join(GetHome(), pkg.WorkspaceName) return &Config{ Version: CurrentVersion, diff --git a/pkg/config/envkeys.go b/pkg/config/envkeys.go index b04ff19f5..615769d3c 100644 --- a/pkg/config/envkeys.go +++ b/pkg/config/envkeys.go @@ -5,6 +5,13 @@ package config +import ( + "os" + "path/filepath" + + "github.com/sipeed/picoclaw/pkg" +) + // Runtime environment variable keys for the picoclaw process. // These control the location of files and binaries at runtime and are read // directly via os.Getenv / os.LookupEnv. All picoclaw-specific keys use the @@ -35,3 +42,16 @@ const ( // Default: "127.0.0.1" EnvGatewayHost = "PICOCLAW_GATEWAY_HOST" ) + +func GetHome() string { + homePath, _ := os.UserHomeDir() + if picoclawHome := os.Getenv(EnvHome); picoclawHome != "" { + homePath = picoclawHome + } else if homePath != "" { + homePath = filepath.Join(homePath, pkg.DefaultPicoClawHome) + } + if homePath == "" { + homePath = "." + } + return homePath +} diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index c35b3e744..a63530806 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "sync" "sync/atomic" "syscall" @@ -36,6 +37,7 @@ import ( "github.com/sipeed/picoclaw/pkg/heartbeat" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/media" + "github.com/sipeed/picoclaw/pkg/pid" "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/state" "github.com/sipeed/picoclaw/pkg/tools" @@ -61,6 +63,7 @@ type services struct { HealthServer *health.Server manualReloadChan chan struct{} reloading atomic.Bool + authToken string } type startupBlockedProvider struct { @@ -107,6 +110,13 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error fmt.Println("🔍 Debug mode enabled") } + // Enforce singleton: write PID file with generated token. + pidData, err := pid.WritePidFile(homePath, cfg.Gateway.Host, cfg.Gateway.Port) + if err != nil { + return fmt.Errorf("singleton check failed: %w", err) + } + defer pid.RemovePidFile(homePath) + provider, modelID, err := createStartupProvider(cfg, allowEmptyStartup) if err != nil { return fmt.Errorf("error creating provider: %w", err) @@ -133,7 +143,7 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error "skills_available": skillsInfo["available"], }) - runningServices, err := setupAndStartServices(cfg, agentLoop, msgBus) + runningServices, err := setupAndStartServices(cfg, agentLoop, msgBus, pidData.Token) if err != nil { return err } @@ -224,6 +234,9 @@ func executeReload( allowEmptyStartup bool, ) error { defer runningServices.reloading.Store(false) + + overridePicoToken(newCfg, runningServices.authToken) + return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup) } @@ -248,6 +261,7 @@ func setupAndStartServices( cfg *config.Config, agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, + authToken string, ) (*services, error) { runningServices := &services{} @@ -290,6 +304,8 @@ func setupAndStartServices( fms.Start() } + overridePicoToken(cfg, authToken) + runningServices.ChannelManager, err = channels.NewManager(cfg, msgBus, runningServices.MediaStore) if err != nil { if fms, ok := runningServices.MediaStore.(*media.FileMediaStore); ok { @@ -314,7 +330,8 @@ func setupAndStartServices( } addr := fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port) - runningServices.HealthServer = health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port) + runningServices.authToken = authToken + runningServices.HealthServer = health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port, authToken) runningServices.ChannelManager.SetupHTTPServer(addr, runningServices.HealthServer) if err = runningServices.ChannelManager.StartAll(context.Background()); err != nil { @@ -524,6 +541,9 @@ func restartServices( logger.InfoCF("voice", "Transcription disabled", nil) } + // NOTE: PID file is written once at startup and not updated on reload. + // Changing the gateway listen address requires a full restart. + return nil } @@ -642,6 +662,22 @@ func setupCronTool( return cronService, nil } +const picoTokenPrefix = "pico-" + +// overridePicoToken replaces the pico channel token with the one from the PID file. +// The PID file is the single source of truth for the pico auth token; +// it is generated once at gateway startup and remains unchanged across reloads. +func overridePicoToken(cfg *config.Config, token string) { + if !cfg.Channels.Pico.Enabled { + return + } + picoToken := cfg.Channels.Pico.Token.String() + if picoToken == "" || strings.HasPrefix(picoToken, picoTokenPrefix) { + return + } + cfg.Channels.Pico.SetToken(picoTokenPrefix + token + picoToken) +} + func createHeartbeatHandler(agentLoop *agent.AgentLoop) func(prompt, channel, chatID string) *tools.ToolResult { return func(prompt, channel, chatID string) *tools.ToolResult { if channel == "" || chatID == "" { diff --git a/pkg/health/server.go b/pkg/health/server.go index 387cb0756..2602cb965 100644 --- a/pkg/health/server.go +++ b/pkg/health/server.go @@ -2,11 +2,11 @@ package health import ( "context" + "crypto/subtle" "encoding/json" "fmt" "maps" "net/http" - "os" "sync" "time" ) @@ -18,6 +18,7 @@ type Server struct { checks map[string]Check startTime time.Time reloadFunc func() error + authToken string // optional bearer token for protected endpoints } type Check struct { @@ -31,15 +32,15 @@ type StatusResponse struct { Status string `json:"status"` Uptime string `json:"uptime"` Checks map[string]Check `json:"checks,omitempty"` - Pid int `json:"pid"` } -func NewServer(host string, port int) *Server { +func NewServer(host string, port int, token string) *Server { mux := http.NewServeMux() s := &Server{ ready: false, checks: make(map[string]Check), startTime: time.Now(), + authToken: token, } mux.HandleFunc("/health", s.healthHandler) @@ -123,6 +124,21 @@ func (s *Server) reloadHandler(w http.ResponseWriter, r *http.Request) { return } + // Token check + s.mu.RLock() + requiredToken := s.authToken + s.mu.RUnlock() + + if requiredToken != "" { + given := extractBearerToken(r.Header.Get("Authorization")) + if given == "" || subtle.ConstantTimeCompare([]byte(given), []byte(requiredToken)) != 1 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) + return + } + } + s.mu.Lock() reloadFunc := s.reloadFunc s.mu.Unlock() @@ -154,7 +170,6 @@ func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { resp := StatusResponse{ Status: "ok", Uptime: uptime.String(), - Pid: os.Getpid(), } json.NewEncoder(w).Encode(resp) @@ -220,3 +235,16 @@ func statusString(ok bool) string { } return "fail" } + +// extractBearerToken returns the token from an "Authorization: Bearer " header, +// or the empty string if the header is missing or malformed. +func extractBearerToken(header string) string { + const prefix = "Bearer " + if len(header) < len(prefix) { + return "" + } + if header[:len(prefix)] != prefix { + return "" + } + return header[len(prefix):] +} diff --git a/pkg/health/server_test.go b/pkg/health/server_test.go index 6e0b5e66b..c4982fff9 100644 --- a/pkg/health/server_test.go +++ b/pkg/health/server_test.go @@ -15,6 +15,7 @@ func newTestServer() *Server { ready: false, checks: make(map[string]Check), startTime: time.Now(), + authToken: "test", } return s } @@ -37,9 +38,6 @@ func TestHealthHandler_ReturnsOK(t *testing.T) { if resp.Status != "ok" { t.Errorf("status = %q, want %q", resp.Status, "ok") } - if resp.Pid == 0 { - t.Error("pid should not be 0") - } if resp.Uptime == "" { t.Error("uptime should not be empty") } @@ -168,6 +166,7 @@ func TestReloadHandler_NoReloadFunc(t *testing.T) { s := newTestServer() req := httptest.NewRequest(http.MethodPost, "/reload", nil) + req.Header.Set("Authorization", "Bearer test") w := httptest.NewRecorder() s.reloadHandler(w, req) @@ -186,6 +185,7 @@ func TestReloadHandler_Success(t *testing.T) { }) req := httptest.NewRequest(http.MethodPost, "/reload", nil) + req.Header.Set("Authorization", "Bearer test") w := httptest.NewRecorder() s.reloadHandler(w, req) @@ -205,6 +205,7 @@ func TestReloadHandler_Error(t *testing.T) { }) req := httptest.NewRequest(http.MethodPost, "/reload", nil) + req.Header.Set("Authorization", "Bearer test") w := httptest.NewRecorder() s.reloadHandler(w, req) @@ -292,7 +293,7 @@ func TestRegisterOnMux(t *testing.T) { } func TestNewServer(t *testing.T) { - s := NewServer("127.0.0.1", 0) + s := NewServer("127.0.0.1", 0, "") if s == nil { t.Fatal("NewServer returned nil") } @@ -305,7 +306,7 @@ func TestNewServer(t *testing.T) { } func TestStartContext_Cancellation(t *testing.T) { - s := NewServer("127.0.0.1", 0) + s := NewServer("127.0.0.1", 0, "") ctx, cancel := context.WithCancel(context.Background()) diff --git a/pkg/migrate/internal/common.go b/pkg/migrate/internal/common.go index 65a87adc4..f1179c3a9 100644 --- a/pkg/migrate/internal/common.go +++ b/pkg/migrate/internal/common.go @@ -1,12 +1,10 @@ package internal import ( - "fmt" "io" "os" "path/filepath" - "github.com/sipeed/picoclaw/pkg" "github.com/sipeed/picoclaw/pkg/config" ) @@ -14,14 +12,7 @@ func ResolveTargetHome(override string) (string, error) { if override != "" { return ExpandHome(override), nil } - if envHome := os.Getenv(config.EnvHome); envHome != "" { - return ExpandHome(envHome), nil - } - home, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("resolving home directory: %w", err) - } - return filepath.Join(home, pkg.DefaultPicoClawHome), nil + return config.GetHome(), nil } func ExpandHome(path string) string { diff --git a/pkg/pid/pidfile.go b/pkg/pid/pidfile.go new file mode 100644 index 000000000..584b9b2b5 --- /dev/null +++ b/pkg/pid/pidfile.go @@ -0,0 +1,159 @@ +package pid + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" +) + +const pidFileName = ".picoclaw.pid" + +// PidFileData is the JSON structure stored in the PID file. +type PidFileData struct { + PID int `json:"pid"` + Token string `json:"token"` + Version string `json:"version"` + Port int `json:"port"` + Host string `json:"host"` +} + +var pidMu sync.Mutex + +// pidFilePath returns the absolute path for the PID file given the home directory. +func pidFilePath(homePath string) string { + return filepath.Join(homePath, pidFileName) +} + +// generateToken creates a cryptographically random 32-character hex token. +func generateToken() string { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + // Fallback to something pseudo-random if crypto/rand fails + return fmt.Sprintf("%032x", time.Now().UnixNano()) + } + return hex.EncodeToString(b) +} + +// WritePidFile creates (or overwrites) the PID file atomically. +// It returns an error if another gateway instance appears to be running +// (a valid PID file exists with a live process). +func WritePidFile(homePath, host string, port int) (*PidFileData, error) { + pidMu.Lock() + defer pidMu.Unlock() + + pidPath := pidFilePath(homePath) + + // Check for existing PID file → singleton enforcement. + if data, err := readPidFileUnlocked(pidPath); err == nil { + if os.Getpid() != data.PID { + logger.Infof("found pid file (PID: %d, version: %s)", data.PID, data.Version) + if isProcessRunning(data.PID) { + return nil, fmt.Errorf("gateway is already running (PID: %d, version: %s)", data.PID, data.Version) + } + logger.Warnf("not running (PID: %d) so will remove the pid file: %s", data.PID, pidPath) + } + // Stale PID file; process no longer exists → clean up. + os.Remove(pidPath) + } + + data := &PidFileData{ + PID: os.Getpid(), + Version: config.GetVersion(), + Port: port, + Host: host, + } + + token := generateToken() + data.Token = token + + raw, err := json.MarshalIndent(data, "", " ") + if err != nil { + return nil, fmt.Errorf("failed to marshal pid file: %w", err) + } + + // Ensure parent directory exists. + dir := filepath.Dir(pidPath) + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, fmt.Errorf("failed to create pid directory: %w", err) + } + + // Write atomically via temp file + rename. + tmp := pidPath + ".tmp" + if err := os.WriteFile(tmp, raw, 0o600); err != nil { + return nil, fmt.Errorf("failed to write pid file: %w", err) + } + if err := os.Rename(tmp, pidPath); err != nil { + os.Remove(tmp) + return nil, fmt.Errorf("failed to rename pid file: %w", err) + } + + return data, nil +} + +// ReadPidFileWithCheck reads the PID file and additionally checks if +// the recorded process is still alive. Returns nil if the file is +// missing, unreadable, or the process has exited. +func ReadPidFileWithCheck(homePath string) *PidFileData { + pidMu.Lock() + defer pidMu.Unlock() + + pidPath := pidFilePath(homePath) + data, err := readPidFileUnlocked(pidPath) + if err != nil { + return nil + } + + if !isProcessRunning(data.PID) { + os.Remove(pidPath) + return nil + } + + return data +} + +// RemovePidFile deletes the PID file (e.g. on graceful shutdown). +func RemovePidFile(homePath string) { + pidMu.Lock() + defer pidMu.Unlock() + + pidPath := pidFilePath(homePath) + // Only remove if the PID matches our own process (avoid deleting + // a file that belongs to a newer gateway instance). + if data, err := readPidFileUnlocked(pidPath); err == nil { + if data.PID != os.Getpid() { + return + } + } + + logger.Infof("remove pid file: %s", pidPath) + os.Remove(pidPath) +} + +// readPidFileUnlocked reads the PID file without acquiring the lock. +// Caller must hold pidMu. +func readPidFileUnlocked(pidPath string) (*PidFileData, error) { + raw, err := os.ReadFile(pidPath) + if err != nil { + return nil, err + } + + var data PidFileData + if err := json.Unmarshal(raw, &data); err != nil { + return nil, err + } + + // Validate PID is a positive integer. + if data.PID <= 0 { + return nil, fmt.Errorf("invalid pid in pid file: %d", data.PID) + } + + return &data, nil +} diff --git a/pkg/pid/pidfile_test.go b/pkg/pid/pidfile_test.go new file mode 100644 index 000000000..921f590ad --- /dev/null +++ b/pkg/pid/pidfile_test.go @@ -0,0 +1,253 @@ +package pid + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +// tmpDir returns a clean temporary directory for a test. +func tmpDir(t *testing.T) string { + t.Helper() + dir, err := os.MkdirTemp("", "pidtest-*") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { os.RemoveAll(dir) }) + return dir +} + +// TestGenerateToken verifies that generateToken produces a 32-character hex string. +func TestGenerateToken(t *testing.T) { + token := generateToken() + if len(token) != 32 { + t.Errorf("expected token length 32, got %d (token: %q)", len(token), token) + } + // Verify all characters are valid hex. + for _, c := range token { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { + t.Errorf("token contains non-hex character: %c", c) + } + } +} + +// TestGenerateTokenUniqueness checks that two consecutive tokens differ. +func TestGenerateTokenUniqueness(t *testing.T) { + a := generateToken() + b := generateToken() + if a == b { + t.Error("two consecutive tokens should not be equal") + } +} + +// TestPidFilePath returns the expected path. +func TestPidFilePath(t *testing.T) { + dir := tmpDir(t) + got := pidFilePath(dir) + want := filepath.Join(dir, pidFileName) + if got != want { + t.Errorf("pidFilePath(%q) = %q, want %q", dir, got, want) + } +} + +// TestWritePidFile creates a PID file and verifies its contents. +func TestWritePidFile(t *testing.T) { + dir := tmpDir(t) + data, err := WritePidFile(dir, "127.0.0.1", 18790) + if err != nil { + t.Fatalf("WritePidFile failed: %v", err) + } + + if data.PID != os.Getpid() { + t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) + } + if data.Host != "127.0.0.1" { + t.Errorf("Host = %q, want %q", data.Host, "127.0.0.1") + } + if data.Port != 18790 { + t.Errorf("Port = %d, want %d", data.Port, 18790) + } + if len(data.Token) != 32 { + t.Errorf("Token length = %d, want 32", len(data.Token)) + } + + // Verify the file exists and can be unmarshalled. + raw, err := os.ReadFile(filepath.Join(dir, pidFileName)) + if err != nil { + t.Fatalf("failed to read pid file: %v", err) + } + + var fileData PidFileData + if err = json.Unmarshal(raw, &fileData); err != nil { + t.Fatalf("failed to unmarshal pid file: %v", err) + } + if fileData.PID != data.PID || fileData.Token != data.Token { + t.Error("file data mismatch") + } + + // Verify file permissions (owner-only read/write). + info, err := os.Stat(filepath.Join(dir, pidFileName)) + if err != nil { + t.Fatalf("failed to stat pid file: %v", err) + } + perm := info.Mode().Perm() + if perm != 0o600 { + t.Errorf("file permission = %o, want 0600", perm) + } +} + +// TestWritePidFileOverwrite writes twice and verifies the PID file is replaced. +func TestWritePidFileOverwrite(t *testing.T) { + dir := tmpDir(t) + + data1, err := WritePidFile(dir, "0.0.0.0", 18790) + if err != nil { + t.Fatalf("first WritePidFile failed: %v", err) + } + + // Second write should succeed because the PID matches our process. + data2, err := WritePidFile(dir, "0.0.0.0", 18800) + if err != nil { + t.Fatalf("second WritePidFile failed: %v", err) + } + + if data2.Token == data1.Token { + t.Error("token should change on re-write") + } + if data2.Port != 18800 { + t.Errorf("Port = %d, want 18800", data2.Port) + } +} + +// TestWritePidFileStalePID writes a PID file with a non-running PID, then +// verifies WritePidFile cleans it up and writes a new one. +func TestWritePidFileStalePID(t *testing.T) { + dir := tmpDir(t) + + // Write a PID file with a PID that almost certainly doesn't exist. + stale := PidFileData{PID: 99999999, Token: "deadbeef12345678deadbeef12345678"} + raw, _ := json.MarshalIndent(stale, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + + data, err := WritePidFile(dir, "127.0.0.1", 18790) + if err != nil { + t.Fatalf("WritePidFile with stale PID failed: %v", err) + } + if data.PID != os.Getpid() { + t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) + } +} + +// TestReadPidFileWithCheck verifies reading a valid PID file for the current process. +func TestReadPidFileWithCheck(t *testing.T) { + dir := tmpDir(t) + + // Some sandboxed environments (e.g. macOS test runner) may restrict + // signal(0), causing isProcessRunning(getpid()) to return false. + if !isProcessRunning(os.Getpid()) { + t.Skip("skipping: isProcessRunning(getpid()) is false in this environment") + } + + written, err := WritePidFile(dir, "127.0.0.1", 18790) + if err != nil { + t.Fatalf("WritePidFile failed: %v", err) + } + + read := ReadPidFileWithCheck(dir) + if read == nil { + t.Fatal("ReadPidFileWithCheck returned nil for current process") + } + if read.PID != written.PID || read.Token != written.Token { + t.Error("read data doesn't match written data") + } +} + +// TestReadPidFileWithCheckNonexistent returns nil for missing file. +func TestReadPidFileWithCheckNonexistent(t *testing.T) { + dir := tmpDir(t) + data := ReadPidFileWithCheck(dir) + if data != nil { + t.Error("expected nil for nonexistent PID file") + } +} + +// TestReadPidFileWithCheckStalePID auto-cleans a PID file whose process is dead. +func TestReadPidFileWithCheckStalePID(t *testing.T) { + dir := tmpDir(t) + + stale := PidFileData{PID: 99999999, Token: "deadbeef12345678deadbeef12345678"} + raw, _ := json.MarshalIndent(stale, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + + data := ReadPidFileWithCheck(dir) + if data != nil { + t.Error("expected nil for stale PID") + } + + // File should be cleaned up. + if _, err := os.Stat(filepath.Join(dir, pidFileName)); !os.IsNotExist(err) { + t.Error("stale PID file should be removed") + } +} + +// TestRemovePidFile removes the PID file for the current process. +func TestRemovePidFile(t *testing.T) { + dir := tmpDir(t) + + if _, err := WritePidFile(dir, "127.0.0.1", 18790); err != nil { + t.Fatalf("WritePidFile failed: %v", err) + } + + RemovePidFile(dir) + + if _, err := os.Stat(filepath.Join(dir, pidFileName)); !os.IsNotExist(err) { + t.Error("PID file should be removed") + } +} + +// TestRemovePidFileDifferentPID does not remove a PID file owned by another process. +func TestRemovePidFileDifferentPID(t *testing.T) { + dir := tmpDir(t) + + other := PidFileData{PID: 99999999, Token: "deadbeef12345678deadbeef12345678"} + raw, _ := json.MarshalIndent(other, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + + RemovePidFile(dir) + + if _, err := os.Stat(filepath.Join(dir, pidFileName)); os.IsNotExist(err) { + t.Error("PID file should NOT be removed (different PID)") + } +} + +// TestRemovePidFileNonexistent does not error on missing file. +func TestRemovePidFileNonexistent(t *testing.T) { + dir := tmpDir(t) + // Should not panic or error. + RemovePidFile(dir) +} + +// TestReadPidFileUnlockedInvalidJSON returns error for malformed content. +func TestReadPidFileUnlockedInvalidJSON(t *testing.T) { + dir := tmpDir(t) + path := filepath.Join(dir, pidFileName) + os.WriteFile(path, []byte("not json"), 0o600) + + _, err := readPidFileUnlocked(path) + if err == nil { + t.Error("expected error for invalid JSON") + } +} + +// TestReadPidFileUnlockedInvalidPID returns error for non-positive PID. +func TestReadPidFileUnlockedInvalidPID(t *testing.T) { + dir := tmpDir(t) + path := filepath.Join(dir, pidFileName) + os.WriteFile(path, []byte(`{"pid": -1, "token": "a"}`), 0o600) + + _, err := readPidFileUnlocked(path) + if err == nil { + t.Error("expected error for invalid PID") + } +} diff --git a/pkg/pid/pidfile_unix.go b/pkg/pid/pidfile_unix.go new file mode 100644 index 000000000..5459d8370 --- /dev/null +++ b/pkg/pid/pidfile_unix.go @@ -0,0 +1,22 @@ +//go:build !windows + +package pid + +import ( + "os" + "syscall" +) + +// isProcessRunning checks whether a process with the given PID is alive +// on Unix-like systems using signal(0). +func isProcessRunning(pid int) bool { + if pid <= 0 { + return false + } + p, err := os.FindProcess(pid) + if err != nil { + return false + } + // Signal(nil) does not kill the process but checks existence on Unix. + return p.Signal(syscall.Signal(0)) == nil +} diff --git a/pkg/pid/pidfile_windows.go b/pkg/pid/pidfile_windows.go new file mode 100644 index 000000000..6a2cce793 --- /dev/null +++ b/pkg/pid/pidfile_windows.go @@ -0,0 +1,42 @@ +//go:build windows + +package pid + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procOpenProcess = kernel32.NewProc("OpenProcess") + procGetExitCodeProcess = kernel32.NewProc("GetExitCodeProcess") + procCloseHandle = kernel32.NewProc("CloseHandle") + processQueryLimitedInformation = uint32(0x1000) + stillActive = uint32(259) +) + +// isProcessRunning checks whether a process with the given PID is alive +// on Windows using OpenProcess + GetExitCodeProcess. +func isProcessRunning(pid int) bool { + if pid <= 0 { + return false + } + + handle, _, err := procOpenProcess.Call( + uintptr(processQueryLimitedInformation), + 0, + uintptr(pid), + ) + if handle == 0 || err != nil { + return false + } + defer procCloseHandle.Call(handle) + + var exitCode uint32 + ret, _, err := procGetExitCodeProcess.Call(handle, uintptr(unsafe.Pointer(&exitCode))) + if ret == 0 || err != nil { + return false + } + return exitCode == stillActive +} diff --git a/web/backend/api/config.go b/web/backend/api/config.go index 0add7594d..8da2f53e0 100644 --- a/web/backend/api/config.go +++ b/web/backend/api/config.go @@ -81,6 +81,9 @@ func (h *Handler) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { return } + // Refresh cached pico token in case user changed it. + refreshPicoToken(&cfg) + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } @@ -175,6 +178,9 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) { return } + // Refresh cached pico token in case user changed it. + refreshPicoToken(&newCfg) + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index 808475cb6..4aebd2eaa 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -20,6 +20,7 @@ import ( "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/health" "github.com/sipeed/picoclaw/pkg/logger" + ppid "github.com/sipeed/picoclaw/pkg/pid" "github.com/sipeed/picoclaw/web/backend/utils" ) @@ -32,11 +33,50 @@ var gateway = struct { runtimeStatus string startupDeadline time.Time logs *LogBuffer + pidData *ppid.PidFileData // pid file data read from picoclaw.pid.json + picoToken string // cached pico token from config (for proxy auth validation) }{ runtimeStatus: "stopped", logs: NewLogBuffer(200), } +// refreshPicoToken updates gateway.picoToken from cfg +func refreshPicoToken(cfg *config.Config) { + gateway.mu.Lock() + defer gateway.mu.Unlock() + gateway.picoToken = cfg.Channels.Pico.Token.String() +} + +// refreshPicoTokensLocked reads the pico token from config and caches it. +// Caller must hold gateway.mu (or be sole writer). +func refreshPicoTokensLocked(configPath string) { + cfg, err := config.LoadConfig(configPath) + if err != nil { + return + } + gateway.picoToken = cfg.Channels.Pico.Token.String() +} + +const ( + protocolKey = "Sec-Websocket-Protocol" + picoTokenPrefix = "pico-" + tokenPrefix = "token." +) + +// picoComposedToken returns "pico-"+pidToken+picoToken for gateway auth. +func picoComposedToken(token string) string { + gateway.mu.Lock() + defer gateway.mu.Unlock() + // if not initial pico token, don't allow gateway auth + if gateway.picoToken == "" || gateway.pidData == nil { + return "" + } + if tokenPrefix+gateway.picoToken != token { + return "" + } + return picoTokenPrefix + gateway.pidData.Token + gateway.picoToken +} + var ( gatewayStartupWindow = 15 * time.Second gatewayRestartGracePeriod = 5 * time.Second @@ -49,16 +89,29 @@ var gatewayHealthGet = func(url string, timeout time.Duration) (*http.Response, return client.Get(url) } -// getGatewayHealth checks the gateway health endpoint and returns the status response +// getGatewayHealth checks the gateway health endpoint and returns the status response. // Returns (*health.StatusResponse, statusCode, error). If error is not nil, the other values are not valid. func (h *Handler) getGatewayHealth(cfg *config.Config, timeout time.Duration) (*health.StatusResponse, int, error) { - port := 18790 - if cfg != nil && cfg.Gateway.Port != 0 { - port = cfg.Gateway.Port + // Prefer port/host from pidData when available. + var port int + var host string + gateway.mu.Lock() + if d := gateway.pidData; d != nil && d.Port > 0 { + port = d.Port + host = d.Host + } + gateway.mu.Unlock() + if port == 0 { + port = 18790 + if cfg != nil && cfg.Gateway.Port != 0 { + port = cfg.Gateway.Port + } + } + if host == "" { + host = gatewayProbeHost(h.effectiveGatewayBindHost(cfg)) } - probeHost := gatewayProbeHost(h.effectiveGatewayBindHost(cfg)) - url := "http://" + net.JoinHostPort(probeHost, strconv.Itoa(port)) + "/health" + url := "http://" + net.JoinHostPort(host, strconv.Itoa(port)) + "/health" return getGatewayHealthByURL(url, timeout) } @@ -91,30 +144,34 @@ func (h *Handler) registerGatewayRoutes(mux *http.ServeMux) { // TryAutoStartGateway checks whether gateway start preconditions are met and // starts it when possible. Intended to be called by the backend at startup. func (h *Handler) TryAutoStartGateway() { - // Check if gateway is already running via health endpoint - cfg, cfgErr := config.LoadConfig(h.configPath) - if cfgErr == nil && cfg != nil { - healthResp, statusCode, err := h.getGatewayHealth(cfg, 2*time.Second) - if err == nil && statusCode == http.StatusOK { - // Gateway is already running, attach to the existing process - pid := healthResp.Pid - gateway.mu.Lock() - defer gateway.mu.Unlock() - ready, reason, err := h.gatewayStartReady() - if err != nil { - logger.ErrorC("gateway", fmt.Sprintf("Skip auto-starting gateway: %v", err)) - return - } - if !ready { - logger.InfoC("gateway", fmt.Sprintf("Skip auto-starting gateway: %s", reason)) - return - } - _, err = h.startGatewayLocked("starting", pid) - if err != nil { - logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to running gateway (PID: %d): %v", pid, err)) - } + // Check PID file first to detect an already-running gateway. + pidData := ppid.ReadPidFileWithCheck(globalConfigDir()) + logger.Infof("pidData: %v", pidData) + if pidData != nil { + gateway.mu.Lock() + ready, reason, err := h.gatewayStartReady() + if err != nil { + logger.ErrorC("gateway", fmt.Sprintf("Skip auto-starting gateway: %v", err)) + gateway.mu.Unlock() return } + logger.Infof("ready: %v, reason: %s", ready, reason) + if !ready { + logger.InfoC("gateway", fmt.Sprintf("Skip auto-starting gateway: %s", reason)) + gateway.mu.Unlock() + return + } + pid := pidData.PID + _, err = h.startGatewayLocked("starting", pid) + if err != nil { + logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to running gateway (PID: %d): %v", pid, err)) + } else { + gateway.pidData = pidData + refreshPicoTokensLocked(h.configPath) + logger.InfoC("gateway", fmt.Sprintf("Attached to running gateway via PID file (PID: %d)", pid)) + } + gateway.mu.Unlock() + return } gateway.mu.Lock() @@ -319,6 +376,7 @@ func stopGatewayLocked() (int, error) { gateway.cmd = nil gateway.owned = false gateway.bootDefaultModel = "" + gateway.pidData = nil setGatewayRuntimeStatusLocked("stopped") return pid, nil @@ -371,6 +429,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int pid = existingPid gateway.cmd = nil // Clear first to ensure clean state if err = attachToGatewayProcessLocked(pid, cfg); err != nil { + logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to existing gateway (PID %d): %v", pid, err)) return 0, err } @@ -380,6 +439,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int // Start new process // Locate the picoclaw executable execPath := utils.FindPicoclawBinary() + logger.InfoC("gateway", fmt.Sprintf("Starting gateway process (%s)", execPath)) cmd = exec.Command(execPath, "gateway", "-E") cmd.Env = os.Environ() @@ -407,10 +467,16 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int gateway.logs.Reset() // Ensure Pico Channel is configured before starting gateway - if _, err := h.EnsurePicoChannel(""); err != nil { + changed, err := h.EnsurePicoChannel("") + if err != nil { logger.ErrorC("gateway", fmt.Sprintf("Warning: failed to ensure pico channel: %v", err)) // Non-fatal: gateway can still start without pico channel } + // Refresh cached pico token in case EnsurePicoChannel generated a new one. + // Already holding gateway.mu from caller. + if changed { + refreshPicoTokensLocked(h.configPath) + } if err := cmd.Start(); err != nil { return 0, fmt.Errorf("failed to start gateway: %w", err) @@ -446,7 +512,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int gateway.mu.Unlock() }() - // Start a goroutine to probe health and update the runtime state once ready. + // Start a goroutine to probe pidFile and health, update runtime state once ready. go func() { for i := 0; i < 30; i++ { // try for up to 15 seconds time.Sleep(500 * time.Millisecond) @@ -456,13 +522,26 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int if !stillOurs { return } + + // Poll for pidFile first — once available we have port/host/token. + if pd := ppid.ReadPidFileWithCheck(globalConfigDir()); pd != nil && pd.PID == pid { + gateway.mu.Lock() + if gateway.cmd == cmd { + gateway.pidData = pd + setGatewayRuntimeStatusLocked("running") + } + gateway.mu.Unlock() + logger.InfoC("gateway", fmt.Sprintf("Gateway pidFile detected (PID: %d, port: %d)", pd.PID, pd.Port)) + return + } + + // Fallback: probe health endpoint to confirm liveness. cfg, err := config.LoadConfig(h.configPath) if err != nil { continue } - healthResp, statusCode, err := h.getGatewayHealth(cfg, 1*time.Second) - if err == nil && statusCode == http.StatusOK && healthResp.Pid == pid { - // Verify the health endpoint returns the expected pid + _, statusCode, err := h.getGatewayHealth(cfg, 1*time.Second) + if err == nil && statusCode == http.StatusOK { gateway.mu.Lock() if gateway.cmd == cmd { setGatewayRuntimeStatusLocked("running") @@ -480,49 +559,47 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int // // POST /api/gateway/start func (h *Handler) handleGatewayStart(w http.ResponseWriter, r *http.Request) { - // Prevent duplicate starts by checking health endpoint - cfg, cfgErr := config.LoadConfig(h.configPath) - if cfgErr == nil && cfg != nil { - healthResp, statusCode, err := h.getGatewayHealth(cfg, 2*time.Second) - if err == nil && statusCode == http.StatusOK { - // Gateway is already running, attach to the existing process - pid := healthResp.Pid - gateway.mu.Lock() - ready, reason, err := h.gatewayStartReady() - if err != nil { - gateway.mu.Unlock() - http.Error( - w, - fmt.Sprintf("Failed to validate gateway start conditions: %v", err), - http.StatusInternalServerError, - ) - return - } - if !ready { - gateway.mu.Unlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(map[string]any{ - "status": "precondition_failed", - "message": reason, - }) - return - } - _, err = h.startGatewayLocked("starting", pid) + // Check PID file first to detect an already-running gateway. + pidData := ppid.ReadPidFileWithCheck(globalConfigDir()) + if pidData != nil { + pid := pidData.PID + gateway.mu.Lock() + ready, reason, err := h.gatewayStartReady() + if err != nil { + gateway.mu.Unlock() + http.Error( + w, + fmt.Sprintf("Failed to validate gateway start conditions: %v", err), + http.StatusInternalServerError, + ) + return + } + if !ready { gateway.mu.Unlock() - if err != nil { - logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to running gateway (PID: %d): %v", pid, err)) - http.Error(w, fmt.Sprintf("Failed to attach to gateway: %v", err), http.StatusInternalServerError) - return - } w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]any{ - "status": "ok", - "pid": pid, + "status": "precondition_failed", + "message": reason, }) return } + _, err = h.startGatewayLocked("starting", pid) + if err != nil { + gateway.mu.Unlock() + logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to running gateway (PID: %d): %v", pid, err)) + http.Error(w, fmt.Sprintf("Failed to attach to gateway: %v", err), http.StatusInternalServerError) + return + } + gateway.pidData = pidData + gateway.mu.Unlock() + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "pid": pid, + }) + return } gateway.mu.Lock() @@ -722,65 +799,56 @@ func (h *Handler) gatewayStatusData() map[string]any { } } - // Probe health endpoint to get pid and status - healthResp, statusCode, err := h.getGatewayHealth(cfg, 2*time.Second) - if err != nil { + // Primary detection: read PID file and check if process is alive. + pidData := ppid.ReadPidFileWithCheck(globalConfigDir()) + if pidData != nil { gateway.mu.Lock() - data["gateway_status"] = gatewayStatusWithoutHealthLocked() + gateway.pidData = pidData + if pidData.Version != "" { + data["gateway_version"] = pidData.Version + } + setGatewayRuntimeStatusLocked("running") + + // Attach if we don't already track this PID. + if gateway.cmd == nil || gateway.cmd.Process == nil || gateway.cmd.Process.Pid != pidData.PID { + _ = attachToGatewayProcessLocked(pidData.PID, cfg) + } + + bootDefaultModel := gateway.bootDefaultModel + if bootDefaultModel != "" { + data["boot_default_model"] = bootDefaultModel + } + data["gateway_status"] = "running" + data["pid"] = pidData.PID gateway.mu.Unlock() - logger.ErrorC("gateway", fmt.Sprintf("Gateway health check failed: %v", err)) } else { - if statusCode != http.StatusOK { - logger.WarnC("gateway", fmt.Sprintf("Gateway health status: %d", statusCode)) + // Fallback: probe health endpoint to get pid and status + _, statusCode, err := h.getGatewayHealth(cfg, 2*time.Second) + if err != nil { gateway.mu.Lock() - setGatewayRuntimeStatusLocked("error") + data["gateway_status"] = gatewayStatusWithoutHealthLocked() + gateway.pidData = nil gateway.mu.Unlock() - data["gateway_status"] = "error" - data["status_code"] = statusCode + logger.ErrorC("gateway", fmt.Sprintf("Gateway health check failed: %v", err)) } else { - gateway.mu.Lock() - setGatewayRuntimeStatusLocked("running") - if gateway.cmd == nil || gateway.cmd.Process == nil || gateway.cmd.Process.Pid != healthResp.Pid { - oldPid := "none" - if gateway.cmd != nil && gateway.cmd.Process != nil { - oldPid = fmt.Sprintf("%d", gateway.cmd.Process.Pid) - } - logger.InfoC( - "gateway", - fmt.Sprintf( - "Detected new gateway PID (old: %s, new: %d), attempting to attach", - oldPid, - healthResp.Pid, - ), - ) - - if err := attachToGatewayProcessLocked(healthResp.Pid, cfg); err != nil { - // Failed to find the process, treat as error - setGatewayRuntimeStatusLocked("error") - data["gateway_status"] = "error" - data["pid"] = healthResp.Pid - logger.ErrorC( - "gateway", - fmt.Sprintf("Failed to attach to new gateway process (PID: %d): %v", healthResp.Pid, err), - ) - } else { - // Successfully attached, update response data - bootDefaultModel := gateway.bootDefaultModel - if bootDefaultModel != "" { - data["boot_default_model"] = bootDefaultModel - } - data["gateway_status"] = "running" - data["pid"] = healthResp.Pid + logger.InfoC("gateway", fmt.Sprintf("Gateway health status: %d", statusCode)) + if statusCode != http.StatusOK { + gateway.mu.Lock() + setGatewayRuntimeStatusLocked("error") + gateway.pidData = nil + gateway.mu.Unlock() + data["gateway_status"] = "error" + data["status_code"] = statusCode + } else { + gateway.mu.Lock() + setGatewayRuntimeStatusLocked("running") + bootDefaultModel := gateway.bootDefaultModel + if bootDefaultModel != "" { + data["boot_default_model"] = bootDefaultModel } + data["gateway_status"] = "running" + gateway.mu.Unlock() } - - bootDefaultModel := gateway.bootDefaultModel - if bootDefaultModel != "" { - data["boot_default_model"] = bootDefaultModel - } - data["gateway_status"] = "running" - data["pid"] = healthResp.Pid - gateway.mu.Unlock() } } diff --git a/web/backend/api/gateway_test.go b/web/backend/api/gateway_test.go index a5ba2bad2..a891be3c1 100644 --- a/web/backend/api/gateway_test.go +++ b/web/backend/api/gateway_test.go @@ -469,9 +469,6 @@ func TestGatewayStatusReportsRunningFromHealthProbe(t *testing.T) { if got := body["gateway_status"]; got != "running" { t.Fatalf("gateway_status = %#v, want %q", got, "running") } - if got := body["pid"]; got != float64(cmd.Process.Pid) { - t.Fatalf("pid = %#v, want %d", got, cmd.Process.Pid) - } if got := body["gateway_restart_required"]; got != false { t.Fatalf("gateway_restart_required = %#v, want false", got) } diff --git a/web/backend/api/pico.go b/web/backend/api/pico.go index d345d980c..8bef33ac8 100644 --- a/web/backend/api/pico.go +++ b/web/backend/api/pico.go @@ -10,6 +10,7 @@ import ( "time" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" ) // registerPicoRoutes binds Pico Channel management endpoints to the ServeMux. @@ -26,20 +27,55 @@ func (h *Handler) registerPicoRoutes(mux *http.ServeMux) { // createWsProxy creates a reverse proxy to the current gateway WebSocket endpoint. // The gateway bind host and port are resolved from the latest configuration. -func (h *Handler) createWsProxy() *httputil.ReverseProxy { - wsProxy := httputil.NewSingleHostReverseProxy(h.gatewayProxyURL()) - wsProxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, "Gateway unavailable: "+err.Error(), http.StatusBadGateway) +func (h *Handler) createWsProxy(origProtocol string, token string) *httputil.ReverseProxy { + wsProxy := &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + target := h.gatewayProxyURL() + r.SetURL(target) + r.Out.Header.Set(protocolKey, tokenPrefix+token) + }, + ModifyResponse: func(r *http.Response) error { + if prot := r.Header.Values(protocolKey); len(prot) > 0 { + r.Header.Del(protocolKey) + if origProtocol != "" { + r.Header.Set(protocolKey, origProtocol) + } + } + return nil + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + logger.Errorf("Failed to proxy WebSocket: %v", err) + http.Error(w, "Gateway unavailable: "+err.Error(), http.StatusBadGateway) + }, } return wsProxy } // handleWebSocketProxy wraps a reverse proxy to handle WebSocket connections. -// The reverse proxy forwards the incoming upgrade handshake as-is. +// It validates the client token before forwarding; rejects immediately on failure. func (h *Handler) handleWebSocketProxy() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - proxy := h.createWsProxy() - proxy.ServeHTTP(w, r) + gateway.mu.Lock() + gatewayAvailable := gateway.pidData != nil + gateway.mu.Unlock() + + if !gatewayAvailable { + logger.Warnf("Gateway not available for WebSocket proxy") + http.Error(w, "Gateway not available", http.StatusServiceUnavailable) + return + } + prot := r.Header.Values(protocolKey) + if len(prot) > 0 { + origProtocol := prot[0] + newToken := picoComposedToken(prot[0]) + if newToken != "" { + h.createWsProxy(origProtocol, newToken).ServeHTTP(w, r) + return + } + } + + logger.Warnf("Invalid Pico token: %v", prot) + http.Error(w, "Invalid Pico token", http.StatusForbidden) } } @@ -81,6 +117,11 @@ func (h *Handler) handleRegenPicoToken(w http.ResponseWriter, r *http.Request) { return } + // Refresh cached pico token. + gateway.mu.Lock() + gateway.picoToken = token + gateway.mu.Unlock() + wsURL := h.buildWsURL(r, cfg) w.Header().Set("Content-Type", "application/json") @@ -140,11 +181,15 @@ func (h *Handler) handlePicoSetup(w http.ResponseWriter, r *http.Request) { return } + // Reload config (EnsurePicoChannel may have modified it) and refresh cache. cfg, err := config.LoadConfig(h.configPath) if err != nil { http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError) return } + if changed { + refreshPicoToken(cfg) + } wsURL := h.buildWsURL(r, cfg) @@ -162,7 +207,7 @@ func generateSecureToken() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { // Fallback to something pseudo-random if crypto/rand fails - return fmt.Sprintf("pico_%x", time.Now().UnixNano()) + return fmt.Sprintf("%032x", time.Now().UnixNano()) } return hex.EncodeToString(b) } diff --git a/web/backend/api/pico_test.go b/web/backend/api/pico_test.go index aa377975d..beff4d77f 100644 --- a/web/backend/api/pico_test.go +++ b/web/backend/api/pico_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/sipeed/picoclaw/pkg/config" + ppid "github.com/sipeed/picoclaw/pkg/pid" ) func TestEnsurePicoChannel_FreshConfig(t *testing.T) { @@ -335,10 +336,22 @@ func TestHandleWebSocketProxyReloadsGatewayTargetFromConfig(t *testing.T) { t.Fatalf("SaveConfig() error = %v", err) } + gateway.pidData = &ppid.PidFileData{} + gateway.picoToken = "pico" req1 := httptest.NewRequest(http.MethodGet, "/pico/ws", nil) + req1.Header.Set(protocolKey, tokenPrefix+"wrong_token") rec1 := httptest.NewRecorder() handler(rec1, req1) + if rec1.Code != http.StatusForbidden { + t.Fatalf("first status = %d, want %d", rec1.Code, http.StatusForbidden) + } + + req1 = httptest.NewRequest(http.MethodGet, "/pico/ws", nil) + req1.Header.Set(protocolKey, tokenPrefix+"pico") + rec1 = httptest.NewRecorder() + handler(rec1, req1) + if rec1.Code != http.StatusOK { t.Fatalf("first status = %d, want %d", rec1.Code, http.StatusOK) } @@ -352,6 +365,7 @@ func TestHandleWebSocketProxyReloadsGatewayTargetFromConfig(t *testing.T) { } req2 := httptest.NewRequest(http.MethodGet, "/pico/ws", nil) + req2.Header.Set(protocolKey, tokenPrefix+"pico") rec2 := httptest.NewRecorder() handler(rec2, req2) diff --git a/web/backend/api/skills.go b/web/backend/api/skills.go index 3c2fb57dd..b2036f66c 100644 --- a/web/backend/api/skills.go +++ b/web/backend/api/skills.go @@ -309,14 +309,7 @@ func loadSkillContent(path string) (string, error) { } func globalConfigDir() string { - if home := os.Getenv(config.EnvHome); home != "" { - return home - } - home, err := os.UserHomeDir() - if err != nil { - return "" - } - return filepath.Join(home, ".picoclaw") + return config.GetHome() } func builtinSkillsDir() string { diff --git a/web/backend/middleware/middleware.go b/web/backend/middleware/middleware.go index 5e0dfeb90..a0b7eb998 100644 --- a/web/backend/middleware/middleware.go +++ b/web/backend/middleware/middleware.go @@ -1,7 +1,9 @@ package middleware import ( + "bufio" "fmt" + "net" "net/http" "runtime/debug" "time" @@ -44,6 +46,15 @@ func (rr *responseRecorder) Unwrap() http.ResponseWriter { return rr.ResponseWriter } +// Hijack implements http.Hijacker so that WebSocket upgrades work through +// the middleware layer. +func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := rr.ResponseWriter.(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, http.ErrNotSupported +} + // Logger logs each HTTP request with method, path, status code, and duration. func Logger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/web/backend/utils/runtime.go b/web/backend/utils/runtime.go index 772cd7ec0..0b9e30979 100644 --- a/web/backend/utils/runtime.go +++ b/web/backend/utils/runtime.go @@ -9,16 +9,13 @@ import ( "runtime" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" ) // GetPicoclawHome returns the picoclaw home directory. // Priority: $PICOCLAW_HOME > ~/.picoclaw func GetPicoclawHome() string { - if home := os.Getenv(config.EnvHome); home != "" { - return home - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".picoclaw") + return config.GetHome() } // GetDefaultConfigPath returns the default path to the picoclaw config file. @@ -47,6 +44,7 @@ func FindPicoclawBinary() string { } if exe, err := os.Executable(); err == nil { + logger.Debugf("Trying to find picoclaw binary in %s", exe) candidate := filepath.Join(filepath.Dir(exe), binaryName) if info, err := os.Stat(candidate); err == nil && !info.IsDir() { return candidate From 4f90909af34cbb9d67ca21e7eb20861c293eb7c7 Mon Sep 17 00:00:00 2001 From: Andy Lo-A-Foe Date: Wed, 25 Mar 2026 07:02:23 +0100 Subject: [PATCH 26/55] feat(bedrock): detect SSO token expiration and provide actionable error When AWS SSO credentials expire, provide a clear error message instructing the user to run 'aws sso login' to refresh their session. --- pkg/providers/bedrock/provider_bedrock.go | 31 ++++++++++ .../bedrock/provider_bedrock_test.go | 62 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/pkg/providers/bedrock/provider_bedrock.go b/pkg/providers/bedrock/provider_bedrock.go index 15c4f664e..9ca29455f 100644 --- a/pkg/providers/bedrock/provider_bedrock.go +++ b/pkg/providers/bedrock/provider_bedrock.go @@ -206,6 +206,10 @@ func (p *Provider) Chat( // Call Bedrock Converse API output, err := p.client.Converse(ctx, input) if err != nil { + // Check for SSO token expiration errors and provide actionable guidance + if isSSOTokenError(err) { + return nil, fmt.Errorf("bedrock converse: AWS credentials may have expired. If using AWS SSO, run 'aws sso login' to refresh: %w", err) + } return nil, fmt.Errorf("bedrock converse: %w", err) } @@ -580,3 +584,30 @@ func parseResponse(output *bedrockruntime.ConverseOutput) (*LLMResponse, error) Usage: usage, }, nil } + +// isSSOTokenError checks if the error is related to expired or invalid AWS SSO tokens. +// This helps provide actionable guidance when SSO credentials need to be refreshed. +// Only matches SSO-specific error patterns to avoid misclassifying other AWS credential errors. +func isSSOTokenError(err error) bool { + if err == nil { + return false + } + lower := strings.ToLower(err.Error()) + + // Check for specific SSO token expiration/refresh-related error patterns (case-insensitive) + // Avoid matching generic patterns that could match non-SSO AWS errors (e.g., STS ExpiredToken) + if strings.Contains(lower, "refresh cached sso token") { + return true + } + if strings.Contains(lower, "read cached sso token") { + return true + } + if strings.Contains(lower, "sso oidc") { + return true + } + if strings.Contains(lower, "invalidgrantexception") { + return true + } + + return false +} diff --git a/pkg/providers/bedrock/provider_bedrock_test.go b/pkg/providers/bedrock/provider_bedrock_test.go index 754d112ee..882c2971c 100644 --- a/pkg/providers/bedrock/provider_bedrock_test.go +++ b/pkg/providers/bedrock/provider_bedrock_test.go @@ -8,6 +8,7 @@ package bedrock import ( + "fmt" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -539,3 +540,64 @@ func TestParseResponse_ToolCallWithNilInput(t *testing.T) { assert.NotNil(t, resp.ToolCalls[0].Arguments) assert.Empty(t, resp.ToolCalls[0].Arguments) } + +func TestIsSSOTokenError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "nil error", + err: nil, + expected: false, + }, + { + name: "generic error", + err: fmt.Errorf("connection refused"), + expected: false, + }, + { + name: "SSO config error not expiration", + err: fmt.Errorf("failed to load SSO profile: invalid SSO session"), + expected: false, + }, + { + name: "STS ExpiredToken error", + err: fmt.Errorf("ExpiredToken: The security token included in the request is expired"), + expected: false, + }, + { + name: "SSO token refresh error", + err: fmt.Errorf("refresh cached SSO token failed"), + expected: true, + }, + { + name: "InvalidGrantException", + err: fmt.Errorf("operation error SSO OIDC: CreateToken, InvalidGrantException"), + expected: true, + }, + { + name: "SSO OIDC error", + err: fmt.Errorf("operation error SSO OIDC: CreateToken, failed"), + expected: true, + }, + { + name: "full SSO error message", + err: fmt.Errorf("get identity: get credentials: failed to refresh cached credentials, refresh cached SSO token failed, unable to refresh SSO token"), + expected: true, + }, + { + name: "SSO token file missing", + err: fmt.Errorf("get identity: get credentials: failed to refresh cached credentials, failed to read cached SSO token file, open ~/.aws/sso/cache/abc123.json: no such file or directory"), + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isSSOTokenError(tt.err) + assert.Equal(t, tt.expected, result) + }) + } +} From 6ea364e67dead37e09ffc8d535ccc262a0ffe202 Mon Sep 17 00:00:00 2001 From: zeed zhao Date: Sun, 29 Mar 2026 13:11:43 +0800 Subject: [PATCH 27/55] feat(web): protect launcher dashboard with token and SPA login (#1953) Add token-based authentication for the Launcher's embedded Web Dashboard. - Ephemeral token generated in-memory each run (or via PICOCLAW_LAUNCHER_TOKEN env var) - HMAC-SHA256 session cookie (HttpOnly, SameSite=Lax, Secure when HTTPS) - Bearer token support for API/script access - Rate limiting on login (10 attempts/IP/min) - Referrer-Policy: no-referrer on all responses - POST-only logout with JSON content-type (CSRF-safe) - System tray "Copy dashboard token" action - Login page shows contextual help (console/tray/log file path) - Path traversal protection via path.Clean - X-Forwarded-Host/Port/Proto support for reverse proxy deployments - Full i18n support (English, Chinese) Co-Authored-By: Claude Opus 4.6 --- docs/configuration.md | 12 + docs/docker.md | 2 +- docs/zh/configuration.md | 12 + docs/zh/docker.md | 4 +- go.mod | 1 + go.sum | 2 + pkg/config/security_integration_test.go | 2 +- web/backend/api/auth.go | 142 +++++++++++ web/backend/api/auth_login_limiter.go | 59 +++++ web/backend/api/auth_test.go | 218 +++++++++++++++++ web/backend/api/gateway_host.go | 118 ++++++++- web/backend/api/gateway_host_test.go | 62 ++++- web/backend/api/pico.go | 6 +- web/backend/app_runtime.go | 8 +- web/backend/i18n.go | 6 + web/backend/launcherconfig/config.go | 35 +++ web/backend/launcherconfig/config_test.go | 47 ++++ web/backend/main.go | 67 +++++- .../middleware/launcher_dashboard_auth.go | 226 ++++++++++++++++++ .../launcher_dashboard_auth_test.go | 162 +++++++++++++ web/backend/middleware/referrer_policy.go | 12 + web/backend/systray.go | 13 + web/backend/tray_offers_copy.go | 5 + web/backend/tray_offers_copy_stub.go | 5 + web/frontend/src/api/channels.ts | 4 +- web/frontend/src/api/gateway.ts | 4 +- web/frontend/src/api/http.ts | 42 ++++ web/frontend/src/api/launcher-auth.ts | 48 ++++ web/frontend/src/api/models.ts | 3 +- web/frontend/src/api/oauth.ts | 4 +- web/frontend/src/api/pico.ts | 4 +- web/frontend/src/api/sessions.ts | 8 +- web/frontend/src/api/skills.ts | 6 +- web/frontend/src/api/system.ts | 4 +- web/frontend/src/api/tools.ts | 4 +- .../src/components/config/config-page.tsx | 3 +- .../src/components/config/raw-config-page.tsx | 5 +- web/frontend/src/features/chat/websocket.ts | 12 + web/frontend/src/i18n/locales/en.json | 14 ++ web/frontend/src/i18n/locales/zh.json | 14 ++ web/frontend/src/lib/launcher-login-path.ts | 9 + web/frontend/src/routeTree.gen.ts | 21 ++ web/frontend/src/routes/__root.tsx | 43 +++- web/frontend/src/routes/launcher-login.tsx | 184 ++++++++++++++ 44 files changed, 1617 insertions(+), 45 deletions(-) create mode 100644 web/backend/api/auth.go create mode 100644 web/backend/api/auth_login_limiter.go create mode 100644 web/backend/api/auth_test.go create mode 100644 web/backend/middleware/launcher_dashboard_auth.go create mode 100644 web/backend/middleware/launcher_dashboard_auth_test.go create mode 100644 web/backend/middleware/referrer_policy.go create mode 100644 web/backend/tray_offers_copy.go create mode 100644 web/backend/tray_offers_copy_stub.go create mode 100644 web/frontend/src/api/http.ts create mode 100644 web/frontend/src/api/launcher-auth.ts create mode 100644 web/frontend/src/lib/launcher-login-path.ts create mode 100644 web/frontend/src/routes/launcher-login.tsx diff --git a/docs/configuration.md b/docs/configuration.md index 9360d3897..3462767e6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,6 +67,18 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa > **Note:** Changes to `AGENT.md`, `SOUL.md`, `USER.md` and `memory/MEMORY.md` are automatically detected at runtime via file modification time (mtime) tracking. You do **not** need to restart the gateway after editing these files — the agent picks up the new content on the next request. +### Web launcher dashboard + +**picoclaw-launcher** serves a browser UI that requires sign-in first. By default, the **dashboard token** and **session signing key** are **generated in memory on each start** (a new random token after every restart). Set **`PICOCLAW_LAUNCHER_TOKEN`** to pin a fixed token for that process (startup logs do not print the secret when this env var is used). + +**Where to read the token**: In **console mode** (`-console`), it is printed at startup. In **tray / GUI mode**, use the tray action **Copy dashboard token**, and check **`$PICOCLAW_HOME/logs/launcher.log`** (typically `~/.picoclaw/logs/launcher.log` if `PICOCLAW_HOME` is unset) for the random token logged on startup. The login page shows hints that match how the launcher is running (including the absolute log path); **responses do not include the token itself**. + +- **Config file**: Same directory as `config.json` (or the file pointed to by `PICOCLAW_CONFIG`). The launcher-specific file is `launcher-config.json`. +- **Sign-in and links**: Enter the token on the login page, or open with `?token=` when the browser is launched automatically. All responses include **`Referrer-Policy: no-referrer`** to reduce leakage of `token` via the `Referer` header. +- **Sign-out**: Use **`POST /api/auth/logout`** with **`Content-Type: application/json`** (body may be `{}`). Do not rely on a GET URL for logout (CSRF-safe pattern). +- **Brute-force**: **`POST /api/auth/login`** is **rate-limited per client IP per minute** (HTTP 429 when exceeded). +- **Session lifetime**: The HttpOnly session cookie lasts about **7 days** by default; sign in again with the token after it expires. + ### Skill Sources By default, skills are loaded from: diff --git a/docs/docker.md b/docs/docker.md index a00dfbe9f..18de1ee83 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -48,7 +48,7 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d Open http://localhost:18800 in your browser. The launcher manages the gateway process automatically. > [!WARNING] -> The web console does not yet support authentication. Avoid exposing it to the public internet. +> The web console uses a dashboard token (in-memory per run unless `PICOCLAW_LAUNCHER_TOKEN` is set). **Do not** expose the launcher to untrusted networks or the public internet. See [Web launcher dashboard](configuration.md#web-launcher-dashboard) in the Configuration Guide. ### Agent Mode (One-shot) diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md index 335566d36..3b0ac9a50 100644 --- a/docs/zh/configuration.md +++ b/docs/zh/configuration.md @@ -51,6 +51,18 @@ PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/work > **提示:** 对 `AGENT.md`、`SOUL.md`、`USER.md` 和 `memory/MEMORY.md` 的修改会通过文件修改时间(mtime)在运行时自动检测。**无需重启 gateway**,Agent 将在下一次请求时自动加载最新内容。 +### Web 启动器控制台 + +用 **picoclaw-launcher** 打开浏览器控制台前需要先登录。**访问口令**与 **会话签名密钥**默认在**每次启动时在内存中生成**(重启后随机口令会变)。若设置环境变量 **`PICOCLAW_LAUNCHER_TOKEN`**,则该进程使用固定口令(启动日志中不会打印具体口令值)。 + +**到哪里找口令**:**控制台模式**(`-console`)请看启动时的终端输出;**托盘 / GUI 模式**可使用托盘菜单中的「复制控制台口令」,并在 **`$PICOCLAW_HOME/logs/launcher.log`**(未设置 `PICOCLAW_HOME` 时一般为 `~/.picoclaw/logs/launcher.log`)中查看本次启动写入的随机口令。登录页在未登录时会根据当前运行方式展示提示(含日志文件绝对路径等;**接口与页面均不会返回口令本身**)。 + +- **配置文件**:与 `config.json` 同一目录(若设置了 `PICOCLAW_CONFIG`,则与它所指的文件同目录)。启动器专用文件名为 `launcher-config.json`。 +- **登录与链接**:在登录页输入口令;自动打开浏览器时可在 URL 上使用 `?token=`。全站响应携带 **`Referrer-Policy: no-referrer`**,减轻 `token` 经 `Referer` 头泄露的风险。 +- **退出登录**:应使用 **`POST /api/auth/logout`**,且请求头为 **`Content-Type: application/json`**(请求体可为 `{}`),勿使用可被第三方页面触发的 GET 链接登出。 +- **暴力尝试**:`POST /api/auth/login` 对同一远程地址有 **每分钟尝试次数上限**(超限返回 HTTP 429)。 +- **会话时长**:登录后的 HttpOnly 会话 Cookie 默认约 **7 天**有效,到期需重新用口令登录。 + ### 技能来源 (Skill Sources) 默认情况下,技能会按以下顺序加载: diff --git a/docs/zh/docker.md b/docs/zh/docker.md index 10bc46544..8aed1e86b 100644 --- a/docs/zh/docker.md +++ b/docs/zh/docker.md @@ -42,10 +42,10 @@ docker compose -f docker/docker-compose.yml --profile gateway down docker compose -f docker/docker-compose.yml --profile launcher up -d ``` -在浏览器中打开 http://localhost:18800。Launcher 会自动管理 Gateway 进程。 +在浏览器中打开 。Launcher 会自动管理 Gateway 进程。 > [!WARNING] -> Web 控制台尚不支持身份验证。请勿将其暴露到公网。 +> Web 控制台通过 dashboard 令牌鉴权(默认每次启动在内存中生成;可用 `PICOCLAW_LAUNCHER_TOKEN` 固定)。**不要**将启动器暴露到不可信网络或公网。完整说明见 [配置指南](configuration.md) 中的「Web 启动器控制台」一节。 ### Agent 模式 (一次性运行) diff --git a/go.mod b/go.mod index 54c275102..202839c29 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect diff --git a/go.sum b/go.sum index ae12473f3..c64f3593d 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY= github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY= diff --git a/pkg/config/security_integration_test.go b/pkg/config/security_integration_test.go index 24170f84b..6ca8637f4 100644 --- a/pkg/config/security_integration_test.go +++ b/pkg/config/security_integration_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -// Test JSON unmarshal of private fields +// Test JSON unmarshal of private fields (unexported fields are never filled, with or without json tag). func TestJSONUnmarshalPrivateFields(t *testing.T) { type testStruct struct { PublicField string `json:"public"` diff --git a/web/backend/api/auth.go b/web/backend/api/auth.go new file mode 100644 index 000000000..b9b4d5f66 --- /dev/null +++ b/web/backend/api/auth.go @@ -0,0 +1,142 @@ +package api + +import ( + "crypto/subtle" + "encoding/json" + "io" + "net/http" + "strings" + + "github.com/sipeed/picoclaw/web/backend/middleware" +) + +// LauncherAuthRouteOpts configures dashboard token login handlers. +type LauncherAuthRouteOpts struct { + DashboardToken string + SessionCookie string + SecureCookie func(*http.Request) bool + // TokenHelp is returned on unauthenticated /api/auth/status responses (no secrets). + TokenHelp LauncherAuthTokenHelp +} + +// LauncherAuthTokenHelp tells the login UI where users can find the dashboard token. +type LauncherAuthTokenHelp struct { + EnvVarName string `json:"env_var_name"` + LogFileAbs string `json:"log_file,omitempty"` + TrayCopyMenu bool `json:"tray_copy_menu"` + ConsoleStdout bool `json:"console_stdout"` +} + +type launcherAuthLoginBody struct { + Token string `json:"token"` +} + +type launcherAuthStatusResponse struct { + Authenticated bool `json:"authenticated"` + TokenHelp *LauncherAuthTokenHelp `json:"token_help,omitempty"` +} + +// RegisterLauncherAuthRoutes registers /api/auth/login|logout|status. +func RegisterLauncherAuthRoutes(mux *http.ServeMux, opts LauncherAuthRouteOpts) { + secure := opts.SecureCookie + if secure == nil { + secure = middleware.DefaultLauncherDashboardSecureCookie + } + h := &launcherAuthHandlers{ + token: opts.DashboardToken, + sessionCookie: opts.SessionCookie, + secureCookie: secure, + tokenHelp: opts.TokenHelp, + loginLimit: newLoginRateLimiter(), + } + mux.HandleFunc("POST /api/auth/login", h.handleLogin) + mux.HandleFunc("POST /api/auth/logout", h.handleLogout) + mux.HandleFunc("GET /api/auth/status", h.handleStatus) +} + +type launcherAuthHandlers struct { + token string + sessionCookie string + secureCookie func(*http.Request) bool + tokenHelp LauncherAuthTokenHelp + loginLimit *loginRateLimiter +} + +func (h *launcherAuthHandlers) handleLogin(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var body launcherAuthLoginBody + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&body); err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"invalid JSON"}`)) + return + } + ip := clientIPForLimiter(r) + if !h.loginLimit.allow(ip) { + w.WriteHeader(http.StatusTooManyRequests) + _, _ = w.Write([]byte(`{"error":"too many login attempts"}`)) + return + } + in := strings.TrimSpace(body.Token) + if len(in) != len(h.token) || subtle.ConstantTimeCompare([]byte(in), []byte(h.token)) != 1 { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"error":"invalid token"}`)) + return + } + + middleware.SetLauncherDashboardSessionCookie(w, r, h.sessionCookie, h.secureCookie) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"status":"ok"}`)) +} + +func (h *launcherAuthHandlers) handleLogout(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + _, _ = w.Write([]byte(`{"error":"method not allowed"}`)) + return + } + ct := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type"))) + if !strings.HasPrefix(ct, "application/json") { + w.WriteHeader(http.StatusUnsupportedMediaType) + _, _ = w.Write([]byte(`{"error":"Content-Type must be application/json"}`)) + return + } + dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, logoutBodyMaxBytes)) + if err := dec.Decode(&struct{}{}); err != nil && err != io.EOF { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"invalid JSON body"}`)) + return + } + if err := dec.Decode(&struct{}{}); err != io.EOF { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"invalid JSON body"}`)) + return + } + + middleware.ClearLauncherDashboardSessionCookie(w, r, h.secureCookie) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"status":"ok"}`)) +} + +func (h *launcherAuthHandlers) handleStatus(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + ok := false + if c, err := r.Cookie(middleware.LauncherDashboardCookieName); err == nil { + ok = subtle.ConstantTimeCompare([]byte(c.Value), []byte(h.sessionCookie)) == 1 + } + if ok { + _, _ = w.Write([]byte(`{"authenticated":true}`)) + return + } + resp := launcherAuthStatusResponse{ + Authenticated: false, + TokenHelp: &h.tokenHelp, + } + enc, err := json.Marshal(resp) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"error":"internal error"}`)) + return + } + _, _ = w.Write(enc) +} diff --git a/web/backend/api/auth_login_limiter.go b/web/backend/api/auth_login_limiter.go new file mode 100644 index 000000000..d606f03cf --- /dev/null +++ b/web/backend/api/auth_login_limiter.go @@ -0,0 +1,59 @@ +package api + +import ( + "net" + "net/http" + "strings" + "sync" + "time" +) + +const ( + loginAttemptsPerIP = 10 + loginAttemptWindow = time.Minute + logoutBodyMaxBytes = 4096 +) + +// loginRateLimiter limits POST /api/auth/login attempts per IP per minute. +type loginRateLimiter struct { + mu sync.Mutex + now func() time.Time + byIP map[string][]time.Time +} + +func newLoginRateLimiter() *loginRateLimiter { + return &loginRateLimiter{ + now: time.Now, + byIP: make(map[string][]time.Time), + } +} + +// allow reserves a slot for this request; false means rate limit exceeded. +func (l *loginRateLimiter) allow(ip string) bool { + l.mu.Lock() + defer l.mu.Unlock() + now := l.now() + cutoff := now.Add(-loginAttemptWindow) + times := l.byIP[ip] + var kept []time.Time + for _, ts := range times { + if ts.After(cutoff) { + kept = append(kept, ts) + } + } + if len(kept) >= loginAttemptsPerIP { + l.byIP[ip] = kept + return false + } + kept = append(kept, now) + l.byIP[ip] = kept + return true +} + +func clientIPForLimiter(r *http.Request) string { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return strings.TrimSpace(r.RemoteAddr) + } + return host +} diff --git a/web/backend/api/auth_test.go b/web/backend/api/auth_test.go new file mode 100644 index 000000000..d2624a440 --- /dev/null +++ b/web/backend/api/auth_test.go @@ -0,0 +1,218 @@ +package api + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/sipeed/picoclaw/web/backend/middleware" +) + +func TestLauncherAuthLoginAndStatus(t *testing.T) { + key := make([]byte, 32) + for i := range key { + key[i] = 0x55 + } + const tok = "dashboard-test-token-9" + sess := middleware.SessionCookieValue(key, tok) + mux := http.NewServeMux() + RegisterLauncherAuthRoutes(mux, LauncherAuthRouteOpts{ + DashboardToken: tok, + SessionCookie: sess, + TokenHelp: LauncherAuthTokenHelp{ + EnvVarName: "PICOCLAW_LAUNCHER_TOKEN", + LogFileAbs: "/tmp/launcher.log", + TrayCopyMenu: true, + ConsoleStdout: false, + }, + }) + + t.Run("status_unauthenticated", func(t *testing.T) { + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/auth/status", nil)) + if rec.Code != http.StatusOK { + t.Fatalf("status code = %d", rec.Code) + } + var body struct { + Authenticated bool `json:"authenticated"` + TokenHelp *LauncherAuthTokenHelp `json:"token_help"` + } + if err := json.NewDecoder(rec.Body).Decode(&body); err != nil { + t.Fatal(err) + } + if body.Authenticated || body.TokenHelp == nil { + t.Fatalf("unexpected body: %+v", body) + } + if body.TokenHelp.EnvVarName != "PICOCLAW_LAUNCHER_TOKEN" || body.TokenHelp.LogFileAbs != "/tmp/launcher.log" { + t.Fatalf("token_help = %+v", body.TokenHelp) + } + }) + + t.Run("login_ok", func(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(`{"token":"`+tok+`"}`)) + req.Header.Set("Content-Type", "application/json") + req.RemoteAddr = "127.0.0.1:12345" + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("login code = %d body=%s", rec.Code, rec.Body.String()) + } + cookies := rec.Result().Cookies() + if len(cookies) != 1 || cookies[0].Name != middleware.LauncherDashboardCookieName { + t.Fatalf("cookies = %#v", cookies) + } + }) + + t.Run("status_authenticated", func(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/auth/status", nil) + req.AddCookie(&http.Cookie{Name: middleware.LauncherDashboardCookieName, Value: sess}) + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status code = %d", rec.Code) + } + if !bytes.Contains(rec.Body.Bytes(), []byte(`"authenticated":true`)) { + t.Fatalf("body = %s", rec.Body.String()) + } + if strings.Contains(rec.Body.String(), "token_help") { + t.Fatalf("authenticated response should omit token_help: %s", rec.Body.String()) + } + }) +} + +func TestLauncherAuthLogoutRequiresPostAndJSON(t *testing.T) { + key := make([]byte, 32) + sess := middleware.SessionCookieValue(key, "tok") + mux := http.NewServeMux() + RegisterLauncherAuthRoutes(mux, LauncherAuthRouteOpts{ + DashboardToken: "tok", + SessionCookie: sess, + TokenHelp: LauncherAuthTokenHelp{EnvVarName: "PICOCLAW_LAUNCHER_TOKEN"}, + }) + + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/auth/logout", nil)) + if rec.Code != http.StatusMethodNotAllowed && rec.Code != http.StatusNotFound { + t.Fatalf("GET logout: code = %d (expected 404 or 405)", rec.Code) + } + + rec2 := httptest.NewRecorder() + req2 := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil) + req2.Header.Set("Content-Type", "application/x-www-form-urlencoded") + mux.ServeHTTP(rec2, req2) + if rec2.Code != http.StatusUnsupportedMediaType { + t.Fatalf("wrong content-type: code = %d body=%s", rec2.Code, rec2.Body.String()) + } + + rec3 := httptest.NewRecorder() + req3 := httptest.NewRequest(http.MethodPost, "/api/auth/logout", strings.NewReader(`{}`)) + req3.Header.Set("Content-Type", "application/json") + mux.ServeHTTP(rec3, req3) + if rec3.Code != http.StatusOK { + t.Fatalf("POST json logout: code = %d", rec3.Code) + } +} + +func TestLauncherAuthLoginRateLimit(t *testing.T) { + key := make([]byte, 32) + const tok = "rate-limit-tok-xxxxxxxx" + sess := middleware.SessionCookieValue(key, tok) + mux := http.NewServeMux() + RegisterLauncherAuthRoutes(mux, LauncherAuthRouteOpts{ + DashboardToken: tok, + SessionCookie: sess, + TokenHelp: LauncherAuthTokenHelp{EnvVarName: "X"}, + }) + + // 11 failing logins by wrong token; each consumes allow() slot after valid JSON. + wrongBody := `{"token":"wrong"}` + for i := 0; i < loginAttemptsPerIP; i++ { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(wrongBody)) + req.Header.Set("Content-Type", "application/json") + req.RemoteAddr = "192.168.5.5:9999" + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusUnauthorized { + t.Fatalf("iter %d: want 401 got %d %s", i, rec.Code, rec.Body.String()) + } + } + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(wrongBody)) + req.Header.Set("Content-Type", "application/json") + req.RemoteAddr = "192.168.5.5:9999" + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusTooManyRequests { + t.Fatalf("11th attempt: want 429 got %d %s", rec.Code, rec.Body.String()) + } +} + +func TestLoginRateLimiterWindow(t *testing.T) { + l := newLoginRateLimiter() + t0 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + l.now = func() time.Time { return t0 } + for i := 0; i < loginAttemptsPerIP; i++ { + if !l.allow("ip") { + t.Fatalf("want allow at %d", i) + } + } + if l.allow("ip") { + t.Fatal("want deny on 11th") + } + l.now = func() time.Time { return t0.Add(loginAttemptWindow + time.Second) } + if !l.allow("ip") { + t.Fatal("want allow after window") + } +} + +func TestReferrerPolicyMiddleware(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }) + h := middleware.ReferrerPolicyNoReferrer(next) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) + if got := rec.Header().Get("Referrer-Policy"); got != "no-referrer" { + t.Fatalf("Referrer-Policy = %q", got) + } +} + +func TestLauncherAuthLogoutEmptyBody(t *testing.T) { + key := make([]byte, 32) + sess := middleware.SessionCookieValue(key, "tok") + mux := http.NewServeMux() + RegisterLauncherAuthRoutes(mux, LauncherAuthRouteOpts{ + DashboardToken: "tok", + SessionCookie: sess, + TokenHelp: LauncherAuthTokenHelp{EnvVarName: "X"}, + }) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil) + req.Header.Set("Content-Type", "application/json") + req.Body = http.NoBody + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("code = %d", rec.Code) + } +} + +func TestLauncherAuthLogoutRejectsTrailingJSON(t *testing.T) { + key := make([]byte, 32) + sess := middleware.SessionCookieValue(key, "tok") + mux := http.NewServeMux() + RegisterLauncherAuthRoutes(mux, LauncherAuthRouteOpts{ + DashboardToken: "tok", + SessionCookie: sess, + TokenHelp: LauncherAuthTokenHelp{EnvVarName: "X"}, + }) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", strings.NewReader(`{}{}`)) + req.Header.Set("Content-Type", "application/json") + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusBadRequest { + t.Fatalf("want 400 got %d %s", rec.Code, rec.Body.String()) + } +} diff --git a/web/backend/api/gateway_host.go b/web/backend/api/gateway_host.go index 592571a28..6190f0c7c 100644 --- a/web/backend/api/gateway_host.go +++ b/web/backend/api/gateway_host.go @@ -93,16 +93,120 @@ func requestWSScheme(r *http.Request) string { return "ws" } -func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string { - host := h.effectiveGatewayBindHost(cfg) - if host == "" || host == "0.0.0.0" { - host = requestHostName(r) +// requestHTTPScheme returns http or https for URLs that are not WebSockets (e.g. SSE). +func requestHTTPScheme(r *http.Request) string { + if forwarded := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); forwarded != "" { + proto := strings.ToLower(strings.TrimSpace(strings.Split(forwarded, ",")[0])) + if proto == "https" || proto == "wss" { + return "https" + } + if proto == "http" || proto == "ws" { + return "http" + } } - // Use web server port instead of gateway port to avoid exposing extra ports - // The WebSocket connection will be proxied by the backend to the gateway + if r.TLS != nil { + return "https" + } + return "http" +} + +// forwardedHostFirst returns the client-visible host from reverse-proxy / tunnel headers +// (e.g. VS Code port forwarding, nginx). Empty if unset. +func forwardedHostFirst(r *http.Request) string { + raw := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")) + if raw == "" { + raw = forwardedRFC7239Host(r) + } + if raw == "" { + return "" + } + if i := strings.IndexByte(raw, ','); i >= 0 { + raw = strings.TrimSpace(raw[:i]) + } + return raw +} + +// forwardedRFC7239Host parses host= from the first Forwarded header element (RFC 7239). +func forwardedRFC7239Host(r *http.Request) string { + v := strings.TrimSpace(r.Header.Get("Forwarded")) + if v == "" { + return "" + } + first := strings.TrimSpace(strings.Split(v, ",")[0]) + for _, part := range strings.Split(first, ";") { + part = strings.TrimSpace(part) + low := strings.ToLower(part) + if !strings.HasPrefix(low, "host=") { + continue + } + val := strings.TrimSpace(part[strings.IndexByte(part, '=')+1:]) + if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' { + val = val[1 : len(val)-1] + } + return val + } + return "" +} + +// forwardedPortFirst returns the first X-Forwarded-Port value, or empty. +func forwardedPortFirst(r *http.Request) string { + raw := strings.TrimSpace(r.Header.Get("X-Forwarded-Port")) + if raw == "" { + return "" + } + if i := strings.IndexByte(raw, ','); i >= 0 { + raw = strings.TrimSpace(raw[:i]) + } + return raw +} + +// clientVisiblePort picks the TCP port the browser uses to reach this app (after proxies). +// Used by picoWebUIAddr → buildWsURL / buildPicoEventsURL / buildPicoSendURL so WebSocket and +// HTTP URLs match the dashboard page origin (cookies / token flow behind tunnels and reverse proxies). +func clientVisiblePort(r *http.Request, serverListenPort int) string { + if p := forwardedPortFirst(r); p != "" { + return p + } + if _, port, err := net.SplitHostPort(r.Host); err == nil && port != "" { + return port + } + if requestHTTPScheme(r) == "https" { + return "443" + } + return strconv.Itoa(serverListenPort) +} + +// joinClientVisibleHostPort builds host:port for absolute URLs returned to the browser. +func joinClientVisibleHostPort(r *http.Request, host string, serverListenPort int) string { + if h, p, err := net.SplitHostPort(host); err == nil { + return net.JoinHostPort(h, p) + } + return net.JoinHostPort(host, clientVisiblePort(r, serverListenPort)) +} + +// picoWebUIAddr is host:port for URLs returned to the browser (/pico/ws, /pico/events, /pico/send). +// It must match the HTTP Host the client used (or X-Forwarded-*), not cfg.Gateway.Host — otherwise +// e.g. page on localhost with ws_url 127.0.0.1 omits cookies and the dashboard auth handshake fails. +func (h *Handler) picoWebUIAddr(r *http.Request) string { wsPort := h.serverPort if wsPort == 0 { wsPort = 18800 // default web server port } - return requestWSScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(wsPort)) + "/pico/ws" + if fwdHost := forwardedHostFirst(r); fwdHost != "" { + return joinClientVisibleHostPort(r, fwdHost, wsPort) + } + host := requestHostName(r) + return net.JoinHostPort(host, strconv.Itoa(wsPort)) +} + +func (h *Handler) buildWsURL(r *http.Request) string { + return requestWSScheme(r) + "://" + h.picoWebUIAddr(r) + "/pico/ws" +} + +func (h *Handler) buildPicoEventsURL(r *http.Request) string { + return requestHTTPScheme(r) + "://" + h.picoWebUIAddr(r) + "/pico/events" +} + +func (h *Handler) buildPicoSendURL(r *http.Request) string { + return requestHTTPScheme(r) + "://" + h.picoWebUIAddr(r) + "/pico/send" } diff --git a/web/backend/api/gateway_host_test.go b/web/backend/api/gateway_host_test.go index ae3434862..7150b6fee 100644 --- a/web/backend/api/gateway_host_test.go +++ b/web/backend/api/gateway_host_test.go @@ -51,9 +51,16 @@ func TestBuildWsURLUsesRequestHostWhenLauncherPublicSaved(t *testing.T) { req := httptest.NewRequest("GET", "http://launcher.local/api/pico/token", nil) req.Host = "192.168.1.9:18800" - if got := h.buildWsURL(req, cfg); got != "ws://192.168.1.9:18800/pico/ws" { + if got := h.buildWsURL(req); got != "ws://192.168.1.9:18800/pico/ws" { t.Fatalf("buildWsURL() = %q, want %q", got, "ws://192.168.1.9:18800/pico/ws") } + + if got := h.buildPicoEventsURL(req); got != "http://192.168.1.9:18800/pico/events" { + t.Fatalf("buildPicoEventsURL() = %q, want %q", got, "http://192.168.1.9:18800/pico/events") + } + if got := h.buildPicoSendURL(req); got != "http://192.168.1.9:18800/pico/send" { + t.Fatalf("buildPicoSendURL() = %q, want %q", got, "http://192.168.1.9:18800/pico/send") + } } func TestGatewayProbeHostUsesLoopbackForWildcardBind(t *testing.T) { @@ -147,7 +154,7 @@ func TestBuildWsURLUsesWSSWhenForwardedProtoIsHTTPS(t *testing.T) { req.Host = "chat.example.com" req.Header.Set("X-Forwarded-Proto", "https") - if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18800/pico/ws" { + if got := h.buildWsURL(req); got != "wss://chat.example.com:18800/pico/ws" { t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18800/pico/ws") } } @@ -164,11 +171,45 @@ func TestBuildWsURLUsesWSSWhenRequestIsTLS(t *testing.T) { req.Host = "secure.example.com" req.TLS = &tls.ConnectionState{} - if got := h.buildWsURL(req, cfg); got != "wss://secure.example.com:18800/pico/ws" { + if got := h.buildWsURL(req); got != "wss://secure.example.com:18800/pico/ws" { t.Fatalf("buildWsURL() = %q, want %q", got, "wss://secure.example.com:18800/pico/ws") } } +func TestBuildPicoURLsPreferXForwardedHost(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + launcherPath := launcherconfig.PathForAppConfig(configPath) + if err := launcherconfig.Save(launcherPath, launcherconfig.Config{ + Port: 18800, + Public: true, + }); err != nil { + t.Fatalf("launcherconfig.Save() error = %v", err) + } + + h := NewHandler(configPath) + h.SetServerOptions(18800, false, false, nil) + + cfg := config.DefaultConfig() + cfg.Gateway.Host = "0.0.0.0" + cfg.Gateway.Port = 18790 + + req := httptest.NewRequest("GET", "http://127.0.0.1:18800/api/pico/token", nil) + req.Host = "127.0.0.1:18800" + req.Header.Set("X-Forwarded-Host", "vscode-tunnel.example.com") + req.Header.Set("X-Forwarded-Proto", "https") + req.Header.Set("X-Forwarded-Port", "443") + + if got := h.buildPicoEventsURL(req); got != "https://vscode-tunnel.example.com:443/pico/events" { + t.Fatalf("buildPicoEventsURL() = %q, want %q", got, "https://vscode-tunnel.example.com:443/pico/events") + } + if got := h.buildPicoSendURL(req); got != "https://vscode-tunnel.example.com:443/pico/send" { + t.Fatalf("buildPicoSendURL() = %q, want %q", got, "https://vscode-tunnel.example.com:443/pico/send") + } + if got := h.buildWsURL(req); got != "wss://vscode-tunnel.example.com:443/pico/ws" { + t.Fatalf("buildWsURL() = %q, want %q", got, "wss://vscode-tunnel.example.com:443/pico/ws") + } +} + func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) { configPath := filepath.Join(t.TempDir(), "config.json") h := NewHandler(configPath) @@ -182,7 +223,20 @@ func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) { req.TLS = &tls.ConnectionState{} req.Header.Set("X-Forwarded-Proto", "http") - if got := h.buildWsURL(req, cfg); got != "ws://chat.example.com:18800/pico/ws" { + if got := h.buildWsURL(req); got != "ws://chat.example.com:18800/pico/ws" { t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com:18800/pico/ws") } } + +func TestBuildWsURLUsesRequestHostNotGatewayBindLoopback(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + h := NewHandler(configPath) + h.SetServerOptions(18800, false, false, nil) + + req := httptest.NewRequest("GET", "http://localhost:18800/api/pico/token", nil) + req.Host = "localhost:18800" + + if got := h.buildWsURL(req); got != "ws://localhost:18800/pico/ws" { + t.Fatalf("buildWsURL() = %q, want %q", got, "ws://localhost:18800/pico/ws") + } +} diff --git a/web/backend/api/pico.go b/web/backend/api/pico.go index d345d980c..a3f1a4ffb 100644 --- a/web/backend/api/pico.go +++ b/web/backend/api/pico.go @@ -53,7 +53,7 @@ func (h *Handler) handleGetPicoToken(w http.ResponseWriter, r *http.Request) { return } - wsURL := h.buildWsURL(r, cfg) + wsURL := h.buildWsURL(r) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ @@ -81,7 +81,7 @@ func (h *Handler) handleRegenPicoToken(w http.ResponseWriter, r *http.Request) { return } - wsURL := h.buildWsURL(r, cfg) + wsURL := h.buildWsURL(r) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ @@ -146,7 +146,7 @@ func (h *Handler) handlePicoSetup(w http.ResponseWriter, r *http.Request) { return } - wsURL := h.buildWsURL(r, cfg) + wsURL := h.buildWsURL(r) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ diff --git a/web/backend/app_runtime.go b/web/backend/app_runtime.go index e3a9ec64f..ab564db2c 100644 --- a/web/backend/app_runtime.go +++ b/web/backend/app_runtime.go @@ -55,8 +55,12 @@ func shutdownApp() { } func openBrowser() error { - if serverAddr == "" { + target := browserLaunchURL + if target == "" { + target = serverAddr + } + if target == "" { return fmt.Errorf("server address not set") } - return utils.OpenBrowser(serverAddr) + return utils.OpenBrowser(target) } diff --git a/web/backend/i18n.go b/web/backend/i18n.go index 9cda9e5d5..106df8506 100644 --- a/web/backend/i18n.go +++ b/web/backend/i18n.go @@ -24,6 +24,8 @@ const ( AppTooltip TranslationKey = "AppTooltip" MenuOpen TranslationKey = "MenuOpen" MenuOpenTooltip TranslationKey = "MenuOpenTooltip" + MenuCopyToken TranslationKey = "MenuCopyToken" + MenuCopyTokenHint TranslationKey = "MenuCopyTokenHint" MenuAbout TranslationKey = "MenuAbout" MenuAboutTooltip TranslationKey = "MenuAboutTooltip" MenuVersion TranslationKey = "MenuVersion" @@ -47,6 +49,8 @@ var translations = map[Language]map[TranslationKey]string{ AppTooltip: "%s - Web Console", MenuOpen: "Open Console", MenuOpenTooltip: "Open PicoClaw console in browser", + MenuCopyToken: "Copy dashboard token", + MenuCopyTokenHint: "Copy the current web console access token to the clipboard", MenuAbout: "About", MenuAboutTooltip: "About PicoClaw", MenuVersion: "Version: %s", @@ -64,6 +68,8 @@ var translations = map[Language]map[TranslationKey]string{ AppTooltip: "%s - Web Console", MenuOpen: "打开控制台", MenuOpenTooltip: "在浏览器中打开 PicoClaw 控制台", + MenuCopyToken: "复制控制台口令", + MenuCopyTokenHint: "将当前 Web 控制台访问口令复制到剪贴板", MenuAbout: "关于", MenuAboutTooltip: "关于 PicoClaw", MenuVersion: "版本: %s", diff --git a/web/backend/launcherconfig/config.go b/web/backend/launcherconfig/config.go index 4dca45b0e..b8465ef74 100644 --- a/web/backend/launcherconfig/config.go +++ b/web/backend/launcherconfig/config.go @@ -1,6 +1,8 @@ package launcherconfig import ( + "crypto/rand" + "encoding/base64" "encoding/json" "fmt" "net" @@ -14,6 +16,11 @@ const ( FileName = "launcher-config.json" // DefaultPort is the default port for the web launcher. DefaultPort = 18800 + + // dashboardSigningKeyBytes is the HMAC-SHA256 key size (256 bits). + dashboardSigningKeyBytes = 32 + // dashboardTokenEntropyBytes is CSPRNG length before base64 for the per-run dashboard token (256 bits). + dashboardTokenEntropyBytes = 32 ) // Config stores launch parameters for the web backend service. @@ -41,6 +48,34 @@ func Validate(cfg Config) error { return nil } +// EnsureDashboardSecrets returns signing key bytes and the effective dashboard token for this +// process. The signing key is freshly random each call; the token comes from the environment +// variable PICOCLAW_LAUNCHER_TOKEN when set, otherwise a new random token. +func EnsureDashboardSecrets() (effectiveToken string, signingKey []byte, newRandomDashboardToken bool, err error) { + signingKey = make([]byte, dashboardSigningKeyBytes) + if _, err = rand.Read(signingKey); err != nil { + return "", nil, false, err + } + + effectiveToken = strings.TrimSpace(os.Getenv("PICOCLAW_LAUNCHER_TOKEN")) + if effectiveToken != "" { + return effectiveToken, signingKey, false, nil + } + tok, genErr := randomDashboardToken() + if genErr != nil { + return "", nil, false, genErr + } + return tok, signingKey, true, nil +} + +func randomDashboardToken() (string, error) { + buf := make([]byte, dashboardTokenEntropyBytes) + if _, err := rand.Read(buf); err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(buf), nil +} + // NormalizeCIDRs trims entries, removes empty values, and deduplicates CIDRs. func NormalizeCIDRs(cidrs []string) []string { if len(cidrs) == 0 { diff --git a/web/backend/launcherconfig/config_test.go b/web/backend/launcherconfig/config_test.go index c63bee09a..4e8a54e41 100644 --- a/web/backend/launcherconfig/config_test.go +++ b/web/backend/launcherconfig/config_test.go @@ -4,6 +4,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/sipeed/picoclaw/web/backend/middleware" ) func TestLoadReturnsFallbackWhenMissing(t *testing.T) { @@ -75,6 +77,51 @@ func TestValidateRejectsInvalidCIDR(t *testing.T) { } } +func TestEnsureDashboardSecrets_GeneratesEphemeral(t *testing.T) { + t.Setenv("PICOCLAW_LAUNCHER_TOKEN", "") + + tok, key, newTok, err := EnsureDashboardSecrets() + if err != nil { + t.Fatalf("EnsureDashboardSecrets() error = %v", err) + } + if !newTok || tok == "" || len(key) != dashboardSigningKeyBytes { + t.Fatalf("unexpected first call: newTok=%v tok=%q keyLen=%d", newTok, tok, len(key)) + } + mac := middleware.SessionCookieValue(key, tok) + if mac == "" { + t.Fatal("empty session mac") + } + + tok2, key2, newTok2, err := EnsureDashboardSecrets() + if err != nil { + t.Fatalf("EnsureDashboardSecrets() second error = %v", err) + } + if !newTok2 { + t.Fatal("second call without env should generate another random token") + } + if tok2 == tok { + t.Fatal("expected a new random dashboard token") + } + if string(key2) == string(key) { + t.Fatal("expected a new signing key") + } +} + +func TestEnsureDashboardSecrets_EnvOverridesGenerated(t *testing.T) { + t.Setenv("PICOCLAW_LAUNCHER_TOKEN", "env-only-token-override") + + tok, _, newTok, err := EnsureDashboardSecrets() + if err != nil { + t.Fatalf("EnsureDashboardSecrets() error = %v", err) + } + if tok != "env-only-token-override" { + t.Fatalf("token = %q, want env value", tok) + } + if newTok { + t.Fatal("newRandomDashboardToken should be false when env is set") + } +} + func TestNormalizeCIDRs(t *testing.T) { got := NormalizeCIDRs([]string{" 192.168.1.0/24 ", "", "10.0.0.0/8", "192.168.1.0/24"}) want := []string{"192.168.1.0/24", "10.0.0.0/8"} diff --git a/web/backend/main.go b/web/backend/main.go index 6987a4515..c58e97361 100644 --- a/web/backend/main.go +++ b/web/backend/main.go @@ -16,6 +16,7 @@ import ( "flag" "fmt" "net/http" + "net/url" "os" "os/signal" "path/filepath" @@ -44,7 +45,12 @@ var ( server *http.Server serverAddr string - apiHandler *api.Handler + // browserLaunchURL is opened by openBrowser() (auto-open + tray "open console"). + // Includes ?token= for same-machine dashboard login; keep serverAddr without secrets for other use. + browserLaunchURL string + apiHandler *api.Handler + // launcherDashboardTokenForClipboard is read by the system tray "copy token" action (GUI mode). + launcherDashboardTokenForClipboard string noBrowser *bool ) @@ -57,7 +63,7 @@ func main() { console := flag.Bool("console", false, "Console mode, no GUI") flag.Usage = func() { - fmt.Fprintf(os.Stderr, "PicoClaw Launcher - A web-based configuration editor\n\n") + fmt.Fprintf(os.Stderr, "%s Launcher - A web-based configuration editor\n\n", appName) fmt.Fprintf(os.Stderr, "Usage: %s [options] [config.json]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Arguments:\n") fmt.Fprintf(os.Stderr, " config.json Path to the configuration file (default: ~/.picoclaw/config.json)\n\n") @@ -98,8 +104,8 @@ func main() { defer logger.DisableFileLogging() } - logger.InfoC("web", fmt.Sprintf("%s Launcher %s starting...", appName, appVersion)) - logger.InfoC("web", fmt.Sprintf("PicoClaw Home: %s", picoHome)) + logger.InfoC("web", fmt.Sprintf("%s launcher starting (version %s)...", appName, appVersion)) + logger.InfoC("web", fmt.Sprintf("%s Home: %s", appName, picoHome)) // Set language from command line or auto-detect if *lang != "" { @@ -118,7 +124,7 @@ func main() { } err = utils.EnsureOnboarded(absPath) if err != nil { - logger.Errorf("Warning: Failed to initialize PicoClaw config automatically: %v", err) + logger.Errorf("Warning: Failed to initialize %s config automatically: %v", appName, err) } var explicitPort bool @@ -156,6 +162,13 @@ func main() { logger.Fatalf("Invalid port %q: %v", effectivePort, err) } + dashboardToken, dashboardSigningKey, newDashTok, dashErr := launcherconfig.EnsureDashboardSecrets() + if dashErr != nil { + logger.Fatalf("Dashboard auth setup failed: %v", dashErr) + } + dashboardSessionCookie := middleware.SessionCookieValue(dashboardSigningKey, dashboardToken) + launcherDashboardTokenForClipboard = dashboardToken + // Determine listen address var addr string if effectivePublic { @@ -167,6 +180,21 @@ func main() { // Initialize Server components mux := http.NewServeMux() + tokenLogFileAbs := "" + if !enableConsole { + tokenLogFileAbs = filepath.Join(picoHome, logPath, logFile) + } + api.RegisterLauncherAuthRoutes(mux, api.LauncherAuthRouteOpts{ + DashboardToken: dashboardToken, + SessionCookie: dashboardSessionCookie, + TokenHelp: api.LauncherAuthTokenHelp{ + EnvVarName: "PICOCLAW_LAUNCHER_TOKEN", + LogFileAbs: tokenLogFileAbs, + TrayCopyMenu: trayOffersDashboardTokenCopy(), + ConsoleStdout: enableConsole, + }, + }) + // API Routes (e.g. /api/status) apiHandler = api.NewHandler(absPath) if _, err = apiHandler.EnsurePicoChannel(""); err != nil { @@ -183,14 +211,21 @@ func main() { logger.Fatalf("Invalid allowed CIDR configuration: %v", err) } + dashAuth := middleware.LauncherDashboardAuth(middleware.LauncherDashboardAuthConfig{ + ExpectedCookie: dashboardSessionCookie, + Token: dashboardToken, + }, accessControlledMux) + // Apply middleware stack handler := middleware.Recoverer( middleware.Logger( - middleware.JSONContentType(accessControlledMux), + middleware.ReferrerPolicyNoReferrer( + middleware.JSONContentType(dashAuth), + ), ), ) - // Print startup banner (only in console mode) + // Print startup banner and token (console mode only). if enableConsole { fmt.Print(utils.Banner) fmt.Println() @@ -203,6 +238,19 @@ func main() { } } fmt.Println() + if newDashTok { + fmt.Printf(" Dashboard token (this run): %s\n", dashboardToken) + } else if os.Getenv("PICOCLAW_LAUNCHER_TOKEN") != "" { + fmt.Printf(" Dashboard token: %s (from PICOCLAW_LAUNCHER_TOKEN)\n", dashboardToken) + } + fmt.Println() + } + + if os.Getenv("PICOCLAW_LAUNCHER_TOKEN") != "" { + logger.InfoC("web", "Dashboard token: environment PICOCLAW_LAUNCHER_TOKEN") + } + if !enableConsole && newDashTok { + logger.InfoC("web", "Dashboard token (this run): "+dashboardToken) } // Log startup info to file @@ -215,6 +263,11 @@ func main() { // Share the local URL with the launcher runtime. serverAddr = fmt.Sprintf("http://localhost:%s", effectivePort) + if dashboardToken != "" { + browserLaunchURL = serverAddr + "?token=" + url.QueryEscape(dashboardToken) + } else { + browserLaunchURL = serverAddr + } // Auto-open browser will be handled by the launcher runtime. diff --git a/web/backend/middleware/launcher_dashboard_auth.go b/web/backend/middleware/launcher_dashboard_auth.go new file mode 100644 index 000000000..7e92fca22 --- /dev/null +++ b/web/backend/middleware/launcher_dashboard_auth.go @@ -0,0 +1,226 @@ +package middleware + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/subtle" + "encoding/hex" + "net/http" + "path" + "strings" + "time" +) + +// LauncherDashboardCookieName is the HttpOnly cookie set after a successful token login. +const LauncherDashboardCookieName = "picoclaw_launcher_auth" + +// launcherDashboardSessionMaxAgeSec is the session cookie lifetime (7 days). +const launcherDashboardSessionMaxAgeSec = 7 * 24 * 3600 + +const launcherSessionMACLabel = "picoclaw-launcher-v1" + +// SessionCookieValue is the expected cookie value for the given signing key and dashboard token. +func SessionCookieValue(signingKey []byte, dashboardToken string) string { + mac := hmac.New(sha256.New, signingKey) + _, _ = mac.Write([]byte(launcherSessionMACLabel)) + _, _ = mac.Write([]byte{0}) + _, _ = mac.Write([]byte(dashboardToken)) + return hex.EncodeToString(mac.Sum(nil)) +} + +// LauncherDashboardAuthConfig holds runtime material for dashboard access checks. +type LauncherDashboardAuthConfig struct { + ExpectedCookie string + Token string + // SecureCookie sets the session cookie's Secure flag. If nil, DefaultLauncherDashboardSecureCookie is used. + SecureCookie func(*http.Request) bool +} + +// DefaultLauncherDashboardSecureCookie mirrors typical production HTTPS detection (TLS or X-Forwarded-Proto). +func DefaultLauncherDashboardSecureCookie(r *http.Request) bool { + if r.TLS != nil { + return true + } + return strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") +} + +// SetLauncherDashboardSessionCookie writes the HttpOnly session cookie after successful dashboard token login. +func SetLauncherDashboardSessionCookie( + w http.ResponseWriter, + r *http.Request, + sessionValue string, + secure func(*http.Request) bool, +) { + if secure == nil { + secure = DefaultLauncherDashboardSecureCookie + } + http.SetCookie(w, &http.Cookie{ + Name: LauncherDashboardCookieName, + Value: sessionValue, + Path: "/", + MaxAge: launcherDashboardSessionMaxAgeSec, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + Secure: secure(r), + }) +} + +// ClearLauncherDashboardSessionCookie clears the dashboard session (e.g. logout). +func ClearLauncherDashboardSessionCookie(w http.ResponseWriter, r *http.Request, secure func(*http.Request) bool) { + if secure == nil { + secure = DefaultLauncherDashboardSecureCookie + } + http.SetCookie(w, &http.Cookie{ + Name: LauncherDashboardCookieName, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + Secure: secure(r), + Expires: time.Unix(0, 0), + }) +} + +// LauncherDashboardAuth requires a valid session cookie or Authorization: Bearer +// before calling next. Public paths are login page and /api/auth/* handlers. +func LauncherDashboardAuth(cfg LauncherDashboardAuthConfig, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + p := canonicalAuthPath(r.URL.Path) + if handled := tryLauncherQueryTokenLogin(w, r, p, cfg); handled { + return + } + if isPublicLauncherDashboardPath(r.Method, p) { + next.ServeHTTP(w, r) + return + } + if validLauncherDashboardAuth(r, cfg) { + next.ServeHTTP(w, r) + return + } + rejectLauncherDashboardAuth(w, r, p) + }) +} + +// canonicalAuthPath matches path cleaning used for routing decisions so +// prefixes like /assets/../ cannot bypass auth (CVE-class traversal). + +// tryLauncherQueryTokenLogin validates ?token= on GET only (non-/api), sets the session +// cookie when correct, and redirects with 303 so the follow-up is a plain GET without side effects. +// Invalid token is rejected like any other unauthenticated browser request. +func tryLauncherQueryTokenLogin( + w http.ResponseWriter, + r *http.Request, + canonicalPath string, + cfg LauncherDashboardAuthConfig, +) bool { + if r.Method != http.MethodGet { + return false + } + if canonicalPath == "/api" || strings.HasPrefix(canonicalPath, "/api/") { + return false + } + qToken := strings.TrimSpace(r.URL.Query().Get("token")) + if qToken == "" { + return false + } + if len(qToken) != len(cfg.Token) || subtle.ConstantTimeCompare([]byte(qToken), []byte(cfg.Token)) != 1 { + rejectLauncherDashboardAuth(w, r, canonicalPath) + return true + } + SetLauncherDashboardSessionCookie(w, r, cfg.ExpectedCookie, cfg.SecureCookie) + http.Redirect(w, r, redirectAfterQueryTokenLogin(r, canonicalPath), http.StatusSeeOther) + return true +} + +func redirectAfterQueryTokenLogin(r *http.Request, canonicalPath string) string { + if canonicalPath == "/launcher-login" { + return "/" + } + q := r.URL.Query() + q.Del("token") + enc := q.Encode() + if enc != "" { + return canonicalPath + "?" + enc + } + return canonicalPath +} + +func canonicalAuthPath(raw string) string { + if raw == "" { + return "/" + } + c := path.Clean(raw) + switch c { + case ".", "": + return "/" + default: + if c[0] != '/' { + return "/" + c + } + return c + } +} + +func isPublicLauncherDashboardPath(method, p string) bool { + if isPublicLauncherDashboardStatic(method, p) { + return true + } + switch p { + case "/api/auth/login": + return method == http.MethodPost + case "/api/auth/logout": + return method == http.MethodPost + case "/api/auth/status": + return method == http.MethodGet + } + return false +} + +// isPublicLauncherDashboardStatic allows the SPA login route and embedded +// frontend assets without a session (GET/HEAD only). +func isPublicLauncherDashboardStatic(method, p string) bool { + if method != http.MethodGet && method != http.MethodHead { + return false + } + if p == "/launcher-login" { + return true + } + if strings.HasPrefix(p, "/assets/") { + return true + } + switch p { + case "/favicon.ico", "/favicon.svg", "/favicon-96x96.png", + "/apple-touch-icon.png", "/site.webmanifest", "/robots.txt": + return true + default: + return false + } +} + +func validLauncherDashboardAuth(r *http.Request, cfg LauncherDashboardAuthConfig) bool { + if c, err := r.Cookie(LauncherDashboardCookieName); err == nil { + if subtle.ConstantTimeCompare([]byte(c.Value), []byte(cfg.ExpectedCookie)) == 1 { + return true + } + } + auth := r.Header.Get("Authorization") + const prefix = "Bearer " + if strings.HasPrefix(auth, prefix) { + token := strings.TrimSpace(auth[len(prefix):]) + if len(token) == len(cfg.Token) && subtle.ConstantTimeCompare([]byte(token), []byte(cfg.Token)) == 1 { + return true + } + } + return false +} + +func rejectLauncherDashboardAuth(w http.ResponseWriter, r *http.Request, canonicalPath string) { + if strings.HasPrefix(canonicalPath, "/api/") { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"error":"unauthorized"}`)) + return + } + http.Redirect(w, r, "/launcher-login", http.StatusFound) +} diff --git a/web/backend/middleware/launcher_dashboard_auth_test.go b/web/backend/middleware/launcher_dashboard_auth_test.go new file mode 100644 index 000000000..1b919bf96 --- /dev/null +++ b/web/backend/middleware/launcher_dashboard_auth_test.go @@ -0,0 +1,162 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestSessionCookieValue_Deterministic(t *testing.T) { + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + a := SessionCookieValue(key, "tok-a") + b := SessionCookieValue(key, "tok-a") + if a != b || a == "" { + t.Fatalf("SessionCookieValue mismatch or empty: %q vs %q", a, b) + } + c := SessionCookieValue(key, "tok-b") + if c == a { + t.Fatal("SessionCookieValue should differ for different tokens") + } +} + +func TestLauncherDashboardAuth_AllowsPublicPaths(t *testing.T) { + cfg := LauncherDashboardAuthConfig{ExpectedCookie: "deadbeef", Token: "x"} + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTeapot) + }) + h := LauncherDashboardAuth(cfg, next) + + for _, tc := range []struct { + method, path string + want int + }{ + {http.MethodGet, "/launcher-login", http.StatusTeapot}, + {http.MethodGet, "/assets/index.js", http.StatusTeapot}, + {http.MethodPost, "/api/auth/login", http.StatusTeapot}, + {http.MethodGet, "/api/auth/status", http.StatusTeapot}, + {http.MethodPost, "/api/auth/logout", http.StatusTeapot}, + {http.MethodGet, "/api/auth/logout", http.StatusUnauthorized}, + {http.MethodGet, "/api/config", http.StatusUnauthorized}, + } { + rec := httptest.NewRecorder() + req := httptest.NewRequest(tc.method, tc.path, nil) + h.ServeHTTP(rec, req) + if rec.Code != tc.want { + t.Fatalf("%s %s: status = %d, want %d", tc.method, tc.path, rec.Code, tc.want) + } + } +} + +func TestLauncherDashboardAuth_URLTokenBootstrapGET(t *testing.T) { + const tok = "secret" + cfg := LauncherDashboardAuthConfig{ExpectedCookie: "deadbeef", Token: tok} + next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusTeapot) + }) + h := LauncherDashboardAuth(cfg, next) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?token="+tok, nil) + h.ServeHTTP(rec, req) + if rec.Code != http.StatusSeeOther { + t.Fatalf("GET /?token=valid: status = %d, want %d", rec.Code, http.StatusSeeOther) + } + if got := rec.Header().Get("Location"); got != "/" { + t.Fatalf("Location = %q, want %q", got, "/") + } + if c := rec.Result().Cookies(); len(c) != 1 || c[0].Name != LauncherDashboardCookieName { + t.Fatalf("expected one session cookie, got %#v", c) + } + + rec1b := httptest.NewRecorder() + req1b := httptest.NewRequest(http.MethodGet, "/config?token="+tok+"&keep=1", nil) + h.ServeHTTP(rec1b, req1b) + if rec1b.Code != http.StatusSeeOther { + t.Fatalf("GET /config?token=valid: status = %d", rec1b.Code) + } + if got := rec1b.Header().Get("Location"); got != "/config?keep=1" { + t.Fatalf("Location = %q, want /config?keep=1", got) + } + + recBad := httptest.NewRecorder() + reqBad := httptest.NewRequest(http.MethodGet, "/?token=wrong", nil) + h.ServeHTTP(recBad, reqBad) + if recBad.Code != http.StatusFound || recBad.Header().Get("Location") != "/launcher-login" { + t.Fatalf("GET /?token=invalid: code=%d loc=%q", recBad.Code, recBad.Header().Get("Location")) + } + + rec2 := httptest.NewRecorder() + req2 := httptest.NewRequest(http.MethodGet, "/api/config?token="+tok, nil) + h.ServeHTTP(rec2, req2) + if rec2.Code != http.StatusUnauthorized { + t.Fatalf("GET /api with token query: status = %d, want %d", rec2.Code, http.StatusUnauthorized) + } + + rec3 := httptest.NewRecorder() + req3 := httptest.NewRequest(http.MethodGet, "/?token=", nil) + h.ServeHTTP(rec3, req3) + if rec3.Code != http.StatusFound { + t.Fatalf("GET /?token=empty: status = %d, want redirect", rec3.Code) + } + + recLogin := httptest.NewRecorder() + reqLogin := httptest.NewRequest(http.MethodGet, "/launcher-login?token="+tok, nil) + h.ServeHTTP(recLogin, reqLogin) + if recLogin.Code != http.StatusSeeOther || recLogin.Header().Get("Location") != "/" { + t.Fatalf("GET /launcher-login?token=valid: code=%d loc=%q", recLogin.Code, recLogin.Header().Get("Location")) + } +} + +func TestLauncherDashboardAuth_DotDotCannotBypass(t *testing.T) { + cfg := LauncherDashboardAuthConfig{ExpectedCookie: "deadbeef", Token: "x"} + next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + t.Fatal("next handler should not run without auth") + }) + h := LauncherDashboardAuth(cfg, next) + + for _, p := range []string{ + "/assets/../api/config", + "/launcher-login/../api/config", + "/./api/config", + } { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, p, nil) + h.ServeHTTP(rec, req) + if rec.Code != http.StatusUnauthorized { + t.Fatalf("%q: status = %d, want %d", p, rec.Code, http.StatusUnauthorized) + } + } +} + +func TestLauncherDashboardAuth_CookieAndBearer(t *testing.T) { + key := make([]byte, 32) + for i := range key { + key[i] = 0xab + } + token := "dashboard-secret-9" + cookieVal := SessionCookieValue(key, token) + cfg := LauncherDashboardAuthConfig{ExpectedCookie: cookieVal, Token: token} + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + h := LauncherDashboardAuth(cfg, next) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.AddCookie(&http.Cookie{Name: LauncherDashboardCookieName, Value: cookieVal}) + h.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("cookie auth: status = %d", rec.Code) + } + + rec2 := httptest.NewRecorder() + req2 := httptest.NewRequest(http.MethodGet, "/", nil) + req2.Header.Set("Authorization", "Bearer "+token) + h.ServeHTTP(rec2, req2) + if rec2.Code != http.StatusOK { + t.Fatalf("bearer auth: status = %d", rec2.Code) + } +} diff --git a/web/backend/middleware/referrer_policy.go b/web/backend/middleware/referrer_policy.go new file mode 100644 index 000000000..5ac066614 --- /dev/null +++ b/web/backend/middleware/referrer_policy.go @@ -0,0 +1,12 @@ +package middleware + +import "net/http" + +// ReferrerPolicyNoReferrer sets Referrer-Policy: no-referrer on every response so sensitive +// query parameters (e.g. ?token= for dashboard bootstrap) are not leaked via the Referer header. +func ReferrerPolicyNoReferrer(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Referrer-Policy", "no-referrer") + next.ServeHTTP(w, r) + }) +} diff --git a/web/backend/systray.go b/web/backend/systray.go index 9dcc025df..744ea4611 100644 --- a/web/backend/systray.go +++ b/web/backend/systray.go @@ -6,6 +6,7 @@ import ( "fmt" "fyne.io/systray" + "github.com/atotto/clipboard" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/web/backend/utils" @@ -23,6 +24,7 @@ func onReady() { // Create menu items mOpen := systray.AddMenuItem(T(MenuOpen), T(MenuOpenTooltip)) + mCopyTok := systray.AddMenuItem(T(MenuCopyToken), T(MenuCopyTokenHint)) mAbout := systray.AddMenuItem(T(MenuAbout), T(MenuAboutTooltip)) // Add version info under About menu @@ -50,6 +52,17 @@ func onReady() { logger.Errorf("Failed to open browser: %v", err) } + case <-mCopyTok.ClickedCh: + if launcherDashboardTokenForClipboard == "" { + logger.WarnC("web", "Dashboard token is empty; cannot copy") + continue + } + if err := clipboard.WriteAll(launcherDashboardTokenForClipboard); err != nil { + logger.Errorf("Failed to copy dashboard token: %v", err) + } else { + logger.InfoC("web", "Dashboard token copied to clipboard") + } + case <-mVersion.ClickedCh: // Version info - do nothing, just shows current version diff --git a/web/backend/tray_offers_copy.go b/web/backend/tray_offers_copy.go new file mode 100644 index 000000000..6b7d17412 --- /dev/null +++ b/web/backend/tray_offers_copy.go @@ -0,0 +1,5 @@ +//go:build (!darwin && !freebsd) || cgo + +package main + +func trayOffersDashboardTokenCopy() bool { return true } diff --git a/web/backend/tray_offers_copy_stub.go b/web/backend/tray_offers_copy_stub.go new file mode 100644 index 000000000..9312700f3 --- /dev/null +++ b/web/backend/tray_offers_copy_stub.go @@ -0,0 +1,5 @@ +//go:build (darwin || freebsd) && !cgo + +package main + +func trayOffersDashboardTokenCopy() bool { return false } diff --git a/web/frontend/src/api/channels.ts b/web/frontend/src/api/channels.ts index 85550ca81..eb4d41fd7 100644 --- a/web/frontend/src/api/channels.ts +++ b/web/frontend/src/api/channels.ts @@ -1,5 +1,7 @@ // API client for channels navigation and channel-specific config flows. +import { launcherFetch } from "@/api/http" + export type ChannelConfig = Record export type AppConfig = Record @@ -22,7 +24,7 @@ interface ConfigActionResponse { const BASE_URL = "" async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, options) + const res = await launcherFetch(`${BASE_URL}${path}`, options) if (!res.ok) { let message = `API error: ${res.status} ${res.statusText}` try { diff --git a/web/frontend/src/api/gateway.ts b/web/frontend/src/api/gateway.ts index 9e02a02b5..2742a0a37 100644 --- a/web/frontend/src/api/gateway.ts +++ b/web/frontend/src/api/gateway.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + // API client for gateway process management. interface GatewayStatusResponse { @@ -27,7 +29,7 @@ interface GatewayActionResponse { const BASE_URL = "" async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, options) + const res = await launcherFetch(`${BASE_URL}${path}`, options) if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`) } diff --git a/web/frontend/src/api/http.ts b/web/frontend/src/api/http.ts new file mode 100644 index 000000000..0eb872f3f --- /dev/null +++ b/web/frontend/src/api/http.ts @@ -0,0 +1,42 @@ +import { isLauncherLoginPathname } from "@/lib/launcher-login-path" + +function isLauncherLoginPath(): boolean { + if (typeof globalThis.location === "undefined") { + return false + } + if (isLauncherLoginPathname(globalThis.location.pathname || "/")) { + return true + } + try { + return isLauncherLoginPathname( + new URL(globalThis.location.href).pathname || "/", + ) + } catch { + return false + } +} + +/** + * Same-origin fetch that sends cookies; redirects to launcher login on 401 JSON responses. + * Skips redirect while already on the login page to avoid reload loops (e.g. gateway poll). + */ +export async function launcherFetch( + input: RequestInfo | URL, + init?: RequestInit, +): Promise { + const res = await fetch(input, { + credentials: "same-origin", + ...init, + }) + if (res.status === 401) { + const ct = res.headers.get("content-type") || "" + if ( + ct.includes("application/json") && + typeof globalThis.location !== "undefined" && + !isLauncherLoginPath() + ) { + globalThis.location.assign("/launcher-login") + } + } + return res +} diff --git a/web/frontend/src/api/launcher-auth.ts b/web/frontend/src/api/launcher-auth.ts new file mode 100644 index 000000000..247d5ab9e --- /dev/null +++ b/web/frontend/src/api/launcher-auth.ts @@ -0,0 +1,48 @@ +/** + * Dashboard launcher token login. Uses plain fetch (not launcherFetch) to avoid + * redirect loops on 401 while on the login page. + */ +export async function postLauncherDashboardLogin( + token: string, +): Promise { + const res = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "same-origin", + body: JSON.stringify({ token: token.trim() }), + }) + return res.ok +} + +export type LauncherAuthTokenHelp = { + env_var_name: string + log_file?: string + tray_copy_menu: boolean + console_stdout: boolean +} + +export type LauncherAuthStatus = { + authenticated: boolean + token_help?: LauncherAuthTokenHelp +} + +export async function getLauncherAuthStatus(): Promise { + const res = await fetch("/api/auth/status", { + method: "GET", + credentials: "same-origin", + }) + if (!res.ok) { + throw new Error(`status ${res.status}`) + } + return (await res.json()) as LauncherAuthStatus +} + +export async function postLauncherDashboardLogout(): Promise { + const res = await fetch("/api/auth/logout", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "same-origin", + body: "{}", + }) + return res.ok +} diff --git a/web/frontend/src/api/models.ts b/web/frontend/src/api/models.ts index aa66a7389..d75b3ec3c 100644 --- a/web/frontend/src/api/models.ts +++ b/web/frontend/src/api/models.ts @@ -1,3 +1,4 @@ +import { launcherFetch } from "@/api/http" import { refreshGatewayState } from "@/store/gateway" // API client for model list management. @@ -39,7 +40,7 @@ interface ModelActionResponse { const BASE_URL = "" async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, options) + const res = await launcherFetch(`${BASE_URL}${path}`, options) if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`) } diff --git a/web/frontend/src/api/oauth.ts b/web/frontend/src/api/oauth.ts index a1ed1afcb..689a2bcd1 100644 --- a/web/frontend/src/api/oauth.ts +++ b/web/frontend/src/api/oauth.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + export type OAuthProvider = "openai" | "anthropic" | "google-antigravity" export type OAuthMethod = "browser" | "device_code" | "token" @@ -51,7 +53,7 @@ interface OAuthProvidersResponse { const BASE_URL = "" async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, options) + const res = await launcherFetch(`${BASE_URL}${path}`, options) if (!res.ok) { const message = await res.text() throw new Error(message || `API error: ${res.status} ${res.statusText}`) diff --git a/web/frontend/src/api/pico.ts b/web/frontend/src/api/pico.ts index 9a1a553d5..6b8ceb49a 100644 --- a/web/frontend/src/api/pico.ts +++ b/web/frontend/src/api/pico.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + // API client for Pico Channel configuration. interface PicoTokenResponse { @@ -16,7 +18,7 @@ interface PicoSetupResponse { const BASE_URL = "" async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, options) + const res = await launcherFetch(`${BASE_URL}${path}`, options) if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`) } diff --git a/web/frontend/src/api/sessions.ts b/web/frontend/src/api/sessions.ts index 10b0d28fd..c91495901 100644 --- a/web/frontend/src/api/sessions.ts +++ b/web/frontend/src/api/sessions.ts @@ -1,5 +1,7 @@ // Sessions API — list and retrieve chat session history +import { launcherFetch } from "@/api/http" + export interface SessionSummary { id: string title: string @@ -26,7 +28,7 @@ export async function getSessions( limit: limit.toString(), }) - const res = await fetch(`/api/sessions?${params.toString()}`) + const res = await launcherFetch(`/api/sessions?${params.toString()}`) if (!res.ok) { throw new Error(`Failed to fetch sessions: ${res.status}`) } @@ -34,7 +36,7 @@ export async function getSessions( } export async function getSessionHistory(id: string): Promise { - const res = await fetch(`/api/sessions/${encodeURIComponent(id)}`) + const res = await launcherFetch(`/api/sessions/${encodeURIComponent(id)}`) if (!res.ok) { throw new Error(`Failed to fetch session ${id}: ${res.status}`) } @@ -42,7 +44,7 @@ export async function getSessionHistory(id: string): Promise { } export async function deleteSession(id: string): Promise { - const res = await fetch(`/api/sessions/${encodeURIComponent(id)}`, { + const res = await launcherFetch(`/api/sessions/${encodeURIComponent(id)}`, { method: "DELETE", }) if (!res.ok) { diff --git a/web/frontend/src/api/skills.ts b/web/frontend/src/api/skills.ts index 307cbd788..72ccbcfe5 100644 --- a/web/frontend/src/api/skills.ts +++ b/web/frontend/src/api/skills.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + export interface SkillSupportItem { name: string path: string @@ -22,7 +24,7 @@ interface SkillActionResponse { } async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(path, options) + const res = await launcherFetch(path, options) if (!res.ok) { throw new Error(await extractErrorMessage(res)) } @@ -41,7 +43,7 @@ export async function importSkill(file: File): Promise { const formData = new FormData() formData.set("file", file) - const res = await fetch("/api/skills/import", { + const res = await launcherFetch("/api/skills/import", { method: "POST", body: formData, }) diff --git a/web/frontend/src/api/system.ts b/web/frontend/src/api/system.ts index 543c8694d..2e2f36f15 100644 --- a/web/frontend/src/api/system.ts +++ b/web/frontend/src/api/system.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + export interface AutoStartStatus { enabled: boolean supported: boolean @@ -12,7 +14,7 @@ export interface LauncherConfig { } async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(path, options) + const res = await launcherFetch(path, options) if (!res.ok) { let message = `API error: ${res.status} ${res.statusText}` try { diff --git a/web/frontend/src/api/tools.ts b/web/frontend/src/api/tools.ts index 9f09efbfd..824bcc0fa 100644 --- a/web/frontend/src/api/tools.ts +++ b/web/frontend/src/api/tools.ts @@ -1,3 +1,5 @@ +import { launcherFetch } from "@/api/http" + export interface ToolSupportItem { name: string description: string @@ -16,7 +18,7 @@ interface ToolActionResponse { } async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(path, options) + const res = await launcherFetch(path, options) if (!res.ok) { let message = `API error: ${res.status} ${res.statusText}` try { diff --git a/web/frontend/src/components/config/config-page.tsx b/web/frontend/src/components/config/config-page.tsx index 664e75440..cbe4d8e91 100644 --- a/web/frontend/src/components/config/config-page.tsx +++ b/web/frontend/src/components/config/config-page.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next" import { toast } from "sonner" import { patchAppConfig } from "@/api/channels" +import { launcherFetch } from "@/api/http" import { getAutoStartStatus, getLauncherConfig, @@ -50,7 +51,7 @@ export function ConfigPage() { const { data, isLoading, error } = useQuery({ queryKey: ["config"], queryFn: async () => { - const res = await fetch("/api/config") + const res = await launcherFetch("/api/config") if (!res.ok) { throw new Error("Failed to load config") } diff --git a/web/frontend/src/components/config/raw-config-page.tsx b/web/frontend/src/components/config/raw-config-page.tsx index 56a922fe6..f8f987651 100644 --- a/web/frontend/src/components/config/raw-config-page.tsx +++ b/web/frontend/src/components/config/raw-config-page.tsx @@ -5,6 +5,7 @@ import { useState } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" +import { launcherFetch } from "@/api/http" import { PageHeader } from "@/components/page-header" import { AlertDialog, @@ -28,7 +29,7 @@ export function RawConfigPage() { const { data: config, isLoading } = useQuery({ queryKey: ["config"], queryFn: async () => { - const res = await fetch("/api/config") + const res = await launcherFetch("/api/config") if (!res.ok) { throw new Error("Failed to fetch config") } @@ -38,7 +39,7 @@ export function RawConfigPage() { const mutation = useMutation({ mutationFn: async (newConfig: string) => { - const res = await fetch("/api/config", { + const res = await launcherFetch("/api/config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: newConfig, diff --git a/web/frontend/src/features/chat/websocket.ts b/web/frontend/src/features/chat/websocket.ts index 6b132e9a6..17ba36075 100644 --- a/web/frontend/src/features/chat/websocket.ts +++ b/web/frontend/src/features/chat/websocket.ts @@ -14,6 +14,18 @@ export function normalizeWsUrlForBrowser(wsUrl: string): string { if (isLocalHost && !isBrowserLocal) { parsedUrl.hostname = window.location.hostname finalWsUrl = parsedUrl.toString() + } else if ( + isLocalHost && + isBrowserLocal && + parsedUrl.hostname !== window.location.hostname && + (parsedUrl.hostname === "127.0.0.1" || + parsedUrl.hostname === "localhost") && + (window.location.hostname === "127.0.0.1" || + window.location.hostname === "localhost") + ) { + // Same machine, but cookies are host-specific; match the page origin. + parsedUrl.hostname = window.location.hostname + finalWsUrl = parsedUrl.toString() } } catch (error) { console.warn("Could not parse ws_url:", error) diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index 25608fe93..38cdeb324 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -14,6 +14,20 @@ "config": "Config", "logs": "Logs" }, + "launcherLogin": { + "title": "Launcher access", + "description": "Sign in with the dashboard access token for this launcher process (it may change after each restart unless you pin it with an environment variable).", + "tokenLabel": "Token", + "tokenPlaceholder": "Enter access token", + "submit": "Continue to Dashboard", + "errorInvalid": "Invalid token. Please try again.", + "errorNetwork": "Network error. Please try again.", + "helpTitle": "Where to find the token", + "helpConsole": "Console mode: printed in the terminal when the launcher starts.", + "helpTray": "Tray mode: menu «Copy dashboard token».", + "helpLogFile": "Log file (startup line includes the token): {{path}}", + "helpEnv": "Stable token: set {{env}}." + }, "chat": { "welcome": "How can I help you today?", "welcomeDesc": "Ask me about weather, settings, or any other tasks. I'm here to assist you.", diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index 346822407..9ec4ec967 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -14,6 +14,20 @@ "config": "配置", "logs": "日志" }, + "launcherLogin": { + "title": "Launcher 访问验证", + "description": "请使用当前 Launcher 进程的访问口令登录(每次重启可能变化,除非用环境变量固定)。", + "tokenLabel": "令牌", + "tokenPlaceholder": "输入访问令牌", + "submit": "进入 Dashboard", + "errorInvalid": "令牌错误,请重试。", + "errorNetwork": "网络错误,请重试。", + "helpTitle": "口令在哪里", + "helpConsole": "控制台模式:启动时在终端输出。", + "helpTray": "托盘模式:菜单「复制控制台口令」。", + "helpLogFile": "日志文件(启动时会写入口令):{{path}}", + "helpEnv": "固定口令:设置环境变量 {{env}}。" + }, "chat": { "welcome": "今天我能为您做些什么?", "welcomeDesc": "您可以询问我天气、设置或其他任何任务,我随时为您效劳。", diff --git a/web/frontend/src/lib/launcher-login-path.ts b/web/frontend/src/lib/launcher-login-path.ts new file mode 100644 index 000000000..52c35d240 --- /dev/null +++ b/web/frontend/src/lib/launcher-login-path.ts @@ -0,0 +1,9 @@ +/** Normalize URL pathname for comparisons (trailing slashes, empty). */ +export function normalizePathname(p: string): string { + const t = p.replace(/\/+$/, "") + return t === "" ? "/" : t +} + +export function isLauncherLoginPathname(pathname: string): boolean { + return normalizePathname(pathname) === "/launcher-login" +} diff --git a/web/frontend/src/routeTree.gen.ts b/web/frontend/src/routeTree.gen.ts index 60f19ab53..536ee560b 100644 --- a/web/frontend/src/routeTree.gen.ts +++ b/web/frontend/src/routeTree.gen.ts @@ -11,6 +11,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as ModelsRouteImport } from './routes/models' import { Route as LogsRouteImport } from './routes/logs' +import { Route as LauncherLoginRouteImport } from './routes/launcher-login' import { Route as CredentialsRouteImport } from './routes/credentials' import { Route as ConfigRouteImport } from './routes/config' import { Route as AgentRouteImport } from './routes/agent' @@ -31,6 +32,11 @@ const LogsRoute = LogsRouteImport.update({ path: '/logs', getParentRoute: () => rootRouteImport, } as any) +const LauncherLoginRoute = LauncherLoginRouteImport.update({ + id: '/launcher-login', + path: '/launcher-login', + getParentRoute: () => rootRouteImport, +} as any) const CredentialsRoute = CredentialsRouteImport.update({ id: '/credentials', path: '/credentials', @@ -83,6 +89,7 @@ export interface FileRoutesByFullPath { '/agent': typeof AgentRouteWithChildren '/config': typeof ConfigRouteWithChildren '/credentials': typeof CredentialsRoute + '/launcher-login': typeof LauncherLoginRoute '/logs': typeof LogsRoute '/models': typeof ModelsRoute '/agent/skills': typeof AgentSkillsRoute @@ -96,6 +103,7 @@ export interface FileRoutesByTo { '/agent': typeof AgentRouteWithChildren '/config': typeof ConfigRouteWithChildren '/credentials': typeof CredentialsRoute + '/launcher-login': typeof LauncherLoginRoute '/logs': typeof LogsRoute '/models': typeof ModelsRoute '/agent/skills': typeof AgentSkillsRoute @@ -110,6 +118,7 @@ export interface FileRoutesById { '/agent': typeof AgentRouteWithChildren '/config': typeof ConfigRouteWithChildren '/credentials': typeof CredentialsRoute + '/launcher-login': typeof LauncherLoginRoute '/logs': typeof LogsRoute '/models': typeof ModelsRoute '/agent/skills': typeof AgentSkillsRoute @@ -125,6 +134,7 @@ export interface FileRouteTypes { | '/agent' | '/config' | '/credentials' + | '/launcher-login' | '/logs' | '/models' | '/agent/skills' @@ -138,6 +148,7 @@ export interface FileRouteTypes { | '/agent' | '/config' | '/credentials' + | '/launcher-login' | '/logs' | '/models' | '/agent/skills' @@ -151,6 +162,7 @@ export interface FileRouteTypes { | '/agent' | '/config' | '/credentials' + | '/launcher-login' | '/logs' | '/models' | '/agent/skills' @@ -165,6 +177,7 @@ export interface RootRouteChildren { AgentRoute: typeof AgentRouteWithChildren ConfigRoute: typeof ConfigRouteWithChildren CredentialsRoute: typeof CredentialsRoute + LauncherLoginRoute: typeof LauncherLoginRoute LogsRoute: typeof LogsRoute ModelsRoute: typeof ModelsRoute } @@ -185,6 +198,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LogsRouteImport parentRoute: typeof rootRouteImport } + '/launcher-login': { + id: '/launcher-login' + path: '/launcher-login' + fullPath: '/launcher-login' + preLoaderRoute: typeof LauncherLoginRouteImport + parentRoute: typeof rootRouteImport + } '/credentials': { id: '/credentials' path: '/credentials' @@ -292,6 +312,7 @@ const rootRouteChildren: RootRouteChildren = { AgentRoute: AgentRouteWithChildren, ConfigRoute: ConfigRouteWithChildren, CredentialsRoute: CredentialsRoute, + LauncherLoginRoute: LauncherLoginRoute, LogsRoute: LogsRoute, ModelsRoute: ModelsRoute, } diff --git a/web/frontend/src/routes/__root.tsx b/web/frontend/src/routes/__root.tsx index 31fdb7804..d2303a29c 100644 --- a/web/frontend/src/routes/__root.tsx +++ b/web/frontend/src/routes/__root.tsx @@ -1,19 +1,56 @@ -import { Outlet, createRootRoute } from "@tanstack/react-router" +import { + Outlet, + createRootRoute, + useRouterState, +} from "@tanstack/react-router" import { TanStackRouterDevtools } from "@tanstack/react-router-devtools" import { useEffect } from "react" import { AppLayout } from "@/components/app-layout" import { initializeChatStore } from "@/features/chat/controller" +import { isLauncherLoginPathname } from "@/lib/launcher-login-path" const RootLayout = () => { + // Prefer the real address bar path: stale embedded bundles may not register + // /launcher-login in the route tree, which would otherwise keep AppLayout + + // gateway polling → 401 → launcherFetch redirect loop. + const routerState = useRouterState({ + select: (s) => ({ + pathname: s.location.pathname, + matches: s.matches, + }), + }) + + const windowPath = + typeof globalThis.location !== "undefined" + ? globalThis.location.pathname || "/" + : routerState.pathname + + const isLauncherLogin = + isLauncherLoginPathname(windowPath) || + isLauncherLoginPathname(routerState.pathname) || + routerState.matches.some((m) => m.routeId === "/launcher-login") + useEffect(() => { + if (isLauncherLogin) { + return + } initializeChatStore() - }, []) + }, [isLauncherLogin]) + + if (isLauncherLogin) { + return ( + <> + + {import.meta.env.DEV ? : null} + + ) + } return ( - + {import.meta.env.DEV ? : null} ) } diff --git a/web/frontend/src/routes/launcher-login.tsx b/web/frontend/src/routes/launcher-login.tsx new file mode 100644 index 000000000..e7f774df7 --- /dev/null +++ b/web/frontend/src/routes/launcher-login.tsx @@ -0,0 +1,184 @@ +import { IconLanguage, IconMoon, IconSun } from "@tabler/icons-react" +import { createFileRoute } from "@tanstack/react-router" +import * as React from "react" +import { useTranslation } from "react-i18next" + +import { + getLauncherAuthStatus, + postLauncherDashboardLogin, + type LauncherAuthTokenHelp, +} from "@/api/launcher-auth" +import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { useTheme } from "@/hooks/use-theme" + +function LauncherLoginPage() { + const { t, i18n } = useTranslation() + const { theme, toggleTheme } = useTheme() + const [token, setToken] = React.useState("") + const [submitting, setSubmitting] = React.useState(false) + const [error, setError] = React.useState("") + const [tokenHelp, setTokenHelp] = React.useState( + null, + ) + + React.useEffect(() => { + let cancelled = false + void getLauncherAuthStatus() + .then((s) => { + if (cancelled || s.authenticated || !s.token_help) { + return + } + setTokenHelp(s.token_help) + }) + .catch(() => { + /* ignore; login form still usable */ + }) + return () => { + cancelled = true + } + }, []) + + const loginWithToken = React.useCallback( + async (tokenValue: string) => { + setError("") + setSubmitting(true) + try { + const ok = await postLauncherDashboardLogin(tokenValue) + if (ok) { + globalThis.location.assign("/") + return + } + setError(t("launcherLogin.errorInvalid")) + } catch { + setError(t("launcherLogin.errorNetwork")) + } finally { + setSubmitting(false) + } + }, + [t], + ) + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault() + await loginWithToken(token) + } + + return ( +
    +
    + + + + + + i18n.changeLanguage("en")}> + English + + i18n.changeLanguage("zh")}> + 简体中文 + + + + +
    + +
    + + + {t("launcherLogin.title")} + {t("launcherLogin.description")} + + +
    +
    + + setToken(e.target.value)} + placeholder={t("launcherLogin.tokenPlaceholder")} + /> +
    + + {error ? ( +

    + {error} +

    + ) : null} +
    + {tokenHelp ? ( +
    +

    + {t("launcherLogin.helpTitle")} +

    +
      + {tokenHelp.console_stdout ? ( +
    • {t("launcherLogin.helpConsole")}
    • + ) : null} + {tokenHelp.tray_copy_menu ? ( +
    • {t("launcherLogin.helpTray")}
    • + ) : null} + {tokenHelp.log_file ? ( +
    • + {t("launcherLogin.helpLogFile", { + path: tokenHelp.log_file, + })} +
    • + ) : null} + {tokenHelp.env_var_name ? ( +
    • + {t("launcherLogin.helpEnv", { + env: tokenHelp.env_var_name, + })} +
    • + ) : null} +
    +
    + ) : null} +
    +
    +
    +
    + ) +} + +export const Route = createFileRoute("/launcher-login")({ + component: LauncherLoginPage, +}) From e414b82ac3b160ad5efdca83cae9337767cbe8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E9=9D=92=E5=B7=9D?= <46062972+ShenQingchuan@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:47:28 +0800 Subject: [PATCH 28/55] fix(cron): publish agent response to outbound bus for cron-triggered jobs (#2100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cron): publish agent response to outbound bus for cron-triggered jobs When a cron job triggers agent execution via ProcessDirectWithChannel, the agent response was silently discarded — the code assumed AgentLoop would auto-publish it, but SendResponse is false on this path. Delegate to PublishResponseIfNeeded (exported from AgentLoop) so the response reaches the originating channel (e.g. Telegram) only when the message tool did not already deliver content in the same round. Also adds a "directive" message type to CronPayload, allowing cron jobs to instruct the agent to execute a task rather than echo static text. * fix(cron): add type validation and directive test coverage Address reviewer blocking feedback: 1. Server-side whitelist for `type` parameter — the `enum` in Parameters() is only an LLM schema hint; any string was persisted. Now `addJob` rejects values other than "message" and "directive". 2. Comprehensive test coverage for the directive code path: - directive adds prompt prefix to ProcessDirectWithChannel - deliver=true + directive routes through agent (not direct publish) - directive prompt content, sessionKey, channel, chatID are correct - invalid type is rejected; valid types ("", "message", "directive") pass - deliver=true message type goes directly to bus (regression) - agent error path does not trigger publish (regression) Also merge the two UpdateJob calls in addJob into one to avoid redundant disk I/O (non-blocking suggestion from review). * fix(cron): remove omitempty from CronPayload.Type for consistent JSON Empty string and "message" are semantically equivalent defaults; always serializing the field avoids asymmetric JSON output. * test(cron): remove redundant test, strengthen error path coverage - Remove ExecuteJobDirectivePassesCorrectContent: its assertions on sessionKey/channel/chatID duplicate ExecuteJobPublishesAgentResponse; its prompt check duplicates DirectiveAddsPromptPrefix. - Strengthen DirectiveAddsPromptPrefix with exact prompt match and publish response assertion. - Fix ReturnsErrorWithoutPublish: set non-empty stub response so the test verifies the error branch early-return, not the response=="" guard. * fix(ci): satisfy golines and gosmopolitan in cron code --- pkg/agent/loop.go | 6 +- pkg/cron/service.go | 1 + pkg/tools/cron.go | 54 +++++++-- pkg/tools/cron_test.go | 261 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 309 insertions(+), 13 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index db476c212..ef2951365 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -461,7 +461,7 @@ func (al *AgentLoop) Run(ctx context.Context) error { if target == nil { cancelDrain() if finalResponse != "" { - al.publishResponseIfNeeded(ctx, msg.Channel, msg.ChatID, finalResponse) + al.PublishResponseIfNeeded(ctx, msg.Channel, msg.ChatID, finalResponse) } return } @@ -521,7 +521,7 @@ func (al *AgentLoop) Run(ctx context.Context) error { } if finalResponse != "" { - al.publishResponseIfNeeded(ctx, target.Channel, target.ChatID, finalResponse) + al.PublishResponseIfNeeded(ctx, target.Channel, target.ChatID, finalResponse) } }() } @@ -603,7 +603,7 @@ func (al *AgentLoop) Stop() { al.running.Store(false) } -func (al *AgentLoop) publishResponseIfNeeded(ctx context.Context, channel, chatID, response string) { +func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatID, response string) { if response == "" { return } diff --git a/pkg/cron/service.go b/pkg/cron/service.go index 77a413133..c1a224013 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -25,6 +25,7 @@ type CronSchedule struct { type CronPayload struct { Kind string `json:"kind"` + Type string `json:"type"` Message string `json:"message"` Command string `json:"command,omitempty"` Deliver bool `json:"deliver"` diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 154ec75f0..60d9d5e5a 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -16,6 +16,9 @@ import ( // JobExecutor is the interface for executing cron jobs through the agent type JobExecutor interface { ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error) + // PublishResponseIfNeeded sends response to the outbound bus only when the + // agent did not already deliver content through the message tool in this round. + PublishResponseIfNeeded(ctx context.Context, channel, chatID, response string) } // CronTool provides scheduling capabilities for the agent @@ -111,6 +114,11 @@ func (t *CronTool) Parameters() map[string]any { "type": "string", "description": "Job ID (for remove/enable/disable)", }, + "type": map[string]any{ + "type": "string", + "enum": []string{"message", "directive"}, + "description": "Message generation strategy. 'message' (default): content is sent directly as-is. 'directive': content is treated as instructions for an AI agent to execute before delivery.", + }, "deliver": map[string]any{ "type": "boolean", "description": "If true, send message directly to channel. If false, let agent process message (for complex tasks). Default: false", @@ -197,6 +205,12 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult deliver = d } + // Validate type parameter (server-side whitelist, not just LLM schema hint) + msgType, _ := args["type"].(string) + if msgType != "" && msgType != "message" && msgType != "directive" { + return ErrorResult(fmt.Sprintf("invalid type %q, must be 'message' or 'directive'", msgType)) + } + // GHSA-pv8c-p6jf-3fpp: command scheduling requires internal channel. When // allow_command is disabled, explicit confirmation is required as an override. // Non-command reminders remain open to all channels. @@ -230,9 +244,17 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult return ErrorResult(fmt.Sprintf("Error adding job: %v", err)) } + // Apply optional payload fields and persist in a single UpdateJob call + needsUpdate := false if command != "" { job.Payload.Command = command - // Need to save the updated payload + needsUpdate = true + } + if msgType != "" { + job.Payload.Type = msgType + needsUpdate = true + } + if needsUpdate { t.cronService.UpdateJob(job) } @@ -347,8 +369,13 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return "ok" } - // If deliver=true, send message directly without agent processing - if job.Payload.Deliver { + // Determine message generation strategy + // Type="directive": treat message as instructions for AI agent to execute + // Type="" or "message" (default): static message content + isDirective := job.Payload.Type == "directive" + + // If deliver=true and not directive, send message directly without agent processing + if job.Payload.Deliver && !isDirective { pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) defer pubCancel() t.msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ @@ -359,13 +386,23 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return "ok" } - // For deliver=false, process through agent (for complex tasks) + // For deliver=false OR directive mode, process through agent sessionKey := fmt.Sprintf("cron-%s", job.ID) - // Call agent with job's message + // Prepare the prompt based on type + prompt := job.Payload.Message + if isDirective { + // For directive type, prefix to clarify this is an instruction + prompt = fmt.Sprintf( + "Please execute the following directive and provide the result:\n\n%s", + job.Payload.Message, + ) + } + + // Call agent with the prepared prompt response, err := t.executor.ProcessDirectWithChannel( ctx, - job.Payload.Message, + prompt, sessionKey, channel, chatID, @@ -374,7 +411,8 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return fmt.Sprintf("Error: %v", err) } - // Response is automatically sent via MessageBus by AgentLoop - _ = response // Will be sent by AgentLoop + if response != "" { + t.executor.PublishResponseIfNeeded(ctx, channel, chatID, response) + } return "ok" } diff --git a/pkg/tools/cron_test.go b/pkg/tools/cron_test.go index cd7d39860..186c6a75e 100644 --- a/pkg/tools/cron_test.go +++ b/pkg/tools/cron_test.go @@ -2,6 +2,7 @@ package tools import ( "context" + "fmt" "path/filepath" "strings" "testing" @@ -12,18 +13,59 @@ import ( "github.com/sipeed/picoclaw/pkg/cron" ) -func newTestCronToolWithConfig(t *testing.T, cfg *config.Config) *CronTool { +type stubJobExecutor struct { + response string + err error + alreadySent bool // simulate message tool having already sent in this round + lastPrompt string + lastKey string + lastChan string + lastChatID string + publishedResp string + publishedChan string + publishedChatID string +} + +func (s *stubJobExecutor) ProcessDirectWithChannel( + _ context.Context, + content, sessionKey, channel, chatID string, +) (string, error) { + s.lastPrompt = content + s.lastKey = sessionKey + s.lastChan = channel + s.lastChatID = chatID + return s.response, s.err +} + +func (s *stubJobExecutor) PublishResponseIfNeeded( + _ context.Context, + channel, chatID, response string, +) { + if s.alreadySent { + return + } + s.publishedResp = response + s.publishedChan = channel + s.publishedChatID = chatID +} + +func newTestCronToolWithExecutorAndConfig(t *testing.T, executor JobExecutor, cfg *config.Config) *CronTool { t.Helper() storePath := filepath.Join(t.TempDir(), "cron.json") cronService := cron.NewCronService(storePath, nil) msgBus := bus.NewMessageBus() - tool, err := NewCronTool(cronService, nil, msgBus, t.TempDir(), true, 0, cfg) + tool, err := NewCronTool(cronService, executor, msgBus, t.TempDir(), true, 0, cfg) if err != nil { t.Fatalf("NewCronTool() error: %v", err) } return tool } +func newTestCronToolWithConfig(t *testing.T, cfg *config.Config) *CronTool { + t.Helper() + return newTestCronToolWithExecutorAndConfig(t, nil, cfg) +} + func newTestCronTool(t *testing.T) *CronTool { t.Helper() return newTestCronToolWithConfig(t, config.DefaultConfig()) @@ -237,3 +279,218 @@ func TestCronTool_ExecuteJobPublishesErrorWhenExecDisabled(t *testing.T) { t.Fatalf("expected exec disabled message, got: %s", msg.Content) } } + +func TestCronTool_ExecuteJobPublishesAgentResponse(t *testing.T) { + executor := &stubJobExecutor{response: "generated reply"} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-1"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "send me a poem" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + if executor.lastKey != "cron-job-1" { + t.Fatalf("sessionKey = %q, want cron-job-1", executor.lastKey) + } + if executor.lastChan != "telegram" || executor.lastChatID != "chat-1" { + t.Fatalf("executor target = %s/%s, want telegram/chat-1", executor.lastChan, executor.lastChatID) + } + if executor.lastPrompt != "send me a poem" { + t.Fatalf("prompt = %q, want original message", executor.lastPrompt) + } + if executor.publishedResp != "generated reply" { + t.Fatalf("published response = %q, want generated reply", executor.publishedResp) + } + if executor.publishedChan != "telegram" || executor.publishedChatID != "chat-1" { + t.Fatalf("published target = %s/%s, want telegram/chat-1", executor.publishedChan, executor.publishedChatID) + } +} + +func TestCronTool_ExecuteJobSkipsEmptyAgentResponse(t *testing.T) { + executor := &stubJobExecutor{} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-empty"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "say nothing" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + if executor.publishedResp != "" { + t.Fatalf("unexpected published response: %q", executor.publishedResp) + } +} + +func TestCronTool_ExecuteJobSkipsWhenMessageToolAlreadySent(t *testing.T) { + executor := &stubJobExecutor{response: "Sent.", alreadySent: true} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-msg-sent"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "send weather" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + if executor.publishedResp != "" { + t.Fatalf("expected no published response when message tool already sent, got: %q", executor.publishedResp) + } +} + +func TestCronTool_ExecuteJobDirectiveAddsPromptPrefix(t *testing.T) { + executor := &stubJobExecutor{response: "directive result"} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + originalMsg := "check the weather and summarize" + job := &cron.CronJob{ID: "job-dir-1"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = originalMsg + job.Payload.Type = "directive" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + wantPrompt := "Please execute the following directive and provide the result:\n\n" + originalMsg + if executor.lastPrompt != wantPrompt { + t.Fatalf("prompt = %q, want exact %q", executor.lastPrompt, wantPrompt) + } + if executor.publishedResp != "directive result" { + t.Fatalf("published response = %q, want %q", executor.publishedResp, "directive result") + } +} + +func TestCronTool_ExecuteJobDirectiveWithDeliverRoutesToAgent(t *testing.T) { + executor := &stubJobExecutor{response: "agent processed"} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-dir-deliver"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "generate daily report" + job.Payload.Type = "directive" + job.Payload.Deliver = true + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + if executor.lastPrompt == "" { + t.Fatal("expected agent to be called for directive+deliver, but ProcessDirectWithChannel was not invoked") + } + if executor.publishedResp != "agent processed" { + t.Fatalf("published response = %q, want %q", executor.publishedResp, "agent processed") + } + + // Verify no direct publish happened on the bus (agent path, not direct path) + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + select { + case msg := <-tool.msgBus.OutboundChan(): + t.Fatalf("unexpected direct bus message: %+v", msg) + case <-ctx.Done(): + // expected: no direct bus message + } +} + +func TestCronTool_ExecuteJobDeliverMessageDirectlyToBus(t *testing.T) { + executor := &stubJobExecutor{response: "should not be called"} + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-deliver"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "hello world" + job.Payload.Deliver = true + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + if executor.lastPrompt != "" { + t.Fatal("expected agent NOT to be invoked for deliver=true message type") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + select { + case msg := <-tool.msgBus.OutboundChan(): + if msg.Content != "hello world" { + t.Fatalf("bus content = %q, want %q", msg.Content, "hello world") + } + case <-ctx.Done(): + t.Fatal("timeout waiting for direct bus message") + } +} + +func TestCronTool_ExecuteJobReturnsErrorWithoutPublish(t *testing.T) { + executor := &stubJobExecutor{ + response: "this response must not be published", + err: fmt.Errorf("agent failure"), + } + tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) + + job := &cron.CronJob{ID: "job-err"} + job.Payload.Channel = "telegram" + job.Payload.To = "chat-1" + job.Payload.Message = "do something" + + got := tool.ExecuteJob(context.Background(), job) + if !strings.Contains(got, "agent failure") { + t.Fatalf("ExecuteJob() = %q, want error message", got) + } + + if executor.publishedResp != "" { + t.Fatalf("unexpected publish on error path: %q", executor.publishedResp) + } +} + +func TestCronTool_AddJobRejectsInvalidType(t *testing.T) { + tool := newTestCronTool(t) + ctx := WithToolContext(context.Background(), "cli", "direct") + result := tool.Execute(ctx, map[string]any{ + "action": "add", + "message": "test", + "at_seconds": float64(60), + "type": "invalid_type", + }) + + if !result.IsError { + t.Fatal("expected error for invalid type parameter") + } + if !strings.Contains(result.ForLLM, "invalid type") { + t.Errorf("expected 'invalid type' error, got: %s", result.ForLLM) + } +} + +func TestCronTool_AddJobAcceptsValidTypes(t *testing.T) { + for _, msgType := range []string{"", "message", "directive"} { + t.Run("type="+msgType, func(t *testing.T) { + tool := newTestCronTool(t) + ctx := WithToolContext(context.Background(), "cli", "direct") + args := map[string]any{ + "action": "add", + "message": "test", + "at_seconds": float64(60), + } + if msgType != "" { + args["type"] = msgType + } + + result := tool.Execute(ctx, args) + if result.IsError { + t.Fatalf("expected valid type %q to succeed, got: %s", msgType, result.ForLLM) + } + }) + } +} From e70928cc6f4fcf88f4f7a34ba18a7126824b42c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=85=89=E6=98=A5?= Date: Sun, 29 Mar 2026 14:37:22 +0800 Subject: [PATCH 29/55] feat(mcp): support DisableStandaloneSSE for HTTP transport (#2108) --- pkg/mcp/manager.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/mcp/manager.go b/pkg/mcp/manager.go index 7b63cc979..323df0312 100644 --- a/pkg/mcp/manager.go +++ b/pkg/mcp/manager.go @@ -276,14 +276,25 @@ func (m *Manager) ConnectServer( if cfg.URL == "" { return fmt.Errorf("URL is required for SSE/HTTP transport") } + + // Configure DisableStandaloneSSE based on transport type. + // - "http": Request-response only mode. Disable the standalone SSE stream + // to avoid compatibility issues with servers that don't support GET /mcp. + // - "sse": Bidirectional mode. Enable the standalone SSE stream to receive + // server-initiated notifications (e.g., ToolListChangedNotification). + // - Empty or auto-detected: Defaults to "sse" behavior (standalone SSE enabled). + disableStandaloneSSE := (cfg.Type == "http") + logger.DebugCF("mcp", "Using SSE/HTTP transport", map[string]any{ - "server": name, - "url": cfg.URL, + "server": name, + "url": cfg.URL, + "disableStandaloneSSE": disableStandaloneSSE, }) sseTransport := &mcp.StreamableClientTransport{ - Endpoint: cfg.URL, + Endpoint: cfg.URL, + DisableStandaloneSSE: disableStandaloneSSE, } // Add custom headers if provided From f0c0219c4cd6cc86b12b8635c185dc0a4327a225 Mon Sep 17 00:00:00 2001 From: Cytown Date: Sun, 29 Mar 2026 16:58:48 +0800 Subject: [PATCH 30/55] fix for review --- pkg/channels/pico/protocol.go | 2 ++ pkg/gateway/gateway.go | 8 +++----- web/backend/api/gateway.go | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/channels/pico/protocol.go b/pkg/channels/pico/protocol.go index 0a630e193..192c96164 100644 --- a/pkg/channels/pico/protocol.go +++ b/pkg/channels/pico/protocol.go @@ -17,6 +17,8 @@ const ( TypeTypingStop = "typing.stop" TypeError = "error" TypePong = "pong" + + PicoTokenPrefix = "pico-" ) // PicoMessage is the wire format for all Pico Protocol messages. diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index a63530806..1a9ab4461 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -22,7 +22,7 @@ import ( _ "github.com/sipeed/picoclaw/pkg/channels/line" _ "github.com/sipeed/picoclaw/pkg/channels/maixcam" _ "github.com/sipeed/picoclaw/pkg/channels/onebot" - _ "github.com/sipeed/picoclaw/pkg/channels/pico" + "github.com/sipeed/picoclaw/pkg/channels/pico" _ "github.com/sipeed/picoclaw/pkg/channels/qq" _ "github.com/sipeed/picoclaw/pkg/channels/slack" _ "github.com/sipeed/picoclaw/pkg/channels/telegram" @@ -662,8 +662,6 @@ func setupCronTool( return cronService, nil } -const picoTokenPrefix = "pico-" - // overridePicoToken replaces the pico channel token with the one from the PID file. // The PID file is the single source of truth for the pico auth token; // it is generated once at gateway startup and remains unchanged across reloads. @@ -672,10 +670,10 @@ func overridePicoToken(cfg *config.Config, token string) { return } picoToken := cfg.Channels.Pico.Token.String() - if picoToken == "" || strings.HasPrefix(picoToken, picoTokenPrefix) { + if picoToken == "" || strings.HasPrefix(picoToken, pico.PicoTokenPrefix) { return } - cfg.Channels.Pico.SetToken(picoTokenPrefix + token + picoToken) + cfg.Channels.Pico.SetToken(pico.PicoTokenPrefix + token + picoToken) } func createHeartbeatHandler(agentLoop *agent.AgentLoop) func(prompt, channel, chatID string) *tools.ToolResult { diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index 98fb77a04..ce3a9ca1e 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -17,6 +17,7 @@ import ( "syscall" "time" + "github.com/sipeed/picoclaw/pkg/channels/pico" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/health" "github.com/sipeed/picoclaw/pkg/logger" @@ -59,9 +60,8 @@ func refreshPicoTokensLocked(configPath string) { } const ( - protocolKey = "Sec-Websocket-Protocol" - picoTokenPrefix = "pico-" - tokenPrefix = "token." + protocolKey = "Sec-Websocket-Protocol" + tokenPrefix = "token." ) // picoComposedToken returns "pico-"+pidToken+picoToken for gateway auth. @@ -75,7 +75,7 @@ func picoComposedToken(token string) string { if tokenPrefix+gateway.picoToken != token { return "" } - return picoTokenPrefix + gateway.pidData.Token + gateway.picoToken + return pico.PicoTokenPrefix + gateway.pidData.Token + gateway.picoToken } var ( From 42e3aaff35c6568721d8e2dc21ab6babcc32c20f Mon Sep 17 00:00:00 2001 From: Cytown Date: Sun, 29 Mar 2026 18:22:25 +0800 Subject: [PATCH 31/55] make logger more clear with highlight component and use package name for default component --- pkg/logger/logger.go | 45 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 1244addb2..f88d5ed2b 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -21,6 +21,8 @@ const ( WARN = zerolog.WarnLevel ERROR = zerolog.ErrorLevel FATAL = zerolog.FatalLevel + + Component = "component" ) var ( @@ -50,6 +52,18 @@ func init() { // Custom formatter to handle multiline strings and JSON objects FormatFieldValue: formatFieldValue, + PartsOrder: []string{ + zerolog.TimestampFieldName, + zerolog.LevelFieldName, + Component, + zerolog.CallerFieldName, + zerolog.MessageFieldName, + }, + FieldsExclude: []string{Component}, + FormatPrepare: func(fields map[string]any) error { + fields[Component] = fmt.Sprintf("\x1b[33m%v\x1b[0m", fields[Component]) + return nil + }, } logger = zerolog.New(consoleWriter).With().Timestamp().Caller().Logger() @@ -193,7 +207,19 @@ func ConfigureFromEnv() { } } -func getCallerSkip() int { +func getPackageNameFromFile(filePath string) string { + dir := filepath.Dir(filePath) + importPath := filepath.ToSlash(dir) + + parts := strings.Split(importPath, "/") + if len(parts) == 0 { + return "" + } + + return parts[len(parts)-1] +} + +func getCallerSkip() (int, string) { for i := 2; i < 15; i++ { pc, file, _, ok := runtime.Caller(i) if !ok { @@ -217,10 +243,10 @@ func getCallerSkip() int { continue } - return i - 1 + return i - 1, getPackageNameFromFile(file) } - return 3 + return 3, "" } //nolint:zerologlint @@ -246,14 +272,16 @@ func logMessage(level LogLevel, component string, message string, fields map[str return } - skip := getCallerSkip() + skip, pkg := getCallerSkip() event := getEvent(logger, level) - if component != "" { - event.Str("component", component) + if component == "" { + component = pkg } + event.Str(Component, component) + appendFields(event, fields) event.CallerSkipFrame(skip).Msg(message) @@ -261,10 +289,7 @@ func logMessage(level LogLevel, component string, message string, fields map[str if fileLogger.GetLevel() != zerolog.NoLevel { fileEvent := getEvent(fileLogger, level) - if component != "" { - fileEvent.Str("component", component) - } - // fileEvent.Str("caller", fmt.Sprintf("%s:%d (%s)", callerFile, callerLine, callerFunc)) + fileEvent.Str(Component, component) appendFields(fileEvent, fields) fileEvent.CallerSkipFrame(skip).Msg(message) From a4574f72a3b328666782c1d143134e79f75c1f0b Mon Sep 17 00:00:00 2001 From: Alix-007 Date: Sun, 29 Mar 2026 22:19:13 +0800 Subject: [PATCH 32/55] fix(web/config): persist Discord token updates from channel settings (#2024) * fix: save Discord token updates from channel settings - preserve secret fields from PUT/PATCH /api/config payloads via setters - include _token edit fields in channel save payload construction - add regression test for Discord token patch flow (issue #2005) * fix: resolve shadow lint warnings in config secret mapping * fix(web/api): adapt config secret patch path after #2068 --------- Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com> --- web/backend/api/config.go | 150 ++++++++++++++++++ web/backend/api/config_test.go | 36 +++++ .../channels/channel-config-page.tsx | 25 +-- 3 files changed, 200 insertions(+), 11 deletions(-) diff --git a/web/backend/api/config.go b/web/backend/api/config.go index 0add7594d..06391b8fc 100644 --- a/web/backend/api/config.go +++ b/web/backend/api/config.go @@ -52,6 +52,11 @@ func (h *Handler) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest) return } + var raw map[string]any + if err = json.Unmarshal(body, &raw); err != nil { + http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest) + return + } if execAllowRemoteOmitted(body) { cfg.Tools.Exec.AllowRemote = config.DefaultConfig().Tools.Exec.AllowRemote } @@ -63,6 +68,7 @@ func (h *Handler) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Failed to apply security config: %v", err), http.StatusInternalServerError) return } + applyConfigSecretsFromMap(&cfg, raw) if errs := validateConfig(&cfg); len(errs) > 0 { w.Header().Set("Content-Type", "application/json") @@ -159,6 +165,7 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Failed to apply security config: %v", err), http.StatusInternalServerError) return } + applyConfigSecretsFromMap(&newCfg, base) if errs := validateConfig(&newCfg); len(errs) > 0 { w.Header().Set("Content-Type", "application/json") @@ -325,3 +332,146 @@ func mergeMap(dst, src map[string]any) { } } } + +func asMapField(value map[string]any, key string) (map[string]any, bool) { + raw, exists := value[key] + if !exists { + return nil, false + } + m, isMap := raw.(map[string]any) + return m, isMap +} + +func getSecretString(m map[string]any, key string) (string, bool) { + if raw, exists := m[key]; exists { + s, isString := raw.(string) + if isString { + return s, true + } + } + if raw, exists := m["_"+key]; exists { + s, isString := raw.(string) + if isString { + return s, true + } + } + return "", false +} + +func applyConfigSecretsFromMap(cfg *config.Config, raw map[string]any) { + channels, hasChannels := asMapField(raw, "channels") + if hasChannels { + if telegram, hasTelegram := asMapField(channels, "telegram"); hasTelegram { + if token, hasToken := getSecretString(telegram, "token"); hasToken { + cfg.Channels.Telegram.SetToken(token) + } + } + if feishu, hasFeishu := asMapField(channels, "feishu"); hasFeishu { + if appSecret, hasAppSecret := getSecretString(feishu, "app_secret"); hasAppSecret { + cfg.Channels.Feishu.AppSecret.Set(appSecret) + } + if encryptKey, hasEncryptKey := getSecretString(feishu, "encrypt_key"); hasEncryptKey { + cfg.Channels.Feishu.EncryptKey.Set(encryptKey) + } + if verificationToken, hasVerificationToken := getSecretString( + feishu, + "verification_token", + ); hasVerificationToken { + cfg.Channels.Feishu.VerificationToken.Set(verificationToken) + } + } + if discord, hasDiscord := asMapField(channels, "discord"); hasDiscord { + if token, hasToken := getSecretString(discord, "token"); hasToken { + cfg.Channels.Discord.Token.Set(token) + } + } + if weixin, hasWeixin := asMapField(channels, "weixin"); hasWeixin { + if token, hasToken := getSecretString(weixin, "token"); hasToken { + cfg.Channels.Weixin.SetToken(token) + } + } + if qq, hasQQ := asMapField(channels, "qq"); hasQQ { + if appSecret, hasAppSecret := getSecretString(qq, "app_secret"); hasAppSecret { + cfg.Channels.QQ.AppSecret.Set(appSecret) + } + } + if dingtalk, hasDingTalk := asMapField(channels, "dingtalk"); hasDingTalk { + if clientSecret, hasClientSecret := getSecretString(dingtalk, "client_secret"); hasClientSecret { + cfg.Channels.DingTalk.ClientSecret.Set(clientSecret) + } + } + if slack, hasSlack := asMapField(channels, "slack"); hasSlack { + if botToken, hasBotToken := getSecretString(slack, "bot_token"); hasBotToken { + cfg.Channels.Slack.BotToken.Set(botToken) + } + if appToken, hasAppToken := getSecretString(slack, "app_token"); hasAppToken { + cfg.Channels.Slack.AppToken.Set(appToken) + } + } + if matrix, hasMatrix := asMapField(channels, "matrix"); hasMatrix { + if accessToken, hasAccessToken := getSecretString(matrix, "access_token"); hasAccessToken { + cfg.Channels.Matrix.AccessToken.Set(accessToken) + } + } + if line, hasLine := asMapField(channels, "line"); hasLine { + if channelSecret, hasChannelSecret := getSecretString(line, "channel_secret"); hasChannelSecret { + cfg.Channels.LINE.ChannelSecret.Set(channelSecret) + } + if channelAccessToken, hasChannelAccessToken := getSecretString( + line, + "channel_access_token", + ); hasChannelAccessToken { + cfg.Channels.LINE.ChannelAccessToken.Set(channelAccessToken) + } + } + if onebot, hasOneBot := asMapField(channels, "onebot"); hasOneBot { + if accessToken, hasAccessToken := getSecretString(onebot, "access_token"); hasAccessToken { + cfg.Channels.OneBot.AccessToken.Set(accessToken) + } + } + if wecom, hasWeCom := asMapField(channels, "wecom"); hasWeCom { + if secret, hasSecret := getSecretString(wecom, "secret"); hasSecret { + cfg.Channels.WeCom.SetSecret(secret) + } + } + if pico, hasPico := asMapField(channels, "pico"); hasPico { + if token, hasToken := getSecretString(pico, "token"); hasToken { + cfg.Channels.Pico.SetToken(token) + } + } + if irc, hasIRC := asMapField(channels, "irc"); hasIRC { + if password, hasPassword := getSecretString(irc, "password"); hasPassword { + cfg.Channels.IRC.Password.Set(password) + } + if nickservPassword, hasNickservPassword := getSecretString(irc, "nickserv_password"); hasNickservPassword { + cfg.Channels.IRC.NickServPassword.Set(nickservPassword) + } + if saslPassword, hasSASLPassword := getSecretString(irc, "sasl_password"); hasSASLPassword { + cfg.Channels.IRC.SASLPassword.Set(saslPassword) + } + } + } + + tools, hasTools := asMapField(raw, "tools") + if !hasTools { + return + } + skills, hasSkills := asMapField(tools, "skills") + if !hasSkills { + return + } + if github, hasGithub := asMapField(skills, "github"); hasGithub { + if token, hasToken := getSecretString(github, "token"); hasToken { + cfg.Tools.Skills.Github.Token.Set(token) + } + } + registries, hasRegistries := asMapField(skills, "registries") + if !hasRegistries { + return + } + if clawHub, hasClawHub := asMapField(registries, "clawhub"); hasClawHub { + if authToken, hasAuthToken := getSecretString(clawHub, "auth_token"); hasAuthToken { + cfg.Tools.Skills.Registries.ClawHub.AuthToken.Set(authToken) + } + } +} diff --git a/web/backend/api/config_test.go b/web/backend/api/config_test.go index 644284849..d3e25a7f9 100644 --- a/web/backend/api/config_test.go +++ b/web/backend/api/config_test.go @@ -251,6 +251,42 @@ func TestHandlePatchConfig_SucceedsWhenPicoTokenInSecurityOnly(t *testing.T) { } } +func TestHandlePatchConfig_SavesDiscordTokenFromPayload(t *testing.T) { + configPath, cleanup := setupOAuthTestEnv(t) + defer cleanup() + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{ + "channels": { + "discord": { + "enabled": true, + "token": "discord-test-token" + } + } + }`)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("PATCH /api/config status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + cfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + if !cfg.Channels.Discord.Enabled { + t.Fatal("discord should be enabled after PATCH") + } + if got := cfg.Channels.Discord.Token.String(); got != "discord-test-token" { + t.Fatalf("discord token = %q, want %q", got, "discord-test-token") + } +} + func TestHandlePatchConfig_AllowsInvalidDenyRegexPatternsWhenDenyPatternsDisabled(t *testing.T) { configPath, cleanup := setupOAuthTestEnv(t) defer cleanup() diff --git a/web/frontend/src/components/channels/channel-config-page.tsx b/web/frontend/src/components/channels/channel-config-page.tsx index 6af821ac9..3890924e0 100644 --- a/web/frontend/src/components/channels/channel-config-page.tsx +++ b/web/frontend/src/components/channels/channel-config-page.tsx @@ -62,10 +62,8 @@ function asBool(value: unknown): boolean { function buildEditConfig(config: ChannelConfig): ChannelConfig { const edit: ChannelConfig = { ...config } - for (const secretKey of Object.keys(SECRET_FIELD_MAP)) { - if (secretKey in config) { - edit[SECRET_FIELD_MAP[secretKey]] = "" - } + for (const editKey of Object.values(SECRET_FIELD_MAP)) { + edit[editKey] = "" } return edit } @@ -94,17 +92,22 @@ function buildSavePayload( for (const [key, value] of Object.entries(editConfig)) { if (key.startsWith("_")) continue if (key === "enabled") continue - - if (key in SECRET_FIELD_MAP) { - const editKey = SECRET_FIELD_MAP[key] - const incoming = asString(editConfig[editKey]) - payload[key] = incoming !== "" ? incoming : value - continue - } + if (key in SECRET_FIELD_MAP) continue payload[key] = value } + for (const [secretKey, editKey] of Object.entries(SECRET_FIELD_MAP)) { + const incoming = asString(editConfig[editKey]) + if (incoming !== "") { + payload[secretKey] = incoming + continue + } + if (secretKey in editConfig) { + payload[secretKey] = editConfig[secretKey] + } + } + if (channel.name === "whatsapp_native") { payload.use_native = true } From 1ef0553929c73ee5c3fa4d719ff6cf4851da9244 Mon Sep 17 00:00:00 2001 From: Cytown Date: Sun, 29 Mar 2026 22:32:39 +0800 Subject: [PATCH 33/55] add logger test case for console log format for component (#2162) --- pkg/logger/logger.go | 23 +++++++++++++++++++---- pkg/logger/logger_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index f88d5ed2b..33079616a 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -11,6 +11,7 @@ import ( "sync" "github.com/rs/zerolog" + "golang.org/x/term" ) type LogLevel = zerolog.Level @@ -46,6 +47,8 @@ func init() { once.Do(func() { zerolog.SetGlobalLevel(zerolog.InfoLevel) + isTTY := term.IsTerminal(int(os.Stdout.Fd())) + consoleWriter := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "15:04:05", // TODO: make it configurable??? @@ -61,9 +64,12 @@ func init() { }, FieldsExclude: []string{Component}, FormatPrepare: func(fields map[string]any) error { - fields[Component] = fmt.Sprintf("\x1b[33m%v\x1b[0m", fields[Component]) + if isTTY { + fields[Component] = fmt.Sprintf("\x1b[33m%v\x1b[0m", fields[Component]) + } return nil }, + NoColor: !isTTY, } logger = zerolog.New(consoleWriter).With().Timestamp().Caller().Logger() @@ -207,16 +213,25 @@ func ConfigureFromEnv() { } } +const ( + locUnknown = "" +) + func getPackageNameFromFile(filePath string) string { dir := filepath.Dir(filePath) importPath := filepath.ToSlash(dir) parts := strings.Split(importPath, "/") if len(parts) == 0 { - return "" + return locUnknown } - return parts[len(parts)-1] + pkg := parts[len(parts)-1] + if pkg == "." { + return "
    " + } + + return pkg } func getCallerSkip() (int, string) { @@ -246,7 +261,7 @@ func getCallerSkip() (int, string) { return i - 1, getPackageNameFromFile(file) } - return 3, "" + return 3, locUnknown } //nolint:zerologlint diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go index 1eca72607..7a7712de0 100644 --- a/pkg/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -406,3 +406,28 @@ func TestConfigureFromEnvNoEnv(t *testing.T) { os.Unsetenv("PICOCLAW_LOG_FILE") ConfigureFromEnv() } + +func TestGetPackageNameFromFile(t *testing.T) { + tests := []struct { + name string + path string + want string + }{ + {"normal package path", "/home/user/project/pkg/logger/logger.go", "logger"}, + {"nested package", "/home/user/project/internal/service/auth/handler.go", "auth"}, + {"cmd package", "/home/user/project/cmd/server/main.go", "server"}, + {"project root returns main", "./main.go", "
    "}, + {"single dot returns main", ".", "
    "}, + {"single directory", "mypkg/file.go", "mypkg"}, + {"deep nesting", "/a/b/c/d/e/f.go", "e"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getPackageNameFromFile(tt.path) + if got != tt.want { + t.Errorf("getPackageNameFromFile(%q) = %q, want %q", tt.path, got, tt.want) + } + }) + } +} From 1fc5345857692365ad09fe3ea5f449c221a6b407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?daming=E5=A4=A7=E9=93=AD?= Date: Sun, 29 Mar 2026 22:52:34 +0800 Subject: [PATCH 34/55] refactor(cron): remove deliver and type params, unify agent execution path (#2147) The agent path now publishes to outbound bus directly (since #2100), making the deliver=true direct-to-bus shortcut and the directive type prompt wrapping redundant. All cron jobs now uniformly route through the agent. This is an intentional behavior change: old jobs with deliver=true will execute through the agent instead of bypassing it. Co-authored-by: Claude Opus 4.6 --- cmd/picoclaw/internal/cron/add.go | 4 +- cmd/picoclaw/internal/cron/add_test.go | 1 - pkg/cron/service.go | 4 - pkg/cron/service_test.go | 10 +- pkg/tools/cron.go | 61 +--------- pkg/tools/cron_test.go | 149 ------------------------- 6 files changed, 9 insertions(+), 220 deletions(-) diff --git a/cmd/picoclaw/internal/cron/add.go b/cmd/picoclaw/internal/cron/add.go index 947557d5a..f9d73089d 100644 --- a/cmd/picoclaw/internal/cron/add.go +++ b/cmd/picoclaw/internal/cron/add.go @@ -14,7 +14,6 @@ func newAddCommand(storePath func() string) *cobra.Command { message string every int64 cronExp string - deliver bool channel string to string ) @@ -37,7 +36,7 @@ func newAddCommand(storePath func() string) *cobra.Command { } cs := cron.NewCronService(storePath(), nil) - job, err := cs.AddJob(name, schedule, message, deliver, channel, to) + job, err := cs.AddJob(name, schedule, message, channel, to) if err != nil { return fmt.Errorf("error adding job: %w", err) } @@ -52,7 +51,6 @@ func newAddCommand(storePath func() string) *cobra.Command { cmd.Flags().StringVarP(&message, "message", "m", "", "Message for agent") cmd.Flags().Int64VarP(&every, "every", "e", 0, "Run every N seconds") cmd.Flags().StringVarP(&cronExp, "cron", "c", "", "Cron expression (e.g. '0 9 * * *')") - cmd.Flags().BoolVarP(&deliver, "deliver", "d", false, "Deliver response to channel") cmd.Flags().StringVar(&to, "to", "", "Recipient for delivery") cmd.Flags().StringVar(&channel, "channel", "", "Channel for delivery") diff --git a/cmd/picoclaw/internal/cron/add_test.go b/cmd/picoclaw/internal/cron/add_test.go index 09701fab5..53875dc51 100644 --- a/cmd/picoclaw/internal/cron/add_test.go +++ b/cmd/picoclaw/internal/cron/add_test.go @@ -21,7 +21,6 @@ func TestNewAddSubcommand(t *testing.T) { assert.NotNil(t, cmd.Flags().Lookup("every")) assert.NotNil(t, cmd.Flags().Lookup("cron")) - assert.NotNil(t, cmd.Flags().Lookup("deliver")) assert.NotNil(t, cmd.Flags().Lookup("to")) assert.NotNil(t, cmd.Flags().Lookup("channel")) diff --git a/pkg/cron/service.go b/pkg/cron/service.go index c1a224013..6a8728943 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -25,10 +25,8 @@ type CronSchedule struct { type CronPayload struct { Kind string `json:"kind"` - Type string `json:"type"` Message string `json:"message"` Command string `json:"command,omitempty"` - Deliver bool `json:"deliver"` Channel string `json:"channel,omitempty"` To string `json:"to,omitempty"` } @@ -410,7 +408,6 @@ func (cs *CronService) AddJob( name string, schedule CronSchedule, message string, - deliver bool, channel, to string, ) (*CronJob, error) { cs.mu.Lock() @@ -429,7 +426,6 @@ func (cs *CronService) AddJob( Payload: CronPayload{ Kind: "agent_turn", Message: message, - Deliver: deliver, Channel: channel, To: to, }, diff --git a/pkg/cron/service_test.go b/pkg/cron/service_test.go index c55e62174..6dff3b387 100644 --- a/pkg/cron/service_test.go +++ b/pkg/cron/service_test.go @@ -20,7 +20,7 @@ func TestSaveStore_FilePermissions(t *testing.T) { cs := NewCronService(storePath, nil) - _, err := cs.AddJob("test", CronSchedule{Kind: "every", EveryMS: int64Ptr(60000)}, "hello", false, "cli", "direct") + _, err := cs.AddJob("test", CronSchedule{Kind: "every", EveryMS: int64Ptr(60000)}, "hello", "cli", "direct") if err != nil { t.Fatalf("AddJob failed: %v", err) } @@ -52,7 +52,7 @@ func TestCronService_CRUD(t *testing.T) { // Test AddJob at := time.Now().Add(time.Hour).UnixMilli() - job, err := cs.AddJob("Task1", CronSchedule{Kind: "at", AtMS: &at}, "msg", true, "ch", "to") + job, err := cs.AddJob("Task1", CronSchedule{Kind: "at", AtMS: &at}, "msg", "ch", "to") if err != nil || job.ID == "" { t.Fatalf("AddJob failed: %v", err) } @@ -134,7 +134,7 @@ func TestCronService_ExecutionFlow(t *testing.T) { // Add a job then runs 100ms from now target := time.Now().Add(100 * time.Millisecond).UnixMilli() - job, _ := cs.AddJob("FastJob", CronSchedule{Kind: "at", AtMS: &target}, "", false, "", "") + job, _ := cs.AddJob("FastJob", CronSchedule{Kind: "at", AtMS: &target}, "", "", "") // Check for job execution with a timeout success := false @@ -167,7 +167,7 @@ func TestCronService_PersistenceIntegrity(t *testing.T) { // write a job and persist cs1 := NewCronService(tmpFile, nil) at := int64(2000000000000) - cs1.AddJob("PersistMe", CronSchedule{Kind: "at", AtMS: &at}, "payload", true, "ch1", "") + cs1.AddJob("PersistMe", CronSchedule{Kind: "at", AtMS: &at}, "payload", "ch1", "") // check file exists if _, err := os.Stat(tmpFile); os.IsNotExist(err) { @@ -213,7 +213,7 @@ func TestCronService_ConcurrentAccess(t *testing.T) { defer wg.Done() for j := range iterations { at := time.Now().Add(time.Hour).UnixMilli() - cs.AddJob(fmt.Sprintf("Job-%d-%d", id, j), CronSchedule{Kind: "at", AtMS: &at}, "", false, "", "") + cs.AddJob(fmt.Sprintf("Job-%d-%d", id, j), CronSchedule{Kind: "at", AtMS: &at}, "", "", "") time.Sleep(100 * time.Microsecond) } }(i) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 60d9d5e5a..c6ac3a129 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -92,7 +92,7 @@ func (t *CronTool) Parameters() map[string]any { }, "command": map[string]any{ "type": "string", - "description": "Optional: Shell command to execute directly (e.g., 'df -h'). If set, the agent will run this command and report output instead of just showing the message. 'deliver' will be forced to false for commands.", + "description": "Optional: Shell command to execute directly (e.g., 'df -h'). If set, the agent will run this command and report output instead of just showing the message.", }, "command_confirm": map[string]any{ "type": "boolean", @@ -114,15 +114,6 @@ func (t *CronTool) Parameters() map[string]any { "type": "string", "description": "Job ID (for remove/enable/disable)", }, - "type": map[string]any{ - "type": "string", - "enum": []string{"message", "directive"}, - "description": "Message generation strategy. 'message' (default): content is sent directly as-is. 'directive': content is treated as instructions for an AI agent to execute before delivery.", - }, - "deliver": map[string]any{ - "type": "boolean", - "description": "If true, send message directly to channel. If false, let agent process message (for complex tasks). Default: false", - }, }, "required": []string{"action"}, } @@ -199,18 +190,6 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult return ErrorResult("one of at_seconds, every_seconds, or cron_expr is required") } - // Read deliver parameter, default to false so scheduled tasks execute through the agent - deliver := false - if d, ok := args["deliver"].(bool); ok { - deliver = d - } - - // Validate type parameter (server-side whitelist, not just LLM schema hint) - msgType, _ := args["type"].(string) - if msgType != "" && msgType != "message" && msgType != "directive" { - return ErrorResult(fmt.Sprintf("invalid type %q, must be 'message' or 'directive'", msgType)) - } - // GHSA-pv8c-p6jf-3fpp: command scheduling requires internal channel. When // allow_command is disabled, explicit confirmation is required as an override. // Non-command reminders remain open to all channels. @@ -226,7 +205,6 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult if !t.allowCommand && !commandConfirm { return ErrorResult("command_confirm=true is required when allow_command is disabled") } - deliver = false } // Truncate message for job name (max 30 chars) @@ -236,7 +214,6 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult messagePreview, schedule, message, - deliver, channel, chatID, ) @@ -250,10 +227,6 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult job.Payload.Command = command needsUpdate = true } - if msgType != "" { - job.Payload.Type = msgType - needsUpdate = true - } if needsUpdate { t.cronService.UpdateJob(job) } @@ -369,40 +342,12 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return "ok" } - // Determine message generation strategy - // Type="directive": treat message as instructions for AI agent to execute - // Type="" or "message" (default): static message content - isDirective := job.Payload.Type == "directive" - - // If deliver=true and not directive, send message directly without agent processing - if job.Payload.Deliver && !isDirective { - pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer pubCancel() - t.msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ - Channel: channel, - ChatID: chatID, - Content: job.Payload.Message, - }) - return "ok" - } - - // For deliver=false OR directive mode, process through agent sessionKey := fmt.Sprintf("cron-%s", job.ID) - // Prepare the prompt based on type - prompt := job.Payload.Message - if isDirective { - // For directive type, prefix to clarify this is an instruction - prompt = fmt.Sprintf( - "Please execute the following directive and provide the result:\n\n%s", - job.Payload.Message, - ) - } - - // Call agent with the prepared prompt + // Call agent with the job message response, err := t.executor.ProcessDirectWithChannel( ctx, - prompt, + job.Payload.Message, sessionKey, channel, chatID, diff --git a/pkg/tools/cron_test.go b/pkg/tools/cron_test.go index 186c6a75e..c699908cd 100644 --- a/pkg/tools/cron_test.go +++ b/pkg/tools/cron_test.go @@ -229,28 +229,6 @@ func TestCronTool_NonCommandJobAllowedFromRemoteChannel(t *testing.T) { } } -func TestCronTool_NonCommandJobDefaultsDeliverToFalse(t *testing.T) { - tool := newTestCronTool(t) - ctx := WithToolContext(context.Background(), "telegram", "chat-1") - result := tool.Execute(ctx, map[string]any{ - "action": "add", - "message": "send me a poem", - "at_seconds": float64(600), - }) - - if result.IsError { - t.Fatalf("expected non-command reminder to succeed, got: %s", result.ForLLM) - } - - jobs := tool.cronService.ListJobs(false) - if len(jobs) != 1 { - t.Fatalf("expected 1 job, got %d", len(jobs)) - } - if jobs[0].Payload.Deliver { - t.Fatal("expected deliver=false by default for non-command jobs") - } -} - func TestCronTool_ExecuteJobPublishesErrorWhenExecDisabled(t *testing.T) { cfg := config.DefaultConfig() cfg.Tools.Exec.Enabled = false @@ -346,93 +324,6 @@ func TestCronTool_ExecuteJobSkipsWhenMessageToolAlreadySent(t *testing.T) { } } -func TestCronTool_ExecuteJobDirectiveAddsPromptPrefix(t *testing.T) { - executor := &stubJobExecutor{response: "directive result"} - tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) - - originalMsg := "check the weather and summarize" - job := &cron.CronJob{ID: "job-dir-1"} - job.Payload.Channel = "telegram" - job.Payload.To = "chat-1" - job.Payload.Message = originalMsg - job.Payload.Type = "directive" - - if got := tool.ExecuteJob(context.Background(), job); got != "ok" { - t.Fatalf("ExecuteJob() = %q, want ok", got) - } - - wantPrompt := "Please execute the following directive and provide the result:\n\n" + originalMsg - if executor.lastPrompt != wantPrompt { - t.Fatalf("prompt = %q, want exact %q", executor.lastPrompt, wantPrompt) - } - if executor.publishedResp != "directive result" { - t.Fatalf("published response = %q, want %q", executor.publishedResp, "directive result") - } -} - -func TestCronTool_ExecuteJobDirectiveWithDeliverRoutesToAgent(t *testing.T) { - executor := &stubJobExecutor{response: "agent processed"} - tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) - - job := &cron.CronJob{ID: "job-dir-deliver"} - job.Payload.Channel = "telegram" - job.Payload.To = "chat-1" - job.Payload.Message = "generate daily report" - job.Payload.Type = "directive" - job.Payload.Deliver = true - - if got := tool.ExecuteJob(context.Background(), job); got != "ok" { - t.Fatalf("ExecuteJob() = %q, want ok", got) - } - - if executor.lastPrompt == "" { - t.Fatal("expected agent to be called for directive+deliver, but ProcessDirectWithChannel was not invoked") - } - if executor.publishedResp != "agent processed" { - t.Fatalf("published response = %q, want %q", executor.publishedResp, "agent processed") - } - - // Verify no direct publish happened on the bus (agent path, not direct path) - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) - defer cancel() - select { - case msg := <-tool.msgBus.OutboundChan(): - t.Fatalf("unexpected direct bus message: %+v", msg) - case <-ctx.Done(): - // expected: no direct bus message - } -} - -func TestCronTool_ExecuteJobDeliverMessageDirectlyToBus(t *testing.T) { - executor := &stubJobExecutor{response: "should not be called"} - tool := newTestCronToolWithExecutorAndConfig(t, executor, config.DefaultConfig()) - - job := &cron.CronJob{ID: "job-deliver"} - job.Payload.Channel = "telegram" - job.Payload.To = "chat-1" - job.Payload.Message = "hello world" - job.Payload.Deliver = true - - if got := tool.ExecuteJob(context.Background(), job); got != "ok" { - t.Fatalf("ExecuteJob() = %q, want ok", got) - } - - if executor.lastPrompt != "" { - t.Fatal("expected agent NOT to be invoked for deliver=true message type") - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - select { - case msg := <-tool.msgBus.OutboundChan(): - if msg.Content != "hello world" { - t.Fatalf("bus content = %q, want %q", msg.Content, "hello world") - } - case <-ctx.Done(): - t.Fatal("timeout waiting for direct bus message") - } -} - func TestCronTool_ExecuteJobReturnsErrorWithoutPublish(t *testing.T) { executor := &stubJobExecutor{ response: "this response must not be published", @@ -454,43 +345,3 @@ func TestCronTool_ExecuteJobReturnsErrorWithoutPublish(t *testing.T) { t.Fatalf("unexpected publish on error path: %q", executor.publishedResp) } } - -func TestCronTool_AddJobRejectsInvalidType(t *testing.T) { - tool := newTestCronTool(t) - ctx := WithToolContext(context.Background(), "cli", "direct") - result := tool.Execute(ctx, map[string]any{ - "action": "add", - "message": "test", - "at_seconds": float64(60), - "type": "invalid_type", - }) - - if !result.IsError { - t.Fatal("expected error for invalid type parameter") - } - if !strings.Contains(result.ForLLM, "invalid type") { - t.Errorf("expected 'invalid type' error, got: %s", result.ForLLM) - } -} - -func TestCronTool_AddJobAcceptsValidTypes(t *testing.T) { - for _, msgType := range []string{"", "message", "directive"} { - t.Run("type="+msgType, func(t *testing.T) { - tool := newTestCronTool(t) - ctx := WithToolContext(context.Background(), "cli", "direct") - args := map[string]any{ - "action": "add", - "message": "test", - "at_seconds": float64(60), - } - if msgType != "" { - args["type"] = msgType - } - - result := tool.Execute(ctx, args) - if result.IsError { - t.Fatalf("expected valid type %q to succeed, got: %s", msgType, result.ForLLM) - } - }) - } -} From 93f4c4a843d216ab044c48f91b72b86853de828c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E9=9D=92=E5=B7=9D?= <46062972+ShenQingchuan@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:33:08 +0800 Subject: [PATCH 35/55] fix(web): skills page uses theme colors for dark mode (#2166) - Remove bg-white/80 override on skill cards so bg-card/text-card-foreground apply - Use bg-muted + text-foreground for skill path block readability Made-with: Cursor --- web/frontend/src/components/skills/skills-page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/frontend/src/components/skills/skills-page.tsx b/web/frontend/src/components/skills/skills-page.tsx index d8eeb1d93..7e0d66d47 100644 --- a/web/frontend/src/components/skills/skills-page.tsx +++ b/web/frontend/src/components/skills/skills-page.tsx @@ -169,7 +169,7 @@ export function SkillsPage() { {data.skills.map((skill) => ( @@ -211,7 +211,7 @@ export function SkillsPage() {
    {t("pages.agent.skills.path")}
    -
    +
    {skill.path}
    From cd3f6600ca6661d6fffaae3ac0bc60f05e3fb233 Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:04:51 +0800 Subject: [PATCH 36/55] fix(utils): honor Retry-After for 429 retries --- pkg/utils/http_retry.go | 29 +++++++++++++- pkg/utils/http_retry_test.go | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/pkg/utils/http_retry.go b/pkg/utils/http_retry.go index 135ea0ef5..812271290 100644 --- a/pkg/utils/http_retry.go +++ b/pkg/utils/http_retry.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strconv" "time" ) @@ -36,7 +37,7 @@ func DoRequestWithRetry(client *http.Client, req *http.Request) (*http.Response, } if i < maxRetries-1 { - if err = sleepWithCtx(req.Context(), retryDelayUnit*time.Duration(i+1)); err != nil { + if err = sleepWithCtx(req.Context(), retryDelayForAttempt(resp, i)); err != nil { if resp != nil { resp.Body.Close() } @@ -47,6 +48,32 @@ func DoRequestWithRetry(client *http.Client, req *http.Request) (*http.Response, return resp, err } +func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration { + fallback := retryDelayUnit * time.Duration(attempt+1) + if resp == nil || resp.StatusCode != http.StatusTooManyRequests { + return fallback + } + + retryAfter := resp.Header.Get("Retry-After") + if retryAfter == "" { + return fallback + } + + if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 { + return time.Duration(seconds) * time.Second + } + + if when, err := http.ParseTime(retryAfter); err == nil { + delay := time.Until(when) + if delay < 0 { + return 0 + } + return delay + } + + return fallback +} + func sleepWithCtx(ctx context.Context, d time.Duration) error { timer := time.NewTimer(d) defer timer.Stop() diff --git a/pkg/utils/http_retry_test.go b/pkg/utils/http_retry_test.go index d64cd5eda..918245b57 100644 --- a/pkg/utils/http_retry_test.go +++ b/pkg/utils/http_retry_test.go @@ -80,6 +80,81 @@ func TestDoRequestWithRetry(t *testing.T) { } } +func TestDoRequestWithRetry_RetryAfter429Honored(t *testing.T) { + retryDelayUnit = 10 * time.Millisecond + t.Cleanup(func() { retryDelayUnit = time.Second }) + + attempts := 0 + var firstAttemptAt time.Time + var secondAttemptAt time.Time + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attempts++ + if attempts == 1 { + firstAttemptAt = time.Now() + w.Header().Set("Retry-After", "1") + w.WriteHeader(http.StatusTooManyRequests) + return + } + if attempts == 2 { + secondAttemptAt = time.Now() + } + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + client := &http.Client{Timeout: 5 * time.Second} + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + require.NoError(t, err) + + resp, err := DoRequestWithRetry(client, req) + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + resp.Body.Close() + require.Equal(t, 2, attempts) + + assert.GreaterOrEqual(t, secondAttemptAt.Sub(firstAttemptAt), 900*time.Millisecond) +} + +func TestDoRequestWithRetry_RetryAfter429InvalidFallsBack(t *testing.T) { + retryDelayUnit = 50 * time.Millisecond + t.Cleanup(func() { retryDelayUnit = time.Second }) + + attempts := 0 + var firstAttemptAt time.Time + var secondAttemptAt time.Time + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attempts++ + if attempts == 1 { + firstAttemptAt = time.Now() + w.Header().Set("Retry-After", "invalid") + w.WriteHeader(http.StatusTooManyRequests) + return + } + if attempts == 2 { + secondAttemptAt = time.Now() + } + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + client := &http.Client{Timeout: 5 * time.Second} + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + require.NoError(t, err) + + resp, err := DoRequestWithRetry(client, req) + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + resp.Body.Close() + require.Equal(t, 2, attempts) + + assert.GreaterOrEqual(t, secondAttemptAt.Sub(firstAttemptAt), 45*time.Millisecond) + assert.Less(t, secondAttemptAt.Sub(firstAttemptAt), 500*time.Millisecond) +} + func TestDoRequestWithRetry_ContextCancel(t *testing.T) { // Use a long retry delay so cancellation always hits during sleepWithCtx. retryDelayUnit = 10 * time.Second From 93757812fc64e225c89e1d33ed9e5d0504ad4d75 Mon Sep 17 00:00:00 2001 From: Cytown Date: Mon, 30 Mar 2026 14:01:20 +0800 Subject: [PATCH 37/55] refactor config and add ModelConfig.Enabled --- cmd/picoclaw/internal/model/command.go | 4 +- cmd/picoclaw/internal/model/command_test.go | 34 +- pkg/config/config.go | 182 +++----- pkg/config/config_old.go | 62 ++- pkg/config/config_struct.go | 327 ++++++++++++++ pkg/config/config_struct_test.go | 145 ++++++ pkg/config/config_test.go | 160 +++++++ pkg/config/migration.go | 23 + pkg/config/migration_integration_test.go | 470 ++++++++++++++++++++ pkg/config/multikey_test.go | 2 +- pkg/config/security.go | 236 ---------- pkg/config/security_test.go | 133 ------ web/backend/api/models.go | 2 + 13 files changed, 1272 insertions(+), 508 deletions(-) create mode 100644 pkg/config/config_struct.go create mode 100644 pkg/config/config_struct_test.go diff --git a/cmd/picoclaw/internal/model/command.go b/cmd/picoclaw/internal/model/command.go index 314259d0f..330734b82 100644 --- a/cmd/picoclaw/internal/model/command.go +++ b/cmd/picoclaw/internal/model/command.go @@ -81,7 +81,7 @@ func listAvailableModels(cfg *config.Config) { if model.ModelName == defaultModel { marker = "> " } - if model.APIKey() == "" { + if !model.Enabled { continue } fmt.Printf("%s- %s (%s)\n", marker, model.ModelName, model.Model) @@ -92,7 +92,7 @@ func setDefaultModel(configPath string, cfg *config.Config, modelName string) er // Validate that the model exists in model_list modelFound := false for _, model := range cfg.ModelList { - if model.APIKey() != "" && model.ModelName == modelName { + if model.Enabled && model.ModelName == modelName { modelFound = true break } diff --git a/cmd/picoclaw/internal/model/command_test.go b/cmd/picoclaw/internal/model/command_test.go index 8be29ba95..9e2a7bbae 100644 --- a/cmd/picoclaw/internal/model/command_test.go +++ b/cmd/picoclaw/internal/model/command_test.go @@ -65,11 +65,17 @@ func TestShowCurrentModel_WithDefaultModel(t *testing.T) { }, }, ModelList: []*config.ModelConfig{ - {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: config.SecureStrings{config.NewSecureString("test")}}, + { + ModelName: "gpt-4", + Model: "openai/gpt-4", + APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, + }, { ModelName: "claude-3", Model: "anthropic/claude-3", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -92,7 +98,12 @@ func TestShowCurrentModel_NoDefaultModel(t *testing.T) { }, }, ModelList: []*config.ModelConfig{ - {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: config.SecureStrings{config.NewSecureString("test")}}, + { + ModelName: "gpt-4", + Model: "openai/gpt-4", + APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, + }, }, } @@ -124,11 +135,17 @@ func TestListAvailableModels_WithModels(t *testing.T) { }, }, ModelList: []*config.ModelConfig{ - {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: config.SecureStrings{config.NewSecureString("test")}}, + { + ModelName: "gpt-4", + Model: "openai/gpt-4", + APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, + }, { ModelName: "claude-3", Model: "anthropic/claude-3", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, {ModelName: "no-key-model", Model: "openai/test"}, }, @@ -158,11 +175,13 @@ func TestSetDefaultModel_ValidModel(t *testing.T) { ModelName: "new-model", Model: "openai/new-model", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, { ModelName: "old-model", Model: "openai/old-model", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -194,6 +213,7 @@ func TestSetDefaultModel_InvalidModel(t *testing.T) { ModelName: "existing-model", Model: "openai/existing", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -215,6 +235,7 @@ func TestSetDefaultModel_ModelWithoutAPIKey(t *testing.T) { ModelName: "existing-model", Model: "openai/existing", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, {ModelName: "no-key-model", Model: "openai/nokey"}, }, @@ -238,6 +259,7 @@ func TestSetDefaultModel_SaveConfigError(t *testing.T) { ModelName: "new-model", Model: "openai/new-model", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -283,6 +305,7 @@ func TestModelCommandExecution_Show(t *testing.T) { ModelName: "test-model", Model: "openai/test", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -314,11 +337,13 @@ func TestModelCommandExecution_Set(t *testing.T) { ModelName: "old-model", Model: "openai/old", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, { ModelName: "new-model", Model: "openai/new", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } @@ -356,16 +381,19 @@ func TestListAvailableModels_MarkerLogic(t *testing.T) { ModelName: "first-model", Model: "openai/first", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, { ModelName: "middle-model", Model: "openai/middle", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, { ModelName: "last-model", Model: "openai/last", APIKeys: config.SecureStrings{config.NewSecureString("test")}, + Enabled: true, }, }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 533f45a44..3dc3422fb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,8 +7,8 @@ import ( "math/rand" "os" "path/filepath" - "strings" "sync/atomic" + "time" "github.com/caarlos0/env/v11" @@ -20,89 +20,8 @@ import ( // rrCounter is a global counter for round-robin load balancing across models. var rrCounter atomic.Uint64 -// FlexibleStringSlice is a []string that also accepts JSON numbers, -// so allow_from can contain both "123" and 123. -// It also supports parsing comma-separated strings from environment variables, -// including both English (,) and Chinese (,) commas. -type FlexibleStringSlice []string - -func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error { - // Accept a single JSON string for convenience, e.g.: - // "text": "Thinking..." - var singleString string - if err := json.Unmarshal(data, &singleString); err == nil { - *f = FlexibleStringSlice{singleString} - return nil - } - - // Accept a single JSON number too, to keep symmetry with mixed allow_from - // payloads that may contain numeric identifiers. - var singleNumber float64 - if err := json.Unmarshal(data, &singleNumber); err == nil { - *f = FlexibleStringSlice{fmt.Sprintf("%.0f", singleNumber)} - return nil - } - - // Try []string first - var ss []string - if err := json.Unmarshal(data, &ss); err == nil { - *f = ss - return nil - } - - // Try []interface{} to handle mixed types - var raw []any - if err := json.Unmarshal(data, &raw); err != nil { - var s string - // fail over to compatible to old format string - if err = json.Unmarshal(data, &s); err != nil { - return err - } - *f = []string{s} - return nil - } - - result := make([]string, 0, len(raw)) - for _, v := range raw { - switch val := v.(type) { - case string: - result = append(result, val) - case float64: - result = append(result, fmt.Sprintf("%.0f", val)) - default: - result = append(result, fmt.Sprintf("%v", val)) - } - } - *f = result - return nil -} - -// UnmarshalText implements encoding.TextUnmarshaler to support env variable parsing. -// It handles comma-separated values with both English (,) and Chinese (,) commas. -func (f *FlexibleStringSlice) UnmarshalText(text []byte) error { - if len(text) == 0 { - *f = nil - return nil - } - - s := string(text) - // Replace Chinese comma with English comma, then split - s = strings.ReplaceAll(s, ",", ",") - parts := strings.Split(s, ",") - - result := make([]string, 0, len(parts)) - for _, part := range parts { - part = strings.TrimSpace(part) - if part != "" { - result = append(result, part) - } - } - *f = result - return nil -} - // CurrentVersion is the latest config schema version -const CurrentVersion = 1 +const CurrentVersion = 2 // Config is the current config structure with version support type Config struct { @@ -675,6 +594,11 @@ type ModelConfig struct { APIKeys SecureStrings `json:"api_keys,omitzero" yaml:"api_keys,omitempty"` // API authentication keys (multiple keys for failover) + // Enabled indicates whether this model entry is active. When omitted in + // existing configs, the field is inferred during load: models with API keys + // or the reserved "local-model" name are auto-enabled. + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + // isVirtual marks this model as a virtual model generated from multi-key expansion. // Virtual models should not be persisted to config files. isVirtual bool @@ -1047,6 +971,35 @@ func LoadConfig(path string) (*Config, error) { defer func(cfg *Config) { _ = SaveConfig(path, cfg) }(cfg) + case 1: + // V1→V2 migration: infer Enabled and migrate channel config fields + logger.InfoF("config migrate start", map[string]any{"from": versionInfo.Version, "to": CurrentVersion}) + cfg, err = loadConfig(data) + if err != nil { + return nil, err + } + secPath := securityPath(path) + err = loadSecurityConfig(cfg, secPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("failed to load security config: %w", err) + } + + oldCfg := &configV1{Config: *cfg} + cfg, err = oldCfg.Migrate() + if err != nil { + logger.ErrorF("config migrate fail", map[string]any{"from": versionInfo.Version, "to": CurrentVersion}) + return nil, err + } + + err = makeBackup(path) + if err != nil { + return nil, err + } + + defer func(cfg *Config) { + _ = SaveConfig(path, cfg) + }(cfg) + logger.InfoF("config migrate success", map[string]any{"from": versionInfo.Version, "to": CurrentVersion}) case CurrentVersion: // Current version cfg, err = loadConfig(data) @@ -1064,18 +1017,15 @@ func LoadConfig(path string) (*Config, error) { return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version) } - if err := env.Parse(cfg); err != nil { + if err = env.Parse(cfg); err != nil { return nil, err } // Expand multi-key configs into separate entries for key-level failover cfg.ModelList = expandMultiKeyModels(cfg.ModelList) - // Migrate legacy channel config fields to new unified structures - cfg.migrateChannelConfigs() - // Validate model_list for uniqueness and required fields - if err := cfg.ValidateModelList(); err != nil { + if err = cfg.ValidateModelList(); err != nil { return nil, err } @@ -1097,12 +1047,22 @@ func makeBackup(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { return nil } - // Create backup of the config file before migration - bakPath := path + ".bak" + dateSuffix := time.Now().Format(".20060102.bak") + // Backup config file + bakPath := path + dateSuffix if err := fileutil.CopyFile(path, bakPath, 0o600); err != nil { logger.ErrorF("failed to create config backup", map[string]any{"error": err}) return fmt.Errorf("failed to create config backup: %w", err) } + // Backup security config file + secPath := securityPath(path) + if _, err := os.Stat(secPath); err == nil { + secBakPath := secPath + dateSuffix + if secErr := fileutil.CopyFile(secPath, secBakPath, 0o600); secErr != nil { + logger.ErrorF("failed to create security backup", map[string]any{"error": secErr}) + return fmt.Errorf("failed to create security backup: %w", secErr) + } + } return nil } @@ -1118,19 +1078,6 @@ func toNameIndex(list []*ModelConfig) []string { return nameList } -func (c *Config) migrateChannelConfigs() { - // Discord: mention_only -> group_trigger.mention_only - if c.Channels.Discord.MentionOnly && !c.Channels.Discord.GroupTrigger.MentionOnly { - c.Channels.Discord.GroupTrigger.MentionOnly = true - } - - // OneBot: group_trigger_prefix -> group_trigger.prefixes - if len(c.Channels.OneBot.GroupTriggerPrefix) > 0 && - len(c.Channels.OneBot.GroupTrigger.Prefixes) == 0 { - c.Channels.OneBot.GroupTrigger.Prefixes = c.Channels.OneBot.GroupTriggerPrefix - } -} - func SaveConfig(path string, cfg *Config) error { if cfg.Version < CurrentVersion { cfg.Version = CurrentVersion @@ -1144,6 +1091,10 @@ func SaveConfig(path string, cfg *Config) error { } // Temporarily replace ModelList with filtered version for serialization originalModelList := cfg.ModelList + defer func() { + // Restore original ModelList after serialization + cfg.ModelList = originalModelList + }() cfg.ModelList = nonVirtualModels if err := saveSecurityConfig(securityPath(path), cfg); err != nil { @@ -1152,8 +1103,6 @@ func SaveConfig(path string, cfg *Config) error { } data, err := json.MarshalIndent(cfg, "", " ") - // Restore original ModelList after serialization - cfg.ModelList = originalModelList if err != nil { return err } @@ -1223,29 +1172,6 @@ func (c *Config) SecurityCopyFrom(path string) error { return loadSecurityConfig(c, securityPath(path)) } -func MergeAPIKeys(apiKey string, apiKeys []string) []string { - seen := make(map[string]struct{}) - var all []string - - if k := strings.TrimSpace(apiKey); k != "" { - if _, exists := seen[k]; !exists { - seen[k] = struct{}{} - all = append(all, k) - } - } - - for _, k := range apiKeys { - if trimmed := strings.TrimSpace(k); trimmed != "" { - if _, exists := seen[trimmed]; !exists { - seen[trimmed] = struct{}{} - all = append(all, trimmed) - } - } - } - - return all -} - // expandMultiKeyModels expands ModelConfig entries with multiple API keys into // separate entries for key-level failover. Each key gets its own ModelConfig entry, // and the original entry's fallbacks are set up to chain through the expanded entries. diff --git a/pkg/config/config_old.go b/pkg/config/config_old.go index fd54c9e08..150275aac 100644 --- a/pkg/config/config_old.go +++ b/pkg/config/config_old.go @@ -734,7 +734,8 @@ func (c *configV0) Migrate() (*Config, error) { // Convert []modelConfigV0 to []ModelConfig cfg.ModelList = make([]*ModelConfig, len(c.ModelList)) for i, m := range c.ModelList { - cfg.ModelList[i] = &ModelConfig{ + mergedKeys := toSecureStrings(mergeAPIKeys(m.APIKey, m.APIKeys)) + mc := &ModelConfig{ ModelName: m.ModelName, Model: m.Model, APIBase: m.APIBase, @@ -747,8 +748,13 @@ func (c *configV0) Migrate() (*Config, error) { MaxTokensField: m.MaxTokensField, RequestTimeout: m.RequestTimeout, ThinkingLevel: m.ThinkingLevel, - APIKeys: toSecureStrings(MergeAPIKeys(m.APIKey, m.APIKeys)), + APIKeys: mergedKeys, } + // Infer Enabled during V0→V1 migration + if len(mergedKeys) > 0 || m.ModelName == "local-model" { + mc.Enabled = true + } + cfg.ModelList[i] = mc } } @@ -756,6 +762,52 @@ func (c *configV0) Migrate() (*Config, error) { return cfg, nil } +type configV1 struct { + Config +} + +// Migrate applies V1→Current Version migrations to an already-loaded Config. +// +// It must be called AFTER loadSecurityConfig so that API keys (which live in +// the security file) are available for the Enabled inference. +func (c *configV1) Migrate() (*Config, error) { + c.migrateModelEnabled() + c.migrateChannelConfigs() + return &c.Config, nil +} + +// migrateModelEnabled infers the Enabled field for models loaded from V1 configs +// that predate the field (JSON where "enabled" is absent). +// +// Rules (only applied when Enabled has not been explicitly set by the user): +// - Models with API keys are considered enabled. +// - The reserved "local-model" entry is considered enabled. +func (cfg *configV1) migrateModelEnabled() { + for _, m := range cfg.ModelList { + if m.Enabled { + continue + } + if len(m.APIKeys) > 0 || m.ModelName == "local-model" { + m.Enabled = true + } + } +} + +// migrateChannelConfigs migrates legacy channel config fields in a V1 Config +// to the new unified structures. +func (cfg *configV1) migrateChannelConfigs() { + // Discord: mention_only -> group_trigger.mention_only + if cfg.Channels.Discord.MentionOnly && !cfg.Channels.Discord.GroupTrigger.MentionOnly { + cfg.Channels.Discord.GroupTrigger.MentionOnly = true + } + + // OneBot: group_trigger_prefix -> group_trigger.prefixes + if len(cfg.Channels.OneBot.GroupTriggerPrefix) > 0 && + len(cfg.Channels.OneBot.GroupTrigger.Prefixes) == 0 { + cfg.Channels.OneBot.GroupTrigger.Prefixes = cfg.Channels.OneBot.GroupTriggerPrefix + } +} + type webToolsConfigV0 struct { ToolConfig ` envPrefix:"PICOCLAW_TOOLS_WEB_"` Brave braveConfigV0 ` json:"brave"` @@ -791,7 +843,7 @@ func (v *braveConfigV0) ToBraveConfig() BraveConfig { return BraveConfig{ Enabled: v.Enabled, MaxResults: v.MaxResults, - APIKeys: toSecureStrings(MergeAPIKeys(v.APIKey, v.APIKeys)), + APIKeys: toSecureStrings(mergeAPIKeys(v.APIKey, v.APIKeys)), } } @@ -808,7 +860,7 @@ func (v *tavilyConfigV0) ToTavilyConfig() TavilyConfig { Enabled: v.Enabled, BaseURL: v.BaseURL, MaxResults: v.MaxResults, - APIKeys: toSecureStrings(MergeAPIKeys(v.APIKey, v.APIKeys)), + APIKeys: toSecureStrings(mergeAPIKeys(v.APIKey, v.APIKeys)), } } @@ -823,7 +875,7 @@ func (v *perplexityConfigV0) ToPerplexityConfig() PerplexityConfig { return PerplexityConfig{ Enabled: v.Enabled, MaxResults: v.MaxResults, - APIKeys: toSecureStrings(MergeAPIKeys(v.APIKey, v.APIKeys)), + APIKeys: toSecureStrings(mergeAPIKeys(v.APIKey, v.APIKeys)), } } diff --git a/pkg/config/config_struct.go b/pkg/config/config_struct.go new file mode 100644 index 000000000..0b8dd85c8 --- /dev/null +++ b/pkg/config/config_struct.go @@ -0,0 +1,327 @@ +package config + +import ( + "encoding/json" + "fmt" + "path/filepath" + "runtime" + "strings" + "sync" + + "gopkg.in/yaml.v3" + + "github.com/sipeed/picoclaw/pkg/credential" + "github.com/sipeed/picoclaw/pkg/logger" +) + +// FlexibleStringSlice is a []string that also accepts JSON numbers, +// so allow_from can contain both "123" and 123. +// It also supports parsing comma-separated strings from environment variables, +// including both English (,) and Chinese (,) commas. +type FlexibleStringSlice []string + +func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error { + // Accept a single JSON string for convenience, e.g.: + // "text": "Thinking..." + var singleString string + if err := json.Unmarshal(data, &singleString); err == nil { + *f = FlexibleStringSlice{singleString} + return nil + } + + // Accept a single JSON number too, to keep symmetry with mixed allow_from + // payloads that may contain numeric identifiers. + var singleNumber float64 + if err := json.Unmarshal(data, &singleNumber); err == nil { + *f = FlexibleStringSlice{fmt.Sprintf("%.0f", singleNumber)} + return nil + } + + // Try []string first + var ss []string + if err := json.Unmarshal(data, &ss); err == nil { + *f = ss + return nil + } + + // Try []interface{} to handle mixed types + var raw []any + if err := json.Unmarshal(data, &raw); err != nil { + var s string + // fail over to compatible to old format string + if err = json.Unmarshal(data, &s); err != nil { + return err + } + *f = []string{s} + return nil + } + + result := make([]string, 0, len(raw)) + for _, v := range raw { + switch val := v.(type) { + case string: + result = append(result, val) + case float64: + result = append(result, fmt.Sprintf("%.0f", val)) + default: + result = append(result, fmt.Sprintf("%v", val)) + } + } + *f = result + return nil +} + +// UnmarshalText implements encoding.TextUnmarshaler to support env variable parsing. +// It handles comma-separated values with both English (,) and Chinese (,) commas. +func (f *FlexibleStringSlice) UnmarshalText(text []byte) error { + if len(text) == 0 { + *f = nil + return nil + } + + s := string(text) + // Replace Chinese comma with English comma, then split + s = strings.ReplaceAll(s, ",", ",") + parts := strings.Split(s, ",") + + result := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + result = append(result, part) + } + } + *f = result + return nil +} + +const ( + notHere = `"[NOT_HERE]"` +) + +// SecureStrings is a slice of SecureString +type SecureStrings []*SecureString + +// Values returns the decrypted/resolved values +func (s *SecureStrings) Values() []string { + if s == nil { + return nil + } + keys := make([]string, len(*s)) + for i, k := range *s { + keys[i] = k.String() + } + return unique(keys) +} + +func SimpleSecureStrings(val ...string) SecureStrings { + val = unique(val) + vv := make(SecureStrings, len(val)) + for i, s := range val { + vv[i] = NewSecureString(s) + } + return vv +} + +// unique returns a new slice with duplicate elements removed. +func unique[T comparable](input []T) []T { + m := make(map[T]struct{}) + var result []T + for _, v := range input { + if _, ok := m[v]; !ok { + m[v] = struct{}{} + result = append(result, v) + } + } + return result +} + +func (s SecureStrings) MarshalJSON() ([]byte, error) { + return []byte(notHere), nil +} + +func (s *SecureStrings) UnmarshalJSON(value []byte) error { + if string(value) == notHere { + return nil + } + var v []*SecureString + err := json.Unmarshal(value, &v) + if err != nil { + return err + } + *s = v + return nil +} + +// SecureString the string value that can be decrypted or resolved +// +//nolint:recvcheck +type SecureString struct { + resolved string // Decrypted/resolved value returned by String() + raw string // Persisted raw value (enc://, file://, or plaintext) +} + +func callerFromYaml() bool { + _, file, _, ok := runtime.Caller(2) + if ok { + d := filepath.Dir(file) + // check the caller is from yaml.v + if !strings.Contains(d, "yaml.v") { + return true + } + } + return false +} + +// IsZero returns true if the SecureString is empty +// if caller not yaml, just return true for prevent marshal this field +func (s SecureString) IsZero() bool { + if callerFromYaml() { + return true + } + return s.resolved == "" +} + +func NewSecureString(value string) *SecureString { + s := &SecureString{} + if err := s.fromRaw(value); err != nil { + logger.Warn(fmt.Sprintf("NewSecureString.fromRaw error: %s", err)) + } + return s +} + +func (s *SecureString) String() string { + if s == nil { + return "" + } + return s.resolved +} + +func (s *SecureString) Set(value string) *SecureString { + s.resolved = value + s.raw = "" + return s +} + +func (s SecureString) MarshalJSON() ([]byte, error) { + return []byte(notHere), nil +} + +func (s *SecureString) UnmarshalJSON(value []byte) error { + if string(value) == notHere { + return nil + } + var v string + if err := json.Unmarshal(value, &v); err != nil { + return err + } + return s.fromRaw(v) +} + +func (s SecureString) MarshalYAML() (any, error) { + // Preserve raw value if it is already a reference (enc:// or file://) + if strings.HasPrefix(s.raw, credential.EncScheme) || strings.HasPrefix(s.raw, credential.FileScheme) { + return s.raw, nil + } + // If resolved is a reference format (e.g. set via Set), copy back to raw + if strings.HasPrefix(s.resolved, credential.EncScheme) || strings.HasPrefix(s.resolved, credential.FileScheme) { + s.raw = s.resolved + return s.raw, nil + } + // Try to encrypt the resolved value + if passphrase := credential.PassphraseProvider(); passphrase != "" { + encrypted, err := credential.Encrypt(passphrase, "", s.resolved) + if err != nil { + logger.Errorf("Encrypt error: %v", err) + return nil, err + } + s.raw = encrypted + } else { + s.raw = s.resolved + } + return s.raw, nil +} + +func (s *SecureString) UnmarshalYAML(value *yaml.Node) error { + return s.fromRaw(value.Value) +} + +func (s *SecureString) fromRaw(v string) error { + s.raw = v + vv, err := resolveKey(v) + if err != nil { + return err + } + s.resolved = vv + return nil +} + +var ( + secResolverMu sync.RWMutex + secResolver *credential.Resolver +) + +func updateResolver(path string) { + secResolverMu.Lock() + defer secResolverMu.Unlock() + secResolver = credential.NewResolver(path) +} + +func resolveKey(v string) (string, error) { + secResolverMu.RLock() + resolver := secResolver + secResolverMu.RUnlock() + if resolver == nil { + resolver = credential.NewResolver("") + } + if strings.HasPrefix(v, "enc://") || strings.HasPrefix(v, "file://") { + decrypted, err := resolver.Resolve(v) + if err != nil { + logger.Errorf("Resolve error: %v", err) + return "", err + } + return decrypted, nil + } + return v, nil +} + +func (s *SecureString) UnmarshalText(text []byte) error { + v := string(text) + return s.fromRaw(v) +} + +type SecureModelList []*ModelConfig + +func (v *SecureModelList) UnmarshalYAML(value *yaml.Node) error { + mm := make(map[string]*ModelConfig) + if err := value.Decode(&mm); err != nil { + logger.Errorf("Decode error: %v", err) + return err + } + nameList := toNameIndex(*v) + for i, m := range *v { + sec := mm[nameList[i]] + if sec == nil { + sec = mm[m.ModelName] + } + if sec != nil { + m.APIKeys = sec.APIKeys + } + } + return nil +} + +func (v SecureModelList) MarshalYAML() (any, error) { + type onlySecureData struct { + APIKeys SecureStrings `yaml:"api_keys,omitempty"` + } + mm := make(map[string]onlySecureData) + nameList := toNameIndex(v) + for i, m := range v { + mm[nameList[i]] = onlySecureData{ + APIKeys: m.APIKeys, + } + } + + return mm, nil +} diff --git a/pkg/config/config_struct_test.go b/pkg/config/config_struct_test.go new file mode 100644 index 000000000..674b6a064 --- /dev/null +++ b/pkg/config/config_struct_test.go @@ -0,0 +1,145 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/caarlos0/env/v11" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + + "github.com/sipeed/picoclaw/pkg/credential" +) + +func TestLoadSecurityValue(t *testing.T) { + type valueStruct struct { + Url string `json:"url,omitempty" yaml:"-"` + Token *SecureString `json:"token,omitempty" yaml:"token,omitempty" env:"PICO_TOKEN"` + ApiKeys SecureStrings `json:"api_keys,omitempty" yaml:"api_keys,omitempty" env:"PICO_API_KEYS"` + } + + type testStruct struct { + Pico *valueStruct `json:"pico,omitempty" yaml:"pico,omitempty"` + } + + v1 := &testStruct{ + Pico: &valueStruct{ + Url: "https://example.com", + Token: NewSecureString("token1"), + ApiKeys: SecureStrings{NewSecureString("api-key1"), NewSecureString("api-key2")}, + }, + } + bytes, err := yaml.Marshal(v1) + assert.NoError(t, err) + jsonBytes, err := json.Marshal(v1) + assert.NoError(t, err) + const want = `pico: + token: token1 + api_keys: + - api-key1 + - api-key2 +` + const jsonPost = `{"pico":{"url":"https://example.com","token":"token0"}}` + v0 := &testStruct{} + err = json.Unmarshal([]byte(jsonPost), v0) + assert.NoError(t, err) + assert.Equal(t, "https://example.com", v0.Pico.Url) + assert.Equal(t, "token0", v0.Pico.Token.String()) + + const jsonWant = `{"pico":{"url":"https://example.com","token":"[NOT_HERE]","api_keys":"[NOT_HERE]"}}` + assert.Equal(t, want, string(bytes)) + assert.Equal(t, jsonWant, string(jsonBytes)) + + v2 := &testStruct{} + err = json.Unmarshal(jsonBytes, v2) + assert.NoError(t, err) + err = yaml.Unmarshal(bytes, v2) + assert.NoError(t, err) + assert.Equal(t, "https://example.com", v2.Pico.Url) + if v2.Pico.Token != nil { + assert.Equal(t, "token1", v2.Pico.Token.String()) + assert.Equal(t, "token1", v2.Pico.Token.raw) + } + + v2.Pico.Token = NewSecureString("token1") + v2.Pico.Token.raw = "abc" + err = yaml.Unmarshal(bytes, v2) + assert.NoError(t, err) + assert.Equal(t, "token1", v2.Pico.Token.raw) + + os.Setenv("PICO_TOKEN", "token_env") + err = env.Parse(v2) + assert.NoError(t, err) + assert.NotNil(t, v2.Pico.Token) + assert.Equal(t, "token1", v2.Pico.Token.String()) + + v3 := &testStruct{Pico: &valueStruct{}} + err = env.Parse(v3) + assert.NoError(t, err) + if v3.Pico.Token != nil { + assert.Equal(t, "token_env", v3.Pico.Token.String()) + } + + type toolsStruct struct { + Pico valueStruct `json:"pico,omitempty" yaml:"pico,omitempty"` + } + + type testStruct2 struct { + Tools toolsStruct `json:"tools,omitempty" yaml:",inline"` + } + + v4 := &testStruct2{ + Tools: toolsStruct{ + Pico: valueStruct{ + Url: "https://example.com", + Token: NewSecureString("token1"), + ApiKeys: SecureStrings{NewSecureString("api-key1"), NewSecureString("api-key2")}, + }, + }, + } + bytes, err = yaml.Marshal(v4) + assert.NoError(t, err) + assert.Equal(t, want, string(bytes)) + jsonBytes, err = json.Marshal(v4) + assert.NoError(t, err) + assert.Equal( + t, + `{"tools":{"pico":{"url":"https://example.com","token":"[NOT_HERE]","api_keys":"[NOT_HERE]"}}}`, + string(jsonBytes), + ) + + v5 := &testStruct2{} + err = json.Unmarshal(jsonBytes, v5) + assert.NoError(t, err) + assert.Equal(t, "https://example.com", v5.Tools.Pico.Url) + err = yaml.Unmarshal(bytes, v5) + assert.NoError(t, err) + assert.NotNil(t, v5.Tools.Pico.Token) + assert.Equal(t, "token1", v5.Tools.Pico.Token.raw) + + dir := t.TempDir() + sshKeyPath := filepath.Join(dir, "picoclaw_ed25519.key") + if err = os.WriteFile(sshKeyPath, []byte("fake-ssh-key-material\n"), 0o600); err != nil { + t.Fatalf("setup: %v", err) + } + + const passphrase = "test-passphrase-32bytes-long-ok!" + + t.Setenv(credential.SSHKeyPathEnvVar, sshKeyPath) + + t.Setenv(credential.PassphraseEnvVar, passphrase) + + v5.Tools.Pico.Token.Set("newtoken1") + v5.Tools.Pico.ApiKeys[0].Set("newapi-key1") + bytes, err = yaml.Marshal(v5) + assert.NoError(t, err) + t.Logf("yaml: %s", string(bytes)) + + v6 := &testStruct2{} + err = yaml.Unmarshal(bytes, v6) + assert.NoError(t, err) + assert.NotNil(t, v6.Tools.Pico.Token) + assert.Equal(t, "newtoken1", v6.Tools.Pico.Token.String()) +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 75eb458b8..6734257f4 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1673,3 +1673,163 @@ func TestFilterSensitiveData_AllTokenTypes(t *testing.T) { }) } } + +// --------------------------------------------------------------------------- +// makeBackup tests +// --------------------------------------------------------------------------- + +// TestMakeBackup_WithDateSuffix verifies backup files include a date suffix. +func TestMakeBackup_WithDateSuffix(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "config.json") + if err := os.WriteFile(configPath, []byte(`{"version":2}`), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + if err := makeBackup(configPath); err != nil { + t.Fatalf("makeBackup: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + + var hasDatedBackup bool + for _, e := range entries { + if matched, _ := filepath.Match("config.json.20*.bak", e.Name()); matched { + hasDatedBackup = true + // Verify backup content matches original + bakPath := filepath.Join(dir, e.Name()) + data, err := os.ReadFile(bakPath) + if err != nil { + t.Fatalf("ReadFile backup: %v", err) + } + if string(data) != `{"version":2}` { + t.Errorf("backup content = %q, want original content", string(data)) + } + break + } + } + if !hasDatedBackup { + t.Error("expected backup file with date suffix pattern config.json.20*.bak") + } +} + +// TestMakeBackup_AlsoBacksSecurityFile verifies that the security config file +// is also backed up with the same date suffix. +func TestMakeBackup_AlsoBacksSecurityFile(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "config.json") + secPath := securityPath(configPath) + + os.WriteFile(configPath, []byte(`{"version":2}`), 0o600) + os.WriteFile(secPath, []byte(`model_list:\n test:0:\n api_keys:\n - "sk-test"\n`), 0o600) + + if err := makeBackup(configPath); err != nil { + t.Fatalf("makeBackup: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + + configBackups := 0 + secBackups := 0 + for _, e := range entries { + if matched, _ := filepath.Match("config.json.20*.bak", e.Name()); matched { + configBackups++ + } + if matched, _ := filepath.Match(".security.yml.20*.bak", e.Name()); matched { + secBackups++ + } + } + if configBackups != 1 { + t.Errorf("expected 1 config backup, got %d", configBackups) + } + if secBackups != 1 { + t.Errorf("expected 1 security backup, got %d", secBackups) + } +} + +// TestMakeBackup_NonexistentFileSkipsBackup verifies that makeBackup returns nil +// when the config file does not exist (no error, no panic). +func TestMakeBackup_NonexistentFileSkipsBackup(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "nonexistent.json") + + if err := makeBackup(configPath); err != nil { + t.Fatalf("makeBackup on nonexistent file should return nil, got: %v", err) + } +} + +// TestMakeBackup_OnlyConfigNoSecurity verifies backup succeeds when only +// the config file exists and no security file. +func TestMakeBackup_OnlyConfigNoSecurity(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "config.json") + os.WriteFile(configPath, []byte(`{"version":2}`), 0o600) + + if err := makeBackup(configPath); err != nil { + t.Fatalf("makeBackup: %v", err) + } + + entries, _ := os.ReadDir(dir) + configBackups := 0 + secBackups := 0 + for _, e := range entries { + if matched, _ := filepath.Match("config.json.20*.bak", e.Name()); matched { + configBackups++ + } + if matched, _ := filepath.Match(".security.yml.20*.bak", e.Name()); matched { + secBackups++ + } + } + if configBackups != 1 { + t.Errorf("expected 1 config backup, got %d", configBackups) + } + if secBackups != 0 { + t.Errorf("expected 0 security backups when no security file exists, got %d", secBackups) + } +} + +// TestMakeBackup_SameDateSuffix verifies that config and security backups +// share the same date suffix (they are created in the same makeBackup call). +func TestMakeBackup_SameDateSuffix(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "config.json") + secPath := securityPath(configPath) + + os.WriteFile(configPath, []byte(`{"version":2}`), 0o600) + os.WriteFile(secPath, []byte(`key: value`), 0o600) + + if err := makeBackup(configPath); err != nil { + t.Fatalf("makeBackup: %v", err) + } + + entries, _ := os.ReadDir(dir) + var configDate, secDate string + for _, e := range entries { + name := e.Name() + // Extract date part: after the last . before .bak + // e.g. config.json.20260330.bak → 20260330 + if strings.HasPrefix(name, "config.json.") && strings.HasSuffix(name, ".bak") { + configDate = strings.TrimPrefix(name, "config.json.") + configDate = strings.TrimSuffix(configDate, ".bak") + } + if strings.HasPrefix(name, ".security.yml.") && strings.HasSuffix(name, ".bak") { + secDate = strings.TrimPrefix(name, ".security.yml.") + secDate = strings.TrimSuffix(secDate, ".bak") + } + } + if configDate == "" { + t.Fatal("config backup file not found") + } + if secDate == "" { + t.Fatal("security backup file not found") + } + if configDate != secDate { + t.Errorf("config backup date = %q, security backup date = %q, should match", configDate, secDate) + } +} diff --git a/pkg/config/migration.go b/pkg/config/migration.go index fee800a76..7430050b3 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -534,3 +534,26 @@ func loadConfig(data []byte) (*Config, error) { } return cfg, nil } + +func mergeAPIKeys(apiKey string, apiKeys []string) []string { + seen := make(map[string]struct{}) + var all []string + + if k := strings.TrimSpace(apiKey); k != "" { + if _, exists := seen[k]; !exists { + seen[k] = struct{}{} + all = append(all, k) + } + } + + for _, k := range apiKeys { + if trimmed := strings.TrimSpace(k); trimmed != "" { + if _, exists := seen[trimmed]; !exists { + seen[trimmed] = struct{}{} + all = append(all, trimmed) + } + } + } + + return all +} diff --git a/pkg/config/migration_integration_test.go b/pkg/config/migration_integration_test.go index bc8160967..b180dda90 100644 --- a/pkg/config/migration_integration_test.go +++ b/pkg/config/migration_integration_test.go @@ -681,3 +681,473 @@ web: t.Error("Discord token not preserved in .security.yml file") } } + +// --------------------------------------------------------------------------- +// V1 → V2 migration tests +// --------------------------------------------------------------------------- + +// TestMigrateModelEnabled_APIKeysInferredEnabled verifies that models with API keys +// are marked as enabled during V1→V2 migration. +func TestMigrateModelEnabled_APIKeysInferredEnabled(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: SimpleSecureStrings("sk-test")}, + {ModelName: "claude", Model: "anthropic/claude", APIKeys: SimpleSecureStrings("sk-ant")}, + }, + }} + v1.migrateModelEnabled() + for _, m := range v1.ModelList { + if !m.Enabled { + t.Errorf("model %q with API key should be enabled", m.ModelName) + } + } +} + +// TestMigrateModelEnabled_LocalModelInferredEnabled verifies that the reserved +// "local-model" entry is enabled even without API keys. +func TestMigrateModelEnabled_LocalModelInferredEnabled(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "local-model", Model: "vllm/custom-model", APIBase: "http://localhost:8000/v1"}, + }, + }} + v1.migrateModelEnabled() + if !v1.ModelList[0].Enabled { + t.Error("local-model should be enabled") + } +} + +// TestMigrateModelEnabled_NoKeyStaysDisabled verifies that models without API keys +// and not named "local-model" remain disabled. +func TestMigrateModelEnabled_NoKeyStaysDisabled(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "gpt-4", Model: "openai/gpt-4"}, + {ModelName: "claude", Model: "anthropic/claude"}, + }, + }} + v1.migrateModelEnabled() + for _, m := range v1.ModelList { + if m.Enabled { + t.Errorf("model %q without API key should stay disabled", m.ModelName) + } + } +} + +// TestMigrateModelEnabled_ExplicitEnabledPreserved verifies that a model with +// explicitly enabled=true is NOT overridden by the migration. +func TestMigrateModelEnabled_ExplicitEnabledPreserved(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: SimpleSecureStrings("sk-test"), Enabled: true}, + }, + }} + v1.migrateModelEnabled() + if !v1.ModelList[0].Enabled { + t.Error("explicitly enabled model should remain enabled") + } +} + +// TestMigrateModelEnabled_ExplicitDisabledNotOverridden verifies that a model with +// explicitly enabled=false and API keys gets enabled during migration. +// Note: since Go's zero value for bool is false and JSON omitempty omits false, +// migration cannot distinguish "explicitly false" from "field absent". Both cases +// get the same inference treatment. +func TestMigrateModelEnabled_ExplicitDisabledNotOverridden(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: SimpleSecureStrings("sk-test"), Enabled: false}, + }, + }} + v1.migrateModelEnabled() + // Even though Enabled was set to false, migration infers it as true because + // the migration cannot distinguish from a missing field (both are zero value). + if !v1.ModelList[0].Enabled { + t.Error("model with API key should be enabled by migration inference") + } +} + +// TestMigrateModelEnabled_Mixed verifies a mix of models. +func TestMigrateModelEnabled_Mixed(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "with-key", Model: "openai/gpt-4", APIKeys: SimpleSecureStrings("sk-test")}, + {ModelName: "no-key", Model: "openai/gpt-4"}, + {ModelName: "local-model", Model: "vllm/custom"}, + { + ModelName: "disabled-explicit", + Model: "openai/gpt-4", + APIKeys: SimpleSecureStrings("sk-test"), + Enabled: false, + }, + }, + }} + v1.migrateModelEnabled() + + assertEnabled := func(name string, want bool) { + for _, m := range v1.ModelList { + if m.ModelName == name { + if m.Enabled != want { + t.Errorf("model %q: Enabled=%v, want %v", name, m.Enabled, want) + } + return + } + } + t.Errorf("model %q not found", name) + } + + assertEnabled("with-key", true) + assertEnabled("no-key", false) + assertEnabled("local-model", true) + assertEnabled("disabled-explicit", true) // false is zero value, migration infers from API key +} + +// TestMigrateChannelConfigs_DiscordMentionOnly verifies Discord mention_only migration. +func TestMigrateChannelConfigs_DiscordMentionOnly(t *testing.T) { + v1 := &configV1{Config: Config{ + Channels: ChannelsConfig{ + Discord: DiscordConfig{ + MentionOnly: true, + }, + }, + }} + v1.migrateChannelConfigs() + if !v1.Channels.Discord.GroupTrigger.MentionOnly { + t.Error("Discord GroupTrigger.MentionOnly should be set to true") + } +} + +// TestMigrateChannelConfigs_DiscordAlreadyMigrated is a no-op test. +func TestMigrateChannelConfigs_DiscordAlreadyMigrated(t *testing.T) { + v1 := &configV1{Config: Config{ + Channels: ChannelsConfig{ + Discord: DiscordConfig{ + GroupTrigger: GroupTriggerConfig{MentionOnly: true}, + }, + }, + }} + v1.migrateChannelConfigs() +} + +// TestMigrateChannelConfigs_OneBotPrefix verifies OneBot prefix migration. +func TestMigrateChannelConfigs_OneBotPrefix(t *testing.T) { + v1 := &configV1{Config: Config{ + Channels: ChannelsConfig{ + OneBot: OneBotConfig{ + GroupTriggerPrefix: []string{"/"}, + }, + }, + }} + v1.migrateChannelConfigs() + if len(v1.Channels.OneBot.GroupTrigger.Prefixes) != 1 || v1.Channels.OneBot.GroupTrigger.Prefixes[0] != "/" { + t.Errorf("OneBot GroupTrigger.Prefixes = %v, want [\"/\"]", v1.Channels.OneBot.GroupTrigger.Prefixes) + } +} + +// TestMigrateConfigV1_Combined verifies that configV1.Migrate applies both migrations. +func TestMigrateConfigV1_Combined(t *testing.T) { + v1 := &configV1{Config: Config{ + ModelList: []*ModelConfig{ + {ModelName: "gpt-4", Model: "openai/gpt-4", APIKeys: SimpleSecureStrings("sk-test")}, + }, + Channels: ChannelsConfig{ + Discord: DiscordConfig{MentionOnly: true}, + }, + }} + result, err := v1.Migrate() + if err != nil { + t.Fatalf("Migrate: %v", err) + } + + if !result.ModelList[0].Enabled { + t.Error("model with API key should be enabled after V1→V2 migration") + } + if !result.Channels.Discord.GroupTrigger.MentionOnly { + t.Error("Discord mention_only should be migrated after V1→V2 migration") + } +} + +// TestLoadConfig_V1ToV2Migration verifies end-to-end V1→V2 config migration +// through LoadConfig, including Enabled field inference and version bump. +func TestLoadConfig_V1ToV2Migration(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + + // Write a V1 config with model_list but no "enabled" field + v1Config := `{ + "version": 1, + "model_list": [ + { + "model_name": "gpt-4", + "model": "openai/gpt-4" + }, + { + "model_name": "local-model", + "model": "vllm/custom-model", + "api_base": "http://localhost:8000/v1" + } + ], + "channels": { + "discord": { + "mention_only": true + } + }, + "gateway": {"host": "127.0.0.1", "port": 18790} + }` + + if err := os.WriteFile(configPath, []byte(v1Config), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + cfg, err := LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig: %v", err) + } + + // Version should be bumped to 2 + if cfg.Version != CurrentVersion { + t.Errorf("Version = %d, want %d", cfg.Version, CurrentVersion) + } + + // gpt-4 has no API key → disabled + gpt4, err := cfg.GetModelConfig("gpt-4") + if err != nil { + t.Fatalf("GetModelConfig(gpt-4): %v", err) + } + if gpt4.Enabled { + t.Error("gpt-4 without API key should be disabled after migration") + } + + // local-model → enabled + local, err := cfg.GetModelConfig("local-model") + if err != nil { + t.Fatalf("GetModelConfig(local-model): %v", err) + } + if !local.Enabled { + t.Error("local-model should be enabled after migration") + } + + // Discord channel config should be migrated + if !cfg.Channels.Discord.GroupTrigger.MentionOnly { + t.Error("Discord mention_only should be migrated to group_trigger.mention_only") + } + + // Verify backup was created with date suffix + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + var hasBackup bool + for _, e := range entries { + if matched, _ := filepath.Match("config.json.20*.bak", e.Name()); matched { + hasBackup = true + break + } + } + if !hasBackup { + t.Error("expected backup file with date suffix to be created") + } + + // Verify the saved config on disk now has version 2 + saved, err := os.ReadFile(configPath) + if err != nil { + t.Fatalf("ReadFile saved config: %v", err) + } + var versionCheck struct { + Version int `json:"version"` + } + if err := json.Unmarshal(saved, &versionCheck); err != nil { + t.Fatalf("Unmarshal saved config: %v", err) + } + if versionCheck.Version != 2 { + t.Errorf("saved config version = %d, want 2", versionCheck.Version) + } +} + +// TestLoadConfig_V1WithAPIKeysInferredEnabled verifies that V1 configs with +// API keys in the security file get Enabled=true after migration. +func TestLoadConfig_V1WithAPIKeysInferredEnabled(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + secPath := securityPath(configPath) + + v1Config := `{ + "version": 1, + "model_list": [ + {"model_name": "gpt-4", "model": "openai/gpt-4"}, + {"model_name": "claude", "model": "anthropic/claude"} + ], + "gateway": {"host": "127.0.0.1", "port": 18790} + }` + + securityConfig := `model_list: + gpt-4:0: + api_keys: + - "sk-gpt-key" + claude:0: + api_keys: + - "sk-claude-key" +` + + if err := os.WriteFile(configPath, []byte(v1Config), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + if err := os.WriteFile(secPath, []byte(securityConfig), 0o600); err != nil { + t.Fatalf("WriteFile security: %v", err) + } + + cfg, err := LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig: %v", err) + } + + for _, m := range cfg.ModelList { + if !m.Enabled { + t.Errorf("model %q with API key in security file should be enabled", m.ModelName) + } + } +} + +// TestLoadConfig_V2DirectLoad verifies that V2 configs load directly without +// running any migration. +func TestLoadConfig_V2DirectLoad(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + + v2Config := `{ + "version": 2, + "model_list": [ + { + "model_name": "gpt-4", + "model": "openai/gpt-4", + "enabled": true + }, + { + "model_name": "claude", + "model": "anthropic/claude" + } + ], + "gateway": {"host": "127.0.0.1", "port": 18790} + }` + + if err := os.WriteFile(configPath, []byte(v2Config), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + cfg, err := LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig: %v", err) + } + + if cfg.Version != 2 { + t.Errorf("Version = %d, want 2", cfg.Version) + } + + gpt4, _ := cfg.GetModelConfig("gpt-4") + if !gpt4.Enabled { + t.Error("gpt-4 with explicit enabled=true should remain enabled") + } + + claude, _ := cfg.GetModelConfig("claude") + if claude.Enabled { + t.Error("claude without enabled field should be false (no migration for V2)") + } + + // No backup should be created for V2 load + entries, _ := os.ReadDir(tmpDir) + for _, e := range entries { + if matched, _ := filepath.Match("config.json.*.bak", e.Name()); matched { + t.Errorf("V2 load should not create backup, but found %q", e.Name()) + } + } +} + +// TestLoadConfig_V0MigrateProducesV2 verifies that V0→V2 migration produces +// correct Enabled fields and version. +func TestLoadConfig_V0MigrateProducesV2(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + + v0Config := `{ + "model_list": [ + { + "model_name": "gpt-4", + "model": "openai/gpt-4", + "api_key": "sk-test" + }, + { + "model_name": "claude", + "model": "anthropic/claude" + }, + { + "model_name": "local-model", + "model": "vllm/custom-model" + } + ], + "gateway": {"host": "127.0.0.1", "port": 18790} + }` + + if err := os.WriteFile(configPath, []byte(v0Config), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + cfg, err := LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig: %v", err) + } + + if cfg.Version != CurrentVersion { + t.Errorf("Version = %d, want %d", cfg.Version, CurrentVersion) + } + + // Check enabled status + modelEnabled := func(name string) bool { + m, err := cfg.GetModelConfig(name) + if err != nil { + return false + } + return m.Enabled + } + + if !modelEnabled("gpt-4") { + t.Error("gpt-4 with API key from V0 should be enabled") + } + if modelEnabled("claude") { + t.Error("claude without API key from V0 should be disabled") + } + if !modelEnabled("local-model") { + t.Error("local-model from V0 should be enabled") + } +} + +// TestLoadConfig_UnsupportedVersion verifies that unsupported versions return an error. +func TestLoadConfig_UnsupportedVersion(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + + badConfig := `{"version": 99, "gateway": {"host": "127.0.0.1", "port": 18790}}` + if err := os.WriteFile(configPath, []byte(badConfig), 0o600); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + _, err := LoadConfig(configPath) + if err == nil { + t.Fatal("LoadConfig should return error for unsupported version") + } + if !containsString(err.Error(), "unsupported config version") { + t.Errorf("error = %q, want 'unsupported config version'", err.Error()) + } +} + +func containsString(s, substr string) bool { + return len(s) >= len(substr) && searchString(s, substr) +} + +func searchString(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/pkg/config/multikey_test.go b/pkg/config/multikey_test.go index e58c6dc9e..947e942da 100644 --- a/pkg/config/multikey_test.go +++ b/pkg/config/multikey_test.go @@ -345,7 +345,7 @@ func TestMergeAPIKeys(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := MergeAPIKeys(tt.apiKey, tt.apiKeys) + result := mergeAPIKeys(tt.apiKey, tt.apiKeys) if len(result) != len(tt.expected) { t.Fatalf("expected %d keys, got %d", len(tt.expected), len(result)) } diff --git a/pkg/config/security.go b/pkg/config/security.go index 79dd26e14..2414cd7fa 100644 --- a/pkg/config/security.go +++ b/pkg/config/security.go @@ -7,20 +7,16 @@ package config import ( "bytes" - "encoding/json" "fmt" "os" "path/filepath" "reflect" - "runtime" "strings" "sync" "gopkg.in/yaml.v3" - "github.com/sipeed/picoclaw/pkg/credential" "github.com/sipeed/picoclaw/pkg/fileutil" - "github.com/sipeed/picoclaw/pkg/logger" ) const ( @@ -66,7 +62,6 @@ func saveSecurityConfig(securityPath string, sec *Config) error { return fileutil.WriteFileAtomic(securityPath, buf.Bytes(), 0o600) } -// SensitiveDataCache caches the compiled regex for filtering sensitive data. // SensitiveDataCache caches the strings.Replacer for filtering sensitive data. // Computed once on first access via sync.Once. type SensitiveDataCache struct { @@ -178,234 +173,3 @@ func collectSensitive(v reflect.Value, values *[]string) { } } } - -const ( - notHere = `"[NOT_HERE]"` -) - -// SecureStrings is a slice of SecureString -type SecureStrings []*SecureString - -// Values returns the decrypted/resolved values -func (s *SecureStrings) Values() []string { - if s == nil { - return nil - } - keys := make([]string, len(*s)) - for i, k := range *s { - keys[i] = k.String() - } - return unique(keys) -} - -func SimpleSecureStrings(val ...string) SecureStrings { - val = unique(val) - vv := make(SecureStrings, len(val)) - for i, s := range val { - vv[i] = NewSecureString(s) - } - return vv -} - -// unique returns a new slice with duplicate elements removed. -func unique[T comparable](input []T) []T { - m := make(map[T]struct{}) - var result []T - for _, v := range input { - if _, ok := m[v]; !ok { - m[v] = struct{}{} - result = append(result, v) - } - } - return result -} - -func (s SecureStrings) MarshalJSON() ([]byte, error) { - return []byte(notHere), nil -} - -func (s *SecureStrings) UnmarshalJSON(value []byte) error { - if string(value) == notHere { - return nil - } - var v []*SecureString - err := json.Unmarshal(value, &v) - if err != nil { - return err - } - *s = v - return nil -} - -// SecureString the string value that can be decrypted or resolved -// -//nolint:recvcheck -type SecureString struct { - resolved string // Decrypted/resolved value returned by String() - raw string // Persisted raw value (enc://, file://, or plaintext) -} - -func callerFromYaml() bool { - _, file, _, ok := runtime.Caller(2) - if ok { - d := filepath.Dir(file) - // check the caller is from yaml.v - if !strings.Contains(d, "yaml.v") { - return true - } - } - return false -} - -// IsZero returns true if the SecureString is empty -// if caller not yaml, just return true for prevent marshal this field -func (s SecureString) IsZero() bool { - if callerFromYaml() { - return true - } - return s.resolved == "" -} - -func NewSecureString(value string) *SecureString { - s := &SecureString{} - if err := s.fromRaw(value); err != nil { - logger.Warn(fmt.Sprintf("NewSecureString.fromRaw error: %s", err)) - } - return s -} - -func (s *SecureString) String() string { - if s == nil { - return "" - } - return s.resolved -} - -func (s *SecureString) Set(value string) *SecureString { - s.resolved = value - s.raw = "" - return s -} - -func (s SecureString) MarshalJSON() ([]byte, error) { - return []byte(notHere), nil -} - -func (s *SecureString) UnmarshalJSON(value []byte) error { - if string(value) == notHere { - return nil - } - var v string - if err := json.Unmarshal(value, &v); err != nil { - return err - } - return s.fromRaw(v) -} - -func (s SecureString) MarshalYAML() (any, error) { - // Preserve raw value if it is already a reference (enc:// or file://) - if strings.HasPrefix(s.raw, credential.EncScheme) || strings.HasPrefix(s.raw, credential.FileScheme) { - return s.raw, nil - } - // If resolved is a reference format (e.g. set via Set), copy back to raw - if strings.HasPrefix(s.resolved, credential.EncScheme) || strings.HasPrefix(s.resolved, credential.FileScheme) { - s.raw = s.resolved - return s.raw, nil - } - // Try to encrypt the resolved value - if passphrase := credential.PassphraseProvider(); passphrase != "" { - encrypted, err := credential.Encrypt(passphrase, "", s.resolved) - if err != nil { - logger.Errorf("Encrypt error: %v", err) - return nil, err - } - s.raw = encrypted - } else { - s.raw = s.resolved - } - return s.raw, nil -} - -func (s *SecureString) UnmarshalYAML(value *yaml.Node) error { - return s.fromRaw(value.Value) -} - -func (s *SecureString) fromRaw(v string) error { - s.raw = v - vv, err := resolveKey(v) - if err != nil { - return err - } - s.resolved = vv - return nil -} - -var ( - secResolverMu sync.RWMutex - secResolver *credential.Resolver -) - -func updateResolver(path string) { - secResolverMu.Lock() - defer secResolverMu.Unlock() - secResolver = credential.NewResolver(path) -} - -func resolveKey(v string) (string, error) { - secResolverMu.RLock() - resolver := secResolver - secResolverMu.RUnlock() - if resolver == nil { - resolver = credential.NewResolver("") - } - if strings.HasPrefix(v, "enc://") || strings.HasPrefix(v, "file://") { - decrypted, err := resolver.Resolve(v) - if err != nil { - logger.Errorf("Resolve error: %v", err) - return "", err - } - return decrypted, nil - } - return v, nil -} - -func (s *SecureString) UnmarshalText(text []byte) error { - v := string(text) - return s.fromRaw(v) -} - -type SecureModelList []*ModelConfig - -func (v *SecureModelList) UnmarshalYAML(value *yaml.Node) error { - mm := make(map[string]*ModelConfig) - if err := value.Decode(&mm); err != nil { - logger.Errorf("Decode error: %v", err) - return err - } - nameList := toNameIndex(*v) - for i, m := range *v { - sec := mm[nameList[i]] - if sec == nil { - sec = mm[m.ModelName] - } - if sec != nil { - m.APIKeys = sec.APIKeys - } - } - return nil -} - -func (v SecureModelList) MarshalYAML() (any, error) { - type onlySecureData struct { - APIKeys SecureStrings `yaml:"api_keys,omitempty"` - } - mm := make(map[string]onlySecureData) - nameList := toNameIndex(v) - for i, m := range v { - mm[nameList[i]] = onlySecureData{ - APIKeys: m.APIKeys, - } - } - - return mm, nil -} diff --git a/pkg/config/security_test.go b/pkg/config/security_test.go index 834ba3606..548a6dc87 100644 --- a/pkg/config/security_test.go +++ b/pkg/config/security_test.go @@ -15,8 +15,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" - - "github.com/sipeed/picoclaw/pkg/credential" ) func TestSecurityConfig(t *testing.T) { @@ -227,134 +225,3 @@ skills: assert.Equal(t, "abc", cfg2.Tools.Web.Brave.APIKeys[1].raw) }) } - -func TestLoadSecurityValue(t *testing.T) { - type valueStruct struct { - Url string `json:"url,omitempty" yaml:"-"` - Token *SecureString `json:"token,omitempty" yaml:"token,omitempty" env:"PICO_TOKEN"` - ApiKeys SecureStrings `json:"api_keys,omitempty" yaml:"api_keys,omitempty" env:"PICO_API_KEYS"` - } - - type testStruct struct { - Pico *valueStruct `json:"pico,omitempty" yaml:"pico,omitempty"` - } - - v1 := &testStruct{ - Pico: &valueStruct{ - Url: "https://example.com", - Token: NewSecureString("token1"), - ApiKeys: SecureStrings{NewSecureString("api-key1"), NewSecureString("api-key2")}, - }, - } - bytes, err := yaml.Marshal(v1) - assert.NoError(t, err) - jsonBytes, err := json.Marshal(v1) - assert.NoError(t, err) - const want = `pico: - token: token1 - api_keys: - - api-key1 - - api-key2 -` - const jsonPost = `{"pico":{"url":"https://example.com","token":"token0"}}` - v0 := &testStruct{} - err = json.Unmarshal([]byte(jsonPost), v0) - assert.NoError(t, err) - assert.Equal(t, "https://example.com", v0.Pico.Url) - assert.Equal(t, "token0", v0.Pico.Token.String()) - - const jsonWant = `{"pico":{"url":"https://example.com","token":"[NOT_HERE]","api_keys":"[NOT_HERE]"}}` - assert.Equal(t, want, string(bytes)) - assert.Equal(t, jsonWant, string(jsonBytes)) - - v2 := &testStruct{} - err = json.Unmarshal(jsonBytes, v2) - assert.NoError(t, err) - err = yaml.Unmarshal(bytes, v2) - assert.NoError(t, err) - assert.Equal(t, "https://example.com", v2.Pico.Url) - if v2.Pico.Token != nil { - assert.Equal(t, "token1", v2.Pico.Token.String()) - assert.Equal(t, "token1", v2.Pico.Token.raw) - } - - v2.Pico.Token = NewSecureString("token1") - v2.Pico.Token.raw = "abc" - err = yaml.Unmarshal(bytes, v2) - assert.NoError(t, err) - assert.Equal(t, "token1", v2.Pico.Token.raw) - - os.Setenv("PICO_TOKEN", "token_env") - err = env.Parse(v2) - assert.NoError(t, err) - assert.NotNil(t, v2.Pico.Token) - assert.Equal(t, "token1", v2.Pico.Token.String()) - - v3 := &testStruct{Pico: &valueStruct{}} - err = env.Parse(v3) - assert.NoError(t, err) - if v3.Pico.Token != nil { - assert.Equal(t, "token_env", v3.Pico.Token.String()) - } - - type toolsStruct struct { - Pico valueStruct `json:"pico,omitempty" yaml:"pico,omitempty"` - } - - type testStruct2 struct { - Tools toolsStruct `json:"tools,omitempty" yaml:",inline"` - } - - v4 := &testStruct2{ - Tools: toolsStruct{ - Pico: valueStruct{ - Url: "https://example.com", - Token: NewSecureString("token1"), - ApiKeys: SecureStrings{NewSecureString("api-key1"), NewSecureString("api-key2")}, - }, - }, - } - bytes, err = yaml.Marshal(v4) - assert.NoError(t, err) - assert.Equal(t, want, string(bytes)) - jsonBytes, err = json.Marshal(v4) - assert.NoError(t, err) - assert.Equal( - t, - `{"tools":{"pico":{"url":"https://example.com","token":"[NOT_HERE]","api_keys":"[NOT_HERE]"}}}`, - string(jsonBytes), - ) - - v5 := &testStruct2{} - err = json.Unmarshal(jsonBytes, v5) - assert.NoError(t, err) - assert.Equal(t, "https://example.com", v5.Tools.Pico.Url) - err = yaml.Unmarshal(bytes, v5) - assert.NoError(t, err) - assert.NotNil(t, v5.Tools.Pico.Token) - assert.Equal(t, "token1", v5.Tools.Pico.Token.raw) - - dir := t.TempDir() - sshKeyPath := filepath.Join(dir, "picoclaw_ed25519.key") - if err = os.WriteFile(sshKeyPath, []byte("fake-ssh-key-material\n"), 0o600); err != nil { - t.Fatalf("setup: %v", err) - } - - const passphrase = "test-passphrase-32bytes-long-ok!" - - t.Setenv(credential.SSHKeyPathEnvVar, sshKeyPath) - - t.Setenv(credential.PassphraseEnvVar, passphrase) - - v5.Tools.Pico.Token.Set("newtoken1") - v5.Tools.Pico.ApiKeys[0].Set("newapi-key1") - bytes, err = yaml.Marshal(v5) - assert.NoError(t, err) - t.Logf("yaml: %s", string(bytes)) - - v6 := &testStruct2{} - err = yaml.Unmarshal(bytes, v6) - assert.NoError(t, err) - assert.NotNil(t, v6.Tools.Pico.Token) - assert.Equal(t, "newtoken1", v6.Tools.Pico.Token.String()) -} diff --git a/web/backend/api/models.go b/web/backend/api/models.go index 38a55948b..fd3cd85b7 100644 --- a/web/backend/api/models.go +++ b/web/backend/api/models.go @@ -40,6 +40,7 @@ type modelResponse struct { ThinkingLevel string `json:"thinking_level,omitempty"` ExtraBody map[string]any `json:"extra_body,omitempty"` // Meta + Enabled bool `json:"enabled"` Configured bool `json:"configured"` IsDefault bool `json:"is_default"` IsVirtual bool `json:"is_virtual"` @@ -85,6 +86,7 @@ func (h *Handler) handleListModels(w http.ResponseWriter, r *http.Request) { RequestTimeout: m.RequestTimeout, ThinkingLevel: m.ThinkingLevel, ExtraBody: m.ExtraBody, + Enabled: m.Enabled, Configured: configured[i], IsDefault: m.ModelName == defaultModel, IsVirtual: m.IsVirtual(), From b67d3cfbd86af93a928f717208a69442211ee217 Mon Sep 17 00:00:00 2001 From: BeaconCat <111232138+BeaconCat@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:44:32 +0800 Subject: [PATCH 38/55] docs: document gateway.log_level in all READMEs and i18n configuration docs (#2178) * docs: document gateway.log_level in all READMEs and i18n configuration docs Add gateway log level note to Channels section in all 9 READMEs and add Gateway Log Level section to zh/fr/ja/pt-br/vi configuration docs. - gateway.log_level (default: fatal) controls log verbosity - Supported values: debug, info, warn, error, fatal - Can also be set via PICOCLAW_LOG_LEVEL env var - English docs/configuration.md already had this section * fix(docs): correct gateway.log_level default from fatal to warn DefaultConfig() sets Gateway.LogLevel to "warn", not "fatal". Update all READMEs and i18n configuration docs to reflect the actual default value. --------- Co-authored-by: BeaconCat --- README.fr.md | 2 ++ README.id.md | 2 ++ README.it.md | 2 ++ README.ja.md | 2 ++ README.md | 2 ++ README.my.md | 2 ++ README.pt-br.md | 2 ++ README.vi.md | 2 ++ README.zh.md | 2 ++ docs/configuration.md | 4 ++-- docs/fr/configuration.md | 16 ++++++++++++++++ docs/ja/configuration.md | 16 ++++++++++++++++ docs/pt-br/configuration.md | 16 ++++++++++++++++ docs/vi/configuration.md | 16 ++++++++++++++++ docs/zh/configuration.md | 16 ++++++++++++++++ 15 files changed, 100 insertions(+), 2 deletions(-) diff --git a/README.fr.md b/README.fr.md index 4fd326102..8a035f9b3 100644 --- a/README.fr.md +++ b/README.fr.md @@ -462,6 +462,8 @@ Parlez à votre PicoClaw via plus de 17 plateformes de messagerie : > Tous les channels basés sur webhook partagent un seul serveur HTTP Gateway (`gateway.host`:`gateway.port`, par défaut `127.0.0.1:18790`). Feishu utilise le mode WebSocket/SDK et n'utilise pas le serveur HTTP partagé. +> La verbosité des logs est contrôlée par `gateway.log_level` (par défaut : `warn`). Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. Peut aussi être défini via `PICOCLAW_LOG_LEVEL`. Voir [Configuration](docs/fr/configuration.md#niveau-de-log-du-gateway) pour plus de détails. + Pour les instructions détaillées de configuration des channels, voir [Configuration des applications de chat](docs/fr/chat-apps.md). ## 🔧 Outils diff --git a/README.id.md b/README.id.md index d88f5eb32..3fe4c1276 100644 --- a/README.id.md +++ b/README.id.md @@ -458,6 +458,8 @@ Bicara dengan PicoClaw Anda melalui 17+ platform pesan: > Semua channel berbasis webhook berbagi satu server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu menggunakan mode WebSocket/SDK dan tidak menggunakan server HTTP bersama. +> Verbositas log dikontrol oleh `gateway.log_level` (default: `warn`). Nilai yang didukung: `debug`, `info`, `warn`, `error`, `fatal`. Juga dapat diatur melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk detail. + Untuk instruksi pengaturan channel lengkap, lihat [Konfigurasi Aplikasi Chat](docs/chat-apps.md). ## 🔧 Tools diff --git a/README.it.md b/README.it.md index 5874ca27a..8748aea9c 100644 --- a/README.it.md +++ b/README.it.md @@ -458,6 +458,8 @@ Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica: > Tutti i channel basati su webhook condividono un singolo server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu usa la modalità WebSocket/SDK e non usa il server HTTP condiviso. +> La verbosità dei log è controllata da `gateway.log_level` (default: `warn`). Valori supportati: `debug`, `info`, `warn`, `error`, `fatal`. Può essere impostato anche tramite `PICOCLAW_LOG_LEVEL`. Vedi [Configurazione](docs/configuration.md#gateway-log-level) per i dettagli. + Per istruzioni dettagliate sulla configurazione dei channel, vedi [Configurazione App di Chat](docs/chat-apps.md). ## 🔧 Strumenti diff --git a/README.ja.md b/README.ja.md index 2f17516af..3772ff532 100644 --- a/README.ja.md +++ b/README.ja.md @@ -458,6 +458,8 @@ Provider の完全な設定詳細は [Provider とモデル](docs/ja/providers.m > webhook ベースのすべての Channel は単一の Gateway HTTP サーバー(`gateway.host`:`gateway.port`、デフォルト `127.0.0.1:18790`)を共有します。Feishu は WebSocket/SDK モードを使用し、共有 HTTP サーバーを使用しません。 +> ログの詳細度は `gateway.log_level` で制御します(デフォルト:`warn`)。サポートされる値:`debug`、`info`、`warn`、`error`、`fatal`。`PICOCLAW_LOG_LEVEL` 環境変数でも設定可能です。詳細は[設定ガイド](docs/ja/configuration.md#gateway-ログレベル)を参照してください。 + Channel の詳細なセットアップ手順は [チャットアプリ設定](docs/ja/chat-apps.md) を参照してください。 ## 🔧 ツール diff --git a/README.md b/README.md index e963fb1cf..9441747ca 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,8 @@ Talk to your PicoClaw through 17+ messaging platforms: > All webhook-based channels share a single Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu uses WebSocket/SDK mode and does not use the shared HTTP server. +> Log verbosity is controlled by `gateway.log_level` (default: `warn`). Supported values: `debug`, `info`, `warn`, `error`, `fatal`. Can also be set via `PICOCLAW_LOG_LEVEL`. See [Configuration](docs/configuration.md#gateway-log-level) for details. + For detailed channel setup instructions, see [Chat Apps Configuration](docs/chat-apps.md). ## 🔧 Tools diff --git a/README.my.md b/README.my.md index 2f1a38942..c07cdd005 100644 --- a/README.my.md +++ b/README.my.md @@ -458,6 +458,8 @@ Bercakap dengan PicoClaw anda melalui 17+ platform pemesejan: > Semua saluran berasaskan webhook berkongsi satu pelayan HTTP Gateway (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP yang dikongsi. +> Tahap perincian log dikawal oleh `gateway.log_level` (lalai: `warn`). Nilai yang disokong: `debug`, `info`, `warn`, `error`, `fatal`. Boleh juga ditetapkan melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk butiran. + Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](docs/my/chat-apps.md). ## 🔧 Alat diff --git a/README.pt-br.md b/README.pt-br.md index 42ce5cc95..dfe7cb0f2 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -458,6 +458,8 @@ Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens: > Todos os channels baseados em webhook compartilham um único servidor HTTP do Gateway (`gateway.host`:`gateway.port`, padrão `127.0.0.1:18790`). O Feishu usa modo WebSocket/SDK e não utiliza o servidor HTTP compartilhado. +> A verbosidade dos logs é controlada por `gateway.log_level` (padrão: `warn`). Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. Também pode ser definido via `PICOCLAW_LOG_LEVEL`. Veja [Configuração](docs/pt-br/configuration.md#nível-de-log-do-gateway) para detalhes. + Para instruções detalhadas de configuração de channels, veja [Configuração de Apps de Chat](docs/pt-br/chat-apps.md). ## 🔧 Ferramentas diff --git a/README.vi.md b/README.vi.md index 5ca39d99d..6c8f6ad44 100644 --- a/README.vi.md +++ b/README.vi.md @@ -458,6 +458,8 @@ Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin: > Tất cả các Channel dựa trên webhook dùng chung một Gateway HTTP server (`gateway.host`:`gateway.port`, mặc định `127.0.0.1:18790`). Feishu sử dụng chế độ WebSocket/SDK và không dùng HTTP server chung. +> Mức độ chi tiết log được kiểm soát bởi `gateway.log_level` (mặc định: `warn`). Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. Cũng có thể đặt qua `PICOCLAW_LOG_LEVEL`. Xem [Cấu hình](docs/vi/configuration.md#mức-log-của-gateway) để biết thêm chi tiết. + Để biết hướng dẫn thiết lập Channel chi tiết, xem [Cấu hình Ứng dụng Chat](docs/vi/chat-apps.md). ## 🔧 Tools diff --git a/README.zh.md b/README.zh.md index 7ca20b1ed..b92e8e889 100644 --- a/README.zh.md +++ b/README.zh.md @@ -458,6 +458,8 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模 > 所有基于 Webhook 的 Channel 共用同一个 Gateway HTTP 服务器(`gateway.host`:`gateway.port`,默认 `127.0.0.1:18790`)。飞书使用 WebSocket/SDK 模式,不使用共享 HTTP 服务器。 +> 日志详细程度通过 `gateway.log_level` 控制(默认:`warn`)。支持的值:`debug`、`info`、`warn`、`error`、`fatal`。也可通过 `PICOCLAW_LOG_LEVEL` 环境变量设置。详见[配置指南](docs/zh/configuration.md#gateway-日志等级)。 + 详细 Channel 配置说明请参阅 [聊天应用配置](docs/zh/chat-apps.md)。 ## 🔧 Tools diff --git a/docs/configuration.md b/docs/configuration.md index 3462767e6..9b9524692 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -38,12 +38,12 @@ PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gat ```json { "gateway": { - "log_level": "fatal" + "log_level": "warn" } } ``` -When omitted, the default is `fatal`. Supported values: `debug`, `info`, `warn`, `error`, `fatal`. +When omitted, the default is `warn`. Supported values: `debug`, `info`, `warn`, `error`, `fatal`. You can also override this with the environment variable `PICOCLAW_LOG_LEVEL`. diff --git a/docs/fr/configuration.md b/docs/fr/configuration.md index 8d94620ba..fd8602c68 100644 --- a/docs/fr/configuration.md +++ b/docs/fr/configuration.md @@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway ``` +### Niveau de Log du Gateway + +`gateway.log_level` contrôle la verbosité des logs du Gateway, configurable dans `config.json` : + +```json +{ + "gateway": { + "log_level": "warn" + } +} +``` + +La valeur par défaut est `warn`. Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. + +Peut également être surchargé via la variable d'environnement : `PICOCLAW_LOG_LEVEL=info` + ### Structure du Workspace PicoClaw stocke les données dans votre workspace configuré (par défaut : `~/.picoclaw/workspace`) : diff --git a/docs/ja/configuration.md b/docs/ja/configuration.md index 35676809e..da60b6052 100644 --- a/docs/ja/configuration.md +++ b/docs/ja/configuration.md @@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway ``` +### Gateway ログレベル + +`gateway.log_level` は Gateway のログ詳細度を制御します。`config.json` で設定できます: + +```json +{ + "gateway": { + "log_level": "warn" + } +} +``` + +デフォルト値は `warn` です。サポートされる値:`debug`、`info`、`warn`、`error`、`fatal`。 + +環境変数でも上書き可能です:`PICOCLAW_LOG_LEVEL=info` + ### ワークスペースレイアウト PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw/workspace`)にデータを保存します: diff --git a/docs/pt-br/configuration.md b/docs/pt-br/configuration.md index ff3ce2b34..cb836ce0f 100644 --- a/docs/pt-br/configuration.md +++ b/docs/pt-br/configuration.md @@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway ``` +### Nível de Log do Gateway + +`gateway.log_level` controla a verbosidade dos logs do Gateway, configurável em `config.json`: + +```json +{ + "gateway": { + "log_level": "warn" + } +} +``` + +O valor padrão é `warn`. Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. + +Também pode ser substituído pela variável de ambiente: `PICOCLAW_LOG_LEVEL=info` + ### Layout do Workspace O PicoClaw armazena dados no seu workspace configurado (padrão: `~/.picoclaw/workspace`): diff --git a/docs/vi/configuration.md b/docs/vi/configuration.md index fecadc6ff..b75215c3f 100644 --- a/docs/vi/configuration.md +++ b/docs/vi/configuration.md @@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway ``` +### Mức Log của Gateway + +`gateway.log_level` kiểm soát mức độ chi tiết của log Gateway, có thể cấu hình trong `config.json`: + +```json +{ + "gateway": { + "log_level": "warn" + } +} +``` + +Giá trị mặc định là `warn`. Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. + +Cũng có thể ghi đè bằng biến môi trường: `PICOCLAW_LOG_LEVEL=info` + ### Bố Cục Workspace PicoClaw lưu trữ dữ liệu trong workspace đã cấu hình (mặc định: `~/.picoclaw/workspace`): diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md index 3b0ac9a50..c27a439f0 100644 --- a/docs/zh/configuration.md +++ b/docs/zh/configuration.md @@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway ``` +### Gateway 日志等级 + +`gateway.log_level` 控制 Gateway 的日志详细程度,可在 `config.json` 中配置: + +```json +{ + "gateway": { + "log_level": "warn" + } +} +``` + +默认值为 `warn`。支持的值:`debug`、`info`、`warn`、`error`、`fatal`。 + +也可通过环境变量覆盖:`PICOCLAW_LOG_LEVEL=info` + ### 工作区布局 (Workspace Layout) PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/workspace`): From edda02ce67cc5ef5124351ca86d1c3b522835cca Mon Sep 17 00:00:00 2001 From: wenjie Date: Mon, 30 Mar 2026 14:45:52 +0800 Subject: [PATCH 39/55] build(web): refactor launcher build flow and expand WebUI documentation (#2174) - delegate root launcher builds to the web Makefile - add dedicated frontend and dev picoclaw build targets - document the WebUI architecture, runtime behavior, and build workflow --- Makefile | 13 +- web/Makefile | 64 ++++++--- web/README.md | 374 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 406 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 9581fa633..992182775 100644 --- a/Makefile +++ b/Makefile @@ -130,14 +130,17 @@ build: generate build-launcher: @echo "Building picoclaw-launcher for $(PLATFORM)/$(ARCH)..." @mkdir -p $(BUILD_DIR) - @if [ ! -f web/backend/dist/index.html ]; then \ - echo "Building frontend..."; \ - cd web/frontend && pnpm install && pnpm build:backend; \ - fi - @$(WEB_GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH) ./web/backend + @$(MAKE) -C web build \ + OUTPUT="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)" \ + WEB_GO='$(WEB_GO)' \ + GO_BUILD_TAGS='$(GO_BUILD_TAGS)' \ + LDFLAGS='$(LDFLAGS)' @ln -sf picoclaw-launcher-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher @echo "Build complete: $(BUILD_DIR)/picoclaw-launcher" +build-launcher-frontend: + @$(MAKE) -C web build-frontend + ## build-launcher-tui: Build the picoclaw-launcher TUI binary build-launcher-tui: @echo "Building picoclaw-launcher-tui for $(PLATFORM)/$(ARCH)..." diff --git a/web/Makefile b/web/Makefile index 06717f2b9..891c170c2 100644 --- a/web/Makefile +++ b/web/Makefile @@ -1,12 +1,20 @@ -.PHONY: dev dev-frontend dev-backend build test lint clean +.PHONY: dev dev-frontend dev-backend build build-frontend build-dev-picoclaw test lint clean # Go variables GO?=CGO_ENABLED=0 go WEB_GO?=$(GO) -GOFLAGS?=-v -tags stdjson +GO_BUILD_TAGS?=goolm,stdjson +GOFLAGS?=-v -tags $(GO_BUILD_TAGS) # Build variables BUILD_DIR=build +OUTPUT?=$(BUILD_DIR)/picoclaw-launcher +FRONTEND_DIR=frontend +BACKEND_DIR=backend +BACKEND_DIST=$(BACKEND_DIR)/dist +PICOCLAW_BINARY_NAME=picoclaw +PICOCLAW_BINARY?=$(abspath ../build/$(PICOCLAW_BINARY_NAME)) +LAUNCHER_GUI_LDFLAG= # Version VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") @@ -52,45 +60,63 @@ else ifeq ($(UNAME_S),Darwin) else ifeq ($(UNAME_S),Windows) PLATFORM=windows ARCH=$(UNAME_M) - LDFLAGS=-H=windowsgui $(LDFLAGS) + PICOCLAW_BINARY_NAME=picoclaw.exe + LAUNCHER_GUI_LDFLAG=-H=windowsgui else PLATFORM=$(UNAME_S) ARCH=$(UNAME_M) endif +LAUNCHER_LDFLAGS=$(strip $(LAUNCHER_GUI_LDFLAG) $(LDFLAGS)) + # Run both frontend and backend dev servers -dev: - @if [ ! -f $(BUILD_DIR)/picoclaw-launcher ] || [ ! -d backend/dist ]; then \ - echo "Build artifacts not found, building..."; \ - $(MAKE) build; \ +dev: build-dev-picoclaw + @if [ ! -f "$(BACKEND_DIST)/index.html" ]; then \ + echo "Embedded frontend not found, building..."; \ + $(MAKE) build-frontend; \ fi @echo "Starting backend and frontend dev servers..." - @$(MAKE) dev-backend & $(MAKE) dev-frontend + @$(MAKE) dev-backend BACKEND_ARGS='-no-browser' & $(MAKE) dev-frontend # Start frontend dev server (Vite, with proxy to backend) dev-frontend: - cd frontend && pnpm dev + cd $(FRONTEND_DIR) && pnpm dev # Start backend dev server dev-backend: - cd backend && ${WEB_GO} run -ldflags "$(LDFLAGS)" . + cd $(BACKEND_DIR) && PICOCLAW_BINARY="$(PICOCLAW_BINARY)" ${WEB_GO} run -ldflags "$(LAUNCHER_LDFLAGS)" . $(BACKEND_ARGS) # Build frontend and embed into Go binary -build: - cd frontend && pnpm build:backend - ${WEB_GO} build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/picoclaw-launcher ./backend/ +build: build-frontend + @mkdir -p "$$(dirname "$(OUTPUT)")" + ${WEB_GO} build $(GOFLAGS) -ldflags "$(LAUNCHER_LDFLAGS)" -o "$(OUTPUT)" ./$(BACKEND_DIR)/ + +build-frontend: + @if [ ! -d $(FRONTEND_DIR)/node_modules ] || \ + [ $(FRONTEND_DIR)/package.json -nt $(FRONTEND_DIR)/node_modules ] || \ + [ $(FRONTEND_DIR)/pnpm-lock.yaml -nt $(FRONTEND_DIR)/node_modules ]; then \ + echo "Installing frontend dependencies..."; \ + cd $(FRONTEND_DIR) && pnpm install --frozen-lockfile; \ + fi + @echo "Building frontend..." + @cd $(FRONTEND_DIR) && pnpm build:backend + +build-dev-picoclaw: + @echo "Building picoclaw for launcher development..." + @mkdir -p "$$(dirname "$(PICOCLAW_BINARY)")" + @$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o "$(PICOCLAW_BINARY)" ../cmd/picoclaw # Run all tests test: - cd backend && ${WEB_GO} test ./... - cd frontend && pnpm lint + cd $(BACKEND_DIR) && ${WEB_GO} test ./... + cd $(FRONTEND_DIR) && pnpm lint # Lint and format lint: - cd backend && ${WEB_GO} vet ./... - cd frontend && pnpm check + cd $(BACKEND_DIR) && ${WEB_GO} vet ./... + cd $(FRONTEND_DIR) && pnpm check # Clean build artifacts clean: - rm -rf frontend/dist backend/dist $(BUILD_DIR) - mkdir -p backend/dist && touch backend/dist/.gitkeep + rm -rf $(FRONTEND_DIR)/dist $(BACKEND_DIST) $(BUILD_DIR) + node $(FRONTEND_DIR)/scripts/ensure-backend-gitkeep.cjs diff --git a/web/README.md b/web/README.md index 6ec247bae..a3faa03d1 100644 --- a/web/README.md +++ b/web/README.md @@ -1,51 +1,383 @@ -# Picoclaw Web +# PicoClaw Web -This directory contains the standalone web service for `picoclaw`. -It provides a complete unified web interface, acting as a dashboard, configuration center, and interactive console (channel client) for the core `picoclaw` engine. +`web/` contains the standalone WebUI launcher for PicoClaw. +It is not just a frontend: it is a small launcher service that bundles a React dashboard, exposes a backend API, manages launcher authentication, and starts or attaches to the `picoclaw gateway` process. + +![PicoClaw Launcher](./picoclaw-launcher.png) + +## What This Directory Provides + +- A browser-based chat UI backed by the Pico channel WebSocket proxy. +- A dashboard for models, credentials, channels, agent tools, skills, logs, and runtime settings. +- A launcher process that can auto-open the browser, show a system tray menu, and persist launcher-specific settings. +- A controlled way to start, stop, restart, and inspect the `picoclaw gateway` subprocess. +- A single-binary deployment target where the frontend is embedded into the Go backend. ## Architecture -The service is structured as a monorepo containing both the backend and frontend code to ensure high cohesion and simplify deployment. +This directory is a small monorepo: -* **`backend/`**: The Go-based web server. It provides RESTful APIs, manages WebSocket connections for chat, and handles the lifecycle of the `picoclaw` process. It eventually embeds the compiled frontend assets into a single executable. -* **`frontend/`**: The Vite + React + TanStack Router single-page application (SPA). It provides the interactive user interface. +- `backend/` + - Go HTTP server and launcher runtime. + - Serves REST APIs, authentication endpoints, channel helper flows, and the Pico WebSocket reverse proxy. + - Embeds compiled frontend assets from `backend/dist`. +- `frontend/` + - Vite + React 19 + TanStack Router SPA. + - Provides the launcher dashboard and chat UI. -## Getting Started +At runtime the launcher and the main PicoClaw engine are separate processes: + +1. The launcher starts the web backend on port `18800` by default. +2. The launcher serves the dashboard and handles dashboard authentication. +3. When allowed, it starts or attaches to `picoclaw gateway -E`. +4. The frontend talks only to the launcher backend. +5. The launcher proxies chat traffic to the gateway through `/pico/ws`. + +## Dashboard Capabilities + +The current frontend exposes these major pages and flows: + +- `/` + - Chat UI with session history, default model selection, and Pico channel messaging. +- `/models` + - Add, edit, delete, and set the default model. + - Supports API-key models, OAuth-backed models, and local/CLI-backed models. +- `/credentials` + - Manage provider credentials. + - Current built-in flows: OpenAI, Anthropic, and Google Antigravity. +- `/channels/*` + - Configure supported channels from a shared catalog. + - Current catalog: `weixin`, `telegram`, `discord`, `slack`, `feishu`, `dingtalk`, `line`, `qq`, `onebot`, `wecom`, `whatsapp`, `whatsapp_native`, `pico`, `maixcam`, `matrix`, `irc`. + - Includes QR-based binding helpers for WeChat and WeCom. +- `/agent/skills` + - Browse built-in, global, and workspace skills. + - Import Markdown skills into the workspace and delete workspace-owned skills. +- `/agent/tools` + - View tool availability and enable or disable tool switches through config-backed APIs. +- `/config` + - Edit agent defaults, exec controls, cron controls, heartbeat, device monitoring, launcher networking, and launch-at-login settings. +- `/logs` + - View the in-memory gateway log buffer and clear it. + +The UI currently supports English and Simplified Chinese, plus light and dark themes. + +## Runtime Behavior + +### Config Resolution + +The launcher uses the same PicoClaw config file as the main binary. + +- Default app config path: `~/.picoclaw/config.json` +- Override with environment variable: `PICOCLAW_CONFIG` +- Override with a positional CLI argument: `picoclaw-launcher /path/to/config.json` + +Launcher-only settings are stored beside that app config: + +- File name: `launcher-config.json` +- Default location: `~/.picoclaw/launcher-config.json` + +That file currently stores: + +- `port` +- `public` +- `allowed_cidrs` + +If `-port` or `-public` are passed explicitly, the CLI flag wins for that run. +If they are omitted, stored launcher settings are used. + +### First-Run Onboarding + +If the target config file does not exist, the launcher tries to bootstrap it automatically by running: + +```bash +picoclaw onboard +``` + +The launcher looks for the main PicoClaw binary in this order: + +1. `PICOCLAW_BINARY` +2. A `picoclaw` binary in the same directory as the launcher +3. `picoclaw` from `PATH` + +If onboarding or gateway startup cannot find the main binary, set `PICOCLAW_BINARY` explicitly. + +### Gateway Management + +The launcher manages `picoclaw gateway -E`. + +On startup it tries to auto-start or attach to the gateway, but only when startup preconditions pass. In the current code, the main checks are: + +- a default model is configured +- the default model entry is valid +- the default model has usable credentials +- local/runtime-probed models are reachable + +When a gateway process is started by the launcher, the launcher: + +- captures stdout and stderr into an in-memory ring buffer +- tracks transient states such as `starting`, `restarting`, and `stopping` +- marks restart-required when the default model or enabled tool set changed since boot +- ensures the Pico channel is configured before startup + +### Launcher Authentication + +The dashboard is protected by a launcher access token. + +- If `PICOCLAW_LAUNCHER_TOKEN` is set, that token is used. +- Otherwise a random token is generated for each launcher process. +- The browser auto-open URL includes `?token=...` so local launches can sign in automatically. +- Manual login uses `/launcher-login`. +- API clients may also authenticate with `Authorization: Bearer `. + +Where users can retrieve the token depends on launch mode: + +- Console mode: printed to stdout +- GUI mode: available through the tray menu on supported builds +- GUI mode without stdout: + - random per-run tokens are written to the launcher log + - default log path: `~/.picoclaw/logs/launcher.log` + - if `PICOCLAW_HOME` is set, use `$PICOCLAW_HOME/logs/launcher.log` + - env-pinned tokens are not reprinted there; the log only notes that `PICOCLAW_LAUNCHER_TOKEN` is in use + +### Network Exposure + +By default the launcher listens on: + +```text +127.0.0.1:18800 +``` + +With `-public` or `public: true`, it listens on all interfaces: + +```text +0.0.0.0:18800 +``` + +When public access is enabled: + +- the launcher can still protect the dashboard with the access token +- optional `allowed_cidrs` can restrict which client IP ranges may connect +- the gateway host is overridden so remote clients can still use the launcher-managed proxy paths + +## Build And Run ### Prerequisites -* Go 1.25+ -* Node.js 20+ with pnpm +- Go `1.25+` +- Node.js `20+` +- `pnpm` -### Development +On macOS, the `web` Makefile enables `CGO_ENABLED=1` so tray-enabled launcher builds work as expected. +On Darwin or FreeBSD without cgo, the launcher falls back to headless mode without a tray. -Run both the frontend dev server and the Go backend simultaneously: +If you want to prepare the frontend workspace manually, you can still install dependencies yourself: + +```bash +cd frontend +pnpm install +``` + +### Recommended Development Workflow + +From the `web/` directory: ```bash make dev ``` -Or run them separately: +This does three things: + +1. Builds `../build/picoclaw` for launcher development. +2. Starts the Go backend with `PICOCLAW_BINARY` pointing at that binary. +3. Starts the Vite frontend dev server. + +Use this when you want the full launcher flow during development. + +### Run Frontend And Backend Separately ```bash -make dev-frontend # Vite dev server -make dev-backend # Go backend +make dev-frontend +make dev-backend ``` -### Build +Notes: -Build the frontend and embed it into a single Go binary: +- `dev-frontend` runs the Vite server. +- `dev-backend` runs the Go backend only. +- The Vite dev server proxies `/api` to `http://localhost:18800`. +- Chat WebSocket URLs are generated by the backend, so the frontend does not hardcode gateway addresses. +- Running `dev-backend` alone is mainly useful for backend work or when `backend/dist` already contains a built frontend. + +### Build The Standalone Launcher Binary + +From `web/`: ```bash make build ``` -The output binary is `backend/picoclaw-web`. +This: -### Other Commands +1. Installs frontend dependencies when needed. +2. Builds the frontend into `backend/dist`. +3. Embeds those assets into the Go backend. +4. Produces `build/picoclaw-launcher`. + +Override the output path if needed: ```bash -make test # Run backend tests and frontend lint -make lint # Run go vet and prettier/eslint -make clean # Remove all build artifacts +make build OUTPUT=/tmp/picoclaw-launcher ``` + +From the repository root you can also use: + +```bash +make build-launcher +``` + +That writes the platform-specific launcher to: + +```text +build/picoclaw-launcher-- +``` + +and refreshes the `build/picoclaw-launcher` symlink. + +### Frontend-Only Builds + +For frontend work there are two useful package scripts: + +```bash +cd frontend +pnpm build +pnpm build:backend +``` + +- `pnpm build` writes a normal Vite build to `frontend/dist` +- `pnpm build:backend` writes the embeddable build to `../backend/dist` + +### Run The Built Launcher + +Examples: + +```bash +./build/picoclaw-launcher +./build/picoclaw-launcher -console +./build/picoclaw-launcher -public +./build/picoclaw-launcher -port 19999 /path/to/config.json +``` + +Current launcher flags: + +- `-port` +- `-public` +- `-no-browser` +- `-lang` +- `-console` + +## Make Targets + +From `web/`: + +```bash +make dev +make dev-frontend +make dev-backend +make build +make build-frontend +make test +make lint +make clean +``` + +What they do today: + +- `make build-frontend` + - Runs `pnpm install --frozen-lockfile` when dependencies are missing or stale. + - Builds the embeddable frontend into `backend/dist`. +- `make test` + - Runs backend Go tests. + - Runs frontend `pnpm lint`. +- `make lint` + - Runs backend `go vet`. + - Runs frontend `pnpm check`. + - `pnpm check` currently formats files with Prettier and fixes lint issues with ESLint, so this target can modify your working tree. +- `make clean` + - Removes `frontend/dist`, `backend/dist`, and `build/`, then recreates `backend/dist/.gitkeep`. + +## Directory Layout + +```text +web/ +├── backend/ +│ ├── api/ # REST API handlers and launcher runtime endpoints +│ ├── launcherconfig/ # launcher-config.json load/save/validation +│ ├── middleware/ # auth, content type, logging, CIDR allowlist +│ ├── model/ # Go data structures and logic wrappers +│ ├── utils/ # runtime helpers, onboarding, browser launch +│ ├── winres/ # Windows application resources +│ └── dist/ # embedded frontend build output +├── frontend/ +│ ├── src/api/ # browser API clients +│ ├── src/components/ # UI pages and shared components +│ ├── src/features/ # feature-specific state, controllers, and protocol helpers +│ ├── src/hooks/ # shared React hooks +│ ├── src/i18n/ # internationalization language packs +│ ├── src/lib/ # generic library utilities +│ ├── src/routes/ # TanStack file routes +│ ├── src/store/ # global state management +│ └── vite.config.ts # dev server and build config +├── Makefile +└── README.md +``` + +## Troubleshooting + +### You have to sign in again after the launcher restarts + +Existing dashboard sessions do not survive launcher restarts. +That is expected: each launcher process generates a new signed session value, so old cookies become invalid. + +To make re-login easier, set a stable token: + +```bash +export PICOCLAW_LAUNCHER_TOKEN="replace-with-a-long-random-token" +``` + +Notes: + +- a stable token does not preserve the old cookie-based session by itself +- when the launcher opens the browser automatically, it appends `?token=...` and signs in again automatically +- if you reopen the dashboard manually, use the same stable token on `/launcher-login` + +### "Start Gateway" stays disabled + +The launcher only allows gateway startup when the configured default model is usable. +Check these in the dashboard: + +- a default model is selected +- the model has credentials or OAuth state +- local models such as Ollama or vLLM are reachable + +### The launcher cannot find `picoclaw` + +Set the main binary explicitly: + +```bash +export PICOCLAW_BINARY=/absolute/path/to/picoclaw +``` + +This affects onboarding and gateway subprocess startup. + +### The backend starts but the UI is blank in development + +Use `make dev` for the normal workflow. +If you run only `make dev-backend`, either run `make dev-frontend` alongside it or build the embedded frontend first with `make build-frontend`. + +## Related Docs + +- Main project overview: [`../README.md`](../README.md) +- Configuration guide: [`../docs/configuration.md`](../docs/configuration.md) +- Providers: [`../docs/providers.md`](../docs/providers.md) +- Troubleshooting: [`../docs/troubleshooting.md`](../docs/troubleshooting.md) +- Official docs site: [docs.picoclaw.io](https://docs.picoclaw.io) From f07a8a89d5ea40d68f977cef10b6ab995c422d2c Mon Sep 17 00:00:00 2001 From: wenjie Date: Mon, 30 Mar 2026 15:29:43 +0800 Subject: [PATCH 40/55] chore(web): patch vulnerable frontend tooling dependencies (#2182) - upgrade Vite, ESLint, React plugin, and related frontend packages to secure versions - refresh the pnpm lockfile to pull in patched transitive dependencies - raise the required Node.js version to match the patched toolchain - update the web README with the new frontend runtime requirement --- web/README.md | 2 +- web/frontend/package.json | 11 +- web/frontend/pnpm-lock.yaml | 1157 +++++++++++++++-------------------- 3 files changed, 510 insertions(+), 660 deletions(-) diff --git a/web/README.md b/web/README.md index a3faa03d1..9fc7007e9 100644 --- a/web/README.md +++ b/web/README.md @@ -164,7 +164,7 @@ When public access is enabled: ### Prerequisites - Go `1.25+` -- Node.js `20+` +- Node.js 20.19+ or 22.13+ - `pnpm` On macOS, the `web` Makefile enables `CGO_ENABLED=1` so tray-enabled launcher builds work as expected. diff --git a/web/frontend/package.json b/web/frontend/package.json index 8053d1f2a..fc993451e 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", @@ -42,7 +45,7 @@ "wrap-ansi": "^10.0.0" }, "devDependencies": { - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@tailwindcss/typography": "^0.5.19", "@tanstack/router-plugin": "^1.164.0", "@trivago/prettier-plugin-sort-imports": "^6.0.2", @@ -50,8 +53,8 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^8.57.1", - "@vitejs/plugin-react": "^5.2.0", - "eslint": "^9.39.4", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.1.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", @@ -60,6 +63,6 @@ "prettier-plugin-tailwindcss": "^0.7.2", "typescript": "~5.9.3", "typescript-eslint": "^8.57.1", - "vite": "^7.3.1" + "vite": "^8.0.3" } } diff --git a/web/frontend/pnpm-lock.yaml b/web/frontend/pnpm-lock.yaml index edaf49ccc..36217d0ef 100644 --- a/web/frontend/pnpm-lock.yaml +++ b/web/frontend/pnpm-lock.yaml @@ -13,19 +13,19 @@ importers: version: 5.2.8 '@tabler/icons-react': specifier: ^3.40.0 - version: 3.40.0(react@19.2.4) + version: 3.41.1(react@19.2.4) '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)) '@tanstack/react-query': specifier: ^5.90.21 - version: 5.91.2(react@19.2.4) + version: 5.95.2(react@19.2.4) '@tanstack/react-router': specifier: ^1.167.0 - version: 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router-devtools': specifier: ^1.163.3 - version: 1.166.9(@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.167.5)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.166.11(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -37,13 +37,13 @@ importers: version: 1.11.20 i18next: specifier: ^25.8.14 - version: 25.8.20(typescript@5.9.3) + version: 25.10.10(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 jotai: specifier: ^2.18.1 - version: 2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4) + version: 2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -55,7 +55,7 @@ importers: version: 19.2.4(react@19.2.4) react-i18next: specifier: ^16.5.8 - version: 16.5.8(i18next@25.8.20(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.14)(react@19.2.4) @@ -73,7 +73,7 @@ importers: version: 4.0.1 shadcn: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(typescript@5.9.3) + version: 4.1.1(@types/node@25.5.0)(typescript@5.9.3) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -91,14 +91,14 @@ importers: version: 10.0.0 devDependencies: '@eslint/js': - specifier: ^9.39.4 - version: 9.39.4 + specifier: ^10.0.1 + version: 10.0.1(eslint@10.1.0(jiti@2.6.1)) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.2.2) '@tanstack/router-plugin': specifier: ^1.164.0 - version: 1.166.14(@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + version: 1.167.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 version: 6.0.2(prettier@3.8.1) @@ -113,22 +113,22 @@ importers: version: 19.2.3(@types/react@19.2.14) '@typescript-eslint/eslint-plugin': specifier: ^8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-react': - specifier: ^5.2.0 - version: 5.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)) eslint: - specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + specifier: ^10.1.0 + version: 10.1.0(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + version: 10.1.8(eslint@10.1.0(jiti@2.6.1)) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + version: 7.0.1(eslint@10.1.0(jiti@2.6.1)) eslint-plugin-react-refresh: specifier: ^0.4.26 - version: 0.4.26(eslint@9.39.4(jiti@2.6.1)) + version: 0.4.26(eslint@10.1.0(jiti@2.6.1)) globals: specifier: ^16.5.0 version: 16.5.0 @@ -143,10 +143,10 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.57.1 - version: 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + specifier: ^8.0.3 + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0) packages: @@ -255,18 +255,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.6': resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} @@ -295,8 +283,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@dotenvx/dotenvx@1.57.0': - resolution: {integrity: sha512-WsTEcqfHzKmLFZh3jLGd7o4iCkrIupp+qFH2FJUJtQXUh2GcOnLXD00DcrhlO4H8QSmaKnW9lugOEbrdpu25kA==} + '@dotenvx/dotenvx@1.59.1': + resolution: {integrity: sha512-Qg+meC+XFxliuVSDlEPkKnaUjdaJKK6FNx/Wwl2UxhQR8pyPIuLhMavsF7ePdB9qFZUWV1jEK3ckbJir/WmF4w==} hasBin: true '@ecies/ciphers@0.2.5': @@ -305,6 +293,15 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@esbuild/aix-ppc64@0.27.4': resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} @@ -471,33 +468,34 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.3': + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.3': + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.1.1': + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.3': + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.6.1': + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -590,8 +588,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@modelcontextprotocol/sdk@1.27.1': - resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + '@modelcontextprotocol/sdk@1.28.0': + resolution: {integrity: sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -604,6 +602,12 @@ packages: resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} engines: {node: '>=18'} + '@napi-rs/wasm-runtime@1.1.2': + resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} @@ -637,6 +641,9 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1327,133 +1334,100 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rolldown/pluginutils@1.0.0-rc.3': - resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} - cpu: [x64] - os: [win32] + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -1462,13 +1436,13 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@tabler/icons-react@3.40.0': - resolution: {integrity: sha512-oO5+6QCnna4a//mYubx4euZfECtzQZFDGsDMIdzZUhbdyBCT+3bRVFBPueGIcemWld4Vb/0UQ39C/cmGfGylAg==} + '@tabler/icons-react@3.41.1': + resolution: {integrity: sha512-kUgweE+DJtAlMZVIns1FTDdcbpRVnkK7ZpUOXmoxy3JAF0rSHj0TcP4VHF14+gMJGnF+psH2Zt26BLT6owetBA==} peerDependencies: react: '>= 16' - '@tabler/icons@3.40.0': - resolution: {integrity: sha512-V/Q4VgNPKubRTiLdmWjV/zscYcj5IIk+euicUtaVVqF6luSC9rDngYWgST5/yh3Mrg/mYUwRv1YVTk71Jp0twQ==} + '@tabler/icons@3.41.1': + resolution: {integrity: sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q==} '@tailwindcss/node@4.2.2': resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} @@ -1569,65 +1543,65 @@ packages: resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} engines: {node: '>=20.19'} - '@tanstack/query-core@5.91.2': - resolution: {integrity: sha512-Uz2pTgPC1mhqrrSGg18RKCWT/pkduAYtxbcyIyKBhw7dTWjXZIzqmpzO2lBkyWr4hlImQgpu1m1pei3UnkFRWw==} + '@tanstack/query-core@5.95.2': + resolution: {integrity: sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==} - '@tanstack/react-query@5.91.2': - resolution: {integrity: sha512-GClLPzbM57iFXv+FlvOUL56XVe00PxuTaVEyj1zAObhRiKF008J5vedmaq7O6ehs+VmPHe8+PUQhMuEyv8d9wQ==} + '@tanstack/react-query@5.95.2': + resolution: {integrity: sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==} peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.166.9': - resolution: {integrity: sha512-O49eZmaeEKB5YnKH/qd61AbxV/lW8ICm4stfZ4GNQNpzQQ6rhPIB0p3PMZDIgX+6DoMivdNvLRmXAOOpzpIpDg==} + '@tanstack/react-router-devtools@1.166.11': + resolution: {integrity: sha512-WYR3q4Xui5yPT/5PXtQh8i03iUA7q8dONBjWpV3nsGdM8Cs1FxpfhLstW0wZO1dOvSyElscwTRCJ6nO5N8r3Lg==} engines: {node: '>=20.19'} peerDependencies: - '@tanstack/react-router': ^1.167.2 - '@tanstack/router-core': ^1.167.2 + '@tanstack/react-router': ^1.168.2 + '@tanstack/router-core': ^1.168.2 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' peerDependenciesMeta: '@tanstack/router-core': optional: true - '@tanstack/react-router@1.167.5': - resolution: {integrity: sha512-s1nP6l/7BYZfSwhoNbB7/rUmZ07q/AvkmhBoiDQl3tgy5dpb9Q1qjtIapYdvCOrao1aA/QCaWqxcbGc2Ct1bvQ==} + '@tanstack/react-router@1.168.8': + resolution: {integrity: sha512-t0S0QueXubBKmI9eLPcN/A1sLQgTu8/yHerjrvvsGeD12zMdw0uJPKwEKpStQF2OThQtw64cs34uUSYXBUTSNw==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-store@0.9.2': - resolution: {integrity: sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ==} + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.167.5': - resolution: {integrity: sha512-8fRgJ0zNJf77R4grCaJQ5Imatjyc4YT5v8rlsPkYYYeUlcFNLbuFRhLlAMdND9gRUMznpnbRDXngpTPgx2K7HQ==} + '@tanstack/router-core@1.168.7': + resolution: {integrity: sha512-z4UEdlzMrFaKBsG4OIxlZEm+wsYBtEp//fnX6kW18jhQpETNcM6u2SXNdX+bcIYp6AaR7ERS3SBENzjC/xxwQQ==} engines: {node: '>=20.19'} hasBin: true - '@tanstack/router-devtools-core@1.166.9': - resolution: {integrity: sha512-PNlA7GmOUX9wY7LUG709Pk3Lg33dfHBztQwzjzrOiOsuf4ggp2R6bwarF8nYGNjG79z/MaB5PN+5yvkCVk8jGw==} + '@tanstack/router-devtools-core@1.167.1': + resolution: {integrity: sha512-ECMM47J4KmifUvJguGituSiBpfN8SyCUEoxQks5RY09hpIBfR2eswCv2e6cJimjkKwBQXOVTPkTUk/yRvER+9w==} engines: {node: '>=20.19'} peerDependencies: - '@tanstack/router-core': ^1.167.2 + '@tanstack/router-core': ^1.168.2 csstype: ^3.0.10 peerDependenciesMeta: csstype: optional: true - '@tanstack/router-generator@1.166.13': - resolution: {integrity: sha512-ALxSs6OzimiSgpOuIm+AXmc7eUx/oGPwSPpdQbpZ/kX7WHRh6qM7lv8DAN0K3jWcBpzF8eeOIdryWryX8gH+Yg==} + '@tanstack/router-generator@1.166.22': + resolution: {integrity: sha512-wQ7H8/Q2rmSPuaxWnurJ3DATNnqWV2tajxri9TSiW4QHsG7cWPD34+goeIinKG+GajJyEdfVpz6w/gRJXfbAPw==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.166.14': - resolution: {integrity: sha512-hypyj0qlsAbJf60/glmVYqSVwnRB4hKRrMCUsSXjrPdO2g6gs3z6xHmcWsHQ831C4G9+bSFEK9Uy5EjO3A4THQ==} + '@tanstack/router-plugin@1.167.9': + resolution: {integrity: sha512-h/VV05FEHd4PVyc5Zy8B3trWLcdLt/Pmp+mfifmBKGRw+MUtvdQKbBHhmy4ouOf67s5zDJMc+n8R3xgU7bDwFA==} engines: {node: '>=20.19'} hasBin: true peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.167.5 + '@tanstack/react-router': ^1.168.8 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -1647,8 +1621,8 @@ packages: resolution: {integrity: sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==} engines: {node: '>=20.19'} - '@tanstack/store@0.9.2': - resolution: {integrity: sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA==} + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} '@tanstack/virtual-file-routes@1.161.7': resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} @@ -1677,21 +1651,15 @@ packages: '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -1733,73 +1701,80 @@ packages: '@types/validate-npm-package-name@4.0.2': resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} - '@typescript-eslint/eslint-plugin@8.57.1': - resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==} + '@typescript-eslint/eslint-plugin@8.57.2': + resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.57.1 + '@typescript-eslint/parser': ^8.57.2 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.57.1': - resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==} + '@typescript-eslint/parser@8.57.2': + resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.57.1': - resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} + '@typescript-eslint/project-service@8.57.2': + resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.57.1': - resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} + '@typescript-eslint/scope-manager@8.57.2': + resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.57.1': - resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} + '@typescript-eslint/tsconfig-utils@8.57.2': + resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.57.1': - resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==} + '@typescript-eslint/type-utils@8.57.2': + resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.57.1': - resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} + '@typescript-eslint/types@8.57.2': + resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.57.1': - resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} + '@typescript-eslint/typescript-estree@8.57.2': + resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.57.1': - resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==} + '@typescript-eslint/utils@8.57.2': + resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.57.1': - resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} + '@typescript-eslint/visitor-keys@8.57.2': + resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-react@5.2.0': - resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -1881,8 +1856,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.9: - resolution: {integrity: sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==} + baseline-browser-mapping@2.10.12: + resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1894,14 +1869,11 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -1933,16 +1905,12 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001780: - resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} + caniuse-lite@1.0.30001782: + resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -2007,9 +1975,6 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -2125,8 +2090,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} dotenv@17.3.1: @@ -2144,8 +2109,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.321: - resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} + electron-to-chromium@1.5.328: + resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2221,25 +2186,21 @@ packages: peerDependencies: eslint: '>=8.40' - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.1.0: + resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -2247,9 +2208,9 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -2430,8 +2391,8 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -2441,10 +2402,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} @@ -2461,14 +2418,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql@16.13.1: - resolution: {integrity: sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==} + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -2510,8 +2463,8 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.12.8: - resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} + hono@4.12.9: + resolution: {integrity: sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==} engines: {node: '>=16.9.0'} html-parse-stringify@3.0.1: @@ -2542,10 +2495,10 @@ packages: i18next-browser-languagedetector@8.2.1: resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} - i18next@25.8.20: - resolution: {integrity: sha512-xjo9+lbX/P1tQt3xpO2rfJiBppNfUnNIPKgCvNsTKsvTOCro1Qr/geXVg1N47j5ScOSaXAPq8ET93raK3Rr06A==} + i18next@25.10.10: + resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==} peerDependencies: - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: typescript: optional: true @@ -2696,8 +2649,8 @@ packages: jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} - jotai@2.18.1: - resolution: {integrity: sha512-e0NOzK+yRFwHo7DOp0DS0Ycq74KMEAObDWFGmfEL28PD9nLqBTt3/Ug7jf9ca72x0gC9LQZG9zH+0ISICmy3iA==} + jotai@2.19.0: + resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==} engines: {node: '>=12.20.0'} peerDependencies: '@babel/core': '>=7.0.0' @@ -2847,9 +2800,6 @@ packages: lodash-es@4.17.23: resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} @@ -3038,9 +2988,6 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.9: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} @@ -3051,8 +2998,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.13: - resolution: {integrity: sha512-9CV2mXT9+z0J26MQDfEZZkj/psJ5Er/w0w+t95FWdaGH/DTlhNZBx8vBO5jSYv8AZEnl3ouX+AaTT68KXdAIag==} + msw@2.12.14: + resolution: {integrity: sha512-4KXa4nVBIBjbDbd7vfQNuQ25eFxug0aropCQFoI0JdOBuJWamkT1yLVIWReFI8SiTRc+H1hKzaNk+cLk2N9rtQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -3197,8 +3144,8 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.0: + resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3206,12 +3153,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pkce-challenge@5.0.1: @@ -3350,14 +3297,14 @@ packages: peerDependencies: react: ^19.2.4 - react-i18next@16.5.8: - resolution: {integrity: sha512-2ABeHHlakxVY+LSirD+OiERxFL6+zip0PaHo979bgwzeHg27Sqc82xxXWIrSFmfWX0ZkrvXMHwhsi/NGUf5VQg==} + react-i18next@16.6.6: + resolution: {integrity: sha512-ZgL2HUoW34UKUkOV7uSQFE1CDnRPD+tCR3ywSuWH7u2iapnz86U8Bi3Vrs620qNDzCf1F47NxglCEkchCTDOHw==} peerDependencies: - i18next: '>= 25.6.2' + i18next: '>= 25.10.9' react: '>= 16.8.0' react-dom: '*' react-native: '*' - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: react-dom: optional: true @@ -3372,10 +3319,6 @@ packages: '@types/react': '>=18' react: '>=18' - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} - engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -3468,9 +3411,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true router@2.2.0: @@ -3520,8 +3463,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.1.0: - resolution: {integrity: sha512-3zETJ+0Ezj69FS6RL0HOkLKKAR5yXisXx1iISJdfLQfrUqj/VIQlanQi1Ukk+9OE+XHZVj4FQNTBSfbr2CyCYg==} + shadcn@4.1.1: + resolution: {integrity: sha512-nBj+7LYC9kzV9v9QmRPpoOhfW4KctJVQejywdAt/K+K+z4RYlJOcO2a4AaF7elrRWkfCbgXeGK02liV0KB9HvQ==} hasBin: true shebang-command@2.0.0: @@ -3629,20 +3572,12 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -3653,25 +3588,22 @@ packages: tailwindcss@4.2.2: resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tldts-core@7.0.26: - resolution: {integrity: sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew==} + tldts-core@7.0.27: + resolution: {integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==} - tldts@7.0.26: - resolution: {integrity: sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==} + tldts@7.0.27: + resolution: {integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==} hasBin: true to-regex-range@5.0.1: @@ -3728,8 +3660,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typescript-eslint@8.57.1: - resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} + typescript-eslint@8.57.2: + resolution: {integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -3861,15 +3793,16 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + vite@8.0.3: + resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 jiti: '>=1.21.0' less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -3880,12 +3813,14 @@ packages: peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -3975,10 +3910,10 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zod-to-json-schema@3.25.1: - resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: - zod: ^3.25 || ^4 + zod: ^3.25.28 || ^4 zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} @@ -4138,16 +4073,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -4195,22 +4120,38 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@dotenvx/dotenvx@1.57.0': + '@dotenvx/dotenvx@1.59.1': dependencies: commander: 11.1.0 dotenv: 17.3.1 eciesjs: 0.4.18 execa: 5.1.1 - fdir: 6.5.0(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.4) ignore: 5.3.2 object-treeify: 1.1.33 - picomatch: 4.0.3 + picomatch: 4.0.4 which: 4.0.0 '@ecies/ciphers@0.2.5(@noble/ciphers@1.3.0)': dependencies: '@noble/ciphers': 1.3.0 + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.27.4': optional: true @@ -4289,50 +4230,38 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@2.6.1))': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.23.3': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.3 debug: 4.4.3 - minimatch: 3.1.5 + minimatch: 10.2.4 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.5.3': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.1.1 - '@eslint/core@0.17.0': + '@eslint/core@1.1.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': + '@eslint/js@10.0.1(eslint@10.1.0(jiti@2.6.1))': + optionalDependencies: + eslint: 10.1.0(jiti@2.6.1) + + '@eslint/object-schema@3.0.3': {} + + '@eslint/plugin-kit@0.6.1': dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} - - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.1.1 levn: 0.4.1 '@floating-ui/core@1.7.5': @@ -4354,9 +4283,9 @@ snapshots: '@fontsource-variable/inter@5.2.8': {} - '@hono/node-server@1.19.11(hono@4.12.8)': + '@hono/node-server@1.19.11(hono@4.12.9)': dependencies: - hono: 4.12.8 + hono: 4.12.9 '@humanfs/core@0.19.1': {} @@ -4416,9 +4345,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@modelcontextprotocol/sdk@1.27.1(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.28.0(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.11(hono@4.12.8) + '@hono/node-server': 1.19.11(hono@4.12.9) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -4428,13 +4357,13 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.8 + hono: 4.12.9 jose: 6.2.2 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - supports-color @@ -4447,6 +4376,13 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@noble/ciphers@1.3.0': {} '@noble/curves@1.9.7': @@ -4476,6 +4412,8 @@ snapshots: '@open-draft/until@2.1.0': {} + '@oxc-project/types@0.122.0': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -5223,93 +5161,70 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/pluginutils@1.0.0-rc.3': {} - - '@rollup/rollup-android-arm-eabi@4.59.0': + '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-android-arm64@4.59.0': + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-darwin-arm64@4.59.0': + '@rolldown/binding-darwin-x64@1.0.0-rc.12': optional: true - '@rollup/rollup-darwin-x64@4.59.0': + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': optional: true - '@rollup/rollup-freebsd-arm64@4.59.0': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': optional: true - '@rollup/rollup-freebsd-x64@4.59.0': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.59.0': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm64-gnu@4.59.0': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm64-musl@4.59.0': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-loong64-gnu@4.59.0': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-loong64-musl@4.59.0': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.59.0': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' optional: true - '@rollup/rollup-linux-ppc64-musl@4.59.0': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.59.0': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-riscv64-musl@4.59.0': - optional: true + '@rolldown/pluginutils@1.0.0-rc.12': {} - '@rollup/rollup-linux-s390x-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.59.0': - optional: true - - '@rollup/rollup-openbsd-x64@4.59.0': - optional: true - - '@rollup/rollup-openharmony-arm64@4.59.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.59.0': - optional: true + '@rolldown/pluginutils@1.0.0-rc.7': {} '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} - '@tabler/icons-react@3.40.0(react@19.2.4)': + '@tabler/icons-react@3.41.1(react@19.2.4)': dependencies: - '@tabler/icons': 3.40.0 + '@tabler/icons': 3.41.1 react: 19.2.4 - '@tabler/icons@3.40.0': {} + '@tabler/icons@3.41.1': {} '@tailwindcss/node@4.2.2': dependencies: @@ -5377,73 +5292,67 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.2 - '@tailwindcss/vite@4.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0) '@tanstack/history@1.161.6': {} - '@tanstack/query-core@5.91.2': {} + '@tanstack/query-core@5.95.2': {} - '@tanstack/react-query@5.91.2(react@19.2.4)': + '@tanstack/react-query@5.95.2(react@19.2.4)': dependencies: - '@tanstack/query-core': 5.91.2 + '@tanstack/query-core': 5.95.2 react: 19.2.4 - '@tanstack/react-router-devtools@1.166.9(@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.167.5)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router-devtools@1.166.11(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/react-router': 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-devtools-core': 1.166.9(@tanstack/router-core@1.167.5)(csstype@3.2.3) + '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-devtools-core': 1.167.1(@tanstack/router-core@1.168.7)(csstype@3.2.3) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@tanstack/router-core': 1.167.5 + '@tanstack/router-core': 1.168.7 transitivePeerDependencies: - csstype - '@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.161.6 - '@tanstack/react-store': 0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.167.5 + '@tanstack/react-store': 0.9.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-core': 1.168.7 isbot: 5.1.36 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/react-store@0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-store@0.9.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/store': 0.9.2 + '@tanstack/store': 0.9.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@tanstack/router-core@1.167.5': + '@tanstack/router-core@1.168.7': dependencies: '@tanstack/history': 1.161.6 - '@tanstack/store': 0.9.2 cookie-es: 2.0.0 seroval: 1.5.1 seroval-plugins: 1.5.1(seroval@1.5.1) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.166.9(@tanstack/router-core@1.167.5)(csstype@3.2.3)': + '@tanstack/router-devtools-core@1.167.1(@tanstack/router-core@1.168.7)(csstype@3.2.3)': dependencies: - '@tanstack/router-core': 1.167.5 + '@tanstack/router-core': 1.168.7 clsx: 2.1.1 goober: 2.1.18(csstype@3.2.3) - tiny-invariant: 1.3.3 optionalDependencies: csstype: 3.2.3 - '@tanstack/router-generator@1.166.13': + '@tanstack/router-generator@1.166.22': dependencies: - '@tanstack/router-core': 1.167.5 + '@tanstack/router-core': 1.168.7 '@tanstack/router-utils': 1.161.6 '@tanstack/virtual-file-routes': 1.161.7 prettier: 3.8.1 @@ -5454,7 +5363,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.166.14(@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + '@tanstack/router-plugin@1.167.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -5462,16 +5371,16 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/router-core': 1.167.5 - '@tanstack/router-generator': 1.166.13 + '@tanstack/router-core': 1.168.7 + '@tanstack/router-generator': 1.166.22 '@tanstack/router-utils': 1.161.6 '@tanstack/virtual-file-routes': 1.161.7 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) + '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0) transitivePeerDependencies: - supports-color @@ -5483,13 +5392,13 @@ snapshots: '@babel/types': 7.29.0 ansis: 4.2.0 babel-dead-code-elimination: 1.0.12 - diff: 8.0.3 + diff: 8.0.4 pathe: 2.0.3 tinyglobby: 0.2.15 transitivePeerDependencies: - supports-color - '@tanstack/store@0.9.2': {} + '@tanstack/store@0.9.3': {} '@tanstack/virtual-file-routes@1.161.7': {} @@ -5513,31 +5422,17 @@ snapshots: minimatch: 10.2.4 path-browserify: 1.0.1 - '@types/babel__core@7.20.5': + '@tybys/wasm-util@0.10.1': dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 + tslib: 2.8.1 + optional: true '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 + '@types/esrecurse@4.3.1': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -5576,15 +5471,15 @@ snapshots: '@types/validate-npm-package-name@4.0.2': {} - '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.1 - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/type-utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 + eslint: 10.1.0(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -5592,56 +5487,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.1 + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.57.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.57.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) - '@typescript-eslint/types': 8.57.1 + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.57.1': + '@typescript-eslint/scope-manager@8.57.2': dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 - '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.57.1': {} + '@typescript-eslint/types@8.57.2': {} - '@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 + '@typescript-eslint/project-service': 8.57.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 debug: 4.4.3 minimatch: 10.2.4 semver: 7.7.4 @@ -5651,35 +5546,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + eslint: 10.1.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.57.1': + '@typescript-eslint/visitor-keys@8.57.2': dependencies: - '@typescript-eslint/types': 8.57.1 + '@typescript-eslint/types': 8.57.2 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@5.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))': + '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-rc.3 - '@types/babel__core': 7.20.5 - react-refresh: 0.18.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) - transitivePeerDependencies: - - supports-color + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0) accepts@2.0.0: dependencies: @@ -5727,7 +5615,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 argparse@2.0.1: {} @@ -5754,7 +5642,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.9: {} + baseline-browser-mapping@2.10.12: {} binary-extensions@2.3.0: {} @@ -5772,16 +5660,11 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: + brace-expansion@2.0.3: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.4: + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -5791,9 +5674,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.10.9 - caniuse-lite: 1.0.30001780 - electron-to-chromium: 1.5.321 + baseline-browser-mapping: 2.10.12 + caniuse-lite: 1.0.30001782 + electron-to-chromium: 1.5.328 node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -5815,15 +5698,10 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001780: {} + caniuse-lite@1.0.30001782: {} ccount@2.0.1: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -5880,8 +5758,6 @@ snapshots: commander@14.0.3: {} - concat-map@0.0.1: {} - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -5959,7 +5835,7 @@ snapshots: dependencies: dequal: 2.0.3 - diff@8.0.3: {} + diff@8.0.4: {} dotenv@17.3.1: {} @@ -5978,7 +5854,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.321: {} + electron-to-chromium@1.5.328: {} emoji-regex@10.6.0: {} @@ -5989,7 +5865,7 @@ snapshots: enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.2 entities@6.0.1: {} @@ -6044,58 +5920,55 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) - eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@2.6.1)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.4.26(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react-refresh@0.4.26(eslint@10.1.0(jiti@2.6.1)): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.1.0(jiti@2.6.1) - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.6.1): + eslint@10.1.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 + '@eslint/config-array': 0.23.3 + '@eslint/config-helpers': 0.5.3 + '@eslint/core': 1.1.1 + '@eslint/plugin-kit': 0.6.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 ajv: 6.14.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -6106,8 +5979,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.4 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -6115,11 +5987,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.2.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 + eslint-visitor-keys: 5.0.1 esprima@4.0.1: {} @@ -6232,9 +6104,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 fetch-blob@3.2.0: dependencies: @@ -6332,7 +6204,7 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-tsconfig@4.13.6: + get-tsconfig@4.13.7: dependencies: resolve-pkg-maps: 1.0.0 @@ -6344,8 +6216,6 @@ snapshots: dependencies: is-glob: 4.0.3 - globals@14.0.0: {} - globals@16.5.0: {} goober@2.1.18(csstype@3.2.3): @@ -6356,9 +6226,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql@16.13.1: {} - - has-flag@4.0.0: {} + graphql@16.13.2: {} has-symbols@1.1.0: {} @@ -6453,7 +6321,7 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.12.8: {} + hono@4.12.9: {} html-parse-stringify@3.0.1: dependencies: @@ -6486,7 +6354,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next@25.8.20(typescript@5.9.3): + i18next@25.10.10(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: @@ -6586,7 +6454,7 @@ snapshots: jose@6.2.2: {} - jotai@2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4): + jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4): optionalDependencies: '@babel/core': 7.29.0 '@babel/template': 7.28.6 @@ -6691,8 +6559,6 @@ snapshots: lodash-es@4.17.23: {} - lodash.merge@4.6.2: {} - log-symbols@6.0.0: dependencies: chalk: 5.6.2 @@ -7067,7 +6933,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.54.0: {} @@ -7081,28 +6947,24 @@ snapshots: minimatch@10.2.4: dependencies: - brace-expansion: 5.0.4 - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.12 + brace-expansion: 5.0.5 minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.0.3 minimist@1.2.8: {} ms@2.1.3: {} - msw@2.12.13(@types/node@25.5.0)(typescript@5.9.3): + msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3): dependencies: '@inquirer/confirm': 5.1.21(@types/node@25.5.0) '@mswjs/interceptors': 0.41.3 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 cookie: 1.1.1 - graphql: 16.13.1 + graphql: 16.13.2 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -7256,15 +7118,15 @@ snapshots: path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.0: {} pathe@2.0.3: {} picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pkce-challenge@5.0.1: {} @@ -7397,11 +7259,11 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 - react-i18next@16.5.8(i18next@25.8.20(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.8.20(typescript@5.9.3) + i18next: 25.10.10(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: @@ -7426,8 +7288,6 @@ snapshots: transitivePeerDependencies: - supports-color - react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 @@ -7468,7 +7328,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 recast@0.23.11: dependencies: @@ -7540,36 +7400,29 @@ snapshots: reusify@1.1.0: {} - rollup@4.59.0: + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): dependencies: - '@types/estree': 1.0.8 + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 - fsevents: 2.3.3 + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' router@2.2.0: dependencies: @@ -7577,7 +7430,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.0 transitivePeerDependencies: - supports-color @@ -7628,28 +7481,28 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.1.0(@types/node@25.5.0)(typescript@5.9.3): + shadcn@4.1.1(@types/node@25.5.0)(typescript@5.9.3): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@dotenvx/dotenvx': 1.57.0 - '@modelcontextprotocol/sdk': 1.27.1(zod@3.25.76) + '@dotenvx/dotenvx': 1.59.1 + '@modelcontextprotocol/sdk': 1.28.0(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.28.1 commander: 14.0.3 cosmiconfig: 9.0.1(typescript@5.9.3) dedent: 1.7.2 deepmerge: 4.3.1 - diff: 8.0.3 + diff: 8.0.4 execa: 9.6.1 fast-glob: 3.3.3 fs-extra: 11.3.4 fuzzysort: 3.1.0 https-proxy-agent: 7.0.6 kleur: 4.1.5 - msw: 2.12.13(@types/node@25.5.0)(typescript@5.9.3) + msw: 2.12.14(@types/node@25.5.0)(typescript@5.9.3) node-fetch: 3.3.2 open: 11.0.0 ora: 8.2.0 @@ -7663,7 +7516,7 @@ snapshots: tsconfig-paths: 4.2.0 validate-npm-package-name: 7.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - '@cfworker/json-schema' - '@types/node' @@ -7772,8 +7625,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@3.1.1: {} - style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -7782,32 +7633,26 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - tagged-tag@1.0.0: {} tailwind-merge@3.5.0: {} tailwindcss@4.2.2: {} - tapable@2.3.0: {} + tapable@2.3.2: {} tiny-invariant@1.3.3: {} - tiny-warning@1.0.3: {} - tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 - tldts-core@7.0.26: {} + tldts-core@7.0.27: {} - tldts@7.0.26: + tldts@7.0.27: dependencies: - tldts-core: 7.0.26 + tldts-core: 7.0.27 to-regex-range@5.0.1: dependencies: @@ -7817,7 +7662,7 @@ snapshots: tough-cookie@6.0.1: dependencies: - tldts: 7.0.26 + tldts: 7.0.27 trim-lines@3.0.1: {} @@ -7843,7 +7688,7 @@ snapshots: tsx@4.21.0: dependencies: esbuild: 0.27.4 - get-tsconfig: 4.13.6 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3 @@ -7863,13 +7708,13 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript-eslint@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 10.1.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7921,7 +7766,7 @@ snapshots: dependencies: '@jridgewell/remapping': 2.3.5 acorn: 8.16.0 - picomatch: 4.0.3 + picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 until-async@3.0.2: {} @@ -7995,20 +7840,22 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0): dependencies: - esbuild: 0.27.4 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + lightningcss: 1.32.0 + picomatch: 4.0.4 postcss: 8.5.8 - rollup: 4.59.0 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.5.0 + esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.32.0 tsx: 4.21.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' void-elements@3.1.0: {} @@ -8075,7 +7922,7 @@ snapshots: yoctocolors@2.1.2: {} - zod-to-json-schema@3.25.1(zod@3.25.76): + zod-to-json-schema@3.25.2(zod@3.25.76): dependencies: zod: 3.25.76 From d844bf3683dbc420a3b27959655bc9fe3281ec60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:47:17 +0800 Subject: [PATCH 41/55] build(deps): bump github.com/github/copilot-sdk/go from 0.1.32 to 0.2.0 (#2058) Bumps [github.com/github/copilot-sdk/go](https://github.com/github/copilot-sdk) from 0.1.32 to 0.2.0. - [Release notes](https://github.com/github/copilot-sdk/releases) - [Changelog](https://github.com/github/copilot-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/copilot-sdk/compare/v0.1.32...v0.2.0) --- updated-dependencies: - dependency-name: github.com/github/copilot-sdk/go dependency-version: 0.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 +++++++- go.sum | 23 +++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 202839c29..04f065529 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,8 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/gdamore/encoding v1.0.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect @@ -82,6 +84,10 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect go.mau.fi/libsignal v0.2.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/text v0.35.0 // indirect modernc.org/libc v1.67.6 // indirect @@ -95,7 +101,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/github/copilot-sdk/go v0.1.32 + github.com/github/copilot-sdk/go v0.2.0 github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/jsonschema-go v0.4.2 // indirect diff --git a/go.sum b/go.sum index c64f3593d..3fd1a0235 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,13 @@ github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uh github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU= github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= -github.com/github/copilot-sdk/go v0.1.32 h1:wc9SFWwxXhJts6vyzzboPLJqcEJGnHE8rMCAY1RrUgo= -github.com/github/copilot-sdk/go v0.1.32/go.mod h1:qc2iEF7hdO8kzSvbyGvrcGhuk2fzdW4xTtT0+1EH2ts= +github.com/github/copilot-sdk/go v0.2.0 h1:RnrIIirmtp4wGgqSQFJ2k9phbeveIxOtYZqDogoNEa0= +github.com/github/copilot-sdk/go v0.2.0/go.mod h1:uGWkjVYcp2DV9DgtqYihh5tEoJjNqxIFaUNnrwY4FxM= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= @@ -157,8 +162,9 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -209,8 +215,9 @@ github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoX github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= @@ -277,6 +284,14 @@ go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg= go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 h1:hsmlwsM+VqfF70cpdZEeIUKer2XWCQmQPK0u0tHy3ZQ= go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= From 74dfd9364c2e258806edd8eafbbc8e9534b5eefc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:51:19 +0800 Subject: [PATCH 42/55] build(deps): bump golang.org/x/time from 0.14.0 to 0.15.0 (#2059) Bumps [golang.org/x/time](https://github.com/golang/time) from 0.14.0 to 0.15.0. - [Commits](https://github.com/golang/time/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: golang.org/x/time dependency-version: 0.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 04f065529..5c0fcbd71 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 golang.org/x/oauth2 v0.36.0 golang.org/x/term v0.41.0 - golang.org/x/time v0.14.0 + golang.org/x/time v0.15.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mautrix v0.26.4 diff --git a/go.sum b/go.sum index 3fd1a0235..645bf2db7 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From fd9914dd92168af4a168799ede92fc26832c30a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:58:04 +0800 Subject: [PATCH 43/55] build(deps): bump github.com/aws/aws-sdk-go-v2/service/bedrockruntime (#2061) Bumps [github.com/aws/aws-sdk-go-v2/service/bedrockruntime](https://github.com/aws/aws-sdk-go-v2) from 1.50.2 to 1.50.3. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.50.2...service/s3/v1.50.3) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/bedrockruntime dependency-version: 1.50.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 5c0fcbd71..14be4420b 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,10 @@ require ( github.com/BurntSushi/toml v1.6.0 github.com/adhocore/gronx v1.19.6 github.com/anthropics/anthropic-sdk-go v1.26.0 - github.com/aws/aws-sdk-go-v2 v1.41.4 + github.com/atotto/clipboard v0.1.4 + github.com/aws/aws-sdk-go-v2 v1.41.5 github.com/aws/aws-sdk-go-v2/config v1.32.12 - github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2 + github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4 github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.4.0 github.com/creack/pty v1.1.24 @@ -46,12 +47,11 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect diff --git a/go.sum b/go.sum index 645bf2db7..4ceda90d4 100644 --- a/go.sum +++ b/go.sum @@ -19,24 +19,24 @@ github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAf github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= -github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= -github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2 h1:x0eGAWpd1B5I/vMtrB4Q4Zuc3CXWI8wjHfPPqBSrKmM= -github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2/go.mod h1:V9oTWSDC2MtS1DR71hbNET/bZ8psQp022amEBe1grJc= +github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4 h1:W6tKfa/s37faUnwJ71pGqsBO7/wfUX1L7tVprupQGo4= +github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4/go.mod h1:BZ+9thH0QOTDUwE8KAv/ZwUzsNC7CSMJXj/wtnZMs5k= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= From 5c6e13e188e617a390e28caa220d0629256e68e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:05:11 +0800 Subject: [PATCH 44/55] build(deps): bump modernc.org/sqlite from 1.46.1 to 1.47.0 (#2063) Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.46.1 to 1.47.0. - [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.46.1...v1.47.0) --- updated-dependencies: - dependency-name: modernc.org/sqlite dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 14be4420b..7d242d498 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mautrix v0.26.4 - modernc.org/sqlite v1.46.1 + modernc.org/sqlite v1.47.0 rsc.io/qr v0.2.0 ) @@ -90,7 +90,7 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/text v0.35.0 // indirect - modernc.org/libc v1.67.6 // indirect + modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 4ceda90d4..76d1b46c7 100644 --- a/go.sum +++ b/go.sum @@ -422,18 +422,18 @@ maunium.net/go/mautrix v0.26.4 h1:enHSnkf0L2V9+VnfJfNhKSReSW6pBKS/x3Su+v+Vovs= maunium.net/go/mautrix v0.26.4/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= -modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -442,8 +442,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 7dc0d02a5e51933c799ccd1bbc5e1913324a9493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:14:20 +0800 Subject: [PATCH 45/55] build(deps): bump i18next from 25.8.20 to 25.10.10 in /web/frontend (#2065) Bumps [i18next](https://github.com/i18next/i18next) from 25.8.20 to 25.10.10. - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v25.8.20...v25.10.10) --- updated-dependencies: - dependency-name: i18next dependency-version: 25.10.10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- web/frontend/package.json | 2 +- web/frontend/pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/frontend/package.json b/web/frontend/package.json index fc993451e..426989c38 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -25,7 +25,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.20", - "i18next": "^25.8.14", + "i18next": "^26.0.1", "i18next-browser-languagedetector": "^8.2.1", "jotai": "^2.18.1", "radix-ui": "^1.4.3", diff --git a/web/frontend/pnpm-lock.yaml b/web/frontend/pnpm-lock.yaml index 36217d0ef..f72a50892 100644 --- a/web/frontend/pnpm-lock.yaml +++ b/web/frontend/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^1.11.20 version: 1.11.20 i18next: - specifier: ^25.8.14 - version: 25.10.10(typescript@5.9.3) + specifier: ^26.0.1 + version: 26.0.1(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 @@ -55,7 +55,7 @@ importers: version: 19.2.4(react@19.2.4) react-i18next: specifier: ^16.5.8 - version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.6.6(i18next@26.0.1(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.14)(react@19.2.4) @@ -2495,8 +2495,8 @@ packages: i18next-browser-languagedetector@8.2.1: resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} - i18next@25.10.10: - resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==} + i18next@26.0.1: + resolution: {integrity: sha512-vtz5sXU4+nkCm8yEU+JJ6yYIx0mkg9e68W0G0PXpnOsmzLajNsW5o28DJMqbajxfsfq0gV3XdrBudsDQnwxfsQ==} peerDependencies: typescript: ^5 || ^6 peerDependenciesMeta: @@ -6354,7 +6354,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next@25.10.10(typescript@5.9.3): + i18next@26.0.1(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: @@ -7259,11 +7259,11 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 - react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.6.6(i18next@26.0.1(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.10.10(typescript@5.9.3) + i18next: 26.0.1(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: From 5e1b6a397157b945ef953e51767febba7b8c1cf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:18:08 +0800 Subject: [PATCH 46/55] build(deps-dev): bump globals from 16.5.0 to 17.4.0 in /web/frontend (#2067) Bumps [globals](https://github.com/sindresorhus/globals) from 16.5.0 to 17.4.0. - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v16.5.0...v17.4.0) --- updated-dependencies: - dependency-name: globals dependency-version: 17.4.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- web/frontend/package.json | 2 +- web/frontend/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/frontend/package.json b/web/frontend/package.json index 426989c38..906425b58 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -58,7 +58,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", - "globals": "^16.5.0", + "globals": "^17.4.0", "prettier": "^3.8.1", "prettier-plugin-tailwindcss": "^0.7.2", "typescript": "~5.9.3", diff --git a/web/frontend/pnpm-lock.yaml b/web/frontend/pnpm-lock.yaml index f72a50892..abb906c81 100644 --- a/web/frontend/pnpm-lock.yaml +++ b/web/frontend/pnpm-lock.yaml @@ -130,8 +130,8 @@ importers: specifier: ^0.4.26 version: 0.4.26(eslint@10.1.0(jiti@2.6.1)) globals: - specifier: ^16.5.0 - version: 16.5.0 + specifier: ^17.4.0 + version: 17.4.0 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -2402,8 +2402,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} goober@2.1.18: @@ -6216,7 +6216,7 @@ snapshots: dependencies: is-glob: 4.0.3 - globals@16.5.0: {} + globals@17.4.0: {} goober@2.1.18(csstype@3.2.3): dependencies: From 5e7545a22a9bb9c252b5db6e7deecb8695f382a3 Mon Sep 17 00:00:00 2001 From: mattn Date: Mon, 30 Mar 2026 17:30:25 +0900 Subject: [PATCH 47/55] perf: precompute BM25 index for repeated searches (#2177) --- pkg/utils/bm25.go | 151 +++++++++++++++++++++++------------------ pkg/utils/bm25_test.go | 60 ++++++++++++++++ 2 files changed, 144 insertions(+), 67 deletions(-) diff --git a/pkg/utils/bm25.go b/pkg/utils/bm25.go index 95c63f0e3..f8b9f6882 100644 --- a/pkg/utils/bm25.go +++ b/pkg/utils/bm25.go @@ -29,18 +29,18 @@ const ( DefaultBM25B = 0.75 ) -// BM25Engine is a query-time BM25 search engine over a generic corpus. +// BM25Engine is a BM25 search engine over a generic corpus. // T is the document type; the caller supplies a TextFunc that extracts the // searchable text from each document. // -// The engine is stateless between queries: no caching, no invalidation logic. -// All indexing work is performed inside Search() on every call, making it -// safe to use on corpora that change frequently. +// The engine precomputes its index once at construction time and reuses it for +// subsequent searches. If the corpus content changes, construct a new engine. type BM25Engine[T any] struct { corpus []T textFunc func(T) string k1 float64 b float64 + index *bm25Index } // BM25Option is a functional option to configure a BM25Engine. @@ -51,6 +51,17 @@ type bm25Config struct { b float64 } +type bm25Index struct { + entries []bm25DocEntry + idf map[string]float32 + docLenNorm []float32 + posting map[string][]int32 +} + +type bm25DocEntry struct { + tf map[string]uint32 +} + // WithK1 overrides the term-frequency saturation constant (default 1.2). func WithK1(k1 float64) BM25Option { return func(c *bm25Config) { c.k1 = k1 } @@ -74,12 +85,14 @@ func NewBM25Engine[T any](corpus []T, textFunc func(T) string, opts ...BM25Optio for _, o := range opts { o(&cfg) } - return &BM25Engine[T]{ + engine := &BM25Engine[T]{ corpus: corpus, textFunc: textFunc, k1: cfg.k1, b: cfg.b, } + engine.index = buildBM25Index(corpus, textFunc, cfg.k1, cfg.b) + return engine } // BM25Result is a single ranked result from a Search call. @@ -91,9 +104,8 @@ type BM25Result[T any] struct { // Search ranks the corpus against query and returns the top-k results. // Returns an empty slice (not nil) when there are no matches. // -// Complexity: O(N×L) for indexing + O(|Q|×avgPostingLen) for scoring, -// where N = corpus size, L = average document length, Q = query terms. -// Top-k extraction uses a fixed-size min-heap: O(candidates × log k). +// Complexity: O(|Q|×avgPostingLen + candidates × log k) per search after the +// one-time indexing work performed by NewBM25Engine. func (e *BM25Engine[T]) Search(query string, topK int) []BM25Result[T] { if topK <= 0 { return []BM25Result[T]{} @@ -104,78 +116,24 @@ func (e *BM25Engine[T]) Search(query string, topK int) []BM25Result[T] { return []BM25Result[T]{} } - N := len(e.corpus) - if N == 0 { + if len(e.corpus) == 0 || e.index == nil { return []BM25Result[T]{} } - // Step 1: build per-document tf + raw doc lengths - type docEntry struct { - tf map[string]uint32 - rawLen int - } - - entries := make([]docEntry, N) - df := make(map[string]int, 64) - totalLen := 0 - - for i, doc := range e.corpus { - tokens := bm25Tokenize(e.textFunc(doc)) - totalLen += len(tokens) - - tf := make(map[string]uint32, len(tokens)) - for _, t := range tokens { - tf[t]++ - } - // df: each term counts once per document (iterate the map, keys are unique) - for t := range tf { - df[t]++ - } - - entries[i] = docEntry{tf: tf, rawLen: len(tokens)} - } - - avgDocLen := float64(totalLen) / float64(N) - - // Step 2: pre-compute IDF and per-doc length normalization - // IDF (Robertson smoothing): log( (N - df(t) + 0.5) / (df(t) + 0.5) + 1 ) - idf := make(map[string]float32, len(df)) - for term, freq := range df { - idf[term] = float32(math.Log( - (float64(N)-float64(freq)+0.5)/(float64(freq)+0.5) + 1, - )) - } - - // docLenNorm[i] = k1 * (1 - b + b * |doc_i| / avgDocLen) - // Stored as float32 — sufficient precision for ranking. - docLenNorm := make([]float32, N) - for i, entry := range entries { - docLenNorm[i] = float32(e.k1 * (1 - e.b + e.b*float64(entry.rawLen)/avgDocLen)) - } - - // Step 3: build inverted index (posting lists) - // Iterate the tf map directly — map keys are already unique, no seen-set needed. - posting := make(map[string][]int32, len(df)) - for i, entry := range entries { - for term := range entry.tf { - posting[term] = append(posting[term], int32(i)) - } - } - // Step 4: score via posting lists // Deduplicate query terms to avoid double-weighting the same term. unique := bm25Dedupe(queryTerms) scores := make(map[int32]float32) for _, term := range unique { - termIDF, ok := idf[term] + termIDF, ok := e.index.idf[term] if !ok { continue // term not in vocabulary → zero contribution } - for _, docID := range posting[term] { - freq := float32(entries[docID].tf[term]) + for _, docID := range e.index.posting[term] { + freq := float32(e.index.entries[docID].tf[term]) // TF_norm = freq * (k1+1) / (freq + docLenNorm) - tfNorm := freq * float32(e.k1+1) / (freq + docLenNorm[docID]) + tfNorm := freq * float32(e.k1+1) / (freq + e.index.docLenNorm[docID]) scores[docID] += termIDF * tfNorm } } @@ -212,6 +170,65 @@ func (e *BM25Engine[T]) Search(query string, topK int) []BM25Result[T] { return out } +func buildBM25Index[T any](corpus []T, textFunc func(T) string, k1, b float64) *bm25Index { + N := len(corpus) + if N == 0 { + return nil + } + + entries := make([]bm25DocEntry, N) + rawLens := make([]int, N) + df := make(map[string]int, 64) + totalLen := 0 + + for i, doc := range corpus { + tokens := bm25Tokenize(textFunc(doc)) + totalLen += len(tokens) + rawLens[i] = len(tokens) + + tf := make(map[string]uint32, len(tokens)) + for _, t := range tokens { + tf[t]++ + } + for term := range tf { + df[term]++ + } + + entries[i] = bm25DocEntry{tf: tf} + } + + avgDocLen := float64(totalLen) / float64(N) + if avgDocLen == 0 { + avgDocLen = 1 + } + + idf := make(map[string]float32, len(df)) + for term, freq := range df { + idf[term] = float32(math.Log( + (float64(N)-float64(freq)+0.5)/(float64(freq)+0.5) + 1, + )) + } + + docLenNorm := make([]float32, N) + for i, rawLen := range rawLens { + docLenNorm[i] = float32(k1 * (1 - b + b*float64(rawLen)/avgDocLen)) + } + + posting := make(map[string][]int32, len(df)) + for i, entry := range entries { + for term := range entry.tf { + posting[term] = append(posting[term], int32(i)) + } + } + + return &bm25Index{ + entries: entries, + idf: idf, + docLenNorm: docLenNorm, + posting: posting, + } +} + // bm25Tokenize splits s into lowercase tokens, stripping edge punctuation. func bm25Tokenize(s string) []string { raw := strings.Fields(strings.ToLower(s)) diff --git a/pkg/utils/bm25_test.go b/pkg/utils/bm25_test.go index 4bc85b246..216fe733d 100644 --- a/pkg/utils/bm25_test.go +++ b/pkg/utils/bm25_test.go @@ -1,7 +1,9 @@ package utils import ( + "fmt" "reflect" + "strings" "testing" ) @@ -173,3 +175,61 @@ func TestBM25Search_SortingStability(t *testing.T) { } } } + +func BenchmarkBM25Search_ReusedIndex(b *testing.B) { + corpus := benchmarkBM25Corpus(2000) + engine := NewBM25Engine(corpus, extractText) + query := "hardware gpio i2c sensor controller latency" + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + results := engine.Search(query, 10) + if len(results) == 0 { + b.Fatal("expected non-empty results") + } + } +} + +func BenchmarkBM25Search_RebuildEachTime(b *testing.B) { + corpus := benchmarkBM25Corpus(2000) + query := "hardware gpio i2c sensor controller latency" + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + engine := NewBM25Engine(corpus, extractText) + results := engine.Search(query, 10) + if len(results) == 0 { + b.Fatal("expected non-empty results") + } + } +} + +func benchmarkBM25Corpus(size int) []testDoc { + corpus := make([]testDoc, size) + topics := []string{ + "hardware gpio pwm adc sensor controller latency throughput", + "telegram markdown parser message escape formatting bot command", + "jsonl memory session history storage append compact recovery", + "openai provider routing agent tool search registry hidden tools", + "i2c spi uart serial device bus address transfer clock", + } + + for i := range corpus { + topic := topics[i%len(topics)] + corpus[i] = testDoc{ + ID: i, + Text: fmt.Sprintf( + "doc %d %s repeated repeated %s variant-%d %s", + i, + topic, + topic, + i%17, + strings.Repeat("token ", (i%7)+1), + ), + } + } + + return corpus +} From e88df4ff9c822457b60063d80891ea33af8be0f0 Mon Sep 17 00:00:00 2001 From: Alix-007 Date: Mon, 30 Mar 2026 16:31:34 +0800 Subject: [PATCH 48/55] feat(tools): add reaction tool and reply-aware message sends (#2156) - Add `reaction` tool that reacts to a message (defaults to current inbound message via context) - Extend `message` tool with optional `reply_to_message_id` parameter - Introduce `WithToolInboundContext` to inject inbound message IDs into tool execution context - Surface `MessageID` and `ReplyToMessageID` in `processOptions` for tool-surface consumption Refs #2137 --- pkg/agent/loop.go | 42 +++++++++++++++-- pkg/agent/loop_test.go | 14 ++++++ pkg/tools/base.go | 35 +++++++++++++- pkg/tools/message.go | 9 +++- pkg/tools/message_test.go | 41 ++++++++++++++-- pkg/tools/reaction.go | 87 ++++++++++++++++++++++++++++++++++ pkg/tools/reaction_test.go | 96 ++++++++++++++++++++++++++++++++++++++ pkg/tools/registry_test.go | 27 +++++++++++ 8 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 pkg/tools/reaction.go create mode 100644 pkg/tools/reaction_test.go diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index ef2951365..d7461e76f 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -75,6 +75,8 @@ type processOptions struct { SessionKey string // Session identifier for history/context Channel string // Target channel for tool execution ChatID string // Target chat ID for tool execution + MessageID string // Current inbound platform message ID + ReplyToMessageID string // Current inbound reply target message ID SenderID string // Current sender ID for dynamic context SenderDisplayName string // Current sender display name for dynamic context UserMessage string // User message content (may include prefix) @@ -104,6 +106,7 @@ const ( metadataKeyAccountID = "account_id" metadataKeyGuildID = "guild_id" metadataKeyTeamID = "team_id" + metadataKeyReplyToMessage = "reply_to_message_id" metadataKeyParentPeerKind = "parent_peer_kind" metadataKeyParentPeerID = "parent_peer_id" ) @@ -222,17 +225,37 @@ func registerSharedTools( // Message tool if cfg.Tools.IsToolEnabled("message") { messageTool := tools.NewMessageTool() - messageTool.SetSendCallback(func(channel, chatID, content string) error { + messageTool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) defer pubCancel() return msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ - Channel: channel, - ChatID: chatID, - Content: content, + Channel: channel, + ChatID: chatID, + Content: content, + ReplyToMessageID: replyToMessageID, }) }) agent.Tools.Register(messageTool) } + if cfg.Tools.IsToolEnabled("reaction") { + reactionTool := tools.NewReactionTool() + reactionTool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error { + if al.channelManager == nil { + return fmt.Errorf("channel manager not configured") + } + ch, ok := al.channelManager.GetChannel(channel) + if !ok { + return fmt.Errorf("channel %s not found", channel) + } + rc, ok := ch.(channels.ReactionCapable) + if !ok { + return fmt.Errorf("channel %s does not support reactions", channel) + } + _, err := rc.ReactToMessage(ctx, chatID, messageID) + return err + }) + agent.Tools.Register(reactionTool) + } // Send file tool (outbound media via MediaStore — store injected later by SetMediaStore) if cfg.Tools.IsToolEnabled("send_file") { @@ -1315,6 +1338,8 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) SessionKey: sessionKey, Channel: msg.Channel, ChatID: msg.ChatID, + MessageID: msg.MessageID, + ReplyToMessageID: inboundMetadata(msg, metadataKeyReplyToMessage), SenderID: msg.SenderID, SenderDisplayName: msg.Sender.DisplayName, UserMessage: msg.Content, @@ -2384,8 +2409,15 @@ turnLoop: } toolStart := time.Now() - toolResult := ts.agent.Tools.ExecuteWithContext( + execCtx := tools.WithToolInboundContext( turnCtx, + ts.channel, + ts.chatID, + ts.opts.MessageID, + ts.opts.ReplyToMessageID, + ) + toolResult := ts.agent.Tools.ExecuteWithContext( + execCtx, toolName, toolArgs, ts.channel, diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 25d20c689..58149f92c 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -531,6 +531,20 @@ func TestToolContext_Updates(t *testing.T) { if got := tools.ToolChannel(context.Background()); got != "" { t.Errorf("expected empty channel from bare context, got %q", got) } + + inboundCtx := tools.WithToolInboundContext( + context.Background(), + "telegram", + "chat-42", + "msg-123", + "msg-100", + ) + if got := tools.ToolMessageID(inboundCtx); got != "msg-123" { + t.Errorf("expected messageID 'msg-123', got %q", got) + } + if got := tools.ToolReplyToMessageID(inboundCtx); got != "msg-100" { + t.Errorf("expected replyToMessageID 'msg-100', got %q", got) + } } // TestToolRegistry_GetDefinitions verifies tool definitions can be retrieved diff --git a/pkg/tools/base.go b/pkg/tools/base.go index ec743e164..afee95692 100644 --- a/pkg/tools/base.go +++ b/pkg/tools/base.go @@ -21,8 +21,10 @@ type Tool interface { type toolCtxKey struct{ name string } var ( - ctxKeyChannel = &toolCtxKey{"channel"} - ctxKeyChatID = &toolCtxKey{"chatID"} + ctxKeyChannel = &toolCtxKey{"channel"} + ctxKeyChatID = &toolCtxKey{"chatID"} + ctxKeyMessageID = &toolCtxKey{"messageID"} + ctxKeyReplyToMessageID = &toolCtxKey{"replyToMessageID"} ) // WithToolContext returns a child context carrying channel and chatID. @@ -32,6 +34,23 @@ func WithToolContext(ctx context.Context, channel, chatID string) context.Contex return ctx } +// WithToolMessageContext returns a child context carrying inbound message IDs. +func WithToolMessageContext(ctx context.Context, messageID, replyToMessageID string) context.Context { + ctx = context.WithValue(ctx, ctxKeyMessageID, messageID) + ctx = context.WithValue(ctx, ctxKeyReplyToMessageID, replyToMessageID) + return ctx +} + +// WithToolInboundContext returns a child context carrying channel/chat and inbound IDs. +func WithToolInboundContext( + ctx context.Context, + channel, chatID, messageID, replyToMessageID string, +) context.Context { + ctx = WithToolContext(ctx, channel, chatID) + ctx = WithToolMessageContext(ctx, messageID, replyToMessageID) + return ctx +} + // ToolChannel extracts the channel from ctx, or "" if unset. func ToolChannel(ctx context.Context) string { v, _ := ctx.Value(ctxKeyChannel).(string) @@ -44,6 +63,18 @@ func ToolChatID(ctx context.Context) string { return v } +// ToolMessageID extracts the current inbound message ID from ctx, or "" if unset. +func ToolMessageID(ctx context.Context) string { + v, _ := ctx.Value(ctxKeyMessageID).(string) + return v +} + +// ToolReplyToMessageID extracts the current inbound reply target from ctx, or "" if unset. +func ToolReplyToMessageID(ctx context.Context) string { + v, _ := ctx.Value(ctxKeyReplyToMessageID).(string) + return v +} + // AsyncCallback is a function type that async tools use to notify completion. // When an async tool finishes its work, it calls this callback with the result. // diff --git a/pkg/tools/message.go b/pkg/tools/message.go index 438ceeddd..064065a38 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -6,7 +6,7 @@ import ( "sync/atomic" ) -type SendCallback func(channel, chatID, content string) error +type SendCallback func(channel, chatID, content, replyToMessageID string) error type MessageTool struct { sendCallback SendCallback @@ -41,6 +41,10 @@ func (t *MessageTool) Parameters() map[string]any { "type": "string", "description": "Optional: target chat/user ID", }, + "reply_to_message_id": map[string]any{ + "type": "string", + "description": "Optional: reply target message ID for channels that support threaded replies", + }, }, "required": []string{"content"}, } @@ -69,6 +73,7 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolRes channel, _ := args["channel"].(string) chatID, _ := args["chat_id"].(string) + replyToMessageID, _ := args["reply_to_message_id"].(string) if channel == "" { channel = ToolChannel(ctx) @@ -85,7 +90,7 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolRes return &ToolResult{ForLLM: "Message sending not configured", IsError: true} } - if err := t.sendCallback(channel, chatID, content); err != nil { + if err := t.sendCallback(channel, chatID, content, replyToMessageID); err != nil { return &ToolResult{ ForLLM: fmt.Sprintf("sending message: %v", err), IsError: true, diff --git a/pkg/tools/message_test.go b/pkg/tools/message_test.go index 05630972e..93a611ee0 100644 --- a/pkg/tools/message_test.go +++ b/pkg/tools/message_test.go @@ -10,7 +10,7 @@ func TestMessageTool_Execute_Success(t *testing.T) { tool := NewMessageTool() var sentChannel, sentChatID, sentContent string - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { sentChannel = channel sentChatID = chatID sentContent = content @@ -61,7 +61,7 @@ func TestMessageTool_Execute_WithCustomChannel(t *testing.T) { tool := NewMessageTool() var sentChannel, sentChatID string - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { sentChannel = channel sentChatID = chatID return nil @@ -96,7 +96,7 @@ func TestMessageTool_Execute_SendFailure(t *testing.T) { tool := NewMessageTool() sendErr := errors.New("network error") - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { return sendErr }) @@ -149,7 +149,7 @@ func TestMessageTool_Execute_NoTargetChannel(t *testing.T) { tool := NewMessageTool() // No WithToolContext — channel/chatID are empty - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { return nil }) @@ -251,4 +251,37 @@ func TestMessageTool_Parameters(t *testing.T) { if chatIDProp["type"] != "string" { t.Error("Expected chat_id type to be 'string'") } + + // Check reply_to_message_id property (optional) + replyToProp, ok := props["reply_to_message_id"].(map[string]any) + if !ok { + t.Error("Expected 'reply_to_message_id' property") + } + if replyToProp["type"] != "string" { + t.Error("Expected reply_to_message_id type to be 'string'") + } +} + +func TestMessageTool_Execute_WithReplyToMessageID(t *testing.T) { + tool := NewMessageTool() + + var sentReplyTo string + tool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error { + sentReplyTo = replyToMessageID + return nil + }) + + ctx := WithToolContext(context.Background(), "test-channel", "test-chat-id") + args := map[string]any{ + "content": "Reply test", + "reply_to_message_id": "msg-123", + } + + result := tool.Execute(ctx, args) + if result.IsError { + t.Fatalf("expected success, got error: %s", result.ForLLM) + } + if sentReplyTo != "msg-123" { + t.Fatalf("expected reply_to_message_id msg-123, got %q", sentReplyTo) + } } diff --git a/pkg/tools/reaction.go b/pkg/tools/reaction.go new file mode 100644 index 000000000..3455b07a9 --- /dev/null +++ b/pkg/tools/reaction.go @@ -0,0 +1,87 @@ +package tools + +import ( + "context" + "fmt" +) + +type ReactionCallback func(ctx context.Context, channel, chatID, messageID string) error + +type ReactionTool struct { + reactionCallback ReactionCallback +} + +func NewReactionTool() *ReactionTool { + return &ReactionTool{} +} + +func (t *ReactionTool) Name() string { + return "reaction" +} + +func (t *ReactionTool) Description() string { + return "Add a reaction to a message. Defaults to the current inbound message when message_id is omitted." +} + +func (t *ReactionTool) Parameters() map[string]any { + return map[string]any{ + "type": "object", + "properties": map[string]any{ + "message_id": map[string]any{ + "type": "string", + "description": "Optional: target message ID; defaults to the current inbound message", + }, + "channel": map[string]any{ + "type": "string", + "description": "Optional: target channel (telegram, whatsapp, etc.)", + }, + "chat_id": map[string]any{ + "type": "string", + "description": "Optional: target chat/user ID", + }, + }, + } +} + +func (t *ReactionTool) SetReactionCallback(callback ReactionCallback) { + t.reactionCallback = callback +} + +func (t *ReactionTool) Execute(ctx context.Context, args map[string]any) *ToolResult { + channel, _ := args["channel"].(string) + chatID, _ := args["chat_id"].(string) + messageID, _ := args["message_id"].(string) + + if channel == "" { + channel = ToolChannel(ctx) + } + if chatID == "" { + chatID = ToolChatID(ctx) + } + if messageID == "" { + messageID = ToolMessageID(ctx) + } + + if channel == "" || chatID == "" { + return &ToolResult{ForLLM: "No target channel/chat specified", IsError: true} + } + if messageID == "" { + return &ToolResult{ForLLM: "message_id is required", IsError: true} + } + if t.reactionCallback == nil { + return &ToolResult{ForLLM: "Reaction not configured", IsError: true} + } + + if err := t.reactionCallback(ctx, channel, chatID, messageID); err != nil { + return &ToolResult{ + ForLLM: fmt.Sprintf("adding reaction: %v", err), + IsError: true, + Err: err, + } + } + + return &ToolResult{ + ForLLM: fmt.Sprintf("Reaction added to %s:%s message %s", channel, chatID, messageID), + Silent: true, + } +} diff --git a/pkg/tools/reaction_test.go b/pkg/tools/reaction_test.go new file mode 100644 index 000000000..6fc90445a --- /dev/null +++ b/pkg/tools/reaction_test.go @@ -0,0 +1,96 @@ +package tools + +import ( + "context" + "errors" + "testing" +) + +func TestReactionTool_Execute_UsesContextMessageIDByDefault(t *testing.T) { + tool := NewReactionTool() + + var gotChannel, gotChatID, gotMessageID string + tool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error { + gotChannel = channel + gotChatID = chatID + gotMessageID = messageID + return nil + }) + + ctx := WithToolInboundContext(context.Background(), "telegram", "chat-1", "msg-100", "") + result := tool.Execute(ctx, map[string]any{}) + if result.IsError { + t.Fatalf("expected success, got error: %s", result.ForLLM) + } + if gotChannel != "telegram" || gotChatID != "chat-1" || gotMessageID != "msg-100" { + t.Fatalf("unexpected callback args: channel=%q chatID=%q messageID=%q", gotChannel, gotChatID, gotMessageID) + } +} + +func TestReactionTool_Execute_AllowsExplicitMessageIDOverride(t *testing.T) { + tool := NewReactionTool() + + var gotMessageID string + tool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error { + gotMessageID = messageID + return nil + }) + + ctx := WithToolInboundContext(context.Background(), "telegram", "chat-1", "msg-context", "") + result := tool.Execute(ctx, map[string]any{"message_id": "msg-explicit"}) + if result.IsError { + t.Fatalf("expected success, got error: %s", result.ForLLM) + } + if gotMessageID != "msg-explicit" { + t.Fatalf("expected explicit message id, got %q", gotMessageID) + } +} + +func TestReactionTool_Execute_MissingMessageID(t *testing.T) { + tool := NewReactionTool() + tool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error { return nil }) + + ctx := WithToolContext(context.Background(), "telegram", "chat-1") + result := tool.Execute(ctx, map[string]any{}) + if !result.IsError { + t.Fatal("expected error") + } + if result.ForLLM != "message_id is required" { + t.Fatalf("unexpected error message: %q", result.ForLLM) + } +} + +func TestReactionTool_Execute_CallbackError(t *testing.T) { + tool := NewReactionTool() + tool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error { + return errors.New("unsupported") + }) + + ctx := WithToolInboundContext(context.Background(), "telegram", "chat-1", "msg-100", "") + result := tool.Execute(ctx, map[string]any{}) + if !result.IsError { + t.Fatal("expected error") + } + if result.Err == nil { + t.Fatal("expected wrapped error") + } +} + +func TestReactionTool_Parameters(t *testing.T) { + tool := NewReactionTool() + params := tool.Parameters() + + props, ok := params["properties"].(map[string]any) + if !ok { + t.Fatal("expected properties map") + } + if _, ok := props["message_id"]; !ok { + t.Fatal("expected message_id parameter") + } + if _, ok := props["channel"]; !ok { + t.Fatal("expected channel parameter") + } + if _, ok := props["chat_id"]; !ok { + t.Fatal("expected chat_id parameter") + } +} diff --git a/pkg/tools/registry_test.go b/pkg/tools/registry_test.go index db52749f6..16bd30928 100644 --- a/pkg/tools/registry_test.go +++ b/pkg/tools/registry_test.go @@ -190,6 +190,33 @@ func TestToolRegistry_ExecuteWithContext_EmptyContext(t *testing.T) { } } +func TestToolRegistry_ExecuteWithContext_PreservesMessageContext(t *testing.T) { + r := NewToolRegistry() + ct := &mockContextAwareTool{ + mockRegistryTool: *newMockTool("ctx_tool", "needs context"), + } + r.Register(ct) + + baseCtx := WithToolMessageContext(context.Background(), "msg-123", "msg-100") + r.ExecuteWithContext(baseCtx, "ctx_tool", nil, "telegram", "chat-42", nil) + + if ct.lastCtx == nil { + t.Fatal("expected Execute to be called") + } + if got := ToolChannel(ct.lastCtx); got != "telegram" { + t.Errorf("expected channel 'telegram', got %q", got) + } + if got := ToolChatID(ct.lastCtx); got != "chat-42" { + t.Errorf("expected chatID 'chat-42', got %q", got) + } + if got := ToolMessageID(ct.lastCtx); got != "msg-123" { + t.Errorf("expected messageID 'msg-123', got %q", got) + } + if got := ToolReplyToMessageID(ct.lastCtx); got != "msg-100" { + t.Errorf("expected replyToMessageID 'msg-100', got %q", got) + } +} + func TestToolRegistry_ExecuteWithContext_AsyncCallback(t *testing.T) { r := NewToolRegistry() at := &mockAsyncRegistryTool{ From ff0266a40e7e0388bfe0e76d04ec8458680de7e4 Mon Sep 17 00:00:00 2001 From: LC <64722907+lc6464@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:44:50 +0800 Subject: [PATCH 49/55] feat(web): display backend version info in sidebar (#2087) * feat(web): display backend version info in sidebar * fix(web): improve version parsing and timeout behavior * refactor(web): remove useless --version fallback * feat(web): implement version info caching and improve retrieval logic * fix(web): clarify version timeout rationale * fix(web): harden gateway version probing and tests * style(web): split regexp to two lines for lint --- cmd/picoclaw-launcher-tui/ui/gateway.go | 12 +- web/backend/api/router.go | 3 + web/backend/api/version.go | 345 ++++++++++++++++++++ web/backend/api/version_test.go | 317 ++++++++++++++++++ web/frontend/src/api/system.ts | 11 + web/frontend/src/components/app-sidebar.tsx | 30 ++ web/frontend/src/i18n/locales/en.json | 6 + web/frontend/src/i18n/locales/zh.json | 6 + 8 files changed, 725 insertions(+), 5 deletions(-) create mode 100644 web/backend/api/version.go create mode 100644 web/backend/api/version_test.go diff --git a/cmd/picoclaw-launcher-tui/ui/gateway.go b/cmd/picoclaw-launcher-tui/ui/gateway.go index 1138c12db..397b712f7 100644 --- a/cmd/picoclaw-launcher-tui/ui/gateway.go +++ b/cmd/picoclaw-launcher-tui/ui/gateway.go @@ -35,24 +35,26 @@ func getPidPath() string { } func isProcessRunning(pid int) bool { - if runtime.GOOS == "windows" { + switch runtime.GOOS { + case "windows": cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid)) output, err := cmd.Output() if err != nil { return false } return strings.Contains(string(output), strconv.Itoa(pid)) - } else if runtime.GOOS == "darwin" { + case "darwin": cmd := exec.Command("ps", "aux") output, err := cmd.Output() if err != nil { return false } return strings.Contains(string(output), fmt.Sprintf(" %d ", pid)) + default: + // Linux and other unix-like systems. + _, err := os.Stat(fmt.Sprintf("/proc/%d", pid)) + return err == nil } - // Linux - _, err := os.Stat(fmt.Sprintf("/proc/%d", pid)) - return err == nil } func getGatewayStatus() gatewayStatus { diff --git a/web/backend/api/router.go b/web/backend/api/router.go index ce652d4c4..af490d8b5 100644 --- a/web/backend/api/router.go +++ b/web/backend/api/router.go @@ -76,6 +76,9 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) { // Launcher service parameters (port/public) h.registerLauncherConfigRoutes(mux) + // Runtime build/version metadata + h.registerVersionRoutes(mux) + // WeChat QR login flow h.registerWeixinRoutes(mux) diff --git a/web/backend/api/version.go b/web/backend/api/version.go new file mode 100644 index 000000000..6232b989b --- /dev/null +++ b/web/backend/api/version.go @@ -0,0 +1,345 @@ +package api + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "net/http" + "os/exec" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/web/backend/utils" +) + +type systemVersionResponse struct { + Version string `json:"version"` + GitCommit string `json:"git_commit,omitempty"` + BuildTime string `json:"build_time,omitempty"` + GoVersion string `json:"go_version"` +} + +type cachedSystemVersion struct { + value systemVersionResponse + gatewayPID int +} + +type systemVersionCache struct { + mu sync.Mutex + current cachedSystemVersion + hasCurrent bool + inflightCh chan struct{} +} + +func newSystemVersionCache() *systemVersionCache { + return &systemVersionCache{} +} + +var ( + // 15 seconds matches the gateway startup window used elsewhere in launcher flow, + // giving slow/embedded hosts enough time for first command invocation while + // staying independent from cross-file init ordering. + versionCmdTimeout = 15 * time.Second + maxVersionResolveAttempts = 3 + findPicoclawBinaryForInfo = resolveGatewayBinaryForVersionInfo + runPicoclawVersionOutput = executePicoclawVersion + currentGatewayVersionState = gatewayVersionState + launcherBuildInfoForVersion = fallbackSystemVersionInfoFromConfig + versionInfoCache = newSystemVersionCache() + ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*m`) + versionLinePattern = regexp.MustCompile( + `^(?:[^A-Za-z0-9]*\s*)?picoclaw(?:\.exe)?\s+([^\s(]+)` + + `(?:\s+\(git:\s*([^)]+)\))?\s*$`, + ) +) + +func (h *Handler) registerVersionRoutes(mux *http.ServeMux) { + mux.HandleFunc("GET /api/system/version", h.handleGetVersion) +} + +// handleGetVersion returns runtime version information for web clients. +func (h *Handler) handleGetVersion(w http.ResponseWriter, r *http.Request) { + versionInfo := h.resolveSystemVersionInfo(r.Context()) + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(versionInfo); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return + } +} + +// resolveSystemVersionInfo prefers the actual picoclaw binary version output, +// and falls back to launcher build metadata when command execution fails. +func (h *Handler) resolveSystemVersionInfo(ctx context.Context) systemVersionResponse { + for range maxVersionResolveAttempts { + gatewayPID, gatewayAlive := currentGatewayVersionState() + if cached, ok := versionInfoCache.get(gatewayPID, gatewayAlive); ok { + return cached + } + + leader, ok := versionInfoCache.waitOrStart(ctx) + if !ok { + return fallbackSystemVersionInfo() + } + if !leader { + continue + } + + resolved := h.resolveSystemVersionInfoUncached(ctx) + gatewayPID, gatewayAlive = currentGatewayVersionState() + versionInfoCache.finishResolve(resolved, gatewayPID, gatewayAlive) + return resolved + } + + return fallbackSystemVersionInfo() +} + +func (h *Handler) resolveSystemVersionInfoUncached(ctx context.Context) systemVersionResponse { + if ctx == nil { + ctx = context.Background() + } + + fallback := fallbackSystemVersionInfo() + + execPath := strings.TrimSpace(findPicoclawBinaryForInfo()) + if execPath == "" { + return fallback + } + + cmdCtx, cancel := context.WithTimeout(ctx, versionCmdTimeout) + defer cancel() + + output, err := runPicoclawVersionOutput(cmdCtx, execPath) + if err != nil { + return fallback + } + + parsed, ok := parsePicoclawVersionOutput(output) + if !ok { + return fallback + } + + if parsed.GoVersion == "" { + parsed.GoVersion = fallback.GoVersion + if parsed.GoVersion == "" { + parsed.GoVersion = runtime.Version() + } + } + + return parsed +} + +func fallbackSystemVersionInfo() systemVersionResponse { + return launcherBuildInfoForVersion() +} + +func fallbackSystemVersionInfoFromConfig() systemVersionResponse { + buildTime, goVer := config.FormatBuildInfo() + return systemVersionResponse{ + Version: config.GetVersion(), + GitCommit: config.GitCommit, + BuildTime: buildTime, + GoVersion: goVer, + } +} + +// resolveGatewayBinaryForVersionInfo uses the same executable as the launcher +// gateway start path when available, then falls back to launcher binary lookup. +// This keeps version probing aligned with the actual gateway startup behavior, +// so web and gateway do not drift onto different binaries. +func resolveGatewayBinaryForVersionInfo() string { + gateway.mu.Lock() + cmd := gateway.cmd + gateway.mu.Unlock() + + if cmd != nil { + if execPath := strings.TrimSpace(cmd.Path); execPath != "" { + return execPath + } + } + + return utils.FindPicoclawBinary() +} + +func gatewayVersionState() (int, bool) { + gateway.mu.Lock() + defer gateway.mu.Unlock() + + if gateway.cmd == nil || gateway.cmd.Process == nil { + return 0, false + } + pid := gateway.cmd.Process.Pid + if pid <= 0 { + return 0, false + } + + return pid, isCmdProcessAliveLocked(gateway.cmd) +} + +func (c *systemVersionCache) get(gatewayPID int, gatewayAlive bool) (systemVersionResponse, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.hasCurrent && (!gatewayAlive || gatewayPID <= 0 || gatewayPID != c.current.gatewayPID) { + c.clearCurrentLocked() + } + + if c.hasCurrent { + return c.current.value, true + } + + return systemVersionResponse{}, false +} + +func (c *systemVersionCache) waitOrStart(ctx context.Context) (bool, bool) { + if ctx == nil { + ctx = context.Background() + } + if ctx.Err() != nil { + return false, false + } + + c.mu.Lock() + if c.inflightCh == nil { + c.inflightCh = make(chan struct{}) + c.mu.Unlock() + return true, true + } + waitCh := c.inflightCh + c.mu.Unlock() + + select { + case <-waitCh: + return false, true + case <-ctx.Done(): + return false, false + } +} + +func (c *systemVersionCache) finishResolve(value systemVersionResponse, gatewayPID int, gatewayAlive bool) { + c.mu.Lock() + if gatewayAlive && gatewayPID > 0 { + c.current = cachedSystemVersion{value: value, gatewayPID: gatewayPID} + c.hasCurrent = true + } else { + c.clearCurrentLocked() + } + + inflightCh := c.inflightCh + c.inflightCh = nil + c.mu.Unlock() + + if inflightCh != nil { + close(inflightCh) + } +} + +func (c *systemVersionCache) clearCurrentLocked() { + c.hasCurrent = false + c.current = cachedSystemVersion{} +} + +func (c *systemVersionCache) resetForTest() { + c.mu.Lock() + defer c.mu.Unlock() + + c.current = cachedSystemVersion{} + c.hasCurrent = false + if c.inflightCh != nil { + close(c.inflightCh) + c.inflightCh = nil + } +} + +// executePicoclawVersion runs the version subcommand against the +// discovered picoclaw executable. +func executePicoclawVersion(ctx context.Context, execPath string) (string, error) { + out, err := exec.CommandContext(ctx, execPath, "version").CombinedOutput() + if err == nil { + return string(out), nil + } + + return string(out), fmt.Errorf("failed to execute version command: %w", err) +} + +// parsePicoclawVersionOutput extracts version/build/go fields from CLI output. +// It accepts banner/ANSI-decorated output and only requires the version line. +func parsePicoclawVersionOutput(raw string) (systemVersionResponse, bool) { + var result systemVersionResponse + + scanner := bufio.NewScanner(strings.NewReader(raw)) + for scanner.Scan() { + line := strings.TrimSpace(ansiEscapePattern.ReplaceAllString(scanner.Text(), "")) + if line == "" { + continue + } + + if match := versionLinePattern.FindStringSubmatch(line); len(match) > 0 { + candidateVersion := strings.TrimSpace(match[1]) + if !isLikelyVersionValue(candidateVersion) { + continue + } + result.Version = candidateVersion + if len(match) > 2 { + result.GitCommit = strings.TrimSpace(match[2]) + } + continue + } + + if buildValue, ok := strings.CutPrefix(line, "Build:"); ok { + result.BuildTime = strings.TrimSpace(buildValue) + continue + } + + if goValue, ok := strings.CutPrefix(line, "Go:"); ok { + result.GoVersion = strings.TrimSpace(goValue) + } + } + + if err := scanner.Err(); err != nil { + return systemVersionResponse{}, false + } + + if result.Version == "" { + return systemVersionResponse{}, false + } + + return result, true +} + +func isLikelyVersionValue(value string) bool { + v := strings.TrimSpace(strings.ToLower(value)) + if v == "" { + return false + } + if v == "dev" { + return true + } + + // Accept git-like short/long hashes even when they contain only letters (a-f). + if len(v) >= 7 && len(v) <= 40 { + allHex := true + for _, ch := range v { + if (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') { + continue + } + allHex = false + break + } + if allHex { + return true + } + } + + for _, ch := range v { + if ch >= '0' && ch <= '9' { + return true + } + } + return false +} diff --git a/web/backend/api/version_test.go b/web/backend/api/version_test.go new file mode 100644 index 000000000..31c5366ab --- /dev/null +++ b/web/backend/api/version_test.go @@ -0,0 +1,317 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "os/exec" + "runtime" + "testing" +) + +func setupVersionTestIsolation(t *testing.T) { + t.Helper() + + originalGatewayState := currentGatewayVersionState + originalFinder := findPicoclawBinaryForInfo + originalRunner := runPicoclawVersionOutput + originalFallback := launcherBuildInfoForVersion + t.Cleanup(func() { + currentGatewayVersionState = originalGatewayState + findPicoclawBinaryForInfo = originalFinder + runPicoclawVersionOutput = originalRunner + launcherBuildInfoForVersion = originalFallback + versionInfoCache.resetForTest() + }) + + currentGatewayVersionState = func() (int, bool) { return 0, false } + versionInfoCache.resetForTest() +} + +func TestGetSystemVersionUsesPicoclawBinaryInfo(t *testing.T) { + setupVersionTestIsolation(t) + + launcherBuildInfoForVersion = func() systemVersionResponse { + return systemVersionResponse{Version: "fallback", GoVersion: "go-fallback"} + } + + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + return "🦞 picoclaw v1.2.3 (git: deadbeef)\n Build: 2026-03-27T12:34:56Z\n Go: go1.25.8\n", nil + } + + h := NewHandler("") + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/system/version", nil) + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + var got systemVersionResponse + if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + + if got.Version != "v1.2.3" { + t.Fatalf("version = %q, want %q", got.Version, "v1.2.3") + } + if got.GitCommit != "deadbeef" { + t.Fatalf("git_commit = %q, want %q", got.GitCommit, "deadbeef") + } + if got.BuildTime != "2026-03-27T12:34:56Z" { + t.Fatalf("build_time = %q, want %q", got.BuildTime, "2026-03-27T12:34:56Z") + } + if got.GoVersion != "go1.25.8" { + t.Fatalf("go_version = %q, want %q", got.GoVersion, "go1.25.8") + } +} + +func TestGetSystemVersionFallsBackToLauncherInfoWhenCommandFails(t *testing.T) { + setupVersionTestIsolation(t) + + expected := systemVersionResponse{ + Version: "v9.9.9", + GitCommit: "cafebabe", + BuildTime: "2026-03-27T10:43:34+0000", + GoVersion: "go1.25.8", + } + launcherBuildInfoForVersion = func() systemVersionResponse { return expected } + + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + return "", errors.New("binary unavailable") + } + + h := NewHandler("") + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/system/version", nil) + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + var got systemVersionResponse + if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + + if got.Version != expected.Version { + t.Fatalf("version = %q, want %q", got.Version, expected.Version) + } + if got.GitCommit != expected.GitCommit { + t.Fatalf("git_commit = %q, want %q", got.GitCommit, expected.GitCommit) + } + if got.BuildTime != expected.BuildTime { + t.Fatalf("build_time = %q, want %q", got.BuildTime, expected.BuildTime) + } + if got.GoVersion != expected.GoVersion { + t.Fatalf("go_version = %q, want %q", got.GoVersion, expected.GoVersion) + } +} + +func TestParsePicoclawVersionOutput(t *testing.T) { + setupVersionTestIsolation(t) + + raw := "\u001b[1;31m████\u001b[0m\n🦞 picoclaw 18ec263 (git: 18ec2631)\n Build: 2026-03-27T10:43:34+0000\n Go: go1.25.8\n" + got, ok := parsePicoclawVersionOutput(raw) + if !ok { + t.Fatal("parsePicoclawVersionOutput() should parse valid output") + } + if got.Version != "18ec263" { + t.Fatalf("version = %q, want %q", got.Version, "18ec263") + } + if got.GitCommit != "18ec2631" { + t.Fatalf("git_commit = %q, want %q", got.GitCommit, "18ec2631") + } + if got.BuildTime != "2026-03-27T10:43:34+0000" { + t.Fatalf("build_time = %q, want %q", got.BuildTime, "2026-03-27T10:43:34+0000") + } + if got.GoVersion != "go1.25.8" { + t.Fatalf("go_version = %q, want %q", got.GoVersion, "go1.25.8") + } +} + +func TestParsePicoclawVersionOutputIgnoresUsageLine(t *testing.T) { + setupVersionTestIsolation(t) + + raw := "Usage: picoclaw version [flags]\n" + got, ok := parsePicoclawVersionOutput(raw) + if ok { + t.Fatalf("parsePicoclawVersionOutput() parsed usage line unexpectedly: %#v", got) + } +} + +func TestParsePicoclawVersionOutputAcceptsLetterOnlyHashVersion(t *testing.T) { + setupVersionTestIsolation(t) + + raw := "picoclaw abcdefa (git: abcdefabcdefabcdefabcdefabcdefabcdefabcd)\n" + got, ok := parsePicoclawVersionOutput(raw) + if !ok { + t.Fatal("parsePicoclawVersionOutput() should parse letter-only hash version") + } + if got.Version != "abcdefa" { + t.Fatalf("version = %q, want %q", got.Version, "abcdefa") + } + if got.GitCommit != "abcdefabcdefabcdefabcdefabcdefabcdefabcd" { + t.Fatalf("git_commit = %q, want %q", got.GitCommit, "abcdefabcdefabcdefabcdefabcdefabcdefabcd") + } +} + +func TestResolveSystemVersionInfoFallsBackRuntimeGoVersion(t *testing.T) { + setupVersionTestIsolation(t) + + launcherBuildInfoForVersion = func() systemVersionResponse { + return systemVersionResponse{Version: "dev", GoVersion: ""} + } + + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + return "picoclaw v1.0.0\n", nil + } + + h := NewHandler("") + got := h.resolveSystemVersionInfo(context.Background()) + if got.GoVersion != runtime.Version() { + t.Fatalf("go_version = %q, want runtime version %q", got.GoVersion, runtime.Version()) + } +} + +func TestResolveSystemVersionInfoCachesWhileGatewayAlive(t *testing.T) { + setupVersionTestIsolation(t) + + launcherBuildInfoForVersion = func() systemVersionResponse { + return systemVersionResponse{Version: "dev", GoVersion: "go-fallback"} + } + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + + pid := 4321 + currentGatewayVersionState = func() (int, bool) { return pid, true } + + runCount := 0 + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + runCount++ + return fmt.Sprintf("picoclaw v1.2.%d\n", runCount), nil + } + + h := NewHandler("") + first := h.resolveSystemVersionInfo(context.Background()) + second := h.resolveSystemVersionInfo(context.Background()) + + if first.Version != "v1.2.1" { + t.Fatalf("first version = %q, want %q", first.Version, "v1.2.1") + } + if second.Version != "v1.2.1" { + t.Fatalf("second version = %q, want cached %q", second.Version, "v1.2.1") + } + if runCount != 1 { + t.Fatalf("run count = %d, want %d", runCount, 1) + } +} + +func TestResolveSystemVersionInfoInvalidatesCacheWhenGatewayStops(t *testing.T) { + setupVersionTestIsolation(t) + + launcherBuildInfoForVersion = func() systemVersionResponse { + return systemVersionResponse{Version: "dev", GoVersion: "go-fallback"} + } + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + + alive := true + pid := 9876 + currentGatewayVersionState = func() (int, bool) { + if !alive { + return 0, false + } + return pid, true + } + + runCount := 0 + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + runCount++ + return fmt.Sprintf("picoclaw v2.0.%d\n", runCount), nil + } + + h := NewHandler("") + first := h.resolveSystemVersionInfo(context.Background()) + second := h.resolveSystemVersionInfo(context.Background()) + + if first.Version != "v2.0.1" || second.Version != "v2.0.1" { + t.Fatalf("expected cached version v2.0.1, got first=%q second=%q", first.Version, second.Version) + } + if runCount != 1 { + t.Fatalf("run count after cache hit = %d, want %d", runCount, 1) + } + + alive = false + third := h.resolveSystemVersionInfo(context.Background()) + if third.Version != "v2.0.2" { + t.Fatalf("third version = %q, want refreshed %q", third.Version, "v2.0.2") + } + if runCount != 2 { + t.Fatalf("run count after invalidation = %d, want %d", runCount, 2) + } +} + +func TestResolveSystemVersionInfoSkipsCommandWhenContextCanceled(t *testing.T) { + setupVersionTestIsolation(t) + + launcherBuildInfoForVersion = func() systemVersionResponse { + return systemVersionResponse{Version: "v3.0.0", GoVersion: "go-fallback"} + } + findPicoclawBinaryForInfo = func() string { return "picoclaw" } + + runCount := 0 + runPicoclawVersionOutput = func(_ context.Context, _ string) (string, error) { + runCount++ + return "picoclaw v9.9.9\n", nil + } + + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() + + h := NewHandler("") + got := h.resolveSystemVersionInfo(canceledCtx) + + if runCount != 0 { + t.Fatalf("run count = %d, want %d", runCount, 0) + } + if got.Version != "v3.0.0" { + t.Fatalf("version = %q, want fallback %q", got.Version, "v3.0.0") + } +} + +func TestResolveGatewayBinaryForVersionInfoPrefersGatewayCommandPath(t *testing.T) { + setupVersionTestIsolation(t) + + originalFinder := findPicoclawBinaryForInfo + t.Cleanup(func() { + findPicoclawBinaryForInfo = originalFinder + }) + + gateway.mu.Lock() + originalCmd := gateway.cmd + gateway.cmd = &exec.Cmd{Path: "/tmp/picoclaw-from-gateway"} + gateway.mu.Unlock() + t.Cleanup(func() { + gateway.mu.Lock() + gateway.cmd = originalCmd + gateway.mu.Unlock() + }) + + got := resolveGatewayBinaryForVersionInfo() + if got != "/tmp/picoclaw-from-gateway" { + t.Fatalf("exec path = %q, want %q", got, "/tmp/picoclaw-from-gateway") + } +} diff --git a/web/frontend/src/api/system.ts b/web/frontend/src/api/system.ts index 2e2f36f15..dfc48b6b8 100644 --- a/web/frontend/src/api/system.ts +++ b/web/frontend/src/api/system.ts @@ -13,6 +13,13 @@ export interface LauncherConfig { allowed_cidrs: string[] } +export interface SystemVersionInfo { + version: string + git_commit?: string + build_time?: string + go_version: string +} + async function request(path: string, options?: RequestInit): Promise { const res = await launcherFetch(path, options) if (!res.ok) { @@ -62,3 +69,7 @@ export async function setLauncherConfig( body: JSON.stringify(payload), }) } + +export async function getSystemVersionInfo(): Promise { + return request("/api/system/version") +} diff --git a/web/frontend/src/components/app-sidebar.tsx b/web/frontend/src/components/app-sidebar.tsx index 0e135c0c1..18bc9f092 100644 --- a/web/frontend/src/components/app-sidebar.tsx +++ b/web/frontend/src/components/app-sidebar.tsx @@ -10,10 +10,12 @@ import { IconSparkles, IconTools, } from "@tabler/icons-react" +import { useQuery } from "@tanstack/react-query" import { Link, useRouterState } from "@tanstack/react-router" import * as React from "react" import { useTranslation } from "react-i18next" +import { getSystemVersionInfo } from "@/api/system" import { Collapsible, CollapsibleContent, @@ -27,6 +29,7 @@ import { SidebarGroupLabel, SidebarMenu, SidebarMenuButton, + SidebarFooter, SidebarMenuItem, SidebarRail, } from "@/components/ui/sidebar" @@ -78,6 +81,13 @@ export function AppSidebar({ ...props }: React.ComponentProps) { language: (i18n.resolvedLanguage ?? i18n.language ?? "").toLowerCase(), t, }) + const { data: versionInfo } = useQuery({ + queryKey: ["system", "version"], + queryFn: getSystemVersionInfo, + staleTime: 5 * 60 * 1000, + }) + + const versionText = versionInfo?.version ?? t("footer.version_unknown") const navGroups: NavGroup[] = React.useMemo(() => { return [ @@ -235,6 +245,26 @@ export function AppSidebar({ ...props }: React.ComponentProps) { ))} + +
    +
    + {t("footer.version")}:{" "} + {versionText} +
    + {versionInfo?.git_commit && ( +
    + {t("footer.commit")}:{" "} + {versionInfo.git_commit} +
    + )} + {versionInfo?.build_time && ( +
    + {t("footer.build")}:{" "} + {versionInfo.build_time} +
    + )} +
    +
    ) diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index 38cdeb324..9d170a4c8 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -93,6 +93,12 @@ "labels": { "loading": "Loading..." }, + "footer": { + "version": "Version", + "commit": "Commit", + "build": "Build", + "version_unknown": "Unknown" + }, "credentials": { "description": "Manage OAuth and token-based credentials for supported providers.", "loading": "Loading credentials...", diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index 9ec4ec967..b214753ca 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -93,6 +93,12 @@ "labels": { "loading": "加载中..." }, + "footer": { + "version": "版本", + "commit": "提交", + "build": "构建", + "version_unknown": "未知" + }, "credentials": { "description": "管理已支持服务商的 OAuth 与 Token 凭据。", "loading": "正在加载凭据...", From 7a1f2aba03374aff1c8516b9e8755da49a061922 Mon Sep 17 00:00:00 2001 From: Cytown Date: Mon, 30 Mar 2026 17:43:10 +0800 Subject: [PATCH 50/55] add check for gateway port and fix logger.Fatal not record issue (#2185) --- pkg/gateway/gateway.go | 15 +++++++++-- pkg/logger/logger.go | 59 +++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index c35b3e744..c563a99a2 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -91,13 +91,17 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error defer panicFunc() if err = logger.EnableFileLogging(filepath.Join(homePath, logPath, logFile)); err != nil { - panic(fmt.Sprintf("error enabling file logging: %v", err)) + logger.Fatal(fmt.Sprintf("error enabling file logging: %v", err)) } defer logger.DisableFileLogging() cfg, err := config.LoadConfig(configPath) if err != nil { - return fmt.Errorf("error loading config: %w", err) + logger.Fatalf("error loading config: %v", err) + } + + if err = preCheckConfig(cfg); err != nil { + logger.Fatalf("config pre-check failed: %v", err) } logger.SetLevelFromString(cfg.Gateway.LogLevel) @@ -214,6 +218,13 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error } } +func preCheckConfig(cfg *config.Config) error { + if cfg.Gateway.Port <= 0 || cfg.Gateway.Port > 65535 { + return fmt.Errorf("invalid gateway port: %d, port must be between 1 and 65535", cfg.Gateway.Port) + } + return nil +} + func executeReload( ctx context.Context, agentLoop *agent.AgentLoop, diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 33079616a..6d2e31791 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -35,12 +35,13 @@ var ( FATAL: "FATAL", } - currentLevel = INFO - logger zerolog.Logger - fileLogger zerolog.Logger - logFile *os.File - once sync.Once - mu sync.RWMutex + currentLevel = INFO + logger zerolog.Logger + logFile *os.File + once sync.Once + mu sync.RWMutex + writers []io.Writer + consoleWriter zerolog.ConsoleWriter ) func init() { @@ -49,7 +50,7 @@ func init() { isTTY := term.IsTerminal(int(os.Stdout.Fd())) - consoleWriter := zerolog.ConsoleWriter{ + consoleWriter = zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "15:04:05", // TODO: make it configurable??? @@ -72,8 +73,9 @@ func init() { NoColor: !isTTY, } - logger = zerolog.New(consoleWriter).With().Timestamp().Caller().Logger() - fileLogger = zerolog.Logger{} + writers = append(writers, consoleWriter) + + logger = zerolog.New(io.MultiWriter(writers...)).With().Timestamp().Caller().Logger() }) } @@ -124,7 +126,15 @@ func SetConsoleLevel(level LogLevel) { func DisableConsole() { mu.Lock() defer mu.Unlock() - logger = zerolog.New(io.Discard).With().Timestamp().Caller().Logger() + writers[0] = io.Discard + logger = logger.Output(io.MultiWriter(writers...)) +} + +func EnableConsole() { + mu.Lock() + defer mu.Unlock() + writers[0] = consoleWriter + logger = logger.Output(io.MultiWriter(writers...)) } func GetLevel() LogLevel { @@ -182,7 +192,14 @@ func EnableFileLogging(filePath string) error { } logFile = newFile - fileLogger = zerolog.New(logFile).With().Timestamp().Caller().Logger() + + if len(writers) != 1 { + return fmt.Errorf("failed to configure file logging: %w", err) + } + + writers = append(writers, logFile) + logger = logger.Output(io.MultiWriter(writers...)) + return nil } @@ -194,7 +211,10 @@ func DisableFileLogging() { logFile.Close() logFile = nil } - fileLogger = zerolog.Logger{} + if len(writers) > 1 { + writers = writers[:1] + logger = logger.Output(io.MultiWriter(writers...)) + } } func ConfigureFromEnv() { @@ -298,21 +318,8 @@ func logMessage(level LogLevel, component string, message string, fields map[str event.Str(Component, component) appendFields(event, fields) + event.CallerSkipFrame(skip).Msg(message) - - // Also log to file if enabled - if fileLogger.GetLevel() != zerolog.NoLevel { - fileEvent := getEvent(fileLogger, level) - - fileEvent.Str(Component, component) - - appendFields(fileEvent, fields) - fileEvent.CallerSkipFrame(skip).Msg(message) - } - - if level == FATAL { - os.Exit(1) - } } func appendFields(event *zerolog.Event, fields map[string]any) { From 010d807e61be9f1bb035e06b437f4a8d5d0a508c Mon Sep 17 00:00:00 2001 From: Cytown Date: Mon, 30 Mar 2026 17:59:56 +0800 Subject: [PATCH 51/55] update docs according to newest config version 2 (#2186) --- docs/config-versioning.md | 161 ++++++++++++------------- docs/configuration.md | 25 ++-- docs/credential_encryption.md | 4 +- docs/docker.md | 6 +- docs/fr/configuration.md | 6 +- docs/fr/docker.md | 6 +- docs/fr/providers.md | 33 ++--- docs/ja/configuration.md | 6 +- docs/ja/docker.md | 6 +- docs/ja/providers.md | 46 ++++--- docs/migration/model-list-migration.md | 61 +++++++--- docs/my/docker.md | 6 +- docs/providers.md | 45 +++---- docs/pt-br/configuration.md | 6 +- docs/pt-br/docker.md | 6 +- docs/pt-br/providers.md | 35 +++--- docs/security_configuration.md | 10 +- docs/vi/configuration.md | 6 +- docs/vi/docker.md | 6 +- docs/vi/providers.md | 35 +++--- docs/zh/configuration.md | 28 ++--- docs/zh/docker.md | 6 +- docs/zh/providers.md | 39 +++--- 23 files changed, 323 insertions(+), 265 deletions(-) diff --git a/docs/config-versioning.md b/docs/config-versioning.md index 36d7fdd25..b5cdaf990 100644 --- a/docs/config-versioning.md +++ b/docs/config-versioning.md @@ -11,24 +11,35 @@ PicoClaw uses a schema versioning system for `config.json` to ensure smooth upgr - **Changes**: Added `version` field to Config struct - **Migration**: No structural changes needed for existing configs +### Version 2 +- **Introduction**: Model enable/disable support and channel config unification +- **Changes**: + - Added `enabled` field to `ModelConfig` — allows disabling individual model entries without removing them + - During V1→V2 migration, `enabled` is auto-inferred: models with API keys or the reserved `local-model` name are enabled; others default to disabled + - Migrated legacy channel fields: Discord `mention_only` → `group_trigger.mention_only`, OneBot `group_trigger_prefix` → `group_trigger.prefixes` + - V0 configs now migrate directly to CurrentVersion (V2) instead of going through V1 + - `makeBackup()` now uses date-only suffix (e.g., `config.json.20260330.bak`) and also backs up `.security.yml` + ## How It Works ### Automatic Migration When you load a config file: 1. The system first reads the `version` field from the JSON -2. Based on the detected version, it loads the appropriate config struct (`ConfigV0`, `ConfigV1`, etc.) +2. Based on the detected version, it loads the appropriate config struct (`configV0`, `configV1`, etc.) 3. If the loaded version is less than the latest, migrations are applied incrementally -4. The version number is updated automatically -5. The migrated config is automatically saved back to disk +4. Before saving, the system automatically creates a date-stamped backup of `config.json` and `.security.yml` +5. The version number is updated automatically +6. The migrated config is automatically saved back to disk ### Version Field The `version` field in `config.json` indicates the schema version: - `0` or missing: Legacy config (no version field) -- `1`: Current version with versioning support +- `1`: Previous version (will be auto-migrated to V2 on load) +- `2`: Current version ```json { - "version": 1, + "version": 2, "agents": {...}, ... } @@ -54,25 +65,25 @@ type ConfigV2 struct { ### Step 2: Update Current Config Version ```go -const CurrentConfigVersion = 2 // Increment this +const CurrentVersion = 2 // Increment this ``` ### Step 3: Add a Loader Function ```go -// loadConfigV2 loads a version 2 config -func loadConfigV2(data []byte) (*Config, error) { +// loadConfigV3 loads a version 3 config +func loadConfigV3(data []byte) (*Config, error) { cfg := DefaultConfig() - // Parse to ConfigV2 struct - var v2 ConfigV2 - if err := json.Unmarshal(data, &v2); err != nil { + // Parse to ConfigV3 struct + var v3 ConfigV3 + if err := json.Unmarshal(data, &v3); err != nil { return nil, err } // Convert to current Config - cfg.Version = v2.Version - cfg.Agents = v2.Agents + cfg.Version = v3.Version + cfg.Agents = v3.Agents // ... map other fields return cfg, nil @@ -82,29 +93,12 @@ func loadConfigV2(data []byte) (*Config, error) { ### Step 4: Add Migration Logic ```go -// applyMigration applies a single migration step from fromVersion to toVersion -func applyMigration(cfg *Config, fromVersion, toVersion int) (*Config, error) { - switch toVersion { - case 1: - // Migration from version 0 to 1 - return &Config{ - Version: 1, - Agents: cfg.Agents, - // ... copy all fields - }, nil - case 2: - // Migration from version 1 to 2 - // Example: Move or rename fields - migrated := *cfg - migrated.Version = 2 - // Apply structural changes - if cfg.SomeOldField != "" { - migrated.SomeNewField = cfg.SomeOldField - } - return &migrated, nil - default: - return nil, fmt.Errorf("unsupported migration target version: %d", toVersion) - } +func (c *configV2) Migrate() (*Config, error) { + // Apply V2→V3 structural changes here + migrated := &c.Config + migrated.Version = 3 + // Apply structural changes + return migrated, nil } ``` @@ -120,7 +114,9 @@ func LoadConfig(path string) (*Config, error) { case 1: cfg, err = loadConfigV1(data) case 2: - cfg, err = loadConfigV2(data) + cfg, err = loadConfig(data) + case 3: + cfg, err = loadConfigV3(data) default: return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version) } @@ -134,22 +130,22 @@ func LoadConfig(path string) (*Config, error) { Create a test in `config_migration_test.go`: ```go -func TestMigrateV1ToV2(t *testing.T) { - // Create a version 1 config - v1Config := Config{ - Version: 1, +func TestMigrateV2ToV3(t *testing.T) { + // Create a version 2 config + v2Config := Config{ + Version: 2, // ... set up test data } // Apply migration - migrated, err := applyMigration(&v1Config, 1, 2) + migrated, err := v2Config.Migrate() if err != nil { t.Fatalf("Migration failed: %v", err) } // Verify version is updated - if migrated.Version != 2 { - t.Errorf("Expected version 2, got %d", migrated.Version) + if migrated.Version != 3 { + t.Errorf("Expected version 3, got %d", migrated.Version) } // Verify data is preserved/transformed correctly @@ -164,58 +160,60 @@ func TestMigrateV1ToV2(t *testing.T) { 3. **No Data Loss**: Migrations should preserve all user settings 4. **Idempotent**: Running the same migration multiple times should be safe 5. **Auto-Save**: Migrated configs are automatically saved to update the user's file -6. **Test Thoroughly**: Test with real user config files -7. **Update Defaults**: Keep `defaults.go` in sync with the latest schema +6. **Auto-Backup**: Before saving, the system creates a date-stamped backup of `config.json` and `.security.yml` +7. **Test Thoroughly**: Test with real user config files +8. **Update Defaults**: Keep `defaults.go` in sync with the latest schema ## Example Migration ### Scenario: Adding a new field with default value -Old config (version 1): -```json -{ - "version": 1, - "agents": { - "defaults": { - "max_tokens": 32768 - } - } -} -``` - -Migration to version 2: -```go -case 2: - migrated := *cfg - migrated.Version = 2 - - // Add new field with default value if not set - if migrated.Agents.Defaults.NewFeatureEnabled == false { - // Use default value - } - - return &migrated, nil -``` - -New config (version 2): +Old config (version 2): ```json { "version": 2, - "agents": { - "defaults": { - "max_tokens": 32768, - "new_feature_enabled": false + "model_list": [ + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4" } - } + ] +} +``` + +Migration to version 3: +```go +func (c *configV2) Migrate() (*Config, error) { + migrated := &c.Config + migrated.Version = 3 + + // Add new field with default value if not set + // ... + + return migrated, nil +} +``` + +New config (version 3): +```json +{ + "version": 3, + "model_list": [ + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "new_option": true + } + ] } ``` ## Troubleshooting ### Config Not Upgrading -- Check that `CurrentConfigVersion` is incremented -- Verify migration logic in `applyMigration()` handles the target version -- Ensure `migrateConfig()` is called in `LoadConfig()` +- Check that `CurrentVersion` is incremented +- Verify migration logic handles the target version +- Ensure `Migrate()` is called in `LoadConfig()` ### Migration Errors - Check error messages for specific migration failures @@ -227,4 +225,5 @@ New config (version 2): - Ensure all fields are copied during migration - Check that the migration doesn't overwrite values with defaults unnecessarily - Review the conversion logic in the loader functions +- Check the auto-backup files (e.g., `config.json.20260330.bak`) to recover original data diff --git a/docs/configuration.md b/docs/configuration.md index 9b9524692..560082e0e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -479,8 +479,9 @@ This design also enables **multi-agent support** with flexible provider selectio - **Different agents, different providers**: Each agent can use its own LLM provider - **Model fallbacks**: Configure primary and fallback models for resilience -- **Load balancing**: Distribute requests across multiple endpoints +- **Load balancing**: Distribute requests across multiple endpoints or keys - **Centralized configuration**: Manage all providers in one place +- **Model enable/disable**: Use the `enabled` field to temporarily disable a model without removing its configuration #### 🔒 Security Configuration (Recommended) @@ -581,22 +582,22 @@ For complete documentation, see [`security_configuration.md`](security_configura { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -607,7 +608,9 @@ For complete documentation, see [`security_configuration.md`](security_configura } ``` -> **Security Note**: You can remove `api_key` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details. +> **Security Note**: You can remove `api_keys` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details. +> +> **Note**: The `enabled` field can be set to `false` to disable a model entry without removing it. When omitted, it defaults to `true` during migration for models that have API keys. #### Vendor-Specific Examples @@ -684,7 +687,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic' { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -759,13 +762,13 @@ model_list: "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -773,7 +776,7 @@ model_list: #### Migration from Legacy `providers` Config -The old `providers` configuration is **deprecated** but still supported for backward compatibility. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide. +The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide. ### Provider Architecture @@ -783,7 +786,7 @@ PicoClaw routes providers by protocol family: - **Anthropic**: Claude-native API behavior. - **Codex/OAuth**: OpenAI OAuth/token authentication route. -This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_key`). +This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_keys`).
    Zhipu (legacy providers format) diff --git a/docs/credential_encryption.md b/docs/credential_encryption.md index de3b70e09..54c2ee5f9 100644 --- a/docs/credential_encryption.md +++ b/docs/credential_encryption.md @@ -1,6 +1,6 @@ # Credential Encryption -PicoClaw supports encrypting `api_key` values in `model_list` configuration entries. +PicoClaw supports encrypting `api_key`/`api_keys` values in `model_list` configuration entries. Encrypted keys are stored as `enc://` strings and decrypted automatically at startup. --- @@ -42,6 +42,8 @@ enc://AAAA...base64... ## Supported `api_key` Formats +The same formats apply to both `api_key` (singular) and individual elements in the `api_keys` (array) field: + | Format | Example | Behaviour | |--------|---------|-----------| | Plaintext | `sk-abc123` | Used as-is | diff --git a/docs/docker.md b/docs/docker.md index 18de1ee83..6c32879a6 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -95,19 +95,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/fr/configuration.md b/docs/fr/configuration.md index fd8602c68..7a57cceae 100644 --- a/docs/fr/configuration.md +++ b/docs/fr/configuration.md @@ -334,15 +334,15 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu ```json { "model_list": [ - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] }, + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] } ] } ``` #### Migration depuis l'ancienne config `providers` -L'ancienne configuration `providers` est **dépréciée** mais toujours supportée. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md). +L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md). ### Architecture des Providers diff --git a/docs/fr/docker.md b/docs/fr/docker.md index 432edb1b2..9605440bc 100644 --- a/docs/fr/docker.md +++ b/docs/fr/docker.md @@ -92,19 +92,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/fr/providers.md b/docs/fr/providers.md index 39f5cf36a..d0da81897 100644 --- a/docs/fr/providers.md +++ b/docs/fr/providers.md @@ -73,22 +73,22 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -107,7 +107,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -117,7 +117,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -127,7 +127,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -137,7 +137,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -147,7 +147,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -161,7 +161,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -189,7 +189,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -201,7 +201,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -218,13 +218,13 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -232,7 +232,7 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu #### Migration depuis l'Ancienne Configuration `providers` -L'ancienne configuration `providers` est **dépréciée** mais toujours prise en charge pour la compatibilité ascendante. +L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées. **Ancienne configuration (dépréciée) :** @@ -257,11 +257,12 @@ L'ancienne configuration `providers` est **dépréciée** mais toujours prise en ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { diff --git a/docs/ja/configuration.md b/docs/ja/configuration.md index da60b6052..6d6290e8a 100644 --- a/docs/ja/configuration.md +++ b/docs/ja/configuration.md @@ -335,15 +335,15 @@ HEARTBEAT_OK を返信 ユーザーが直接結果を受信 ```json { "model_list": [ - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] }, + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] } ] } ``` #### 旧 `providers` 設定からの移行 -旧 `providers` 設定は**非推奨**ですが後方互換性のためサポートされています。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。 +旧 `providers` 設定は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。 ### Provider アーキテクチャ diff --git a/docs/ja/docker.md b/docs/ja/docker.md index 31ed17ec5..a585c5e80 100644 --- a/docs/ja/docker.md +++ b/docs/ja/docker.md @@ -94,19 +94,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/ja/providers.md b/docs/ja/providers.md index 9a53a4b69..e29c113f3 100644 --- a/docs/ja/providers.md +++ b/docs/ja/providers.md @@ -73,22 +73,22 @@ { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -107,7 +107,7 @@ { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -117,7 +117,7 @@ { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -127,7 +127,18 @@ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] +} +``` + +**LiteLLM Proxy** + +```json +{ + "model_name": "lite-gpt4", + "model": "litellm/lite-gpt4", + "api_base": "http://localhost:4000/v1", + "api_keys": ["sk-..."] } ``` @@ -137,7 +148,7 @@ { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -147,7 +158,7 @@ { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -161,7 +172,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -189,7 +200,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -201,7 +212,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -218,13 +229,13 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -232,7 +243,7 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック #### レガシー `providers` 設定からの移行 -旧 `providers` 設定形式は**非推奨**ですが、後方互換性のためまだサポートされています。 +旧 `providers` 設定形式は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。 **旧設定(非推奨):** @@ -257,11 +268,12 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { @@ -282,7 +294,7 @@ PicoClaw はプロトコルファミリーごとに Provider をルーティン - Anthropic プロトコル:Claude ネイティブ API 動作。 - Codex/OAuth パス:OpenAI OAuth/Token 認証ルート。 -これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_key`)のみで実現しています。 +これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_keys`)のみで実現しています。
    Zhipu 設定例 diff --git a/docs/migration/model-list-migration.md b/docs/migration/model-list-migration.md index 9d05ac599..f2a545f8f 100644 --- a/docs/migration/model-list-migration.md +++ b/docs/migration/model-list-migration.md @@ -50,22 +50,23 @@ The new `model_list` configuration offers several advantages: ```json { + "version": 2, "model_list": [ { "model_name": "gpt4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key", + "api_keys": ["sk-your-openai-key"], "api_base": "https://api.openai.com/v1" }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "deepseek", "model": "deepseek/deepseek-chat", - "api_key": "sk-your-deepseek-key" + "api_keys": ["sk-your-deepseek-key"] } ], "agents": { @@ -76,6 +77,8 @@ The new `model_list` configuration offers several advantages: } ``` +> **Note**: The `enabled` field can be omitted — during V1→V2 migration it is auto-inferred (models with API keys or the `local-model` name are enabled by default). For new configs, you can explicitly set `"enabled": false` to disable a model entry without removing it. + ## Protocol Prefixes The `model` field uses a protocol prefix format: `[protocol/]model-identifier` @@ -111,7 +114,8 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | `model_name` | Yes | User-facing alias for the model | | `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) | | `api_base` | No | API endpoint URL | -| `api_key` | No* | API authentication key | +| `api_keys` | No | API authentication keys (array; supports multiple keys for load balancing) | +| `enabled` | No | Whether this model entry is active. Defaults to `true` during migration for models with API keys or named `local-model`. Set to `false` to disable. | | `proxy` | No | HTTP proxy URL | | `auth_method` | No | Authentication method: `oauth`, `token` | | `connect_mode` | No | Connection mode for CLI providers: `stdio`, `grpc` | @@ -119,11 +123,13 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | `max_tokens_field` | No | Field name for max tokens | | `request_timeout` | No | HTTP request timeout in seconds; `<=0` uses default `120s` | -*`api_key` is required for HTTP-based protocols unless `api_base` points to a local server. +> **Note**: `api_key` (singular) has been **removed** in V2 configs. Only `api_keys` (array) is supported. During migration from V0/V1, both `api_key` and `api_keys` are automatically merged into the new `api_keys` array. ## Load Balancing -Configure multiple endpoints for the same model to distribute load: +There are two ways to configure load balancing: + +### Option 1: Multiple API Keys in `api_keys` (Recommended) ```json { @@ -131,19 +137,45 @@ Configure multiple endpoints for the same model to distribute load: { "model_name": "gpt4", "model": "openai/gpt-5.4", - "api_key": "sk-key1", + "api_keys": ["sk-key1", "sk-key2", "sk-key3"], + "api_base": "https://api.openai.com/v1" + } + ] +} +``` + +Or via `.security.yml`: + +```yaml +model_list: + gpt4: + api_keys: + - "sk-key1" + - "sk-key2" + - "sk-key3" +``` + +### Option 2: Multiple Model Entries + +```json +{ + "model_list": [ + { + "model_name": "gpt4", + "model": "openai/gpt-5.4", + "api_keys": ["sk-key1"], "api_base": "https://api1.example.com/v1" }, { "model_name": "gpt4", "model": "openai/gpt-5.4", - "api_key": "sk-key2", + "api_keys": ["sk-key2"], "api_base": "https://api2.example.com/v1" }, { "model_name": "gpt4", "model": "openai/gpt-5.4", - "api_key": "sk-key3", + "api_keys": ["sk-key3"], "api_base": "https://api3.example.com/v1" } ] @@ -162,7 +194,7 @@ With `model_list`, adding a new provider requires zero code changes: { "model_name": "my-custom-llm", "model": "openai/my-model-v1", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "api_base": "https://api.your-provider.com/v1" } ] @@ -173,11 +205,12 @@ Just specify `openai/` as the protocol (or omit it for the default), and provide ## Backward Compatibility -During the migration period, your existing `providers` configuration will continue to work: +During the migration period, your existing V0/V1 config will be auto-migrated to V2: 1. If `model_list` is empty and `providers` has data, the system auto-converts internally -2. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"` -3. All existing functionality remains unchanged +2. Both `api_key` (singular) and `api_keys` (array) in V0/V1 configs are merged into the new `api_keys` array +3. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"` +4. All existing functionality remains unchanged ## Migration Checklist @@ -212,7 +245,7 @@ unknown protocol "xxx" in model "xxx/model-name" api_key or api_base is required for HTTP-based protocol "xxx" ``` -**Solution**: Provide `api_key` and/or `api_base` for HTTP-based providers. +**Solution**: Provide `api_keys` and/or `api_base` for HTTP-based providers. ## Need Help? diff --git a/docs/my/docker.md b/docs/my/docker.md index 8fe1aed8c..2f9cac3fd 100644 --- a/docs/my/docker.md +++ b/docs/my/docker.md @@ -91,19 +91,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/providers.md b/docs/providers.md index c6d442a3b..43cba55b5 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -79,22 +79,22 @@ This design also enables **multi-agent support** with flexible provider selectio { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -117,7 +117,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "voice-gemini", "model": "gemini/gemini-2.5-flash", - "api_key": "your-gemini-key" + "api_keys": ["your-gemini-key"] } ], "voice": { @@ -140,7 +140,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -150,7 +150,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -160,7 +160,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -170,7 +170,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "glm-4.7", "model": "openai/glm-4.7", - "api_key": "your-z.ai-key" + "api_keys": ["your-z.ai-key"], "api_base": "https://api.z.ai/api/coding/paas/v4" } ``` @@ -181,7 +181,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -191,7 +191,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -205,7 +205,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic' { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -233,7 +233,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic' "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -245,7 +245,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic' "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -259,7 +259,7 @@ If the standard Zhipu endpoint (`https://open.bigmodel.cn/api/paas/v4`) returns { "model_name": "glm-4.7", "model": "openai/glm-4.7", - "api_key": "your-zhipu-api-key", + "api_keys": ["your-zhipu-api-key"], "api_base": "https://api.z.ai/api/coding/paas/v4" } ``` @@ -277,13 +277,13 @@ Configure multiple endpoints for the same model name—PicoClaw will automatical "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -302,17 +302,17 @@ It also applies cooldown tracking per candidate to avoid immediately retrying a "model_name": "qwen-main", "model": "openai/qwen3.5:cloud", "api_base": "https://api.example.com/v1", - "api_key": "sk-main" + "api_keys": ["sk-main"] }, { "model_name": "deepseek-backup", "model": "deepseek/deepseek-chat", - "api_key": "sk-backup-1" + "api_keys": ["sk-backup-1"] }, { "model_name": "gemini-backup", "model": "gemini/gemini-2.5-flash", - "api_key": "sk-backup-2" + "api_keys": ["sk-backup-2"] } ], "agents": { @@ -330,7 +330,7 @@ If you use key-level failover for the same model, PicoClaw can chain through add #### Migration from Legacy `providers` Config -The old `providers` configuration is **deprecated** but still supported for backward compatibility. +The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated. **Old Config (deprecated):** @@ -355,11 +355,12 @@ The old `providers` configuration is **deprecated** but still supported for back ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { diff --git a/docs/pt-br/configuration.md b/docs/pt-br/configuration.md index cb836ce0f..27cd6d21f 100644 --- a/docs/pt-br/configuration.md +++ b/docs/pt-br/configuration.md @@ -335,15 +335,15 @@ Configure múltiplos endpoints para o mesmo nome de modelo — PicoClaw fará ro ```json { "model_list": [ - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] }, + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] } ] } ``` #### Migração da Configuração Legada `providers` -A configuração antiga `providers` está **depreciada** mas ainda é suportada. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md). +A configuração antiga `providers` está **depreciada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md). ### Arquitetura de Providers diff --git a/docs/pt-br/docker.md b/docs/pt-br/docker.md index bac48954b..a17dc64ec 100644 --- a/docs/pt-br/docker.md +++ b/docs/pt-br/docker.md @@ -92,19 +92,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/pt-br/providers.md b/docs/pt-br/providers.md index 0f7a4b5a1..c7c6305e2 100644 --- a/docs/pt-br/providers.md +++ b/docs/pt-br/providers.md @@ -73,22 +73,22 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -107,7 +107,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -117,7 +117,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -127,7 +127,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -137,7 +137,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -147,7 +147,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -161,7 +161,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -189,7 +189,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -201,7 +201,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -218,13 +218,13 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -232,7 +232,7 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará #### Migração da Configuração Legacy `providers` -A configuração antiga `providers` está **descontinuada** mas ainda é suportada para compatibilidade retroativa. +A configuração antiga `providers` está **descontinuada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas. **Configuração Antiga (descontinuada):** @@ -257,11 +257,12 @@ A configuração antiga `providers` está **descontinuada** mas ainda é suporta ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { @@ -282,7 +283,7 @@ O PicoClaw roteia provedores por família de protocolo: - Protocolo Anthropic: Comportamento nativo da API Claude. - Caminho Codex/OAuth: Rota de autenticação OAuth/token da OpenAI. -Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_key`). +Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_keys`).
    Zhipu diff --git a/docs/security_configuration.md b/docs/security_configuration.md index f4fe0e304..311c1790e 100644 --- a/docs/security_configuration.md +++ b/docs/security_configuration.md @@ -401,7 +401,7 @@ The pattern is: `PICOCLAW_
    __` with underscores separating p 3. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml` 4. **Use different keys** for different environments (dev, staging, production) 5. **Rotate keys regularly** and update `.security.yml` -6. **Backup securely**: Encrypt backups containing `.security.yml` +6. **Backup securely**: Encrypt backups containing `.security.yml`. Note that config migrations automatically create date-stamped backups (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`) 7. **Review access**: Ensure only authorized users have read access to the file ## API @@ -444,7 +444,7 @@ Returns the path to `.security.yml` relative to the config file. ```json { - "version": 1, + "version": 2, "agents": { "defaults": { "workspace": "~/picoclaw-workspace", @@ -557,6 +557,8 @@ go test ./pkg/config -run TestSecurityConfig ### Step 1: Backup your config +The system automatically creates a date-stamped backup before saving a migrated config (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`). If you prefer a manual backup: + ```bash cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup ``` @@ -597,9 +599,11 @@ Test your models and channels to ensure everything works correctly. ### Step 8: Clean up (optional) -If everything works, you can delete the backup: +If everything works, you can delete the backups: ```bash rm ~/.picoclaw/config.json.backup +# Also remove auto-generated date-stamped backups if desired: +rm ~/.picoclaw/config.json.20*.bak ~/.picoclaw/.security.yml.20*.bak ``` ## Advanced: Encrypted API Keys diff --git a/docs/vi/configuration.md b/docs/vi/configuration.md index b75215c3f..56eb8f557 100644 --- a/docs/vi/configuration.md +++ b/docs/vi/configuration.md @@ -335,15 +335,15 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ ```json { "model_list": [ - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, - { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] }, + { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] } ] } ``` #### Di Chuyển Từ Cấu Hình `providers` Cũ -Cấu hình `providers` cũ đã **bị deprecated** nhưng vẫn được hỗ trợ. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md). +Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md). ### Kiến Trúc Provider diff --git a/docs/vi/docker.md b/docs/vi/docker.md index eddc20a75..e6bc74b1a 100644 --- a/docs/vi/docker.md +++ b/docs/vi/docker.md @@ -92,19 +92,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/vi/providers.md b/docs/vi/providers.md index 09b51c56b..ffd992645 100644 --- a/docs/vi/providers.md +++ b/docs/vi/providers.md @@ -73,22 +73,22 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -107,7 +107,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -117,7 +117,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -127,7 +127,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -137,7 +137,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -147,7 +147,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -161,7 +161,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -189,7 +189,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -201,7 +201,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -218,13 +218,13 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -232,7 +232,7 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ #### Di Chuyển Từ Cấu Hình Legacy `providers` -Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được hỗ trợ để tương thích ngược. +Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate. **Cấu hình cũ (ngừng hỗ trợ):** @@ -257,11 +257,12 @@ Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được h ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { @@ -282,7 +283,7 @@ PicoClaw định tuyến provider theo họ giao thức: - Giao thức Anthropic: Hành vi API native của Claude. - Đường dẫn Codex/OAuth: Tuyến xác thực OAuth/token của OpenAI. -Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_key`). +Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_keys`).
    Zhipu diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md index c27a439f0..d4366b6a8 100644 --- a/docs/zh/configuration.md +++ b/docs/zh/configuration.md @@ -386,22 +386,22 @@ Agent 读取 HEARTBEAT.md { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -421,7 +421,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -434,7 +434,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -447,7 +447,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -460,7 +460,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -473,7 +473,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] } ``` @@ -485,7 +485,7 @@ Agent 读取 HEARTBEAT.md { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -514,7 +514,7 @@ Agent 读取 HEARTBEAT.md "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -533,13 +533,13 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -547,7 +547,7 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell #### 从旧版 `providers` 配置迁移 -旧版 `providers` 配置**已废弃**,但仍向后兼容。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。 +旧版 `providers` 配置**已废弃**,V2 中已移除。现有 V0/V1 配置会自动迁移。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。 ### Provider 架构 diff --git a/docs/zh/docker.md b/docs/zh/docker.md index 8aed1e86b..f840290a7 100644 --- a/docs/zh/docker.md +++ b/docs/zh/docker.md @@ -94,19 +94,19 @@ picoclaw onboard { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key", + "api_keys": ["sk-your-api-key"], "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "your-api-key", + "api_keys": ["your-api-key"], "request_timeout": 300 }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "your-anthropic-key" + "api_keys": ["your-anthropic-key"] } ], "tools": { diff --git a/docs/zh/providers.md b/docs/zh/providers.md index 4bcf7087e..d19dcdd89 100644 --- a/docs/zh/providers.md +++ b/docs/zh/providers.md @@ -75,22 +75,22 @@ { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-your-api-key" + "api_keys": ["sk-your-api-key"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-your-openai-key" + "api_keys": ["sk-your-openai-key"] }, { "model_name": "claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6", - "api_key": "sk-ant-your-key" + "api_keys": ["sk-ant-your-key"] }, { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-zhipu-key" + "api_keys": ["your-zhipu-key"] } ], "agents": { @@ -113,7 +113,7 @@ { "model_name": "voice-gemini", "model": "gemini/gemini-2.5-flash", - "api_key": "your-gemini-key" + "api_keys": ["your-gemini-key"] } ], "voice": { @@ -136,7 +136,7 @@ { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -146,7 +146,7 @@ { "model_name": "ark-code-latest", "model": "volcengine/ark-code-latest", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -156,7 +156,7 @@ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ``` @@ -166,7 +166,7 @@ { "model_name": "deepseek-chat", "model": "deepseek/deepseek-chat", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -190,7 +190,7 @@ { "model_name": "claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6", - "api_key": "sk-ant-your-key", + "api_keys": ["sk-ant-your-key"], "api_base": "https://api.anthropic.com" } ``` @@ -218,7 +218,7 @@ "model_name": "my-custom-model", "model": "openai/custom-model", "api_base": "https://my-proxy.com/v1", - "api_key": "sk-...", + "api_keys": ["sk-..."], "request_timeout": 300 } ``` @@ -230,7 +230,7 @@ "model_name": "lite-gpt4", "model": "litellm/lite-gpt4", "api_base": "http://localhost:4000/v1", - "api_key": "sk-..." + "api_keys": ["sk-..."] } ``` @@ -247,13 +247,13 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", - "api_key": "sk-key1" + "api_keys": ["sk-key1"] }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", - "api_key": "sk-key2" + "api_keys": ["sk-key2"] } ] } @@ -272,17 +272,17 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l "model_name": "qwen-main", "model": "openai/qwen3.5:cloud", "api_base": "https://api.example.com/v1", - "api_key": "sk-main" + "api_keys": ["sk-main"] }, { "model_name": "deepseek-backup", "model": "deepseek/deepseek-chat", - "api_key": "sk-backup-1" + "api_keys": ["sk-backup-1"] }, { "model_name": "gemini-backup", "model": "gemini/gemini-2.5-flash", - "api_key": "sk-backup-2" + "api_keys": ["sk-backup-2"] } ], "agents": { @@ -300,7 +300,7 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l #### 从旧的 `providers` 配置迁移 -旧的 `providers` 配置格式**已弃用**,但为向后兼容仍支持。 +旧的 `providers` 配置格式**已弃用**,V2 中已移除。现有 V0/V1 配置会自动迁移。 **旧配置(已弃用):** @@ -325,11 +325,12 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l ```json { + "version": 2, "model_list": [ { "model_name": "glm-4.7", "model": "zhipu/glm-4.7", - "api_key": "your-key" + "api_keys": ["your-key"] } ], "agents": { From 275c1012f1d4100064404ad4c31011da48cd7ec9 Mon Sep 17 00:00:00 2001 From: Cytown Date: Mon, 30 Mar 2026 18:00:18 +0800 Subject: [PATCH 52/55] make gateway reload use new loglevel (#2155) --- pkg/gateway/gateway.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index c563a99a2..209c7069b 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -104,11 +104,13 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error logger.Fatalf("config pre-check failed: %v", err) } - logger.SetLevelFromString(cfg.Gateway.LogLevel) - + // Debug mode permanently overrides the config log level to DEBUG. if debug { logger.SetLevel(logger.DEBUG) fmt.Println("🔍 Debug mode enabled") + } else { + logger.SetLevelFromString(cfg.Gateway.LogLevel) + logger.Infof("Log level set to %q", cfg.Gateway.LogLevel) } provider, modelID, err := createStartupProvider(cfg, allowEmptyStartup) @@ -191,7 +193,7 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error logger.Warn("Config reload skipped: another reload is in progress") continue } - err := executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) + err := executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup, debug) if err != nil { logger.Errorf("Config reload failed: %v", err) } @@ -208,7 +210,7 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error runningServices.reloading.Store(false) continue } - err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) + err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup, debug) if err != nil { logger.Errorf("Manual reload failed: %v", err) } else { @@ -233,9 +235,10 @@ func executeReload( runningServices *services, msgBus *bus.MessageBus, allowEmptyStartup bool, + debug bool, ) error { defer runningServices.reloading.Store(false) - return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup) + return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup, debug) } func createStartupProvider( @@ -403,6 +406,7 @@ func handleConfigReload( runningServices *services, msgBus *bus.MessageBus, allowEmptyStartup bool, + debug bool, ) error { logger.Info("🔄 Config file changed, reloading...") @@ -451,6 +455,14 @@ func handleConfigReload( } logger.Info(" ✓ Provider, configuration, and services reloaded successfully (thread-safe)") + + // Debug mode permanently overrides the config log level to DEBUG. + if !debug { + // Update log level last so that reload-related info/warn logs above are not suppressed. + logger.SetLevelFromString(newCfg.Gateway.LogLevel) + logger.Infof("Log level changing from current to %q", newCfg.Gateway.LogLevel) + } + return nil } From 9440bebca6f8daa683c8b5519809840ebb65daa1 Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:58:12 +0800 Subject: [PATCH 53/55] utils: anchor date retry-after to response date and cap delay --- pkg/utils/http_retry.go | 24 +++++++++++++++---- pkg/utils/http_retry_test.go | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/pkg/utils/http_retry.go b/pkg/utils/http_retry.go index 812271290..fcd8f50f5 100644 --- a/pkg/utils/http_retry.go +++ b/pkg/utils/http_retry.go @@ -11,6 +11,7 @@ import ( const maxRetries = 3 var retryDelayUnit = time.Second +var maxRetrySleepDuration = 1 * time.Minute func shouldRetry(statusCode int) bool { return statusCode == http.StatusTooManyRequests || @@ -51,27 +52,40 @@ func DoRequestWithRetry(client *http.Client, req *http.Request) (*http.Response, func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration { fallback := retryDelayUnit * time.Duration(attempt+1) if resp == nil || resp.StatusCode != http.StatusTooManyRequests { - return fallback + return clampRetryDelay(fallback) } retryAfter := resp.Header.Get("Retry-After") if retryAfter == "" { - return fallback + return clampRetryDelay(fallback) } if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 { - return time.Duration(seconds) * time.Second + return clampRetryDelay(time.Duration(seconds) * time.Second) } if when, err := http.ParseTime(retryAfter); err == nil { delay := time.Until(when) + if serverDate, err := http.ParseTime(resp.Header.Get("Date")); err == nil { + delay = when.Sub(serverDate) + } if delay < 0 { return 0 } - return delay + return clampRetryDelay(delay) } - return fallback + return clampRetryDelay(fallback) +} + +func clampRetryDelay(delay time.Duration) time.Duration { + if delay <= 0 { + return 0 + } + if delay > maxRetrySleepDuration { + return maxRetrySleepDuration + } + return delay } func sleepWithCtx(ctx context.Context, d time.Duration) error { diff --git a/pkg/utils/http_retry_test.go b/pkg/utils/http_retry_test.go index 918245b57..1e5ac4064 100644 --- a/pkg/utils/http_retry_test.go +++ b/pkg/utils/http_retry_test.go @@ -279,3 +279,49 @@ func TestDoRequestWithRetry_Delay(t *testing.T) { assert.GreaterOrEqual(t, delays[2], time.Millisecond) } + +func TestRetryDelayForAttempt_DateRetryAfterUsesResponseDateHeader(t *testing.T) { + maxRetrySleepDuration = time.Minute + t.Cleanup(func() { maxRetrySleepDuration = time.Minute }) + + serverDate := time.Date(2000, 1, 2, 15, 4, 5, 0, time.UTC) + retryAfterAt := serverDate.Add(10 * time.Second) + resp := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + "Retry-After": []string{retryAfterAt.Format(http.TimeFormat)}, + "Date": []string{serverDate.Format(http.TimeFormat)}, + }, + } + + assert.Equal(t, 10*time.Second, retryDelayForAttempt(resp, 0)) +} + +func TestRetryDelayForAttempt_DateRetryAfterInvalidDateFallsBackSafely(t *testing.T) { + maxRetrySleepDuration = time.Minute + t.Cleanup(func() { maxRetrySleepDuration = time.Minute }) + + resp := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + "Retry-After": []string{time.Date(2000, 1, 2, 15, 4, 5, 0, time.UTC).Format(http.TimeFormat)}, + "Date": []string{"invalid-date"}, + }, + } + + assert.Equal(t, time.Duration(0), retryDelayForAttempt(resp, 0)) +} + +func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) { + maxRetrySleepDuration = 2 * time.Second + t.Cleanup(func() { maxRetrySleepDuration = time.Minute }) + + resp := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + "Retry-After": []string{"999999"}, + }, + } + + assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0)) +} From 345d4fddc9b2a1d96b366b0ebd3fe6421eb09d6c Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:02:16 +0800 Subject: [PATCH 54/55] utils: make retry-after numeric clamp overflow-safe --- pkg/utils/http_retry.go | 16 +++++++++-- pkg/utils/http_retry_test.go | 54 ++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/pkg/utils/http_retry.go b/pkg/utils/http_retry.go index fcd8f50f5..a808759ae 100644 --- a/pkg/utils/http_retry.go +++ b/pkg/utils/http_retry.go @@ -60,8 +60,8 @@ func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration { return clampRetryDelay(fallback) } - if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 { - return clampRetryDelay(time.Duration(seconds) * time.Second) + if delay, ok := numericRetryAfterDelay(retryAfter); ok { + return delay } if when, err := http.ParseTime(retryAfter); err == nil { @@ -78,6 +78,18 @@ func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration { return clampRetryDelay(fallback) } +func numericRetryAfterDelay(retryAfter string) (time.Duration, bool) { + seconds, err := strconv.ParseInt(retryAfter, 10, 64) + if err != nil || seconds < 0 { + return 0, false + } + maxSeconds := int64(maxRetrySleepDuration / time.Second) + if seconds > maxSeconds { + return maxRetrySleepDuration, true + } + return clampRetryDelay(time.Duration(seconds) * time.Second), true +} + func clampRetryDelay(delay time.Duration) time.Duration { if delay <= 0 { return 0 diff --git a/pkg/utils/http_retry_test.go b/pkg/utils/http_retry_test.go index 1e5ac4064..4d6021ff7 100644 --- a/pkg/utils/http_retry_test.go +++ b/pkg/utils/http_retry_test.go @@ -297,19 +297,43 @@ func TestRetryDelayForAttempt_DateRetryAfterUsesResponseDateHeader(t *testing.T) assert.Equal(t, 10*time.Second, retryDelayForAttempt(resp, 0)) } -func TestRetryDelayForAttempt_DateRetryAfterInvalidDateFallsBackSafely(t *testing.T) { - maxRetrySleepDuration = time.Minute +func TestRetryDelayForAttempt_DateRetryAfterInvalidOrMissingDateFallsBackSafely(t *testing.T) { + maxRetrySleepDuration = 30 * time.Second t.Cleanup(func() { maxRetrySleepDuration = time.Minute }) - resp := &http.Response{ - StatusCode: http.StatusTooManyRequests, - Header: http.Header{ - "Retry-After": []string{time.Date(2000, 1, 2, 15, 4, 5, 0, time.UTC).Format(http.TimeFormat)}, - "Date": []string{"invalid-date"}, + retryAfterAt := time.Now().UTC().Add(3 * time.Second).Format(http.TimeFormat) + testcases := []struct { + name string + header http.Header + }{ + { + name: "invalid-date-header", + header: http.Header{ + "Retry-After": []string{retryAfterAt}, + "Date": []string{"invalid-date"}, + }, + }, + { + name: "missing-date-header", + header: http.Header{ + "Retry-After": []string{retryAfterAt}, + }, }, } - assert.Equal(t, time.Duration(0), retryDelayForAttempt(resp, 0)) + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + resp := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: tc.header, + } + + delay := retryDelayForAttempt(resp, 0) + assert.Greater(t, delay, time.Duration(0)) + assert.GreaterOrEqual(t, delay, 1500*time.Millisecond) + assert.LessOrEqual(t, delay, 5*time.Second) + }) + } } func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) { @@ -325,3 +349,17 @@ func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) { assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0)) } + +func TestRetryDelayForAttempt_RetryAfterNumericOverflowStillCaps(t *testing.T) { + maxRetrySleepDuration = 2 * time.Second + t.Cleanup(func() { maxRetrySleepDuration = time.Minute }) + + resp := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + "Retry-After": []string{"9223372036854775807"}, + }, + } + + assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0)) +} From 711685192c6e63e0a007114c68fe3dcf4585532f Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:08:21 +0800 Subject: [PATCH 55/55] utils: gofumpt http retry formatting --- pkg/utils/http_retry.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/utils/http_retry.go b/pkg/utils/http_retry.go index a808759ae..514f9781b 100644 --- a/pkg/utils/http_retry.go +++ b/pkg/utils/http_retry.go @@ -10,8 +10,10 @@ import ( const maxRetries = 3 -var retryDelayUnit = time.Second -var maxRetrySleepDuration = 1 * time.Minute +var ( + retryDelayUnit = time.Second + maxRetrySleepDuration = 1 * time.Minute +) func shouldRetry(statusCode int) bool { return statusCode == http.StatusTooManyRequests ||

    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%0D}qhFKL1JKTuly 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�i4WMgP*b+LZW~4jbm{ppU3eA>} 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 NIDK^u^4snIe*kdWXFvb| literal 0 HcmV?d00001 diff --git a/assets/macos-gatekeeper-warning.jpg b/assets/macos-gatekeeper-warning.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c88c1fc7b9adf8a5a0c3a4dd96fc5f539e4b0520 GIT binary patch literal 195256 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