minecodex 1.2.1 → 2.0.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/README.md +9 -3
- package/features/images/README.md +16 -98
- package/features/images/codex-feature.json +6 -14
- package/features/images/src/http-server.mjs +15 -5
- package/features/images/src/image-library.mjs +37 -9
- package/features/images/src/library-prompt-page.mjs +239 -0
- package/features/images/src/prompt-index.mjs +27 -24
- package/features/images/src/prompt-source.mjs +100 -0
- package/features/images/web/app.js +180 -67
- package/features/images/web/i18n.mjs +5 -0
- package/features/images/web/index.html +29 -24
- package/features/images/web/styles.css +169 -139
- package/features/model-slider/README.md +13 -1
- package/features/model-slider/THIRD_PARTY_NOTICES.md +1 -1
- package/features/notes/DESIGN.md +66 -0
- package/features/notes/README.md +18 -99
- package/features/notes/assets/icons/book-open-nav.svg +15 -0
- package/features/notes/assets/icons/book-open.svg +16 -0
- package/features/notes/assets/native-emojis.json +1 -0
- package/features/notes/assets/native-prompt-icons.json +28 -0
- package/features/notes/codex-feature.json +12 -70
- package/features/notes/src/main.mjs +2 -2
- package/features/notes/src/product.mjs +287 -613
- package/features/notes/src/prompt-schema.mjs +117 -0
- package/features/notes/src/quick-prompts-page.mjs +1559 -0
- package/features/notes/src/quick-prompts-server.mjs +167 -0
- package/features/session-status/DESIGN.md +10 -2
- package/features/session-status/README.md +13 -3
- package/features/session-status/src/session-status.mjs +329 -24
- package/features/sidebar-focus/DESIGN.md +16 -0
- package/features/sidebar-focus/codex-feature.json +20 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +134 -0
- package/features/sidebar-focus/vendor/lucide-LICENSE +43 -0
- package/features/unread-inbox/DESIGN.md +28 -0
- package/features/unread-inbox/codex-feature.json +20 -0
- package/features/unread-inbox/src/unread-inbox.mjs +360 -0
- package/features/usage-meter/DESIGN.md +49 -0
- package/features/usage-meter/README.md +18 -0
- package/features/usage-meter/codex-feature.json +13 -0
- package/features/usage-meter/src/usage-meter.mjs +200 -0
- package/package.json +21 -6
- package/packages/cli/src/config.mjs +4 -1
- package/packages/cli/src/control-server.mjs +2 -2
- package/packages/runtime-host/src/cloud-images.mjs +123 -0
- package/packages/runtime-host/src/codex-cdp.mjs +42 -8
- package/packages/runtime-host/src/codex-design-contract.mjs +10 -0
- package/packages/runtime-host/src/codex-injection.mjs +364 -116
- package/packages/runtime-host/src/codex-runtime.mjs +105 -28
- package/packages/runtime-host/src/feature-registry.mjs +3 -0
- package/packages/runtime-host/src/session-status-reader.mjs +65 -24
- package/features/notes/assets/icons/circle.svg +0 -15
- package/features/notes/assets/icons/ellipsis.svg +0 -17
- package/features/notes/assets/icons/external-link.svg +0 -17
- package/features/notes/assets/icons/list-todo.svg +0 -19
- package/features/notes/assets/icons/message-square-quote.svg +0 -17
- package/features/notes/assets/icons/paperclip.svg +0 -15
- package/features/notes/assets/icons/star-off.svg +0 -17
- package/features/notes/assets/icons/star.svg +0 -15
- package/features/notes/assets/icons/sticky-note.svg +0 -16
- package/features/notes/assets/icons/trash-2.svg +0 -19
- package/features/notes/src/http-server.mjs +0 -580
- package/features/notes/web/app.js +0 -1202
- package/features/notes/web/codex-artifact-document.svg +0 -1
- package/features/notes/web/codex-attachment.svg +0 -1
- package/features/notes/web/codex-build.svg +0 -1
- package/features/notes/web/codex-code.svg +0 -1
- package/features/notes/web/codex-cplusplus.svg +0 -1
- package/features/notes/web/codex-css.svg +0 -1
- package/features/notes/web/codex-document.svg +0 -1
- package/features/notes/web/codex-file.svg +0 -1
- package/features/notes/web/codex-folder.svg +0 -1
- package/features/notes/web/codex-hashes.svg +0 -1
- package/features/notes/web/codex-html.svg +0 -1
- package/features/notes/web/codex-image.svg +0 -1
- package/features/notes/web/codex-java.svg +0 -1
- package/features/notes/web/codex-javascript.svg +0 -1
- package/features/notes/web/codex-json.svg +0 -1
- package/features/notes/web/codex-notebook.svg +0 -1
- package/features/notes/web/codex-pdf.svg +0 -1
- package/features/notes/web/codex-pencil.svg +0 -3
- package/features/notes/web/codex-php.svg +0 -1
- package/features/notes/web/codex-plus.svg +0 -3
- package/features/notes/web/codex-presentation.svg +0 -1
- package/features/notes/web/codex-python.svg +0 -1
- package/features/notes/web/codex-react.svg +0 -1
- package/features/notes/web/codex-rust.svg +0 -1
- package/features/notes/web/codex-shell.svg +0 -1
- package/features/notes/web/codex-skill.svg +0 -1
- package/features/notes/web/codex-spreadsheet.svg +0 -1
- package/features/notes/web/codex-task.svg +0 -3
- package/features/notes/web/codex-terminal.svg +0 -1
- package/features/notes/web/codex-toml.svg +0 -1
- package/features/notes/web/codex-typescript.svg +0 -1
- package/features/notes/web/codex-view-all.svg +0 -3
- package/features/notes/web/file-icons.mjs +0 -68
- package/features/notes/web/i18n.mjs +0 -237
- package/features/notes/web/index.html +0 -15
- package/features/notes/web/styles.css +0 -1155
- package/features/notes/web/todo-drag.mjs +0 -18
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cloudImagesAction } from "./cloud-images.mjs";
|
|
1
2
|
// CodexRuntime:Codex Renderer 生命周期与 host action 执行。
|
|
2
3
|
// 注入源(createInjectionSource)与 CDP 客户端分别在 codex-injection.mjs / codex-cdp.mjs。
|
|
3
4
|
|
|
@@ -46,15 +47,11 @@ export {
|
|
|
46
47
|
} from "./codex-injection.mjs";
|
|
47
48
|
|
|
48
49
|
export const SESSION_STATUS_THREAD_ID_EXPRESSION = `(() => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (card?.getAttribute('data-composer-placement') === 'home') return null;
|
|
55
|
-
const selected = document.querySelector('[data-app-action-sidebar-thread-selected="true"]');
|
|
56
|
-
return selected?.getAttribute('data-app-action-sidebar-thread-id')
|
|
57
|
-
?? location.pathname + location.search + location.hash;
|
|
50
|
+
return [...new Set([...document.querySelectorAll('[data-codex-composer="true"][contenteditable="true"]')].map(composer => {
|
|
51
|
+
const card = composer.closest('[data-codex-composer-root]');
|
|
52
|
+
if (card?.getAttribute('data-composer-placement') !== 'thread') return null;
|
|
53
|
+
return card.querySelector('[data-above-composer-portal]')?.getAttribute('data-above-composer-conversation-id');
|
|
54
|
+
}).filter(Boolean))];
|
|
58
55
|
})()`;
|
|
59
56
|
|
|
60
57
|
export class CodexRuntime {
|
|
@@ -103,6 +100,8 @@ export class CodexRuntime {
|
|
|
103
100
|
this.scriptIds = new Map();
|
|
104
101
|
this.bindingTokens = new Map();
|
|
105
102
|
this.hostActionChains = new Map();
|
|
103
|
+
this.sessionStatusUpdates = new Map();
|
|
104
|
+
this.sessionStatusRevision = 0;
|
|
106
105
|
this.refreshPromise = null;
|
|
107
106
|
this.stopping = false;
|
|
108
107
|
this.monitorPromise = null;
|
|
@@ -290,7 +289,9 @@ export class CodexRuntime {
|
|
|
290
289
|
}
|
|
291
290
|
|
|
292
291
|
async startTargetDiscovery() {
|
|
293
|
-
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version
|
|
292
|
+
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version`, {
|
|
293
|
+
signal: AbortSignal.timeout(3_000),
|
|
294
|
+
});
|
|
294
295
|
if (!response?.ok) {
|
|
295
296
|
throw new Error(`Codex CDP version returned HTTP ${response?.status ?? "error"}`);
|
|
296
297
|
}
|
|
@@ -355,7 +356,9 @@ export class CodexRuntime {
|
|
|
355
356
|
|
|
356
357
|
async isAvailable() {
|
|
357
358
|
try {
|
|
358
|
-
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version
|
|
359
|
+
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version`, {
|
|
360
|
+
signal: AbortSignal.timeout(3_000),
|
|
361
|
+
});
|
|
359
362
|
if (!response?.ok) return false;
|
|
360
363
|
requireCdpWebSocketUrl(await response.json(), this.cdpPort);
|
|
361
364
|
return true;
|
|
@@ -518,6 +521,12 @@ export class CodexRuntime {
|
|
|
518
521
|
}
|
|
519
522
|
if (request.token !== this.bindingTokens.get(targetId) || typeof request.requestId !== "string") return;
|
|
520
523
|
const feature = this.features.find((candidate) => candidate.id === request.featureId);
|
|
524
|
+
if (feature?.id === "session-status" && request.action === "refresh-session-status") {
|
|
525
|
+
if (this.sessionStatusReader && this.clients.get(targetId) === client && !this.stopping) {
|
|
526
|
+
await this.publishSessionStatus(targetId);
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
521
530
|
if (feature && request.action === SURFACE_LOAD_ACTION) {
|
|
522
531
|
try {
|
|
523
532
|
const result = await this.loadSurfaceIntoFrame(client, feature, request.payload);
|
|
@@ -537,10 +546,45 @@ export class CodexRuntime {
|
|
|
537
546
|
});
|
|
538
547
|
return;
|
|
539
548
|
}
|
|
549
|
+
if (feature.id === "notes" && request.action === "quick-prompts") {
|
|
550
|
+
try {
|
|
551
|
+
const { route, method = "GET", body } = request.payload ?? {};
|
|
552
|
+
const allowed = (route === "/api/state" && method === "GET")
|
|
553
|
+
|| (["/api/prompts", "/api/categories", "/api/reorder"].includes(route) && method === "POST")
|
|
554
|
+
|| (/^\/api\/categories\/[a-zA-Z0-9-]+$/.test(route) && ["PATCH", "DELETE"].includes(method))
|
|
555
|
+
|| (/^\/api\/prompts\/[a-zA-Z0-9-]+$/.test(route) && ["PATCH", "DELETE"].includes(method))
|
|
556
|
+
|| (/^\/api\/prompts\/[a-zA-Z0-9-]+\/use$/.test(route) && method === "POST");
|
|
557
|
+
const origin = pageScriptServiceOrigin(feature);
|
|
558
|
+
if (!allowed || !origin) throw new Error("Prompt request is not allowed");
|
|
559
|
+
const response = await this.fetchImpl(new URL(route, origin), {
|
|
560
|
+
method, headers: { "Content-Type": "application/json", Origin: CODEX_APP_ORIGIN },
|
|
561
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
562
|
+
signal: AbortSignal.timeout(8_000),
|
|
563
|
+
});
|
|
564
|
+
const result = response.status === 204 ? null : await response.json();
|
|
565
|
+
if (!response.ok) throw new Error(result?.error?.message ?? "Prompt request failed");
|
|
566
|
+
await this.resolveHostAction(client, request.requestId, { ok: true, result });
|
|
567
|
+
} catch (error) {
|
|
568
|
+
await this.resolveHostAction(client, request.requestId, { ok: false, error: { code: "PROMPT_REQUEST_FAILED", message: error.message } });
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
540
572
|
if (request.action === "import-generated-image") {
|
|
541
573
|
await this.importGeneratedImage(feature, request.payload);
|
|
542
574
|
return;
|
|
543
575
|
}
|
|
576
|
+
if (["list-cloud-images", "import-cloud-image"].includes(request.action)) {
|
|
577
|
+
try {
|
|
578
|
+
let result = await cloudImagesAction(client, request.action === "list-cloud-images" ? "list" : "import", request.payload);
|
|
579
|
+
if (request.action === "import-cloud-image" && !result.skipped) {
|
|
580
|
+
result = await this.importGeneratedImage(feature, result);
|
|
581
|
+
}
|
|
582
|
+
await this.resolveHostAction(client, request.requestId, { ok: true, result });
|
|
583
|
+
} catch (error) {
|
|
584
|
+
await this.resolveHostAction(client, request.requestId, { ok: false, error: { code: "CLOUD_IMAGES_FAILED", message: error.message } });
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
544
588
|
try {
|
|
545
589
|
const result = request.action === "create-image-edit-thread"
|
|
546
590
|
? await this.createImageEditThread(client, feature, request.payload, request.requestId)
|
|
@@ -568,12 +612,15 @@ export class CodexRuntime {
|
|
|
568
612
|
if (!response?.ok) {
|
|
569
613
|
throw new Error(`Generated image import returned HTTP ${response?.status ?? "error"}`);
|
|
570
614
|
}
|
|
615
|
+
return response.json();
|
|
571
616
|
}
|
|
572
617
|
|
|
573
618
|
async refreshTargets() {
|
|
574
619
|
let response;
|
|
575
620
|
try {
|
|
576
|
-
response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/list
|
|
621
|
+
response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/list`, {
|
|
622
|
+
signal: AbortSignal.timeout(3_000),
|
|
623
|
+
});
|
|
577
624
|
} catch (error) {
|
|
578
625
|
throw this.markCodexUnavailable(error);
|
|
579
626
|
}
|
|
@@ -604,7 +651,8 @@ export class CodexRuntime {
|
|
|
604
651
|
...this.targetFailures.keys(),
|
|
605
652
|
]);
|
|
606
653
|
for (const id of trackedTargetIds) {
|
|
607
|
-
|
|
654
|
+
const socketState = this.clients.get(id)?.socket.readyState;
|
|
655
|
+
if (liveRendererIds.has(id) && (socketState == null || socketState === 1)) continue;
|
|
608
656
|
removedTarget = true;
|
|
609
657
|
this.clients.get(id)?.socket.close();
|
|
610
658
|
this.clients.delete(id);
|
|
@@ -640,7 +688,13 @@ export class CodexRuntime {
|
|
|
640
688
|
const bindingToken = randomBytes(24).toString("hex");
|
|
641
689
|
this.bindingTokens.set(target.id, bindingToken);
|
|
642
690
|
client.on?.("Runtime.bindingCalled", (params) => {
|
|
643
|
-
|
|
691
|
+
let action;
|
|
692
|
+
try { action = JSON.parse(params.payload)?.action; } catch { return; }
|
|
693
|
+
// 统计读取独立合并,避免排在图片等用户操作后面。
|
|
694
|
+
const work = action === "refresh-session-status"
|
|
695
|
+
? this.handleBindingCalled(target.id, client, params)
|
|
696
|
+
: this.enqueueHostAction(target.id, () => this.handleBindingCalled(target.id, client, params));
|
|
697
|
+
work
|
|
644
698
|
.catch((error) => this.logger.warn?.("Codex Host action failed", error.message));
|
|
645
699
|
});
|
|
646
700
|
const source = createInjectionSource(this.features, {
|
|
@@ -693,22 +747,45 @@ export class CodexRuntime {
|
|
|
693
747
|
this.notifyStatusChange();
|
|
694
748
|
}
|
|
695
749
|
|
|
696
|
-
async publishSessionStatus() {
|
|
750
|
+
async publishSessionStatus(onlyTargetId = null) {
|
|
697
751
|
for (const [targetId, client] of this.clients) {
|
|
752
|
+
if (onlyTargetId && targetId !== onlyTargetId) continue;
|
|
698
753
|
if (!this.injectedTargets.has(targetId)) continue;
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
754
|
+
const pending = this.sessionStatusUpdates.get(targetId);
|
|
755
|
+
if (pending) { await pending; continue; }
|
|
756
|
+
const revision = ++this.sessionStatusRevision;
|
|
757
|
+
const update = (async () => {
|
|
758
|
+
try {
|
|
759
|
+
const response = await client.send("Runtime.evaluate", {
|
|
760
|
+
expression: SESSION_STATUS_THREAD_ID_EXPRESSION,
|
|
761
|
+
returnByValue: true,
|
|
762
|
+
}, { timeoutMs: 3_000 });
|
|
763
|
+
for (const value of response.result?.value ?? []) {
|
|
764
|
+
const threadId = threadIdFromLocation(value);
|
|
765
|
+
const data = threadId ? await this.sessionStatusReader.read(threadId) : null;
|
|
766
|
+
const snapshot = data ?? (threadId ? {
|
|
767
|
+
threadId,
|
|
768
|
+
retryAfterMs: this.sessionStatusReader.retryAfterMs?.(threadId) ?? 500,
|
|
769
|
+
} : null);
|
|
770
|
+
if (this.stopping || this.clients.get(targetId) !== client) return;
|
|
771
|
+
await client.send("Runtime.evaluate", {
|
|
772
|
+
expression: `window.__minecodexSessionStatus?.update(${JSON.stringify(snapshot)}, ${revision})`,
|
|
773
|
+
returnByValue: true,
|
|
774
|
+
}, { timeoutMs: 3_000 });
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
this.logger.warn?.("Session Status update failed", error.message);
|
|
778
|
+
if (this.clients.get(targetId) === client) {
|
|
779
|
+
client.socket.close();
|
|
780
|
+
this.injectedTargets.delete(targetId);
|
|
781
|
+
this.notifyStatusChange();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
})();
|
|
785
|
+
this.sessionStatusUpdates.set(targetId, update);
|
|
786
|
+
try { await update; }
|
|
787
|
+
finally {
|
|
788
|
+
if (this.sessionStatusUpdates.get(targetId) === update) this.sessionStatusUpdates.delete(targetId);
|
|
712
789
|
}
|
|
713
790
|
}
|
|
714
791
|
}
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
const MANIFEST_NAME = "codex-feature.json";
|
|
8
8
|
const HOST_ACTIONS = new Set([
|
|
9
|
+
"quick-prompts",
|
|
9
10
|
"insert-text",
|
|
10
11
|
"attach-file",
|
|
11
12
|
"open-detail-tab",
|
|
@@ -13,6 +14,8 @@ const HOST_ACTIONS = new Set([
|
|
|
13
14
|
"open-sidebar",
|
|
14
15
|
"resolve-file-paths",
|
|
15
16
|
"import-generated-image",
|
|
17
|
+
"list-cloud-images",
|
|
18
|
+
"import-cloud-image",
|
|
16
19
|
"create-image-edit-thread",
|
|
17
20
|
]);
|
|
18
21
|
|
|
@@ -62,6 +62,7 @@ function emptyState(filePath) {
|
|
|
62
62
|
offset: 0,
|
|
63
63
|
pending: "",
|
|
64
64
|
turns: new Set(),
|
|
65
|
+
runningTurns: new Set(),
|
|
65
66
|
responses: new Set(),
|
|
66
67
|
hasResponseUsage: false,
|
|
67
68
|
total: null,
|
|
@@ -69,9 +70,7 @@ function emptyState(filePath) {
|
|
|
69
70
|
contextWindow: null,
|
|
70
71
|
firstOutputAt: null,
|
|
71
72
|
pendingToolIntervals: [],
|
|
72
|
-
|
|
73
|
-
decodeMs: 0,
|
|
74
|
-
speedSamples: 0,
|
|
73
|
+
speedMeasurements: [],
|
|
75
74
|
ttftTotalMs: 0,
|
|
76
75
|
ttftSamples: 0,
|
|
77
76
|
toolMs: 0,
|
|
@@ -109,6 +108,9 @@ export function applySessionStatusEvent(state, event) {
|
|
|
109
108
|
const payload = event?.payload ?? {};
|
|
110
109
|
if (event?.type === "compacted") {
|
|
111
110
|
state.compactionRecords += 1;
|
|
111
|
+
state.last = null;
|
|
112
|
+
state.firstOutputAt = null;
|
|
113
|
+
state.pendingToolIntervals = [];
|
|
112
114
|
if (Number.isSafeInteger(payload.window_number) && payload.window_number >= 0) {
|
|
113
115
|
state.compactionWindow = Math.max(state.compactionWindow, payload.window_number);
|
|
114
116
|
}
|
|
@@ -133,8 +135,24 @@ export function applySessionStatusEvent(state, event) {
|
|
|
133
135
|
state.baseInstructionTokens = estimatedTextTokens(payload.base_instructions?.text);
|
|
134
136
|
}
|
|
135
137
|
if (event?.type === "event_msg") {
|
|
136
|
-
if (payload.type === "context_compacted")
|
|
137
|
-
|
|
138
|
+
if (payload.type === "context_compacted") {
|
|
139
|
+
state.compactionEvents += 1;
|
|
140
|
+
state.last = null;
|
|
141
|
+
state.firstOutputAt = null;
|
|
142
|
+
state.pendingToolIntervals = [];
|
|
143
|
+
}
|
|
144
|
+
if (payload.type === "task_started" && payload.turn_id) {
|
|
145
|
+
state.firstOutputAt = null;
|
|
146
|
+
state.pendingToolIntervals = [];
|
|
147
|
+
state.turns.add(payload.turn_id);
|
|
148
|
+
state.runningTurns.add(payload.turn_id);
|
|
149
|
+
}
|
|
150
|
+
if (["task_complete", "task_interrupted", "turn_aborted"].includes(payload.type)) {
|
|
151
|
+
state.firstOutputAt = null;
|
|
152
|
+
state.pendingToolIntervals = [];
|
|
153
|
+
if (payload.turn_id) state.runningTurns.delete(payload.turn_id);
|
|
154
|
+
else state.runningTurns.clear();
|
|
155
|
+
}
|
|
138
156
|
if (payload.type === "task_complete" && Number.isFinite(payload.time_to_first_token_ms)) {
|
|
139
157
|
state.ttftTotalMs += payload.time_to_first_token_ms;
|
|
140
158
|
state.ttftSamples += 1;
|
|
@@ -150,13 +168,17 @@ export function applySessionStatusEvent(state, event) {
|
|
|
150
168
|
if (TOOL_ITEMS.has(itemType) && Number.isFinite(start) && Number.isFinite(end)) {
|
|
151
169
|
state.toolMs += Math.max(0, end - start);
|
|
152
170
|
state.pendingToolIntervals.push([start, end]);
|
|
171
|
+
// 工具完成事件可能晚于用量记录落盘,补扣已记录样本中的重叠区间。
|
|
172
|
+
for (const sample of state.speedMeasurements) {
|
|
173
|
+
if (start < sample.end && end > sample.start) sample.tools.push([start, end]);
|
|
174
|
+
}
|
|
153
175
|
}
|
|
154
176
|
}
|
|
155
177
|
if (payload.type === "token_count") {
|
|
156
178
|
if (!state.hasResponseUsage && validUsage(payload.info?.total_token_usage)) {
|
|
157
179
|
state.total = payload.info.total_token_usage;
|
|
158
180
|
}
|
|
159
|
-
if (
|
|
181
|
+
if (validUsage(payload.info?.last_token_usage)) {
|
|
160
182
|
state.last = payload.info.last_token_usage;
|
|
161
183
|
}
|
|
162
184
|
if (Number.isFinite(payload.info?.model_context_window)) {
|
|
@@ -166,18 +188,18 @@ export function applySessionStatusEvent(state, event) {
|
|
|
166
188
|
}
|
|
167
189
|
if (event?.type === "token_usage_record") {
|
|
168
190
|
const responseId = payload.response_id;
|
|
169
|
-
if (responseId &&
|
|
191
|
+
if (responseId && state.responses.has(responseId)) return;
|
|
192
|
+
if (responseId) {
|
|
170
193
|
state.responses.add(responseId);
|
|
171
194
|
const responseEnd = Date.parse(event.timestamp);
|
|
172
|
-
const elapsed = state.firstOutputAt == null ? null : responseEnd - state.firstOutputAt
|
|
173
|
-
- overlappingToolMs(state.pendingToolIntervals, state.firstOutputAt, responseEnd);
|
|
174
195
|
const output = payload.usage?.output_tokens;
|
|
175
|
-
//
|
|
196
|
+
// 起止点来自会话事件,并非模型首末 token 时间;只能提供观测约值。
|
|
176
197
|
if (Number.isFinite(output) && output > 0 && state.firstOutputAt != null
|
|
177
|
-
&& Number.isFinite(
|
|
178
|
-
state.
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
&& Number.isFinite(responseEnd) && responseEnd > state.firstOutputAt) {
|
|
199
|
+
state.speedMeasurements.push({
|
|
200
|
+
start: state.firstOutputAt, end: responseEnd, output,
|
|
201
|
+
tools: state.pendingToolIntervals.slice(),
|
|
202
|
+
});
|
|
181
203
|
}
|
|
182
204
|
}
|
|
183
205
|
state.firstOutputAt = null;
|
|
@@ -192,6 +214,16 @@ export function applySessionStatusEvent(state, event) {
|
|
|
192
214
|
}
|
|
193
215
|
|
|
194
216
|
export function sessionStatusSnapshot(state, threadId) {
|
|
217
|
+
let decodeTokens = 0;
|
|
218
|
+
let decodeMs = 0;
|
|
219
|
+
let speedSamples = 0;
|
|
220
|
+
for (const sample of state.speedMeasurements) {
|
|
221
|
+
const elapsed = sample.end - sample.start - overlappingToolMs(sample.tools, sample.start, sample.end);
|
|
222
|
+
if (elapsed <= 0 || elapsed >= 600_000) continue;
|
|
223
|
+
decodeTokens += sample.output;
|
|
224
|
+
decodeMs += elapsed;
|
|
225
|
+
speedSamples += 1;
|
|
226
|
+
}
|
|
195
227
|
const total = state.total;
|
|
196
228
|
const input = total?.input_tokens ?? 0;
|
|
197
229
|
const cached = total?.cached_input_tokens ?? 0;
|
|
@@ -200,6 +232,7 @@ export function sessionStatusSnapshot(state, threadId) {
|
|
|
200
232
|
const contextWindow = state.contextWindow > 0 ? state.contextWindow : null;
|
|
201
233
|
return {
|
|
202
234
|
threadId,
|
|
235
|
+
isRunning: state.runningTurns.size > 0,
|
|
203
236
|
turns: state.turns.size,
|
|
204
237
|
steps: state.responses.size,
|
|
205
238
|
totalTokens: total?.total_tokens ?? null,
|
|
@@ -209,9 +242,9 @@ export function sessionStatusSnapshot(state, threadId) {
|
|
|
209
242
|
uncachedInputTokens: total ? Math.max(0, input - cached - cacheWrite) : null,
|
|
210
243
|
outputTokens: total?.output_tokens ?? null,
|
|
211
244
|
cacheHitPercent: input > 0 ? Math.round(cached / input * 100) : null,
|
|
212
|
-
observedTps:
|
|
213
|
-
speedSamples
|
|
214
|
-
observedModelMs:
|
|
245
|
+
observedTps: decodeMs > 0 ? Math.round(decodeTokens / (decodeMs / 1000)) : null,
|
|
246
|
+
speedSamples,
|
|
247
|
+
observedModelMs: decodeMs,
|
|
215
248
|
toolMs: state.toolMs,
|
|
216
249
|
averageFirstTokenMs: state.ttftSamples > 0 ? Math.round(state.ttftTotalMs / state.ttftSamples) : null,
|
|
217
250
|
contextUsedTokens: contextUsed,
|
|
@@ -248,24 +281,32 @@ async function findLog(directory, threadId) {
|
|
|
248
281
|
}
|
|
249
282
|
|
|
250
283
|
export class SessionStatusReader {
|
|
251
|
-
constructor({ sessionsDir = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"), "sessions") } = {}) {
|
|
284
|
+
constructor({ sessionsDir = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"), "sessions"), now = Date.now } = {}) {
|
|
252
285
|
this.sessionsDir = sessionsDir;
|
|
286
|
+
this.now = now;
|
|
253
287
|
this.states = new Map();
|
|
254
|
-
this.
|
|
288
|
+
this.missingChecks = new Map();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
retryAfterMs(threadId) {
|
|
292
|
+
return Math.max(0, (this.missingChecks.get(threadId)?.nextCheckAt ?? 0) - this.now());
|
|
255
293
|
}
|
|
256
294
|
|
|
257
295
|
async read(threadId) {
|
|
258
296
|
if (!THREAD_ID.test(threadId ?? "")) return null;
|
|
259
297
|
let state = this.states.get(threadId);
|
|
260
298
|
if (!state) {
|
|
261
|
-
|
|
262
|
-
if (Date.now() - lastCheck < 10_000) return null;
|
|
263
|
-
this.missingCheckedAt.set(threadId, Date.now());
|
|
299
|
+
if (this.retryAfterMs(threadId) > 0) return null;
|
|
264
300
|
const filePath = await findLog(this.sessionsDir, threadId);
|
|
265
|
-
if (!filePath)
|
|
301
|
+
if (!filePath) {
|
|
302
|
+
const attempts = this.missingChecks.get(threadId)?.attempts ?? 0;
|
|
303
|
+
const delay = [500, 1000, 2000][attempts] ?? 10_000;
|
|
304
|
+
this.missingChecks.set(threadId, { attempts: attempts + 1, nextCheckAt: this.now() + delay });
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
266
307
|
state = emptyState(filePath);
|
|
267
308
|
this.states.set(threadId, state);
|
|
268
|
-
this.
|
|
309
|
+
this.missingChecks.delete(threadId);
|
|
269
310
|
}
|
|
270
311
|
let info;
|
|
271
312
|
try { info = await stat(state.filePath); }
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-circle"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<circle cx="12" cy="12" r="10" />
|
|
15
|
-
</svg>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-ellipsis"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<circle cx="12" cy="12" r="1" />
|
|
15
|
-
<circle cx="19" cy="12" r="1" />
|
|
16
|
-
<circle cx="5" cy="12" r="1" />
|
|
17
|
-
</svg>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-external-link"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M15 3h6v6" />
|
|
15
|
-
<path d="M10 14 21 3" />
|
|
16
|
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
17
|
-
</svg>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-list-todo"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M13 5h8" />
|
|
15
|
-
<path d="M13 12h8" />
|
|
16
|
-
<path d="M13 19h8" />
|
|
17
|
-
<path d="m3 17 2 2 4-4" />
|
|
18
|
-
<rect x="3" y="4" width="6" height="6" rx="1" />
|
|
19
|
-
</svg>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-message-square-quote"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M14 14a2 2 0 0 0 2-2V8h-2" />
|
|
15
|
-
<path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" />
|
|
16
|
-
<path d="M8 14a2 2 0 0 0 2-2V8H8" />
|
|
17
|
-
</svg>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-paperclip"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" />
|
|
15
|
-
</svg>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-star-off"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="m10.344 4.688 1.181-2.393a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.237 3.152" />
|
|
15
|
-
<path d="m17.945 17.945.43 2.505a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a8 8 0 0 0 .4-.099" />
|
|
16
|
-
<path d="m2 2 20 20" />
|
|
17
|
-
</svg>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-star"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" />
|
|
15
|
-
</svg>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-sticky-note"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z" />
|
|
15
|
-
<path d="M15 3v5a1 1 0 0 0 1 1h5" />
|
|
16
|
-
</svg>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!-- @license lucide-static v1.31.0 - ISC -->
|
|
2
|
-
<svg
|
|
3
|
-
class="lucide lucide-trash-2"
|
|
4
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
width="24"
|
|
6
|
-
height="24"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
stroke-width="2"
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
>
|
|
14
|
-
<path d="M10 11v6" />
|
|
15
|
-
<path d="M14 11v6" />
|
|
16
|
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
|
|
17
|
-
<path d="M3 6h18" />
|
|
18
|
-
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
19
|
-
</svg>
|