quota-keepalive 0.1.6 → 0.1.8

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.en.md CHANGED
@@ -44,8 +44,10 @@ hook. This plugin only coordinates those capabilities and presents the results.
44
44
  - Respect provider-level and account-level switches; unsupported or signed-out
45
45
  accounts are reported explicitly instead of being silently skipped.
46
46
  - Send real-chat keepalive through each provider's signed adapter.
47
- - Use short probes by default. Long-context probes are opt-in and respect
48
- `inputTokens`.
47
+ - Use a real activity conversation by default. For WorkBuddy CN this follows
48
+ dsh's measured probe shape: about 25k input tokens and one output token, which
49
+ consumes credits. Use the short probe only when a low-cost connectivity check
50
+ is enough.
49
51
  - Prefer models that recently completed a real keepalive successfully, then fall
50
52
  back by success rate, recency, and known failures. Provider model order is used
51
53
  when there is no history.
@@ -132,7 +134,9 @@ enable automatic keepalive by default.
132
134
  - **Refresh quota**: refresh all enabled providers; each provider row also has a
133
135
  provider-only refresh button.
134
136
  - **Save schedule**: set the shared interval and action type.
135
- - **Short probe / Long context**: choose the request shape used by keepalive.
137
+ - **Real activity / Short probe**: real activity fills the configured input and
138
+ can leave an activity record, but consumes credits. A short probe is cheaper
139
+ and faster, but does not guarantee an activity record.
136
140
 
137
141
  Saving a schedule starts the next run after the configured interval. The next run
138
142
  time is persisted, and a task already due when Magpie starts is run immediately.
@@ -166,10 +170,12 @@ Keepalive does not blindly choose the cheapest or a free model:
166
170
  6. Fall back to the next candidate on a missing model, credit rejection, upstream
167
171
  outage, or empty reply.
168
172
 
169
- For WorkBuddy CN, the keepalive path follows dsh probe semantics: an HTTP 2xx means
170
- the upstream accepted and recorded a real chat, even when a one-token response only
171
- contains `[DONE]`, usage data, or an empty visible body. JSON business errors, HTML
172
- login pages, and timeouts are failures.
173
+ For WorkBuddy CN, the real activity path follows dsh probe semantics and sends
174
+ about 25k input tokens with a one-token output cap. Activity mode requires either
175
+ visible model content or usage/credit/prompt-token evidence in the response. A bare
176
+ `[DONE]`, an empty 200, JSON business errors, HTML login pages, and timeouts are
177
+ failures. The short-probe mode remains available for low-cost checks, but it does
178
+ not claim that an activity record was created.
173
179
 
174
180
  ## Configuration
175
181
 
@@ -185,7 +191,7 @@ magpie plugin options quota-keepalive '{
185
191
  "codearts": "deepseek-v4.1-flash"
186
192
  },
187
193
  "modelCandidates": 4,
188
- "probeMode": "short",
194
+ "probeMode": "activity",
189
195
  "probeMessage": "Magpie daily connectivity check. Reply with OK.",
190
196
  "inputTokens": 25000,
191
197
  "maxAttempts": 3,
@@ -200,15 +206,21 @@ magpie plugin options quota-keepalive '{
200
206
  - `model`: optional preferred model; it is not a hard allowlist.
201
207
  - `modelPreferences`: preferred model per provider.
202
208
  - `modelCandidates`: maximum fallback candidates per account, default `4`.
203
- - `probeMode`: `short` by default; `context` sends the configured long input.
209
+ - `probeMode`: `activity` by default and sends the configured long activity input;
210
+ `short` sends only a short message; `context` is the backwards-compatible alias
211
+ for `activity`.
204
212
  - `probeMessage`: short probe text, up to 500 characters.
205
- - `inputTokens`: long-context input size, only used with `probeMode=context`.
213
+ - `inputTokens`: long-input size for `activity`/`context`, default 25000.
206
214
  - `maxAttempts`: maximum keepalive attempts per account per Beijing day.
207
215
  - `timeoutMs`: overall timeout for one model request, default 90 seconds.
208
216
  - `logMaxBytes`: rotate one previous log after this size, default 2 MiB.
209
217
  - `statePath`, `logPath`, `snapshotPath`, `commandPath`, `schedulePath`: optional
210
218
  absolute paths.
211
219
 
220
+ Upgrade note: an old installation that explicitly saved `probeMode: "short"` keeps
221
+ that choice. Select **Real activity** and save the schedule (or update plugin
222
+ options) when you want WorkBuddy activity records to be written.
223
+
212
224
  ## Logs
213
225
 
214
226
  The default log is:
package/README.md CHANGED
@@ -34,7 +34,8 @@ quota-keepalive 不接管任何供应商的登录,也不会把页面伪装成
34
34
  - 一键签到:通过各供应商登录插件公开的 `auth.checkin` 接口执行,尊重供应商、
35
35
  账号两级启停;页面会明确显示未适配或未登录的账号,不会静默跳过。
36
36
  - 真实对话保活:通过各供应商自己的登录插件取得签名请求,发送一条真实消息。
37
- 默认使用“短探活”,只发送一条短消息;需要长上下文时才显式切换“长上下文”模式。
37
+ 默认使用“真实活跃对话”,WorkBuddy CN 会按 dsh 实测语义填充约 25k 输入 token
38
+ 并消耗额度;只想做低成本连通性检查时才切换到“短探活”。
38
39
  - 模型选择:只从该账号实际列出的模型中选择。默认优先近期真实保活成功过的模型,
39
40
  再按成功率、成功时间和失败次数排序;没有历史记录时沿用供应商自己的模型顺序,
40
41
  不再按免费或价格抢先选择。第一个模型不可用时自动换下一档,全部不可用才记失败。
@@ -149,8 +150,8 @@ npm publish --access public --otp=<6 位动态码>
149
150
  不会因为快速连续点击触发“每秒只能写入一次”的报错。
150
151
  - `刷新额度`按钮:立即刷新全部启用供应商额度;供应商卡片内的刷新按钮只刷新该供应商。
151
152
  - `保存计划`:设置统一执行间隔和任务类型。计划默认关闭,防止无意间消耗积分。
152
- - `短探活/长上下文`:短探活风险、耗时和额度消耗更低;长上下文会按 `inputTokens`
153
- 填充请求,适合明确需要维持长上下文窗口的场景。
153
+ - `真实活跃对话/短探活`:真实活跃对话按 `inputTokens` 填充请求,能增加上活跃记录
154
+ 的概率并可能消耗积分;短探活风险、耗时和额度消耗更低,但不保证供应商记录为一次对话。
154
155
  - 定时计划保存或修改后,从下一个间隔开始执行;下次执行时间会持久化,Magpie 重启或
155
156
  电脑休眠后会补跑已经到期的任务。需要立即执行时使用页面顶部的手动按钮。
156
157
 
@@ -170,9 +171,10 @@ npm publish --access public --otp=<6 位动态码>
170
171
  3. 账号没有记录时,优先该供应商其他账号成功过的模型;仍无记录则跟随供应商模型列表顺序。
171
172
  4. 已知失败的模型排在未验证模型之后;单个账号最多尝试配置数量的候选模型。
172
173
  5. 模型不存在、额度拒绝、上游不可用或空回复时切换下一个候选。
173
- 6. WorkBuddy CN 按 dsh 的 probe 语义,HTTP 2xx 表示上游已经接受并记录一次真实对话;
174
- `max_tokens` 为 1 时可能只有 `[DONE]`、usage 或空正文,仍算成功。JSON 业务错误、
175
- HTML 登录页和超时不算成功。
174
+ 6. WorkBuddy CN 的真实活跃对话按 dsh 的 probe 语义发送约 25k 输入和 1 个输出 token。
175
+ 活动模式要求响应里出现模型正文,或 usage/credit/prompt token 证据;只有 `[DONE]`、
176
+ 空 200、JSON 业务错误、HTML 登录页和超时都不算成功。短探活仍保留低成本兼容模式,
177
+ 但不会声称已经留下真实活跃记录。
176
178
 
177
179
  账号卡片会优先显示最近一次真实保活实际使用的模型(`实际模型`);没有执行历史时,
178
180
  依次显示用户指定的 `所选模型` 或自动排序出的 `自动首选`。供应商设置行也会汇总
@@ -196,7 +198,7 @@ magpie plugin options quota-keepalive '{
196
198
  "codearts": "deepseek-v4.1-flash"
197
199
  },
198
200
  "modelCandidates": 4,
199
- "probeMode": "short",
201
+ "probeMode": "activity",
200
202
  "probeMessage": "Magpie daily connectivity check. Reply with OK.",
201
203
  "inputTokens": 25000,
202
204
  "maxAttempts": 3,
@@ -214,9 +216,10 @@ magpie plugin options quota-keepalive '{
214
216
  WorkBuddy CN 默认优先 `deepseek-v4.1-flash`,这是 dsh 实测较稳定的真实对话模型;
215
217
  其他供应商按近 5 天可用性排序,没有成功记录时按供应商模型顺序选择,不按免费或倍率抢先选择。
216
218
  - `modelCandidates`:每个账号每次最多尝试的模型数,默认 4,范围 1–12。
217
- - `probeMode`:`short`(默认)只发一条短消息;`context` 才使用长上下文填充。
219
+ - `probeMode`:`activity`(默认)发送真实活跃对话,约 25k 输入;`short` 只发短消息;
220
+ `context` 是旧版配置的兼容别名,行为与 `activity` 相同。
218
221
  - `probeMessage`:短探活消息,最多 500 字符;留空时使用默认连通性检查文本。
219
- - `inputTokens`:仅 `probeMode=context` 时生效,默认 25000,范围 1000–25000。
222
+ - `inputTokens`:`activity`/`context` 模式的长输入规模,默认 25000,范围 1000–25000。
220
223
  - `maxAttempts`:每个账号每个北京时间日最多保活尝试轮数,默认 3。
221
224
  - `timeoutMs`:单个模型请求的整体截止时间,默认 90 秒,范围 10–180 秒。WorkBuddy
222
225
  CN 的 25k 输入请求可能先返回响应头、再继续流式读取;90 秒比 25 秒更接近 dsh 的
@@ -225,6 +228,10 @@ magpie plugin options quota-keepalive '{
225
228
  - `logMaxBytes`:单个日志上限,默认 2 MiB,超出后轮转一份 `.1`。
226
229
  - `statePath`、`logPath`、`snapshotPath`、`commandPath`、`schedulePath`:可选绝对路径。
227
230
 
231
+ 升级提示:旧版本如果已经显式保存过 `probeMode: "short"`,新版本会尊重这项用户设置,
232
+ 不会静默改成 `activity`。需要点亮 WorkBuddy 活跃记录时,请在页面选择“真实活跃对话”
233
+ 并保存计划,或者执行 `magpie plugin options quota-keepalive` 更新配置。
234
+
228
235
  ## 执行周期与失败
229
236
 
230
237
  Magpie 自己的每日签到调度仍会在启动后约 2 分钟进行首轮,之后约每 30 分钟巡检;
@@ -236,9 +243,9 @@ Magpie 自己的每日签到调度仍会在启动后约 2 分钟进行首轮,
236
243
 
237
244
  保活失败会按 `maxAttempts` 重试;达到上限后当天不再自动发送。网络失败、登录过期、
238
245
  模型不可用和空回复都会进入日志。遇到明确的 HTTP 402/429 额度耗尽或免费额度用尽时,
239
- 当天停止自动重试,避免持续发送无效请求。手动“发送保活”或账号级“执行保活”可以重试
240
- 当天失败或额度耗尽的账号;签到或保活当天已经成功时,即使手动触发也会跳过,不会重复
241
- 发送。供应商整体停用时,账号级手动按钮也会禁用。
246
+ 当天停止自动重试,避免持续发送无效请求。当天已经成功后,自动任务和手动触发都会
247
+ 跳过,不会重复发送;手动“发送保活”只能在当天尚未成功时重试失败账号。供应商整体
248
+ 停用时,账号级手动按钮也会禁用。
242
249
 
243
250
  ## 日志
244
251
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quota-keepalive",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Unofficial multi-provider quota inventory, check-in, keepalive scheduling, history, and quota trends for Magpie",
5
5
  "keywords": [
6
6
  "magpie",
package/src/config.js CHANGED
@@ -19,7 +19,7 @@ export const DEFAULT_KEEPALIVE = Object.freeze({
19
19
  model: "",
20
20
  modelCandidates: 4,
21
21
  inputTokens: 25_000,
22
- probeMode: "short",
22
+ probeMode: "activity",
23
23
  probeMessage: DEFAULT_PROBE_MESSAGE,
24
24
  maxAttempts: 3,
25
25
  scheduleEnabled: false,
@@ -75,7 +75,9 @@ export function resolveKeepaliveOptions(raw) {
75
75
  scheduleMinutes: integerIn(source.scheduleMinutes, DEFAULT_KEEPALIVE.scheduleMinutes, 5, 10_080),
76
76
  configHome: absolutePath(source.configHome, undefined),
77
77
  inputTokens: integerIn(source.inputTokens, DEFAULT_KEEPALIVE.inputTokens, 1_000, 25_000),
78
- probeMode: source.probeMode === "context" ? "context" : DEFAULT_KEEPALIVE.probeMode,
78
+ probeMode: ["short", "context", "activity"].includes(source.probeMode)
79
+ ? source.probeMode
80
+ : DEFAULT_KEEPALIVE.probeMode,
79
81
  probeMessage: probeMessage || DEFAULT_PROBE_MESSAGE,
80
82
  maxAttempts: integerIn(source.maxAttempts, DEFAULT_KEEPALIVE.maxAttempts, 1, 10),
81
83
  timeoutMs: integerIn(source.timeoutMs, DEFAULT_KEEPALIVE.timeoutMs, 10_000, 180_000),
package/src/keepalive.js CHANGED
@@ -4,6 +4,9 @@ const ORIGIN = "https://copilot.tencent.com"
4
4
  const CHAT_PATH = "/v2/chat/completions"
5
5
  const FILLER = "The quick brown fox jumps over the lazy dog. "
6
6
  const CHARS_PER_TOKEN = 4.5
7
+ const DEFAULT_ACTIVITY_INPUT_TOKENS = 25_000
8
+ const ACTIVITY_PROMPT = "Reply with a single word."
9
+ const ACTIVITY_SYSTEM_PROMPT = "You are a connectivity check. Reply with a single word."
7
10
  export const DEFAULT_PROBE_MESSAGE = "Magpie daily connectivity check. Reply with OK."
8
11
 
9
12
  function probeMessage(value) {
@@ -18,18 +21,26 @@ export function buildKeepaliveBody({
18
21
  probeMessage: message = "",
19
22
  }) {
20
23
  const prompt = `${probeMessage(message)} Today is ${day}.`
24
+ const activity = probeMode === "activity"
25
+ const context = activity || probeMode === "context"
21
26
  let content = prompt
22
- if (probeMode === "context") {
23
- const targetChars = Math.ceil(inputTokens * CHARS_PER_TOKEN)
27
+ let system = "You are a concise connectivity check. Reply with a single word."
28
+ if (context) {
29
+ const tokens = Number(inputTokens) || (activity ? DEFAULT_ACTIVITY_INPUT_TOKENS : 0)
30
+ const targetChars = Math.ceil(tokens * CHARS_PER_TOKEN)
24
31
  const filler = FILLER.repeat(Math.ceil(targetChars / FILLER.length))
25
- content = `${filler}${prompt}`
32
+ // WorkBuddy's CN gateway measures a real request by its input size. The
33
+ // activity shape mirrors dsh's probe body so the request is recorded as
34
+ // a conversation instead of a short connectivity check.
35
+ content = activity ? `${filler}${ACTIVITY_PROMPT}` : `${filler}${prompt}`
36
+ if (activity) system = ACTIVITY_SYSTEM_PROMPT
26
37
  }
27
38
  return {
28
39
  model,
29
40
  messages: [
30
41
  {
31
42
  role: "system",
32
- content: "You are a concise connectivity check. Reply with a single word.",
43
+ content: system,
33
44
  },
34
45
  {
35
46
  role: "user",
@@ -53,6 +64,64 @@ function parsedJson(value) {
53
64
  }
54
65
  }
55
66
 
67
+ function numberValue(value) {
68
+ if (typeof value === "number") return Number.isFinite(value) ? value : undefined
69
+ if (typeof value !== "string" || !value.trim()) return undefined
70
+ const n = Number(value.trim())
71
+ return Number.isFinite(n) ? n : undefined
72
+ }
73
+
74
+ function hasKey(value, name) {
75
+ return object(value) && Object.prototype.hasOwnProperty.call(value, name)
76
+ }
77
+
78
+ function numberField(value, names) {
79
+ for (const name of names) {
80
+ if (!hasKey(value, name)) continue
81
+ const n = numberValue(value[name])
82
+ if (n !== undefined) return n
83
+ }
84
+ return undefined
85
+ }
86
+
87
+ function usageEvidence(payloads) {
88
+ const result = {
89
+ sawUsage: false,
90
+ credit: undefined,
91
+ promptTokens: undefined,
92
+ completionTokens: undefined,
93
+ totalTokens: undefined,
94
+ }
95
+ for (const payload of Array.isArray(payloads) ? payloads : []) {
96
+ const candidates = [
97
+ payload,
98
+ payload?.usage,
99
+ payload?.data?.usage,
100
+ payload?.data,
101
+ ].filter(object)
102
+ for (const candidate of candidates) {
103
+ const credit = numberField(candidate, ["credit", "credits"])
104
+ const promptTokens = numberField(candidate, ["prompt_tokens", "promptTokens", "input_tokens", "inputTokens"])
105
+ const completionTokens = numberField(candidate, [
106
+ "completion_tokens",
107
+ "completionTokens",
108
+ "output_tokens",
109
+ "outputTokens",
110
+ ])
111
+ const totalTokens = numberField(candidate, ["total_tokens", "totalTokens"])
112
+ if ([credit, promptTokens, completionTokens, totalTokens].every((value) => value === undefined)) {
113
+ continue
114
+ }
115
+ result.sawUsage = true
116
+ result.credit ??= credit
117
+ result.promptTokens ??= promptTokens
118
+ result.completionTokens ??= completionTokens
119
+ result.totalTokens ??= totalTokens
120
+ }
121
+ }
122
+ return result
123
+ }
124
+
56
125
  function businessFailure(body) {
57
126
  if (!object(body)) return ""
58
127
  if (body.success === false) {
@@ -71,6 +140,26 @@ function businessFailure(body) {
71
140
  return ""
72
141
  }
73
142
 
143
+ function contentValue(value) {
144
+ if (typeof value === "string") return value
145
+ if (Array.isArray(value)) return value.map((part) => contentValue(part?.text)).join("")
146
+ return ""
147
+ }
148
+
149
+ function payloadReply(payload) {
150
+ const choices = Array.isArray(payload?.choices) ? payload.choices : []
151
+ for (const choice of choices) {
152
+ if (!object(choice)) continue
153
+ const value = choice.delta?.content ?? choice.message?.content ?? choice.text
154
+ const content = contentValue(value)
155
+ if (content) return content
156
+ }
157
+ const blocks = Array.isArray(payload?.content) ? payload.content : []
158
+ const blockText = blocks.map((block) => contentValue(block?.text)).join("")
159
+ if (blockText) return blockText
160
+ return contentValue(payload?.output_text)
161
+ }
162
+
74
163
  export function responseMessage(raw) {
75
164
  const body = parsedJson(raw)
76
165
  if (object(body)) {
@@ -107,9 +196,10 @@ export function assistantReply(raw) {
107
196
  choice.delta?.content ??
108
197
  choice.message?.content ??
109
198
  choice.text
110
- if (typeof value === "string" && value) {
199
+ const piece = contentValue(value)
200
+ if (piece) {
111
201
  sawReply = true
112
- content += value
202
+ content += piece
113
203
  }
114
204
  }
115
205
  }
@@ -121,7 +211,7 @@ export function assistantReply(raw) {
121
211
  }
122
212
  }
123
213
 
124
- export function workbuddyProbeResult(response, raw) {
214
+ export function workbuddyProbeResult(response, raw, { probeMode = "short" } = {}) {
125
215
  const source = text(raw)
126
216
  const body = source.trim()
127
217
  const contentType = text(response?.headers?.get?.("content-type")).toLowerCase()
@@ -153,13 +243,37 @@ export function workbuddyProbeResult(response, raw) {
153
243
  }
154
244
  }
155
245
 
156
- // WorkBuddy can accept a real conversation without emitting assistant text:
157
- // max_tokens is one and the signed gateway may finish with only [DONE].
158
- // dsh treats HTTP 2xx as a successful probe for that reason.
159
246
  const reply = assistantReply(source)
247
+ let content = reply.content
248
+ let hasReply = reply.ok
249
+ for (const payload of payloads) {
250
+ if (content) break
251
+ const direct = payloadReply(payload)
252
+ if (direct) {
253
+ content = direct
254
+ hasReply = true
255
+ break
256
+ }
257
+ }
258
+ const evidence = usageEvidence(payloads)
259
+ const activity = probeMode === "activity" || probeMode === "context"
260
+ const activityVerified = activity && (hasReply || evidence.sawUsage)
261
+ if (activity && !activityVerified) {
262
+ return {
263
+ ok: false,
264
+ content,
265
+ activityVerified: false,
266
+ error: "WorkBuddy's keepalive chat returned 200 without a model reply or usage evidence; no active conversation was confirmed",
267
+ }
268
+ }
160
269
  return {
161
270
  ok: true,
162
- content: reply.content,
271
+ content: sanitize(content, 120),
272
+ activityVerified,
273
+ ...(evidence.credit === undefined ? {} : { credit: evidence.credit }),
274
+ ...(evidence.promptTokens === undefined ? {} : { promptTokens: evidence.promptTokens }),
275
+ ...(evidence.completionTokens === undefined ? {} : { completionTokens: evidence.completionTokens }),
276
+ ...(evidence.totalTokens === undefined ? {} : { totalTokens: evidence.totalTokens }),
163
277
  }
164
278
  }
165
279
 
@@ -168,7 +282,7 @@ export async function sendKeepalive({
168
282
  origin = ORIGIN,
169
283
  model,
170
284
  inputTokens,
171
- probeMode,
285
+ probeMode = "activity",
172
286
  probeMessage,
173
287
  timeoutMs,
174
288
  day,
@@ -207,12 +321,17 @@ export async function sendKeepalive({
207
321
  error.retryToday = !terminal
208
322
  throw error
209
323
  }
210
- const probe = workbuddyProbeResult(response, raw)
324
+ const probe = workbuddyProbeResult(response, raw, { probeMode })
211
325
  if (!probe.ok) {
212
326
  throw new Error(probe.error)
213
327
  }
214
328
  return {
215
329
  model,
216
330
  reply: probe.content,
331
+ activityVerified: probe.activityVerified,
332
+ ...(probe.credit === undefined ? {} : { credit: probe.credit }),
333
+ ...(probe.promptTokens === undefined ? {} : { promptTokens: probe.promptTokens }),
334
+ ...(probe.completionTokens === undefined ? {} : { completionTokens: probe.completionTokens }),
335
+ ...(probe.totalTokens === undefined ? {} : { totalTokens: probe.totalTokens }),
217
336
  }
218
337
  }
package/src/log.js CHANGED
@@ -230,6 +230,7 @@ export function formatLogRecord(record) {
230
230
  ["probeMode", record.probeMode],
231
231
  ["inputTokens", record.inputTokens],
232
232
  ["credit", record.credit],
233
+ ["activityVerified", record.activityVerified],
233
234
  ["streak", record.streak],
234
235
  ["durationMs", record.durationMs],
235
236
  ["retry", record.retry],
@@ -56,7 +56,7 @@ export function anthropicBody({
56
56
  const message = sanitize(probeMessage, 500).trim() || DEFAULT_PROBE_MESSAGE
57
57
  const prompt = `${message} Today is ${day}.`
58
58
  let content = prompt
59
- if (probeMode === "context") {
59
+ if (probeMode === "context" || probeMode === "activity") {
60
60
  const targetChars = Math.ceil(inputTokens * CHARS_PER_TOKEN)
61
61
  const filler = FILLER.repeat(Math.ceil(targetChars / FILLER.length))
62
62
  content = `${filler}${prompt}`
package/src/snapshot.js CHANGED
@@ -53,7 +53,7 @@ export function publicRecord(record) {
53
53
  if (value) out[name] = value
54
54
  }
55
55
  if (out.account && !ACCOUNT.test(out.account)) delete out.account
56
- for (const name of ["attempts", "inputTokens", "credit", "streak", "durationMs", "retry", "persisted", "keepalive"]) {
56
+ for (const name of ["attempts", "inputTokens", "credit", "streak", "durationMs", "retry", "persisted", "keepalive", "activityVerified"]) {
57
57
  const value = record?.[name]
58
58
  if (value !== undefined && value !== null && (typeof value === "number" || typeof value === "boolean")) {
59
59
  out[name] = value
package/src/workbuddy.js CHANGED
@@ -267,7 +267,8 @@ export function authIdentity(auth, key) {
267
267
  }
268
268
 
269
269
  export function resultFields(result, config, model = "") {
270
- const inputTokens = config?.probeMode === "context" ? Number(config.inputTokens) || 0 : 0
270
+ const longProbe = config?.probeMode === "context" || config?.probeMode === "activity"
271
+ const configuredInputTokens = longProbe ? Number(config.inputTokens) || 0 : 0
271
272
  switch (result?.kind) {
272
273
  case "skipped":
273
274
  if (result.reason === "done-today") {
@@ -313,6 +314,8 @@ export function resultFields(result, config, model = "") {
313
314
  }
314
315
  case "sent":
315
316
  const terminal = result.cause?.retryToday === false
317
+ const reply = result.result || {}
318
+ const inputTokens = Number(reply.promptTokens) || configuredInputTokens
316
319
  return {
317
320
  status: result.ok ? "success" : "failed",
318
321
  outcome: result.ok
@@ -323,8 +326,11 @@ export function resultFields(result, config, model = "") {
323
326
  ? "retry"
324
327
  : "exhausted",
325
328
  attempts: result.attempts,
326
- model: result.result?.model || model,
329
+ model: reply.model || model,
330
+ probeMode: config?.probeMode,
327
331
  inputTokens,
332
+ ...(reply.credit === undefined ? {} : { credit: reply.credit }),
333
+ ...(reply.activityVerified === undefined ? {} : { activityVerified: reply.activityVerified }),
328
334
  retry: !result.ok && !terminal && result.attempts < config.maxAttempts,
329
335
  persisted: result.persisted !== false,
330
336
  error: result.error,
@@ -528,17 +534,21 @@ export function createProviderKeepalive(options = {}) {
528
534
  run,
529
535
  model: candidates[0].id,
530
536
  models: candidates.map((candidate) => candidate.id).join(","),
531
- inputTokens: config.probeMode === "context" ? config.inputTokens : 0,
537
+ inputTokens: ["context", "activity"].includes(config.probeMode) ? config.inputTokens : 0,
532
538
  probeMode: config.probeMode,
533
539
  keepalive: true,
534
540
  })
535
541
 
542
+ const activityMode = config.probeMode === "short" ? "short" : "activity"
536
543
  const result = await runDailyOnce({
537
544
  statePath: config.statePath,
538
- account: accountTag(`${provider}|${identity}`),
545
+ // A legacy short probe and a real activity conversation are not the
546
+ // same task. Keeping them separate lets a user switch to activity
547
+ // after a short probe has already been recorded today.
548
+ account: accountTag(`${provider}|${identity}|keepalive:${activityMode}`),
539
549
  day,
540
550
  model: candidates[0].id,
541
- inputTokens: config.probeMode === "context" ? config.inputTokens : 0,
551
+ inputTokens: ["context", "activity"].includes(config.probeMode) ? config.inputTokens : 0,
542
552
  probeMode: config.probeMode,
543
553
  maxAttempts: config.maxAttempts,
544
554
  lockWaitMs: config.lockWaitMs,
package/ui/app.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const ACTIONS = {
4
4
  detect: { title: "检测账号", detail: "正在读取 Magpie 当前已登录的供应商与账号。" },
5
5
  checkin: { title: "全部签到", detail: "正在让已有签到能力的供应商执行签到。" },
6
- keepalive: { title: "发送保活", detail: "正在为全部已适配供应商账号发送真实对话。" },
6
+ keepalive: { title: "发送保活", detail: "正在为全部已适配供应商账号发送真实活跃对话;会消耗额度。" },
7
7
  refresh: { title: "刷新额度", detail: "正在刷新全部启用供应商的实时额度。" },
8
8
  }
9
9
  const EVENT_NAMES = {
@@ -995,9 +995,12 @@
995
995
  ["供应商", record?.provider],
996
996
  ["账号", record?.account],
997
997
  ["模型", record?.model],
998
- ["保活方式", record?.probeMode === "context" ? "长上下文" : record?.probeMode === "short" ? "短探活" : ""],
998
+ ["保活方式", record?.probeMode === "activity" || record?.probeMode === "context"
999
+ ? "真实活跃对话"
1000
+ : record?.probeMode === "short" ? "短探活" : ""],
999
1001
  ["输入", number(record?.inputTokens) ? `${formatNumber(record.inputTokens)} tokens` : ""],
1000
1002
  ["本次积分", number(record?.credit) ? `+${formatNumber(record.credit)}` : ""],
1003
+ ["活跃确认", record?.activityVerified === true ? "已验证" : ""],
1001
1004
  ["连续签到", number(record?.streak) ? `${formatNumber(record.streak)} 天` : ""],
1002
1005
  ["耗时", durationText(record?.durationMs)],
1003
1006
  ["日期", record?.day],
@@ -1192,7 +1195,9 @@
1192
1195
  keepalive: "真实保活",
1193
1196
  all: "签到 + 真实保活",
1194
1197
  }[options.scheduleAction] || "签到"
1195
- const probe = options.probeMode === "context" ? "长上下文保活" : "短探活"
1198
+ const probe = options.probeMode === "activity" || options.probeMode === "context"
1199
+ ? "真实活跃对话(会消耗积分)"
1200
+ : "短探活(不保证活跃记录)"
1196
1201
  return `每 ${number(options.scheduleMinutes) || 60} 分钟执行一次${action};${probe}。`
1197
1202
  }
1198
1203
 
@@ -1211,7 +1216,7 @@
1211
1216
  $("schedule-action").value = ["checkin", "keepalive", "all"].includes(options.scheduleAction)
1212
1217
  ? options.scheduleAction
1213
1218
  : "checkin"
1214
- $("probe-mode").value = options.probeMode === "context" ? "context" : "short"
1219
+ $("probe-mode").value = options.probeMode === "short" ? "short" : "activity"
1215
1220
  $("schedule-status").textContent = scheduleDescription(options)
1216
1221
  if (showToast) toast("定时计划已保存")
1217
1222
  } catch (error) {
package/ui/index.html CHANGED
@@ -112,10 +112,10 @@
112
112
  </svg>
113
113
  </label>
114
114
  <label class="select-field probe-field"
115
- title="短探活只发送一条短消息;长上下文会填充到配置的输入规模">
115
+ title="真实活跃对话会发送约 25k 输入并消耗积分;短探活只做低成本连通性检查">
116
116
  <select id="probe-mode" aria-label="保活方式">
117
+ <option value="activity">真实活跃对话</option>
117
118
  <option value="short">短探活</option>
118
- <option value="context">长上下文</option>
119
119
  </select>
120
120
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"
121
121
  stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
@@ -167,7 +167,7 @@
167
167
  <section class="confirm-card" role="dialog" aria-modal="true" aria-labelledby="confirm-title">
168
168
  <p class="eyebrow">REAL REQUEST</p>
169
169
  <h2 id="confirm-title">发送真实保活消息?</h2>
170
- <p id="confirm-detail">这会为全部已适配供应商的账号发送真实对话,优先尝试近期验证可用且能成功回复的模型;仍可能消耗积分。</p>
170
+ <p id="confirm-detail">这会为全部已适配供应商的账号发送真实活跃对话,优先尝试近期验证可用且能成功回复的模型;请求可能消耗积分。</p>
171
171
  <div class="confirm-actions">
172
172
  <button class="button" id="confirm-cancel" type="button">取消</button>
173
173
  <button class="button caution" id="confirm-send" type="button">确认发送</button>
@@ -1 +1 @@
1
- <svg fill="#12B886" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>opencode</title><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"></path></svg>
1
+ <svg fill="#0B8F63" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>opencode</title><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"></path></svg>
@@ -1 +1 @@
1
- <svg fill="#F56A00" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qoder</title><path d="M23.376 14.458v-4.056c0-2.304-1.003-4.154-2.748-5.075L11.612.574l-.046.086-.045.086c1.68.886 2.644 2.673 2.644 4.902v4.056a7.928 7.928 0 01-.014.454l-.005.061c-.005.081-.01.164-.018.245a4.897 4.897 0 01-.011.1l-.01.076c-.008.068-.015.135-.025.203l-.018.113-.01.058a9.99 9.99 0 01-.098.513l-.007.03a7.209 7.209 0 01-.074.294l-.024.086c-.027.099-.056.197-.087.296l-.027.085a9.592 9.592 0 01-.111.323l-.033.085-.018.046c-.032.082-.064.166-.098.248-.019.048-.04.096-.061.145l-.007.017a6 6 0 01-.084.187c-.024.056-.05.11-.077.165-.03.061-.058.122-.089.182a9.423 9.423 0 01-.176.332c-.03.056-.062.111-.094.167-.031.053-.062.108-.095.16-.033.055-.066.11-.101.164-.033.053-.065.104-.1.155-.034.055-.07.107-.111.169l-.099.144a15.193 15.193 0 01-.34.457c-.04.05-.08.102-.121.151l-.107.128-.007.008a6.987 6.987 0 01-.262.298l-.149.16-.116.12a9.562 9.562 0 01-.204.198l-.03.03-.072.069a9.05 9.05 0 01-.263.235l-.025.022-.029.026-.042.035a11.7 11.7 0 01-.22.18l-.07.055-.018.013a8.904 8.904 0 01-.194.146c-.029.02-.057.042-.086.063a7.7 7.7 0 01-.22.152l-.057.04a8.865 8.865 0 01-.293.185l-.062.037a10.424 10.424 0 01-.307.173l-.037.02-.196.103-.108.052-.012.006a6.196 6.196 0 01-.315.143c-.065.028-.13.054-.196.08l-.035.014-.086.034c-.07.026-.143.05-.215.075l-.039.014-.064.023a8.056 8.056 0 01-.323.097l-.63.173a7.285 7.285 0 01-.33.08l-.07.015c-.053.012-.104.023-.157.032l-.065.011-.085.015a2.332 2.332 0 01-.194.027l-.085.01a4.715 4.715 0 01-.16.018l-.034.003a4.861 4.861 0 01-.246.016h-.033a2.714 2.714 0 01-.155.005h-.106a3.384 3.384 0 01-.225-.007H4.86l-.15-.012-.066-.006a5.586 5.586 0 01-.187-.02l-.04-.005a5.14 5.14 0 01-.219-.035l-.054-.01a6.943 6.943 0 01-.347-.082l-.03-.008-.038-.01a5.034 5.034 0 01-.269-.086l-.063-.023a4.216 4.216 0 01-.188-.073l-.071-.031-.016-.007a4.959 4.959 0 01-.16-.074l-.026-.013a.164.164 0 00-.014-.007l-.671-.351.486.486h.016l8.995 4.742.093.048.03.014.02.01c.056.026.111.052.169.076l.016.008.073.032.195.076.022.008a.718.718 0 01.03.012l.014.004a4.693 4.693 0 00.323.1l.027.007c.073.02.147.038.22.055l.018.004.027.006.066.013.088.016c.075.014.15.026.226.038l.042.004c.064.009.128.016.193.022l.126.012.06.003.05.002.098.005c.046.002.094.002.14.003h.12c.05 0 .1-.002.161-.005h.033l.07-.004a6.17 6.17 0 00.184-.014l.033-.003a.753.753 0 00.058-.005l.108-.012.081-.01.08-.01.129-.021.082-.014.071-.012c.054-.01.107-.021.16-.033l.066-.013.059-.013c.096-.022.192-.046.287-.073l.63-.172a7.354 7.354 0 00.397-.122l.04-.015c.075-.025.149-.051.222-.078.032-.011.062-.024.093-.037l.03-.012c.067-.027.135-.054.2-.082l.128-.056.195-.09.021-.01.102-.05c.068-.034.135-.069.202-.105l.037-.02.073-.038c.08-.044.16-.092.24-.139l.026-.015a8.086 8.086 0 00.322-.2l.065-.045 1.98.902a1.748 1.748 0 002.472-1.59v-7.33l.004.004z" opacity=".5"></path><path d="M11.617.576a3.904 3.904 0 00-.093-.047c-.016-.009-.033-.016-.05-.024a5.854 5.854 0 00-.166-.077l-.09-.04a4.094 4.094 0 00-.194-.074c-.017-.006-.035-.015-.053-.02l-.013-.005a5.18 5.18 0 00-.277-.088l-.07-.019a4.034 4.034 0 00-.219-.053c-.015-.003-.03-.008-.044-.012L10.253.1l-.057-.011a5.177 5.177 0 00-.225-.036L9.928.047a5.972 5.972 0 00-.191-.022L9.669.02a1.33 1.33 0 00-.058-.005L9.515.009a4.058 4.058 0 00-.111-.005C9.354 0 9.304 0 9.254 0h-.109c-.052 0-.106.004-.16.004L8.884.01A5.32 5.32 0 008.7.022l-.083.006h-.008c-.05.005-.1.013-.15.019l-.12.014c-.056.008-.112.018-.169.028-.037.006-.074.011-.11.018a7.054 7.054 0 00-.572.133l-.63.172c-.111.031-.22.064-.33.1-.037.011-.072.025-.108.037a12.91 12.91 0 00-.345.126c-.067.027-.133.054-.2.083l-.128.055a11.916 11.916 0 00-.318.15 7.376 7.376 0 00-.311.163c-.082.045-.16.092-.24.14a9.424 9.424 0 00-.35.218l-.016.01-.06.04a9.242 9.242 0 00-.51.37 12.54 12.54 0 00-.315.254l-.043.034-.01.007-.045.041c-.09.078-.18.158-.268.24l-.106.101c-.07.067-.139.135-.207.204l-.056.054-.063.067-.153.164-.12.133-.148.17c-.024.03-.049.057-.073.086l-.043.053a5.96 5.96 0 00-.123.155l-.118.151c-.04.053-.08.106-.118.16l-.075.101-.038.056-.107.155-.11.164a9.91 9.91 0 00-.168.265c-.012.02-.023.043-.037.063a12.43 12.43 0 00-.192.335l-.09.168c-.019.034-.04.07-.057.105-.011.022-.02.043-.032.065l-.092.188-.08.167a19.15 19.15 0 00-.083.192c-.017.038-.035.076-.05.115-.008.016-.014.034-.021.051-.035.083-.067.167-.1.253l-.051.134c-.04.11-.077.22-.113.33l-.02.057c0 .002 0 .005-.002.007l-.008.026c-.032.1-.06.2-.09.301l-.023.089c-.027.1-.052.2-.075.301l-.007.03a7.63 7.63 0 00-.057.267l-.008.048c-.015.074-.026.148-.038.223L.082 8.4c-.011.078-.02.156-.029.234-.006.051-.013.103-.017.154a6.57 6.57 0 00-.02.26c-.003.044-.007.086-.009.13-.004.128-.007.257-.007.386v4.056c0 1.478.42 2.741 1.138 3.692A4.75 4.75 0 002.73 18.67l9.015 4.753c-1.656-.874-2.728-2.685-2.73-5.051v-4.056c0-.13.004-.26.01-.39.002-.043.006-.085.01-.128.005-.088.01-.174.019-.261l.017-.155c.01-.077.018-.155.029-.234l.026-.164c.013-.074.025-.15.039-.223.02-.105.04-.21.064-.313l.008-.031c.023-.1.048-.201.075-.301l.023-.088c.028-.1.058-.202.09-.302l.008-.025.021-.063c.036-.11.073-.22.113-.33l.052-.134c.031-.085.065-.169.1-.253.022-.056.047-.111.07-.166a13.856 13.856 0 01.164-.358c.03-.063.06-.126.092-.188l.088-.172a9.22 9.22 0 01.187-.338l.096-.164c.034-.057.069-.112.104-.168l.1-.159a10.49 10.49 0 01.567-.786l.123-.155.116-.139c.05-.057.098-.115.148-.171a14.092 14.092 0 01.272-.297 9.706 9.706 0 01.432-.425c.088-.083.177-.163.268-.241l.054-.048a10.08 10.08 0 01.553-.435l.092-.068c.075-.053.15-.105.226-.156.019-.013.038-.028.06-.04a9.18 9.18 0 01.362-.227c.08-.047.16-.094.241-.139l.11-.058a7.643 7.643 0 01.521-.256l.126-.056a7.509 7.509 0 01.546-.208l.107-.037c.11-.036.22-.069.33-.1l.63-.172c.095-.026.191-.05.287-.072l.097-.02c.062-.014.125-.029.187-.04l.114-.018c.055-.01.11-.02.166-.028.04-.006.08-.01.12-.014.053-.007.105-.014.157-.019l.083-.006c.061-.005.123-.01.184-.013.034-.003.067-.003.101-.004l.16-.006h.11a3.187 3.187 0 01.261.008l.153.01.067.007c.064.006.128.013.192.022l.043.005a5.232 5.232 0 01.281.047l.141.03c.073.016.146.035.218.053l.07.019c.094.026.187.055.278.087l.067.025a4.326 4.326 0 01.449.19l.143.072L11.617.576z"></path></svg>
1
+ <svg fill="#E8590C" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qoder</title><path d="M23.376 14.458v-4.056c0-2.304-1.003-4.154-2.748-5.075L11.612.574l-.046.086-.045.086c1.68.886 2.644 2.673 2.644 4.902v4.056a7.928 7.928 0 01-.014.454l-.005.061c-.005.081-.01.164-.018.245a4.897 4.897 0 01-.011.1l-.01.076c-.008.068-.015.135-.025.203l-.018.113-.01.058a9.99 9.99 0 01-.098.513l-.007.03a7.209 7.209 0 01-.074.294l-.024.086c-.027.099-.056.197-.087.296l-.027.085a9.592 9.592 0 01-.111.323l-.033.085-.018.046c-.032.082-.064.166-.098.248-.019.048-.04.096-.061.145l-.007.017a6 6 0 01-.084.187c-.024.056-.05.11-.077.165-.03.061-.058.122-.089.182a9.423 9.423 0 01-.176.332c-.03.056-.062.111-.094.167-.031.053-.062.108-.095.16-.033.055-.066.11-.101.164-.033.053-.065.104-.1.155-.034.055-.07.107-.111.169l-.099.144a15.193 15.193 0 01-.34.457c-.04.05-.08.102-.121.151l-.107.128-.007.008a6.987 6.987 0 01-.262.298l-.149.16-.116.12a9.562 9.562 0 01-.204.198l-.03.03-.072.069a9.05 9.05 0 01-.263.235l-.025.022-.029.026-.042.035a11.7 11.7 0 01-.22.18l-.07.055-.018.013a8.904 8.904 0 01-.194.146c-.029.02-.057.042-.086.063a7.7 7.7 0 01-.22.152l-.057.04a8.865 8.865 0 01-.293.185l-.062.037a10.424 10.424 0 01-.307.173l-.037.02-.196.103-.108.052-.012.006a6.196 6.196 0 01-.315.143c-.065.028-.13.054-.196.08l-.035.014-.086.034c-.07.026-.143.05-.215.075l-.039.014-.064.023a8.056 8.056 0 01-.323.097l-.63.173a7.285 7.285 0 01-.33.08l-.07.015c-.053.012-.104.023-.157.032l-.065.011-.085.015a2.332 2.332 0 01-.194.027l-.085.01a4.715 4.715 0 01-.16.018l-.034.003a4.861 4.861 0 01-.246.016h-.033a2.714 2.714 0 01-.155.005h-.106a3.384 3.384 0 01-.225-.007H4.86l-.15-.012-.066-.006a5.586 5.586 0 01-.187-.02l-.04-.005a5.14 5.14 0 01-.219-.035l-.054-.01a6.943 6.943 0 01-.347-.082l-.03-.008-.038-.01a5.034 5.034 0 01-.269-.086l-.063-.023a4.216 4.216 0 01-.188-.073l-.071-.031-.016-.007a4.959 4.959 0 01-.16-.074l-.026-.013a.164.164 0 00-.014-.007l-.671-.351.486.486h.016l8.995 4.742.093.048.03.014.02.01c.056.026.111.052.169.076l.016.008.073.032.195.076.022.008a.718.718 0 01.03.012l.014.004a4.693 4.693 0 00.323.1l.027.007c.073.02.147.038.22.055l.018.004.027.006.066.013.088.016c.075.014.15.026.226.038l.042.004c.064.009.128.016.193.022l.126.012.06.003.05.002.098.005c.046.002.094.002.14.003h.12c.05 0 .1-.002.161-.005h.033l.07-.004a6.17 6.17 0 00.184-.014l.033-.003a.753.753 0 00.058-.005l.108-.012.081-.01.08-.01.129-.021.082-.014.071-.012c.054-.01.107-.021.16-.033l.066-.013.059-.013c.096-.022.192-.046.287-.073l.63-.172a7.354 7.354 0 00.397-.122l.04-.015c.075-.025.149-.051.222-.078.032-.011.062-.024.093-.037l.03-.012c.067-.027.135-.054.2-.082l.128-.056.195-.09.021-.01.102-.05c.068-.034.135-.069.202-.105l.037-.02.073-.038c.08-.044.16-.092.24-.139l.026-.015a8.086 8.086 0 00.322-.2l.065-.045 1.98.902a1.748 1.748 0 002.472-1.59v-7.33l.004.004z" opacity=".5"></path><path d="M11.617.576a3.904 3.904 0 00-.093-.047c-.016-.009-.033-.016-.05-.024a5.854 5.854 0 00-.166-.077l-.09-.04a4.094 4.094 0 00-.194-.074c-.017-.006-.035-.015-.053-.02l-.013-.005a5.18 5.18 0 00-.277-.088l-.07-.019a4.034 4.034 0 00-.219-.053c-.015-.003-.03-.008-.044-.012L10.253.1l-.057-.011a5.177 5.177 0 00-.225-.036L9.928.047a5.972 5.972 0 00-.191-.022L9.669.02a1.33 1.33 0 00-.058-.005L9.515.009a4.058 4.058 0 00-.111-.005C9.354 0 9.304 0 9.254 0h-.109c-.052 0-.106.004-.16.004L8.884.01A5.32 5.32 0 008.7.022l-.083.006h-.008c-.05.005-.1.013-.15.019l-.12.014c-.056.008-.112.018-.169.028-.037.006-.074.011-.11.018a7.054 7.054 0 00-.572.133l-.63.172c-.111.031-.22.064-.33.1-.037.011-.072.025-.108.037a12.91 12.91 0 00-.345.126c-.067.027-.133.054-.2.083l-.128.055a11.916 11.916 0 00-.318.15 7.376 7.376 0 00-.311.163c-.082.045-.16.092-.24.14a9.424 9.424 0 00-.35.218l-.016.01-.06.04a9.242 9.242 0 00-.51.37 12.54 12.54 0 00-.315.254l-.043.034-.01.007-.045.041c-.09.078-.18.158-.268.24l-.106.101c-.07.067-.139.135-.207.204l-.056.054-.063.067-.153.164-.12.133-.148.17c-.024.03-.049.057-.073.086l-.043.053a5.96 5.96 0 00-.123.155l-.118.151c-.04.053-.08.106-.118.16l-.075.101-.038.056-.107.155-.11.164a9.91 9.91 0 00-.168.265c-.012.02-.023.043-.037.063a12.43 12.43 0 00-.192.335l-.09.168c-.019.034-.04.07-.057.105-.011.022-.02.043-.032.065l-.092.188-.08.167a19.15 19.15 0 00-.083.192c-.017.038-.035.076-.05.115-.008.016-.014.034-.021.051-.035.083-.067.167-.1.253l-.051.134c-.04.11-.077.22-.113.33l-.02.057c0 .002 0 .005-.002.007l-.008.026c-.032.1-.06.2-.09.301l-.023.089c-.027.1-.052.2-.075.301l-.007.03a7.63 7.63 0 00-.057.267l-.008.048c-.015.074-.026.148-.038.223L.082 8.4c-.011.078-.02.156-.029.234-.006.051-.013.103-.017.154a6.57 6.57 0 00-.02.26c-.003.044-.007.086-.009.13-.004.128-.007.257-.007.386v4.056c0 1.478.42 2.741 1.138 3.692A4.75 4.75 0 002.73 18.67l9.015 4.753c-1.656-.874-2.728-2.685-2.73-5.051v-4.056c0-.13.004-.26.01-.39.002-.043.006-.085.01-.128.005-.088.01-.174.019-.261l.017-.155c.01-.077.018-.155.029-.234l.026-.164c.013-.074.025-.15.039-.223.02-.105.04-.21.064-.313l.008-.031c.023-.1.048-.201.075-.301l.023-.088c.028-.1.058-.202.09-.302l.008-.025.021-.063c.036-.11.073-.22.113-.33l.052-.134c.031-.085.065-.169.1-.253.022-.056.047-.111.07-.166a13.856 13.856 0 01.164-.358c.03-.063.06-.126.092-.188l.088-.172a9.22 9.22 0 01.187-.338l.096-.164c.034-.057.069-.112.104-.168l.1-.159a10.49 10.49 0 01.567-.786l.123-.155.116-.139c.05-.057.098-.115.148-.171a14.092 14.092 0 01.272-.297 9.706 9.706 0 01.432-.425c.088-.083.177-.163.268-.241l.054-.048a10.08 10.08 0 01.553-.435l.092-.068c.075-.053.15-.105.226-.156.019-.013.038-.028.06-.04a9.18 9.18 0 01.362-.227c.08-.047.16-.094.241-.139l.11-.058a7.643 7.643 0 01.521-.256l.126-.056a7.509 7.509 0 01.546-.208l.107-.037c.11-.036.22-.069.33-.1l.63-.172c.095-.026.191-.05.287-.072l.097-.02c.062-.014.125-.029.187-.04l.114-.018c.055-.01.11-.02.166-.028.04-.006.08-.01.12-.014.053-.007.105-.014.157-.019l.083-.006c.061-.005.123-.01.184-.013.034-.003.067-.003.101-.004l.16-.006h.11a3.187 3.187 0 01.261.008l.153.01.067.007c.064.006.128.013.192.022l.043.005a5.232 5.232 0 01.281.047l.141.03c.073.016.146.035.218.053l.07.019c.094.026.187.055.278.087l.067.025a4.326 4.326 0 01.449.19l.143.072L11.617.576z"></path></svg>
@@ -1 +1 @@
1
- <svg fill="#00B8A9" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>TRAE</title><path d="M24 20.541H3.428v-3.426H0V3.4h24V20.54zM3.428 17.115h17.144V6.827H3.428v10.288zm8.573-5.196l-2.425 2.424-2.424-2.424 2.424-2.424 2.425 2.424zm6.857-.001l-2.424 2.423-2.425-2.423 2.425-2.425 2.424 2.425z"></path></svg>
1
+ <svg fill="#008B80" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>TRAE</title><path d="M24 20.541H3.428v-3.426H0V3.4h24V20.54zM3.428 17.115h17.144V6.827H3.428v10.288zm8.573-5.196l-2.425 2.424-2.424-2.424 2.424-2.424 2.425 2.424zm6.857-.001l-2.424 2.423-2.425-2.423 2.425-2.425 2.424 2.425z"></path></svg>