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.
- package/README.ko.md +9 -1
- package/README.md +9 -1
- package/assets/android/zigmini.apk +0 -0
- package/assets/android/zigmini.apk.json +3 -3
- package/assets/build.json +1 -1
- package/docs/manual.ko.md +6 -1
- package/docs/manual.md +6 -1
- package/host/omo-check/patches/omo-5.1.51-chatgpt-terra.patch +21 -0
- package/host/omo-check/patches/omo-5.1.51-gateway-port.manifest.json +19 -3
- package/package.json +1 -1
- package/src/engine/compat.json +1 -1
- package/src/main.ts +2 -1
- package/src/profile.ts +13 -5
- package/src/usage.ts +18 -7
- package/web/DESIGN.md +2 -1
- package/web/profile.js +5 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
-
"revision":
|
|
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.
|
|
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
package/src/engine/compat.json
CHANGED
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
|
-
/**
|
|
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
|
|
44
|
+
/** The agent name as saved: whitespace collapsed and trimmed; "" for blank. */
|
|
41
45
|
function ownAgentName(value: string): string {
|
|
42
|
-
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
296
|
-
|
|
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 "지그"
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
74
|
+
const name = normalizeAgentName(value);
|
|
75
|
+
storage.setItem(AGENT_NAME_KEY, name);
|
|
76
|
+
return name;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|