Commit Graph
111 Commits
Author SHA1 Message Date
mika kuns 27dca48c43 Merge branch 'claudedo/387-unit-branch-teil-3-firmen-modus-fetch-pu' 2026-10-01 12:30:13 +02:00
mika kunsandClaude Sonnet 5.5 2dbec543df feat(mission-control): show running Worker operations in a Running now section
Registry of active verify/merge/rebase/worktree-creation operations in the Worker,
GetActiveOperations snapshot + OperationStarted/OperationEnded hub events, and a
Running now section in Mission Control with ticking elapsed time.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 12:08:43 +02:00
mika kuns bc959f25b3 feat(worker): approve in a company repo verifies in the worktree, pushes and opens a draft PR 2026-10-01 12:05:33 +02:00
mika kuns 78f4d182e8 feat(worker): Azure DevOps client and PAT store, ported from pr-create.ps1 2026-10-01 11:55:11 +02:00
mika kuns 9da522b0c9 feat(worker): fast-forward finished children into the parent's unit branch
Planning parents get their own worktree on Queue plan, children fork from the
unit head and are absorbed via merge --ff-only before Done (ff failure -> Failed,
interactive child approve -> absorb instead of merge to the target).
2026-10-01 11:25:47 +02:00
mika kuns 2b40ea2d2b fix(mcp): register ticket import tools in the external MCP container
The external MCP server has its own DI container; TicketMcpTools could not
resolve TicketImportService there, so both tools failed at runtime. Also
surface a skipped import in the prime outcome and treat a sweep where every
project board fails as an error instead of an empty backlog.
2026-10-01 08:49:31 +02:00
mika kuns 57c30c4a18 feat(mcp): add get_ticket_import_candidates and import_ticket tools 2026-10-01 08:37:44 +02:00
Mika Kuns 57dd676672 feat(claude-do): UI-Prozess loggt auf Platte (ui-.log) + fehlgeschlagene Hub-
## Problem
Beim DeleteTask-Bug (Fix 352aaa52) gab es null Spuren: Der UI-Prozess (ClaudeDo.App) hat gar kein File-Logging (weder Serilog noch `Microsoft.Extensions.Logging` sind in App/Ui referenziert), und im Worker loggt SignalR fehlgeschlagene Hub-Invocations nur auf Debug (`Microsoft.AspNetCore` steht in `src/ClaudeDo.Worker/Program.cs` Z. 60 auf Warning). Die Ursache ließ sich nur finden, ind

ClaudeDo-Task: d2ae3831-733f-4d40-93d7-70c93da11590
2026-09-28 13:27:21 +02:00
Mika Kuns 08d1797ead chore(claude-do): Mission Control: allgemeiner Session-Handoff an eine frische
## Ziel

Jede Mission-Control-Session (Ad-hoc UND Task-Session) soll ihre Arbeit an eine frische Session
uebergeben koennen: "gib das ab an eine neue Session". Die alte Kachel wird geschlossen, an
derselben Position oeffnet eine frische Session im selben cwd, die mit einem Brief startet, den
die abgebende Session selbst geschrieben hat.

Motivation: eine lange Session mit vollem/verrottetem Kontex

ClaudeDo-Task: 8b846deb-ad10-4243-9272-9aea2a01ec93
2026-09-28 11:57:37 +02:00
mika kuns 3d09323ee1 fix(worker): ConnectedClientTracker in den externen MCP-Container teilen 2026-09-10 14:58:29 +02:00
mika kuns 451c01848c fix(worker): start_list_handler ehrlich machen, wenn keine App verbunden ist
ConnectedClientTracker zaehlt verbundene UI-Clients (WorkerHub.OnConnectedAsync/
OnDisconnectedAsync). StartListHandler prueft ihn vor dem Broadcast und liefert
bei 0 verbundenen Clients Requested=false plus Reason statt unbedingtem true.
Tool-Beschreibung und external-mcp.md an das reale Verhalten angepasst.
2026-09-10 13:33:30 +02:00
mika kuns c9d4823f06 fix(worker): report startup port conflicts instead of crashing silently
Mutex was Local\ (per Windows session), so a second RDP/fast-user-switching
session bypassed the single-instance guard. A Kestrel bind failure on top of
that had no try/catch, so a second worker (or any process on 47821/47822)
died as an unhandled crash with nothing in the log to explain why the UI
never connects. Mutex is now Global\, bind failures and any last-resort
unhandled exception log a Fatal line and exit(1) instead of crashing raw.
2026-09-10 13:12:25 +02:00
mika kuns e64d570554 fix(worker): raise SignalR MaximumParallelInvocationsPerClient
Default of 1 serializes every hub call on a connection - a slow one
(GetTicketProjects hitting an unreachable ticket system, 10s timeout)
blocked unrelated calls like UpdateList/UpdateListConfig behind it for
the same duration, making list-settings save appear to hang.
2026-09-03 07:47:26 +02:00
mika kuns 574edf6c1a Merge branch 'claudedo/f2f0c6ee1e974c09bc8a8261978cba34'
feat(worker): Handler-Session vor dem Usage-Limit sauber uebergeben (#275); Konflikte mit start_list_handler aufgeloest, WorkerLog-Aufruf im Fire-and-forget-Pfad wieder abgesichert
2026-09-02 21:50:26 +02:00
mika kuns fe8186514f feat(worker): Handler-Session vor dem Usage-Limit warnen und Handoff bis zum Reset zurueckhalten
Eine PowerUse-Handler-Session soll am 5h-Limit nicht mehr mitten im Merge
sterben, sondern vorher selbst sauber uebergeben:

- HandlerUsageGuard (neu) warnt die laufende Session genau einmal pro Runde,
  sobald FiveHour.Utilization den Schwellwert PowerUseHandoffPct erreicht.
  Die Warnung wird per HandlerUsageWarning-Broadcast an Mission Control
  geschickt und dort in das Pane der Handler-TaskId getippt.
- handoff_list_handler broadcastet den Handoff dann nicht sofort, sondern parkt
  ihn bis ResetsAt + 60 s; das Ergebnis traegt deferredUntil. Ohne ResetsAt
  wird wie bisher sofort gesendet.
- Der geparkte Handoff liegt im HandlerRoundState (handler-rounds.json) und
  wird vom Guard-Tick ausgeloest, also auch nach einem Worker-Neustart wieder
  eingeplant bzw. sofort gesendet, wenn er ueberfaellig ist.
- Review-Befund aus #272: HandlerRestartCoordinator wartet nicht mehr selbst
  bis ResetsAt, sondern parkt den Neustart im selben Slot. AutoRestartUsed wird
  erst gesetzt, wenn der Broadcast wirklich rausging - ein Worker-Absturz
  waehrend der Wartezeit laesst die Runde nicht mehr dauerhaft tot zurueck.
  Zurueckgehaltener Handoff und Auto-Neustart teilen sich einen Slot, also
  oeffnet nie beides dieselbe Runde. UsageLimitProofPct entfaellt zugunsten von
  PowerUseHandoffPct.
- PtyTerminalSession.SendLineAsync schreibt in die laufende Session. Gegen eine
  echte claude-Session verifiziert: Text und Enter muessen zwei getrennte
  Writes sein, die Enter-Bytes kommen von Terminal.GenerateKeyInput.
2026-09-02 21:35:29 +02:00
mika kuns c2166a0d4f feat(worker): start_list_handler MCP-Tool startet einen List-Handler-Run wie der Button 2026-09-02 21:05:55 +02:00
mika kuns 016b9203f8 feat(worker): Handler-Session nach Usage-Limit-Reset automatisch neu starten
Eine List-Handler-ConPTY-Session, die mitten in einer Phase stirbt, war bisher
ein stilles totes Tile: InteractiveTerminalViewModel.OnSessionProcessExited
setzte nur bei einem Tod innerhalb des 5s-Grace-Fensters ein sichtbares Banner.

Neu:
- HandlerRoundState (JSON unter ~/.claudeDo/handler-rounds.json, keine
  EF-Migration): Phase + Task-Set der laufenden Runde, geschrieben von
  BuildForMergeHelperAsync ("triage") und BuildForMergeHelperHandoffAsync.
  Ohne das existieren beide nur im transienten HandoffRequested-Broadcast.
- HandlerRestartCoordinator: entscheidet den Neustart, fail-closed. Beleg fuer
  "war das Usage-Limit" ist FiveHour.Utilization >= 95 zum Todeszeitpunkt --
  eine ConPTY-Session hat kein stdout, TaskRunner.IsUsageLimitMessage hat hier
  nichts zu lesen. Nur bei PowerUse (beide Keys), max ein Neustart pro Runde,
  Warten bis FiveHour.ResetsAt, dann HandoffRequested erneut broadcasten.
  Verbraucht kein Handoff-Budget (HandoffRoundTracker.Register wird nicht
  angefasst). WorkerLog-Zeilen beim Tod und beim Neustart.
- Ui meldet den Exit-Code (nur dort bekannt) via
  WorkerHub.ReportInteractiveSessionExited, ohne selbst zu filtern.

Sibling zu UsageLimitAutoContinueCoordinator, kein Umbau davon: der sieht nur
headless Runs.
2026-09-02 19:17:51 +02:00
Mika Kuns 81a5fc5d33 feat(worker): surface the handoff round budget in the merge-helper handoff brief
A handler session only learned the chain was capped when handoff_list_handler
threw at the end of its work, stranding open tasks. The round count already
lived in HandoffRoundTracker the whole time -- it just never reached the
brief. InteractiveLaunchSpecService now reads HandoffRoundTracker.GetState
(read-only, doesn't advance the round) and renders round N of MaxHandoffs
plus the remaining count into the handoff brief, replacing the old one-line
finalNote. The rejection error also names the next action (start a new
handler run) instead of just failing.
2026-09-02 14:26:56 +02:00
Mika Kuns 7a0b97df59 refactor(claude-do): refactor(worker): move the merge and worktree MCP tools out
`src/ClaudeDo.Worker/External/ExternalMcpService.cs` ist **1897 Zeilen** (nachgezählt 2026-09-02; die Datei ist seit dem Anlegen dieses Tasks von 1696 weiter gewachsen): ~30 DTO-Records oben plus ~25 Tool-Methoden — während **13** Geschwisterdateien `*McpTools.cs` im selben Ordner das Topic-Split-Muster schon tragen (`ListMcpTools`, `ConfigMcpTools`, `BatchMcpTools`, `NotesMcpTools`, `QueueStateMc

ClaudeDo-Task: 1862e160-ffde-4a50-aa03-bb7251f56088
2026-09-02 13:02:10 +02:00
Mika Kuns 25ead4c8dd chore(claude-do): feat(worktrees): warn about git worktrees ClaudeDo doesn't k
From `docs/open.md` (re-verified against the code 2026-09-02, still open): nothing reconciles `git worktree list` against the `worktrees` table. `ExternalMcpService.ListWorktrees` and the Worktrees overview read DB rows only; `WorktreeManager` calls git solely inside its self-heal path (`ListWorktreePathsForBranchAsync`). Observed once: 22 git-registered worktrees + 26 `claudedo/*` branches in `C:

ClaudeDo-Task: 765fed13-db66-44e4-9285-c4b2d415b443
2026-09-02 12:09:12 +02:00
CubeGameLPandClaude Opus 5 ae0c7d3771 feat(notes): add_note MCP tool with a per-day agent cap
Agents can write into the user's daily notes, but only within limits:
max 10 agent notes a day, max 500 characters, add only — no edit, no
delete. A new from_agent column separates their lines from the user's
so the cap counts the right ones and the UI can mark them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:02:37 +02:00
mika kuns 2e236736a7 feat(worker): Ticket-Import pro Liste 2026-08-27 13:05:38 +02:00
mika kuns 5aafeb6c3d feat(worker): Ticket-Status-Rueckmeldung ueber TaskStateService 2026-08-27 12:53:38 +02:00
mika kuns 1065278021 feat(worker): BandelTicketClient fuer die Ticketsystem-REST-API 2026-08-27 12:42:22 +02:00
mika kuns 2a3c937f70 feat(worker): TicketSystemConfig + ticket_api_base_url in worker.config.json 2026-08-27 12:34:24 +02:00
mika kuns 41bfc242fa refactor(worker): OnlineTokenStore zu DpapiTokenStore verallgemeinert 2026-08-27 12:27:34 +02:00
Mika Kuns c4928b4def chore: move appdata folder to .claudeDo 2026-08-26 14:26:19 +02:00
mika kuns 0f007c5367 refactor: drop the remaining single-implementation interfaces
IBaseDirtyChecker, IInteractiveLaunchSpecService and the LaunchSpec wrapper
had one implementation and one caller each; ProcessRunnerAdapter existed only
to give a static class an interface, and InstallArtifactLocator used
inheritance for two constructor arguments. The external MCP container now
shares its singletons through a Share<T> helper instead of 17 near-identical
registrations.
2026-08-26 13:55:51 +02:00
mika kuns 69fcceb2ed feat(worker): run both MCP servers stateless
Set Stateless = true on the internal (/mcp on the SignalR port) and external
MCP HTTP transports. Two effects:

- No Mcp-Session-Id, so a worker restart can no longer 404 a session that
  outlives it -- ConPTY tiles in the UI process and externally registered
  claude sessions keep working across a restart.
- A 2026-07-28 client is no longer refused back to the initialize handshake.
  A stateful server rejects that revision on purpose (see the SDK's
  StreamableHttpHandler.s_sessionSupportingProtocolVersions), so the new
  per-request protocol path was unreachable regardless of SDK version.

Nothing here used the stateful-only features (sampling, elicitation, resource
subscriptions, unsolicited notifications). In-tool progress notifications ride
the POST's own response stream and are unaffected -- which matters, since ~20
tools use them to hold off the client's 300s idle abort. All three consumers
(TaskRunner, PlanningSessionManager, the installer's RegisterMcpStep) already
register type: "http", so dropping the legacy SSE endpoint breaks nothing.

Verified against the running worker across two restarts: tools/list returns
57 tools over a bare POST with no initialize and no session id, and a full
2026-07-28 tools/call round-trip returns real data.
2026-08-26 11:05:31 +02:00
mika kuns 296251e27e refactor: collapse single-implementation interfaces
Nine interfaces had exactly one implementation and no test double — they existed
only to be named twice in a DI registration: IFindingsStore, IFindingsStoreLocator,
IPrimeScheduleSignal, IRefineRunner, IWeekReportService, IMergeCoordinator,
IMissionControlPane, IOnlineLoginService, ITaskListFilter. Consumers now depend on
the concrete type; the DTO records that shared those files moved next to their
implementation. IInteractiveLaunchSpecService stays — it carries 54 lines of
contract documentation, which is not ceremony.

IMergeCoordinator in particular had a redundant null object: MergeCoordinator with
a null Handler already no-ops, and every test used the real class with Handler set.

Filtering/ collapses from 8 files to 1. ITaskListFilter and TaskListFilterBase were
a double abstraction over four predicates, with MatchesAsContext => false declared
in both. SmartFlagFilter also compiled its expression twice (its own _flag plus the
inherited Matches cache) — it now uses the cache.

StaticTokenAuthProvider was in src but production uses ZitadelAuthProvider; it is
a test double, so it moves to the test project. Its own test goes away with it.
2026-08-26 10:12:23 +02:00
mika kuns 44337d4b94 feat(usage): serve model and task usage from the TokenTracker export 2026-08-24 13:41:21 +02:00
mika kuns 07dd75700d feat(worker): Toggle "Continue on session limit reset" für Usage-Limit-Abbrüche
Klassifiziert einen echten Usage-Limit-Abbruch als eigene FailureReason
"usage_limit" (TaskRunner.ClassifyFailureReason: nur bei terminal_reason
"api_error" plus einem Limit-Muster im gerenderten Fehlertext, nicht an
Status==Failed allein). Neuer Toggle AutoContinueOnUsageLimit (app_settings,
Default aus) unter Settings → General → "Usage limit stop":

- UsageLimitAutoContinueCoordinator feuert pro Task genau einmal ContinueTask
  über OverrideSlotService, sobald das 5h-Fenster (UsageState.Snapshot.FiveHour
  .ResetsAt) tatsächlich zurückgesetzt ist; ein persistenter Marker
  (TaskEntity.UsageLimitAutoContinuedAt) verhindert einen zweiten Anlauf bei
  einem erneuten Limit-Treffer.
- QueueService schedult zusätzlich einen exakten Wake-Timer auf den
  Reset-Zeitpunkt, statt nur auf den 30s-Backstop zu warten.
- Fail-open durchgängig: kein Snapshot/keine Reset-Zeit → kein Timer, kein
  Continue, kein Throw. Toggle aus ändert das heutige Verhalten nicht.

Migration AddUsageLimitAutoContinue fügt beide Spalten hinzu; die von
`dotnet ef migrations add` mitgescaffoldete leere UpdateData auf app_settings
(columns/values: []) erzeugte ungültiges SQL ("near WHERE") und wurde entfernt
— TaskNumberMigrationTests deckte das über den vollen Migrate()-Pfad auf.
2026-08-21 18:43:02 +02:00
mika kuns ea2271cc3e feat(mcp): update_task kann isManual setzen + List-Handler stellt seinen Task auf Review
Teil A: update_task bekommt einen optionalen isManual-Parameter (null =
unveraendert); TaskDto/TaskRefDto spiegeln IsManual zurueck.

Teil B: neuer submit_task_for_review MCP-Tool (LifecycleMcpTools) laesst
einen Handler-Task selbst auf WaitingForReview gehen -- fuer einen
worktree-losen Task wird HandlerHeadCommit vom aktuellen HEAD gestempelt,
sonst werden offene Aenderungen committet. Die Submit-Logik ist aus
WorkerHub.SubmitTaskForReview in InteractiveReviewSubmissionService
extrahiert; der Hub ruft sie jetzt nur noch auf. Der Merge-Prompt-Default
weist die Endzweige (merge ohne Rerun, merge_final) an, den eigenen
Handler-Task nach der Summary einzureichen.
2026-08-21 16:51:18 +02:00
mika kuns 6ec81f653b feat(worker): surface startup recovery via OperationProgress channel
The six Lifecycle/*Recovery hosted services now broadcast one
OperationProgress("startup-recovery", <phase>, current, total) message each
after they finish, instead of leaving the UI on a bare "connecting" text
during worker startup. IslandsShellViewModel subscribes and swaps in
"Recovering... (i/n)" (existing ops.worker.startupRecovery key, no locale
changes) while Worker.IsReconnecting is true, and clears it once actually
connected so a later transient reconnect doesn't replay stale text.

OperationProgress broadcasts to Clients.All with no replay-on-connect, so a
UI that hasn't finished its SignalR handshake yet can miss some or all of
these messages and simply keep showing "connecting" as before -- accepted
rather than adding a cached-state + reconnect-replay path (mirroring
RefreshExternalMergeConflictsAsync) for what is a fast, best-effort,
local-only startup sweep with no UI-visible failure mode beyond that.
2026-08-21 13:36:25 +02:00
mika kuns 39b24c2f74 fix(worker): cap the list-handler wait/merge handoff chain server-side
HandoffMcpTools.HandoffListHandler only validated the phase name and
broadcast it -- nothing bounded how many times a ConPTY session could
hand off wait<->merge, so a model that skimmed past the prompt's
"final round" line could loop indefinitely. Add HandoffRoundTracker,
an in-memory per-handler-task counter (a list-handler run always
creates a fresh handler task, so no reset logic is needed): past 4
handoffs (two full wait/merge cycles) a non-final nextPhase is
coerced to its "_final" counterpart, and any handoff after a
merge_final round for that task is rejected outright.
2026-08-11 16:52:49 +02:00
mika kuns 66f7a8ae99 Merge branch 'claudedo/9f963a215bb34ca4a28050d4f39178d3' 2026-08-10 15:19:57 +02:00
mika kuns d3155e6868 feat(queue): warn when the base branch has uncommitted changes
Queuing (update_task_status, batch_update_task_status) and run_task_now
now surface a non-blocking baseDirty warning (separate modified/untracked
counts) when the list's working dir has uncommitted changes at enqueue
time, since a new worktree forks from the commit tip and silently misses
them. BaseDirtyChecker caches per working dir for a few seconds so a
batch queue over many tasks in one list only shells out to git once. The
UI surfaces the same warning via the footer error strip on queue actions.
2026-08-10 14:30:33 +02:00
mika kuns 5d362b6973 fix(worker): stop sibling worktrees from landing inside the repo tree
Path.GetDirectoryName returned workingDir itself instead of its parent
whenever list.WorkingDir ended in a directory separator, placing
.claudedo-worktrees inside the target repo's own working tree. Add
WorktreeRootResolver to normalize the trailing separator before deriving
the sibling root and to guard that the resulting worktree path never
lands inside workingDir, used by both WorktreeManager and
PlanningSessionManager. Add a startup sweep (LegacyWorktreeFolderRecovery)
that warns when a list's working dir already contains a leftover
.claudedo-worktrees folder from before the fix.
2026-08-10 11:58:34 +02:00
mika kuns 519f64c02e feat(mcp): add save_finding to the in-task server 2026-08-10 10:27:13 +02:00
mika kuns 2d4288abca feat(mcp): add save_finding to the external server 2026-08-10 10:19:45 +02:00
mika kuns 79f90a9a8e fix(worker,ui): block cancelling a task while its unit merge is draining
TaskStateService.CancelAsync allowed cancelling a WaitingForReview task
even while PlanningMergeOrchestrator was mid-drain on it: ApproveReview
awaits the whole multi-subtask merge synchronously, so a concurrent
CancelReview (UI or MCP) could flip the parent to Cancelled while the
orchestrator kept merging children onto the target branch, then
FinalizeParentDoneAsync would find the parent no longer WaitingForReview
and give up - leaving the merged diffs stranded with no rollback.

CancelAsync now rejects with a clear reason when HasActiveMerge(taskId)
is true. TaskStateService can't take PlanningMergeOrchestrator as a
direct constructor dependency (circular back to ITaskStateService), so
it takes a lazily-resolved Func<IActiveMergeState> instead, mirroring
the existing Func<ITaskStateService> cycle-break already used for
PlanningChainCoordinator.

UI polish: DetailsIslandViewModel.IsMergeDraining gates
CancelReviewCommand's CanExecute (same shape as
WorktreesOverviewModalViewModel.IsMerging), and the command's catch now
raises ErrorReported instead of swallowing the rejection silently.
2026-08-06 13:38:02 +02:00
mika kuns ad2d91f658 Merge claudedo/ccd650a8d2b04a7092e81ed07c16dbe0 2026-08-05 22:46:37 +02:00
mika kuns cdfd0614dd Merge claudedo/616befd0f8a64dcb9e3dac9d6499de16 2026-08-05 22:45:32 +02:00
mika kuns d43b5fcefc feat(worker-mcp): raise wait_for_task_change timeout, expose queue slot state
MaxTimeoutSeconds was 170s against runs that take tens of minutes, forcing
a dozen full-context wait rounds per long-running batch. Raise it to 900s
and raise MCP_TOOL_TIMEOUT in lockstep (ClaudeProcess + every
InteractiveLaunchSpecService launch spec) to 930000ms so the client
connection actually stays open that long instead of aborting first.

Add get_queue_state (QueueStateMcpTools): configured vs. effective
parallel-slot count (via QueueService.GetSlotCountsAsync, extracted from
the former GetEffectiveMaxParallelAsync), active slots with taskId +
startedAt including the run_task_now override slot, and queued tasks in
pick order -- so a caller can observe queue occupancy instead of inferring
it from maxParallelExecutions.
2026-08-05 20:47:57 +02:00
mika kuns 29bd1b5069 fix(worker): filter EF Core/ASP.NET Core noise out of log ring buffer
EF Core logs every SQL statement at Information, which BroadcastLogSink
buffers into LogRingBuffer regardless of level, flooding the Log
Visualizer overlay and worker log file with SQL noise that buries
hand-curated business events. Override both to Warning so real
EF Core / ASP.NET Core problems still surface.
2026-08-05 20:46:57 +02:00
mika kuns 4ef01274f9 feat(list-handler): hand off to a fresh session after Phase 2
The merge-helper ("Let Claude handle it") system prompt now calls a new
handoff_list_handler MCP tool once every surviving task is enhanced,
instead of continuing into Phases 3-5 in the same session -- avoiding
paying for Phases 0-2's dedupe/rewrite context on every polling round of
the run/review/merge phases.

The tool broadcasts HandoffRequested; Mission Control opens a second
ConPTY tile for the SAME handler task id (no new task, HandlerBaseCommit
untouched) running a fresh handoff brief that starts at Phase 3. The
original tile stays open. Adds PromptKind.MergeHelperHandoff,
InteractiveLaunchSpecService.BuildForMergeHelperHandoffAsync, and the
GetMergeHelperHandoffLaunchSpec hub method.
2026-08-05 20:27:39 +02:00
mika kuns 2700c3d817 fix(usage): stop 429s with an activity-dependent poll cadence + manual refresh
The usage monitor polled the undocumented OAuth usage endpoint every 60s and
earned 429s. It now polls every 5 min while any task is Running and every
15 min while idle (usage_poll_interval_active_seconds / _idle_seconds, both
clamped to >= 60; the old single usage_poll_interval_seconds key is gone).

A 429 comes back as UsageRateLimitedException carrying Retry-After and adds
exponential backoff on top, capped at 30 min and never shorter than the normal
cadence; the strike count resets on the first success. The schedule arithmetic
is the pure static UsagePollSchedule.NextDelay.

Since the idle cadence is slow on purpose, WorkerHub.RefreshUsage drives
UsageMonitorService.RefreshNowAsync behind a Refresh now button in the Usage
Monitor modal: an out-of-band poll that pushes the loop's next-due time out so
no double poll follows, with a 10s cooldown so click-spam can't earn a 429.

Staleness now measures against the slower (idle) interval so an idle worker
isn't flagged stale just for not polling.
2026-08-05 16:40:34 +02:00
mika kuns b153869216 fix(prompts): stop on-disk prompt overrides from freezing forever
EnsureExists blindly seeded ~/.todo-app/prompts/*.md with the bundled
default and never revisited it, so any file created by opening the
Files settings tab shadowed every later default change permanently
(SuggestImprovement/AskUser sections never reached real runs since
2026-06-04). PromptFiles now hashes what a file was seeded/saved with
in prompts/.defaults.json: Classify() tells missing/current-default/
known-past-default/edited apart, ReconcileStaleDefaults() drops files
that only ever matched a superseded default, and real edits are left
alone and surfaced in the Files tab with a diff + reset-to-default
action. QuarantineOrphans() moves stale-named leftovers (agent.md,
planning.md) into prompts/_orphans instead of silently deleting them.
Wired as a Worker startup sweep (PromptFileRecovery) alongside the
existing OrphanRecovery/AttachmentOrphanRecovery services.
2026-08-05 15:59:03 +02:00
mika kuns d4cd202460 feat(worker): expose usage/model-usage hub surface and persist run model
Adds GetUsageSnapshot/GetModelUsage/GetTaskUsage to WorkerHub (backed by a
shared UsageSnapshotBuilder), a UsageUpdated broadcast fired after every
UsageMonitorService poll cycle, and records the resolved model on each
task_runs row so per-model/per-task usage can be reported from history.
2026-08-05 12:37:50 +02:00
mika kuns 1aaa40b894 Merge branch 'claudedo/0b2fbb48d44c41558c21d3464c0bd5c2' 2026-08-05 12:11:55 +02:00