81aeee7d1e9d2287a293ec51c1f1840c74f9bffe
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
580d533656 |
v0.9.6: documents, downloads, PDFs and the phone's own files
Four tools and a document writer, plus the storage half of the phone: until now the picker's answer (a content:// URI) reached the engine as a path, and it said "cannot read" -- the app had no way to hand a phone's file over. - md_to_html (new, src/tools/web.c + cst_web_md_document()): markdown in, one self-contained HTML document out -- doctype, utf-8, viewport, escaped title, a small stylesheet; no script, no external request. It is written into the workspace and delivered as a file card (for_user, so it never enters a request), the way deliver_file does. The default name comes from the source file's stem or, for pasted markdown, from the title with what a path cannot carry turned into '-'. It has a preview, so an approval prompt says "render x -> y.html" before anything is written. - download (src/tools/web.c): an http(s) URL into a file in the workspace, streamed with a 128 MB ceiling; a redirect chain, the same CA handling and the same refusals as web_fetch, but the bytes stay on disk (a half file is removed on any failure, including HTTP >= 400). The name comes from the URL when the caller names only a directory. - read_pdf (src/tools/fs_tools.c): the text layer and page count of a PDF, which read_file refuses because the bytes are binary -- and a PDF is exactly what arrives from a phone. Same extractor an attachment uses, its own 4 MiB input ceiling, 256 KiB of text by default with a note when cut. - session_search (new, src/tools/session_tools.c): every stored session -- this one and all the ones before it -- searched for a substring, reporting the session's id, message count and title, then the matching lines with their number and role. Case-insensitive by default, capped at 20 matches. - device_info (same file, perm_name "device"): version, system/arch/CPU count, workspace, sandbox, model and permissions, config/temp/program directories, pid -- so the model stops guessing where it runs. - Tools 3 and 4 come with tests/test_tools_ext.c (also in both build files): the document is a whole page, a script tag and a javascript: link do not survive, a hostile title stays text and cannot write outside the workspace, the preview names the file, read_pdf/download refuse what they must (no network in a test), session_search finds a fixture session and says so when nothing matches, device_info names the workspace. README's tool table grew five rows. - Android storage: the picker (SAF) answers with a content:// URI, and the engine opens files by path, so the app now copies what it points at into its own directory first -- app/android/src/xyz/whcself/cstagent/ AttachFile.java, called through a new app::materializeAttachment() (ToolProvider.h: one API for every platform; a desktop strips file:// and refuses content:// with a reason). The copy needs no permission at all: the grant travels with the URI. - "All files access" (MANAGE_EXTERNAL_STORAGE) is the other half: it is what lets the engine read a path in shared storage that a task names rather than picks. Android has no runtime dialog for it, so main.cpp opens the one Settings screen once (AttachFile.java's requestAllFilesAccess, with the all-apps screen as a fallback) and never insists: without it everything works, one picked file at a time. On the NX789J the screen came up, the app-op reads back "allow", and a relaunch no longer asks. - Composer.qml hands a content:// URI through untouched (it is not a path, and decoding it would corrupt it); AgentCore::attachFile() turns whatever the picker gave into a path first. Desktop: build clean, make check 36/36 (the new test included, also under ASan), --uitest all green, --selftest and cst-app-selftest OK. The APK builds and installs (86 MB, release-signed) with the new Java class in its dex. Version 0.9.6. |
||
|
|
7299f39245 |
v0.9.4: a turn keeps running when the app is not on screen
A phone freezes an app it cannot see, and the front end had nothing holding it: a turn that took minutes -- a long shell command, a provider that thinks, a build -- stopped quietly the moment the user pressed home, and the answer was only there if the window happened to come back. The engine's own /attach flow made this visible: an APK turn survived, a pocket did not. - app/android/src/xyz/whcself/cstagent/CstService.java: a foreground service started with an explicit component (nothing outside the app can reach it, no intent filter) for as long as a turn runs. Being a foreground service makes the process one Android neither freezes nor kills; a partial wake lock (30 min ceiling) keeps the CPU awake with the screen off. Its ongoing notification says what is running -- "CST-agent is working" plus the first line of the prompt -- on the same cst-agent channel the notification_send tool uses, and taps back into the app. A phone that refuses the foreground start (Android 12+ does, from the background) loses the protection, not the turn: the failure is caught and the service stops itself. - Manifest: FOREGROUND_SERVICE, FOREGROUND_SERVICE_DATA_SYNC (API 34+ wants the typed one) and WAKE_LOCK, plus the <service> with foregroundServiceType="dataSync". The first build of this failed with "Expected '>', but got ' '" -- a manifest comment cannot contain "--". - Front end: app::holdForeground(text) / app::releaseForeground() in ToolProvider.h, one call for every platform -- Android starts and stops the service through JNI, a desktop has nothing to hold (its window is the app). AgentCore::send() calls it when the turn starts (firstLine() of the prompt is what the notification says) and finishTurn() releases it, so the notification exists exactly as long as the work does. CST_APP_NO_SERVICE=1 keeps a test run from touching the phone. - On the NX789J, with the app sent home and the screen off: the service read back isForeground=true with its notification, the wake lock cst-agent:turn was taken at the start of the turn and dropped at its end, run_shell "sleep 60" finished with the screen dark, and the service was gone after. Desktop: build clean, --uitest all green, --selftest and cst-app-selftest OK (the hold is a no-op there, so nothing about the Qt side changed). Version 0.9.4. |
||
|
|
d4f65b17cf |
v0.9.3: notification_send posts on the phone, and Java helpers have a home
The tool was the last Android one that never worked on the NX789J: it would fail with "Notification.Builder.build() failed: no exception detail" and the shade stayed empty. The detail was the whole story -- Qt clears a Java exception before C++ gets to look at it, so a build() that throws and a build() that returns null look exactly the same from here, and the only way to catch the difference is to let Java catch it. - app/android/src/xyz/whcself/cstagent/Notifier.java: the builder, the PendingIntent, build() and notify() in one try/catch that returns "" or the throwable's class and message. androiddeployqt copies app/android/ with the rest of the package source and Qt's Gradle template puts its src/ on java.srcDirs, so a class dropped there needs no build-script change (the first one is this; the foreground service of the next step goes there too). - AndroidTools.cpp keeps what it can see from C++ (the context, the channel on API 26+, the small icon looked up by name, the launch intent) and calls Notifier.post(); the result line now carries the Java failure when there is one. The javaErrorText() helper of the previous attempt is gone: reading a pending exception from C++ is exactly what does not work here. - On the device: the tool answers "notification sent", the system holds the record (channel cst-agent, importance 3, the framework small icon, the tap intent back into the app). Earlier attempts in the same session are in the transcript next to it: four failures from the old path, then this one. Docs: docs/android.md gets the Java-helper mechanism (and why the tool has to be wrapped in Java at all) and no longer lists notification_send as missing; app/README.md's platform table and the Android note say where a Java class goes. Version 0.9.3. |
||
|
|
60cbce3c5c |
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.
|
||
|
|
c2320f946b |
v0.9.0: the Qt front end as an Android APK, images in the transcript
Two strands land together: the window draws pictures now, and the whole front end builds, installs and runs on the phone with the engine linked in-process. Images (the "make a picture actually show up" part) - Engine, additively: cst_agent_hooks_t.on_tool_images(call, list, n, ud) fires when a call left attachments behind, and cst_agent_pending_rel() / cst_agent_pending_is_image() read that list without the app touching the store. tests/test_attach.c::test_agent_pending pins the three cases (an image, a stored name that passes cst_attach_rel_ok, out-of-range NULL). - App: image://cstattach/<stored name> (app/src/ui/SessionImages.cpp) serves only the current session's attachment directory, checked with cst_attach_rel_ok, decodes down to the requested sourceSize and never upscales, and holds a mutex because requestImage() runs on the render thread while the directory can change underneath it. AgentCore gained attachmentDir(), pendingImages(), the toolImages()/promptImages() signals and hookToolImages(); history() gives replayed rows their images too. - QML: ImageStrip.qml (a thumb strip, <=150 px tall, shaped by the picture, tooltip with name/size/origin, the file's name when it went missing), wired into MessageCard and ToolCard. Qt 6's ListModel cannot hold a list-valued role (append() reads back undefined, setProperty drops it), so a row carries one image per line as <stored>|<name>|<origin>|<w>x<h> and ImageStrip decodes that. Android (the engine is already in the APK; this is the front end) - app/src/main.cpp: on Android the workspace, config, tmp and HOME go under the app's own data directory before the engine opens anything, because QDir::currentPath() is "/" there and /tmp is not writable. - app/android/: the manifest (QtApplication/QtActivity, INTERNET and POST_NOTIFICATIONS, adjustResize, no storage permission -- the picker will be the system one), strings, a vector icon; minSdk 24 and targetSdk 35 come from app/CMakeLists.txt through QT_ANDROID_MIN/TARGET_SDK_VERSION. - app/src/platform/android/AndroidTools.cpp: the Android file behind PlatformInit(), owner "app:android" (notification, clipboard, share sheet, file picker are stubs for now, so the tool list does not lie about them). - app/tools/selftest.c decides its scratch workspace with os_temp_dir(), so the C smoke test runs on the device too. - scripts/build_apk_android.sh: sources to installed APK in one command (environment checks, qt-cmake configure, --target apk, adb install), and the host-side plumbing the NixOS + qt.io combination needs -- the NDK sysroot include dirs for Qt's FindGLESv2/FindEGL (they only look under <sysroot>/include), -L$PREFIX/lib on the shared/module link lines as well as the executables', the Android debug keystore created on first use with QT_ANDROID_SIGN_APK + QT_ANDROID_KEYSTORE_* (androiddeployqt copies the unsigned gradle output and signs it itself), and the #!/bin/bash shebang of the build-tools wrappers, which NixOS does not have. - scripts/gradle-android-init.gradle (installed into ~/.gradle/init.d/): the release lint task is switched off -- lintVitalAnalyzeRelease resolves its own tool jars straight from dl.google.com, bypassing every repository the build configures, and fails the build after all the real work is done. - Icons: app/icons/*.svg (Breeze's 24@3x files, the 19 names the window asks for) are compiled in as :/cst-icons and ThemeIcons.cpp falls back to them when the platform has no icon theme. On the phone the Qt theme lookup found nothing and the QML glyph fallback was an empty box in the phone's fonts. - docs/android.md: "The APK (Qt front end)" -- the SDK assembled without sdkmanager, the host Qt's missing libraries on NixOS, the Gradle mirrors, lint, signing -- and a Status that says what runs where. app/README.md gains the images and APK lines, and stops claiming Android platform tools that are still stubs. Verified: engine `make check` green; desktop `cst-app-selftest: OK` and --uitest all checks pass; phone (NX789J, Android 16, arm64-v8a) -- the APK installs, starts on the workspace under /data/user/0/xyz.whcself.cstagent/ files/, a turn reaches a configured provider over HTTPS from inside the ART process and renders the reply, rotation keeps the session and docks the sidebar, and the toolbar/card icons draw. Version 0.9.0. mode = coding |