failproofai 0.0.9-beta.1 → 0.0.9-beta.2
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0su~k6f._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_codex-projects_ts_07qqk1g._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01743wx._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0.f_cyx._.js → [root-of-the-server]__0eu4j_n._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gs6wz4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0it81ys._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u4a9jq._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0dub28-._.js → [root-of-the-server]__0vrlxf2._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +18 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12.h2mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_04w00cm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_codex-projects_ts_0eosib~._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_068jk73._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0zaq1hm._.js → node_modules_0ttbz1~._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0_c_yox08g_44.js → 01q52wg_amm60.js} +2 -2
- package/.next/standalone/.next/static/chunks/03egp37o1l629.js +1 -0
- package/.next/standalone/.next/static/chunks/06j6c0ofqjy0v.js +6 -0
- package/.next/standalone/.next/static/chunks/06x4-d1~o-opr.js +1 -0
- package/.next/standalone/.next/static/chunks/{0jryicwtm9z2g.js → 0e8c_1f7-8e7t.js} +3 -3
- package/.next/standalone/.next/static/chunks/0mumk7h5i.1xd.js +1 -0
- package/.next/standalone/.next/static/chunks/0n~s0gafwnp2y.js +1 -0
- package/.next/standalone/.next/static/chunks/{0bghqwo4iloy0.js → 0zdn~84f58hvf.js} +1 -1
- package/.next/standalone/.next/static/chunks/0zig0fh30t6ou.js +1 -0
- package/.next/standalone/.next/static/chunks/{0z-jh701rc~j8.js → 10mlwc4y_kqo2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0kzk5-mh1_x53.js → 12simlrcfk3g2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0w1f.k~gi-y6..js → 15_mi91qaeieu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ufq8smh~i7wc.js → 18a9xv2p3~x.9.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-0s36is87fc9r2.js → turbopack-0o7k.hakttp4k.js} +1 -1
- package/.next/standalone/app/components/cli-badge.tsx +24 -0
- package/.next/standalone/app/components/project-list.tsx +13 -7
- package/.next/standalone/app/components/sessions-list.tsx +4 -2
- package/.next/standalone/app/policies/hooks-client.tsx +66 -10
- package/.next/standalone/app/project/[name]/page.tsx +49 -22
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +51 -19
- package/.next/standalone/components/reach-developers.tsx +6 -1
- package/.next/standalone/lib/codex-projects.ts +250 -0
- package/.next/standalone/lib/codex-sessions.ts +414 -0
- package/.next/standalone/lib/format-date.ts +21 -0
- package/.next/standalone/lib/log-entries.ts +3 -3
- package/.next/standalone/lib/paths.ts +13 -0
- package/.next/standalone/lib/projects.ts +57 -3
- package/.next/standalone/lib/utils.ts +6 -22
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +1 -0
- package/dist/cli.mjs +1042 -122
- package/lib/codex-projects.ts +250 -0
- package/lib/codex-sessions.ts +414 -0
- package/lib/format-date.ts +21 -0
- package/lib/log-entries.ts +3 -3
- package/lib/paths.ts +13 -0
- package/lib/projects.ts +57 -3
- package/lib/utils.ts +6 -22
- package/package.json +1 -1
- package/scripts/launch.ts +2 -1
- package/src/hooks/builtin-policies.ts +7 -1
- package/src/hooks/hook-activity-store.ts +3 -0
- package/src/hooks/manager.ts +1 -1
- package/src/hooks/resolve-permission-mode.ts +6 -91
- package/.next/standalone/.next/server/chunks/[externals]__080wern._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b57.gk._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03rd.z8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e74wa-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vu.o-3._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +0 -17
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0zqcovi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__105.l_7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0uy6m~m._.js +0 -3
- package/.next/standalone/.next/static/chunks/00b5h4r1el.6f.js +0 -1
- package/.next/standalone/.next/static/chunks/0fw2h.g66c0h3.js +0 -1
- package/.next/standalone/.next/static/chunks/0gu87mlr5ssnt.js +0 -6
- package/.next/standalone/.next/static/chunks/0igf3xbisp1lx.js +0 -1
- package/.next/standalone/.next/static/chunks/0p5zh2diw90a1.js +0 -1
- package/.next/standalone/.next/static/chunks/0vwqucikost_q.js +0 -1
- /package/.next/standalone/.next/static/{CiVeb_yiVt-O2JYrzGzB7 → SyaO-J1hupjAiRCG-Syzg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{CiVeb_yiVt-O2JYrzGzB7 → SyaO-J1hupjAiRCG-Syzg}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{CiVeb_yiVt-O2JYrzGzB7 → SyaO-J1hupjAiRCG-Syzg}/_ssgManifest.js +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { useState, useMemo, useEffect, useRef } from "react";
|
|
8
8
|
import { SessionFile } from "@/lib/projects";
|
|
9
|
-
import { formatDate } from "@/lib/
|
|
9
|
+
import { formatDate } from "@/lib/format-date";
|
|
10
10
|
import {
|
|
11
11
|
FILTER_PRESETS,
|
|
12
12
|
ITEMS_PER_PAGE,
|
|
@@ -25,6 +25,7 @@ import Link from "next/link";
|
|
|
25
25
|
import PaginationControls from "./pagination-controls";
|
|
26
26
|
import DatePickerInput from "./date-picker-input";
|
|
27
27
|
import { CopyButton } from "./copy-button";
|
|
28
|
+
import { CliBadge } from "./cli-badge";
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
interface SessionsListProps {
|
|
@@ -190,7 +191,7 @@ export default function SessionsList({ files, projectName }: SessionsListProps)
|
|
|
190
191
|
<File className="w-5 h-5 text-primary" />
|
|
191
192
|
</td>
|
|
192
193
|
<td className="px-4 py-3 max-w-md">
|
|
193
|
-
<div className="flex items-center gap-
|
|
194
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
194
195
|
{file.sessionId ? (
|
|
195
196
|
<>
|
|
196
197
|
<Link
|
|
@@ -206,6 +207,7 @@ export default function SessionsList({ files, projectName }: SessionsListProps)
|
|
|
206
207
|
{file.name.replace(/\.jsonl$/, "")}
|
|
207
208
|
</span>
|
|
208
209
|
)}
|
|
210
|
+
{file.cli && <CliBadge cli={file.cli} />}
|
|
209
211
|
</div>
|
|
210
212
|
</td>
|
|
211
213
|
<td className="px-4 py-3 text-sm text-muted-foreground">
|
|
@@ -63,10 +63,45 @@ function projectFromTranscriptPath(transcriptPath: string): string | null {
|
|
|
63
63
|
return folder;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function
|
|
66
|
+
function encodeCwdForUrl(cwd: string): string {
|
|
67
|
+
// Inverse of decodeFolderName, inlined here so this component file stays
|
|
68
|
+
// client-side and doesn't pull lib/paths server imports.
|
|
69
|
+
const driveMatch = /^([A-Za-z]):[\\/](.*)$/.exec(cwd);
|
|
70
|
+
if (driveMatch) return driveMatch[1] + "--" + driveMatch[2].replace(/[\\/]/g, "-");
|
|
71
|
+
return cwd.replace(/[\\/]/g, "-");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function SessionCell({
|
|
75
|
+
sessionId,
|
|
76
|
+
transcriptPath,
|
|
77
|
+
integration,
|
|
78
|
+
cwd,
|
|
79
|
+
}: {
|
|
80
|
+
sessionId?: string;
|
|
81
|
+
transcriptPath?: string;
|
|
82
|
+
integration?: string;
|
|
83
|
+
cwd?: string;
|
|
84
|
+
}) {
|
|
67
85
|
if (!sessionId) return <span className="text-muted-foreground">\u2014</span>;
|
|
68
|
-
const project = transcriptPath ? projectFromTranscriptPath(transcriptPath) : null;
|
|
69
86
|
const short = shortenSession(sessionId);
|
|
87
|
+
|
|
88
|
+
const isCodex = integration === "codex" || (transcriptPath?.includes("/.codex/") ?? false);
|
|
89
|
+
if (isCodex) {
|
|
90
|
+
// The session route auto-detects CLI by file location, so [name] only
|
|
91
|
+
// affects the breadcrumb. Encode the cwd Claude-style when we have it.
|
|
92
|
+
const projectSeg = cwd ? encodeCwdForUrl(cwd) : "codex";
|
|
93
|
+
return (
|
|
94
|
+
<Link
|
|
95
|
+
href={`/project/${encodeURIComponent(projectSeg)}/session/${encodeURIComponent(sessionId)}`}
|
|
96
|
+
className="text-primary hover:underline font-mono"
|
|
97
|
+
onClick={(e) => e.stopPropagation()}
|
|
98
|
+
>
|
|
99
|
+
{short}
|
|
100
|
+
</Link>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const project = transcriptPath ? projectFromTranscriptPath(transcriptPath) : null;
|
|
70
105
|
if (project) {
|
|
71
106
|
return (
|
|
72
107
|
<Link
|
|
@@ -331,9 +366,13 @@ function ActivityTab({
|
|
|
331
366
|
const [filterEventType, setFilterEventType] = useState(() => url.get("event") ?? "");
|
|
332
367
|
const [filterPolicy, setFilterPolicy] = useState(() => url.get("policy") ?? "");
|
|
333
368
|
const [filterSessionId, setFilterSessionId] = useState(() => url.get("session") ?? "");
|
|
369
|
+
const [filterCli, setFilterCli] = useState<"" | "claude" | "codex">(() => {
|
|
370
|
+
const v = url.get("cli");
|
|
371
|
+
return v === "claude" || v === "codex" ? v : "";
|
|
372
|
+
});
|
|
334
373
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
335
|
-
const filtersRef = useRef({ filterDecision, filterEventType, filterPolicy, filterSessionId });
|
|
336
|
-
filtersRef.current = { filterDecision, filterEventType, filterPolicy, filterSessionId };
|
|
374
|
+
const filtersRef = useRef({ filterDecision, filterEventType, filterPolicy, filterSessionId, filterCli });
|
|
375
|
+
filtersRef.current = { filterDecision, filterEventType, filterPolicy, filterSessionId, filterCli };
|
|
337
376
|
|
|
338
377
|
useEffect(() => {
|
|
339
378
|
if (!mountedRef.current) {
|
|
@@ -345,17 +384,18 @@ function ActivityTab({
|
|
|
345
384
|
event: filterEventType || undefined,
|
|
346
385
|
policy: filterPolicy || undefined,
|
|
347
386
|
session: filterSessionId || undefined,
|
|
387
|
+
cli: filterCli || undefined,
|
|
348
388
|
page: pageToParam(page),
|
|
349
389
|
});
|
|
350
390
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
351
|
-
}, [filterDecision, filterEventType, filterPolicy, filterSessionId, page]);
|
|
391
|
+
}, [filterDecision, filterEventType, filterPolicy, filterSessionId, filterCli, page]);
|
|
352
392
|
|
|
353
|
-
const hasActiveFilters = filterDecision !== "" || filterEventType !== "" || filterPolicy !== "" || filterSessionId !== "";
|
|
393
|
+
const hasActiveFilters = filterDecision !== "" || filterEventType !== "" || filterPolicy !== "" || filterSessionId !== "" || filterCli !== "";
|
|
354
394
|
|
|
355
395
|
const fetchData = useCallback(async (p: number) => {
|
|
356
396
|
try {
|
|
357
|
-
const { filterDecision: fd, filterEventType: fe, filterPolicy: fp, filterSessionId: fs } = filtersRef.current;
|
|
358
|
-
const active = fd !== "" || fe !== "" || fp !== "" || fs !== "";
|
|
397
|
+
const { filterDecision: fd, filterEventType: fe, filterPolicy: fp, filterSessionId: fs, filterCli: fc } = filtersRef.current;
|
|
398
|
+
const active = fd !== "" || fe !== "" || fp !== "" || fs !== "" || fc !== "";
|
|
359
399
|
let result: HookActivityPayload;
|
|
360
400
|
if (active) {
|
|
361
401
|
result = await searchHookActivityAction(
|
|
@@ -364,6 +404,7 @@ function ActivityTab({
|
|
|
364
404
|
eventType: fe || undefined,
|
|
365
405
|
policyName: fp || undefined,
|
|
366
406
|
sessionId: fs || undefined,
|
|
407
|
+
integration: fc || undefined,
|
|
367
408
|
},
|
|
368
409
|
p,
|
|
369
410
|
);
|
|
@@ -394,7 +435,7 @@ function ActivityTab({
|
|
|
394
435
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
395
436
|
};
|
|
396
437
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
397
|
-
}, [filterDecision, filterEventType, filterPolicy, filterSessionId]);
|
|
438
|
+
}, [filterDecision, filterEventType, filterPolicy, filterSessionId, filterCli]);
|
|
398
439
|
|
|
399
440
|
const items = data?.entries ?? [];
|
|
400
441
|
const totalPages = data?.totalPages ?? 1;
|
|
@@ -427,6 +468,16 @@ function ActivityTab({
|
|
|
427
468
|
<option value="UserPromptSubmit">UserPromptSubmit</option>
|
|
428
469
|
<option value="PermissionRequest">PermissionRequest</option>
|
|
429
470
|
</select>
|
|
471
|
+
<select
|
|
472
|
+
value={filterCli}
|
|
473
|
+
onChange={(e) => setFilterCli(e.target.value as "" | "claude" | "codex")}
|
|
474
|
+
className="h-7 rounded-md border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary/40 transition-shadow"
|
|
475
|
+
aria-label="Filter by CLI"
|
|
476
|
+
>
|
|
477
|
+
<option value="">All CLIs</option>
|
|
478
|
+
<option value="claude">Claude Code</option>
|
|
479
|
+
<option value="codex">OpenAI Codex</option>
|
|
480
|
+
</select>
|
|
430
481
|
<div className="relative">
|
|
431
482
|
<input
|
|
432
483
|
type="text"
|
|
@@ -548,7 +599,12 @@ function ActivityTab({
|
|
|
548
599
|
<DurationDisplay ms={item.durationMs} />
|
|
549
600
|
</td>
|
|
550
601
|
<td className="px-3 py-2" title={item.sessionId ?? ""}>
|
|
551
|
-
<SessionCell
|
|
602
|
+
<SessionCell
|
|
603
|
+
sessionId={item.sessionId}
|
|
604
|
+
transcriptPath={item.transcriptPath}
|
|
605
|
+
integration={item.integration}
|
|
606
|
+
cwd={item.cwd}
|
|
607
|
+
/>
|
|
552
608
|
</td>
|
|
553
609
|
<td className="px-3 py-2">
|
|
554
610
|
{item.permissionMode ? (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Project page — shows metadata and a filterable sessions list for a single project. */
|
|
2
2
|
import { Suspense } from "react";
|
|
3
|
-
import { resolveProjectPath, getCachedSessionFiles } from "@/lib/projects";
|
|
3
|
+
import { resolveProjectPath, getCachedSessionFiles, type SessionFile } from "@/lib/projects";
|
|
4
|
+
import { getCachedCodexSessionsByEncodedName } from "@/lib/codex-projects";
|
|
4
5
|
import { logWarn } from "@/lib/logger";
|
|
5
6
|
import { decodeFolderName } from "@/lib/paths";
|
|
6
7
|
import { notFound } from "next/navigation";
|
|
@@ -8,7 +9,7 @@ import { existsSync } from "fs";
|
|
|
8
9
|
import { stat } from "fs/promises";
|
|
9
10
|
import Link from "next/link";
|
|
10
11
|
import { ArrowLeft } from "lucide-react";
|
|
11
|
-
import { formatDate } from "@/lib/
|
|
12
|
+
import { formatDate } from "@/lib/format-date";
|
|
12
13
|
import SessionsList from "@/app/components/sessions-list";
|
|
13
14
|
|
|
14
15
|
export const dynamic = "force-dynamic";
|
|
@@ -21,34 +22,61 @@ interface ProjectPageProps {
|
|
|
21
22
|
|
|
22
23
|
export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
23
24
|
const { name } = await params;
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
let
|
|
25
|
+
// Resolve under ~/.claude/projects/. Validation may throw RangeError; on bad input
|
|
26
|
+
// we still want to try Codex, since a Codex-only cwd never escapes this check.
|
|
27
|
+
let claudeProjectPath: string | null = null;
|
|
27
28
|
try {
|
|
28
|
-
|
|
29
|
+
claudeProjectPath = resolveProjectPath(name);
|
|
29
30
|
} catch {
|
|
30
|
-
|
|
31
|
+
claudeProjectPath = null;
|
|
31
32
|
}
|
|
32
33
|
const decodedName = decodeFolderName(name);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const claudeExists = claudeProjectPath ? existsSync(claudeProjectPath) : false;
|
|
36
|
+
|
|
37
|
+
let claudeSessions: SessionFile[] = [];
|
|
38
|
+
if (claudeExists && claudeProjectPath) {
|
|
39
|
+
claudeSessions = await getCachedSessionFiles(claudeProjectPath);
|
|
40
|
+
}
|
|
41
|
+
// Note: decodeFolderName is lossy when cwds contain `-` (every `-` becomes `/`),
|
|
42
|
+
// so we look up Codex sessions by re-encoding each session's cwd and matching the slug.
|
|
43
|
+
const codex = await getCachedCodexSessionsByEncodedName(name);
|
|
44
|
+
const codexSessions = codex.sessions;
|
|
45
|
+
|
|
46
|
+
if (!claudeExists && codexSessions.length === 0) {
|
|
36
47
|
notFound();
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
//
|
|
50
|
+
// Prefer the canonical Codex cwd when available — `decodeFolderName(name)` is
|
|
51
|
+
// ambiguous for cwds containing `-` (every `-` becomes `/`). Codex transcripts
|
|
52
|
+
// record the literal cwd, so they round-trip correctly.
|
|
53
|
+
const canonicalRoot = codex.cwd ?? decodedName;
|
|
54
|
+
|
|
55
|
+
// Project header metadata
|
|
40
56
|
let lastModified: Date | null = null;
|
|
41
57
|
let lastModifiedFormatted: string | null = null;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
if (claudeExists && claudeProjectPath) {
|
|
59
|
+
try {
|
|
60
|
+
const stats = await stat(claudeProjectPath);
|
|
61
|
+
lastModified = stats.mtime;
|
|
62
|
+
lastModifiedFormatted = formatDate(stats.mtime);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logWarn(`Failed to get stats for project ${decodedName}:`, error);
|
|
65
|
+
}
|
|
48
66
|
}
|
|
67
|
+
const newestCodex = codexSessions[0]?.lastModified ?? null;
|
|
68
|
+
if (newestCodex && (!lastModified || newestCodex.getTime() > lastModified.getTime())) {
|
|
69
|
+
lastModified = newestCodex;
|
|
70
|
+
lastModifiedFormatted = formatDate(newestCodex);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const sessionFiles: SessionFile[] = [...claudeSessions, ...codexSessions].sort(
|
|
74
|
+
(a, b) => b.lastModified.getTime() - a.lastModified.getTime(),
|
|
75
|
+
);
|
|
49
76
|
|
|
50
|
-
//
|
|
51
|
-
|
|
77
|
+
// Path line: prefer the Claude storage dir if present (matches existing UX);
|
|
78
|
+
// otherwise show the canonical Codex cwd.
|
|
79
|
+
const displayPath = claudeExists && claudeProjectPath ? claudeProjectPath : canonicalRoot;
|
|
52
80
|
|
|
53
81
|
return (
|
|
54
82
|
<main className="min-h-screen bg-background">
|
|
@@ -63,11 +91,11 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
63
91
|
|
|
64
92
|
<div className="mb-8">
|
|
65
93
|
<h1 className="text-4xl font-bold text-foreground mb-2 break-words break-all">
|
|
66
|
-
{
|
|
94
|
+
{canonicalRoot}
|
|
67
95
|
</h1>
|
|
68
96
|
<div className="space-y-1">
|
|
69
97
|
<p className="text-muted-foreground">
|
|
70
|
-
<span className="font-medium">Path:</span> {
|
|
98
|
+
<span className="font-medium">Path:</span> {displayPath}
|
|
71
99
|
</p>
|
|
72
100
|
{lastModifiedFormatted && (
|
|
73
101
|
<p className="text-muted-foreground">
|
|
@@ -80,7 +108,7 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
80
108
|
{/* Sessions Section */}
|
|
81
109
|
<div className="bg-card text-card-foreground rounded-lg border border-border p-6 shadow-sm">
|
|
82
110
|
<h2 className="text-2xl font-semibold mb-4">Sessions</h2>
|
|
83
|
-
|
|
111
|
+
|
|
84
112
|
{sessionFiles.length === 0 ? (
|
|
85
113
|
<div className="text-center py-8">
|
|
86
114
|
<p className="text-muted-foreground mb-2">
|
|
@@ -98,4 +126,3 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
98
126
|
</main>
|
|
99
127
|
);
|
|
100
128
|
}
|
|
101
|
-
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { ArrowLeft, Download } from "lucide-react";
|
|
4
4
|
import { notFound } from "next/navigation";
|
|
5
|
-
import { getCachedSessionLog } from "@/lib/log-entries";
|
|
5
|
+
import { getCachedSessionLog, type LogEntry } from "@/lib/log-entries";
|
|
6
|
+
import { getCachedCodexSessionLog } from "@/lib/codex-sessions";
|
|
6
7
|
import { decodeFolderName } from "@/lib/paths";
|
|
7
8
|
import { baseSessionId } from "@/lib/utils/session-id";
|
|
8
9
|
import { resolveProjectPath, UUID_RE } from "@/lib/projects";
|
|
9
10
|
import LazyLogViewer from "@/app/components/lazy-log-viewer";
|
|
10
11
|
import { CopyButton } from "@/app/components/copy-button";
|
|
12
|
+
import { CliBadge } from "@/app/components/cli-badge";
|
|
11
13
|
|
|
12
14
|
export const dynamic = "force-dynamic";
|
|
13
15
|
|
|
@@ -30,9 +32,11 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
30
32
|
const decodedSessionId = baseSessionId(sessionId);
|
|
31
33
|
if (!UUID_RE.test(decodedSessionId)) notFound();
|
|
32
34
|
|
|
33
|
-
let entries = null;
|
|
35
|
+
let entries: LogEntry[] | null = null;
|
|
34
36
|
let rawLines: Record<string, unknown>[] | null = null;
|
|
35
37
|
let error: string | null = null;
|
|
38
|
+
let cli: "claude" | "codex" = "claude";
|
|
39
|
+
let codexCwd: string | undefined;
|
|
36
40
|
|
|
37
41
|
try {
|
|
38
42
|
// Use raw folder name for file operations — decodedName is for display only
|
|
@@ -41,28 +45,50 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
41
45
|
rawLines = result.rawLines;
|
|
42
46
|
} catch (e) {
|
|
43
47
|
const isNotFound = (e as NodeJS.ErrnoException).code === "ENOENT";
|
|
44
|
-
|
|
48
|
+
if (isNotFound) {
|
|
49
|
+
// Fall back to Codex transcripts. Codex stores files at
|
|
50
|
+
// ~/.codex/sessions/<YYYY>/<MM>/<DD>/<file containing sessionId>.jsonl,
|
|
51
|
+
// so the [name] segment is irrelevant — we look up by sessionId.
|
|
52
|
+
const codex = await getCachedCodexSessionLog(decodedSessionId);
|
|
53
|
+
if (codex) {
|
|
54
|
+
entries = codex.entries;
|
|
55
|
+
rawLines = codex.rawLines;
|
|
56
|
+
codexCwd = codex.cwd;
|
|
57
|
+
cli = "codex";
|
|
58
|
+
} else {
|
|
59
|
+
error = "Session log file not found.";
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
error = "Failed to read session log.";
|
|
63
|
+
}
|
|
45
64
|
}
|
|
46
65
|
|
|
66
|
+
const isCodex = cli === "codex";
|
|
67
|
+
const headerLabel = isCodex ? "CLI" : "Project";
|
|
68
|
+
const headerValue = isCodex ? `OpenAI Codex${codexCwd ? ` · ${codexCwd}` : ""}` : decodedName;
|
|
69
|
+
|
|
47
70
|
return (
|
|
48
71
|
<main className="min-h-screen bg-background">
|
|
49
72
|
<div className="container mx-auto p-8">
|
|
50
73
|
<Link
|
|
51
|
-
href={`/project/${encodeURIComponent(name)}`}
|
|
74
|
+
href={isCodex ? "/policies?tab=activity" : `/project/${encodeURIComponent(name)}`}
|
|
52
75
|
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground mb-6 transition-colors"
|
|
53
76
|
>
|
|
54
77
|
<ArrowLeft className="w-4 h-4" />
|
|
55
|
-
<span>Back to Sessions</span>
|
|
78
|
+
<span>{isCodex ? "Back to Activity" : "Back to Sessions"}</span>
|
|
56
79
|
</Link>
|
|
57
80
|
|
|
58
81
|
<div className="mb-8">
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
<div className="flex flex-wrap items-center gap-3 mb-2">
|
|
83
|
+
<h1 className="text-4xl font-bold text-foreground">
|
|
84
|
+
Session Log
|
|
85
|
+
</h1>
|
|
86
|
+
<CliBadge cli={cli} />
|
|
87
|
+
</div>
|
|
62
88
|
<div className="space-y-1">
|
|
63
89
|
<p className="text-muted-foreground">
|
|
64
|
-
<span className="font-medium">
|
|
65
|
-
{
|
|
90
|
+
<span className="font-medium">{headerLabel}:</span>{" "}
|
|
91
|
+
{headerValue}
|
|
66
92
|
</p>
|
|
67
93
|
<p className="text-muted-foreground break-words break-all inline-flex items-center gap-1">
|
|
68
94
|
<span className="font-medium">Session:</span> {decodedSessionId}
|
|
@@ -73,14 +99,16 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
73
99
|
<p className="text-muted-foreground">
|
|
74
100
|
<span className="font-medium">{rawLines.length}</span> log lines
|
|
75
101
|
</p>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
{!isCodex && (
|
|
103
|
+
<a
|
|
104
|
+
href={`/api/download/${encodeURIComponent(name)}/${encodeURIComponent(decodedSessionId)}`}
|
|
105
|
+
download
|
|
106
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors"
|
|
107
|
+
>
|
|
108
|
+
<Download className="w-4 h-4" />
|
|
109
|
+
Download Logs
|
|
110
|
+
</a>
|
|
111
|
+
)}
|
|
84
112
|
</div>
|
|
85
113
|
)}
|
|
86
114
|
</div>
|
|
@@ -92,7 +120,11 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
92
120
|
</div>
|
|
93
121
|
)}
|
|
94
122
|
{!error && entries && (
|
|
95
|
-
<LazyLogViewer
|
|
123
|
+
<LazyLogViewer
|
|
124
|
+
entries={entries}
|
|
125
|
+
projectName={isCodex ? (codexCwd ?? "OpenAI Codex") : decodedName}
|
|
126
|
+
sessionId={decodedSessionId}
|
|
127
|
+
/>
|
|
96
128
|
)}
|
|
97
129
|
</div>
|
|
98
130
|
</main>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
import React, { useState, useCallback } from "react";
|
|
5
|
-
import { GitBranch, Lightbulb, Bug, MessageSquare, ChevronDown, Star, BookOpen } from "lucide-react";
|
|
5
|
+
import { GitBranch, Lightbulb, Bug, MessageSquare, ChevronDown, Star, BookOpen, Hash } from "lucide-react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
|
|
8
8
|
const GITHUB_REPO = "https://github.com/exospherehost/failproofai";
|
|
@@ -19,6 +19,11 @@ const options = [
|
|
|
19
19
|
icon: BookOpen,
|
|
20
20
|
href: "https://befailproof.ai",
|
|
21
21
|
},
|
|
22
|
+
{
|
|
23
|
+
label: "Join our Slack",
|
|
24
|
+
icon: Hash,
|
|
25
|
+
href: "https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ",
|
|
26
|
+
},
|
|
22
27
|
{
|
|
23
28
|
label: "Request a Feature",
|
|
24
29
|
icon: Lightbulb,
|