zigmini 0.8.0-beta.6 → 0.9.0-beta.1

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.
Files changed (62) hide show
  1. package/README.ko.md +90 -19
  2. package/README.md +91 -19
  3. package/assets/agent/gateway-request-rules.md +3 -0
  4. package/assets/agent/gateway-rules.md +6 -0
  5. package/assets/android/zigmini.apk +0 -0
  6. package/assets/android/zigmini.apk.json +3 -3
  7. package/assets/build.json +1 -1
  8. package/docs/manual.ko.md +69 -1
  9. package/docs/manual.md +67 -1
  10. package/host/omo-bridge/task-control.ts +4 -0
  11. package/host/omo-check/patches/omo-5.1.35-compaction-idle.patch +77 -0
  12. package/host/omo-check/patches/omo-5.1.35-compaction-intent.patch +212 -0
  13. package/host/omo-check/patches/omo-5.1.35-compaction-model.patch +299 -0
  14. package/host/omo-check/patches/omo-5.1.35-compaction-preservation.patch +1063 -0
  15. package/host/omo-check/patches/omo-5.1.35-compaction-refusal.patch +83 -0
  16. package/host/omo-check/patches/omo-5.1.35-gateway-port.manifest.json +169 -7
  17. package/host/omo-check/patches/omo-5.1.35-request-rules.patch +109 -0
  18. package/host/omo-check/patches/omo-5.1.35-turn-stats.patch +76 -0
  19. package/host/omo-check/patches/omo-5.1.44-compaction-port.patch +2630 -0
  20. package/host/omo-check/patches/omo-5.1.44-gateway-port.manifest.json +329 -0
  21. package/host/omo-check/patches/omo-5.1.44-provider-port.patch +440 -0
  22. package/host/omo-check/patches/omo-5.1.44-rpc-port.patch +778 -0
  23. package/package.json +4 -1
  24. package/scripts/secret-rules.ts +101 -0
  25. package/scripts/secret-scan.allow.json +155 -0
  26. package/scripts/secret-scan.rules.json +30 -0
  27. package/src/agent-request-rules.ts +82 -0
  28. package/src/cli/access.ts +31 -34
  29. package/src/cli/launchd.ts +30 -4
  30. package/src/cli/main.ts +3 -43
  31. package/src/cli/messages.ts +160 -0
  32. package/src/cli/pair.ts +32 -25
  33. package/src/cli/setup.ts +164 -75
  34. package/src/decisions.ts +48 -10
  35. package/src/engine/compat.json +3 -3
  36. package/src/hub.ts +183 -47
  37. package/src/main.ts +60 -4
  38. package/src/notification-history.ts +56 -0
  39. package/src/omo/client.ts +28 -0
  40. package/src/protocol.ts +32 -2
  41. package/src/reply-notifications.ts +144 -0
  42. package/src/runtime/paths.ts +2 -0
  43. package/src/security-allow.ts +126 -0
  44. package/src/security-check.ts +559 -0
  45. package/src/store.ts +236 -6
  46. package/src/turn-stats.ts +49 -0
  47. package/src/work-watch.ts +36 -10
  48. package/src/work.ts +45 -6
  49. package/web/DESIGN.md +39 -0
  50. package/web/app.js +289 -2
  51. package/web/decisions.d.ts +1 -1
  52. package/web/decisions.js +6 -0
  53. package/web/diagram.d.ts +28 -0
  54. package/web/diagram.js +228 -0
  55. package/web/index.html +29 -0
  56. package/web/notifications.d.ts +25 -0
  57. package/web/notifications.js +118 -0
  58. package/web/security-inbox.d.ts +35 -0
  59. package/web/security-inbox.js +237 -0
  60. package/web/style.css +43 -1
  61. package/web/turn-stats.d.ts +26 -0
  62. package/web/turn-stats.js +58 -0
@@ -0,0 +1,299 @@
1
+ --- a/@code-yeongyu/senpi/dist/core/agent-session.js
2
+ +++ b/@code-yeongyu/senpi/dist/core/agent-session.js
3
+ @@ -758,2 +758,2 @@
4
+ - * Unset, malformed, or unknown overrides keep a usable session model.
5
+ - * A spent account instead uses the held target or another authenticated model.
6
+ + * Auto selection stays within the active provider; explicit values never fall back.
7
+ + * Selection uses the availability snapshot. Authentication remains request-time.
8
+ @@ -761,27 +761,50 @@
9
+ - _resolveCompactionModel(sessionModel) {
10
+ - const override = this._getCompactionSettings().model;
11
+ - const slash = override?.indexOf("/") ?? -1;
12
+ - const configured = override && slash > 0
13
+ - ? this._modelRuntime.getModel(override.slice(0, slash), override.slice(slash + 1))
14
+ - : undefined;
15
+ - const usable = (model) => !this._retryFallback.isProviderSpent(model.provider);
16
+ - // Preserve the explicit override and original-model geometry on healthy sessions.
17
+ - // A held downswitch uses its target only when the original account cannot serve.
18
+ - if (usable(sessionModel))
19
+ - return configured && usable(configured) ? configured : sessionModel;
20
+ - const pending = this._pendingModelSwitch?.model;
21
+ - if (pending && usable(pending))
22
+ - return pending;
23
+ - if (configured && usable(configured))
24
+ - return configured;
25
+ - const requiredContextWindow = estimateMessagesTokens(filterContextExcludedMessages(this._runtimeMessages())) +
26
+ - this._getCompactionSettings().reserveTokens;
27
+ - const available = this._modelRuntime
28
+ - .getModels()
29
+ - .find((model) => usable(model) &&
30
+ - model.contextWindow >= requiredContextWindow &&
31
+ - this._modelRuntime.hasConfiguredAuth(model.provider));
32
+ - if (available)
33
+ - return available;
34
+ - throw new AccountUsageLimitError(sessionModel.provider, this._retryFallback.accountError(sessionModel.provider));
35
+ - }
36
+ + _resolveCompactionModel(sessionModel, inputTokens = 0) {
37
+ + const settings = this._getCompactionSettings();
38
+ + const override = settings.model;
39
+ + const explicit = override !== undefined && override !== "auto";
40
+ + const fail = (kind, model = sessionModel) => {
41
+ + throw Object.assign(new Error(
42
+ + explicit ? "Compaction model unavailable; check compaction.model or set it to auto."
43
+ + : "No usable compaction model within the active provider."),
44
+ + { failure: { kind, provider: model.provider, model: model.id } });
45
+ + };
46
+ + const chat = this._modelRuntime.getModels();
47
+ + const available = this._modelRuntime.getAvailableSnapshot();
48
+ + const same = (left, right) => left.provider === right.provider && left.id === right.id;
49
+ + const usable = (model) => chat.some((entry) => same(entry, model)) && (!available.some((entry) => entry.provider === model.provider) || available.some((entry) => same(entry, model))) &&
50
+ + !this._retryFallback.isProviderSpent(model.provider) &&
51
+ + this._modelRuntime.isFallbackEligible(model.provider);
52
+ + const fits = (model) => {
53
+ + const output = Math.min(32768, model.maxTokens > 0 ? model.maxTokens : 32768, Math.floor(model.contextWindow / 2));
54
+ + return model.contextWindow > 0 && inputTokens + Math.max(settings.reserveTokens, output) <= model.contextWindow;
55
+ + };
56
+ + if (explicit) {
57
+ + if (typeof override !== "string" || !/^[^/\s]+\/\S+$/.test(override))
58
+ + return fail("invalid_model");
59
+ + const slash = override.indexOf("/");
60
+ + const configured = this._modelRuntime.getModel(override.slice(0, slash), override.slice(slash + 1));
61
+ + if (!configured || !chat.some((model) => same(model, configured)))
62
+ + return fail("invalid_model");
63
+ + if (this._retryFallback.isProviderSpent(configured.provider))
64
+ + return fail("quota", configured);
65
+ + if (!usable(configured))
66
+ + return fail("model_unavailable", configured);
67
+ + if (!fits(configured))
68
+ + return fail("context_budget", configured);
69
+ + return configured;
70
+ + }
71
+ + if (this._retryFallback.isProviderSpent(sessionModel.provider))
72
+ + return fail("quota");
73
+ + const family = ["anthropic", "anthropic-subscription"].includes(sessionModel.provider) && /^claude-/.test(sessionModel.id) ? /^claude-sonnet-/
74
+ + : ["openai", "chatgpt-subscription"].includes(sessionModel.provider) && /^gpt-/.test(sessionModel.id) ? /^gpt-.*-luna(?:-fast)?$/ : undefined;
75
+ + const preferred = family ? available.filter((model) =>
76
+ + model.provider === sessionModel.provider && family.test(model.id)) : [];
77
+ + // Stable sort preserves registry order within the regular and fast Luna groups.
78
+ + preferred.sort((left, right) => Number(/-luna-fast$/.test(left.id)) - Number(/-luna-fast$/.test(right.id)));
79
+ + const candidates = [...(family?.test(sessionModel.id) ? [sessionModel] : []), ...preferred, sessionModel];
80
+ + for (const candidate of candidates) {
81
+ + if (candidate.provider === sessionModel.provider && usable(candidate) && fits(candidate))
82
+ + return candidate;
83
+ + }
84
+ + return fail(usable(sessionModel) ? "context_budget" : "model_unavailable");
85
+ + }
86
+ @@ -788,7 +811,6 @@
87
+ - async _getCompactionRequestAuth(model, signal, callbacks) {
88
+ - const auth = await this._getSummarizationRequestAuth(model, signal, callbacks);
89
+ - return {
90
+ - ...auth,
91
+ - extraBody: this._modelRuntime.getCompatibilityRequestConfig(model).extraBody,
92
+ - };
93
+ - }
94
+ + async _getCompactionRequestAuth(model, signal, callbacks) {
95
+ + const prepared = await retryTransientCall(() => this._modelRuntime.prepareRequest(model, { signal }),
96
+ + isOAuthRefreshUnavailableError, this.settingsManager.getRetrySettings(), signal,
97
+ + callbacks ?? this._summarizationRetryCallbacks({ source: "compaction" }));
98
+ + return { ...prepared.options, model: prepared.model, thinkingLevel: this.thinkingLevel };
99
+ + }
100
+ @@ -6305,1 +6327,0 @@
101
+ - const compactionModel = this._resolveCompactionModel(model);
102
+ @@ -6336,5 +6357,6 @@
103
+ - // A configured compaction.model override redirects only the
104
+ - // summarization call to that model; every other part of the session
105
+ - // (lifecycle record, preparation, branch) still tracks the session
106
+ - // model. Falls back to the session model when the override is unset
107
+ - // or cannot be resolved.
108
+ + // Size the complete source, including control text, before selecting a model.
109
+ + // The final serialized request is checked again at the dispatch boundary.
110
+ + const inputTokens = estimateMessagesTokens([...preparation.messagesToSummarize, ...preparation.turnPrefixMessages]) +
111
+ + Math.ceil(JSON.stringify([this.agent.state.systemPrompt, this.agent.state.tools,
112
+ + preparation.previousSummary, request.customInstructions]).length / 4) + 8192;
113
+ + const compactionModel = this._resolveCompactionModel(model, inputTokens);
114
+ @@ -6342,1 +6364,4 @@
115
+ - compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason, thinkingLevel);
116
+ + compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason, thinkingLevel);
117
+ + compactionResult.details = { ...compactionResult.details,
118
+ + selectionMode: settings.model === undefined || settings.model === "auto" ? "auto" : "explicit",
119
+ + requestedProvider: compactionModel.provider, requestedModel: compactionModel.id };
120
+ @@ -7704,3 +7729,4 @@
121
+ - getCompactionModel: () => this.model && this._retryFallback.isProviderSpent(this.model.provider)
122
+ - ? this._resolveCompactionModel(this.model)
123
+ - : this.model,
124
+ + getCompactionModel: (inputTokens) => {
125
+ + const model = this._limitsModel();
126
+ + return model && this._resolveCompactionModel(model, inputTokens);
127
+ + },
128
+ --- a/@code-yeongyu/senpi/dist/core/extensions/runner.js
129
+ +++ b/@code-yeongyu/senpi/dist/core/extensions/runner.js
130
+ @@ -987,3 +987,3 @@
131
+ - ? () => {
132
+ - runner.assertActive();
133
+ - return runner.getCompactionModelFn?.();
134
+ + ? (inputTokens) => {
135
+ + runner.assertActive();
136
+ + return runner.getCompactionModelFn?.(inputTokens);
137
+ --- a/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
138
+ +++ b/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
139
+ @@ -446,1 +446,1 @@
140
+ - getCompactionModel?(): Model<Api> | undefined;
141
+ + getCompactionModel?(inputTokens?: number): Model<Api> | undefined;
142
+ @@ -2415,1 +2415,1 @@
143
+ - getCompactionModel?: () => Model<Api> | undefined;
144
+ + getCompactionModel?: (inputTokens?: number) => Model<Api> | undefined;
145
+ --- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/lane-policy.js
146
+ +++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/lane-policy.js
147
+ @@ -94,2 +94,3 @@
148
+ - if (context.getCompactionSettings?.().model)
149
+ - return false;
150
+ + const override = context.getCompactionSettings?.().model;
151
+ + if (typeof override === "string" && /^[^/\s]+\/\S+$/.test(override))
152
+ + return false;
153
+ --- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
154
+ +++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
155
+ @@ -8,1 +8,1 @@
156
+ -import { allowOverflowRetry, boundSummarizationInput, SUMMARIZATION_INPUT_BUDGET_RATIO, SummarizationOverflowExhaustedError, shrinkSummarizationInputForOverflowRetry, } from "./overflow-retry.js";
157
+ +import { estimateTotalTokens, SummarizationOverflowExhaustedError } from "./overflow-retry.js";
158
+ @@ -143,26 +143,44 @@
159
+ - const selectedModel = context.getCompactionModel?.() ?? snapshot.model;
160
+ - const selectedSnapshot = selectedModel === snapshot.model
161
+ - ? snapshot
162
+ - : { ...snapshot, model: selectedModel, contextWindow: selectedModel.contextWindow };
163
+ - const auth = await context.modelRegistry?.getApiKeyAndHeaders(selectedModel);
164
+ - if (signal?.aborted)
165
+ - return undefined;
166
+ - // A provider is authenticated for summarization by a resolved key, a credential
167
+ - // request header, or ambient request-time auth. The ambient marker comes from the
168
+ - // same provider resolution normal turns use; an unconfigured keyed provider still
169
+ - // has none of these and is rejected before a request.
170
+ - if (!auth?.ok || !(auth.apiKey || hasCredentialHeaders(auth.headers) || auth.ambient)) {
171
+ - const detail = auth && !auth.ok ? auth.error : `no credentials resolved for provider "${selectedModel.provider}"`;
172
+ - throw new SummaryGenerationError("auth", `summarization credentials unavailable: ${detail}`);
173
+ - }
174
+ - const requestSnapshot = auth.baseUrl
175
+ - ? { ...selectedSnapshot, model: { ...selectedModel, baseUrl: auth.baseUrl } }
176
+ - : selectedSnapshot;
177
+ - const prompt = buildPrompt({
178
+ - variant: requestSnapshot.promptVariant,
179
+ - previousSummary: requestSnapshot.preparation.previousSummary,
180
+ - taskIntent: resolveInheritedTaskIntent(requestSnapshot.branchEntries ?? []),
181
+ - customInstructions: requestSnapshot.customInstructions,
182
+ - });
183
+ - const promptTokens = approxTokens(prompt.user);
184
+ - let messages = boundSummarizationInput(pruneToolResults([...requestSnapshot.preparation.messagesToSummarize, ...requestSnapshot.preparation.turnPrefixMessages], requestSnapshot.contextWindow, SUMMARIZATION_INPUT_BUDGET_RATIO), requestSnapshot.contextWindow, promptTokens);
185
+ + const prompt = buildPrompt({
186
+ + variant: snapshot.promptVariant,
187
+ + previousSummary: snapshot.preparation.previousSummary,
188
+ + taskIntent: resolveInheritedTaskIntent(snapshot.branchEntries ?? []),
189
+ + customInstructions: snapshot.customInstructions,
190
+ + });
191
+ + const messages = [...snapshot.preparation.messagesToSummarize, ...snapshot.preparation.turnPrefixMessages];
192
+ + const inputTokens = estimateTotalTokens([
193
+ + ...messages,
194
+ + { role: "user", content: prompt.user },
195
+ + { role: "user", content: snapshot.systemPrompt ?? prompt.system },
196
+ + { role: "user", content: JSON.stringify(snapshot.tools ?? []) },
197
+ + ]);
198
+ + const selectedModel = context.getCompactionModel?.(inputTokens) ?? snapshot.model;
199
+ + const selectedSnapshot = { ...snapshot, model: selectedModel, contextWindow: selectedModel.contextWindow, selectionMode: context.getCompactionSettings?.().model === undefined || context.getCompactionSettings?.().model === "auto" ? "auto" : "explicit" };
200
+ + let auth;
201
+ + try {
202
+ + const runtime = context.modelRegistry?.modelRuntime;
203
+ + if (runtime) {
204
+ + const prepared = await runtime.prepareRequest(selectedModel, { signal });
205
+ + auth = { ok: true, ...prepared.options, baseUrl: prepared.model.baseUrl, ambient: true };
206
+ + }
207
+ + else {
208
+ + auth = await context.modelRegistry?.getApiKeyAndHeaders(selectedModel);
209
+ + }
210
+ + }
211
+ + catch (error) {
212
+ + if (signal?.aborted)
213
+ + return undefined;
214
+ + const kind = error?.failure?.kind ?? error?.kind;
215
+ + const known = ["auth_missing", "auth_expired", "quota", "org_denied", "model_unavailable"];
216
+ + const failure = { kind: kind === "auth" ? "auth_missing" : known.includes(kind) ? kind : "provider",
217
+ + provider: selectedModel.provider, model: selectedModel.id };
218
+ + throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction request authentication failed"), { failure, cause: error });
219
+ + }
220
+ + if (signal?.aborted)
221
+ + return undefined;
222
+ + if (!auth?.ok || !(auth.apiKey || hasCredentialHeaders(auth.headers) || auth.ambient)) {
223
+ + const failure = { kind: "auth_missing", provider: selectedModel.provider, model: selectedModel.id };
224
+ + throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction request authentication unavailable"), { failure });
225
+ + }
226
+ + const requestSnapshot = auth.baseUrl
227
+ + ? { ...selectedSnapshot, model: { ...selectedModel, baseUrl: auth.baseUrl } }
228
+ + : selectedSnapshot;
229
+ @@ -173,2 +191,0 @@
230
+ - const overflowRetryStartMs = Date.now();
231
+ - let overflowAttempts = 0;
232
+ @@ -216,1 +232,2 @@
233
+ - extraBody: auth.extraBody,
234
+ + extraBody: auth.extraBody,
235
+ + env: auth.env,
236
+ @@ -223,1 +240,1 @@
237
+ - !isContextOverflow(attempt, snapshot.contextWindow)) {
238
+ + !isContextOverflow(attempt, requestSnapshot.contextWindow)) {
239
+ @@ -240,12 +257,4 @@
240
+ - if (isAssistantMessage(response) && isContextOverflow(response, snapshot.contextWindow)) {
241
+ - overflowAttempts++;
242
+ - const elapsedMs = Date.now() - overflowRetryStartMs;
243
+ - const retryMessages = allowOverflowRetry(overflowAttempts, elapsedMs)
244
+ - ? shrinkSummarizationInputForOverflowRetry(messages, snapshot.contextWindow, promptTokens)
245
+ - : undefined;
246
+ - if (!retryMessages) {
247
+ - throw new SummarizationOverflowExhaustedError(overflowAttempts, elapsedMs);
248
+ - }
249
+ - messages = retryMessages;
250
+ - continue;
251
+ - }
252
+ + if (isAssistantMessage(response) && isContextOverflow(response, requestSnapshot.contextWindow)) {
253
+ + const failure = { kind: "context_budget", provider: selectedModel.provider, model: selectedModel.id };
254
+ + throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction input exceeds the provider context budget"), { failure });
255
+ + }
256
+ @@ -303,1 +312,6 @@
257
+ - schema: SUMMARY_SCHEMA,
258
+ + schema: SUMMARY_SCHEMA,
259
+ + selectionMode: selectedSnapshot.selectionMode,
260
+ + requestedProvider: selectedModel.provider,
261
+ + requestedModel: selectedModel.id,
262
+ + ...(isAssistantMessage(response) && response.provider && response.responseModel
263
+ + ? { observedProvider: response.provider, observedModel: response.responseModel } : {}),
264
+ --- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative-summary.js
265
+ +++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative-summary.js
266
+ @@ -6,1 +6,2 @@
267
+ -import { repairOrphanedToolResults } from "./repair-tool-pairs.js";
268
+ +import { repairOrphanedToolResults } from "./repair-tool-pairs.js";
269
+ +import { estimateTotalTokens } from "./overflow-retry.js";
270
+ @@ -109,1 +110,10 @@
271
+ - const headers = providerRequest
272
+ + const inputTokens = estimateTotalTokens([
273
+ + ...requestContext.messages,
274
+ + { role: "user", content: requestContext.systemPrompt },
275
+ + { role: "user", content: JSON.stringify(requestContext.tools ?? []) },
276
+ + ]);
277
+ + if (inputTokens + summaryMaxTokens(options.snapshot.model, options.snapshot.contextWindow) > options.snapshot.contextWindow) {
278
+ + throw Object.assign(new Error("Compaction request exceeds the selected model context budget"),
279
+ + { failure: { kind: "context_budget", provider: options.snapshot.model.provider, model: options.snapshot.model.id } });
280
+ + }
281
+ + const headers = providerRequest
282
+ @@ -115,1 +125,3 @@
283
+ - extraBody: options.auth.extraBody,
284
+ + extraBody: options.auth.extraBody,
285
+ + env: options.auth.env,
286
+ + streamKind: "auxiliary",
287
+ --- a/@code-yeongyu/senpi/dist/core/compaction/compaction.js
288
+ +++ b/@code-yeongyu/senpi/dist/core/compaction/compaction.js
289
+ @@ -635,1 +635,2 @@
290
+ - affinitySessionId: options.affinitySessionId ?? options.sessionId,
291
+ + affinitySessionId: options.affinitySessionId ?? options.sessionId,
292
+ + streamKind: "auxiliary",
293
+ @@ -638,1 +639,5 @@
294
+ - const callerSignal = options.signal;
295
+ + if (model.contextWindow <= 0 || estimateProviderContextTokens(context).tokens + options.maxTokens > model.contextWindow) {
296
+ + throw Object.assign(new Error("Compaction request exceeds the selected model context budget"),
297
+ + { failure: { kind: "context_budget", provider: model.provider, model: model.id } });
298
+ + }
299
+ + const callerSignal = options.signal;