stitchkit 0.70.6 → 0.71.0
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/dist/agent-runtime/coding-tool-contract.d.ts +11 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-listing.d.ts +15 -0
- package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -0
- package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-refusals.d.ts +59 -0
- package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -0
- package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tools.d.ts +3 -1
- package/dist/agent-runtime/coding-tools.d.ts.map +1 -1
- package/dist/agent-runtime/compaction.d.ts +35 -0
- package/dist/agent-runtime/compaction.d.ts.map +1 -1
- package/dist/agent-runtime/contained-files.d.ts +21 -2
- package/dist/agent-runtime/contained-files.d.ts.map +1 -1
- package/dist/agent-runtime/provider-failure.d.ts +66 -0
- package/dist/agent-runtime/provider-failure.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +31 -1
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts +1 -0
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime-coding-tools.js +438 -124
- package/dist/agent-runtime-harness.js +4 -4
- package/dist/agent-runtime-openrouter.d.ts +19 -0
- package/dist/agent-runtime-openrouter.d.ts.map +1 -1
- package/dist/agent-runtime-openrouter.js +2 -1
- package/dist/agent-runtime.d.ts +3 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +118 -14
- package/dist/cli.js +5 -5
- package/dist/{index-cx84zg25.js → index-118v0z41.js} +1 -1
- package/dist/{index-ejv31h14.js → index-7qy2ex0m.js} +1 -1
- package/dist/{index-trrf7nch.js → index-ezmn6ac6.js} +3 -3
- package/dist/{index-ekkt4gy9.js → index-jqtsc9mj.js} +2 -2
- package/dist/{index-hxtm2xr2.js → index-k2zczx1g.js} +1 -1
- package/dist/{index-by57nwhc.js → index-m668wzyc.js} +10 -2
- package/dist/{index-w9m1wznn.js → index-nemjkxjp.js} +118 -1
- package/dist/{index-tnazr5tc.js → index-pz7ytdga.js} +3 -3
- package/dist/{index-1z5xxfst.js → index-rqpdar1e.js} +102 -5
- package/dist/{index-qm87g51s.js → index-s4c8wy8m.js} +1 -1
- package/dist/node.js +2 -2
- package/dist/observability/index.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -2
- package/dist/server/request.d.ts +19 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/telegram/init-data.d.ts +134 -0
- package/dist/telegram/init-data.d.ts.map +1 -0
- package/dist/telegram/send-failure.d.ts +73 -0
- package/dist/telegram/send-failure.d.ts.map +1 -0
- package/dist/telegram.d.ts +3 -0
- package/dist/telegram.d.ts.map +1 -0
- package/dist/telegram.js +224 -0
- package/dist/tool-invoker.js +4 -4
- package/dist/tools.js +7 -7
- package/llms-full.txt +266 -9
- package/native/darwin-arm64.node +0 -0
- package/native/darwin-x64.node +0 -0
- package/package.json +6 -2
package/dist/telegram.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord
|
|
3
|
+
} from "./index-qyrqwr4c.js";
|
|
4
|
+
|
|
5
|
+
// src/telegram/init-data.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var TelegramInitDataUserSchema = z.object({
|
|
8
|
+
id: z.int(),
|
|
9
|
+
is_bot: z.boolean().optional(),
|
|
10
|
+
first_name: z.string(),
|
|
11
|
+
last_name: z.string().optional(),
|
|
12
|
+
username: z.string().optional(),
|
|
13
|
+
language_code: z.string().optional(),
|
|
14
|
+
is_premium: z.boolean().optional(),
|
|
15
|
+
allows_write_to_pm: z.boolean().optional(),
|
|
16
|
+
photo_url: z.string().optional()
|
|
17
|
+
}).transform((user) => ({
|
|
18
|
+
id: user.id,
|
|
19
|
+
firstName: user.first_name,
|
|
20
|
+
...user.is_bot !== undefined && { isBot: user.is_bot },
|
|
21
|
+
...user.last_name !== undefined && { lastName: user.last_name },
|
|
22
|
+
...user.username !== undefined && { username: user.username },
|
|
23
|
+
...user.language_code !== undefined && { languageCode: user.language_code },
|
|
24
|
+
...user.is_premium !== undefined && { isPremium: user.is_premium },
|
|
25
|
+
...user.allows_write_to_pm !== undefined && { allowsWriteToPm: user.allows_write_to_pm },
|
|
26
|
+
...user.photo_url !== undefined && { photoUrl: user.photo_url }
|
|
27
|
+
}));
|
|
28
|
+
var encoder = new TextEncoder;
|
|
29
|
+
async function hmacSha256(key, message) {
|
|
30
|
+
const imported = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
31
|
+
return crypto.subtle.sign("HMAC", imported, encoder.encode(message));
|
|
32
|
+
}
|
|
33
|
+
function toHex(buffer) {
|
|
34
|
+
return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
35
|
+
}
|
|
36
|
+
function digestsEqual(a, b) {
|
|
37
|
+
if (a.length !== b.length)
|
|
38
|
+
return false;
|
|
39
|
+
let difference = 0;
|
|
40
|
+
for (let index = 0;index < a.length; index += 1) {
|
|
41
|
+
difference |= a.charCodeAt(index) ^ b.charCodeAt(index);
|
|
42
|
+
}
|
|
43
|
+
return difference === 0;
|
|
44
|
+
}
|
|
45
|
+
function optionalUser(raw, key) {
|
|
46
|
+
const encoded = raw[key];
|
|
47
|
+
if (encoded === undefined)
|
|
48
|
+
return;
|
|
49
|
+
return TelegramInitDataUserSchema.parse(JSON.parse(encoded));
|
|
50
|
+
}
|
|
51
|
+
async function verifyTelegramInitData(options) {
|
|
52
|
+
if (!options.botToken)
|
|
53
|
+
throw new TypeError("botToken is required to verify initData");
|
|
54
|
+
if (options.maxAgeSeconds !== undefined && (!Number.isFinite(options.maxAgeSeconds) || options.maxAgeSeconds < 0)) {
|
|
55
|
+
throw new TypeError("maxAgeSeconds must be a non-negative finite number");
|
|
56
|
+
}
|
|
57
|
+
const params = new URLSearchParams(options.initData);
|
|
58
|
+
const hash = params.get("hash");
|
|
59
|
+
if (!hash)
|
|
60
|
+
return { valid: false, reason: "missing-hash" };
|
|
61
|
+
const signed = {};
|
|
62
|
+
for (const [key, value] of params) {
|
|
63
|
+
if (key === "hash")
|
|
64
|
+
continue;
|
|
65
|
+
signed[key] = value;
|
|
66
|
+
}
|
|
67
|
+
const dataCheckString = Object.keys(signed).sort().map((key) => `${key}=${signed[key]}`).join(`
|
|
68
|
+
`);
|
|
69
|
+
const secretKey = await hmacSha256(encoder.encode("WebAppData"), options.botToken);
|
|
70
|
+
const expected = toHex(await hmacSha256(secretKey, dataCheckString));
|
|
71
|
+
if (!digestsEqual(expected, hash.toLowerCase())) {
|
|
72
|
+
return { valid: false, reason: "signature-mismatch" };
|
|
73
|
+
}
|
|
74
|
+
const authDateSeconds = Number(signed.auth_date);
|
|
75
|
+
if (!Number.isSafeInteger(authDateSeconds) || authDateSeconds <= 0) {
|
|
76
|
+
return { valid: false, reason: "malformed" };
|
|
77
|
+
}
|
|
78
|
+
const nowMs = options.now?.() ?? Date.now();
|
|
79
|
+
const ageSeconds = Math.floor(nowMs / 1000) - authDateSeconds;
|
|
80
|
+
if (options.maxAgeSeconds !== undefined && ageSeconds > options.maxAgeSeconds) {
|
|
81
|
+
return { valid: false, reason: "expired", ageSeconds };
|
|
82
|
+
}
|
|
83
|
+
let user;
|
|
84
|
+
let receiver;
|
|
85
|
+
try {
|
|
86
|
+
user = optionalUser(signed, "user");
|
|
87
|
+
receiver = optionalUser(signed, "receiver");
|
|
88
|
+
} catch {
|
|
89
|
+
return { valid: false, reason: "malformed" };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
valid: true,
|
|
93
|
+
data: {
|
|
94
|
+
...user !== undefined && { user },
|
|
95
|
+
...receiver !== undefined && { receiver },
|
|
96
|
+
authDate: new Date(authDateSeconds * 1000),
|
|
97
|
+
ageSeconds,
|
|
98
|
+
...signed.query_id !== undefined && { queryId: signed.query_id },
|
|
99
|
+
...signed.start_param !== undefined && { startParam: signed.start_param },
|
|
100
|
+
...signed.chat_type !== undefined && { chatType: signed.chat_type },
|
|
101
|
+
...signed.chat_instance !== undefined && { chatInstance: signed.chat_instance },
|
|
102
|
+
raw: Object.freeze({ ...signed })
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// src/telegram/send-failure.ts
|
|
107
|
+
var BY_DESCRIPTION = [
|
|
108
|
+
[/bot was blocked by the user|bot was kicked/i, "blocked-by-user"],
|
|
109
|
+
[/user is deactivated|account is deactivated/i, "user-deactivated"],
|
|
110
|
+
[/can'?t initiate conversation|need to start a conversation/i, "not-started"],
|
|
111
|
+
[/chat not found|peer_id_invalid|user not found/i, "chat-not-found"],
|
|
112
|
+
[/too many requests|retry later|flood/i, "rate-limited"],
|
|
113
|
+
[
|
|
114
|
+
/message is too long|can'?t parse entities|message text is empty|wrong file identifier|button_url_invalid/i,
|
|
115
|
+
"message-invalid"
|
|
116
|
+
]
|
|
117
|
+
];
|
|
118
|
+
var RETRYABLE = new Set([
|
|
119
|
+
"rate-limited",
|
|
120
|
+
"server-error"
|
|
121
|
+
]);
|
|
122
|
+
var UNREACHABLE = new Set([
|
|
123
|
+
"blocked-by-user",
|
|
124
|
+
"user-deactivated",
|
|
125
|
+
"chat-not-found",
|
|
126
|
+
"not-started"
|
|
127
|
+
]);
|
|
128
|
+
function numberAt(value, keys) {
|
|
129
|
+
if (!isRecord(value))
|
|
130
|
+
return;
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
const candidate = value[key];
|
|
133
|
+
if (typeof candidate === "number" && Number.isFinite(candidate))
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
function statusOf(value, depth = 0) {
|
|
139
|
+
if (!isRecord(value) || depth > 4)
|
|
140
|
+
return;
|
|
141
|
+
const direct = numberAt(value, ["error_code", "statusCode", "status"]);
|
|
142
|
+
if (direct !== undefined && direct >= 400 && direct < 600)
|
|
143
|
+
return direct;
|
|
144
|
+
const nested = value.cause ?? value.response ?? value.error;
|
|
145
|
+
return nested === value ? undefined : statusOf(nested, depth + 1);
|
|
146
|
+
}
|
|
147
|
+
function retryAfterOf(value, depth = 0) {
|
|
148
|
+
if (!isRecord(value) || depth > 4)
|
|
149
|
+
return;
|
|
150
|
+
const direct = numberAt(value.parameters, ["retry_after"]);
|
|
151
|
+
if (direct !== undefined)
|
|
152
|
+
return direct;
|
|
153
|
+
const nested = value.cause ?? value.response ?? value.error;
|
|
154
|
+
return nested === value ? undefined : retryAfterOf(nested, depth + 1);
|
|
155
|
+
}
|
|
156
|
+
function descriptionOf(value, depth = 0) {
|
|
157
|
+
if (typeof value === "string")
|
|
158
|
+
return value;
|
|
159
|
+
if (value instanceof Error) {
|
|
160
|
+
const nested2 = isRecord(value) ? descriptionOf(value.cause, depth + 1) : "";
|
|
161
|
+
return `${value.message} ${nested2}`.trim();
|
|
162
|
+
}
|
|
163
|
+
if (!isRecord(value) || depth > 4)
|
|
164
|
+
return "";
|
|
165
|
+
const own = typeof value.description === "string" ? value.description : "";
|
|
166
|
+
const message = typeof value.message === "string" ? value.message : "";
|
|
167
|
+
const nested = descriptionOf(value.cause ?? value.response ?? value.error, depth + 1);
|
|
168
|
+
return [own, message, nested].filter(Boolean).join(" ");
|
|
169
|
+
}
|
|
170
|
+
function classifyTelegramSendFailure(error) {
|
|
171
|
+
const status = statusOf(error);
|
|
172
|
+
const retryAfterSeconds = retryAfterOf(error);
|
|
173
|
+
const description = descriptionOf(error);
|
|
174
|
+
if (retryAfterSeconds !== undefined) {
|
|
175
|
+
return {
|
|
176
|
+
reason: "rate-limited",
|
|
177
|
+
...status !== undefined && { status },
|
|
178
|
+
retryAfterSeconds,
|
|
179
|
+
retryable: true,
|
|
180
|
+
recipientUnreachable: false,
|
|
181
|
+
evidence: "parameters"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
for (const [pattern, reason] of BY_DESCRIPTION) {
|
|
185
|
+
if (pattern.test(description)) {
|
|
186
|
+
return {
|
|
187
|
+
reason,
|
|
188
|
+
...status !== undefined && { status },
|
|
189
|
+
retryable: RETRYABLE.has(reason),
|
|
190
|
+
recipientUnreachable: UNREACHABLE.has(reason),
|
|
191
|
+
evidence: "description"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (status === 429) {
|
|
196
|
+
return {
|
|
197
|
+
reason: "rate-limited",
|
|
198
|
+
status,
|
|
199
|
+
retryable: true,
|
|
200
|
+
recipientUnreachable: false,
|
|
201
|
+
evidence: "status"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (status !== undefined && status >= 500) {
|
|
205
|
+
return {
|
|
206
|
+
reason: "server-error",
|
|
207
|
+
status,
|
|
208
|
+
retryable: true,
|
|
209
|
+
recipientUnreachable: false,
|
|
210
|
+
evidence: "status"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
reason: "unknown",
|
|
215
|
+
...status !== undefined && { status },
|
|
216
|
+
retryable: false,
|
|
217
|
+
recipientUnreachable: false,
|
|
218
|
+
evidence: "none"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
verifyTelegramInitData,
|
|
223
|
+
classifyTelegramSendFailure
|
|
224
|
+
};
|
package/dist/tool-invoker.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createToolInvoker
|
|
3
|
-
} from "./index-
|
|
4
|
-
import"./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
3
|
+
} from "./index-jqtsc9mj.js";
|
|
4
|
+
import"./index-7qy2ex0m.js";
|
|
5
|
+
import"./index-s4c8wy8m.js";
|
|
6
|
+
import"./index-nemjkxjp.js";
|
|
7
7
|
import"./index-22by16v6.js";
|
|
8
8
|
import"./index-cby4ar3v.js";
|
|
9
9
|
import"./index-nt1mp8km.js";
|
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
buildToolManifest,
|
|
6
6
|
mountAgent
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-ezmn6ac6.js";
|
|
8
8
|
import"./index-3xnq72rz.js";
|
|
9
9
|
import {
|
|
10
10
|
signJwt,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "./index-vbf2p6me.js";
|
|
21
21
|
import {
|
|
22
22
|
createToolInvoker
|
|
23
|
-
} from "./index-
|
|
23
|
+
} from "./index-jqtsc9mj.js";
|
|
24
24
|
import {
|
|
25
25
|
WaitTimeoutError,
|
|
26
26
|
createCli,
|
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
fetchPinnedDocument,
|
|
30
30
|
readCapped,
|
|
31
31
|
runWaitOperation
|
|
32
|
-
} from "./index-
|
|
32
|
+
} from "./index-pz7ytdga.js";
|
|
33
33
|
import {
|
|
34
34
|
collectToolSurface
|
|
35
|
-
} from "./index-
|
|
35
|
+
} from "./index-k2zczx1g.js";
|
|
36
36
|
import {
|
|
37
37
|
createRuntimeToolFactory,
|
|
38
38
|
defineRuntimeTool
|
|
@@ -41,19 +41,19 @@ import {
|
|
|
41
41
|
collectTools,
|
|
42
42
|
createToolRunner,
|
|
43
43
|
formatToolError
|
|
44
|
-
} from "./index-
|
|
44
|
+
} from "./index-7qy2ex0m.js";
|
|
45
45
|
import {
|
|
46
46
|
ToolExecutionControlError,
|
|
47
47
|
coerceJsonArgs,
|
|
48
48
|
executeToolMethod,
|
|
49
49
|
isToolExecutionControlError,
|
|
50
50
|
toolResultFromError
|
|
51
|
-
} from "./index-
|
|
51
|
+
} from "./index-s4c8wy8m.js";
|
|
52
52
|
import {
|
|
53
53
|
getRequestContext,
|
|
54
54
|
getTraceId,
|
|
55
55
|
runWithRequestContext
|
|
56
|
-
} from "./index-
|
|
56
|
+
} from "./index-nemjkxjp.js";
|
|
57
57
|
import {
|
|
58
58
|
ManagedFileError
|
|
59
59
|
} from "./index-bfcpjw20.js";
|
package/llms-full.txt
CHANGED
|
@@ -55,11 +55,12 @@ own, recorded as an ADR.
|
|
|
55
55
|
| `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
|
|
56
56
|
| `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
|
|
57
57
|
| `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
|
|
58
|
+
| `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification and Bot API send-failure classification |
|
|
58
59
|
| `stitchkit/observability` | server | stable | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
59
60
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
60
61
|
| `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
61
62
|
| `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
|
|
62
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the
|
|
63
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 16 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
63
64
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
64
65
|
| `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
|
|
65
66
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
@@ -67,7 +68,7 @@ own, recorded as an ADR.
|
|
|
67
68
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
68
69
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
69
70
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
70
|
-
| `stitchkit/application` | server | evolving<br>_redefined in 3 of the
|
|
71
|
+
| `stitchkit/application` | server | evolving<br>_redefined in 3 of the 16 minors since 0.56.2, most recently 0.67.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
|
|
71
72
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
72
73
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
73
74
|
|
|
@@ -180,6 +181,7 @@ map — feature → packages:
|
|
|
180
181
|
| **Socket.IO server on Node** | `socket.io` |
|
|
181
182
|
| Socket.IO client | `socket.io-client` (runtime peer; unrelated root declarations remain peer-free) |
|
|
182
183
|
| grammY lifecycle adapters (`stitchkit/application/grammy`) | `grammy` |
|
|
184
|
+
| Telegram platform primitives (`stitchkit/telegram`) | — (peer-free) |
|
|
183
185
|
| OpenTelemetry gauges (`stitchkit/application/opentelemetry`) | `@opentelemetry/api` |
|
|
184
186
|
|
|
185
187
|
```bash
|
|
@@ -4101,7 +4103,8 @@ responses and results with a different call or tool name are invalid; dropping a
|
|
|
4101
4103
|
approval input fails the run with a private diagnostic rather than starting a fresh model turn.
|
|
4102
4104
|
|
|
4103
4105
|
`stitchkit/agent-runtime/coding-tools` returns ordinary direct runtime tools named `read_file`,
|
|
4104
|
-
`write_file`, `
|
|
4106
|
+
`write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command` and optional
|
|
4107
|
+
`read_output`. Every call passes a
|
|
4105
4108
|
required host authorization callback. File paths are relative, bounded and contained after
|
|
4106
4109
|
descriptor-relative resolution: each ancestor is opened without following symlinks and remains
|
|
4107
4110
|
pinned through authorization and the filesystem effect. Reads revalidate the pinned file identity;
|
|
@@ -4117,10 +4120,32 @@ Arguments, output and time are bounded, while cancellation terminates the child.
|
|
|
4117
4120
|
root and cwd are path boundaries, not a security sandbox: isolate the process when an executable
|
|
4118
4121
|
must not access the rest of the machine.
|
|
4119
4122
|
|
|
4120
|
-
`
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4123
|
+
`edit_file` replaces one exact snippet. `oldText` is itself the freshness guard for the region it
|
|
4124
|
+
changes, so the digest is the optional `expectedSha256` and an edit is one call; pass the digest
|
|
4125
|
+
`read_file` returned when you want the whole-file guarantee, and read the `sha256` it returns to
|
|
4126
|
+
chain the next edit without re-reading. The read, the occurrence count and the construction of the
|
|
4127
|
+
new content all happen inside one per-target lock, so two concurrent edits of different snippets in
|
|
4128
|
+
one file cannot each build a file from the same base and have the second erase the first — the lock
|
|
4129
|
+
is process-local, which covers many agents in one process and not two processes over one workspace.
|
|
4130
|
+
It authorizes the exact replacement count, result digest and byte size, and deliberately does not
|
|
4131
|
+
claim multi-file atomicity.
|
|
4132
|
+
|
|
4133
|
+
`write_file` creates missing parent directories inside the root and names them in
|
|
4134
|
+
`createdDirectories`; the walk that finds them runs before authorization and reports them in the
|
|
4135
|
+
authorization payload, so no directory is created before a host approves it.
|
|
4136
|
+
|
|
4137
|
+
**Every ordinary outcome is a refusal a model can act on** — a missing file is `NOT_FOUND`, an
|
|
4138
|
+
existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is `CONFLICT` carrying its
|
|
4139
|
+
occurrence count, a path outside the root is `FORBIDDEN` — each with a `hint` naming the next move.
|
|
4140
|
+
Host-level causes stay scrubbed to `INTERNAL_SERVER_ERROR` and name nothing outside the workspace.
|
|
4141
|
+
→ ADR 0139
|
|
4142
|
+
|
|
4143
|
+
`list_directory` marks excluded directories rather than hiding them, and `glob` reports
|
|
4144
|
+
`skippedDirectories` beside its matches: an empty result from a tree whose files all live under an
|
|
4145
|
+
excluded directory is not "no files", and a model told only "nothing found" concludes the wrong
|
|
4146
|
+
thing. `search_files` takes `regex`, `context` lines and an `include` pattern; regex is bounded by
|
|
4147
|
+
refusing backreferences and lookaround and capping line length rather than by a timeout, because a
|
|
4148
|
+
JavaScript `RegExp` cannot be interrupted once it starts backtracking. With an optional `AgentCodingArtifactStore`, shell output beyond the inline
|
|
4124
4149
|
preview continues into an opaque bounded artifact and `read_output` reads slices without
|
|
4125
4150
|
exposing a host path. Without a store, the previous finite output-limit behavior is unchanged.
|
|
4126
4151
|
|
|
@@ -4807,6 +4832,40 @@ replace a leading summary on the next compaction.
|
|
|
4807
4832
|
Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
|
|
4808
4833
|
eligible range and recomputes the summary; the stale summary is never retried.
|
|
4809
4834
|
|
|
4835
|
+
### Compacting without the store
|
|
4836
|
+
|
|
4837
|
+
The *selection* inside compaction needs no store, no version and no runtime, so
|
|
4838
|
+
it is published on its own as `selectCompactableHistory` — for an application
|
|
4839
|
+
that drives the model itself and keeps its own history (→ ADR 0142):
|
|
4840
|
+
|
|
4841
|
+
```ts
|
|
4842
|
+
import { selectCompactableHistory } from 'stitchkit/agent-runtime'
|
|
4843
|
+
|
|
4844
|
+
const { leadingSummary, compactable, retained } = selectCompactableHistory({
|
|
4845
|
+
messages,
|
|
4846
|
+
keepRecentTurns: 3,
|
|
4847
|
+
})
|
|
4848
|
+
if (compactable.length > 0) {
|
|
4849
|
+
const summary = await summarize(compactable, leadingSummary)
|
|
4850
|
+
messages = [summary, ...retained] // replace the old summary, never stack one
|
|
4851
|
+
}
|
|
4852
|
+
```
|
|
4853
|
+
|
|
4854
|
+
`compactable` is the oldest **whole complete** turns and `retained` is
|
|
4855
|
+
everything the model must still hear; together they are the input, in order. A
|
|
4856
|
+
turn holding a tool call whose result never arrived is never eligible, and
|
|
4857
|
+
neither is anything after it: half a turn hands the provider a call with no
|
|
4858
|
+
result, which most of them refuse outright.
|
|
4859
|
+
|
|
4860
|
+
`structuredCompaction` calls this same function, so the published selection and
|
|
4861
|
+
the runtime's cannot drift into two behaviours. What it adds is the part that
|
|
4862
|
+
does need the store — writing the result back under a version check.
|
|
4863
|
+
|
|
4864
|
+
Deciding *when* to compact is yours: `AgentRuntimeRunContext.contextUsage`
|
|
4865
|
+
carries the last step's prompt size beside the model's window, and dividing them
|
|
4866
|
+
is one line where the threshold is decided. The core does not own that ratio, on
|
|
4867
|
+
purpose — see ADR 0142 for what it publishes and what it declines to.
|
|
4868
|
+
|
|
4810
4869
|
## Observability
|
|
4811
4870
|
|
|
4812
4871
|
`createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
|
|
@@ -7445,6 +7504,68 @@ const payload = await verifyJwt(token, env.JWT_SECRET)
|
|
|
7445
7504
|
`extractToken(req, cookieName?)` reads a bearer token from the `Authorization`
|
|
7446
7505
|
header, or from the named cookie.
|
|
7447
7506
|
|
|
7507
|
+
## Telegram Mini Apps
|
|
7508
|
+
|
|
7509
|
+
A Mini App hands its backend an `initData` query string that Telegram signed
|
|
7510
|
+
with a key derived from the bot token. `verifyTelegramInitData` checks that
|
|
7511
|
+
signature and reports what the string says — and refuses with a **reason**,
|
|
7512
|
+
because an application answers a stale string differently from a forged one.
|
|
7513
|
+
|
|
7514
|
+
```ts
|
|
7515
|
+
import { verifyTelegramInitData } from 'stitchkit/telegram'
|
|
7516
|
+
import { unauthorized } from 'stitchkit/contract'
|
|
7517
|
+
import { env } from './env'
|
|
7518
|
+
|
|
7519
|
+
const result = await verifyTelegramInitData({
|
|
7520
|
+
initData,
|
|
7521
|
+
botToken: env.BOT_TOKEN,
|
|
7522
|
+
maxAgeSeconds: 3600, // a signed string is otherwise valid forever
|
|
7523
|
+
})
|
|
7524
|
+
if (!result.valid) {
|
|
7525
|
+
// 'missing-hash' | 'signature-mismatch' | 'malformed' | 'expired'
|
|
7526
|
+
throw unauthorized(result.reason === 'expired' ? 'Reopen the app' : 'Invalid session')
|
|
7527
|
+
}
|
|
7528
|
+
const telegramId = result.data.user?.id
|
|
7529
|
+
```
|
|
7530
|
+
|
|
7531
|
+
Three things it does that a hand-written check usually does not:
|
|
7532
|
+
|
|
7533
|
+
- the **signature is checked first**, before `auth_date` or anything else is
|
|
7534
|
+
read — an expiry inside an unverified payload is a number the sender chose;
|
|
7535
|
+
- digests are compared **without an early exit**, so the time a rejection takes
|
|
7536
|
+
is not a measurement of how much of the digest was right;
|
|
7537
|
+
- `raw` keeps every signed pair, so a field Telegram adds after this release
|
|
7538
|
+
does not need a release to reach.
|
|
7539
|
+
|
|
7540
|
+
`maxAgeSeconds` is optional and there is no default: only the application knows
|
|
7541
|
+
how long its own session is worth. Omitting it means the string never expires,
|
|
7542
|
+
which is a decision rather than an oversight.
|
|
7543
|
+
|
|
7544
|
+
The module is server-only and pulls in no bot library — the token never belongs
|
|
7545
|
+
in a browser bundle. `stitchkit/application/grammy` remains the lifecycle
|
|
7546
|
+
adapter for an injected grammY bot. Its companion,
|
|
7547
|
+
`classifyTelegramSendFailure`, names why a send was refused and separates *retry
|
|
7548
|
+
this send* from *stop addressing this recipient*:
|
|
7549
|
+
|
|
7550
|
+
```ts
|
|
7551
|
+
import { classifyTelegramSendFailure } from 'stitchkit/telegram'
|
|
7552
|
+
|
|
7553
|
+
try {
|
|
7554
|
+
await bot.api.sendMessage(chatId, text)
|
|
7555
|
+
} catch (error) {
|
|
7556
|
+
const failure = classifyTelegramSendFailure(error)
|
|
7557
|
+
if (failure.recipientUnreachable) await markUnreachable(chatId, failure.reason)
|
|
7558
|
+
else if (failure.retryable) await requeue(chatId, failure.retryAfterSeconds)
|
|
7559
|
+
}
|
|
7560
|
+
```
|
|
7561
|
+
|
|
7562
|
+
The two flags are separate because one answer cannot serve both. A rate limit is
|
|
7563
|
+
retryable and implicates nobody; a blocked user is unreachable and no retry
|
|
7564
|
+
helps; a message Telegram could not parse is *neither* — the recipient is fine
|
|
7565
|
+
and our payload is wrong, which is the case a list of substrings quietly counts
|
|
7566
|
+
against the user. An unrecognised refusal leaves the recipient reachable: losing
|
|
7567
|
+
a working subscriber forever costs more than one wasted send.
|
|
7568
|
+
|
|
7448
7569
|
## Cookies
|
|
7449
7570
|
|
|
7450
7571
|
```ts
|
|
@@ -9172,6 +9293,94 @@ additive** — adopting it changes nothing in your code. (See
|
|
|
9172
9293
|
So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
|
|
9173
9294
|
current one *up to* your target, and apply each snippet.
|
|
9174
9295
|
|
|
9296
|
+
## Released migration: 0.71.0
|
|
9297
|
+
|
|
9298
|
+
The Agent coding tools. Two of the three changes are visible to the compiler;
|
|
9299
|
+
the third is the one to read carefully, because nothing will point at it.
|
|
9300
|
+
|
|
9301
|
+
### If you implement `authorize`
|
|
9302
|
+
|
|
9303
|
+
The operation union gained `edit`, `list` and `glob` and lost `patch`. An
|
|
9304
|
+
exhaustive matcher stops compiling and the compiler shows you every arm. **A
|
|
9305
|
+
matcher with a default branch does not**, and that is the dangerous case:
|
|
9306
|
+
|
|
9307
|
+
```ts
|
|
9308
|
+
// before — and after this upgrade, silently wrong in both directions
|
|
9309
|
+
if (request.operation === 'patch') return reviewPatch(request)
|
|
9310
|
+
return true // now also authorizes edit, list and glob
|
|
9311
|
+
// …or: return false // now also kills edit_file
|
|
9312
|
+
```
|
|
9313
|
+
|
|
9314
|
+
```ts
|
|
9315
|
+
// after
|
|
9316
|
+
switch (request.operation) {
|
|
9317
|
+
case 'edit': return reviewEdit(request) // the old `patch` payload, unchanged
|
|
9318
|
+
case 'list':
|
|
9319
|
+
case 'glob': return true // or your own policy
|
|
9320
|
+
// …existing read / write / search / shell / artifact-read arms
|
|
9321
|
+
}
|
|
9322
|
+
```
|
|
9323
|
+
|
|
9324
|
+
`write` gained `createsDirectories` — the workspace-relative directories the
|
|
9325
|
+
call would create, outermost first, reported **before** anything is created. A
|
|
9326
|
+
host that wants to refuse implicit directory creation now can.
|
|
9327
|
+
|
|
9328
|
+
### If you call `apply_patch`
|
|
9329
|
+
|
|
9330
|
+
It is `edit_file`, and it is one call:
|
|
9331
|
+
|
|
9332
|
+
```ts
|
|
9333
|
+
// before
|
|
9334
|
+
const read = await readFile({ path })
|
|
9335
|
+
await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: true })
|
|
9336
|
+
await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: false })
|
|
9337
|
+
|
|
9338
|
+
// after
|
|
9339
|
+
await editFile({ path, oldText, newText })
|
|
9340
|
+
```
|
|
9341
|
+
|
|
9342
|
+
`expectedSha256` is optional and still refuses a stale base with `CONFLICT` when
|
|
9343
|
+
you pass it; `edit_file` returns the resulting `sha256`, so a chain of edits
|
|
9344
|
+
never needs to re-read. If you key an approval policy or a UI label on the string
|
|
9345
|
+
`apply_patch`, update the key — nothing will fail loudly.
|
|
9346
|
+
|
|
9347
|
+
### If you match on `INTERNAL_SERVER_ERROR` from a coding tool
|
|
9348
|
+
|
|
9349
|
+
Ordinary outcomes no longer arrive that way. A missing file is `NOT_FOUND`, an
|
|
9350
|
+
existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is
|
|
9351
|
+
`CONFLICT` with the occurrence count, a path outside the root is `FORBIDDEN`.
|
|
9352
|
+
Code that treated any coding-tool failure as an internal fault will now see
|
|
9353
|
+
codes it did not before; code that showed the model an empty error now has a
|
|
9354
|
+
sentence and a `hint` to show it. Host-level causes are unchanged and still
|
|
9355
|
+
scrubbed.
|
|
9356
|
+
|
|
9357
|
+
### If you write files into new directories
|
|
9358
|
+
|
|
9359
|
+
Nothing to change: `write_file` creates missing parents inside the root. Read
|
|
9360
|
+
`createdDirectories` in the result if you want to notice a typo — a path that
|
|
9361
|
+
was a failure before is now a successful write into a new tree.
|
|
9362
|
+
|
|
9363
|
+
### If you want a step to know its context budget
|
|
9364
|
+
|
|
9365
|
+
Opt in by reading it; nothing is injected for you:
|
|
9366
|
+
|
|
9367
|
+
```ts
|
|
9368
|
+
loop: {
|
|
9369
|
+
prepareStep: ({ contextUsage }) => {
|
|
9370
|
+
const used = contextUsage?.usedTokens
|
|
9371
|
+
if (used?.provenance === 'unavailable') return {} // no step has landed yet
|
|
9372
|
+
const fraction = (used?.value ?? 0) / (contextUsage?.contextWindow ?? 1)
|
|
9373
|
+
// …render it wherever you put it
|
|
9374
|
+
return {}
|
|
9375
|
+
},
|
|
9376
|
+
}
|
|
9377
|
+
```
|
|
9378
|
+
|
|
9379
|
+
Put it at the **tail** of the conversation rather than in the system
|
|
9380
|
+
instructions unless you have a reason: changing the system prompt on every step
|
|
9381
|
+
invalidates the provider's prefix cache for the whole conversation, and on a long
|
|
9382
|
+
run that is a multiple of the input cost.
|
|
9383
|
+
|
|
9175
9384
|
## Released migration: 0.70.0
|
|
9176
9385
|
|
|
9177
9386
|
### Descriptor-backed Agent filesystem containment
|
|
@@ -12143,6 +12352,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
12143
12352
|
| `generateTraceId` | function | a fresh trace id |
|
|
12144
12353
|
| `resolveTraceId` | function | the default per-request trace-id resolver |
|
|
12145
12354
|
| `extractIp` | function | the caller IP from a request |
|
|
12355
|
+
| `isPublicIp` | function | whether an address belongs to the public internet; anything unparseable is not |
|
|
12146
12356
|
| `resolveSocketIp` | function | the caller IP for a Socket.IO handshake (proxy-aware) |
|
|
12147
12357
|
| `getClientInfo` | function | caller IP + user-agent |
|
|
12148
12358
|
| `EventBus` | _type_ | the `createEventBus` handle |
|
|
@@ -12397,6 +12607,8 @@ Server-only optional application runtime. See the
|
|
|
12397
12607
|
| `AgentConversationMessagePageSchema` / `AgentConversationMessagePage` | schema / _type_ | cursor-paged durable message history |
|
|
12398
12608
|
| `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware signed context budget; irreducible reservation deficits are `oversized`, not compactable history |
|
|
12399
12609
|
| `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
|
|
12610
|
+
| `selectCompactableHistory` | function | which oldest whole complete turns may be summarised away — the half of compaction that needs no store (→ ADR 0142) |
|
|
12611
|
+
| `SelectCompactableHistoryOptions` / `CompactableHistory` | _type_ | message list, retained-turn count and evidence policy in; `leadingSummary`, `compactable` and `retained` out |
|
|
12400
12612
|
| `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
|
|
12401
12613
|
| `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
|
|
12402
12614
|
| `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
|
|
@@ -12425,6 +12637,29 @@ transport adapters validate the same records. Runtime composition types are `Age
|
|
|
12425
12637
|
`AgentSessionCoordinator`, `AgentCompactionContext`, `AgentCompactionResult` and
|
|
12426
12638
|
`StructuredCompactionConfig`.
|
|
12427
12639
|
|
|
12640
|
+
A provider refusal is classified rather than phrased. `classifyProviderFailure` returns an
|
|
12641
|
+
`AgentProviderFailure` — an `AgentProviderFailureReason` (`insufficient-credits`, `rate-limited`,
|
|
12642
|
+
`model-unavailable`, `context-overflow`, `timeout`, `cancelled`, `unknown`), the provider's `status`
|
|
12643
|
+
when it supplied one, whether the same request is `retryable` unchanged, and the `evidence` the
|
|
12644
|
+
answer rests on: `status` is the provider stating its own answer, `message` is us reading its prose,
|
|
12645
|
+
and `none` is an honest refusal to guess. The sentence a user reads stays with the application —
|
|
12646
|
+
its tone and its decision about what to admit are not the core's to make. `isToolResultFailure`
|
|
12647
|
+
recognises a failure carried inside a *successful* tool result, in both the bare and the
|
|
12648
|
+
`{ value: … }` envelope. Both are plain functions and need no runtime. → ADR 0141
|
|
12649
|
+
|
|
12650
|
+
`normalizeOpenRouterUsage` is the same normalisation `openRouterProvider`
|
|
12651
|
+
applies, exported so an application calling the SDK directly gets provenance-correct numbers
|
|
12652
|
+
without adopting the runtime.
|
|
12653
|
+
|
|
12654
|
+
`AgentContextUsage` reaches every step through `AgentRuntimeRunContext.contextUsage`: how full the
|
|
12655
|
+
model's context is, as `usedTokens` (an `AgentUsageValue`, so it carries the provenance that says
|
|
12656
|
+
where the number came from) beside the model's declared `contextWindow`. It is the **last completed
|
|
12657
|
+
step's prompt size**, not the run's cumulative input tokens — cumulative counts every step's prompt
|
|
12658
|
+
again and is a multiple of the real fill. Before the first step lands there is no provider-reported
|
|
12659
|
+
number and the provenance is `unavailable`, which is a different fact from zero. No fraction is
|
|
12660
|
+
exposed: dividing is one line where it is rendered, and the output reserve belongs to the
|
|
12661
|
+
consumer's prompt budget rather than to this layer.
|
|
12662
|
+
|
|
12428
12663
|
Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentTerminalAcceptance`,
|
|
12429
12664
|
`AgentTerminalAcceptanceInput`, `hasAgentTerminalOutput`, `AgentRecordIdSchema`, `AgentRecordVersionSchema`,
|
|
12430
12665
|
`AgentTimestampSchema`, `AgentJsonObjectSchema`, `AgentProviderEnvelopeSchema`,
|
|
@@ -12566,12 +12801,14 @@ explicit policy; use the Bun or Node SQLite leaf for durable reopen.
|
|
|
12566
12801
|
## `stitchkit/agent-runtime/coding-tools`
|
|
12567
12802
|
|
|
12568
12803
|
Server-only evolving, peer-free direct runtime tools. `createAgentCodingTools(config)` returns
|
|
12569
|
-
`read_file`, `write_file`, `
|
|
12804
|
+
`read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, optional
|
|
12805
|
+
`run_command` and, when an
|
|
12570
12806
|
artifact store is supplied, `read_output`.
|
|
12571
12807
|
|
|
12572
12808
|
| Export | Kind | Summary |
|
|
12573
12809
|
|--------|------|---------|
|
|
12574
|
-
| `createAgentCodingTools` | function | construct direct host-authorized bounded file, search,
|
|
12810
|
+
| `createAgentCodingTools` | function | construct direct host-authorized bounded file, listing, glob, search, exact-snippet edit, shell and artifact runtime-tool definitions; every ordinary refusal is a typed code with an instructive `hint`, and filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
|
|
12811
|
+
| `AGENT_CODING_TOOL_NAMES` | const | the mounted tool names — `read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command`, `read_output` |
|
|
12575
12812
|
| `AgentCodingToolDefinition` | _type_ | peer-free structural direct-tool shape accepted by the canonical runtime-tool surface |
|
|
12576
12813
|
| `AgentCodingToolConfig` | _type_ | absolute root, required authorization callback, finite executable alias map, exact child environment and optional limits |
|
|
12577
12814
|
| `AgentCodingToolAuthorizationSchema` / `AgentCodingToolAuthorization` | schema / _type_ | discriminated read/write/search/patch/shell/artifact decision presented to host policy before effect |
|
|
@@ -13094,6 +13331,26 @@ available from `stitchkit/contract`.
|
|
|
13094
13331
|
|
|
13095
13332
|
---
|
|
13096
13333
|
|
|
13334
|
+
## `stitchkit/telegram`
|
|
13335
|
+
|
|
13336
|
+
Peer-free server-only Telegram platform primitives. Importing this resolves no
|
|
13337
|
+
bot library; `stitchkit/application/grammy` remains the lifecycle adapter for an
|
|
13338
|
+
injected grammY bot. → ADR 0143
|
|
13339
|
+
|
|
13340
|
+
| Export | Kind | Summary |
|
|
13341
|
+
|--------|------|---------|
|
|
13342
|
+
| `verifyTelegramInitData` | function | verify a Mini App `initData` signature against the bot token in constant time, before reading anything out of it |
|
|
13343
|
+
| `VerifyTelegramInitDataOptions` | _type_ | raw `initData`, bot token, optional `maxAgeSeconds` bound and injected clock |
|
|
13344
|
+
| `TelegramInitDataVerification` | _type_ | `{ valid: true, data }` or a refusal carrying its reason |
|
|
13345
|
+
| `TelegramInitData` | _type_ | verified `user`/`receiver`, `authDate`, `ageSeconds`, `queryId`, `startParam`, `chatType`, `chatInstance` and every signed pair in `raw` |
|
|
13346
|
+
| `TelegramInitDataUser` | _type_ | camelCase user record inferred from Telegram's signed `user` payload |
|
|
13347
|
+
| `TelegramInitDataRefusal` | _type_ | `missing-hash` / `signature-mismatch` / `malformed` / `expired` — an expired string is not a forged one |
|
|
13348
|
+
| `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
|
|
13349
|
+
| `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
|
|
13350
|
+
| `TelegramSendFailureReason` | _type_ | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `unknown` |
|
|
13351
|
+
|
|
13352
|
+
---
|
|
13353
|
+
|
|
13097
13354
|
## `stitchkit/declaration`
|
|
13098
13355
|
|
|
13099
13356
|
Zod-only, dependency-free. The **project declaration**: the single
|
package/native/darwin-arm64.node
CHANGED
|
Binary file
|
package/native/darwin-x64.node
CHANGED
|
Binary file
|