Commit Graph
343 Commits
Author SHA1 Message Date
黄云龙 8d81c61b7a test(runtime): cover JSON escapes end to end and pin the i64 boundary pair (#3305)
Reviewed and verified: 4 new tests added, all passing. 116 lines added to test JSON escape sequences and i64 boundaries. No production code changes.

—
*[repo owner's gaebal-gajae (clawdbot) 🦞]*
2026-10-13 07:14:47 +09:00
Ming LiuandClaude Opus 4.6 43d68dba75 feat: add persistent auto-memory system (#3241)
feat: add persistent auto-memory system

Implement a file-based persistent memory system that allows the AI to retain knowledge about users, feedback, projects, and references across sessions.

## Implementation Details
- MemoryStore data layer with FNV-1a workspace fingerprinting
- MemoryRead/MemoryWrite tools with security hardening (path traversal, symlink, absolute path, and size limit protections)
- System prompt injection of MEMORY.md index with prompt injection mitigation (fenced code block + trust-lowering notice)
- autoMemoryEnabled config option (defaults to true)
- Doctor health check for auto-memory state
- /memory command enhancement showing persistent memory info

## Verification
✓ 1023 lib tests pass
✓ Output format contract tests pass
✓ Release build succeeds
✓ Security hardening verified

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-13 07:13:36 +09:00
黄云龙 86428abce6 refactor(runtime): make the permission privilege ladder an explicit rank (#3303)
* fix(runtime): prompt permission mode never invokes the prompter

PermissionMode derives Ord from declaration order, placing Prompt (and Allow)
above the real privilege ladder (ReadOnly < WorkspaceWrite < DangerFullAccess).
In PermissionPolicy::authorize_with_context the ladder check
'current_mode >= required_mode' therefore evaluates true for every tool when the
active mode is Prompt (3 >= {0,1,2}), returning Allow and making the subsequent
'if current_mode == PermissionMode::Prompt' branch that routes to prompt_or_deny
unreachable.

Effect: a session configured with the 'prompt' permission mode silently
auto-allows every tool and never calls the prompter (reached via
ConversationRuntime::run_turn with an interactive prompter), defeating the mode's
entire purpose. The sibling PermissionEnforcer already special-cases Prompt
before its own 'active_mode >= required_mode' check, confirming Prompt is not
meant to be part of the ordered ladder.

Fix: exclude Prompt from the ordered-ladder comparison so Prompt mode falls
through to the interactive prompt path. Allow remains handled by its explicit
equality check. The same comparison appears twice in authorize_with_context —
once in the shared tail and once inside the 'Some(PermissionOverride::Allow)'
arm, which is reached when a hook emits permissionDecision "allow" — so both
copies carry the guard; guarding only the tail still left a hook able to
auto-allow every tool in Prompt mode without ever consulting the prompter.

Adds regression tests covering Prompt mode with a prompter, without a prompter,
and with a hook allow override.

* refactor(runtime): make the permission privilege ladder an explicit rank

`PermissionMode` derived `PartialOrd`/`Ord`, so the ladder deciding whether a
tool runs without asking was declaration order. A variant inserted in the middle
of the enum re-ranked it silently, and the difference between "this tool may
run" and "this tool may run after a prompt" was a property of where an `enum`
line happened to sit. That is how `Prompt` came to outrank `DangerFullAccess`,
satisfy `current_mode >= required_mode` for every tool, and skip the prompter.

Replace the derive with `privilege_rank`, an exhaustive `match` naming every
variant, so inserting one is a build failure rather than a silent re-ranking.
`Ord` is deliberately not derived, so a stray `>=` on two modes no longer
compiles; the two comparisons outside this module now say which rank they read
(`PermissionEnforcer::check_with_required_mode` and the `claw doctor` permission
check). Both keep their previous answers, since `privilege_rank` reproduces the
old declaration order exactly, including `Prompt` and `Allow` above the tiers.

`Prompt` and `Allow` are modes of operation, not privilege tiers, so they are
never ranked *against* a tool requirement. The new `covers` predicate states
that by name rather than by number: `Allow` always covers, `Prompt` never does
(it asks), and only the three tiers compare ranks.

That also collapses the duplicated ladder in `authorize_with_context`. The
`Some(PermissionOverride::Allow)` arm carried a second, identical copy of the
ask-rule check and the ladder; it now falls through to the single copy below,
so a hook `allow` is an override rather than a bypass and the two copies can no
longer drift apart.

Behaviour is unchanged for every (mode, requirement, rule, override)
combination. The tests pin the `Prompt` routes — plain and hook-`allow` — across
all three tiers, plus the rank ordering itself.
2026-10-13 07:04:44 +09:00
gaebal-gajae 794254597c Fix untrusted project hooks in read-only mode 2026-10-12 22:02:05 +00:00
Bellman 16ce599ef0 Merge pull request #3286 from SulimanAbdulrazzaq/fix/3278-windows-workspace-path
fix(runtime): normalize Windows workspace paths
2026-10-12 05:38:29 +09:00
Suliman Abdulrazzaq dc346498fb fix(runtime): accept other Windows spellings of a workspace path
validate_workspace_boundary compared paths lexically, so a location inside
the workspace spelled with different letter case (a working directory typed
as c:\users\... for C:\Users\...) or through the \\.\ device namespace was
reported as escaping the workspace.

When the lexical comparison fails, compare the path's canonical form before
reporting an escape. A path whose canonical form is outside the workspace
stays rejected.
2026-09-18 12:03:14 +03:00
Suliman Abdulrazzaq dbf5389729 test(runtime): gate the Windows extended-path boundary test to Windows
accepts_equivalent_extended_path_at_workspace_boundary builds its paths
with backslash separators. Backslashes only separate path components on
Windows, so on Linux each path is a single component, the boundary check
rejects the path, and cargo test fails.

Run the test only on Windows, and call validate_workspace_boundary through
super:: so non-Windows test builds do not warn about an unused import.
2026-09-17 13:15:05 +00:00
YeonGyu-Kim 08106b0c37 docs: add hierarchical AGENTS.md knowledge base
Root knowledge base plus complexity-scored subdirectory files for the
rust/ workspace, its five highest-mass crates (runtime, rusty-claude-cli,
api, tools, commands, plugins), and the src/ Python porting workspace.

Generated via init-deep: 13 parallel explore agents, LSP/ast-grep code
map, centrality-scored placement. Snapshot in .omo/init-deep.json (local).
2026-08-16 15:18:33 +09:00
Suliman Abdulrazzaq 9e7fd601a5 fix(runtime): normalize Windows workspace paths
Keep extended-length and regular Windows paths comparable when enforcing workspace boundaries, including first-run roots that cannot yet be canonicalized. Add regression coverage for DOS and UNC path forms.

Signed-off-by: Suliman Abdulrazzaq <suliman9000a@gmail.com>
2026-08-09 15:14:37 +03:00
code-yeongyuandlinkst 525035b51b fix(sandbox): probe the full launcher shape, not just the mapping
The startup probe validated only the mapping flags against the trivial
program `true`, but the real launcher always adds
--mount --ipc --pid --uts --fork. On environments where the user
namespace is created but mount propagation inside it is restricted
(e.g. AppArmor-restricted CI runners), the fallback mapping passed the
probe and the sandbox activated, yet every sandboxed command died with
"cannot change root filesystem propagation: Permission denied",
silently returning empty tool output and breaking the mock parity
suite.

The candidates now define the complete static launcher shape (mapping
flags + namespace flags), so probe success implies launch success; the
launcher reuses the candidate instead of re-appending the namespace
flags, keeping probe and launch as one source of truth. The
order-guarding test asserts the namespace flags are present in every
candidate.

Co-authored-by: linkst <2024023709@m.scnu.edu.cn>
2026-08-06 19:41:27 +09:00
linkst 9cbe6d9a8c docs(sandbox): document newuidmap/newgidmap dependency for --map-auto fallback
The fallback candidate relies on the setuid newuidmap/newgidmap helpers
(uidmap package) plus a subuid/subgid range for the current user. Note in
the candidate docs that the startup probe rejects the candidate when those
are missing, so the plain --map-root-user form is used instead.
2026-08-01 15:21:56 +08:00
linkst 277fdda894 fix(sandbox): fall back to --map-auto when root-user mapping is restricted
Plain `unshare --user --map-root-user` fails on kernels and containers
that block unprivileged writes to /proc/self/uid_map (e.g. GitHub Actions,
restricted AppArmor profiles). On those systems util-linux delegates to the
setuid newuidmap/newgidmap helpers when --map-auto is also present.

Add the combined form as a fallback candidate and build the launcher args
from the probed mapping, so systems without newuidmap/newgidmap or a
/etc/subuid range keep using the plain form.
2026-07-31 17:09:00 +08:00
linkst 1c8338ea66 refactor(sandbox): extract unshare mapping probe into cached helper
No behavior change. Move the inline probe out of
unshare_user_namespace_works into a reusable unshare_probe helper and a
cached working_unshare_mapping() that picks the first working candidate
from UNSHARE_MAPPING_CANDIDATES, so the launcher and the capability probe
share one code path.
2026-07-31 17:08:51 +08:00
YeonGyu-Kim d229a9b022 Merge pull request #3227 from TheArchitectit/worktree-wizard-entry-points
feat: wizard entry points — /setup command, claw setup subcommand (rebased)
2026-06-08 15:06:11 +09:00
YeonGyu-Kim 05f0201ec7 fix: preserve runtime config validation compatibility 2026-06-08 15:01:48 +09:00
YeonGyu-Kim a1da1ca8e6 test(cli): serialize env-sensitive model alias checks 2026-06-08 01:37:28 +09:00
YeonGyu-Kim 27acfe1014 test(runtime): isolate session and git metadata checks 2026-06-08 01:23:32 +09:00
TheArchitectit d58197cca4 fix: update slash command count and add /setup assertion in test
- Update slash_command_specs().len() assertion from 139 to 140.
  The /setup command added by this PR increased the spec count by 1
  but the test's expected count was not updated, causing CI failure.

- Add assert!(help.contains("/setup")) to the
  renders_help_from_shared_specs test so the new command is
  verified in the help output.

  Fixes CI Build ❌ and Test ❌ on #3218.
2026-06-04 22:06:32 -05:00
Your NameandClaude Opus 4.8 3845040b9d feat: wizard entry points -- /setup command, claw setup subcommand, and RuntimeProviderConfig
The setup wizard was merged in PR #3017 but was orphaned -- it was not
declared as a module in main.rs, making it unreachable. Additionally,
the setup_wizard.rs imports RuntimeProviderConfig which did not exist
on upstream/main. This commit makes the wizard accessible and adds the
necessary RuntimeProviderConfig type.

Changes:
- Add RuntimeProviderConfig struct to runtime/src/config.rs with
  kind(), api_key(), base_url(), model() accessors.
- Add parse_optional_provider_config() to parse the provider object
  from merged settings JSON.
- Add provider() method to RuntimeConfig and RuntimeFeatureConfig.
- Export RuntimeProviderConfig, save_user_provider_settings,
  clear_user_provider_settings, and default_config_home from runtime
  crate public API (runtime/src/lib.rs).
- Add mod setup_wizard to rusty-claude-cli/src/main.rs.
- Add claw setup CLI subcommand.
- Add /setup slash command.
- Add Setup variant to SlashCommand enum.
- Add Setup to LocalHelpTopic enum.
- Add setup to diagnostic subcommand matching.
- Add subagentModel to TOP_LEVEL_FIELDS in config_validate.rs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:52:48 -05:00
bellmanandGajae Code 503d515f38 style: cargo fmt after merged PRs (#3164, #3209, #3214, #3216)
Fix formatting inconsistencies introduced by merged external PRs.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 10:52:37 +09:00
Bellman 114c2da9fc Merge pull request #3216 from TheArchitectit/worktree-session-resume-fixes
fix: session resume — scan all workspaces, skip current empty session
2026-06-05 10:38:19 +09:00
Bellman b90f18f75a Merge pull request #3214 from TheArchitectit/worktree-api-timeout-retry-v2
feat: API timeout config, Retry-After header, configurable retry, and 400 transient retry
2026-06-05 10:33:35 +09:00
Bellman 5b15197117 Merge pull request #3209 from Sam0urr/harden-permission-enforcer
Harden permission enforcement against sandbox bypasses
2026-06-05 10:32:50 +09:00
Bellman ef0d0c30a4 Merge pull request #3164 from petterreinholdtsen/llama.cpp-errors
fix: recover from llama.cpp context overflow and reqwest SSE decode failures
2026-06-05 10:25:54 +09:00
bellman 2f0b5b36eb fix: wrap concurrent ENOENT as domain-specific session error (#112)
Session save_to_path now wraps ENOENT errors from rotate and atomic
write with a clear "possible concurrent modification" message instead
of surfacing raw OS errno. Helps operators debugging race conditions
when multiple claw invocations touch the same session file.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 10:19:43 +09:00
bellman 7c4bcd92b6 fix: expand ${VAR} and ~/ in MCP config fields (#92)
MCP server config now expands ${VAR} environment variable references
and ~/ home directory prefix in command, args, and url fields. Previously
these values were passed verbatim to execve/URL-parse, causing silent
"No such file or directory" failures for standard config patterns.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 10:00:45 +09:00
bellman f8822aabdb fix: config merge concatenates arrays instead of replacing (#106)
deep_merge_objects now concatenates arrays when both layers provide
the same key. Previously permissions.allow, hooks.PreToolUse, etc.
from earlier config layers (e.g. ~/.claw/settings.json) were silently
discarded when a later layer (e.g. project .claw/settings.json) set
the same key. Now arrays are merged additively across layers.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 09:57:22 +09:00
bellman aca6584fd5 fix: normalize permission rule tool names to lowercase (#94)
PermissionRule::parse now normalizes tool_name to lowercase, matching
the runtime convention. Previously "Bash(rm:*)" would never match
because the runtime tool name is lowercase "bash". Same fix applied
to denied_tools list.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 09:53:26 +09:00
bellman 4d4d72cd49 fix: add prefix-aware matching to config key suggestion
When input is a prefix of a candidate (e.g., mcp → mcpServers), return
the prefix match directly instead of relying on edit-distance which
would incorrectly suggest env (distance 3) over mcpServers (distance 7).

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-05 01:36:35 +09:00
bellman 453d8945bb fix: validate hook config entries partially
Hook config now supports the Claude Code structured hook format with
partial validation. Invalid hook entries are recorded in invalid_hooks
while valid siblings are retained, following the same pattern as MCP
partial validation (#440).

Key changes:
- RuntimeInvalidHookConfig now includes typed kind field (invalid_hooks_config
  or unknown_hook_event) for machine-readable error classification
- Hook parsing collects all invalid entries instead of halting at first error
- Unknown hook event names recorded as invalid without rejecting valid hooks
- Legacy bare-string hooks still load with deprecation warnings
- Claude Code documented format loads without error (matcher + nested hooks)
- config/status/doctor JSON surfaces hook_validation metadata
- classify_error_kind maps hook errors to invalid_hooks_config

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-04 23:42:58 +09:00
TheArchitectit 9e50cb6e20 Merge remote-tracking branch 'upstream/main' into worktree-api-timeout-retry-v2
# Conflicts:
#	rust/crates/runtime/src/config.rs
#	rust/crates/runtime/src/lib.rs
2026-06-04 09:17:43 -05:00
TheArchitectit 346772a8b3 test: add exclude_id and 0-message filtering coverage; cargo fmt 2026-06-04 09:17:42 -05:00
TheArchitectit ef392e5938 Merge remote-tracking branch 'upstream/main' into worktree-session-resume-fixes 2026-06-04 09:15:19 -05:00
bellmanandGajae Code 4619375c14 fix: load partial MCP configs
Generated with https://github.com/Yeachan-Heo/gajae-code

Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-04 18:31:58 +09:00
bellmanandGajae Code 10fe72498a fix: bound parent memory discovery
Generated with https://github.com/Yeachan-Heo/gajae-code

Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-04 17:07:00 +09:00
bellmanandGajae Code 5b22bc0480 fix: load Claw and Agents memory files
Generated with https://github.com/Yeachan-Heo/gajae-code

Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-04 16:36:04 +09:00
bellmanandGajae Code d8535bf938 fix: keep failed resume side-effect free
Generated with https://github.com/Yeachan-Heo/gajae-code

Co-authored-by: Gajae Code <dev@gajae-code.com>
2026-06-04 15:08:56 +09:00
TheArchitectit 41034bb3f3 fix: address CI test failure and add empty-session error message
- Fix latest_session_alias_resolves_most_recent_managed_session test:
  the test created sessions with 0 messages, which are now filtered out
  by the message_count > 0 check in latest_session_excluding(). Updated
  the test to call push_user_text() before saving so sessions have
  at least one message and are findable by /resume latest.

- Add distinct error message when all sessions are empty (0 messages).
  Previously, the same "no managed sessions found" message was returned
  whether there were zero sessions or all sessions had 0 messages. Now:
  - No sessions at all → "no managed sessions found in {path}. Start
    claw to create a session..."
  - Sessions exist but all empty → "all sessions are empty (0 messages)
    in {path}. This usually means a fresh claw session is running but
    no messages have been sent yet. Wait for a response in your other
    session, then try --resume latest again."

- Add test for the all-sessions-empty error path.

  Addresses reviewer feedback on #3216.
2026-06-03 13:44:20 -05:00
bellman 94be902ce1 fix: attribute config precedence in JSON 2026-06-03 23:47:27 +09:00
bellman 36218ac1b1 fix: report config file load statuses 2026-06-03 21:46:47 +09:00
bellman 6388a2ba3f fix: parse object-style hook config 2026-06-03 21:23:00 +09:00
bellman 9c8375da99 feat: import project instruction rules 2026-06-03 21:01:48 +09:00
Heo, Sung 0cef5390f7 fix: resolve clippy pedantic warnings
Apply the bounded clippy pedantic cleanup from PR #3009.
2026-06-03 20:39:05 +09:00
bellman d07664b44c fix: keep hooks clean and close bash stdin 2026-06-03 20:20:04 +09:00
bellman e752b05425 fix: load common instruction files and typed unknown commands 2026-06-03 18:54:36 +09:00
TheArchitectitandClaude Opus 4.8 e459a727e9 fix: session resume — skip current empty session, unify cross-workspace loading
Three improvements to the /resume command:

1. /resume latest now skips the current empty session
   When a new session is created on startup (with 0 messages), /resume
   latest previously returned that empty session. Now it skips sessions
   with message_count == 0 and excludes the current session ID via the
   new exclude_id parameter, so it finds the previous session with
   actual conversation history.

2. Unified load_session_excluding() replaces load_session_loose()
   The previous load_session_loose() only handled cross-workspace
   resume for aliases. The new load_session_excluding() combines the
   loose workspace validation logic with the exclude_id parameter,
   simplifying the call chain and ensuring all resume paths skip the
   current empty session when appropriate.

3. All existing session scanning paths (global root + project-local
   .claw/sessions/) are already in place from prior commits, and now
   the exclude_id filter is applied consistently across both local
   and global session scans.

Changes:
- session_control.rs: Add resolve_reference_excluding() that delegates
  from resolve_reference(), adding optional exclude_id filtering for
  alias references.
- session_control.rs: Add latest_session_excluding() that delegates
  from latest_session(), filtering out excluded session IDs and
  sessions with 0 messages in both local and global scan paths.
- session_control.rs: Add load_session_excluding() that replaces
  load_session_loose(), combining cross-workspace alias handling with
  the exclude_id parameter.
- main.rs: Add load_session_reference_excluding() that delegates from
  load_session_reference(), using the new store method.
- main.rs: Wire LiveCli::resume_session() to pass the current session
  ID as the exclude_id so /resume latest skips the current empty
  session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:49:21 -05:00
TheArchitectit 04bc5f5788 feat: API timeout config, Retry-After header, configurable retry, and 400 transient retry
Cherry-picked from PR #2816 onto current upstream/main, resolving
conflicts from PR #3015's merge (which added retry_after to ApiError
but some construction sites were missing it).

Commits preserved:
- ade85398: API timeout config, Retry-After header, configurable retry
  - TimeoutConfig in HTTP client builder (connect 30s, request 5min)
  - CLAW_API_CONNECT_TIMEOUT and CLAW_API_REQUEST_TIMEOUT env vars
  - Retry-After header parsing on 429 responses
  - ApiTimeoutConfig in runtime config (settings.json)
- 8a883430: retry 400 responses with transient gateway error bodies
  - Detects known gateway phrases in 400 response bodies
  - Marks them as retryable instead of hard-failing
- ed91a61e: add 'no parseable body' to CONTEXT_WINDOW_ERROR_MARKERS
  - Some providers return 400 with 'no parseable body' for oversized
    requests instead of a proper context_length_exceeded error

Commits skipped (already in upstream via PR #3015):
- 453ab642: optional id field (already merged)
- baa8d1ba: HTML detection in streaming (already merged)
- 33d2f789: JSON error detection in streaming (already merged)

8 files changed, 299 insertions, 80 deletions
2026-06-02 15:35:29 -05:00
TheArchitectit d8c57ed317 feat: API timeout config, Retry-After header support, and configurable retry
- Add TimeoutConfig to HTTP client builder with connect_timeout (30s)
  and request_timeout (5min) defaults, configurable via
  CLAW_API_CONNECT_TIMEOUT and CLAW_API_REQUEST_TIMEOUT env vars
- Add with_timeout() builder to both AnthropicClient and
  OpenAiCompatClient for per-client timeout configuration
- Parse Retry-After header on 429 responses and use it to override
  exponential backoff delay when present
- Add ApiTimeoutConfig to runtime config with apiTimeout settings
  in ~/.claw/settings.json (connectTimeout, requestTimeout, maxRetries)
- Add retry_after field to ApiError::Api for propagating rate limit
  backoff hints through the retry pipeline
2026-06-02 15:30:22 -05:00
Sam LamrabteandClaude Opus 4.8 e8c8ef1142 Harden permission enforcement against sandbox bypasses
Close two ways the permission system could be bypassed:

- Workspace path traversal: normalize `.`/`..` lexically before the
  boundary prefix comparison so paths like `/workspace/../../etc` can no
  longer escape the sandbox. Fixed in both the runtime enforcer and the
  duplicate check in the tools PowerShell path classifier.
- read-only mode no longer trusts the leading token alone: reject shell
  metacharacters (chaining/substitution/redirect/pipe/subshell), drop
  interpreters and build drivers (python/node/ruby/cargo/rustc) from the
  allow-list, gate `git` to non-mutating subcommands, and reject `find`
  actions that execute or delete.

Adds regression tests for both holes. The pre-existing, unrelated
worker_boot git-metadata test failure is not affected by this change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 13:26:39 +02:00
YeonGyu-Kim de7edd5bb1 fix: suppress config deprecation stderr in JSON mode globally (#824)
Add SUPPRESS_CONFIG_WARNINGS_STDERR AtomicBool flag in runtime/config.rs
and expose suppress_config_warnings_for_json_mode() via runtime crate.

In main.rs, scan raw argv for --output-format json before parse_args
and activate the flag so no settings-load warnings reach stderr on any
JSON-mode surface (status, sandbox, system-prompt, mcp list, skills list,
agents list, --resume /config*, etc.).

Text-mode surfaces are unaffected; prose deprecation warnings continue
to appear on stderr.

All 572+ tests pass (one pre-existing worker_boot failure unrelated).
2026-05-29 14:00:32 +09:00