zigmini 0.8.0-beta.7 → 0.9.0-beta.2
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 +27 -5
- package/README.md +27 -5
- package/assets/agent/gateway-request-rules.md +3 -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 +57 -4
- package/docs/manual.md +55 -4
- package/host/omo-bridge/ascii-json.ts +5 -0
- package/host/omo-bridge/bridge.ts +50 -44
- package/host/omo-check/patches/omo-5.1.44-compaction-port.patch +2630 -0
- package/host/omo-check/patches/omo-5.1.44-gateway-port.manifest.json +329 -0
- package/host/omo-check/patches/omo-5.1.44-provider-port.patch +440 -0
- package/host/omo-check/patches/omo-5.1.44-rpc-port.patch +778 -0
- package/host/omo-check/patches/omo-5.1.48-compaction-port.patch +2629 -0
- package/host/omo-check/patches/omo-5.1.48-gateway-port.manifest.json +355 -0
- package/host/omo-check/patches/omo-5.1.48-provider-port.patch +440 -0
- package/host/omo-check/patches/omo-5.1.48-rpc-port.patch +799 -0
- package/host/omo-check/patches/omo-5.1.48-runtime-compat.patch +31 -0
- package/package.json +1 -1
- package/src/cli/setup.ts +6 -2
- package/src/decisions.ts +32 -13
- package/src/engine/compat.json +4 -4
- package/src/engine/install.ts +68 -20
- package/src/engine/rpc-socket.ts +36 -0
- package/src/engine/schema-guard.ts +34 -2
- package/src/hub.ts +57 -31
- package/src/main.ts +22 -7
- package/src/notification-history.ts +56 -0
- package/src/omo/client.ts +16 -3
- package/src/omo/frame-socket.ts +330 -0
- package/src/protocol.ts +2 -1
- package/src/security-allow.ts +126 -0
- package/src/security-check.ts +355 -37
- package/src/store.ts +165 -5
- package/src/update/handoff-system.ts +22 -0
- package/src/update/handoff.ts +10 -2
- package/src/update/helper.ts +11 -1
- package/src/update/request.ts +15 -2
- package/src/work.ts +21 -7
- package/web/DESIGN.md +39 -0
- package/web/app.js +198 -69
- package/web/decisions.d.ts +1 -1
- package/web/decisions.js +3 -0
- package/web/diagram.d.ts +28 -0
- package/web/diagram.js +228 -0
- package/web/index.html +13 -0
- package/web/security-inbox.d.ts +35 -0
- package/web/security-inbox.js +237 -0
- package/web/style.css +31 -1
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
--- a/@code-yeongyu/senpi/dist/core/auth-storage.js
|
|
2
|
+
+++ b/@code-yeongyu/senpi/dist/core/auth-storage.js
|
|
3
|
+
@@ -344,7 +344,6 @@
|
|
4
|
+
*/
|
|
5
|
+
export class AuthStorage {
|
|
6
|
+
constructor(storage, authPath) {
|
|
7
|
+
- this.data = {};
|
|
8
|
+
this.runtimeOverrides = new Map();
|
|
9
|
+
this.extensionOAuthProviders = new Map();
|
|
10
|
+
this.errors = [];
|
|
11
|
+
@@ -367,14 +366,21 @@
|
|
12
|
+
// Another instance already loaded this store: start from its credentials, not an
|
|
13
|
+
// empty snapshot, so a busy read below keeps them. A repaired or migrated load
|
|
14
|
+
// leaves the revision unset, so only an exact revision match skips the re-read.
|
|
15
|
+
- this.data = this.readState.data;
|
|
16
|
+
this.dataLoaded = true;
|
|
17
|
+
const revision = getFileContentRevision(authPath);
|
|
18
|
+
if (revision !== undefined && revision === this.readState.revision)
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.reload();
|
|
22
|
+
+ }
|
|
23
|
+
+ // Synchronous extension reads and async runtime reads share one snapshot.
|
|
24
|
+
+ get data() {
|
|
25
|
+
+ return this.readState.data;
|
|
26
|
+
}
|
|
27
|
+
+ set data(data) {
|
|
28
|
+
+ this.readState.data = data;
|
|
29
|
+
+ this.readState.revision = undefined;
|
|
30
|
+
+ }
|
|
31
|
+
static create(authPath = join(getAgentDir(), "auth.json")) {
|
|
32
|
+
const normalizedAuthPath = normalizePath(authPath);
|
|
33
|
+
return new AuthStorage(new FileAuthStorageBackend(normalizedAuthPath), normalizedAuthPath);
|
|
34
|
+
@@ -445,7 +451,6 @@
|
|
35
|
+
return backupPath;
|
|
36
|
+
}
|
|
37
|
+
updateReadState(data, revision) {
|
|
38
|
+
- this.data = data;
|
|
39
|
+
this.dataLoaded = true;
|
|
40
|
+
this.dataFromBusyRead = false;
|
|
41
|
+
this.readState.data = data;
|
|
42
|
+
@@ -897,4 +902,4 @@
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
-//# sourceMappingURL=auth-storage.js.map
|
|
47
|
+
|
|
48
|
+
+//# sourceMappingURL=auth-storage.js.map
|
|
49
|
+
--- a/@code-yeongyu/senpi/dist/core/credential-accounts.js
|
|
50
|
+
+++ b/@code-yeongyu/senpi/dist/core/credential-accounts.js
|
|
51
|
+
@@ -75,6 +75,8 @@
|
|
52
|
+
* here without auth.json ever carrying block state.
|
|
53
|
+
*/
|
|
54
|
+
export async function getCredentialAccounts(storage, provider, env = process.env, repository) {
|
|
55
|
+
+ // Refresh metadata without resolving keys or applying runtime overrides.
|
|
56
|
+
+ await storage.list();
|
|
57
|
+
return summarizeCredentialAccounts(provider, storage.get(provider), env, repository ?? credentialPoolRepository(storage));
|
|
58
|
+
}
|
|
59
|
+
/** Storage-free variant for callers that already hold the credential (e.g. auth check). */
|
|
60
|
+
@@ -211,4 +213,4 @@
|
|
61
|
+
await repo.mutateSlotState(provider, "stored", name, () => undefined);
|
|
62
|
+
emitProviderAccountsChanged(provider);
|
|
63
|
+
}
|
|
64
|
+
-//# sourceMappingURL=credential-accounts.js.map
|
|
65
|
+
|
|
66
|
+
+//# sourceMappingURL=credential-accounts.js.map
|
|
67
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts
|
|
68
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts
|
|
69
|
+
@@ -20,7 +20,7 @@
|
|
70
|
+
export type AuthenticatedQueryInput = {
|
|
71
|
+
prompt: Parameters<SdkQuery>[0]["prompt"];
|
|
72
|
+
query: SdkQuery;
|
|
73
|
+
- buildOptions: (lane: AnthropicSubscriptionTokenInjection) => Options;
|
|
74
|
+
+ buildOptions: (lane: AnthropicSubscriptionTokenInjection) => Options | Promise<Options>;
|
|
75
|
+
providerSettings: AnthropicSubscriptionProviderSettings;
|
|
76
|
+
/** Effective request auth environment; overrides the host for account discovery and SDK spawn. */
|
|
77
|
+
env?: Record<string, string>;
|
|
78
|
+
@@ -38,4 +38,4 @@
|
|
79
|
+
export declare function resolveEffectiveLane(settings: AnthropicSubscriptionProviderSettings, accounts: readonly AccountSlot[]): AnthropicSubscriptionTokenInjection;
|
|
80
|
+
/** Resolves managed OAuth immediately before each subprocess spawn and retries only pre-delta failures. */
|
|
81
|
+
export declare function queryWithAuthLane(input: AuthenticatedQueryInput): AsyncGenerator<SDKMessage>;
|
|
82
|
+
-//# sourceMappingURL=auth-lane.d.ts.map
|
|
83
|
+
|
|
84
|
+
+//# sourceMappingURL=auth-lane.d.ts.map
|
|
85
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js
|
|
86
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js
|
|
87
|
+
@@ -145,7 +145,7 @@
|
|
88
|
+
// Lane-owned variables from buildOptions (the compaction-owner overlay) win over the host.
|
|
89
|
+
let askedLegacyLongContext = false;
|
|
90
|
+
const ambientAttempt = async () => {
|
|
91
|
+
- const options = input.buildOptions("ambient");
|
|
92
|
+
+ const options = await input.buildOptions("ambient");
|
|
93
|
+
askedLegacyLongContext = asksLegacyLongContext(options.model);
|
|
94
|
+
options.env = { ...ambientEnvironment, ...options.env };
|
|
95
|
+
return createAttemptMessages(input, { accountName: "ambient", accounts: [], authLane: "ambient", options });
|
|
96
|
+
@@ -184,7 +184,7 @@
|
|
97
|
+
...(input.model === undefined ? {} : { model: input.model }),
|
|
98
|
+
}),
|
|
99
|
+
runAttempt: async (slot) => {
|
|
100
|
+
- const options = input.buildOptions(pool.lane);
|
|
101
|
+
+ const options = await input.buildOptions(pool.lane);
|
|
102
|
+
askedLegacyLongContext = asksLegacyLongContext(options.model);
|
|
103
|
+
const accounts = pool.accounts.map((account) => ({ ...account }));
|
|
104
|
+
const prepared = await prepareSlot(pool, slot, signal);
|
|
105
|
+
@@ -215,4 +215,4 @@
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
-//# sourceMappingURL=auth-lane.js.map
|
|
110
|
+
|
|
111
|
+
+//# sourceMappingURL=auth-lane.js.map
|
|
112
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js
|
|
113
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js
|
|
114
|
+
@@ -21,6 +21,7 @@
|
|
115
|
+
toolOutput: (toolName) => `[image returned by the ${toolName} tool to your own call (tool output, not a user attachment)]`,
|
|
116
|
+
userHistory: "[image the user attached in this earlier turn (history, not part of the current message)]",
|
|
117
|
+
duplicate: (origin, first) => `${subject(origin)}: identical to an image already shown above (${source(first)}); not attached again]`,
|
|
118
|
+
+ userAnswered: "[image the user attached in an earlier turn: you already saw and answered it, so it is not attached again and is not part of the current message; ask the user to attach it again if you need it]",
|
|
119
|
+
answered: (origin) => `[image returned earlier by the ${origin.toolName} tool, id=${origin.toolCallId}: already seen earlier in this conversation; run the tool again if you need it]`,
|
|
120
|
+
capped: (origin) => `${subject(origin)}: omitted from this replay, which re-sends at most ${MAX_REPLAYED_HISTORY_IMAGES} earlier images; re-read the source if you need it]`,
|
|
121
|
+
unreadable: (origin) => `${subject(origin)}: omitted because its image data is missing or unreadable]`,
|
|
122
|
+
@@ -86,14 +87,16 @@
|
|
123
|
+
return new Set(ordered.slice(0, MAX_REPLAYED_HISTORY_IMAGES).map(([hash]) => hash));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
- * A tool image the assistant already responded to is not re-sent (#2768): a cold
|
|
127
|
+
- * seed packs the whole history into one user message, and replaying earlier tool
|
|
128
|
+
- * reads there made the model answer plain questions as if the user had just sent
|
|
129
|
+
- * those images. The user's own uploads and images still awaiting a response are
|
|
130
|
+
- * replayed.
|
|
131
|
+
+ * An image the assistant already responded to is not re-sent (#2768): a cold
|
|
132
|
+
+ * seed packs the whole history into one user message, and replaying earlier
|
|
133
|
+
+ * images there made the model answer plain questions as if the user had just
|
|
134
|
+
+ * sent those images. zigmini: this covers the user's own earlier uploads too;
|
|
135
|
+
+ * Claude Code saved each replayed upload as a new images/N.png on every model
|
|
136
|
+
+ * call and the model read it as a new attachment. Images still awaiting a
|
|
137
|
+
+ * response (the current turn) are replayed.
|
|
138
|
+
*/
|
|
139
|
+
function isAnsweredToolImage(occurrence) {
|
|
140
|
+
- return occurrence.answered && occurrence.origin.kind === "tool";
|
|
141
|
+
+ return occurrence.answered;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Rewrites the image entries of a cold-seed history (#2490): a tool-result image
|
|
145
|
+
@@ -124,8 +127,8 @@
|
|
146
|
+
if (decoded === undefined || hash === undefined) {
|
|
147
|
+
replacement = [{ type: "text", text: coldSeedImageText.unreadable(origin) }];
|
|
148
|
+
}
|
|
149
|
+
- else if (origin.kind === "tool" && occurrence.answered && shownEarlier === undefined) {
|
|
150
|
+
- replacement = [{ type: "text", text: coldSeedImageText.answered(origin) }];
|
|
151
|
+
+ else if (occurrence.answered && shownEarlier === undefined) {
|
|
152
|
+
+ replacement = [{ type: "text", text: origin.kind === "tool" ? coldSeedImageText.answered(origin) : coldSeedImageText.userAnswered }];
|
|
153
|
+
}
|
|
154
|
+
else if (!replayed.has(hash) && shownEarlier === undefined) {
|
|
155
|
+
replacement = [{ type: "text", text: coldSeedImageText.capped(origin) }];
|
|
156
|
+
@@ -159,4 +162,4 @@
|
|
157
|
+
}
|
|
158
|
+
return rewritten;
|
|
159
|
+
}
|
|
160
|
+
-//# sourceMappingURL=cold-seed-images.js.map
|
|
161
|
+
|
|
162
|
+
+//# sourceMappingURL=cold-seed-images.js.map
|
|
163
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/errors.js
|
|
164
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/errors.js
|
|
165
|
+
@@ -1,4 +1,5 @@
|
|
166
|
+
import { rateLimitModelFamily } from "../../../credential-pool/model-scope.js";
|
|
167
|
+
+import { isClaudeSdkRefusal } from "./refusal.js";
|
|
168
|
+
const SDK_ERROR_CLASSIFICATIONS = {
|
|
169
|
+
authentication_failed: { kind: "auth_error", retryable: true },
|
|
170
|
+
oauth_org_not_allowed: { kind: "org_not_allowed", retryable: true },
|
|
171
|
+
@@ -76,6 +77,8 @@
|
|
172
|
+
}
|
|
173
|
+
/** Classifies Anthropic Subscription error codes and HTTP-shaped fallback text in one place. */
|
|
174
|
+
export function classifySdkError(error) {
|
|
175
|
+
+ if (isClaudeSdkRefusal(error))
|
|
176
|
+
+ return OTHER_ERROR;
|
|
177
|
+
const classification = classifyErrorKind(error);
|
|
178
|
+
if (classification.kind !== "rate_limit")
|
|
179
|
+
return classification;
|
|
180
|
+
@@ -130,4 +133,4 @@
|
|
181
|
+
}
|
|
182
|
+
return OTHER_ERROR;
|
|
183
|
+
}
|
|
184
|
+
-//# sourceMappingURL=errors.js.map
|
|
185
|
+
|
|
186
|
+
+//# sourceMappingURL=errors.js.map
|
|
187
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/refusal.js
|
|
188
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/refusal.js
|
|
189
|
+
@@ -26,6 +26,13 @@
|
|
190
|
+
return details ? new ClaudeSdkRefusalError(details.content, details.category) : undefined;
|
|
191
|
+
}
|
|
192
|
+
export function isClaudeSdkRefusal(error) {
|
|
193
|
+
- return error instanceof ClaudeSdkRefusalError;
|
|
194
|
+
+ return getClaudeSdkRefusal(error) !== undefined;
|
|
195
|
+
}
|
|
196
|
+
-//# sourceMappingURL=refusal.js.map
|
|
197
|
+
|
|
198
|
+
+export function getClaudeSdkRefusal(error) {
|
|
199
|
+
+ if (error instanceof ClaudeSdkRefusalError)
|
|
200
|
+
+ return error;
|
|
201
|
+
+ if (error !== null && typeof error === "object" && "original" in error)
|
|
202
|
+
+ return getClaudeSdkRefusal(error.original);
|
|
203
|
+
+ return undefined;
|
|
204
|
+
+}
|
|
205
|
+
+//# sourceMappingURL=refusal.js.map
|
|
206
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/stream.js
|
|
207
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/stream.js
|
|
208
|
+
@@ -9,7 +9,7 @@
|
|
209
|
+
import { buildDeferredPromptStream, buildPromptBlocks } from "./prompt-bridge.js";
|
|
210
|
+
import { pinOneShotPromptCacheTtl } from "./prompt-cache-ttl.js";
|
|
211
|
+
import { dedupeUltraworkBlocks } from "./prompt-directive-dedupe.js";
|
|
212
|
+
-import { refusalError } from "./refusal.js";
|
|
213
|
+
+import { getClaudeSdkRefusal, refusalError } from "./refusal.js";
|
|
214
|
+
import { getSdkBoundary, loadClaudeAgentSdk } from "./sdk-boundary.js";
|
|
215
|
+
import { emitContinuityObservation } from "./session-observability.js";
|
|
216
|
+
import { forgetBinding } from "./session-reattach.js";
|
|
217
|
+
@@ -74,7 +74,7 @@
|
|
218
|
+
claudeCodeRun = resolveClaudeCodeRun(defaultExecutableDeps());
|
|
219
|
+
const executable = claudeCodeRun.executable;
|
|
220
|
+
let oneShotCacheTtl;
|
|
221
|
+
- const buildOptions = (authLane) => {
|
|
222
|
+
+ const buildOptions = async (authLane) => {
|
|
223
|
+
const queryOptions = buildAnthropicSubscriptionQueryOptions({
|
|
224
|
+
model,
|
|
225
|
+
context,
|
|
226
|
+
@@ -97,7 +97,8 @@
|
|
227
|
+
if (!useResidentSession) {
|
|
228
|
+
oneShotCacheTtl = pinOneShotPromptCacheTtl(queryOptions, authLane ?? providerSettings.tokenInjection ?? "ambient", { ...process.env, ...options?.env });
|
|
229
|
+
}
|
|
230
|
+
- return queryOptions;
|
|
231
|
+
+ const replacement = await options?.onPayload?.(queryOptions, model);
|
|
232
|
+
+ return replacement === undefined ? queryOptions : replacement;
|
|
233
|
+
};
|
|
234
|
+
const recordContinuity = (observation) => {
|
|
235
|
+
// Not a failure: carry the observation as details only, with no synthesized error.
|
|
236
|
+
@@ -252,6 +253,9 @@
|
|
237
|
+
if (nativeCompactionStopped && options?.sessionId)
|
|
238
|
+
forgetBinding(options.sessionId);
|
|
239
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
240
|
+
+ const refusal = getClaudeSdkRefusal(error);
|
|
241
|
+
+ if (refusal)
|
|
242
|
+
+ output.stopDetails = { type: "refusal", explanation: refusal.message };
|
|
243
|
+
// A failed result still bills its tokens; managed and resident lanes
|
|
244
|
+
// throw before the result reaches this loop, so account for it here.
|
|
245
|
+
const billed = sdkResultFailureUsage(error);
|
|
246
|
+
@@ -269,4 +273,4 @@
|
|
247
|
+
})();
|
|
248
|
+
return stream;
|
|
249
|
+
}
|
|
250
|
+
-//# sourceMappingURL=stream.js.map
|
|
251
|
+
|
|
252
|
+
+//# sourceMappingURL=stream.js.map
|
|
253
|
+
--- a/@code-yeongyu/senpi/dist/core/model-runtime.js
|
|
254
|
+
+++ b/@code-yeongyu/senpi/dist/core/model-runtime.js
|
|
255
|
+
@@ -76,6 +76,7 @@
|
|
256
|
+
onPayload: async (payload, providerModel) => await onPayload(payload, providerModel, {
|
|
257
|
+
model,
|
|
258
|
+
headers: options.headers ?? {},
|
|
259
|
+
+ streamKind: options.streamKind,
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
@@ -1160,4 +1161,4 @@
|
|
264
|
+
return model && !isVirtualModel(model) ? model : undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
-//# sourceMappingURL=model-runtime.js.map
|
|
268
|
+
|
|
269
|
+
+//# sourceMappingURL=model-runtime.js.map
|
|
270
|
+
--- a/@earendil-works/pi-agent-core/dist/agent.d.ts
|
|
271
|
+
+++ b/@earendil-works/pi-agent-core/dist/agent.d.ts
|
|
272
|
+
@@ -188,6 +188,9 @@
|
|
273
|
+
* the continuation input, while timeout overrides still apply to its first provider request.
|
|
274
|
+
*/
|
|
275
|
+
continueWithQueuedMessages(options?: AgentContinuationOptions): Promise<void>;
|
|
276
|
+
+ continueQueuedInput(clientInputId: string, options?: AgentContinuationOptions & {
|
|
277
|
+
+ onStart?: () => void;
|
|
278
|
+
+ }): Promise<"busy" | "not_head" | "resumed">;
|
|
279
|
+
/**
|
|
280
|
+
* Continue from the current transcript. The last message must be a user or tool-result message.
|
|
281
|
+
* Queue deferral and timeout overrides apply only to the first provider request; later requests in
|
|
282
|
+
@@ -236,4 +239,4 @@
|
|
283
|
+
*/
|
|
284
|
+
emitExternalEvent(event: AgentEvent, runSignal?: AbortSignal): Promise<void>;
|
|
285
|
+
}
|
|
286
|
+
-//# sourceMappingURL=agent.d.ts.map
|
|
287
|
+
|
|
288
|
+
+//# sourceMappingURL=agent.d.ts.map
|
|
289
|
+
--- a/@earendil-works/pi-agent-core/dist/agent.js
|
|
290
|
+
+++ b/@earendil-works/pi-agent-core/dist/agent.js
|
|
291
|
+
@@ -297,6 +297,21 @@
|
|
292
|
+
* Queue-first recovery takes precedence over `deferQueuedMessages`: the selected queued message is
|
|
293
|
+
* the continuation input, while timeout overrides still apply to its first provider request.
|
|
294
|
+
*/
|
|
295
|
+
+ async continueQueuedInput(clientInputId, options = {}) {
|
|
296
|
+
+ if (this.activeRun)
|
|
297
|
+
+ return "busy";
|
|
298
|
+
+ const steering = this.steeringQueue.hasItems();
|
|
299
|
+
+ const queue = steering ? this.steeringQueue : this.followUpQueue;
|
|
300
|
+
+ if (queue.messages[0]?.clientInputId !== clientInputId)
|
|
301
|
+
+ return "not_head";
|
|
302
|
+
+ options.onStart?.();
|
|
303
|
+
+ await this.runQueuedMessages(steering ? "steering" : "followUp", {
|
|
304
|
+
+ skipInitialSteeringPoll: steering,
|
|
305
|
+
+ initialRequestTimeoutMs: options.timeoutMs,
|
|
306
|
+
+ initialRequestStreamStartTimeoutMs: options.streamStartTimeoutMs,
|
|
307
|
+
+ });
|
|
308
|
+
+ return "resumed";
|
|
309
|
+
+ }
|
|
310
|
+
async continueWithQueuedMessages(options = {}) {
|
|
311
|
+
if (this.activeRun) {
|
|
312
|
+
throw new Error("Agent is already processing. Wait for completion before continuing.");
|
|
313
|
+
@@ -672,4 +687,4 @@
|
|
314
|
+
await this.processEvents(event);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
-//# sourceMappingURL=agent.js.map
|
|
318
|
+
|
|
319
|
+
+//# sourceMappingURL=agent.js.map
|
|
320
|
+
--- a/@earendil-works/pi-ai/dist/types.d.ts
|
|
321
|
+
+++ b/@earendil-works/pi-ai/dist/types.d.ts
|
|
322
|
+
@@ -85,6 +85,7 @@
|
|
323
|
+
export type ProviderRequestMetadata = {
|
|
324
|
+
model: Model<Api>;
|
|
325
|
+
headers: ProviderHeaders;
|
|
326
|
+
+ streamKind?: "main" | "auxiliary";
|
|
327
|
+
};
|
|
328
|
+
export type FetchFunction = typeof globalThis.fetch;
|
|
329
|
+
export interface ProviderResponse {
|
|
330
|
+
@@ -510,6 +511,8 @@
|
|
331
|
+
timestamp: number;
|
|
332
|
+
}
|
|
333
|
+
export interface UserMessage {
|
|
334
|
+
+ /** Gateway input identity preserved through expansion and queue consumption. */
|
|
335
|
+
+ clientInputId?: string;
|
|
336
|
+
role: "user";
|
|
337
|
+
content: string | (TextContent | ImageContent)[];
|
|
338
|
+
timestamp: number;
|
|
339
|
+
@@ -1242,4 +1245,4 @@
|
|
340
|
+
export type ModelType = keyof ModelTypeMap;
|
|
341
|
+
/** Anything a provider can list. Narrow with `isModelType()`. */
|
|
342
|
+
export type AnyModel = ModelTypeMap[ModelType];
|
|
343
|
+
-//# sourceMappingURL=types.d.ts.map
|
|
344
|
+
|
|
345
|
+
+//# sourceMappingURL=types.d.ts.map
|
|
346
|
+
--- a/omo-ai/bin/lib/package-paths.js
|
|
347
|
+
+++ b/omo-ai/bin/lib/package-paths.js
|
|
348
|
+
@@ -113,7 +113,8 @@
|
|
349
|
+
// launcher change.
|
|
350
|
+
const bundlePath = join(distDir, "bundle", "cli.js")
|
|
351
|
+
const unbundledPath = join(distDir, "cli.js")
|
|
352
|
+
- const cliPath = existsSync(bundlePath) ? bundlePath : unbundledPath
|
|
353
|
+
+ // Gateway package ports live in the unbundled graph, including relative RPC workers.
|
|
354
|
+
+ const cliPath = unbundledPath
|
|
355
|
+
if (!existsSync(cliPath)) {
|
|
356
|
+
throw new Error(`senpi CLI is missing at ${cliPath}; reinstall with: ${updateTarget().command}`)
|
|
357
|
+
}
|
|
358
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/tps.js
|
|
359
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/tps.js
|
|
360
|
+
@@ -16,6 +16,8 @@
|
|
361
|
+
let lastDeltaMs = null;
|
|
362
|
+
let generationMs = 0;
|
|
363
|
+
let streamedOutput = 0;
|
|
364
|
+
+ let turnId = null;
|
|
365
|
+
+ let completedMessages = [];
|
|
366
|
+
const finishActiveAssistantTiming = () => {
|
|
367
|
+
if (activeAssistantStartMs === null)
|
|
368
|
+
return;
|
|
369
|
+
@@ -34,7 +36,11 @@
|
|
370
|
+
lastDeltaMs = null;
|
|
371
|
+
thinkingStreamed = false;
|
|
372
|
+
};
|
|
373
|
+
- pi.on("agent_start", () => {
|
|
374
|
+
+ pi.on("agent_start", (_event, ctx) => {
|
|
375
|
+
+ turnId = ctx.mode === "rpc" ? crypto.randomUUID() : null;
|
|
376
|
+
+ completedMessages = [];
|
|
377
|
+
+ if (turnId !== null)
|
|
378
|
+
+ pi.rpc.emit("zgw.turn_started", { version: 1, turnId });
|
|
379
|
+
activeAssistantStartMs = null;
|
|
380
|
+
assistantElapsedMs = 0;
|
|
381
|
+
generationMs = 0;
|
|
382
|
+
@@ -69,6 +75,7 @@
|
|
383
|
+
// counted in usage.output may be hidden or only summarised after it finished, so it was generated
|
|
384
|
+
// before the first delta. Such a message (usage.reasoning, or a thinking block whose text never
|
|
385
|
+
// streamed: Bedrock and omitted display report no reasoning count) is timed from its start.
|
|
386
|
+
+ completedMessages.push(event.message);
|
|
387
|
+
const output = event.message.usage?.output ?? 0;
|
|
388
|
+
const windowStart = reasonedBeforeFirstDelta(event.message, thinkingStreamed) ? messageStartMs : firstDeltaMs;
|
|
389
|
+
const counted = event.message.stopReason !== "aborted" && event.message.stopReason !== "error" && output > 0;
|
|
390
|
+
@@ -83,6 +90,43 @@
|
|
391
|
+
const elapsedMs = assistantElapsedMs;
|
|
392
|
+
const measuredGenerationMs = generationMs;
|
|
393
|
+
const measuredOutput = streamedOutput;
|
|
394
|
+
+ // Structured RPC metadata stays out of messages and extension_ui_request notifications.
|
|
395
|
+
+ const completedTurnId = turnId;
|
|
396
|
+
+ turnId = null;
|
|
397
|
+
+ const messages = completedMessages;
|
|
398
|
+
+ completedMessages = [];
|
|
399
|
+
+ const finalMessage = messages.at(-1);
|
|
400
|
+
+ const lastAssistant = event.messages.findLast(isAssistantMessage);
|
|
401
|
+
+ if (ctx.mode === "rpc" && completedTurnId !== null && !event.aborted && !event.willRetry && !event.abortSource && !ctx.signal?.aborted
|
|
402
|
+
+ && finalMessage && (finalMessage.stopReason === "stop" || finalMessage.stopReason === "length")
|
|
403
|
+
+ && Number.isInteger(finalMessage.timestamp) && finalMessage.timestamp > 0
|
|
404
|
+
+ && lastAssistant?.timestamp === finalMessage.timestamp
|
|
405
|
+
+ && !messages.some((message) => message.stopReason === "error" || message.stopReason === "aborted")) {
|
|
406
|
+
+ const stats = {};
|
|
407
|
+
+ const hasOutput = messages.some((message) => Number.isFinite(message.usage?.output) && message.usage.output > 0);
|
|
408
|
+
+ if (hasOutput) {
|
|
409
|
+
+ if (Number.isFinite(elapsedMs) && elapsedMs > 0)
|
|
410
|
+
+ stats.modelDurationMs = elapsedMs;
|
|
411
|
+
+ const rate = measuredOutput / (measuredGenerationMs / 1000);
|
|
412
|
+
+ if (measuredGenerationMs >= MIN_GENERATION_MS && Number.isFinite(rate) && rate > 0)
|
|
413
|
+
+ stats.tokensPerSecond = rate;
|
|
414
|
+
+ // Unknown usage must not look like a measured zero-percent cache hit.
|
|
415
|
+
+ if (messages.every((message) => [message.usage?.input, message.usage?.cacheRead, message.usage?.cacheWrite]
|
|
416
|
+
+ .every((value) => Number.isFinite(value) && value >= 0))) {
|
|
417
|
+
+ let promptTokens = 0;
|
|
418
|
+
+ let cacheRead = 0;
|
|
419
|
+
+ for (const message of messages) {
|
|
420
|
+
+ promptTokens += message.usage.input + message.usage.cacheRead + message.usage.cacheWrite;
|
|
421
|
+
+ cacheRead += message.usage.cacheRead;
|
|
422
|
+
+ }
|
|
423
|
+
+ const hit = cacheRead / promptTokens * 100;
|
|
424
|
+
+ if (Number.isFinite(promptTokens) && Number.isFinite(cacheRead) && Number.isFinite(hit) && hit >= 0 && hit <= 100)
|
|
425
|
+
+ stats.cacheHitPercent = hit;
|
|
426
|
+
+ }
|
|
427
|
+
+ }
|
|
428
|
+
+ if (Object.keys(stats).length > 0)
|
|
429
|
+
+ pi.rpc.emit("zgw.turn_stats", { version: 1, turnId: completedTurnId, finalMessageTimestamp: finalMessage.timestamp, finalMessageIndex: messages.length, stats });
|
|
430
|
+
+ }
|
|
431
|
+
activeAssistantStartMs = null;
|
|
432
|
+
assistantElapsedMs = 0;
|
|
433
|
+
generationMs = 0;
|
|
434
|
+
@@ -121,4 +165,4 @@
|
|
435
|
+
ctx.ui.notify(`${rate}${stats}`, "info");
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
-//# sourceMappingURL=tps.js.map
|
|
439
|
+
|
|
440
|
+
+//# sourceMappingURL=tps.js.map
|