@tangle-network/agent-app 0.60.215 → 0.60.217

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 (42) hide show
  1. package/dist/chat-routes/index.js +1 -1
  2. package/dist/{chunk-QA2CBATZ.js → chunk-4QUOPWIG.js} +12 -9
  3. package/dist/chunk-4QUOPWIG.js.map +1 -0
  4. package/dist/chunk-CD2LQXY6.js +306 -0
  5. package/dist/chunk-CD2LQXY6.js.map +1 -0
  6. package/dist/chunk-LRYY3OX7.js +105 -0
  7. package/dist/chunk-LRYY3OX7.js.map +1 -0
  8. package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
  9. package/dist/chunk-QM7RTTDY.js.map +1 -0
  10. package/dist/{chunk-KDPTXXOQ.js → chunk-WDY47GBR.js} +812 -115
  11. package/dist/chunk-WDY47GBR.js.map +1 -0
  12. package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
  13. package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
  14. package/dist/launch-invariants/catalog.d.ts +16 -0
  15. package/dist/launch-invariants/cli.js +5 -2
  16. package/dist/launch-invariants/cli.js.map +1 -1
  17. package/dist/launch-invariants/index.d.ts +1 -1
  18. package/dist/launch-invariants/index.js +13 -5
  19. package/dist/launch-invariants/index.js.map +1 -1
  20. package/dist/launch-invariants/limit-alarms.d.ts +19 -1
  21. package/dist/launch-invariants/testing.d.ts +0 -3
  22. package/dist/launch-invariants/testing.js +5 -2
  23. package/dist/sandbox/index.d.ts +3 -3
  24. package/dist/sandbox/index.js +1 -1
  25. package/dist/signoff/app-defaults.d.ts +65 -0
  26. package/dist/signoff/cache.d.ts +29 -0
  27. package/dist/signoff/cli.js +75 -4
  28. package/dist/signoff/cli.js.map +1 -1
  29. package/dist/signoff/defaults-cli.d.ts +1 -0
  30. package/dist/signoff/index.js +2 -1
  31. package/dist/signoff/store.d.ts +2 -0
  32. package/dist/signoff/types.d.ts +12 -0
  33. package/dist/signoff/worker-defaults.d.ts +8 -0
  34. package/dist/turn-health/index.js +12 -291
  35. package/dist/turn-health/index.js.map +1 -1
  36. package/dist/turn-trace/index.js +20 -3
  37. package/dist/turn-trace/index.js.map +1 -1
  38. package/dist/turn-trace/otel.d.ts +11 -4
  39. package/package.json +5 -2
  40. package/dist/chunk-KDPTXXOQ.js.map +0 -1
  41. package/dist/chunk-LLO436QO.js.map +0 -1
  42. package/dist/chunk-QA2CBATZ.js.map +0 -1
@@ -3,297 +3,18 @@ import {
3
3
  sizedBatches
4
4
  } from "../chunk-5UQJ7F3P.js";
5
5
  import {
6
- postSlackAlert
7
- } from "../chunk-F2VX6TTJ.js";
8
-
9
- // src/turn-health/classify.ts
10
- var ARTIFACT_PART_KINDS = /* @__PURE__ */ new Set(["file", "image", "work-product", "plan", "interaction"]);
11
- var KNOWN_NON_OUTPUT_PART_KINDS = /* @__PURE__ */ new Set([
12
- "reasoning",
13
- "step-start",
14
- "step-finish",
15
- "source",
16
- "source-url",
17
- "data"
18
- ]);
19
- var SAMPLE_CHARS = 120;
20
- function asRecord(value) {
21
- return typeof value === "object" && value !== null ? value : null;
22
- }
23
- function nonEmptyString(value) {
24
- return typeof value === "string" && value.trim().length > 0 ? value : null;
25
- }
26
- function isUnparseableJson(value) {
27
- const trimmed = value.trim();
28
- if (trimmed.length === 0) return false;
29
- try {
30
- JSON.parse(trimmed);
31
- return false;
32
- } catch {
33
- return true;
34
- }
35
- }
36
- var SETTLED_TOOL_STATUSES = /* @__PURE__ */ new Set(["completed", "complete", "success", "done"]);
37
- var FAILED_TOOL_STATUSES = /* @__PURE__ */ new Set(["error", "failed"]);
38
- function classifyTurnOutcome(input) {
39
- const reasons = [];
40
- if (input.failed) {
41
- reasons.push({
42
- kind: "turn_failed",
43
- reason: nonEmptyString(input.failureReason) ?? "unspecified"
44
- });
45
- }
46
- const parts = Array.isArray(input.parts) ? input.parts : [];
47
- let hasVisibleText = nonEmptyString(input.finalText) !== null;
48
- let artifactCount = 0;
49
- let toolCalls = 0;
50
- const opaqueTypes = [];
51
- let interpretedParts = 0;
52
- for (const raw of parts) {
53
- const part = asRecord(raw);
54
- if (!part) continue;
55
- const type = typeof part.type === "string" ? part.type : "";
56
- if (type === "text") {
57
- interpretedParts += 1;
58
- if (nonEmptyString(part.text) !== null) hasVisibleText = true;
59
- continue;
60
- }
61
- if (ARTIFACT_PART_KINDS.has(type)) {
62
- interpretedParts += 1;
63
- artifactCount += 1;
64
- continue;
65
- }
66
- if (KNOWN_NON_OUTPUT_PART_KINDS.has(type)) {
67
- interpretedParts += 1;
68
- continue;
69
- }
70
- if (type !== "tool") {
71
- opaqueTypes.push(type || "(missing type)");
72
- continue;
73
- }
74
- interpretedParts += 1;
75
- toolCalls += 1;
76
- const tool = nonEmptyString(part.tool) ?? "unknown";
77
- const state = asRecord(part.state);
78
- const status = typeof state?.status === "string" ? state.status : "unknown";
79
- const toolInput = state?.input;
80
- const inputRecord = asRecord(toolInput);
81
- const rejection = nonEmptyString(inputRecord?.error) ?? nonEmptyString(state?.error);
82
- if (rejection && SETTLED_TOOL_STATUSES.has(status)) {
83
- reasons.push({
84
- kind: "tool_call_rejected",
85
- // The payload names the tool the model MEANT to call; the part's own
86
- // `tool` is the harness's placeholder for a rejected call.
87
- tool: nonEmptyString(inputRecord?.tool) ?? tool,
88
- error: rejection.slice(0, 200)
89
- });
90
- continue;
91
- }
92
- if (typeof toolInput === "string" && isUnparseableJson(toolInput)) {
93
- reasons.push({
94
- kind: "malformed_tool_call",
95
- tool,
96
- inputLength: toolInput.length,
97
- sample: toolInput.slice(0, SAMPLE_CHARS)
98
- });
99
- continue;
100
- }
101
- if (!SETTLED_TOOL_STATUSES.has(status) && !FAILED_TOOL_STATUSES.has(status)) {
102
- reasons.push({ kind: "tool_call_no_effect", tool, status });
103
- }
104
- }
105
- const partsReadable = opaqueTypes.length === 0;
106
- const uniqueOpaque = [...new Set(opaqueTypes)];
107
- const unreadable = opaqueTypes.length > 0 && interpretedParts === 0 && !hasVisibleText;
108
- if (unreadable) {
109
- reasons.push({ kind: "unreadable_turn", partTypes: uniqueOpaque });
110
- return {
111
- healthy: false,
112
- severity: "warning",
113
- reasons,
114
- toolCalls,
115
- unreadable: true,
116
- partsReadable: false,
117
- opaquePartTypes: uniqueOpaque,
118
- // Structurally 0 here — `unreadable` is only reached when
119
- // `interpretedParts === 0`. Carried rather than hardcoded so the field
120
- // has one source on every return path.
121
- interpretedParts
122
- };
123
- }
124
- if (input.gated) {
125
- reasons.push({ kind: "answered_without_model" });
126
- } else if (!input.failed && !hasVisibleText && artifactCount === 0) {
127
- reasons.push({
128
- kind: "empty_completion",
129
- outputTokens: input.outputTokens ?? null,
130
- partCount: parts.length,
131
- ...input.durationMs !== void 0 ? { durationMs: input.durationMs } : {}
132
- });
133
- }
134
- return {
135
- healthy: reasons.length === 0,
136
- severity: severityOf(reasons),
137
- reasons,
138
- toolCalls,
139
- unreadable: false,
140
- partsReadable,
141
- opaquePartTypes: uniqueOpaque,
142
- interpretedParts
143
- };
144
- }
145
- function severityOf(reasons) {
146
- if (reasons.length === 0) return null;
147
- const critical = reasons.some(
148
- (r) => r.kind === "empty_completion" || r.kind === "turn_failed" || r.kind === "tool_call_rejected"
149
- );
150
- return critical ? "critical" : "warning";
151
- }
152
- function describeReason(reason) {
153
- switch (reason.kind) {
154
- case "empty_completion":
155
- return `completed with NO output (${reason.partCount} parts, outputTokens=${reason.outputTokens ?? "unknown"})`;
156
- case "malformed_tool_call":
157
- return `tool \`${reason.tool}\` arguments did not parse (${reason.inputLength} chars): ${reason.sample}`;
158
- case "tool_call_no_effect":
159
- return `tool \`${reason.tool}\` left no effect (status=${reason.status})`;
160
- case "turn_failed":
161
- return `turn failed: ${reason.reason}`;
162
- case "tool_call_rejected":
163
- return `tool \`${reason.tool}\` was REJECTED but settled as completed: ${reason.error}`;
164
- case "answered_without_model":
165
- return "answered by a pre-producer gate \u2014 the model never ran";
166
- case "unreadable_turn":
167
- return `turn could not be read: every part had an uninterpretable type (${reason.partTypes.join(
168
- ", "
169
- )})`;
170
- }
171
- }
172
-
173
- // src/turn-health/sink.ts
174
- function turnAlert(input) {
175
- const kinds = [...new Set(input.reasons.map((r) => r.kind))].sort();
176
- return {
177
- product: input.product,
178
- severity: input.severity,
179
- // Keyed by product + reason kinds ONLY. A blank-completion storm across
180
- // 200 turns is one incident, not 200 pages.
181
- key: `turn:${input.product}:${kinds.join("+")}`,
182
- title: `${input.product}: turn completed but delivered nothing (${kinds.join(", ")})`,
183
- details: input.reasons.map(describeReason),
184
- data: {
185
- kinds,
186
- ...input.threadId ? { threadId: input.threadId } : {},
187
- ...input.turnId ? { turnId: input.turnId } : {},
188
- ...input.model ? { model: input.model } : {}
189
- },
190
- at: input.at ?? Date.now()
191
- };
192
- }
193
- function createWebhookAlertSink(options) {
194
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
195
- return {
196
- async deliver(alert) {
197
- const icon = alert.severity === "critical" ? ":rotating_light:" : ":warning:";
198
- const lines = [
199
- `${icon} *${alert.title}*`,
200
- ...alert.details.map((d) => `\u2022 ${d}`),
201
- `_${new Date(alert.at).toISOString()}_`
202
- ];
203
- const response = await fetchImpl(options.webhookUrl, {
204
- method: "POST",
205
- headers: { "Content-Type": "application/json" },
206
- body: JSON.stringify({ text: lines.join("\n") })
207
- });
208
- if (!response.ok) {
209
- throw new Error(`alert webhook responded ${response.status}`);
210
- }
211
- }
212
- };
213
- }
214
- function createSlackBotAlertSink(options) {
215
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
216
- return {
217
- async deliver(alert) {
218
- const icon = alert.severity === "critical" ? ":rotating_light:" : ":warning:";
219
- const lines = [
220
- `${icon} *${alert.title}*`,
221
- ...alert.details.map((d) => `\u2022 ${d}`),
222
- `_${new Date(alert.at).toISOString()}_`
223
- ];
224
- const outcome = await postSlackAlert({
225
- token: options.botToken,
226
- channel: options.channel,
227
- text: lines.join("\n"),
228
- attempts: 1,
229
- // Preserve the public minimal-fetch seam without owning a second Slack
230
- // request builder or response parser. Real callers use native fetch.
231
- fetchImpl: options.fetchImpl ? (async (url, init) => {
232
- const response = await fetchImpl(String(url), {
233
- method: init?.method ?? "POST",
234
- headers: Object.fromEntries(new Headers(init?.headers)),
235
- body: String(init?.body ?? "")
236
- });
237
- return new Response(await response.text?.() ?? "", { status: response.status });
238
- }) : void 0
239
- });
240
- if (!outcome.delivered) throw new Error(`Slack alert failed (${outcome.reason}): ${outcome.detail}`);
241
- }
242
- };
243
- }
244
- function createConsoleAlertSink(log = console.error) {
245
- return {
246
- async deliver(alert) {
247
- log(
248
- `[turn-health] ${alert.severity.toUpperCase()} ${alert.title} :: ${alert.details.join(" | ")}`
249
- );
250
- }
251
- };
252
- }
253
- function createMultiAlertSink(sinks) {
254
- return {
255
- async deliver(alert) {
256
- const settled = await Promise.allSettled(sinks.map((s) => s.deliver(alert)));
257
- const failures = settled.filter((r) => r.status === "rejected");
258
- if (failures.length === sinks.length && sinks.length > 0) {
259
- throw new Error("every alert sink failed");
260
- }
261
- }
262
- };
263
- }
264
- function createMemoryThrottleStore() {
265
- const seen = /* @__PURE__ */ new Map();
266
- return {
267
- async lastSentAt(key) {
268
- return seen.get(key) ?? null;
269
- },
270
- async markSent(key, at) {
271
- seen.set(key, at);
272
- }
273
- };
274
- }
275
- function createThrottledAlertSink(inner, options) {
276
- const store = options.store ?? createMemoryThrottleStore();
277
- return {
278
- async deliver(alert) {
279
- const last = await store.lastSentAt(alert.key);
280
- if (last !== null && alert.at - last < options.windowMs) return;
281
- await inner.deliver(alert);
282
- await store.markSent(alert.key, alert.at);
283
- }
284
- };
285
- }
286
- function createGuardedAlertSink(inner, onError = (e) => console.error("[turn-health] alert delivery failed", e)) {
287
- return {
288
- async deliver(alert) {
289
- try {
290
- await inner.deliver(alert);
291
- } catch (error) {
292
- onError(error);
293
- }
294
- }
295
- };
296
- }
6
+ classifyTurnOutcome,
7
+ createConsoleAlertSink,
8
+ createGuardedAlertSink,
9
+ createMemoryThrottleStore,
10
+ createMultiAlertSink,
11
+ createSlackBotAlertSink,
12
+ createThrottledAlertSink,
13
+ createWebhookAlertSink,
14
+ describeReason,
15
+ turnAlert
16
+ } from "../chunk-CD2LQXY6.js";
17
+ import "../chunk-F2VX6TTJ.js";
297
18
 
298
19
  // src/turn-health/lifecycle.ts
299
20
  function errorText(error) {