minecodex 2.0.2 → 2.0.3
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/DESIGN.md +149 -0
- package/apps/dashboard/DESIGN.md +45 -0
- package/features/images/DESIGN.md +71 -0
- package/features/images/README.md +2 -0
- package/features/images/codex-feature.json +2 -1
- package/features/images/src/adapters/codex/cloud-import.mjs +52 -0
- package/features/images/src/adapters/codex/image-prompt.mjs +22 -0
- package/features/images/src/adapters/codex/library-api.mjs +55 -0
- package/features/images/src/adapters/codex/library-viewer.mjs +18 -0
- package/features/images/src/adapters/codex/prompt-resolver-source.mjs +8 -0
- package/features/images/src/backend/cloud-library.mjs +10 -0
- package/features/images/src/domain/library-prompt.mjs +7 -0
- package/features/images/src/library-prompt-page.mjs +7 -75
- package/features/images/src/library-prompt-source.mjs +10 -0
- package/features/images/web/app.js +9 -9
- package/features/images/web/styles.css +219 -219
- package/features/model-slider/DESIGN.md +34 -0
- package/features/model-slider/src/adapters/codex/controller.mjs +93 -0
- package/features/model-slider/src/domain/model-rules.mjs +143 -0
- package/features/model-slider/src/frontend/install.mjs +2110 -0
- package/features/model-slider/src/injection-source.mjs +10 -0
- package/features/notes/DESIGN.md +13 -15
- package/features/notes/README.md +7 -1
- package/features/notes/assets/icons/book-open-nav-fill.svg +1 -0
- package/features/notes/codex-feature.json +5 -1
- package/features/notes/src/{default-prompts.mjs → backend/default-prompts.mjs} +1 -1
- package/features/notes/src/{product.mjs → backend/product.mjs} +1 -1
- package/features/notes/src/{quick-prompts-server.mjs → backend/quick-prompts-server.mjs} +2 -2
- package/features/notes/src/{quick-prompts-page.mjs → frontend/entry.mjs} +64 -257
- package/features/notes/src/frontend/icons.json +13 -0
- package/features/notes/src/frontend/styles.css +110 -0
- package/features/notes/src/main.mjs +2 -2
- package/features/notes/vendor/phosphor-LICENSE +21 -0
- package/features/session-status/DESIGN.md +7 -7
- package/features/session-status/README.md +4 -0
- package/features/session-status/codex-feature.json +1 -1
- package/features/session-status/src/adapters/codex/context-usage.mjs +39 -0
- package/features/session-status/src/adapters/codex/session-log.mjs +81 -0
- package/features/session-status/src/adapters/codex/session-reader.mjs +106 -0
- package/features/session-status/src/domain/session-stats.mjs +141 -0
- package/features/session-status/src/injection-source.mjs +6 -0
- package/features/session-status/src/session-status.mjs +41 -101
- package/features/sidebar-focus/DESIGN.md +6 -5
- package/features/sidebar-focus/codex-feature.json +2 -1
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +28 -0
- package/features/sidebar-focus/src/injection-source.mjs +6 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +19 -66
- package/features/sites/DESIGN.md +60 -0
- package/features/sites/README.md +27 -0
- package/features/sites/codex-feature.json +28 -0
- package/features/sites/src/adapters/codex/catalog.mjs +26 -0
- package/features/sites/src/adapters/codex/renderer.mjs +83 -0
- package/features/sites/src/backend/discovery.mjs +73 -0
- package/features/sites/src/backend/project-sites.mjs +34 -0
- package/features/sites/src/backend/site-launcher.mjs +54 -0
- package/features/sites/src/backend/sites-preview.mjs +53 -0
- package/features/sites/src/backend/sites-service.mjs +162 -0
- package/features/sites/src/domain/site.mjs +40 -0
- package/features/sites/src/frontend/entry.mjs +383 -0
- package/features/sites/src/frontend/icons.json +12 -0
- package/features/sites/src/frontend/styles.css +72 -0
- package/features/sites/src/injection-source.mjs +7 -0
- package/features/sites/vendor/lucide-LICENSE +35 -0
- package/features/unread-inbox/DESIGN.md +6 -2
- package/features/unread-inbox/codex-feature.json +2 -1
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +66 -0
- package/features/unread-inbox/src/injection-source.mjs +6 -0
- package/features/unread-inbox/src/unread-inbox.mjs +34 -89
- package/features/usage-meter/DESIGN.md +9 -9
- package/features/usage-meter/README.md +2 -1
- package/features/usage-meter/codex-feature.json +3 -3
- package/features/usage-meter/src/adapters/codex/renderer.mjs +61 -0
- package/features/usage-meter/src/injection-source.mjs +6 -0
- package/features/usage-meter/src/usage-meter.mjs +27 -66
- package/package.json +17 -4
- package/packages/cli/src/config.mjs +2 -1
- package/packages/codex-adapter/README.md +15 -0
- package/packages/codex-adapter/package.json +1 -0
- package/packages/codex-adapter/src/renderer/environment.mjs +53 -0
- package/packages/runtime-host/README.md +27 -0
- package/packages/runtime-host/src/codex-injection.mjs +32 -2393
- package/packages/runtime-host/src/codex-runtime.mjs +21 -3
- package/packages/runtime-host/src/feature-registry.mjs +28 -3
- package/packages/ui/DESIGN.md +31 -0
- package/packages/ui/package.json +8 -0
- package/packages/ui/src/components/button.mjs +21 -0
- package/packages/ui/src/components/composer-menu.mjs +18 -0
- package/packages/ui/src/components/dialog.mjs +55 -0
- package/packages/ui/src/components/navigation-rail.mjs +77 -0
- package/packages/ui/src/components/page.mjs +8 -0
- package/packages/ui/src/components/tooltip.mjs +96 -0
- package/packages/ui/src/foundation/layers.mjs +14 -0
- package/packages/ui/src/foundation/surfaces.mjs +34 -0
- package/packages/ui/src/install.mjs +22 -0
- package/packages/ui/src/native/navigation-rail.mjs +33 -0
- package/packages/ui/src/native/theme.mjs +35 -0
- package/packages/{runtime-host/src/codex-design-contract.mjs → ui/src/native/tokens.mjs} +67 -24
- package/packages/runtime-host/src/cloud-images.mjs +0 -123
- package/packages/runtime-host/src/feature-tooltips.mjs +0 -65
- package/packages/runtime-host/src/session-status-reader.mjs +0 -340
- /package/features/notes/src/{prompt-schema.mjs → backend/prompt-schema.mjs} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title-control { display:flex; align-items:stretch; min-height:40px; border:1px solid var(--mcx-color-border-primary-outline); border-radius:var(--mcx-radius-lg); background:var(--color-background-primary-soft); overflow:hidden; }
|
|
2
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title-control:focus-within { border-color:var(--mcx-color-ring); }
|
|
3
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title-control>.pm-emoji-trigger { display:flex; flex:none; align-items:center; justify-content:center; width:40px; padding:0; border-inline-end:1px solid var(--mcx-color-border); }
|
|
4
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title-control>.pm-emoji-trigger:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
5
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title-control>.pm-emoji-trigger:focus-visible { outline-offset:-3px; }
|
|
6
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor .pm-title-control>input { flex:1; min-width:0; border:0; border-radius:0; background:transparent; padding:8px 10px; outline:none; box-shadow:none; }
|
|
7
|
+
|
|
8
|
+
[data-minecodex-quick-prompts=panel] { position:fixed; z-index:50; zoom:var(--codex-window-zoom,1); transform:translateY(-100%); box-sizing:border-box; display:flex; flex-direction:column; overflow:hidden; padding:var(--mcx-spacing); color:var(--mcx-color-text-primary); font-family:var(--mcx-font-sans-default); font-size:var(--mcx-text-sm); line-height:1.43; }
|
|
9
|
+
[data-minecodex-quick-prompts=panel] *, [data-minecodex-quick-prompts=button] { box-sizing:border-box; }
|
|
10
|
+
[data-minecodex-quick-prompts=panel] button { cursor:var(--cursor-interaction,pointer); }
|
|
11
|
+
[data-minecodex-quick-prompts=panel] button:disabled { opacity:.4; cursor:default; }
|
|
12
|
+
[data-minecodex-quick-prompts=panel] button:focus-visible, [data-minecodex-quick-prompts=panel] input:focus-visible, [data-minecodex-quick-prompts=panel] textarea:focus-visible { outline:2px solid var(--mcx-color-ring,var(--mcx-color-text-secondary)); outline-offset:-2px; }
|
|
13
|
+
[data-minecodex-quick-prompts=panel] svg { width:16px; height:16px; flex:none; }
|
|
14
|
+
[data-minecodex-quick-prompts=panel] .qp-header { display:flex; align-items:center; justify-content:space-between; padding:var(--mcx-spacing) var(--padding-row-x,8px); color:var(--mcx-color-text-tertiary); flex:none; }
|
|
15
|
+
[data-minecodex-quick-prompts=panel] .qp-scroll { overflow-y:auto; min-height:0; padding-bottom:var(--mcx-spacing); }
|
|
16
|
+
[data-minecodex-quick-prompts=panel] .qp-row { display:flex; align-items:center; min-width:0; border-radius:var(--suggestion-menu-item-radius,var(--mcx-radius-xl)); }
|
|
17
|
+
[data-minecodex-quick-prompts=panel] .qp-row:hover, [data-minecodex-quick-prompts=panel] .qp-row:focus-within { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
18
|
+
[data-minecodex-quick-prompts=panel] .qp-select { display:flex; flex:1; align-items:center; min-width:0; gap:calc(var(--mcx-spacing)*1.5); text-align:left; padding:var(--suggestion-menu-item-padding,var(--app-menu-item-padding,var(--padding-row-y,5px) var(--padding-row-x,8px))); border-radius:inherit; }
|
|
19
|
+
[data-minecodex-quick-prompts=panel] .qp-title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
20
|
+
[data-minecodex-quick-prompts=panel] .qp-preview { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:var(--mcx-color-text-tertiary); }
|
|
21
|
+
[data-minecodex-quick-prompts=panel] .qp-empty { padding:calc(var(--mcx-spacing)*3) var(--padding-row-x,8px); color:var(--mcx-color-text-tertiary); }
|
|
22
|
+
[data-minecodex-quick-prompts=panel] .qp-command { padding:var(--mcx-spacing) calc(var(--mcx-spacing)*2); border-radius:var(--mcx-radius-xl); }
|
|
23
|
+
[data-minecodex-quick-prompts=panel] .qp-command:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
24
|
+
[data-minecodex-quick-prompts=panel] .qp-error { color:var(--mcx-color-text-danger,var(--mcx-color-text-primary)); padding:var(--mcx-spacing) var(--padding-row-x,8px); }
|
|
25
|
+
.qp-symbol { display:inline-flex; width:20px; height:20px; flex:none; align-items:center; justify-content:center; font-size:18px; line-height:1; color:var(--prompt-icon-light,var(--mcx-color-text-secondary)); }
|
|
26
|
+
.qp-symbol svg { width:20px; height:20px; }
|
|
27
|
+
.dark .qp-symbol, [data-theme=dark] .qp-symbol { color:var(--prompt-icon-dark,var(--mcx-color-text-secondary)); }
|
|
28
|
+
[data-minecodex-quick-prompts=page] { overflow:auto; color:var(--mcx-color-text-primary); font-family:var(--mcx-font-sans-default); font-size:var(--mcx-text-sm); line-height:var(--mcx-text-sm--line-height); }
|
|
29
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) * { box-sizing:border-box; }
|
|
30
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) button { cursor:var(--cursor-interaction,pointer); }
|
|
31
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) button:disabled { opacity:.4; cursor:default; }
|
|
32
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) button:focus-visible, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) input:focus-visible, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) textarea:focus-visible { outline:2px solid var(--mcx-color-text-secondary); outline-offset:2px; }
|
|
33
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-wrap { max-width:1440px; margin:auto; padding:28px 48px 48px; }
|
|
34
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-heading { display:flex; align-items:center; justify-content:space-between; gap:16px; min-height:36px; margin-bottom:24px; }
|
|
35
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) h1 { font-size:var(--mcx-text-heading-lg); font-weight:var(--font-weight-medium); line-height:1.2; margin:0; }
|
|
36
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) h2 { font-size:var(--mcx-text-base,14px); font-weight:600; margin:24px 0 12px; }
|
|
37
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-header-actions { display:flex; align-items:center; gap:8px; }
|
|
38
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tab-row { display:flex; align-items:center; gap:8px; margin-bottom:24px; }
|
|
39
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tab-row .pm-tabs { margin-bottom:0; min-width:0; }
|
|
40
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon { display:flex; align-items:center; justify-content:center; width:32px; height:32px; flex:none; padding:0; border-radius:var(--mcx-radius-full); color:var(--mcx-color-text-secondary); }
|
|
41
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon[hidden] { display:none; }
|
|
42
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon svg { width:16px; height:16px; }
|
|
43
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon:hover, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon[aria-expanded=true], :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-icon[aria-pressed=true] { background:var(--mcx-color-background-primary-ghost-hover); color:var(--mcx-color-text-primary); }
|
|
44
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup { position:fixed; z-index:30; width:200px; padding:var(--app-menu-gutter,var(--mcx-spacing)); border-radius:var(--mcx-radius-2xl); }
|
|
45
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup button { width:100%; min-height:var(--app-menu-item-height,30px); display:flex; align-items:center; justify-content:space-between; gap:16px; padding:var(--app-menu-item-padding,5px 8px); border-radius:var(--mcx-radius-xl); text-align:left; }
|
|
46
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup button:hover, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup button:focus-visible { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
47
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup svg { width:16px; height:16px; }
|
|
48
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-command { display:inline-flex; align-items:center; justify-content:center; gap:6px; min-height:32px; padding:6px 12px; border-radius:var(--mcx-radius-full,999px); color:inherit; white-space:nowrap; }
|
|
49
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-command:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
50
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-primary { background:var(--mcx-color-background-primary-solid); color:var(--mcx-color-text-primary-solid); }
|
|
51
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-primary:hover { background:var(--mcx-color-background-primary-solid-hover); }
|
|
52
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tabs { display:flex; gap:4px; overflow:auto; padding-bottom:4px; margin-bottom:24px; }
|
|
53
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tab { display:flex; align-items:center; gap:6px; flex:none; height:36px; border-radius:999px; padding:0 16px; font-weight:500; color:var(--mcx-color-text-secondary); }
|
|
54
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tab:hover, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-tab[aria-selected=true] { background:var(--mcx-color-background-primary-ghost-hover); color:var(--mcx-color-text-primary); }
|
|
55
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) input, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) textarea { font:inherit; color:inherit; background:transparent; border:1px solid var(--mcx-color-border); border-radius:var(--mcx-radius-lg,12px); padding:8px 12px; }
|
|
56
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-list { display:flex; flex-direction:column; gap:4px; }
|
|
57
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-row { display:flex; align-items:center; gap:12px; padding:16px 12px; border-radius:var(--mcx-radius-xl,16px); }
|
|
58
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-row:hover { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
59
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-info { flex:1; min-width:0; text-align:left; }
|
|
60
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-title { font-weight:500; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
|
|
61
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-preview { max-width:65%; color:var(--mcx-color-text-secondary); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; margin-top:3px; }
|
|
62
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-stats { color:var(--mcx-color-text-tertiary); min-width:130px; text-align:right; font-variant-numeric:tabular-nums; font-size:var(--mcx-text-xs,12px); }
|
|
63
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-count { color:var(--mcx-color-text-secondary); }
|
|
64
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty { color:var(--mcx-color-text-tertiary); padding:48px 0; text-align:center; }
|
|
65
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-form { display:flex; flex-direction:column; gap:16px; }
|
|
66
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-field { display:flex; flex-direction:column; gap:6px; color:var(--mcx-color-text-secondary); }
|
|
67
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-field textarea { min-height:220px; resize:vertical; color:var(--mcx-color-text-primary); }
|
|
68
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-grid { display:flex; flex-wrap:wrap; gap:4px; }
|
|
69
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-choice { width:36px; height:36px; border-radius:10px; display:flex; justify-content:center; align-items:center; }
|
|
70
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-choice[aria-pressed=true] { background:var(--mcx-color-background-primary-ghost-hover); outline:1px solid var(--mcx-color-text-secondary); }
|
|
71
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-swatch { width:18px; height:18px; border-radius:50%; background:currentColor; }
|
|
72
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-error { color:var(--mcx-color-text-danger,var(--mcx-color-text-primary)); margin:12px 0; }
|
|
73
|
+
@media(max-width:800px) { :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-wrap { padding:24px 30px; } :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-stats { min-width:90px; } }
|
|
74
|
+
[data-minecodex-quick-prompts=modal].pm-modal-layer { inset:0; display:grid; place-items:center; padding:24px; }
|
|
75
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor { top:auto; width:440px; max-width:calc(100vw - 48px); max-height:calc(100vh - 64px); margin:0; overflow:auto; }
|
|
76
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-prompt-editor { width:520px; }
|
|
77
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-modal-heading .pm-icon { width:24px; height:24px; }
|
|
78
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor .pm-row { min-height:44px; padding:8px; gap:10px; border-radius:var(--mcx-radius-lg); }
|
|
79
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor .pm-form { gap:12px; }
|
|
80
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor .pm-field { gap:8px; color:var(--mcx-color-text-primary); }
|
|
81
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor input, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor textarea { border-radius:var(--mcx-radius-md); padding:8px 10px; background:var(--color-background-primary-soft,rgba(0,0,0,.025)); }
|
|
82
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-grid { gap:4px; }
|
|
83
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-choice { width:36px; height:36px; border-radius:var(--mcx-radius-lg); }
|
|
84
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-choice svg { width:20px; height:20px; }
|
|
85
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-choice[aria-pressed=true] { outline:none; box-shadow:inset 0 0 0 1px var(--mcx-color-border); }
|
|
86
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-search-inline { display:flex; align-items:center; width:clamp(150px,22vw,300px); height:32px; padding:0 12px; gap:8px; border:1px solid var(--mcx-color-border-primary-outline,var(--mcx-color-border)); border-radius:var(--mcx-radius-full); background:transparent; }
|
|
87
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-search-inline input { min-width:0; width:100%; padding:0; border:0; background:transparent; outline:none; }
|
|
88
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-search-inline>svg { width:16px; height:16px; flex:none; color:var(--mcx-color-text-tertiary); }
|
|
89
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-list>h2 { color:var(--mcx-color-text-tertiary); font-size:var(--mcx-text-sm,13px); font-weight:500; }
|
|
90
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-stats { min-width:64px; }
|
|
91
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor input:focus, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-editor textarea:focus, :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup input:focus { outline:none; box-shadow:none; border-color:var(--mcx-color-ring,var(--mcx-color-border-primary-outline)); }
|
|
92
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-confirm-description { color:var(--mcx-color-text-secondary); line-height:1.6; }
|
|
93
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-select { display:flex; align-items:center; gap:8px; min-height:36px; padding:8px 10px; background:var(--color-background-primary-soft); border:1px solid var(--mcx-color-border); border-radius:var(--mcx-radius-md); text-align:left; }
|
|
94
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-select>svg { width:16px; height:16px; }
|
|
95
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-option-label { flex:1; }
|
|
96
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:340px; gap:8px; text-align:center; }
|
|
97
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty-art { font-size:var(--text-5xl,48px); line-height:1; margin-bottom:8px; }
|
|
98
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty-title { font-size:var(--text-lg,18px); line-height:24px; font-weight:500; }
|
|
99
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty-description { color:var(--mcx-color-text-secondary); }
|
|
100
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-empty-state>.pm-command { margin-top:24px; }
|
|
101
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-drag { cursor:grab; color:var(--mcx-color-text-tertiary); }
|
|
102
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-drag:active { cursor:grabbing; }
|
|
103
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-drop-placeholder { flex:none; border:1px dashed var(--mcx-color-border-primary-outline,var(--mcx-color-border)); border-radius:var(--mcx-radius-xl); background:var(--mcx-color-background-primary-ghost-hover); box-sizing:border-box; }
|
|
104
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-dragging { position:absolute; opacity:0; pointer-events:none; }
|
|
105
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-popup { z-index:70; max-height:calc(100vh - 32px); overflow:auto; }
|
|
106
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-symbol-picker { width:320px; padding:12px; display:flex; flex-direction:column; gap:12px; }
|
|
107
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-symbol-picker>input { width:100%; flex:none; border-radius:var(--mcx-radius-full); }
|
|
108
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-grid { display:grid; grid-template-columns:repeat(8,minmax(0,1fr)); gap:4px; max-height:264px; overflow:auto; }
|
|
109
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-grid .pm-emoji-choice { width:32px; height:32px; min-height:32px; padding:0; display:flex; justify-content:center; font-size:22px; border-radius:var(--mcx-radius-lg); }
|
|
110
|
+
:is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-choice[aria-pressed=true] { background:var(--mcx-color-background-primary-ghost-hover); }
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
-
import { createQuickPromptsServer } from "./quick-prompts-server.mjs";
|
|
7
|
-
import { CodexNotesProduct } from "./product.mjs";
|
|
6
|
+
import { createQuickPromptsServer } from "./backend/quick-prompts-server.mjs";
|
|
7
|
+
import { CodexNotesProduct } from "./backend/product.mjs";
|
|
8
8
|
|
|
9
9
|
const host = process.env.CODEX_NOTES_HOST ?? "127.0.0.1";
|
|
10
10
|
const port = Number(process.env.CODEX_NOTES_PORT ?? "47832");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Phosphor Icons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Session Status 设计约定
|
|
2
2
|
|
|
3
|
+
通用视觉、材质、按钮、Tooltip 与原生 sidebar 层级遵循根 [DESIGN.md](../../DESIGN.md)。本文件只记录功能特有组件、布局、数据语义与通用组件选型。
|
|
4
|
+
|
|
3
5
|
## 意图
|
|
4
6
|
|
|
5
7
|
状态行紧随 Codex 原生输入框,帮助用户在长任务中快速判断回合、模型步骤、输出速度和 token 用量。视觉上作为输入框的附属信息,不争夺发送按钮与模型选择器的层级。
|
|
@@ -10,13 +12,7 @@
|
|
|
10
12
|
| --- | --- | --- |
|
|
11
13
|
| 次要文字 | `--color-text-tertiary` | 状态行文字与分隔符 |
|
|
12
14
|
| 主要文字 | `--color-text-primary` | 弹层标题和数值 |
|
|
13
|
-
| 弹层表面 | `--color-token-dropdown-background` | 点击后的明细 |
|
|
14
|
-
| 边框 | `--color-border-subtle` | 弹层与分割线 |
|
|
15
|
-
| 悬停 | `--color-token-list-hover-background` | 状态按钮反馈 |
|
|
16
|
-
| 焦点描边 | `--input-outline-border-color-focus` | 键盘焦点反馈 |
|
|
17
15
|
| 分类进度 | Codex `--app-block-accent-purple`、`--app-block-accent-blue`、`--app-block-accent-green`,以及 `--color-token-border-default` | 各弹层的分类比例与未分类段 |
|
|
18
|
-
| 字体 | `--font-sans-default`、`--text-xs` | 与原生辅助信息一致 |
|
|
19
|
-
| 圆角与阴影 | `--radius-xl`、`--shadow-lg` | 明细弹层 |
|
|
20
16
|
|
|
21
17
|
状态行和输入框共用内容宽度,单行高度为 24px,顶部留 2px 内边距;每个状态按钮高 22px,内边距为上下 3px、左右 6px。按钮之间 gap 为 4px,两组可见内容间距为 16px(6 + 4 + 6);按钮内图标与文字 gap 为 6px。状态行展开且可见时,将包含它的原生 `.bottom-0.pb-4` 容器底部 padding 从 16px 覆盖为 4px;收起、隐藏或卸载后自动恢复原生留白,展开的状态行使用 -4px 顶部外边距,将输入框下方原生 8px gap 的实际距离收为4px,收起时恢复。宽度不足时自然换行,不覆盖输入框或悬浮控件。统计图标采用 Lucide Gauge,用量图标采用 Phosphor Database 单路径版本,两者以 14px 绘制;上下文使用与 Codex 原生相同参数的 12px 百分环。弹层由按钮锚定,键盘可聚焦,Escape 和点击外部关闭。遵守系统减少动态效果设置,不添加持续动画。
|
|
22
18
|
|
|
@@ -32,10 +28,14 @@ TPS 根据 Codex 会话事件计算有效样本的会话平均约值,以 `N to
|
|
|
32
28
|
|
|
33
29
|
会话统计浮窗只显示回合、步骤、首字延迟和 TPS。Token 用量浮窗以 Total 为主项,紧接比例条和输出(紫)、其他输入(蓝)、缓存输入(绿)三项,顺序与上下文弹层一致;分隔线后才显示缓存命中率。其他输入为总输入减缓存输入,包含缓存写入。三项使用同一分母。
|
|
34
30
|
|
|
31
|
+
TPS 标签后放置 12px Lucide info 图标,继承标签颜色,间距 6px。悬浮或键盘聚焦时使用共享 MineCodex tooltip,说明 GPT 等模型未公开完整思考过程及逐 Token 生成时间,此处为体感响应速度参考值,不代表真实 Token 生成速度;中英文随界面语言切换。Tooltip 层级高于详情浮窗。
|
|
32
|
+
|
|
35
33
|
### 折叠入口
|
|
36
34
|
- 首次安装默认展开;用户收起后在模型选择左侧显示 12px context 环,24px 热区;原生 context 环开启时隐藏原生重复入口,卸载时恢复。
|
|
37
|
-
- Tooltip 沿用原生 context 内容:上下文窗口、使用/剩余比例、已用/容量(k tokens
|
|
35
|
+
- Tooltip 沿用原生 context 内容:上下文窗口、使用/剩余比例、已用/容量(k tokens);样式遵循通用 Tooltip。
|
|
38
36
|
- 点击环展开输入框下方详情,右侧 chevron-up 收起;200ms grid 高度与160ms透明度过渡,尊重 reduced-motion。展开状态跨会话、主聊天和 side chat 共享并持久保存。展开时隐藏输入框内的 context 环;收起动效完成后容器 display:none,不保留父级 gap,收起内容不参与键盘焦点。每个输入框订阅自己的会话上下文和统计。
|
|
39
37
|
- Cache 按原始 cachedInputTokens / inputTokens 计算:大于99%显示一位小数,其余取整数,状态栏和详情共用格式。
|
|
40
38
|
|
|
41
39
|
输入框实例仅绑定可见的 thread composer。全屏标签页中的隐藏聊天、浮动 Quick Chat(含 right-panel composer overlay)、inert 区域不注入;原生移动输入框或切换可见性时销毁实例并取消订阅,返回聊天后重建。
|
|
40
|
+
|
|
41
|
+
明细采用通用 Popover,保持本功能的定位、尺寸及内容层级。当前通过 UI 组件库的 `config.ui.surface(panel, "popover")` 选择原生 Popover;功能继续拥有定位、尺寸与内容。
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Context 直接订阅 Codex 原生会话状态。其他统计由 Runtime Host 只读解析本机 `~/.codex/sessions` 中的 Codex 会话事件,再通过已有 CDP 连接向注入脚本发送数值快照;页面只向 Host 请求刷新,不发送对话内容,也不修改 Codex 会话日志。远程任务或缺少本机日志时,无法取得的指标显示 `—`。
|
|
6
6
|
|
|
7
|
+
同一会话可能有多个 rollout 文件,包括续聊产生的 `<thread-id>_<session-id>.jsonl` 分段。读取器每 2 秒检查分段列表;新增分段时按事件时间重建会话统计,随后按各文件独立的读取位置增量更新。回合及模型响应按 ID 去重,累计 Token 采用日志给出的会话总量,不将不同分段的累计值相加。
|
|
8
|
+
|
|
7
9
|
新聊天没有会话 ID 时隐藏;发送首条消息、原生输入框出现会话 ID 后立即显示占位并请求数据,无需等待日志落盘、回答完成或 Token 用量返回。切换聊天时立即清除旧数值,拒绝 ID 不匹配的迟到快照;折叠侧栏不影响当前会话识别。输入框会话标记的属性变化也会触发更新。
|
|
8
10
|
|
|
9
11
|
生成中依据日志的 `task_started` 与完成/中断事件,每 500ms 请求统计;空闲时由 Runtime 每 2 秒刷新兜底,页面不重复轮询。首次找不到日志按 500ms、1s、2s、随后 10s 重试;持续缺失时不会高频扫描目录。并发请求按页面合并,统计刷新不占用图片等用户操作的串行队列。页面卸载或功能关闭会取消定时器、观察器与原生状态订阅。
|
|
@@ -22,6 +24,8 @@ Context 直接订阅 Codex 原生会话状态。其他统计由 Runtime Host 只
|
|
|
22
24
|
|
|
23
25
|
统计图标使用 Lucide Static v1.31.0 的 Gauge;用量图标使用 Phosphor Icons 的 Database,单路径填充避免半透明色在路径交叠处加深。许可见 [`vendor/lucide-LICENSE`](vendor/lucide-LICENSE) 和 [`vendor/phosphor-LICENSE`](vendor/phosphor-LICENSE)。
|
|
24
26
|
|
|
27
|
+
会话统计浮窗的 TPS 标签带有同色 Lucide info 图标(Lucide 0.577.0 官方资源);悬浮或键盘聚焦可查看说明:GPT 等模型不公开完整思考过程及逐 Token 生成时间,TPS 仅为基于可观测事件估算的体感响应速度参考,不代表模型真实的 Token 生成速度。
|
|
28
|
+
|
|
25
29
|
## 上下文分类的可行性
|
|
26
30
|
|
|
27
31
|
当前桌面版原生百分环只需要 `last.totalTokens` 和 `modelContextWindow`。Codex 的会话用量事件还提供输入、缓存、输出和推理输出,但没有系统提示词、工具定义、对话消息各自的 token 数。日志中的 `session_meta.base_instructions`、`response_item` 角色与类型、`compacted.replacement_history` 可以形成有边界的活跃历史分段:基础与开发者指令、消息(包含压缩摘要)、工具调用与结果。参考 Codex 自身的粗估口径,将 UTF-8 可见文本按约 4 字节/token 计,图片按固定大小估算;全程本地运算,不调用模型。加密摘要参考 Codex 对编码长度的换算。
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"en": "Shows conversation steps, observed output speed, token usage and context details below the composer.",
|
|
8
8
|
"zh-CN": "在输入框下方显示对话步骤、观测输出速度、Token 用量与上下文明细。"
|
|
9
9
|
},
|
|
10
|
-
"entry": { "kind": "page-script", "script": "src/session-status.mjs" },
|
|
10
|
+
"entry": { "kind": "page-script", "script": "src/session-status.mjs", "sourceFactory": "src/injection-source.mjs" },
|
|
11
11
|
"icon": {
|
|
12
12
|
"viewBox": "0 0 24 24",
|
|
13
13
|
"markup": "<path d=\"m12 14 4-4\"></path><path d=\"M3.34 19a10 10 0 1 1 17.32 0\"></path>"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// 原生 atom、scope 和 bundle 字段不进入状态栏呈现层。
|
|
2
|
+
export function createContextUsageAdapter(signal, codex, onChange, loadFamily = null) {
|
|
3
|
+
let family=null, thread=null, composer=null, unsubscribe=null;
|
|
4
|
+
const empty = () => ({ contextUsedTokens:null, contextWindow:null, contextPercent:null });
|
|
5
|
+
function disconnect() { unsubscribe?.(); unsubscribe=null; }
|
|
6
|
+
function connect(id, element) {
|
|
7
|
+
if (thread === id && unsubscribe) return;
|
|
8
|
+
disconnect(); thread=id; composer=element; onChange(empty());
|
|
9
|
+
if (!id || !family || !element || signal.aborted) return;
|
|
10
|
+
const props=codex.props(element, p => p.value instanceof Map && [...p.value.values()].some(node => node?.token?.__scopeBrand === 'AppScope'));
|
|
11
|
+
const chain=props?.value, node=chain && [...chain.values()].find(node => node?.token?.__scopeBrand === 'AppScope');
|
|
12
|
+
if (!node) { codex.report("context-usage",false,"app-scope-unavailable"); return; }
|
|
13
|
+
try {
|
|
14
|
+
const atom=family.resolve(node,chain,id).resolve(node,chain);
|
|
15
|
+
const update=()=>{
|
|
16
|
+
if (signal.aborted || thread !== id) return;
|
|
17
|
+
const usage=node.store.get(atom), window=usage?.modelContextWindow, total=usage?.last?.totalTokens;
|
|
18
|
+
const valid=Number.isFinite(window) && window > 0 && Number.isFinite(total) && total >= 0;
|
|
19
|
+
const used=valid ? Math.min(total,window) : null;
|
|
20
|
+
onChange(valid ? {contextUsedTokens:used,contextWindow:window,contextPercent:Math.round(used/window*100)} : empty());
|
|
21
|
+
};
|
|
22
|
+
unsubscribe=node.store.sub(atom,update); update(); codex.report("context-usage",true);
|
|
23
|
+
} catch { codex.report("context-usage",false,"atom-contract-unavailable"); }
|
|
24
|
+
}
|
|
25
|
+
async function discover() {
|
|
26
|
+
if (loadFamily) return loadFamily();
|
|
27
|
+
const url=[...document.querySelectorAll('link[rel=modulepreload]')].map(link=>link.href).find(url=>/\/app-shared-[^/]+\.js(?:$|\?)/.test(url));
|
|
28
|
+
if (!url) throw new Error("context-bundle-unavailable");
|
|
29
|
+
const source=await (await fetch(url,{signal})).text();
|
|
30
|
+
const local=source.match(/([\w$]+)=[\w$]+\(\(\{latestTokenUsageInfo:/)?.[1];
|
|
31
|
+
const exported=[...source.matchAll(/([\w$]+) as ([\w$]+)/g)].find(match=>match[1]===local)?.[2];
|
|
32
|
+
if (!exported) throw new Error("context-export-unavailable");
|
|
33
|
+
return (await import(url))[exported];
|
|
34
|
+
}
|
|
35
|
+
void discover().then(value=>{ if (!signal.aborted) { family=value; connect(thread,composer); } })
|
|
36
|
+
.catch(()=>codex.report("context-usage",false,"context-capability-unavailable"));
|
|
37
|
+
signal.addEventListener('abort',disconnect,{once:true});
|
|
38
|
+
return { connect, dispose:disconnect };
|
|
39
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const MODEL_OUTPUT_ITEMS = new Set(["Reasoning", "AgentMessage"]);
|
|
2
|
+
const TOOL_ITEMS = new Set(["CommandExecution", "McpToolCall", "DynamicToolCall"]);
|
|
3
|
+
const IMAGE_TOKENS_ESTIMATE = 1844;
|
|
4
|
+
|
|
5
|
+
function estimatedTextTokens(value) {
|
|
6
|
+
return typeof value === "string" ? Math.ceil(Buffer.byteLength(value, "utf8") / 4) : 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function estimatedContentTokens(content) {
|
|
10
|
+
if (!Array.isArray(content)) return 0;
|
|
11
|
+
return content.reduce((total, item) => {
|
|
12
|
+
if (item?.type === "input_image" || item?.type === "output_image") return total + IMAGE_TOKENS_ESTIMATE;
|
|
13
|
+
return total + estimatedTextTokens(item?.text);
|
|
14
|
+
}, 0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function estimatedEncryptedTokens(value) {
|
|
18
|
+
if (typeof value !== "string") return 0;
|
|
19
|
+
// 参考 Codex 的历史粗估:先去掉加密封装,再按约 4 字节/token 换算。
|
|
20
|
+
return Math.ceil(Math.max(0, Math.floor(value.length * 3 / 4) - 650) / 4);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function estimatedSections(item) {
|
|
24
|
+
const type = item?.type;
|
|
25
|
+
if (type === "message") {
|
|
26
|
+
const tokens = estimatedContentTokens(item.content);
|
|
27
|
+
return item.role === "developer" || item.role === "system"
|
|
28
|
+
? { instructions: tokens, messages: 0, tools: 0 }
|
|
29
|
+
: { instructions: 0, messages: tokens, tools: 0 };
|
|
30
|
+
}
|
|
31
|
+
if (type === "function_call" || type === "custom_tool_call") {
|
|
32
|
+
return { instructions: 0, messages: 0, tools: estimatedTextTokens(item.arguments ?? item.input) };
|
|
33
|
+
}
|
|
34
|
+
if (type === "function_call_output" || type === "custom_tool_call_output") {
|
|
35
|
+
return { instructions: 0, messages: 0, tools: estimatedContentTokens(item.output) };
|
|
36
|
+
}
|
|
37
|
+
if (type === "reasoning" || type === "compaction") {
|
|
38
|
+
return { instructions: 0, messages: estimatedContentTokens(item.summary)
|
|
39
|
+
+ estimatedEncryptedTokens(item.encrypted_content), tools: 0 };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 外部记录转换成可归约的内部事实;未知记录不参与统计。
|
|
45
|
+
export function sessionFacts(record) {
|
|
46
|
+
const p = record?.payload ?? {}, at = record?.timestamp;
|
|
47
|
+
const facts = [], emit = (kind, data = {}) => facts.push({ kind, at, ...data });
|
|
48
|
+
const usage = value => value && ["input_tokens", "cached_input_tokens", "output_tokens", "total_tokens"]
|
|
49
|
+
.every(key => Number.isFinite(value[key]) && value[key] >= 0)
|
|
50
|
+
? { input:value.input_tokens, cached:value.cached_input_tokens, output:value.output_tokens,
|
|
51
|
+
total:value.total_tokens, cacheWrite:value.cache_write_input_tokens ?? null } : null;
|
|
52
|
+
if (record?.type === "compacted") {
|
|
53
|
+
const sections = Array.isArray(p.replacement_history) ? {instructions:0,messages:0,tools:0} : null;
|
|
54
|
+
for (const item of Array.isArray(p.replacement_history) ? p.replacement_history : []) {
|
|
55
|
+
const next = estimatedSections(item);
|
|
56
|
+
if (next) for (const key of Object.keys(sections)) sections[key] += next[key];
|
|
57
|
+
}
|
|
58
|
+
emit("history-replaced", { window:p.window_number, sections });
|
|
59
|
+
}
|
|
60
|
+
if (record?.type === "response_item") {
|
|
61
|
+
const sections = estimatedSections(p);
|
|
62
|
+
if (sections) emit("history-added", { sections });
|
|
63
|
+
}
|
|
64
|
+
if (record?.type === "turn_context" && p.turn_id) emit("turn-observed", { id:p.turn_id });
|
|
65
|
+
if (record?.type === "session_meta") emit("session-configured", { contextWindow:p.context_window,
|
|
66
|
+
instructionTokens:estimatedTextTokens(p.base_instructions?.text) });
|
|
67
|
+
if (record?.type === "event_msg") {
|
|
68
|
+
if (p.type === "context_compacted") emit("context-reset");
|
|
69
|
+
if (p.type === "task_started" && p.turn_id) emit("turn-started", { id:p.turn_id });
|
|
70
|
+
if (["task_complete", "task_interrupted", "turn_aborted"].includes(p.type)) emit("turn-ended", { id:p.turn_id });
|
|
71
|
+
if (p.type === "task_complete" && Number.isFinite(p.time_to_first_token_ms)) emit("first-token-measured", { elapsed:p.time_to_first_token_ms });
|
|
72
|
+
if (p.type === "item_completed") {
|
|
73
|
+
if (p.item?.type === "ContextCompaction") emit("compaction-finished");
|
|
74
|
+
if (MODEL_OUTPUT_ITEMS.has(p.item?.type) && Number.isFinite(p.started_at_ms)) emit("output-observed", { start:p.started_at_ms });
|
|
75
|
+
if (TOOL_ITEMS.has(p.item?.type) && Number.isFinite(p.started_at_ms) && Number.isFinite(p.completed_at_ms)) emit("tool-measured", { start:p.started_at_ms, end:p.completed_at_ms });
|
|
76
|
+
}
|
|
77
|
+
if (p.type === "token_count") emit("usage-observed", { total:usage(p.info?.total_token_usage), last:usage(p.info?.last_token_usage), contextWindow:p.info?.model_context_window });
|
|
78
|
+
}
|
|
79
|
+
if (record?.type === "token_usage_record") emit("response-measured", { id:p.response_id, total:usage(p.thread_token_usage), last:usage(p.usage), end:Date.parse(at) });
|
|
80
|
+
return facts;
|
|
81
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { sessionFacts } from "./session-log.mjs";
|
|
7
|
+
import { createSessionState, applySessionStatusEvent, sessionStatusSnapshot } from "../../domain/session-stats.mjs";
|
|
8
|
+
|
|
9
|
+
const THREAD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
export function threadIdFromLocation(value) {
|
|
11
|
+
return String(value ?? "").match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)?.[0] ?? null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function emptyState(filePaths) {
|
|
15
|
+
return { ...createSessionState(), files:new Map(filePaths.map(filePath => [filePath, { offset:0, pending:"" }])), nextDiscoveryAt:0 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function findLogs(directory, threadId) {
|
|
19
|
+
let entries;
|
|
20
|
+
try { entries = await readdir(directory, { withFileTypes: true }); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === "ENOENT") return [];
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const files = [];
|
|
26
|
+
const filename = new RegExp(`-${threadId}(?:_[0-9a-f-]{36})?\\.jsonl$`, "i");
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const filePath = path.join(directory, entry.name);
|
|
29
|
+
if (entry.isFile() && filename.test(entry.name)) files.push(filePath);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
files.push(...await findLogs(filePath, threadId));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return files.sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class SessionStatusReader {
|
|
38
|
+
constructor({ sessionsDir = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"), "sessions"), now = Date.now } = {}) {
|
|
39
|
+
this.sessionsDir = sessionsDir;
|
|
40
|
+
this.now = now;
|
|
41
|
+
this.states = new Map();
|
|
42
|
+
this.missingChecks = new Map();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
retryAfterMs(threadId) {
|
|
46
|
+
return Math.max(0, (this.missingChecks.get(threadId)?.nextCheckAt ?? 0) - this.now());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async read(threadId) {
|
|
50
|
+
if (!THREAD_ID.test(threadId ?? "")) return null;
|
|
51
|
+
let state = this.states.get(threadId);
|
|
52
|
+
if (!state || this.now() >= state.nextDiscoveryAt) {
|
|
53
|
+
if (this.retryAfterMs(threadId) > 0) return null;
|
|
54
|
+
const filePaths = await findLogs(this.sessionsDir, threadId);
|
|
55
|
+
if (!filePaths.length) {
|
|
56
|
+
this.states.delete(threadId);
|
|
57
|
+
const attempts = this.missingChecks.get(threadId)?.attempts ?? 0;
|
|
58
|
+
const delay = [500, 1000, 2000][attempts] ?? 10_000;
|
|
59
|
+
this.missingChecks.set(threadId, { attempts: attempts + 1, nextCheckAt: this.now() + delay });
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// 新分段出现时重新按时间合并,避免只停留在最初的 rollout。
|
|
63
|
+
if (!state || filePaths.join("\n") !== [...state.files.keys()].join("\n")) state = emptyState(filePaths);
|
|
64
|
+
state.nextDiscoveryAt = this.now() + 2_000;
|
|
65
|
+
this.states.set(threadId, state);
|
|
66
|
+
this.missingChecks.delete(threadId);
|
|
67
|
+
}
|
|
68
|
+
let sizes;
|
|
69
|
+
try { sizes = await Promise.all([...state.files.keys()].map(async filePath => [filePath, (await stat(filePath)).size])); }
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code !== "ENOENT") throw error;
|
|
72
|
+
this.states.delete(threadId);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (sizes.some(([filePath, size]) => size < state.files.get(filePath).offset)) {
|
|
76
|
+
const nextDiscoveryAt = state.nextDiscoveryAt;
|
|
77
|
+
state = emptyState([...state.files.keys()]);
|
|
78
|
+
state.nextDiscoveryAt = nextDiscoveryAt;
|
|
79
|
+
}
|
|
80
|
+
const events = [];
|
|
81
|
+
for (const [filePath, size] of sizes) {
|
|
82
|
+
const file = state.files.get(filePath);
|
|
83
|
+
if (size <= file.offset) continue;
|
|
84
|
+
const stream = createReadStream(filePath, {
|
|
85
|
+
start: file.offset,
|
|
86
|
+
end: size - 1,
|
|
87
|
+
encoding: "utf8",
|
|
88
|
+
});
|
|
89
|
+
for await (const chunk of stream) {
|
|
90
|
+
const lines = `${file.pending}${chunk}`.split("\n");
|
|
91
|
+
file.pending = lines.pop() ?? "";
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
if (!line.includes('"token_usage_record"') && !line.includes('"event_msg"')
|
|
94
|
+
&& !line.includes('"turn_context"') && !line.includes('"session_meta"')
|
|
95
|
+
&& !line.includes('"response_item"') && !line.includes('"compacted"')) continue;
|
|
96
|
+
try { events.push(JSON.parse(line)); } catch { /* 忽略损坏的单行 */ }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
file.offset = size;
|
|
100
|
+
}
|
|
101
|
+
events.sort((left, right) => (Date.parse(left.timestamp) || 0) - (Date.parse(right.timestamp) || 0));
|
|
102
|
+
for (const event of events) for (const fact of sessionFacts(event)) applySessionStatusEvent(state, fact);
|
|
103
|
+
this.states.set(threadId, state);
|
|
104
|
+
return sessionStatusSnapshot(state, threadId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// 归约 MineCodex 会话事实;不依赖 Codex 日志字段或外部环境。
|
|
2
|
+
function addSections(target, sections) {
|
|
3
|
+
if (!sections) return;
|
|
4
|
+
for (const key of ["instructions", "messages", "tools"]) target[key] += sections[key];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createSessionState() {
|
|
8
|
+
return {
|
|
9
|
+
turns: new Set(),
|
|
10
|
+
runningTurns: new Set(),
|
|
11
|
+
responses: new Set(),
|
|
12
|
+
hasResponseUsage: false,
|
|
13
|
+
total: null,
|
|
14
|
+
last: null,
|
|
15
|
+
contextWindow: null,
|
|
16
|
+
firstOutputAt: null,
|
|
17
|
+
pendingToolIntervals: [],
|
|
18
|
+
speedMeasurements: [],
|
|
19
|
+
ttftTotalMs: 0,
|
|
20
|
+
ttftSamples: 0,
|
|
21
|
+
toolMs: 0,
|
|
22
|
+
compactionRecords: 0,
|
|
23
|
+
compactionCompleted: 0,
|
|
24
|
+
compactionEvents: 0,
|
|
25
|
+
compactionWindow: 0,
|
|
26
|
+
baseInstructionTokens: 0,
|
|
27
|
+
visibleSections: { instructions: 0, messages: 0, tools: 0 },
|
|
28
|
+
hasVisibleHistory: false,
|
|
29
|
+
updatedAt: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function overlappingToolMs(intervals, start, end) {
|
|
34
|
+
const clipped = intervals
|
|
35
|
+
.map(([toolStart, toolEnd]) => [Math.max(start, toolStart), Math.min(end, toolEnd)])
|
|
36
|
+
.filter(([left, right]) => right > left)
|
|
37
|
+
.sort((left, right) => left[0] - right[0]);
|
|
38
|
+
let elapsed = 0;
|
|
39
|
+
let rightEdge = start;
|
|
40
|
+
for (const [left, right] of clipped) {
|
|
41
|
+
elapsed += Math.max(0, right - Math.max(left, rightEdge));
|
|
42
|
+
rightEdge = Math.max(rightEdge, right);
|
|
43
|
+
}
|
|
44
|
+
return elapsed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function applySessionStatusEvent(state, event) {
|
|
48
|
+
const reset = () => { state.firstOutputAt=null; state.pendingToolIntervals=[]; };
|
|
49
|
+
switch (event.kind) {
|
|
50
|
+
case "history-replaced":
|
|
51
|
+
state.compactionRecords++; state.last=null; reset();
|
|
52
|
+
if (Number.isSafeInteger(event.window) && event.window >= 0) state.compactionWindow=Math.max(state.compactionWindow,event.window);
|
|
53
|
+
state.visibleSections=event.sections ?? {instructions:0,messages:0,tools:0};
|
|
54
|
+
state.hasVisibleHistory=event.sections !== null;
|
|
55
|
+
break;
|
|
56
|
+
case "history-added": addSections(state.visibleSections,event.sections); state.hasVisibleHistory=true; break;
|
|
57
|
+
case "turn-observed": state.turns.add(event.id); break;
|
|
58
|
+
case "session-configured":
|
|
59
|
+
if (Number.isFinite(event.contextWindow)) state.contextWindow=event.contextWindow;
|
|
60
|
+
state.baseInstructionTokens=event.instructionTokens; break;
|
|
61
|
+
case "context-reset": state.compactionEvents++; state.last=null; reset(); break;
|
|
62
|
+
case "turn-started": reset(); state.turns.add(event.id); state.runningTurns.add(event.id); break;
|
|
63
|
+
case "turn-ended": reset(); if (event.id) state.runningTurns.delete(event.id); else state.runningTurns.clear(); break;
|
|
64
|
+
case "first-token-measured": state.ttftTotalMs+=event.elapsed; state.ttftSamples++; break;
|
|
65
|
+
case "compaction-finished": state.compactionCompleted++; break;
|
|
66
|
+
case "output-observed": state.firstOutputAt=Math.min(state.firstOutputAt ?? event.start,event.start); break;
|
|
67
|
+
case "tool-measured":
|
|
68
|
+
state.toolMs+=Math.max(0,event.end-event.start);
|
|
69
|
+
state.pendingToolIntervals.push([event.start,event.end]);
|
|
70
|
+
// 工具记录可能晚到,补扣与既有响应样本重叠的时间。
|
|
71
|
+
for (const sample of state.speedMeasurements) if (event.start < sample.end && event.end > sample.start) sample.tools.push([event.start,event.end]);
|
|
72
|
+
break;
|
|
73
|
+
case "usage-observed":
|
|
74
|
+
if (!state.hasResponseUsage && event.total) state.total=event.total;
|
|
75
|
+
if (event.last) state.last=event.last;
|
|
76
|
+
if (Number.isFinite(event.contextWindow)) state.contextWindow=event.contextWindow;
|
|
77
|
+
break;
|
|
78
|
+
case "response-measured":
|
|
79
|
+
if (event.id && state.responses.has(event.id)) return;
|
|
80
|
+
if (event.id) {
|
|
81
|
+
state.responses.add(event.id);
|
|
82
|
+
const output=event.last?.output;
|
|
83
|
+
if (Number.isFinite(output) && output > 0 && state.firstOutputAt != null && Number.isFinite(event.end) && event.end > state.firstOutputAt)
|
|
84
|
+
state.speedMeasurements.push({ start:state.firstOutputAt,end:event.end,output,tools:state.pendingToolIntervals.slice() });
|
|
85
|
+
}
|
|
86
|
+
reset();
|
|
87
|
+
if (event.total) { state.total=event.total; state.hasResponseUsage=true; }
|
|
88
|
+
if (event.last) state.last=event.last;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (state.total || state.turns.size) state.updatedAt=event.at ?? state.updatedAt;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function sessionStatusSnapshot(state, threadId) {
|
|
95
|
+
let decodeTokens = 0;
|
|
96
|
+
let decodeMs = 0;
|
|
97
|
+
let speedSamples = 0;
|
|
98
|
+
for (const sample of state.speedMeasurements) {
|
|
99
|
+
const elapsed = sample.end - sample.start - overlappingToolMs(sample.tools, sample.start, sample.end);
|
|
100
|
+
if (elapsed <= 0 || elapsed >= 600_000) continue;
|
|
101
|
+
decodeTokens += sample.output;
|
|
102
|
+
decodeMs += elapsed;
|
|
103
|
+
speedSamples += 1;
|
|
104
|
+
}
|
|
105
|
+
const total = state.total;
|
|
106
|
+
const input = total?.input ?? 0;
|
|
107
|
+
const cached = total?.cached ?? 0;
|
|
108
|
+
const cacheWrite = total?.cacheWrite ?? 0;
|
|
109
|
+
const contextUsed = state.last?.total ?? null;
|
|
110
|
+
const contextWindow = state.contextWindow > 0 ? state.contextWindow : null;
|
|
111
|
+
return {
|
|
112
|
+
threadId,
|
|
113
|
+
isRunning: state.runningTurns.size > 0,
|
|
114
|
+
turns: state.turns.size,
|
|
115
|
+
steps: state.responses.size,
|
|
116
|
+
totalTokens: total?.total ?? null,
|
|
117
|
+
inputTokens: total?.input ?? null,
|
|
118
|
+
cachedInputTokens: total?.cached ?? null,
|
|
119
|
+
cacheWriteInputTokens: total?.cacheWrite ?? null,
|
|
120
|
+
uncachedInputTokens: total ? Math.max(0, input - cached - cacheWrite) : null,
|
|
121
|
+
outputTokens: total?.output ?? null,
|
|
122
|
+
cacheHitPercent: input > 0 ? Math.round(cached / input * 100) : null,
|
|
123
|
+
observedTps: decodeMs > 0 ? Math.round(decodeTokens / (decodeMs / 1000)) : null,
|
|
124
|
+
speedSamples,
|
|
125
|
+
observedModelMs: decodeMs,
|
|
126
|
+
toolMs: state.toolMs,
|
|
127
|
+
averageFirstTokenMs: state.ttftSamples > 0 ? Math.round(state.ttftTotalMs / state.ttftSamples) : null,
|
|
128
|
+
contextUsedTokens: contextUsed,
|
|
129
|
+
contextWindow,
|
|
130
|
+
contextPercent: contextUsed != null && contextWindow != null
|
|
131
|
+
? Math.min(100, Math.round(contextUsed / contextWindow * 100)) : null,
|
|
132
|
+
compactions: Math.max(state.compactionWindow, state.compactionRecords,
|
|
133
|
+
state.compactionCompleted, state.compactionEvents),
|
|
134
|
+
contextSections: state.hasVisibleHistory ? {
|
|
135
|
+
instructions: state.baseInstructionTokens + state.visibleSections.instructions,
|
|
136
|
+
messages: state.visibleSections.messages,
|
|
137
|
+
toolHistory: state.visibleSections.tools,
|
|
138
|
+
} : null,
|
|
139
|
+
updatedAt: state.updatedAt,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createContextUsageAdapter } from "./adapters/codex/context-usage.mjs";
|
|
3
|
+
export async function createPageScriptSource() {
|
|
4
|
+
const source=await readFile(new URL("./session-status.mjs",import.meta.url),"utf8");
|
|
5
|
+
return `const createContextUsageAdapter=${createContextUsageAdapter.toString()};\n${source}`;
|
|
6
|
+
}
|