v0.9.2: files the agent delivers come back as cards, and the saved key is used
A turn could write a report and say "here it is" -- and there was nothing to
open. The engine had no way to hand a finished file to the person, and the
front end had no way to show one. This release adds the whole path: a tool to
deliver, a store that keeps the copy, a card in the transcript, and open /
share through the platform. It also fixes two bugs the phone exposed while it
was being tried there.
deliver_file (engine)
- cst_attachment_t.for_user marks a copy that belongs to the person, not to
the model; cst_attach_import_artifact() stores any bytes up to
CST_ARTIFACT_MAX_BYTES (64 MB) with the mime taken from the name's
extension. Attachments are unchanged (content decides the type, 16 MB).
- It is a read-policy engine tool: path resolves inside the workspace, a
directory is refused, title (optional) is the display name.
- The wire is what keeps it out of the conversation: parts_add_attachments()
skips for_user entries without even a "not sent" note, and a turn that only
delivered a file no longer invents an empty user message. tests/test_artifacts.c
pins that down end to end, including a session-file round trip.
The card (Qt front end)
- filesText() replaced imagesText(): one line per file -- kind, stored name,
name, origin, mime, bytes, pixels -- decoded by ImageStrip.qml. A for_user
entry is a file card (extension badge, name, type and size, Open, Share);
a picture stays a thumbnail and opens on tap. Both shapes are read, so
sessions written by an older version still draw.
- AgentCore::openArtifact() / shareArtifact() accept a name only when one of
the session's own messages carries it, and hand the display name to the
platform layer for the status line.
- Android opens through the app's FileProvider (content:// + ACTION_VIEW,
resolveActivity() checked, read permission granted) and shares through the
share sheet; a desktop uses QDesktopServices / the clipboard.
CST_APP_NO_OPEN=1 makes --uitest run without opening anything.
Two bugs the device found
- The session writer had its own copy of the attachment JSON and forgot
`for_user`: the file survived the file, but came back as an attachment --
no card, and put back in front of the model. It now uses the one writer
(cst_attachments_to_json()), with the round trip covered by a test.
- engineToolImages() looked the card up with findTool(), which only finds a
card that is still running -- and the engine reports the result *before*
the files, so the card was already complete and the files were dropped.
A live turn therefore showed no card while a replay of the same session
did. It now matches on the call id.
- A tap that could not work said nothing: ImageStrip.qml's errorText was
never drawn. The card now carries the reason ("no app on the phone opens
report.md").
Also
- The saved provider key reaches the engine on every start: AgentCore::open()
was handed an empty key and the engine never resolves credentials itself
(only the CLI did), so every start asked for it again. It resolves the same
order main.c uses (saved key, provider environment, CST_API_KEY) and wipes
the copy it made; --selftest checks it against a scratch credentials file.
- Android: notification_send reads the Java exception text into its result,
and keeps failing on the NX789J -- the build() returns null and Qt clears
the exception before C++ can read it, so the call has to be wrapped in Java
next (docs/android.md says so).
- --uitest grew the delivered-file case (card, Open, Share, a forged name
refused), the live order of result-then-files, and the startup row count.
Version 0.9.2.
This commit is contained in:
+43
-4
@@ -131,7 +131,12 @@ header opens the dialog that changes it.
|
||||
with each entry's base URL and where its key would come from: "key saved"
|
||||
(`.agent`-independent, `<config>/cst-agent/credentials`), "key from
|
||||
OPENAI_API_KEY", or "no key". A key itself is never sent to the QML side —
|
||||
the dialog only ever describes it.
|
||||
the dialog only ever describes it. The window starts the engine without a
|
||||
key (`AgentCore::open()` is passed an empty one), so `open()` resolves it
|
||||
for the provider it starts on: saved key, then the provider's env variable,
|
||||
then `CST_API_KEY` — the same order the CLI uses in `main.c`. Without that
|
||||
the key sat in the file but never in the turn, and every start asked for it
|
||||
again.
|
||||
- "Use this" calls `AgentCore::useProvider()`, which runs
|
||||
`cst_ps_switch_provider()` / `cst_ps_set_key()` on the engine's live config:
|
||||
a pasted key is verified against the provider's `/models` before it is saved
|
||||
@@ -233,6 +238,35 @@ session: `AgentCore::attachmentDir()` is re-read on every switch.
|
||||
A document (PDF, UTF-8 text) still arrives as its name in the message text --
|
||||
those are sent to the model as text, not as a picture.
|
||||
|
||||
## Files the agent delivers
|
||||
|
||||
The opposite direction: the engine's `deliver_file` tool (registered in
|
||||
`src/tools/registry.c`, run in `src/tools/fs_tools.c`) copies a finished file
|
||||
out of the workspace into the session's own store, and the transcript shows it
|
||||
as a **file card** -- an extension badge, the file's name, its type and size,
|
||||
and two buttons.
|
||||
|
||||
- The copy is marked `for_user` (`cst_attachment_t.for_user`,
|
||||
`cst_attach_import_artifact()`): any bytes up to `CST_ARTIFACT_MAX_BYTES`
|
||||
(64 MB), the name's extension decides the mime, and **no request ever
|
||||
contains it** -- `src/llm_openai.c` skips those entries when it builds the
|
||||
body, which is what keeps a delivered report from being pasted back into the
|
||||
context. `tests/test_artifacts.c` holds that down at the wire level.
|
||||
- A card never invents a path. The row carries the stored file's name, and
|
||||
`AgentCore::openArtifact()` / `shareArtifact()` accept it only when one of
|
||||
the session's own messages carries that name (`artifactPathOf()`, plus the
|
||||
engine's `cst_attach_rel_ok()`), so a forged name is refused with a reason.
|
||||
- **Open** hands the file to the system: Android builds a `content://` URI
|
||||
through the app's FileProvider, checks `resolveActivity()`, starts
|
||||
`ACTION_VIEW` and grants the reader permission; a desktop calls
|
||||
`QDesktopServices::openUrl`. **Share** uses the phone's share sheet; a
|
||||
desktop has none, so the path goes to the clipboard.
|
||||
- `CST_APP_NO_OPEN=1` makes both do nothing but report the line they would
|
||||
write. `--uitest` sets it, so a test run never opens anything for real: it
|
||||
replays a session with a delivered `report.md`, checks the card and its two
|
||||
buttons, clicks Open (the header must say `opened report.md`), clicks Share
|
||||
and checks a name the session does not own is refused.
|
||||
|
||||
## Transcript, tool calls, streaming text
|
||||
|
||||
- A tool call is a card of its own, and the call **id** is what pairs it with
|
||||
@@ -328,7 +362,7 @@ GUI only through queued signals.
|
||||
|---|---|---|
|
||||
| Linux | `cmake -S app -B build-app` (libcurl via pkg-config) | clipboard (shell helpers), notification (`notify-send` / D-Bus) |
|
||||
| Windows | Qt's MinGW kit, `cmake -G Ninja -DCMAKE_PREFIX_PATH=<Qt>/lib/cmake`; the engine needs libcurl for Windows (see `docs/README-windows.md`) | notification via the WinRT/tray API, clipboard through the core's Win32 path |
|
||||
| Android | engine compiled with the NDK inside the APK (`scripts/build_apk_android.sh`, `-DCST_TUI=0`, `CST_NO_WEBUI=1`); no port, QML talks to `AgentCore` in-process | none yet: `app/src/platform/android/AndroidTools.cpp` is a stub, so notification, share sheet, clipboard and the file picker are still missing |
|
||||
| Android | engine compiled with the NDK inside the APK (`scripts/build_apk_android.sh`, `-DCST_TUI=0`, `CST_NO_WEBUI=1`); no port, QML talks to `AgentCore` in-process | notification (`NotificationChannel` + permission probe), clipboard (`QClipboard` under the engine's own tool names), share sheet (`ACTION_SEND` + chooser), and the file card's open/share through the app's FileProvider (`AndroidTools.cpp`) |
|
||||
| macOS | Qt from the installer, libcurl from the system | notification (`osascript`), clipboard (`pbpaste`/`pbcopy`) |
|
||||
|
||||
Android's long-term shape: the core lives in the APK (no Termux needed), the
|
||||
@@ -353,8 +387,13 @@ The build flag that keeps Unix helpers out of the picture is
|
||||
thumbnails (`image://cstattach/<stored name>`, see the Images section)
|
||||
- [x] Android APK: the engine is linked in (no port), builds, installs under
|
||||
`xyz.whcself.cstagent` and runs on the device (see `docs/android.md`)
|
||||
- [x] the saved provider key reaches the engine on every start (`AgentCore::open()`
|
||||
resolves it: saved key, provider environment, `CST_API_KEY`)
|
||||
- [x] delivered files: `deliver_file` stores the copy next to the session and the
|
||||
transcript draws a file card with Open and Share (see the Files section)
|
||||
- [x] Android platform tools (JNI: notification, clipboard, share sheet) and the
|
||||
file card's open/share through the app's FileProvider
|
||||
- [ ] a real turn driven through the window (needs a provider key)
|
||||
- [ ] Android platform tools (JNI: notification, share sheet, clipboard, file
|
||||
picker), the back gesture, the icon glyphs on the phone
|
||||
- [ ] SAF file picker, the back gesture, the icon glyphs on the phone
|
||||
- [ ] Kirigami (the rest of the Breeze look: colors, controls), tray,
|
||||
global shortcuts
|
||||
|
||||
Reference in New Issue
Block a user