taskchef 9.4.0 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/docs/mcp-app.md +43 -3
- package/package.json +1 -1
- package/src/calendar-history.js +30 -0
- package/src/codex-session-scanner.js +22 -0
- package/src/mcp-app/dist/index.html +139 -132
- package/src/mcp-app/react/CalendarView.test.tsx +59 -0
- package/src/mcp-app/react/CalendarView.tsx +91 -0
- package/src/mcp-app/react/UsageView.test.tsx +27 -6
- package/src/mcp-app/react/UsageView.tsx +2 -1
- package/src/mcp-app/react/main.test.tsx +121 -0
- package/src/mcp-app/react/main.tsx +21 -14
- package/src/mcp-app/react/preferences.ts +34 -0
- package/src/mcp-app/react/styles.css +32 -4
- package/src/mcp-app.js +9 -0
package/docs/mcp-app.md
CHANGED
|
@@ -299,7 +299,7 @@ UI surfaces. Native font families use locally available fonts with system fallba
|
|
|
299
299
|
read the installed desktop application or fetch theme files from the network.
|
|
300
300
|
|
|
301
301
|
The compact **Pinned** toggle button shows chats pinned in Codex and combines with project,
|
|
302
|
-
update window, and search filters. It
|
|
302
|
+
update window, and search filters. It defaults to off and remembers the user’s selection locally. Pin
|
|
303
303
|
changes appear on the next data refresh. The current desktop pin list takes
|
|
304
304
|
precedence over the database `is_pinned` field. TaskChef ignores old migration
|
|
305
305
|
snapshots and never writes pin state. Without either source, chats are unpinned.
|
|
@@ -371,8 +371,9 @@ a marketplace does not automatically move their installed plugin identity.
|
|
|
371
371
|
|
|
372
372
|
The Board/Usage selector sits beside the logo in the navbar, separated from
|
|
373
373
|
Board filters by a divider. It opens an on-demand historical usage view. Ranges are
|
|
374
|
-
7, 30, or 90 calendar days including today, This month (
|
|
375
|
-
|
|
374
|
+
7, 30, or 90 calendar days including today, This month (through today), and
|
|
375
|
+
Last month (the full previous calendar month). The default is 30 days; the
|
|
376
|
+
selected range is remembered locally when Usage is reopened.
|
|
376
377
|
There is no custom range. Dates use the view's local timezone, including DST.
|
|
377
378
|
Range changes and reopening Usage query one shared history snapshot without
|
|
378
379
|
rescanning logs. The snapshot is scoped by CLI/exec visibility, independently of
|
|
@@ -408,3 +409,42 @@ Cost is an estimate at current API-equivalent rates, not subscription billing
|
|
|
408
409
|
or remaining allowance. The first read shows a progress bar with the actual number of root chats
|
|
409
410
|
processed and polls an app-only tool. Cached charts stay visible during later
|
|
410
411
|
refreshes, with their last-update time and scan progress.
|
|
412
|
+
|
|
413
|
+
## Month calendar
|
|
414
|
+
|
|
415
|
+
Calendar sits beside Board and Usage. It opens on the current local month;
|
|
416
|
+
previous/next arrows surround the month title. Next is disabled at the current
|
|
417
|
+
local month, so future months cannot be selected. There is no Today control.
|
|
418
|
+
Arrows stay hidden until the month navigation is hovered or contains focus;
|
|
419
|
+
a clicked arrow remains visible while focused. This also applies in embedded
|
|
420
|
+
hosts that do not report mouse hover support. Project, search, update window, and Pinned
|
|
421
|
+
filters are remembered locally. An unavailable saved project resets to All
|
|
422
|
+
projects only after the project inventory loads.
|
|
423
|
+
Project, Search Chats, Pinned, and native visibility settings apply. The Board's
|
|
424
|
+
Updated filter does not apply to Calendar. A Calendar-only Scheduled
|
|
425
|
+
toggle starts enabled and hides chats with an active schedule when disabled,
|
|
426
|
+
including those currently Running or Waiting. Its choice is remembered locally across page visits and does not filter
|
|
427
|
+
Board or Usage. Selecting one project hides
|
|
428
|
+
project prefixes on calendar bars. The grid has no persistent helper captions. Scheduled and Pinned share
|
|
429
|
+
the same toggle styling for their on, off, and hover states.
|
|
430
|
+
|
|
431
|
+
Each chat's recorded turn intervals become calendar dates in the viewer's
|
|
432
|
+
timezone. Adjacent dates merge into one bar. One full idle date breaks the bar,
|
|
433
|
+
including weekends; a later segment has a resumed marker and opens the same
|
|
434
|
+
chat Details. Continuous work across a week boundary uses continuation arrows.
|
|
435
|
+
An exact midnight end does not occupy the following date. A current running
|
|
436
|
+
turn extends to now; completed turns do not extend through idle review waits.
|
|
437
|
+
Project colors stay consistent when navigating or filtering. Crowded weeks show
|
|
438
|
+
three bar rows and a control to expand the remaining chats. Narrow views scroll
|
|
439
|
+
horizontally to retain seven readable day columns.
|
|
440
|
+
|
|
441
|
+
The calendar reads all turn timing rows for each eligible chat's selected
|
|
442
|
+
SQLite history, cached until the databases change. The calendar rereads on
|
|
443
|
+
changed snapshots or database reads, explicit Refresh, and local date rollover;
|
|
444
|
+
an unchanged five-second poll does not reread calendar history. It does not scan usage,
|
|
445
|
+
modify Codex records, or create editable start/end dates. Earlier histories
|
|
446
|
+
replaced by a rewind may be absent. Missing start timestamps are omitted;
|
|
447
|
+
missing durations show only a known start date. Missing timing data does not
|
|
448
|
+
add a notice to the calendar.
|
|
449
|
+
|
|
450
|
+

|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Date-only arithmetic uses UTC; recorded instants are first converted to the
|
|
2
|
+
// viewer's timezone. This keeps full idle days consistent across DST changes.
|
|
3
|
+
const nextDay = date => new Date(Date.parse(`${date}T00:00:00Z`) + 86400000).toISOString().slice(0, 10);
|
|
4
|
+
export function calendarSegments(turns, { month, timeZone, now = Date.now(), latestTurnId }) {
|
|
5
|
+
const format = new Intl.DateTimeFormat('en-CA', { timeZone, year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
6
|
+
const day = time => {
|
|
7
|
+
const parts = Object.fromEntries(format.formatToParts(time).map(part => [part.type, part.value]));
|
|
8
|
+
return `${parts.year}-${parts.month}-${parts.day}`;
|
|
9
|
+
};
|
|
10
|
+
let incomplete = false;
|
|
11
|
+
const intervals = [];
|
|
12
|
+
for (const turn of turns) {
|
|
13
|
+
const start = turn.started_at * 1000;
|
|
14
|
+
if (!Number.isFinite(start) || turn.started_at == null || start <= 0 || start > now) { incomplete = true; continue; }
|
|
15
|
+
const running = turn.status === 'inProgress' && turn.turn_id === latestTurnId;
|
|
16
|
+
const hasDuration = Number.isFinite(turn.duration_ms) && turn.duration_ms >= 0;
|
|
17
|
+
if (!running && !hasDuration) incomplete = true;
|
|
18
|
+
const end = running ? now : Math.min(now, start + (hasDuration ? turn.duration_ms : 0));
|
|
19
|
+
// A turn ending exactly at midnight did not work on the following day.
|
|
20
|
+
intervals.push({ start: day(start), end: day(Math.max(start, end - 1)) });
|
|
21
|
+
}
|
|
22
|
+
intervals.sort((a, b) => a.start.localeCompare(b.start) || a.end.localeCompare(b.end));
|
|
23
|
+
const segments = [];
|
|
24
|
+
for (const interval of intervals) {
|
|
25
|
+
const previous = segments.at(-1);
|
|
26
|
+
if (previous && interval.start <= nextDay(previous.end)) previous.end = previous.end > interval.end ? previous.end : interval.end;
|
|
27
|
+
else segments.push({ ...interval, resumed: segments.length > 0 });
|
|
28
|
+
}
|
|
29
|
+
return { incomplete, segments: segments.filter(segment => segment.start.slice(0, 7) <= month && segment.end.slice(0, 7) >= month) };
|
|
30
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { calendarSegments } from "./calendar-history.js";
|
|
1
2
|
import { aggregateUsage } from "./usage-analytics.js";
|
|
2
3
|
import { openRollout, rolloutChunks } from "./codex-rollout.js";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
@@ -651,6 +652,27 @@ export class CodexSessionScanner {
|
|
|
651
652
|
) SELECT id, rollout_path FROM threads WHERE id IN (SELECT id FROM family)`).all(id);
|
|
652
653
|
return this.sessionUsage.read({ id, latestTurnId: task.turnId, sessions, savedDurations });
|
|
653
654
|
}
|
|
655
|
+
async calendarHistory({ month, timeZone, settings }) {
|
|
656
|
+
const snapshot = await this.refresh();
|
|
657
|
+
if (!snapshot.healthy) throw new Error(snapshot.scan.error);
|
|
658
|
+
const records = this.databaseCache.records;
|
|
659
|
+
const cache = records.calendarTurns ??= new Map();
|
|
660
|
+
const rows = new Map(records.rows.map(row => [row.id, row]));
|
|
661
|
+
const query = this.connections.history.prepare(`SELECT turn_id, started_at, duration_ms, status
|
|
662
|
+
FROM thread_turns WHERE thread_id = ? ORDER BY rollout_ordinal`);
|
|
663
|
+
const chats = [];
|
|
664
|
+
for (const task of snapshot.tasks) {
|
|
665
|
+
if ((!settings.showArchived && task.observed?.archive) ||
|
|
666
|
+
(!settings.showCli && task.observed?.source === "cli") ||
|
|
667
|
+
(!settings.showExec && task.observed?.source === "exec")) continue;
|
|
668
|
+
const row = rows.get(task.id);
|
|
669
|
+
let turns = cache.get(task.id);
|
|
670
|
+
if (!turns) { turns = query.all(historyId(row)); cache.set(task.id, turns); }
|
|
671
|
+
const result = calendarSegments(turns, { month, timeZone, now: this.now(), latestTurnId: task.turnId });
|
|
672
|
+
if (result.segments.length || result.incomplete) chats.push({ id: task.id, ...result });
|
|
673
|
+
}
|
|
674
|
+
return { month, timeZone, chats };
|
|
675
|
+
}
|
|
654
676
|
async usageAnalytics({ range, timeZone, settings }) {
|
|
655
677
|
return aggregateUsage(await this.usageHistory({ settings }), { range, timeZone, now: this.now() });
|
|
656
678
|
}
|