zigmini 1.0.0-beta.2 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schema": 1,
3
- "revision": 3,
3
+ "revision": 4,
4
4
  "upstream": {
5
5
  "omo": "5.1.51",
6
6
  "senpi": "2026.10.27-1"
7
7
  },
8
- "candidate": "<zigmini home>/omo/5.1.51-zgw.3",
8
+ "candidate": "<zigmini home>/omo/5.1.51-zgw.4",
9
9
  "pristine": "<zigmini home>/omo/5.1.51",
10
10
  "entrypoint": "node_modules/@code-yeongyu/senpi/dist/cli.js",
11
11
  "patchRoot": "node_modules",
@@ -16,7 +16,8 @@
16
16
  "omo-5.1.51-runtime-compat.patch",
17
17
  "omo-5.1.51-compaction-recovery.patch",
18
18
  "omo-5.1.51-provider-failover.patch",
19
- "omo-5.1.51-compaction-long-context.patch"
19
+ "omo-5.1.51-compaction-long-context.patch",
20
+ "omo-5.1.51-chatgpt-terra.patch"
20
21
  ],
21
22
  "files": [
22
23
  {
@@ -358,6 +359,21 @@
358
359
  "path": "node_modules/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/failover.js",
359
360
  "originalSha256": "bcc9dbb3aaafbbd2840d95635c00d31d062ff1b61450ec039042873c71f8f51e",
360
361
  "patchedSha256": "007bac87475fff79afd3070c023e668747399ae30156b4bf8db9e2bdab9c2d98"
362
+ },
363
+ {
364
+ "path": "node_modules/@earendil-works/pi-ai/dist/providers/data/chatgpt-subscription.json",
365
+ "originalSha256": "42b88c93d717b2a7469b71da7da5adf87347d0d9cc0f264b39117f0f940b51c0",
366
+ "patchedSha256": "2058226d5808194448c8e301a2c265238a608d0a97b6410672aff7f91bbfcf73"
367
+ },
368
+ {
369
+ "path": "node_modules/@code-yeongyu/senpi/dist/bundle/chunks/chunk-ZAS4NBVK.js",
370
+ "originalSha256": "ecb242933351a6c2535cb4283ed6fb7da2174b9326915fc82a2a5b6a0c162794",
371
+ "patchedSha256": "c3c4345fecec02a1070f5658e66547d0bb4f2199fa4f9b3eb0cdaa278986f2ac"
372
+ },
373
+ {
374
+ "path": "node_modules/@code-yeongyu/senpi/dist/bundle/chunks/session-worker.js",
375
+ "originalSha256": "8cd4a903ea9243667f63b131b9c3cb87ca3d9e1b46f8e8d54cfa2a11add853b2",
376
+ "patchedSha256": "cf6a2a7e06aef03bf8fdc3583578e66c0e0f35951bf6a373107f9e375e00bbd5"
361
377
  }
362
378
  ]
363
379
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigmini",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zigmini": "bin/zigmini"
@@ -2,5 +2,5 @@
2
2
  "omo": "5.1.51",
3
3
  "senpi": "2026.10.27-1",
4
4
  "threadSchema": 10,
5
- "verifiedAt": "2026-10-31"
5
+ "verifiedAt": "2026-11-03"
6
6
  }
package/src/main.ts CHANGED
@@ -39,7 +39,7 @@ import { CHAT_TOPIC, showsThinking, THINKING_TOPIC, topicOf } from "./client-top
39
39
  import { APK_DIR, APK_PATH, APP_DOWNLOAD_LINK_PATH, APP_VERSION_PATH, ApkSource, appVersion, createDownloadLink, DownloadLinks, serveApk } from "./app-download.ts";
40
40
  import { loadConfig } from "./config.ts";
41
41
  import { memoUpdatedData, parseMemoPatch } from "./memo-images.ts";
42
- import { readProfile, saveProfile } from "./profile.ts";
42
+ import { fixAgentName, readProfile, saveProfile } from "./profile.ts";
43
43
  import { handleProfileIcon, PROFILE_ICON_PATH, ProfileIcon, profileUpdated } from "./profile-icon.ts";
44
44
  import { Hub, type ScopedQuestionFrame } from "./hub.ts";
45
45
  import { parseSummaryModels, SpeechSummarizer } from "./speech-summary.ts";
@@ -97,6 +97,7 @@ const gcRoots: GcRoots = { kind: installKind(), home: gcHome.home, backups: join
97
97
  const gcDeps = systemGcDeps(join(import.meta.dir, ".."), { ...process.env, ZIGMINI_HOME: gcHome.home });
98
98
  const store = new Store(config.dataDir);
99
99
  const GATEWAY_ID = gatewayId(store);
100
+ fixAgentName(store);
100
101
  const GATEWAY_VERSION = (JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string }).version;
101
102
  const securityBaseline = new SecurityBaseline(store, { secrets: runtime.secrets, gatewayVersion: GATEWAY_VERSION });
102
103
  // Improvement 250: formal verification switches; installs run in the gateway process and report through this event.
package/src/profile.ts CHANGED
@@ -4,7 +4,11 @@ import { isTextOf, say, type Text } from "./server-text.ts";
4
4
  import type { Store } from "./store.ts";
5
5
 
6
6
  export const NICKNAME_SETTING = "profile.nickname";
7
- /** Built-in default names. They follow the saved UI language; a stored built-in default of either language reads as the current one. */
7
+ /**
8
+ * Built-in default names. The nickname default follows the saved UI language; a stored built-in nickname of either
9
+ * language reads as the current one. The agent name is not translated (bug 271): fixAgentName stores the default once
10
+ * at startup and every saved agent name, "Zig" and "지그" included, stays as written in every language.
11
+ */
8
12
  export const NICKNAME_DEFAULT: Text = { en: "Me", ko: "나" };
9
13
  export const NICKNAME_MAX = 20;
10
14
 
@@ -37,10 +41,9 @@ function ownNickname(value: string): string {
37
41
  return isTextOf(name, NICKNAME_DEFAULT) ? "" : name;
38
42
  }
39
43
 
40
- /** The user's own agent name: whitespace collapsed and trimmed; "" for blank or a built-in default. */
44
+ /** The agent name as saved: whitespace collapsed and trimmed; "" for blank. */
41
45
  function ownAgentName(value: string): string {
42
- const name = value.replace(/\s+/g, " ").trim();
43
- return isTextOf(name, AGENT_NAME_DEFAULT) ? "" : name;
46
+ return value.replace(/\s+/g, " ").trim();
44
47
  }
45
48
 
46
49
  /** The nickname to show: the user's own, else the current language's default. */
@@ -83,13 +86,18 @@ export function readProfile(store: Pick<Store, "getSetting">): Profile {
83
86
  * Saves the `nickname` and/or `agentName` of a PUT body and returns the whole profile. Nothing is written unless every
84
87
  * given field is valid. A body with neither field is `nickname_required`, the error older clients already know.
85
88
  */
89
+ /** Gateway startup: an install without a saved agent name stores the default of the current language once. */
90
+ export function fixAgentName(store: Pick<Store, "getSetting" | "setSetting">): void {
91
+ if (!ownAgentName(store.getSetting(AGENT_NAME_SETTING) ?? "")) store.setSetting(AGENT_NAME_SETTING, defaultAgentName());
92
+ }
93
+
86
94
  export function saveProfile(store: Pick<Store, "getSetting" | "setSetting">, body: unknown): ProfileSave {
87
95
  if (typeof body !== "object" || body === null) return { error: "nickname_required" };
88
96
  const { nickname, agentName } = body as { nickname?: unknown; agentName?: unknown };
89
97
  if (nickname === undefined && agentName === undefined) return { error: "nickname_required" };
90
98
  if (nickname !== undefined && typeof nickname !== "string") return { error: "nickname_required" };
91
99
  if (agentName !== undefined && typeof agentName !== "string") return { error: "agent_name_required" };
92
- const name = agentName === undefined ? undefined : ownAgentName(agentName);
100
+ const name = agentName === undefined ? undefined : normalizeAgentName(agentName);
93
101
  if (name !== undefined && displayWidth(name) > AGENT_NAME_WIDTH) return { error: "agent_name_too_long" };
94
102
  if (nickname !== undefined) store.setSetting(NICKNAME_SETTING, ownNickname(nickname));
95
103
  if (name !== undefined) store.setSetting(AGENT_NAME_SETTING, name);
package/src/usage.ts CHANGED
@@ -26,6 +26,7 @@ const USAGE_TEXT = {
26
26
  reset: { en: " (resets {time})", ko: " (초기화 {time})" },
27
27
  lastUsed: { en: " · Last used {time}", ko: " · 최근 사용 {time}" },
28
28
  failed: { en: "Lookup failed ({detail})", ko: "조회 실패 ({detail})" },
29
+ limited: { en: " · Limit reached ({detail})", ko: " · 한도 도달 ({detail})" },
29
30
  unknown: { en: "Unknown", ko: "알 수 없음" },
30
31
  expired: { en: "Token expired (the engine will refresh it on next use)", ko: "토큰 만료 (엔진이 다음 사용 때 갱신)" },
31
32
  unsupported: { en: "Usage API unavailable", ko: "사용량 API 없음" },
@@ -69,7 +70,8 @@ export interface UsageRow {
69
70
  readonly provider: string;
70
71
  readonly slot: string;
71
72
  readonly pinned: boolean;
72
- readonly status: "ok" | "failed" | "expired" | "unsupported";
73
+ /** limited: HTTP 429; the windows are the last ones seen for this slot, as omo-usage keeps them. */
74
+ readonly status: "ok" | "limited" | "failed" | "expired" | "unsupported";
73
75
  /** Why a lookup failed: an HTTP status or a fixed phrase, never a server message. */
74
76
  readonly detail?: string;
75
77
  readonly windows: readonly UsageWindow[];
@@ -280,7 +282,7 @@ async function lookup(account: Account, now: number, deps: UsageDeps): Promise<U
280
282
  redirect: "error",
281
283
  signal: AbortSignal.timeout(deps.timeoutMs ?? 10_000),
282
284
  });
283
- if (!response.ok) return { ...base, status: "failed", detail: say(USAGE_TEXT.http, { status: response.status }) };
285
+ if (!response.ok) return { ...base, status: response.status === 429 ? "limited" : "failed", detail: say(USAGE_TEXT.http, { status: response.status }) };
284
286
  const windows = windowsOf(account.kind, await response.json().catch(() => undefined), now);
285
287
  return windows.length > 0 ? { ...base, status: "ok", windows } : { ...base, status: "failed", detail: say(USAGE_TEXT.noUsage) };
286
288
  } catch (error) {
@@ -289,11 +291,18 @@ async function lookup(account: Account, now: number, deps: UsageDeps): Promise<U
289
291
  }
290
292
  }
291
293
 
292
- export async function collectUsage(auth: unknown, poolState: unknown, now: number, deps: UsageDeps = {}): Promise<UsageRow[]> {
294
+ /** `last` holds each slot's newest windows (key provider/name); a 429 shows them, and without them the slot failed. */
295
+ export async function collectUsage(auth: unknown, poolState: unknown, now: number, deps: UsageDeps = {}, last = new Map<string, readonly UsageWindow[]>()): Promise<UsageRow[]> {
293
296
  const used = lastUsed(poolState);
294
297
  return Promise.all(readAccounts(auth).map(async (account) => {
295
- const row = await lookup(account, now, deps);
296
- const at = used.get(`${account.provider}/${account.name}`);
298
+ const key = `${account.provider}/${account.name}`;
299
+ let row = await lookup(account, now, deps);
300
+ if (row.status === "ok") last.set(key, row.windows);
301
+ if (row.status === "limited") {
302
+ const windows = last.get(key);
303
+ row = windows ? { ...row, windows } : { ...row, status: "failed" };
304
+ }
305
+ const at = used.get(key);
297
306
  return at === undefined ? row : { ...row, lastUsedAt: at };
298
307
  }));
299
308
  }
@@ -310,8 +319,9 @@ export function formatUsageReport(rows: readonly UsageRow[], timeZone?: string):
310
319
  for (const row of rows) {
311
320
  const name = say(USAGE_TEXT.account, { slot: row.slot, provider: row.provider, pinned: row.pinned ? say(USAGE_TEXT.pinned) : "" });
312
321
  let body: string;
313
- if (row.status === "ok") {
322
+ if (row.status === "ok" || row.status === "limited") {
314
323
  body = row.windows.map((window) => `${say(USAGE_TEXT.remaining, { label: window.label === "기간" ? say(USAGE_TEXT.period) : window.label, percent: window.remaining })}${window.resetsAt === null ? "" : say(USAGE_TEXT.reset, { time: stamp(window.resetsAt, timeZone) })}`).join(" · ");
324
+ if (row.status === "limited") body += say(USAGE_TEXT.limited, { detail: row.detail ?? say(USAGE_TEXT.unknown) });
315
325
  } else if (row.status === "failed") body = say(USAGE_TEXT.failed, { detail: row.detail ?? say(USAGE_TEXT.unknown) });
316
326
  else body = say(USAGE_TEXT[row.status]);
317
327
  const recent = row.lastUsedAt === undefined ? "" : say(USAGE_TEXT.lastUsed, { time: stamp(row.lastUsedAt, timeZone) });
@@ -340,13 +350,14 @@ export function createUsageReport(deps: UsageDeps & {
340
350
  now?: () => number;
341
351
  timeZone?: string;
342
352
  }): () => Promise<string> {
353
+ const last = new Map<string, readonly UsageWindow[]>();
343
354
  return async () => {
344
355
  if (deps.inContainer) return `${say(USAGE_TEXT.title)}\n${say(USAGE_UNAVAILABLE)}`;
345
356
  const auth = readJson(join(deps.dir, "auth.json"));
346
357
  if ("missing" in auth) return `${say(USAGE_TEXT.title)}\n${say(USAGE_TEXT.emptyAuth)}`;
347
358
  if ("broken" in auth) return `${say(USAGE_TEXT.title)}\n${say(USAGE_TEXT.brokenAuth)}`;
348
359
  const pool = readJson(join(deps.dir, "credential-pool-state.json"));
349
- const rows = await collectUsage(auth.value, "value" in pool ? pool.value : null, (deps.now ?? Date.now)(), deps);
360
+ const rows = await collectUsage(auth.value, "value" in pool ? pool.value : null, (deps.now ?? Date.now)(), deps, last);
350
361
  return formatUsageReport(rows, deps.timeZone);
351
362
  };
352
363
  }
package/web/DESIGN.md CHANGED
@@ -49,7 +49,8 @@ former 72px rail, widened by 200px on 2026-10-04 for the 작업 상태 drawing;
49
49
  below 1280px, 200px at 900px and below). The profile block is split in two halves. The left half shows the owner's nickname (double-click edits it:
50
50
  Enter or leaving saves, Esc cancels); the gateway keeps it (`GET/PUT /api/profile`, `profile.updated`) and the
51
51
  same name labels the user's chat messages, their avatar letter and the 작업 상태 user card. The right half shows the
52
- agent's (gateway main) name with the Zig icon and the same editing (`agentName` in the same API; default "지그"; at most
52
+ agent's (gateway main) name with the Zig icon and the same editing (`agentName` in the same API; the gateway stores the default "지그" or "Zig" of its language once at first start, and the
53
+ name is never translated afterwards (bug 271); at most
53
54
  20 display-width units, Hangul/CJK 2 and ASCII 1; a name too wide stays red and is not saved). The main session reads
54
55
  the name at the start of each turn, and the 작업 상태 hub shows it when WebGL is unavailable. The conversation list keeps at least 140px; the 작업 상태 drawing shrinks before it does.
55
56
 
package/web/profile.js CHANGED
@@ -30,9 +30,9 @@ function ownNickname(value) {
30
30
  return isBuiltIn(name, "profile.nickname.default") ? "" : name;
31
31
  }
32
32
 
33
+ /** The agent name as saved: whitespace collapsed and trimmed; "" for blank. Not translated (bug 271), "Zig" and "지그" included. */
33
34
  function ownAgentName(value) {
34
- const name = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
35
- return isBuiltIn(name, "profile.agent.default") ? "" : name;
35
+ return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
36
36
  }
37
37
 
38
38
  /** The nickname to show: the user's own, else the current language's default. */
@@ -71,8 +71,9 @@ export function readAgentName(storage) {
71
71
  }
72
72
 
73
73
  export function writeAgentName(storage, value) {
74
- storage.setItem(AGENT_NAME_KEY, ownAgentName(value));
75
- return normalizeAgentName(value);
74
+ const name = normalizeAgentName(value);
75
+ storage.setItem(AGENT_NAME_KEY, name);
76
+ return name;
76
77
  }
77
78
 
78
79
  /**