# Release v1.41.1

Released: 2026-04-03

## Added
- AI-powered narrative summaries for JIRA weekly status reports (tries each API provider until one succeeds)

## Changed
- JIRA reports now filter to current user's tickets only via `currentUser()` JQL
- Status report UI simplified to personal weekly status format with copy-to-clipboard
- Ticket keys rendered as clickable JIRA links anywhere in report text

## Changed
- Pin all npm dependencies to exact versions across all packages (no more `^` or `~` ranges) to prevent supply chain attacks
- Heavy-mode dependency audit — 15 packages flagged for removal, full replacement plan saved to PLAN.md
- Replace `axios` with owned `server/lib/httpClient.js` (fetch-based, AbortSignal.timeout, self-signed TLS via https.Agent)
- Replace `multer` with owned `server/lib/multipart.js` (streaming multipart parser, no memory buffering, safe for 500MB+ files)
- Replace `unzipper` with owned `server/lib/zipStream.js` (streaming ZIP parser via zlib.createInflateRaw, DEFLATE + stored entries)
- Remove `cors` package from scaffold-generated project templates; generated code now uses inline CORS middleware

## Changed
- Replace `node-telegram-bot-api` with owned `server/lib/telegramClient.js` (fetch-based polling loop, all bot methods)
- Replace `supertest` with owned `server/lib/testHelper.js` (HTTP server lifecycle + fetch-based request wrapper; 9 test files updated)
- Replace `react-hot-toast` with owned `client/src/components/ui/Toast.jsx` (module-level store, Toaster component; 101 import sites updated)
- Replace `react-markdown` with inline regex block/inline parser in `MarkdownOutput.jsx` (h1–h6, bold, italic, code, tables, lists, links)
- Replace `react-diff-viewer-continued` with inline Myers LCS diff in `CrossDomainTab.jsx`; `InlineDiff` wrapped with `React.memo`
- Replace `fflate` direct usage with native `DecompressionStream` + inline EOCD ZIP parser in `GenomeTab.jsx`

## Changed
- God file decomposition: split `server/routes/cos.js` (1,464 lines) into 9 focused sub-modules (`cosStatusRoutes`, `cosTaskRoutes`, `cosAgentRoutes`, `cosReportRoutes`, `cosLearningRoutes`, `cosScheduleRoutes`, `cosJobRoutes`, `cosTemplateRoutes`, `cosInsightRoutes`)
- God file decomposition: split `server/routes/scaffold.js` (1,667 lines) into 4 template generators (`scaffoldVite`, `scaffoldExpress`, `scaffoldIOS`, `scaffoldPortOS`)
- God file decomposition: split `client/src/services/api.js` (2,016 lines) into 27 domain sub-modules with barrel re-export; no consumer imports changed
- Block npm postinstall scripts by default via `.npmrc` `ignore-scripts=true`; `install:all` explicitly rebuilds trusted packages (`esbuild`, `node-pty`)
- Move Digital Twin work to backlog; promote god file decomposition to Next Up in PLAN.md
- God file decomposition: split `server/services/digital-twin.js` (2,970 lines) into 10 focused modules (`digital-twin-constants`, `digital-twin-helpers`, `digital-twin-meta`, `digital-twin-documents`, `digital-twin-testing`, `digital-twin-enrichment`, `digital-twin-export`, `digital-twin-context`, `digital-twin-analysis`, `digital-twin-import`, `digital-twin-status`); `digital-twin.js` is now a thin barrel re-export

## Fixed
- Scheduled AI jobs (e.g. Daily Briefing) could get permanently stuck as "Due" if agent spawn failed — `task:ready` handler now catches errors and emits `job:spawn-failed` to re-register the timer; 5-minute spawning timeout also re-registers as safety net
- Bug: `providerOverride` was incorrectly passed as model argument in `processEnrichmentAnswer`; corrected to `modelOverride`
- Dynamic `import()` of `safeJSONParse` inside `analyzeEnrichmentList` replaced with static top-level import
- `loadMeta()` was called inside per-document loop in `analyzeAssessment`; hoisted out to load once

## Changed
- `getDocuments()` now parallelizes all `stat()` calls with `Promise.all`
- `getAllTwinContent()` now parallelizes all `readFile()` calls with `Promise.all`
- Added `extractJSON()` and `ensureDocumentInMeta()` helpers to `digital-twin-helpers.js`; three duplicate document-push blocks in enrichment module replaced with helper calls

## Changed
- God file decomposition: split `server/services/subAgentSpawner.js` (3,820 lines) into 9 focused modules (`agentState`, `agentModelSelection`, `agentRunTracking`, `agentErrorAnalysis`, `agentCompletion`, `agentPromptBuilder`, `agentCliSpawning`, `agentLifecycle`, `agentManagement`); main file is now a 187-line orchestrator with re-exports for backward compatibility
- `isTruthyMeta`/`isFalsyMeta` consolidated into `agentState.js` (single source of truth)
- `terminateAgent`/`killAgent` runner path de-duplicated via shared `terminateRunnerAgent` helper
- `killAllAgents` now terminates all agents in parallel with `Promise.all`
- `buildAgentPrompt` context fetches (memory, CLAUDE.md, digital twin, tools) and `.planning/` file reads now run in parallel
- `safeParse` in `agentCliSpawning.js` replaced with `safeJSONParse` from fileUtils
- Removed TOCTOU `existsSync` guards in `agentPromptBuilder.js`; rely on `.catch(() => null)` instead

## Fixed
- Auto-update from the UI was failing at `git fetch` step — switched from `portos-update.sh` (tag checkout) to `update.sh` (git pull --rebase) which adds STEP markers for UI progress, completion markers for boot-time result recording, and Windows support via `update.ps1`
- Scheduled AI jobs (e.g. Daily Briefing) could get permanently stuck as "Due" if agent spawn failed — `task:ready` handler now catches errors and emits `job:spawn-failed` to re-register the timer
- `httpClient.js` now rejects hanging promise when signal is pre-aborted; toast shown on task add failure
- JIRA report provider discovery now tries all available API providers instead of failing on first unreachable one
- `agentCliSpawning.js` imported `completeExecution`/`errorExecution` from `executionLanes.js` (wrong module); corrected to import from `toolStateMachine.js`, fixing server crash on startup
- OpenClaw stream route now calls `res.flushHeaders()` before proxying SSE so headers reach the client immediately
- OpenClaw stream reader loop wrapped in try/catch to handle AbortError on client disconnect without unhandled rejection
- Orphaned `htmlFor="compact-task-app"` sr-only label in `TaskAddForm` compact mode removed (referenced select id that no longer exists after AppContextPicker refactor)
- XSS: `MarkdownOutput.jsx` links now validate `href` protocol (allow `http://`, `https://`, `/` only); unsafe URIs render as plain text
- Toast notifications missing background color — added `bg-port-card` with border
- `InlineDiff` word diff used `Set` which collapsed duplicate common words; replaced with sequential LCS pointer matching
- `httpClient.js` now auto-sets `Content-Type: application/json` when sending JSON body
- `zipStream.js` sanitizes ZIP entry paths (normalizes separators, strips `..` and `.` components)
- `multipart.js` sanitizes file extension to alphanumeric characters only

## Added
- `AppContextPicker` component — reusable app selector with repo path display; replaces inline `<select>` in `TaskAddForm` (compact and full modes)
- OpenClaw operator chat: SSE streaming with optimistic UI (user + streaming assistant placeholder appear immediately)
- OpenClaw: file/image attachment support — drag & drop, paste screenshots, file picker
- OpenClaw: app context, directory context, and extra instructions fields sent with messages
- OpenClaw: Stop/Cancel button aborts in-flight stream via AbortController
- OpenClaw: Cmd/Ctrl+Enter keyboard shortcut submits the composer
- OpenClaw: auto-scroll to latest message during streaming
- OpenClaw: collapsible "older chats" section for sessions beyond the top 6 non-empty ones
- OpenClaw: synthetic default session injected when `defaultSession` config key has no matching session
- `formatDateTime` added to `client/src/utils/formatters.js` (medium date + short time via `Intl.DateTimeFormat`)
- `readFileAsBase64` exported from `client/src/utils/fileUpload.js` for reuse across components

## Changed
- OpenClaw integration rewritten to use OpenResponses `/v1/responses` API and `/tools/invoke` for session discovery
- `normalizeAttachment` builds OpenAI-compatible `input_image`/`input_file` blocks from base64 or URL sources
- `streamSessionMessage` proxies upstream SSE with `timeoutMs: 0` for long-running streaming responses
- `fetchWithTimeout` accepts `timeoutMs: 0` to opt out of the timeout (needed for streaming)
- `loadRuntime` stabilized via `selectedSessionIdRef` — session switches no longer trigger a full runtime reload
- After send, session `lastMessageAt` updated locally instead of triggering a full `loadRuntime()` reload
- `context` state in OpenClaw simplified — removed derived `appName`/`repoPath`; computed at send time
- Duplicate `setMessages` update pattern in `handleSend` extracted into `updateAssistant(updater)` helper

## Full Changelog

**Full Diff**: https://github.com/atomantic/PortOS/compare/v1.41.0...v1.41.1
