@foxden-app/foxclaw 0.3.17 → 0.3.18
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/dist/codex_app/local_usage.d.ts +7 -6
- package/dist/codex_app/local_usage.js +55 -66
- package/dist/controller/controller.d.ts +1 -1
- package/dist/controller/controller.js +15 -9
- package/dist/i18n.d.ts +4 -4
- package/dist/i18n.js +4 -4
- package/docs/user-manual.md +1 -1
- package/docs/zh/user-manual.md +1 -1
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +2 -0
- package/skills/npm-publish/SKILL.md +3 -0
|
@@ -11,19 +11,20 @@ export interface CodexLocalUsageStats {
|
|
|
11
11
|
turns: number;
|
|
12
12
|
usageEvents: number;
|
|
13
13
|
totals: CodexLocalUsageTotals;
|
|
14
|
-
|
|
14
|
+
responseThroughput: CodexLocalResponseThroughputStats;
|
|
15
15
|
latestSessionMtimeMs: number | null;
|
|
16
16
|
}
|
|
17
17
|
export interface CodexLocalUsageSnapshot {
|
|
18
18
|
computedAtMs: number;
|
|
19
19
|
stats: CodexLocalUsageStats;
|
|
20
20
|
}
|
|
21
|
-
export interface
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
export interface CodexLocalResponseThroughputStats {
|
|
22
|
+
completedTurns: number;
|
|
23
|
+
visibleOutputTokens: number;
|
|
24
24
|
seconds: number;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
recentCompletedTurns: number;
|
|
26
|
+
recentVisibleOutputTokens: number;
|
|
27
|
+
recentSeconds: number;
|
|
27
28
|
}
|
|
28
29
|
export declare function readCodexLocalUsageStats(codexHome?: string): Promise<CodexLocalUsageStats>;
|
|
29
30
|
export declare function readCodexLocalUsageSnapshot(snapshotPath: string): Promise<CodexLocalUsageSnapshot | null>;
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import readline from 'node:readline';
|
|
6
|
+
const RECENT_COMPLETED_TURNS = 10;
|
|
6
7
|
export async function readCodexLocalUsageStats(codexHome = resolveCodexHome()) {
|
|
7
8
|
const sessionFiles = await listJsonlFiles([
|
|
8
9
|
path.join(codexHome, 'sessions'),
|
|
@@ -13,7 +14,7 @@ export async function readCodexLocalUsageStats(codexHome = resolveCodexHome()) {
|
|
|
13
14
|
let sessionsWithUsage = 0;
|
|
14
15
|
let usageEvents = 0;
|
|
15
16
|
let latestSessionMtimeMs = null;
|
|
16
|
-
const
|
|
17
|
+
const completedTurnSamples = [];
|
|
17
18
|
for (const filePath of sessionFiles) {
|
|
18
19
|
const stat = await fs.stat(filePath).catch(() => null);
|
|
19
20
|
if (stat) {
|
|
@@ -21,7 +22,7 @@ export async function readCodexLocalUsageStats(codexHome = resolveCodexHome()) {
|
|
|
21
22
|
}
|
|
22
23
|
const fileUsage = await readSessionUsage(filePath, turnIds);
|
|
23
24
|
usageEvents += fileUsage.usageEvents;
|
|
24
|
-
|
|
25
|
+
completedTurnSamples.push(...fileUsage.completedTurnSamples);
|
|
25
26
|
if (fileUsage.totalUsage) {
|
|
26
27
|
sessionsWithUsage += 1;
|
|
27
28
|
addUsage(totals, fileUsage.totalUsage);
|
|
@@ -33,7 +34,7 @@ export async function readCodexLocalUsageStats(codexHome = resolveCodexHome()) {
|
|
|
33
34
|
turns: turnIds.size,
|
|
34
35
|
usageEvents,
|
|
35
36
|
totals,
|
|
36
|
-
|
|
37
|
+
responseThroughput: summarizeResponseThroughput(completedTurnSamples),
|
|
37
38
|
latestSessionMtimeMs,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
@@ -87,8 +88,8 @@ async function readSessionUsage(filePath, turnIds) {
|
|
|
87
88
|
const reader = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
88
89
|
let totalUsage = null;
|
|
89
90
|
let usageEvents = 0;
|
|
90
|
-
let
|
|
91
|
-
const
|
|
91
|
+
let activeTurn = null;
|
|
92
|
+
const completedTurnSamples = [];
|
|
92
93
|
try {
|
|
93
94
|
for await (const line of reader) {
|
|
94
95
|
if (!line.trim())
|
|
@@ -105,28 +106,49 @@ async function readSessionUsage(filePath, turnIds) {
|
|
|
105
106
|
if (turnId) {
|
|
106
107
|
turnIds.add(turnId);
|
|
107
108
|
}
|
|
108
|
-
if (timestampMs !== null
|
|
109
|
-
|
|
109
|
+
if (timestampMs !== null
|
|
110
|
+
&& turnId
|
|
111
|
+
&& event?.type === 'event_msg'
|
|
112
|
+
&& event?.payload?.type === 'task_started') {
|
|
113
|
+
activeTurn = { turnId, startedAtMs: timestampMs, visibleOutputTokens: 0 };
|
|
110
114
|
}
|
|
111
115
|
const info = event?.payload?.info;
|
|
112
116
|
const lastTokenUsage = info?.last_token_usage ?? info?.lastTokenUsage;
|
|
113
117
|
if (lastTokenUsage) {
|
|
114
118
|
usageEvents += 1;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
generationStartMs = timestampMs;
|
|
119
|
+
if (activeTurn) {
|
|
120
|
+
activeTurn.visibleOutputTokens += visibleOutputTokens(lastTokenUsage);
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
const totalTokenUsage = info?.total_token_usage ?? info?.totalTokenUsage;
|
|
121
124
|
if (totalTokenUsage) {
|
|
122
125
|
totalUsage = totalTokenUsage;
|
|
123
126
|
}
|
|
127
|
+
const completedTurn = activeTurn;
|
|
128
|
+
if (completedTurn !== null
|
|
129
|
+
&& timestampMs !== null
|
|
130
|
+
&& turnId
|
|
131
|
+
&& completedTurn.turnId === turnId
|
|
132
|
+
&& event?.type === 'event_msg'
|
|
133
|
+
&& (event?.payload?.type === 'task_complete' || event?.payload?.type === 'turn_aborted')) {
|
|
134
|
+
if (event.payload.type === 'task_complete' && completedTurn.visibleOutputTokens > 0) {
|
|
135
|
+
const seconds = (timestampMs - completedTurn.startedAtMs) / 1000;
|
|
136
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
137
|
+
completedTurnSamples.push({
|
|
138
|
+
completedAtMs: timestampMs,
|
|
139
|
+
visibleOutputTokens: completedTurn.visibleOutputTokens,
|
|
140
|
+
seconds,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
activeTurn = null;
|
|
145
|
+
}
|
|
124
146
|
}
|
|
125
147
|
}
|
|
126
148
|
finally {
|
|
127
149
|
reader.close();
|
|
128
150
|
}
|
|
129
|
-
return { usageEvents, totalUsage,
|
|
151
|
+
return { usageEvents, totalUsage, completedTurnSamples };
|
|
130
152
|
}
|
|
131
153
|
function emptyTotals() {
|
|
132
154
|
return {
|
|
@@ -137,22 +159,13 @@ function emptyTotals() {
|
|
|
137
159
|
totalTokens: 0,
|
|
138
160
|
};
|
|
139
161
|
}
|
|
140
|
-
function emptyOutputSpeedStats() {
|
|
141
|
-
return {
|
|
142
|
-
samples: 0,
|
|
143
|
-
outputTokens: 0,
|
|
144
|
-
seconds: 0,
|
|
145
|
-
latestTokensPerSecond: null,
|
|
146
|
-
latestSampleAtMs: null,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
162
|
function isCodexLocalUsageStats(value) {
|
|
150
163
|
if (!value || typeof value !== 'object') {
|
|
151
164
|
return false;
|
|
152
165
|
}
|
|
153
166
|
const stats = value;
|
|
154
167
|
const totals = stats.totals;
|
|
155
|
-
const
|
|
168
|
+
const throughput = stats.responseThroughput;
|
|
156
169
|
return isFiniteNumber(stats.sessionFiles)
|
|
157
170
|
&& isFiniteNumber(stats.sessionsWithUsage)
|
|
158
171
|
&& isFiniteNumber(stats.turns)
|
|
@@ -163,12 +176,13 @@ function isCodexLocalUsageStats(value) {
|
|
|
163
176
|
&& isFiniteNumber(totals?.outputTokens)
|
|
164
177
|
&& isFiniteNumber(totals?.reasoningOutputTokens)
|
|
165
178
|
&& isFiniteNumber(totals?.totalTokens)
|
|
166
|
-
&& Boolean(
|
|
167
|
-
&& isFiniteNumber(
|
|
168
|
-
&& isFiniteNumber(
|
|
169
|
-
&& isFiniteNumber(
|
|
170
|
-
&&
|
|
171
|
-
&&
|
|
179
|
+
&& Boolean(throughput)
|
|
180
|
+
&& isFiniteNumber(throughput?.completedTurns)
|
|
181
|
+
&& isFiniteNumber(throughput?.visibleOutputTokens)
|
|
182
|
+
&& isFiniteNumber(throughput?.seconds)
|
|
183
|
+
&& isFiniteNumber(throughput?.recentCompletedTurns)
|
|
184
|
+
&& isFiniteNumber(throughput?.recentVisibleOutputTokens)
|
|
185
|
+
&& isFiniteNumber(throughput?.recentSeconds)
|
|
172
186
|
&& isNullableFiniteNumber(stats.latestSessionMtimeMs);
|
|
173
187
|
}
|
|
174
188
|
function isFiniteNumber(value) {
|
|
@@ -187,34 +201,21 @@ function addUsage(totals, usage) {
|
|
|
187
201
|
totals.reasoningOutputTokens += numberField(usage, 'reasoning_output_tokens', 'reasoningOutputTokens');
|
|
188
202
|
totals.totalTokens += totalTokens || inputTokens + outputTokens;
|
|
189
203
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
target.seconds += source.seconds;
|
|
194
|
-
if (source.latestTokensPerSecond !== null
|
|
195
|
-
&& source.latestSampleAtMs !== null
|
|
196
|
-
&& (target.latestSampleAtMs === null || source.latestSampleAtMs > target.latestSampleAtMs)) {
|
|
197
|
-
target.latestTokensPerSecond = source.latestTokensPerSecond;
|
|
198
|
-
target.latestSampleAtMs = source.latestSampleAtMs;
|
|
199
|
-
}
|
|
204
|
+
function visibleOutputTokens(usage) {
|
|
205
|
+
return Math.max(0, numberField(usage, 'output_tokens', 'outputTokens')
|
|
206
|
+
- numberField(usage, 'reasoning_output_tokens', 'reasoningOutputTokens'));
|
|
200
207
|
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
stats.samples += 1;
|
|
214
|
-
stats.outputTokens += outputTokens;
|
|
215
|
-
stats.seconds += seconds;
|
|
216
|
-
stats.latestTokensPerSecond = outputTokens / seconds;
|
|
217
|
-
stats.latestSampleAtMs = timestampMs;
|
|
208
|
+
function summarizeResponseThroughput(samples) {
|
|
209
|
+
const ordered = samples.slice().sort((left, right) => left.completedAtMs - right.completedAtMs);
|
|
210
|
+
const recent = ordered.slice(-RECENT_COMPLETED_TURNS);
|
|
211
|
+
return {
|
|
212
|
+
completedTurns: ordered.length,
|
|
213
|
+
visibleOutputTokens: ordered.reduce((total, sample) => total + sample.visibleOutputTokens, 0),
|
|
214
|
+
seconds: ordered.reduce((total, sample) => total + sample.seconds, 0),
|
|
215
|
+
recentCompletedTurns: recent.length,
|
|
216
|
+
recentVisibleOutputTokens: recent.reduce((total, sample) => total + sample.visibleOutputTokens, 0),
|
|
217
|
+
recentSeconds: recent.reduce((total, sample) => total + sample.seconds, 0),
|
|
218
|
+
};
|
|
218
219
|
}
|
|
219
220
|
function numberField(source, snakeKey, camelKey) {
|
|
220
221
|
const snakeValue = source[snakeKey];
|
|
@@ -229,15 +230,3 @@ function parseTimestampMs(value) {
|
|
|
229
230
|
const timestamp = Date.parse(value);
|
|
230
231
|
return Number.isFinite(timestamp) ? timestamp : null;
|
|
231
232
|
}
|
|
232
|
-
function isGenerationBoundary(event) {
|
|
233
|
-
const payload = event?.payload;
|
|
234
|
-
if (event?.type === 'response_item' && payload?.type === 'function_call_output') {
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
if (event?.type !== 'event_msg') {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
return payload?.type === 'task_started'
|
|
241
|
-
|| payload?.type === 'exec_command_end'
|
|
242
|
-
|| payload?.type === 'user_message';
|
|
243
|
-
}
|
|
@@ -267,7 +267,7 @@ export declare class BridgeSessionCore {
|
|
|
267
267
|
private buildNativeCollaborationMode;
|
|
268
268
|
private buildCodexUsageStatusLines;
|
|
269
269
|
private buildCodexLocalUsageStatusLines;
|
|
270
|
-
private
|
|
270
|
+
private formatCodexLocalResponseThroughputStatusLines;
|
|
271
271
|
private resolveFastStatusLabel;
|
|
272
272
|
private readCachedCodexLocalUsageStats;
|
|
273
273
|
private refreshCodexLocalUsageIfNeeded;
|
|
@@ -4544,11 +4544,12 @@ export class BridgeSessionCore {
|
|
|
4544
4544
|
t(locale, 'status_codex_local_tokens', {
|
|
4545
4545
|
total: formatTokenCount(stats.totals.totalTokens),
|
|
4546
4546
|
input: formatTokenCount(stats.totals.inputTokens),
|
|
4547
|
+
visible: formatTokenCount(Math.max(0, stats.totals.outputTokens - stats.totals.reasoningOutputTokens)),
|
|
4547
4548
|
output: formatTokenCount(stats.totals.outputTokens),
|
|
4548
4549
|
cached: formatTokenCount(stats.totals.cachedInputTokens),
|
|
4549
4550
|
reasoning: formatTokenCount(stats.totals.reasoningOutputTokens),
|
|
4550
4551
|
}),
|
|
4551
|
-
...this.
|
|
4552
|
+
...this.formatCodexLocalResponseThroughputStatusLines(locale, stats),
|
|
4552
4553
|
t(locale, 'status_codex_local_snapshot_at', {
|
|
4553
4554
|
value: formatLocalTimestamp(snapshot.computedAtMs / 1000),
|
|
4554
4555
|
}),
|
|
@@ -4559,16 +4560,21 @@ export class BridgeSessionCore {
|
|
|
4559
4560
|
return [t(locale, 'status_codex_local_usage_unavailable', { error: formatShortStatusError(error) })];
|
|
4560
4561
|
}
|
|
4561
4562
|
}
|
|
4562
|
-
|
|
4563
|
-
const
|
|
4564
|
-
if (
|
|
4563
|
+
formatCodexLocalResponseThroughputStatusLines(locale, stats) {
|
|
4564
|
+
const throughput = stats.responseThroughput;
|
|
4565
|
+
if (throughput.completedTurns === 0
|
|
4566
|
+
|| throughput.visibleOutputTokens <= 0
|
|
4567
|
+
|| throughput.seconds <= 0
|
|
4568
|
+
|| throughput.recentCompletedTurns === 0
|
|
4569
|
+
|| throughput.recentVisibleOutputTokens <= 0
|
|
4570
|
+
|| throughput.recentSeconds <= 0) {
|
|
4565
4571
|
return [];
|
|
4566
4572
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4573
|
+
return [t(locale, 'status_codex_local_throughput', {
|
|
4574
|
+
overall: formatCompactNumber(throughput.visibleOutputTokens / throughput.seconds),
|
|
4575
|
+
recent: formatCompactNumber(throughput.recentVisibleOutputTokens / throughput.recentSeconds),
|
|
4576
|
+
recentTurns: formatTokenCount(throughput.recentCompletedTurns),
|
|
4577
|
+
turns: formatTokenCount(throughput.completedTurns),
|
|
4572
4578
|
})];
|
|
4573
4579
|
}
|
|
4574
4580
|
async resolveFastStatusLabel(locale, settings) {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -111,8 +111,8 @@ declare const MESSAGES: {
|
|
|
111
111
|
readonly status_codex_usage_reset: ", resets {value}";
|
|
112
112
|
readonly status_codex_usage_unavailable: "Codex usage: unavailable ({error})";
|
|
113
113
|
readonly status_codex_local_history: "Codex local history: {sessions} sessions, {turns} turns, {events} usage records";
|
|
114
|
-
readonly status_codex_local_tokens: "Codex local tokens: total {total}; input {input}, output {
|
|
115
|
-
readonly
|
|
114
|
+
readonly status_codex_local_tokens: "Codex local tokens: total {total}; input {input}, visible output {visible}, reasoning output {reasoning}, total output {output}, cached input {cached}";
|
|
115
|
+
readonly status_codex_local_throughput: "Codex visible reply throughput (end-to-end, excluding reasoning): overall {overall} token/s, last {recentTurns} completed turns {recent} token/s ({turns} completed turns sampled)";
|
|
116
116
|
readonly status_codex_local_snapshot_at: "Codex local stats snapshot: {value}";
|
|
117
117
|
readonly status_codex_local_usage_refreshing: "Codex local history: building snapshot in background";
|
|
118
118
|
readonly status_codex_local_usage_unavailable: "Codex local history: unavailable ({error})";
|
|
@@ -675,8 +675,8 @@ declare const MESSAGES: {
|
|
|
675
675
|
readonly status_codex_usage_reset: ",重置时间 {value}";
|
|
676
676
|
readonly status_codex_usage_unavailable: "Codex 用量:无法获取({error})";
|
|
677
677
|
readonly status_codex_local_history: "Codex 本地历史:{sessions} 个会话,{turns} 轮,{events} 条用量记录";
|
|
678
|
-
readonly status_codex_local_tokens: "Codex 本地 Token:总计 {total};输入 {input}
|
|
679
|
-
readonly
|
|
678
|
+
readonly status_codex_local_tokens: "Codex 本地 Token:总计 {total};输入 {input},可见输出 {visible},推理输出 {reasoning},总输出 {output},缓存输入 {cached}";
|
|
679
|
+
readonly status_codex_local_throughput: "Codex 可见答复吞吐(端到端,排除推理 token):整体 {overall} token/s,最近 {recentTurns} 个完成轮次 {recent} token/s({turns} 个完成轮次样本)";
|
|
680
680
|
readonly status_codex_local_snapshot_at: "Codex 本地统计快照:{value}";
|
|
681
681
|
readonly status_codex_local_usage_refreshing: "Codex 本地历史:正在后台生成统计快照";
|
|
682
682
|
readonly status_codex_local_usage_unavailable: "Codex 本地历史:无法获取({error})";
|
package/dist/i18n.js
CHANGED
|
@@ -109,8 +109,8 @@ const MESSAGES = {
|
|
|
109
109
|
status_codex_usage_reset: ', resets {value}',
|
|
110
110
|
status_codex_usage_unavailable: 'Codex usage: unavailable ({error})',
|
|
111
111
|
status_codex_local_history: 'Codex local history: {sessions} sessions, {turns} turns, {events} usage records',
|
|
112
|
-
status_codex_local_tokens: 'Codex local tokens: total {total}; input {input}, output {
|
|
113
|
-
|
|
112
|
+
status_codex_local_tokens: 'Codex local tokens: total {total}; input {input}, visible output {visible}, reasoning output {reasoning}, total output {output}, cached input {cached}',
|
|
113
|
+
status_codex_local_throughput: 'Codex visible reply throughput (end-to-end, excluding reasoning): overall {overall} token/s, last {recentTurns} completed turns {recent} token/s ({turns} completed turns sampled)',
|
|
114
114
|
status_codex_local_snapshot_at: 'Codex local stats snapshot: {value}',
|
|
115
115
|
status_codex_local_usage_refreshing: 'Codex local history: building snapshot in background',
|
|
116
116
|
status_codex_local_usage_unavailable: 'Codex local history: unavailable ({error})',
|
|
@@ -673,8 +673,8 @@ const MESSAGES = {
|
|
|
673
673
|
status_codex_usage_reset: ',重置时间 {value}',
|
|
674
674
|
status_codex_usage_unavailable: 'Codex 用量:无法获取({error})',
|
|
675
675
|
status_codex_local_history: 'Codex 本地历史:{sessions} 个会话,{turns} 轮,{events} 条用量记录',
|
|
676
|
-
status_codex_local_tokens: 'Codex 本地 Token:总计 {total};输入 {input}
|
|
677
|
-
|
|
676
|
+
status_codex_local_tokens: 'Codex 本地 Token:总计 {total};输入 {input},可见输出 {visible},推理输出 {reasoning},总输出 {output},缓存输入 {cached}',
|
|
677
|
+
status_codex_local_throughput: 'Codex 可见答复吞吐(端到端,排除推理 token):整体 {overall} token/s,最近 {recentTurns} 个完成轮次 {recent} token/s({turns} 个完成轮次样本)',
|
|
678
678
|
status_codex_local_snapshot_at: 'Codex 本地统计快照:{value}',
|
|
679
679
|
status_codex_local_usage_refreshing: 'Codex 本地历史:正在后台生成统计快照',
|
|
680
680
|
status_codex_local_usage_unavailable: 'Codex 本地历史:无法获取({error})',
|
package/docs/user-manual.md
CHANGED
|
@@ -230,7 +230,7 @@ Later commands are sorted by recent usage. Plain text, photos, and files continu
|
|
|
230
230
|
|
|
231
231
|
### 3.2 `/status`, `/account`, `/quota`, `/update`
|
|
232
232
|
|
|
233
|
-
- `/status`: FoxClaw, app-server, current thread binding, model, access, and Codex usage summary. Local session, token, and
|
|
233
|
+
- `/status`: FoxClaw, app-server, current thread binding, model, access, and Codex usage summary. Local session, token, and visible-reply-throughput metrics use a background-generated historical snapshot instead of scanning large logs during the request; throughput is computed end-to-end for completed turns, excluding reasoning tokens while including waiting and tool execution time.
|
|
234
234
|
- `/account`: current Codex account.
|
|
235
235
|
- `/quota`: Codex usage and quota window.
|
|
236
236
|
- `/update`: upgrade FoxClaw, run checks, and restart the service; it refuses while a turn, approval, or question is active, then reports the result after restart.
|
package/docs/zh/user-manual.md
CHANGED
|
@@ -230,7 +230,7 @@ TG_ALLOWED_TOPIC_ID=42
|
|
|
230
230
|
|
|
231
231
|
### 3.2 `/status`、`/account`、`/quota`、`/update`
|
|
232
232
|
|
|
233
|
-
- `/status`:查看 FoxClaw、app-server、当前绑定线程、模型、权限和 Codex 用量摘要。本地 session/Token
|
|
233
|
+
- `/status`:查看 FoxClaw、app-server、当前绑定线程、模型、权限和 Codex 用量摘要。本地 session/Token/可见答复吞吐使用后台生成的历史快照,避免状态查询现场扫描大量日志;答复吞吐按完成轮次端到端耗时计算,排除推理 token,但包含等待与工具执行时间。
|
|
234
234
|
- `/account`:查看当前 Codex 登录账号。
|
|
235
235
|
- `/quota`:查看 Codex 用量和额度窗口。
|
|
236
236
|
- `/update`:升级 FoxClaw、自检并重启服务;当前有运行中回复、审批或待确认问题时会拒绝执行,重启后会回报结果。
|
package/package.json
CHANGED
package/skills/foxclaw/SKILL.md
CHANGED
|
@@ -190,10 +190,12 @@ Use this checklist when the user asks for standard closing actions, release wrap
|
|
|
190
190
|
- If `doctor` fails only because `DEFAULT_CWD` is missing, report that separately; do not treat it as evidence that the service update failed.
|
|
191
191
|
7. Publish to npm when requested:
|
|
192
192
|
- Prefer GitHub Actions trusted publishing via `.github/workflows/publish.yml`: bump and commit the package version, push `main`, then push a matching `v<version>` tag. The tag version must match `package.json`.
|
|
193
|
+
- Treat `workflow_dispatch` only as a retry path from an existing matching release tag; do not manually run publishing from `main`.
|
|
193
194
|
- Configure npmjs.com trusted publishing for `foxden-app/foxclaw` and workflow `publish.yml`; do not store npm tokens if OIDC trusted publishing is available.
|
|
194
195
|
- Temporary fallback: store an npm automation/bypass-2FA token as the GitHub Actions secret `NPM_TOKEN`. Never print the token or commit it.
|
|
195
196
|
- Check `npm whoami` and `npm view @foxden-app/foxclaw version`.
|
|
196
197
|
- If the target version is already published, bump with `npm version patch --no-git-tag-version` before validation and commit.
|
|
198
|
+
- If a workflow fails before `npm publish`, inspect that failed GitHub Actions step before attributing it to npm trusted publishing.
|
|
197
199
|
- Manual fallback only: run `BROWSER=true npm publish` in a TTY.
|
|
198
200
|
- If npm returns `ENEEDAUTH`, report that npm publish is blocked by registry login and leave the package unreported as published.
|
|
199
201
|
- Never print npm tokens or `.npmrc` auth values.
|
|
@@ -73,6 +73,8 @@ Use this path when the repo has `.github/workflows/publish.yml` and npmjs.com ha
|
|
|
73
73
|
npm view <package-name> version
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
If the workflow provides `workflow_dispatch` as a recovery path, dispatch it only from an existing release tag whose version matches `package.json`. Do not dispatch publishing from a branch containing an unpublished version unless the workflow explicitly resolves and validates a release tag.
|
|
77
|
+
|
|
76
78
|
Do not store or print npm tokens when trusted publishing is available. If trusted publishing is not configured on npmjs.com, the workflow can use a GitHub Actions secret named `NPM_TOKEN` as a temporary fallback. Store only automation/bypass-2FA tokens there, never paste tokens in chat or commit them.
|
|
77
79
|
|
|
78
80
|
### Manual Publish
|
|
@@ -124,4 +126,5 @@ Press ENTER to open in the browser...
|
|
|
124
126
|
- If `xdg-open` fails because the environment has no browser, rerun with `BROWSER=true npm publish`.
|
|
125
127
|
- If the auth link expires or the publish process exits, rerun `BROWSER=true npm publish` to generate a new link.
|
|
126
128
|
- If the user provides a classic authenticator OTP instead of using the web link, publish can be retried with `npm publish --otp <code>`, but prefer web auth when the user asks for a clickable confirmation link.
|
|
129
|
+
- If a GitHub Actions release fails before the `npm publish` step, such as during checkout or action download authentication, do not diagnose it as a trusted publishing rejection. Inspect the failed step and GitHub Actions status first.
|
|
127
130
|
- Never print npm tokens or `.npmrc` auth values.
|