feat(dgw): list Gateway-generated artifacts in the recording manifest - #2003
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 20 commits into
Conversation
A .slog pushed for a session that already has a recording is now listed
in a new `logs` field of recording.json, named `log-{n}.slog`.
The `files` list keeps holding recordings only, so released players and
streamers keep working.
A log-only session (AD Console) still lists its .slog in `files`.
The `logs` field is omitted when empty, so existing manifests stay the same.
The session ZIP download now includes the logs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Let maintainers know that an action is required on their side
|
A post-session .slog push kept the session "connected", so /shadow streamed the finished recording instead of refusing like master did. /shadow now closes with "streaming ended" when the ongoing push is a log, and log chunks no longer wake /shadow streamers. The ongoing push now tracks which manifest entry it writes to (recording or log, by index) instead of a bool, which removes the "no log file" bug branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ZIP now holds logs as well as recordings, so "clip" no longer fits. The public endpoint doc is unchanged, so the OpenAPI output is too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Routing .slog pushes into `logs` by file type changed where existing clients' .slog streams land, which breaks backward compatibility. JREC push now takes an optional `materialType` query parameter: `recording` (the default when absent) or `log`. Recording material requires `fileType` and is stored in `files` as before, including `slog`. Log material rejects `fileType`, is opaque to Gateway, and is stored in `logs` as `log-N.slog`, even before any recording exists. Invalid combinations are refused with HTTP 400 before the upgrade.
Runs the ai-log task end to end: streams the terminal recording into chunk files in a task workspace, describes each chunk with the AI (checkpointed so a retry resumes, splitting a chunk when the answer is cut at the output limit), writes the .slog and appends it to the manifest `logs` through the recording manager. Also reports truncated answers from the AI crate and regenerates the ai-log substate docs. Re-stacked on #2003 (materialType): squashes the earlier bcebf25, 707ab33, 9b6404b, 40d675b and a5ae0ad. Adding a log no longer requires a recording in `files`, per recording.intent.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Logs are pushed to `/jet/jrec/push/{id}/logs` instead of
`?materialType=log`, so a log push has no `fileType` to validate.
`/jet/jrec/push/{id}?fileType=...` is unchanged, including `slog`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Follows Benoit's route 2: one push route, `?fileType=slog&category=log` stores the stream in `logs`, and a missing `category` still means recording. Keeping `fileType` for logs leaves room for other log formats; only `slog` is accepted today. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…file type `PushMaterial` becomes `PushCategory`, matching the `category` query parameter. A log push now carries its file type and is named after its extension (`log-N.<ext>`), so only the API decides which formats may be pushed as logs; today that is `slog`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replaces the `logs` list with an `artifacts` object keyed by role, as discussed with Benoit: `?fileType=slog&artifact=ai-analysis` stores the stream as `ai-analysis-N.slog` under `artifacts["ai-analysis"]`. A push without `artifact` is a recording and lands in `files` as before, `slog` included. Roles are a fixed enum for pushes, but the manifest keeps its keys as strings so roles written by a newer Gateway survive a rewrite. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`PushCategory` is a leftover of the `category` parameter; the query now uses `artifact`, so the destination is a `PushTarget`. A test checks that each role's manifest key matches its query value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…f the recording policy The push query takes `kind=<role>` instead of `artifact=<role>`, as written in recording.intent.md. An artifact push no longer satisfies a session's recording policy: `EnsureRecordingPolicyTask` now asks `is_recording`, which only counts Recording pushes, so pushing an ai-analysis log alone can't keep a session that must be recorded alive. An artifact push also never carries the policy, so its end doesn't kill the session. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ording policy Ongoing pushes are now keyed by (session, kind). Before, an artifact push replaced the session's single entry, which dropped a disconnected must-be-recorded Recording's TTL kill and blocked its reconnect. - Only the Recording kind drives the recording policy, active_recordings, /shadow wake-ups and manifest timing; artifact kinds touch none of them. - A Recording and an artifact may push at the same time; one push per kind. - The manager reads and writes the manifest from disk for each change, so concurrent pushes keep each other's entries. - PushTarget::new owns the "ai-analysis needs slog" rule. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`artifacts` is a struct with one list per `ArtifactKind` instead of a string-keyed map, reached through an exhaustive match, so adding a kind forces every reader and writer to handle it. Kinds unknown to this Gateway are no longer kept when it rewrites a manifest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`ArtifactKind`, the manifest `artifacts` struct and its entries, and the file-type error now live in their own module. The session ZIP reuses the same typed struct instead of a second copy of it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Artifact pushes no longer take any part in the recording lifecycle: the recording manager only allocates the next `<kind>-N.<ext>` name and adds it to the manifest `artifacts` list, then the push streams bytes to the file. No ongoing entry, disconnect, TTL, recording policy, active recordings, shadow wake-up or timing writes. Recording pushes are back to master's shape (one per session, keyed by session ID). Their disconnect re-reads the manifest from disk and updates its own `files` entry, so artifact entries added meanwhile are kept. Removes `KindPolicy`, `PushKind`, `is_recording` and the test-only fake session manager. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Describes recording pushes, artifact pushes with `kind`, and that artifacts take no part in the recording policy or session shadowing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A session has one recording push at a time and artifacts never go in `files`, so the pushing recording is always the last entry. Drop the stored index; only re-read the manifest from disk before updating it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep master's `clip_names` naming and docs for the session ZIP, build the `PushTarget` directly in the push handler instead of through a wrapper, and leave the "ai-analysis must be slog" check to the recording tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The JREC push endpoint and ClientPush go back to master. Artifacts are added through RecordingMessageSender::add_artifact, which moves a file into the session folder and lists it in the manifest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lets Gateway attach its own non-recording artifacts (AI analysis first) to a session recording, without breaking released players, which treat every
filesentry as playable.artifactsobject next tofiles, hard-typed per kind (onlyai-analysistoday):{ "files": [ { "fileName": "recording-0.webm", "startTime": 1787255035, "duration": 25 } ], "artifacts": { "ai-analysis": [ { "fileName": "ai-analysis-0.slog" } ] } }RecordingMessageSender::add_artifact; there is no artifact push, so a push token can't upload one. Nothing calls it yet: the first caller is the AI log task in feat(dgw): generate a session log with AI #2008slogincluded: AD console.slogis that session's recording and stays infiles/shadowor duration handlingartifactsis omitted when empty, so existing manifests are byte-for-byte the sameDesign notes:
devolutions-gateway/src/recording.intent.md.Tested:
artifacts::1/1,recording::8/8,api::jrec::13/13,dvls_compatibility24/24.🤖 Generated with Claude Code