Commit Graph

5191 Commits

Author SHA1 Message Date
Mohamed Boudra 4e60c2880b chore(release): cut 0.7.0-beta.2 v0.7.0-beta.2 2026-08-28 18:35:49 +02:00
Mohamed Boudra 510074f555 docs: add v0.7.0-beta.2 changelog 2026-08-28 18:28:12 +02:00
Christoph Leiter ed628ff82e feat(app): add workspace actions to the command center (#3013)
Eight actions were only reachable through the sidebar workspace menu,
the workspace header menu, or a keybind you had to remember: Rename
workspace, Copy workspace path, Copy branch name, Pin/Unpin, the three
layout toggles, and Show setup. All eight are now in the command
center, plus a new one: label assignment. Type a label's name to
assign or unassign it on the active workspace without leaving the
keyboard.

Rename needed more than a palette entry. Its dialog lived inside the
sidebar row component, so it disappeared whenever that row was not
rendered: a collapsed project or status group, a collapsed Pinned
section, or focus mode. It now has a global host keyed on the active
workspace selection, mirroring use-global-workspace-pin-action.ts.

Each entry reuses the handler the existing menu item already calls, so
behaviour matches the menu. Entries that cannot act are omitted rather
than present and failing: Copy branch name without a branch, Pin when
the host lacks workspacePinning, Show setup when there is nothing to
show, and labels when the catalog has not loaded.

Toggle Explorer sidebar and Toggle focus mode are registered as
workspace contributions, not global ones. Their handlers live in
workspace-screen.tsx behind an isRouteFocused gate, so a global
registration would have listed two entries that close the palette and
do nothing on /settings, /sessions, /schedules and Home.

The copy-path, copy-branch, and rename-dialog logic was duplicated
across three sidebar files. It moves into one hook and one component so
the palette is not a fourth copy. That also fixes drift in
sidebar-status-list.tsx, where Copy branch name copied the workspace
name instead of the branch and four strings were hardcoded English.
This supersedes #3383, which fixes the same bug directly without the
shared-hook refactor.

Toggle focus mode also had a hidden duplicate: buildPaneContributions
already dispatches the same action once split panes are available, so
the standalone entry now steps aside there instead of listing the
toggle twice.

No new i18n keys.
2026-08-28 17:43:46 +02:00
Mohamed Boudra 667664847d Connect to remote daemons over SSH (#3989)
Add transport-only SSH connectivity for the CLI and Electron desktop through the user's OpenSSH configuration. Remote installation and daemon lifecycle remain explicit, separate operations.

Preserve actionable SSH failure details when the proxy socket closes before the SSH child exits.

Closes #3949

Co-authored-by: James Strain <jstrain@nvidia.com>
Co-authored-by: Reid Levesque <reidlevesque@users.noreply.github.com>
2026-08-28 16:32:12 +02:00
Mohamed Boudra 7ea4288117 Keep restored workspaces out of reconciliation loops (#3987)
* fix(app): stop workspace tab reconciliation loop

Duplicate startup tabs could move workspace focus into Explorer. Once hydration removed the stale agent, empty-workspace reconciliation kept seeding drafts into Explorer and retriggering itself. Preserve ordinary-pane focus at the reconciliation boundary.

* fix(app): normalize workspace focus before reconciliation

Persisted Explorer focus must not steer newly reconciled tabs into the sidebar before the output invariant is applied.
2026-08-28 15:50:44 +02:00
Mohamed Boudra 42b6d99d60 Wait for in-flight dictation commits before finishing (#3968)
* fix(dictation): preserve audio queued at stop

Stopping capture disabled native recording before the final microphone buffer had crossed the bridge, so accepting immediately could omit the end of a dictation. Wait for the native producer to publish its final buffer before completing stop, and serialize stop with restart and destruction.

* fix(voice): serialize capture lifecycle

A capture start already awaiting permission could resume after teardown, and repeated destroy calls did not share completion. Order capture lifecycle operations behind one promise and build the native module declarations before clean-CI typechecking.

* fix(dictation): drain queued web capture before finish

* fix(dictation): await in-flight segment commits

* Revert "fix(dictation): drain queued web capture before finish"

This reverts commit 5168a6554d.

* Revert "fix(voice): serialize capture lifecycle"

This reverts commit 00fd3e3556.

* Revert "fix(dictation): preserve audio queued at stop"

This reverts commit ec087dc9cd.
2026-08-28 15:29:42 +02:00
paseo-bot[bot] 23debf3af1 fix(server): honor configured commit signing (#3976)
Paseo forced signing off for commits created through the app and squash merges. Let Git apply the user's configured policy; the existing command timeout still bounds interactive signer waits.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-28 19:24:56 +08:00
Mohamed Boudra 25eb322ef7 fix(app): prevent archived workspaces returning from cache (#3975)
Durable replica reads could race accepted directory replacements and restore rows that had already been removed in memory. Serialize reads behind pending writes and discard reads when the host changes in flight; local archives also suppress targeted cache restoration until removal is acknowledged.
2026-08-28 13:18:26 +02:00
Mohamed Boudra deb846eed6 Let plugins use Paseo modals, icons, and toasts (#3967)
* feat(plugins): add React Native UI primitives

Give client plugins host-owned adaptive UI without exposing app internals. Preserve plugin runtime contexts when modal content moves through the compact portal host.

* docs(plugins): clarify host UI reference

* refactor(plugins): harden host UI boundary

Replace mocked pass-through coverage with a real browser plugin journey, keep host runtime ownership inside the evaluator, preserve toast context across compact portals, and expose modal content as a compound component.

* fix(plugins): defer native UI presentation runtime

Loading the plugin evaluator initialized the modal and toast presentation stacks even when no plugin requested host UI. Keep the capability stable while delaying platform-heavy presentation code until Modal.Content renders.

* refactor(plugins): move modal metadata to root

Keep Modal.Content body-only while the top-level Modal owns the host-rendered title and leading icon. This leaves presentation metadata stable as the compound API gains future regions.
2026-08-28 11:43:30 +02:00
Desmond Chai a1ed5a8bad Stop PR status polling from rate-limiting your entire GitHub account (#3825)
* Batch GitHub PR status polls into aliased GraphQL requests

The PR status poller ran one `gh pr view` (GraphQL-backed) plus a
per-PR facts query for every workspace on its own timer, so cost
scaled linearly with workspace count and exhausted the 5,000 pts/hour
GraphQL quota machine-wide past ~50 workspaces (#3587, #2470).

Replace the per-target timers with a global scheduler that snaps poll
targets to a shared 5s grid and flushes all due targets together:

- One aliased discovery query per batch of up to 25 targets (grouped
  by GH host) resolves PR identity, state, mergeability, and the merge
  facts that previously required a second GraphQL call per PR.
- A second aliased query fetches statusCheckRollup contexts only for
  the PRs that matched. Checks for merged/closed PRs are frozen and
  cached by repo#number@headOid, so idle workspaces settle to the
  discovery query alone.
- A failed batch backs off every target in it with one request spent;
  healthy aliases are recovered from the body gh still prints on
  stdout when a single alias errors (ForgeCommandError now carries
  stdout for this).
- Targets whose origin slug/host is still resolving, and fork repos
  whose PR lives in the parent, fall back to the legacy per-target
  path for that cycle. Unowned lookups filter out other forks' PRs
  that share a branch name.

Fixes #3587. Addresses the API budget in #2470.

* fix(server): reserve the shared GitHub polling budget

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-28 11:29:34 +02:00
Christoph Leiter 3d69b0f458 fix(app): render markdown verbatim instead of substituting characters (#3253)
markdown-it's typographer option rewrites characters after parsing, so
a literal (c) came out as ©. Agents label steps and options (a), (b),
(c) all the time, and an ordinary list rendering as (a), (b), © stops
your eye every time.

The same option turns ... into an ellipsis, --- into an em dash, and
straight quotes into curly ones. Agent output is text people paste
back into a shell or a file, so a curly quote in --name="my repo"
looks correct on screen and fails when pasted. The markdown file
preview had the same problem, showing characters that are not in the
file on disk.

Every in-app markdown surface now goes through one factory in
utils/markdown-parser.ts, which leaves typographer off: agent chat,
plan cards, pull request comment bodies and the file preview. The plan
card was rendering through its library's built-in parser because it
never passed a markdownit prop, and that default has typographer on.

Link behaviour is unchanged. The assistant parser keeps its file://
permission and no other surface gains it, and linkify stays on for
chat and the default renderer and off for plan cards, matching what
each did before.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:58:27 +00:00
Christoph Leiter 51a435d3cf feat(app): find workspaces by PR/MR number in the command center (#3008)
* feat(app): find workspaces by PR/MR number in the command center

The command center matched workspaces on title, host, project, and
branch. Typing the change-request number you actually remember found
nothing, so you had to recall the branch name instead.

Workspace results now carry their pull/merge request number, and a
numeric query resolves it directly: 42, #42, !42, pr 42, and mr 42 all
reach the same workspace. Matching compares the parsed number exactly
rather than as a substring, so 42 no longer drags in PR 142 or a branch
that happens to contain those digits. Exact number hits sort above
ordinary text matches for the same query.

Nothing changes visually. The number is a search key only; result rows
render exactly as before.

The number is read from the pull request the daemon already sends.
selectPrHintFromStatus is the fallback only: it derives the number by
parsing the PR/MR URL, which yields nothing for a noncanonical or
absent URL even when the daemon sent a usable number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(app): exercise change-request parsing through the matcher

parseChangeRequestQuery was exported only so the test could reach it,
which made an implementation helper part of the module surface and let
the parser cases pass without covering how the parser composes with
workspace matching.

Unexport it and drive the same spellings through matchWorkspaceQuery.
The rejection cases now assert what they actually protect: a query like
fix-42-retries must not take the number path, so a workspace whose PR is
42 does not match it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:50:31 +00:00
Mohamed Boudra fb67d2cdc8 Keep the daemon responsive during Git queue pressure (#3945)
* fix(server): keep Git pressure from stalling daemon

Git process starts were refilled synchronously, so slow process spawning could monopolize the main loop long enough to trip connection liveness checks. Admit one process per event-loop turn and expose refresh/fetch provenance in runtime queue metrics.

* perf(server): avoid delaying isolated Git commands

Start the first command immediately when the scheduler is idle. Backlog admission and completion-driven refills still yield between spawns, preserving daemon liveness without adding a turn to normal sequential Git traffic.

* fix(server): keep Git admission outside fake clocks

Subprocess admission is event-loop ordering rather than simulated wall-clock behavior. Use the native Node immediate so service tests can fake debounce timers without deadlocking queued real Git processes.

* refactor(server): bind Git command provenance explicitly

* test(server): expect bound Git fetch runner
2026-08-28 10:25:20 +02:00
Mohamed Boudra fe803f6e70 Let plugins contribute contextual composer pills (#3956)
* feat(plugin): allow composer pill contributions

Keep pill presentation host-owned while plugin components render contextual content. Pill callbacks reuse the selected host's API, RPC, surface, and registered-panel navigation capabilities without exposing router or workspace layout internals.

* fix(plugin): place composer pills in track bar

* fix(plugin): make composer pills client-owned
2026-08-28 16:02:00 +08:00
Gustavo Ambrozio a501e1e051 feat(sdk): expose a session's commands and full snapshot on the agent handle (#3719)
* feat(sdk): expose a session's own commands on the agent handle

`DaemonClient.listCommands` has always been able to ask a running session
which slash commands and skills it loaded, but `PaseoAgentHandle` never
surfaced it, so SDK and plugin consumers could only see what they could
find on disk. Provider built-ins — the ones bundled inside the agent
binary rather than installed into a scannable directory — were therefore
invisible to them.

Add `agent.commands()` as a thin delegation to the existing RPC. Every
provider that implements `listCommands` (claude, codex, opencode, acp,
omp, pi) answers it, so a consumer no longer needs per-provider
filesystem knowledge to enumerate what a session can run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(sdk): expose the observed agent snapshot on the agent handle

The handle mirrored four snapshot fields (workspaceId, cwd, status, id)
and hid the rest behind current(). Add getters for capabilities,
availableModes, pendingPermissions, activeTurn, lastUsage, lastError,
features, runtimeInfo, and archivedAt so callers can read one field
without destructuring the whole snapshot and null-checking it.

Each reads the last observed snapshot and never fetches, matching the
existing getters. null means "not observed yet".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(sdk): clarify nullable snapshot getters

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: marvin-ambrozio <marvin@gustavo.eng.br>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-28 07:09:20 +00:00
paseo-ai[bot] 1fc5250544 fix: update lockfile signatures and Nix hash [skip ci] 2026-08-27 18:24:46 +00:00
Mohamed Boudra 1860a6f3af chore(release): cut 0.7.0-beta.1 v0.7.0-beta.1 2026-08-27 19:42:09 +02:00
Mohamed Boudra b6641d7066 docs(changelog): prepare 0.7.0-beta.1 2026-08-27 19:33:45 +02:00
Mohamed Boudra 5c7d62df8c revert(acp): restore prior turn handling 2026-08-27 18:11:37 +02:00
Mohamed Boudra 96cb70f5ce Restore rewound prompts immediately on iOS (#3946)
* fix(app): restore rewound text atomically

The native composer could consume a replacement revision before the matching draft-store value arrived, leaving the mounted input blank until remount. Carry the replacement text with its revision so scheduling order cannot split the command from its payload.

* fix(app): isolate launch composer drafts

Give chat and terminal launch composers separate React identities so each uncontrolled editor lifetime remains owned by its draft mode.
2026-08-27 16:50:18 +02:00
Mohamed Boudra a8734a9724 Update project licensing (#3944) 2026-08-27 14:25:31 +02:00
Mohamed Boudra cda441d3b2 fix(app): prevent composer height flash after settings (#3943)
Retained workspace panels collapse to zero width while Settings is visible. Treat that geometry as an absent surface so hidden measurements cannot overwrite the last valid composer height before reveal.
2026-08-27 13:58:05 +02:00
smagnuso 2d7896999c Add steering support for ACP providers that indicate support (#3921)
* Add steering support for ACP providers that indicate support

* fix(acp): keep steering inside the admitted turn

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-27 11:05:16 +00:00
Mohamed Boudra 12e3acd188 fix(plugins): restore native plugin setup flows (#3942)
Plugin client bundles bypass Metro, so authored async callbacks reached Hermes without Metro's syntax lowering. Plugin connection inputs also selected bottom-sheet behavior from device form factor instead of their actual render scope.
2026-08-27 12:50:25 +02:00
Omer Cohen 8e0af11b66 feat(plugin): add host-rendered Icon component (#3903) 2026-08-27 10:50:20 +00:00
Caique Maurano 3377997f30 (feat): Open Child Folders as Apps (port from conductor) (#3615)
* feat(app): open child folders in preferred editor

* fix(app): hide unavailable desktop open targets

* fix(app): test desktop targets without JSDOM

* refactor(app): isolate open-in-editor feature
2026-08-27 10:35:17 +00:00
Mohamed Boudra 7ae5133ed2 feat(plugins): let plugins transform and render timeline items (#3940)
* feat(plugins): add timeline item contributions

Keep canonical daemon history unchanged while plugins project and render custom timeline items in clients. Live matches refresh authoritative projected history before replacement so lifecycle deltas stay collapsed.

* fix(nix): update npm dependency hash

The plugin SDK lockfile change invalidated the fixed-output npm dependency derivation. Use the hash calculated by the macOS Nix build for the current lockfile.
2026-08-27 11:40:17 +02:00
Christoph Leiter b6d18d0c01 fix(desktop): make desktop-settings.json forward-compatible (#3909)
Preserve unmodelled keys at the document, settings, nested settings, and migration levels when an older desktop build writes known settings.

Validate known fields independently and keep the public DesktopSettings value limited to fields this build understands.
2026-08-27 10:32:27 +02:00
Will Daubney 4e52125f5b fix(app): show selected workspace in light theme (#3922)
Co-authored-by: Will Daubney <16829568+wdaubney@users.noreply.github.com>
2026-08-27 08:15:24 +00:00
Omer Cohen f82886f284 feat(plugin): expose semantic theme tokens (#3898) 2026-08-27 08:15:22 +00:00
PM b0ce40804d fix(acp): enable terminal client capability by default (#3910)
* fix(acp): enable terminal client capability by default (#3812)

* test(acp): remove non-asserting Kimi coverage

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-27 08:10:25 +00:00
Christoph Leiter 2755596b9f fix(app): align the scrolling diff order with the Changes tree (#3913)
The Changes view listed the same files in two different orders. The
tree rail put directories before files at each level of the path.
The scrolling diff compared the full path byte by byte. A root file
such as .gitlab-ci.yml therefore opened the diff, but sat below every
directory in the tree. Scrolling the diff did not walk the tree from
top to bottom.

Two sorts caused this. sortTree in git/diff-tree.ts sorted the tree.
compareCheckoutDiffPaths in git/diff-order.ts sorted the flat list at
ingest.

The tree is now the only ordering authority.
orderCheckoutDiffFiles builds the tree and reads the file sequence
back out of it. compareCheckoutDiffPaths is deleted, so the two
surfaces can no longer disagree.

The daemon keeps its own whole-path sort, so daemons that are not
updated also get this fix. Commit diffs keep the order that git
gives them, because that panel shows no tree rail.
2026-08-27 08:00:25 +00:00
Omer Cohen 657152c47a feat(client): expose project list in public SDK (#3899) 2026-08-27 07:54:20 +00:00
Omer Cohen 4c46f20179 docs(plugins): document client runtime contract (#3900) 2026-08-27 07:43:24 +00:00
Antonio Santos da930b4c7d Fix Spanish translations for close actions (#3934) 2026-08-27 07:42:48 +00:00
Mohamed Boudra ecfb77b014 feat(plugins): install and update Git sources (#3920)
Make every trusted Git repository directly distributable without introducing a registry or package manager. Managed checkouts retain their resolved commit, track branches explicitly, and stage validation before activation so failed updates restore the running version.
2026-08-27 15:24:01 +08:00
Omer Cohen 4de913fb82 fix(server): plugin daemon sessions see all providers by pinning the daemon appVersion (#3902)
* fix(server): expose all providers to plugin sessions

* fix(app): route workspace actions semantically (#3896)

* fix(app): route workspace actions semantically

Separate Explorer visibility from view navigation, and give composer changes and pull request opens semantic routing owners. Unify diff destinations, keep compact PR navigation in Explorer, and derive maximize availability from visible workspace panes.

* test(app): cover composer changes routing

Update the browser journey for the Explorer-first composer action, then assert the second press follows the shared diff destination.

* test(server): track plugin runtime dependency parameter

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-26 20:57:22 +00:00
Mohamed Boudra 0ad69d3eb0 fix(app): paint conversations immediately after restart (#3907)
* fix(app): paint cached conversations before reconnect

Cold startup restored every saved host before publishing any session, so a valid cached chat was blocked behind unrelated directory validation and network-only readiness. Directory and timeline owners now resolve demand through typed storage and authoritative reconciliation while consumers remain source-agnostic.

* fix(app): stabilize demand-driven cache restore

Create host projections before network availability, route cached workspace and timeline preparation through their owners, and coalesce directory reconciliation per online connection. Update browser cache fixtures for per-agent timeline rows.

* fix(app): resume cached timeline through owner

* fix(app): surface timeline owner load errors
2026-08-26 22:55:48 +02:00
Mohamed Boudra 46b401eac6 fix(app): route workspace actions semantically (#3896)
* fix(app): route workspace actions semantically

Separate Explorer visibility from view navigation, and give composer changes and pull request opens semantic routing owners. Unify diff destinations, keep compact PR navigation in Explorer, and derive maximize availability from visible workspace panes.

* test(app): cover composer changes routing

Update the browser journey for the Explorer-first composer action, then assert the second press follows the shared diff destination.
2026-08-26 15:29:17 +02:00
paseo-bot[bot] 96cf0f2ae5 chore(server): upgrade Claude Agent SDK to 0.3.246 (#3893)
* chore(server): upgrade Claude Agent SDK to 0.3.246

Bumps @anthropic-ai/claude-agent-sdk from 0.3.220 to 0.3.246, keeping the
pinned exact version so fresh installs stay reproducible.

The SDK's peer requirements are unchanged (@anthropic-ai/sdk >=0.93.0,
@modelcontextprotocol/sdk ^1.29.0, zod ^4.0.0) and already satisfied, so
nothing else in the tree moves.

Nothing Paseo imports was removed or narrowed. The type surface Paseo
touches is additive; the removed ExitReason member
'bypass_permissions_disabled' and the OnElicitation/OnUserDialog signature
changes are unused here.

* chore(nix): refresh npm deps hash for the SDK bump

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-08-26 20:32:56 +08:00
Mohamed Boudra 59e6232784 fix(server): resolve canceled permission requests (#3792)
Provider callbacks can be canceled without a user response. Publish a terminal resolution for that path so every surfaced request has a matching lifecycle event, while preserving the existing interruption cleanup.
2026-08-26 09:53:41 +02:00
Mohamed Boudra d46267aed6 fix(desktop): keep manually admitted updates available (#3865)
Automatic readiness polls reapplied the stable rollout gate and cleared the same update that a manual check had already admitted. Keep same-version admission monotonic while still clearing withdrawn releases and rejecting newer releases that have not reached the user.
2026-08-26 09:51:20 +02:00
Mohamed Boudra 5a38a33345 fix(app): open mobile changes and PRs in Explorer (#3867)
The Explorer pane rewrite routed semantic Changes and pull-request actions through ordinary workspace tabs on compact layouts. Centralize those actions so mobile and native dock layouts select the Explorer surface while desktop keeps its placement preferences.
2026-08-26 09:50:28 +02:00
paseo-ai[bot] 5c9a04d333 fix: update lockfile signatures and Nix hash [skip ci] 2026-08-25 21:49:06 +00:00
Mohamed Boudra d3b10f0299 Merge branch 'next' into main 2026-08-25 23:40:04 +02:00
Mohamed Boudra 0f68e55eca fix(app): harden replica row persistence
Serialize pending batches at write execution time so retries cannot overwrite newer rows or resurrect removed hosts. Reject oversized hosts before storage writes and preserve unchanged directory identities so routine reconciliation stays row-grained.
2026-08-25 23:39:54 +02:00
Mohamed Boudra 579c6a58f5 feat(app): switch replica cache to row storage
Persist only changed replica entities so steady-state writes scale with the update rather than the complete cached world. Restore remains an all-or-nothing validated boundary, with whole-host byte-budget eviction.
2026-08-25 23:38:38 +02:00
Mohamed Boudra f59db03d67 feat(app): add replica cache row store
Ship the platform storage engines behind a dark interface so the cache migration can wire them separately. Both engines share a contract, including atomic writes and destructive schema-version resets.
2026-08-25 23:38:38 +02:00
Mohamed Boudra fd22784e21 perf(app): move replica cache validation to restore
Add an observational live measurement harness for the row-store migration baseline. Trust already-shaped cache values on persist while retaining full schema validation at the restore boundary.
2026-08-25 23:38:38 +02:00
Anton Lazarev f6288629ff Fastlane metadata format for F-Droid releases (#2501)
* add f-droid metadata support

* share version code calculation with website

* preserve blockquoted sections in changelogs

* refactor(android): simplify stable F-Droid metadata

Generate store changelogs only for stable releases and keep native version-code ownership in the existing release module.

* test(android): run F-Droid changelog suite in CI

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-25 23:37:54 +02:00