@tangle-network/agent-app 0.60.215 → 0.60.216
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/chunk-CD2LQXY6.js +306 -0
- package/dist/chunk-CD2LQXY6.js.map +1 -0
- package/dist/{chunk-KDPTXXOQ.js → chunk-J3N6RNMH.js} +456 -75
- package/dist/chunk-J3N6RNMH.js.map +1 -0
- package/dist/chunk-LRYY3OX7.js +105 -0
- package/dist/chunk-LRYY3OX7.js.map +1 -0
- package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
- package/dist/chunk-QM7RTTDY.js.map +1 -0
- package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
- package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
- package/dist/launch-invariants/catalog.d.ts +16 -0
- package/dist/launch-invariants/cli.js +5 -2
- package/dist/launch-invariants/cli.js.map +1 -1
- package/dist/launch-invariants/index.d.ts +1 -1
- package/dist/launch-invariants/index.js +13 -5
- package/dist/launch-invariants/index.js.map +1 -1
- package/dist/launch-invariants/limit-alarms.d.ts +19 -1
- package/dist/launch-invariants/testing.d.ts +0 -3
- package/dist/launch-invariants/testing.js +5 -2
- package/dist/signoff/app-defaults.d.ts +65 -0
- package/dist/signoff/cli.js +71 -4
- package/dist/signoff/cli.js.map +1 -1
- package/dist/signoff/defaults-cli.d.ts +1 -0
- package/dist/signoff/index.js +2 -1
- package/dist/signoff/types.d.ts +6 -0
- package/dist/signoff/worker-defaults.d.ts +8 -0
- package/dist/turn-health/index.js +12 -291
- package/dist/turn-health/index.js.map +1 -1
- package/dist/turn-trace/index.js +20 -3
- package/dist/turn-trace/index.js.map +1 -1
- package/dist/turn-trace/otel.d.ts +11 -4
- package/package.json +1 -1
- package/dist/chunk-KDPTXXOQ.js.map +0 -1
- package/dist/chunk-LLO436QO.js.map +0 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The defaults an agent app inherits from agent-app, and whether this app has them.
|
|
3
|
+
*
|
|
4
|
+
* Each proven improvement ships as a default inside agent-app, so an app gets it by construction. This report reads
|
|
5
|
+
* an app's checkout and says which defaults it has, which it turned off, and which it replaced with a local copy.
|
|
6
|
+
* It is a report: it fails only on an explicit opt-out that gives no reason, so a gap stays visible without
|
|
7
|
+
* blocking a merge, and an exemption is a line in the app's `signoff.config.mjs` with the reason beside it.
|
|
8
|
+
*
|
|
9
|
+
* The rows are the outcomes Drew named on 2026-10-19: the turn trace on every turn with `first_text_ms`, the shared
|
|
10
|
+
* prewarm route, Worker placement beside the D1 primary, limit alarms wired in the server entry, and the Worker
|
|
11
|
+
* upload size against Cloudflare's limit. A row is read from the source, the Wrangler config and the lockfile; it
|
|
12
|
+
* never runs the app.
|
|
13
|
+
*/
|
|
14
|
+
/** The name the built-in step reports under; a repo cannot declare a step with it. */
|
|
15
|
+
export declare const APP_DEFAULTS_STEP = "agent-app defaults";
|
|
16
|
+
/** Defaults an app may turn off with a reason. */
|
|
17
|
+
export declare const EXEMPTIBLE_DEFAULTS: readonly ['turn-trace', 'first-text', 'prewarm', 'placement', 'limit-alarms'];
|
|
18
|
+
type ExemptibleDefault = (typeof EXEMPTIBLE_DEFAULTS)[number];
|
|
19
|
+
/** An app's declared opt-outs: the default and why. An empty reason is a configuration error. */
|
|
20
|
+
export type AppDefaultsExemptions = Readonly<Partial<Record<ExemptibleDefault, string>>>;
|
|
21
|
+
type DefaultStatus = 'ok' | 'gap' | 'unknown' | 'exempt' | 'opted-out' | 'na';
|
|
22
|
+
interface DefaultRow {
|
|
23
|
+
readonly id: 'pin' | 'turn-trace' | 'first-text' | 'prewarm' | 'placement' | 'tool-count' | 'limit-alarms' | 'worker-bundle';
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly status: DefaultStatus;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
}
|
|
28
|
+
interface AppDefaultsReport {
|
|
29
|
+
/** The package name. */
|
|
30
|
+
readonly app: string;
|
|
31
|
+
/** The package directory relative to the repo root. */
|
|
32
|
+
readonly dir: string;
|
|
33
|
+
/** The `@tangle-network/agent-app` version the package pins, or null when it is not a plain version. */
|
|
34
|
+
readonly pin: string | null;
|
|
35
|
+
readonly rows: readonly DefaultRow[];
|
|
36
|
+
/** Opt-outs that give no reason. These alone fail the step. */
|
|
37
|
+
readonly findings: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
export interface AppDefaultsResult {
|
|
40
|
+
readonly apps: readonly AppDefaultsReport[];
|
|
41
|
+
readonly findings: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
export interface AppDefaultsOptions {
|
|
44
|
+
/** Declared opt-outs per package name. Applied to every app when keyed by `*`. */
|
|
45
|
+
readonly exemptions?: AppDefaultsExemptions;
|
|
46
|
+
/** Measure the Worker upload with the app's own Wrangler (default true). Needs a built app. */
|
|
47
|
+
readonly measureBundle?: boolean;
|
|
48
|
+
/** The last turn trace Intelligence accepted per Intelligence project; see {@link readLastTurnExport}. */
|
|
49
|
+
readonly exports?: ReadonlyMap<string, LastTurnExport | null>;
|
|
50
|
+
}
|
|
51
|
+
/** The newest `agent_app.turn` root span Intelligence holds for a project. */
|
|
52
|
+
interface LastTurnExport {
|
|
53
|
+
readonly at: number;
|
|
54
|
+
readonly traceId: string;
|
|
55
|
+
readonly firstTextMs?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Report each agent app in the checkout against the defaults. Reads only tracked files; measures the Worker upload with the app's own Wrangler. */
|
|
58
|
+
export declare function checkAppDefaults(repoRoot: string, options?: AppDefaultsOptions): AppDefaultsResult;
|
|
59
|
+
export declare function formatAppDefaults(result: AppDefaultsResult): string;
|
|
60
|
+
/**
|
|
61
|
+
* The newest turn Intelligence holds for each project the apps' sinks name, read with one platform key. A project the
|
|
62
|
+
* key's tenant does not own, or one the ingest has never accepted, reads as null; a failed request leaves it out.
|
|
63
|
+
*/
|
|
64
|
+
export declare function collectLastExports(repoRoot: string, apiKey: string): Promise<Map<string, LastTurnExport | null>>;
|
|
65
|
+
export {};
|
package/dist/signoff/cli.js
CHANGED
|
@@ -1,19 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
checkAppDefaults,
|
|
4
|
+
collectLastExports,
|
|
5
|
+
formatAppDefaults,
|
|
3
6
|
formatSignoffLine,
|
|
4
7
|
formatSignoffReport,
|
|
8
|
+
loadSignoffConfig,
|
|
5
9
|
runShellAlignmentCli,
|
|
6
10
|
runSignoff,
|
|
7
11
|
runSurfaceLadderCli,
|
|
8
12
|
runWorkspaceSurfaceCli
|
|
9
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-J3N6RNMH.js";
|
|
14
|
+
import "../chunk-LRYY3OX7.js";
|
|
10
15
|
import {
|
|
11
16
|
invokedAsScript
|
|
12
17
|
} from "../chunk-C3CAYGGQ.js";
|
|
13
18
|
|
|
14
19
|
// src/signoff/cli.ts
|
|
20
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
21
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
22
|
+
|
|
23
|
+
// src/signoff/defaults-cli.ts
|
|
15
24
|
import { mkdirSync, writeFileSync } from "fs";
|
|
16
25
|
import { dirname, resolve } from "path";
|
|
26
|
+
function defaultsUsage() {
|
|
27
|
+
return [
|
|
28
|
+
"Usage: agent-app-signoff defaults [repoDir] [options]",
|
|
29
|
+
"",
|
|
30
|
+
" --probe ask Intelligence for each app's last exported turn (needs TANGLE_INTELLIGENCE_API_KEY)",
|
|
31
|
+
" --no-bundle skip the Worker upload measure (it runs the app's own wrangler, so build first)",
|
|
32
|
+
" --json <path> write the rows"
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
async function runDefaultsCli(argv) {
|
|
36
|
+
let repoDir = process.cwd();
|
|
37
|
+
let probe = false;
|
|
38
|
+
let measureBundle = true;
|
|
39
|
+
let jsonPath;
|
|
40
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
41
|
+
const flag = argv[index];
|
|
42
|
+
if (flag === "--probe") probe = true;
|
|
43
|
+
else if (flag === "--no-bundle") measureBundle = false;
|
|
44
|
+
else if (flag === "--json") {
|
|
45
|
+
jsonPath = argv[index + 1];
|
|
46
|
+
index += 1;
|
|
47
|
+
if (jsonPath === void 0) return usageError("--json needs a path");
|
|
48
|
+
} else if (flag === "-h" || flag === "--help") {
|
|
49
|
+
process.stdout.write(`${defaultsUsage()}
|
|
50
|
+
`);
|
|
51
|
+
return 0;
|
|
52
|
+
} else if (flag.startsWith("-")) return usageError(`unknown option: ${flag}`);
|
|
53
|
+
else repoDir = resolve(flag);
|
|
54
|
+
}
|
|
55
|
+
let exemptions;
|
|
56
|
+
try {
|
|
57
|
+
exemptions = (await loadSignoffConfig({ repoRoot: repoDir })).config.agentApp?.exemptions;
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
const apiKey = process.env.TANGLE_INTELLIGENCE_API_KEY?.trim();
|
|
61
|
+
if (probe && !apiKey) return usageError("--probe needs TANGLE_INTELLIGENCE_API_KEY");
|
|
62
|
+
const exports = probe && apiKey ? await collectLastExports(repoDir, apiKey) : void 0;
|
|
63
|
+
const result = checkAppDefaults(repoDir, { exemptions, measureBundle, exports });
|
|
64
|
+
if (jsonPath !== void 0) {
|
|
65
|
+
const abs = resolve(jsonPath);
|
|
66
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
67
|
+
writeFileSync(abs, `${JSON.stringify(result, null, 2)}
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write(formatAppDefaults(result));
|
|
71
|
+
return result.findings.length === 0 ? 0 : 1;
|
|
72
|
+
}
|
|
73
|
+
function usageError(message) {
|
|
74
|
+
process.stderr.write(`agent-app-signoff defaults: ${message}
|
|
75
|
+
|
|
76
|
+
${defaultsUsage()}
|
|
77
|
+
`);
|
|
78
|
+
return 2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/signoff/cli.ts
|
|
17
82
|
function usage() {
|
|
18
83
|
return [
|
|
19
84
|
"Usage: agent-app-signoff [repoDir] [options]",
|
|
@@ -30,6 +95,7 @@ function usage() {
|
|
|
30
95
|
" --quiet verdict only; no per-step progress",
|
|
31
96
|
"",
|
|
32
97
|
"Subcommands:",
|
|
98
|
+
" defaults report which agent-app defaults each app has (--help for options)",
|
|
33
99
|
" shell-alignment measure header dividers against the rail in a browser (--help for options)",
|
|
34
100
|
" surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)",
|
|
35
101
|
" workspace-surface every page sits on one shared panel under an inset shell header (--help for options)"
|
|
@@ -136,15 +202,16 @@ function progressWriter() {
|
|
|
136
202
|
};
|
|
137
203
|
}
|
|
138
204
|
async function runSignoffCli(argv) {
|
|
205
|
+
if (argv[0] === "defaults") return runDefaultsCli(argv.slice(1));
|
|
139
206
|
if (argv[0] === "shell-alignment") return runShellAlignmentCli(argv.slice(1));
|
|
140
207
|
if (argv[0] === "surface-ladder") return runSurfaceLadderCli(argv.slice(1));
|
|
141
208
|
if (argv[0] === "workspace-surface") return runWorkspaceSurfaceCli(argv.slice(1));
|
|
142
209
|
const args = parseArgs(argv);
|
|
143
210
|
const report = await runSignoff({ ...args, onEvent: args.quiet ? void 0 : progressWriter() });
|
|
144
211
|
if (args.jsonPath !== void 0) {
|
|
145
|
-
const abs =
|
|
146
|
-
|
|
147
|
-
|
|
212
|
+
const abs = resolve2(args.jsonPath);
|
|
213
|
+
mkdirSync2(dirname2(abs), { recursive: true });
|
|
214
|
+
writeFileSync2(abs, `${JSON.stringify(report, null, 2)}
|
|
148
215
|
`);
|
|
149
216
|
}
|
|
150
217
|
process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}
|
package/dist/signoff/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/signoff/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-signoff` — run the local sign-off gate and print the proof.\n *\n * Exits 0 only when every declared step passed in a pristine tree. Exit 1 is a\n * real failure; exit 2 is a usage or config error, so a script can tell \"your\n * code is broken\" from \"your gate is misconfigured\".\n *\n * agent-app-signoff # this repo, working tree, default config\n * agent-app-signoff --source head # exactly the commit that would merge\n * agent-app-signoff --seed 12345 # reproduce a previous run's orders\n * agent-app-signoff --keep-going # run every step, full picture\n * agent-app-signoff --json proof.json # machine-readable record\n * agent-app-signoff shell-alignment --base-url <url> --route <path> # header dividers meet the rail\n * agent-app-signoff surface-ladder --base-url <url> --route <path> # cards and fields step off their parent\n * agent-app-signoff workspace-surface --base-url <url> --route <path> # every page on the shared panel\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { invokedAsScript } from './invoked-as-script'\nimport { formatSignoffLine, formatSignoffReport } from './report'\nimport { runSignoff, type RunSignoffOptions } from './run'\nimport { runShellAlignmentCli } from './shell-alignment-cli'\nimport { runSurfaceLadderCli } from './surface-ladder-cli'\nimport { runWorkspaceSurfaceCli } from './workspace-surface-cli'\nimport type { SignoffEvent, SignoffSource } from './types'\n\ninterface ParsedArgs extends RunSignoffOptions {\n readonly jsonPath?: string\n readonly quiet?: boolean\n}\n\nfunction usage(): string {\n return [\n 'Usage: agent-app-signoff [repoDir] [options]',\n '',\n ' --source head|working-tree bytes to verify (default working-tree)',\n ' --config <path> config module (default signoff.config.mjs)',\n ' --seed <n> base seed; reproduces a previous run',\n ' --shuffle-runs <n> override every shuffled step\\'s run count',\n ' --max-parallel <n> cap concurrent steps',\n ' --cache-dir <path> clean trees + pristine stores live here',\n ' --keep-going run every step even after one fails',\n ' --keep-workspace leave the clean tree on disk to inspect',\n ' --json <path> write the machine-readable report',\n ' --quiet verdict only; no per-step progress',\n '',\n 'Subcommands:',\n ' shell-alignment measure header dividers against the rail in a browser (--help for options)',\n ' surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)',\n ' workspace-surface every page sits on one shared panel under an inset shell header (--help for options)',\n ].join('\\n')\n}\n\nfunction fail(message: string): never {\n process.stderr.write(`agent-app-signoff: ${message}\\n\\n${usage()}\\n`)\n process.exit(2)\n}\n\nfunction positiveInt(raw: string, flag: string): number {\n const value = Number.parseInt(raw, 10)\n if (!Number.isFinite(value) || value < 0) fail(`${flag} needs a non-negative integer, got \"${raw}\"`)\n return value\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const parsed: {\n repoDir?: string\n configPath?: string\n source?: SignoffSource\n seed?: number\n shuffleRuns?: number\n maxParallel?: number\n cacheDir?: string\n keepGoing?: boolean\n keepWorkspace?: boolean\n jsonPath?: string\n quiet?: boolean\n } = {}\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n const take = (): string => {\n const value = argv[index + 1]\n if (value === undefined) fail(`${flag} needs a value`)\n index += 1\n return value\n }\n switch (flag) {\n case '--source': {\n const value = take()\n if (value !== 'head' && value !== 'working-tree') fail(`--source must be head or working-tree, got \"${value}\"`)\n parsed.source = value\n break\n }\n case '--config': parsed.configPath = take(); break\n case '--seed': parsed.seed = positiveInt(take(), '--seed'); break\n case '--shuffle-runs': parsed.shuffleRuns = positiveInt(take(), '--shuffle-runs'); break\n case '--max-parallel': parsed.maxParallel = positiveInt(take(), '--max-parallel'); break\n case '--cache-dir': parsed.cacheDir = take(); break\n case '--keep-going': parsed.keepGoing = true; break\n case '--keep-workspace': parsed.keepWorkspace = true; break\n case '--json': parsed.jsonPath = take(); break\n case '--quiet': parsed.quiet = true; break\n case '-h':\n case '--help':\n process.stdout.write(`${usage()}\\n`)\n process.exit(0)\n break\n default:\n if (flag.startsWith('-')) fail(`unknown option: ${flag}`)\n if (parsed.repoDir !== undefined) fail(`unexpected second directory: ${flag}`)\n parsed.repoDir = flag\n }\n }\n return parsed\n}\n\nfunction progressWriter(): (event: SignoffEvent) => void {\n return (event) => {\n switch (event.kind) {\n case 'tree':\n process.stderr.write(`· clean tree at ${event.path} (${event.head.slice(0, 12)}${event.dirty ? ' + working tree' : ''})\\n`)\n break\n case 'store':\n process.stderr.write(`· store ${event.cacheHit ? 'warm' : 'COLD'} ${event.cacheKey.slice(0, 16)}\\n`)\n break\n case 'install-start':\n process.stderr.write(`· ${event.command}\\n`)\n break\n case 'install-end':\n process.stderr.write(`· install exit ${event.exitCode} in ${event.durationMs}ms\\n`)\n break\n case 'step-start':\n process.stderr.write(`· start ${event.name}${event.seed === null ? '' : ` (seed ${event.seed})`}\\n`)\n break\n case 'step-end':\n process.stderr.write(`· ${event.status === 'passed' ? 'ok' : event.status} ${event.name} ${event.durationMs}ms\\n`)\n break\n }\n }\n}\n\nexport async function runSignoffCli(argv: readonly string[]): Promise<number> {\n if (argv[0] === 'shell-alignment') return runShellAlignmentCli(argv.slice(1))\n if (argv[0] === 'surface-ladder') return runSurfaceLadderCli(argv.slice(1))\n if (argv[0] === 'workspace-surface') return runWorkspaceSurfaceCli(argv.slice(1))\n const args = parseArgs(argv)\n const report = await runSignoff({ ...args, onEvent: args.quiet ? undefined : progressWriter() })\n\n if (args.jsonPath !== undefined) {\n const abs = resolve(args.jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(report, null, 2)}\\n`)\n }\n\n // `--quiet` is documented as \"verdict only\", and CLAUDE.md names the one-line\n // `signoff PASS <sha> …` summary as an accepted PR proof. Printing the full\n // report here contradicted both, and left the only producer of that summary\n // with no caller.\n process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}\\n`)\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n runSignoffCli(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(2)\n })\n}\n/* c8 ignore stop */\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/signoff/cli.ts","../../src/signoff/defaults-cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-signoff` — run the local sign-off gate and print the proof.\n *\n * Exits 0 only when every declared step passed in a pristine tree. Exit 1 is a\n * real failure; exit 2 is a usage or config error, so a script can tell \"your\n * code is broken\" from \"your gate is misconfigured\".\n *\n * agent-app-signoff # this repo, working tree, default config\n * agent-app-signoff --source head # exactly the commit that would merge\n * agent-app-signoff --seed 12345 # reproduce a previous run's orders\n * agent-app-signoff --keep-going # run every step, full picture\n * agent-app-signoff --json proof.json # machine-readable record\n * agent-app-signoff defaults [repoDir] # which agent-app defaults each app has\n * agent-app-signoff shell-alignment --base-url <url> --route <path> # header dividers meet the rail\n * agent-app-signoff surface-ladder --base-url <url> --route <path> # cards and fields step off their parent\n * agent-app-signoff workspace-surface --base-url <url> --route <path> # every page on the shared panel\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { runDefaultsCli } from './defaults-cli'\nimport { invokedAsScript } from './invoked-as-script'\nimport { formatSignoffLine, formatSignoffReport } from './report'\nimport { runSignoff, type RunSignoffOptions } from './run'\nimport { runShellAlignmentCli } from './shell-alignment-cli'\nimport { runSurfaceLadderCli } from './surface-ladder-cli'\nimport { runWorkspaceSurfaceCli } from './workspace-surface-cli'\nimport type { SignoffEvent, SignoffSource } from './types'\n\ninterface ParsedArgs extends RunSignoffOptions {\n readonly jsonPath?: string\n readonly quiet?: boolean\n}\n\nfunction usage(): string {\n return [\n 'Usage: agent-app-signoff [repoDir] [options]',\n '',\n ' --source head|working-tree bytes to verify (default working-tree)',\n ' --config <path> config module (default signoff.config.mjs)',\n ' --seed <n> base seed; reproduces a previous run',\n ' --shuffle-runs <n> override every shuffled step\\'s run count',\n ' --max-parallel <n> cap concurrent steps',\n ' --cache-dir <path> clean trees + pristine stores live here',\n ' --keep-going run every step even after one fails',\n ' --keep-workspace leave the clean tree on disk to inspect',\n ' --json <path> write the machine-readable report',\n ' --quiet verdict only; no per-step progress',\n '',\n 'Subcommands:',\n ' defaults report which agent-app defaults each app has (--help for options)',\n ' shell-alignment measure header dividers against the rail in a browser (--help for options)',\n ' surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)',\n ' workspace-surface every page sits on one shared panel under an inset shell header (--help for options)',\n ].join('\\n')\n}\n\nfunction fail(message: string): never {\n process.stderr.write(`agent-app-signoff: ${message}\\n\\n${usage()}\\n`)\n process.exit(2)\n}\n\nfunction positiveInt(raw: string, flag: string): number {\n const value = Number.parseInt(raw, 10)\n if (!Number.isFinite(value) || value < 0) fail(`${flag} needs a non-negative integer, got \"${raw}\"`)\n return value\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const parsed: {\n repoDir?: string\n configPath?: string\n source?: SignoffSource\n seed?: number\n shuffleRuns?: number\n maxParallel?: number\n cacheDir?: string\n keepGoing?: boolean\n keepWorkspace?: boolean\n jsonPath?: string\n quiet?: boolean\n } = {}\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n const take = (): string => {\n const value = argv[index + 1]\n if (value === undefined) fail(`${flag} needs a value`)\n index += 1\n return value\n }\n switch (flag) {\n case '--source': {\n const value = take()\n if (value !== 'head' && value !== 'working-tree') fail(`--source must be head or working-tree, got \"${value}\"`)\n parsed.source = value\n break\n }\n case '--config': parsed.configPath = take(); break\n case '--seed': parsed.seed = positiveInt(take(), '--seed'); break\n case '--shuffle-runs': parsed.shuffleRuns = positiveInt(take(), '--shuffle-runs'); break\n case '--max-parallel': parsed.maxParallel = positiveInt(take(), '--max-parallel'); break\n case '--cache-dir': parsed.cacheDir = take(); break\n case '--keep-going': parsed.keepGoing = true; break\n case '--keep-workspace': parsed.keepWorkspace = true; break\n case '--json': parsed.jsonPath = take(); break\n case '--quiet': parsed.quiet = true; break\n case '-h':\n case '--help':\n process.stdout.write(`${usage()}\\n`)\n process.exit(0)\n break\n default:\n if (flag.startsWith('-')) fail(`unknown option: ${flag}`)\n if (parsed.repoDir !== undefined) fail(`unexpected second directory: ${flag}`)\n parsed.repoDir = flag\n }\n }\n return parsed\n}\n\nfunction progressWriter(): (event: SignoffEvent) => void {\n return (event) => {\n switch (event.kind) {\n case 'tree':\n process.stderr.write(`· clean tree at ${event.path} (${event.head.slice(0, 12)}${event.dirty ? ' + working tree' : ''})\\n`)\n break\n case 'store':\n process.stderr.write(`· store ${event.cacheHit ? 'warm' : 'COLD'} ${event.cacheKey.slice(0, 16)}\\n`)\n break\n case 'install-start':\n process.stderr.write(`· ${event.command}\\n`)\n break\n case 'install-end':\n process.stderr.write(`· install exit ${event.exitCode} in ${event.durationMs}ms\\n`)\n break\n case 'step-start':\n process.stderr.write(`· start ${event.name}${event.seed === null ? '' : ` (seed ${event.seed})`}\\n`)\n break\n case 'step-end':\n process.stderr.write(`· ${event.status === 'passed' ? 'ok' : event.status} ${event.name} ${event.durationMs}ms\\n`)\n break\n }\n }\n}\n\nexport async function runSignoffCli(argv: readonly string[]): Promise<number> {\n if (argv[0] === 'defaults') return runDefaultsCli(argv.slice(1))\n if (argv[0] === 'shell-alignment') return runShellAlignmentCli(argv.slice(1))\n if (argv[0] === 'surface-ladder') return runSurfaceLadderCli(argv.slice(1))\n if (argv[0] === 'workspace-surface') return runWorkspaceSurfaceCli(argv.slice(1))\n const args = parseArgs(argv)\n const report = await runSignoff({ ...args, onEvent: args.quiet ? undefined : progressWriter() })\n\n if (args.jsonPath !== undefined) {\n const abs = resolve(args.jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(report, null, 2)}\\n`)\n }\n\n // `--quiet` is documented as \"verdict only\", and CLAUDE.md names the one-line\n // `signoff PASS <sha> …` summary as an accepted PR proof. Printing the full\n // report here contradicted both, and left the only producer of that summary\n // with no caller.\n process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}\\n`)\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n runSignoffCli(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(2)\n })\n}\n/* c8 ignore stop */\n","/**\n * `agent-app-signoff defaults` — report which agent-app defaults each app in a checkout has.\n *\n * agent-app-signoff defaults [repoDir] # the matrix for the apps in repoDir\n * agent-app-signoff defaults --probe # also ask Intelligence for each app's last exported turn\n * agent-app-signoff defaults --json matrix.json # the same rows, machine-readable\n *\n * Exit 0 unless an app turns a default off without a reason (1), or the usage is wrong (2). `--probe` reads the\n * Intelligence key from `TANGLE_INTELLIGENCE_API_KEY`; it reads the traces of the tenant that owns the apps' export keys.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { checkAppDefaults, collectLastExports, formatAppDefaults } from './app-defaults'\nimport { loadSignoffConfig } from './config'\n\nfunction defaultsUsage(): string {\n return [\n 'Usage: agent-app-signoff defaults [repoDir] [options]',\n '',\n ' --probe ask Intelligence for each app\\'s last exported turn (needs TANGLE_INTELLIGENCE_API_KEY)',\n ' --no-bundle skip the Worker upload measure (it runs the app\\'s own wrangler, so build first)',\n ' --json <path> write the rows',\n ].join('\\n')\n}\n\nexport async function runDefaultsCli(argv: readonly string[]): Promise<number> {\n let repoDir = process.cwd()\n let probe = false\n let measureBundle = true\n let jsonPath: string | undefined\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n if (flag === '--probe') probe = true\n else if (flag === '--no-bundle') measureBundle = false\n else if (flag === '--json') {\n jsonPath = argv[index + 1]\n index += 1\n if (jsonPath === undefined) return usageError('--json needs a path')\n } else if (flag === '-h' || flag === '--help') {\n process.stdout.write(`${defaultsUsage()}\\n`)\n return 0\n } else if (flag.startsWith('-')) return usageError(`unknown option: ${flag}`)\n else repoDir = resolve(flag)\n }\n let exemptions\n try {\n exemptions = (await loadSignoffConfig({ repoRoot: repoDir })).config.agentApp?.exemptions\n } catch {\n // A checkout with no signoff config, or one that does not load here, declares no exemptions.\n }\n const apiKey = process.env.TANGLE_INTELLIGENCE_API_KEY?.trim()\n if (probe && !apiKey) return usageError('--probe needs TANGLE_INTELLIGENCE_API_KEY')\n const exports = probe && apiKey ? await collectLastExports(repoDir, apiKey) : undefined\n const result = checkAppDefaults(repoDir, { exemptions, measureBundle, exports })\n if (jsonPath !== undefined) {\n const abs = resolve(jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(result, null, 2)}\\n`)\n }\n process.stdout.write(formatAppDefaults(result))\n return result.findings.length === 0 ? 0 : 1\n}\n\nfunction usageError(message: string): number {\n process.stderr.write(`agent-app-signoff defaults: ${message}\\n\\n${defaultsUsage()}\\n`)\n return 2\n}\n"],"mappings":"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","names":["mkdirSync","writeFileSync","dirname","resolve","resolve","mkdirSync","dirname","writeFileSync"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDefaultsCli(argv: readonly string[]): Promise<number>;
|
package/dist/signoff/index.js
CHANGED
package/dist/signoff/types.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* install filter, step list, language (one has a Python step) and repo-specific
|
|
11
11
|
* gates.
|
|
12
12
|
*/
|
|
13
|
+
import type { AppDefaultsExemptions } from './app-defaults';
|
|
13
14
|
/** Which bytes get verified. */
|
|
14
15
|
export type SignoffSource =
|
|
15
16
|
/** Exactly the commit that would merge. Uncommitted work is NOT included. */
|
|
@@ -99,6 +100,11 @@ export interface SignoffConfig {
|
|
|
99
100
|
* every tracked `wrangler.{toml,json,jsonc}`. Each is checked against the
|
|
100
101
|
* agent-app Worker defaults (`worker-defaults.ts`). */
|
|
101
102
|
readonly workerConfigs?: readonly string[];
|
|
103
|
+
/** What this app declares about the agent-app defaults (`app-defaults.ts`). */
|
|
104
|
+
readonly agentApp?: {
|
|
105
|
+
/** Defaults the app turns off, each with the reason. An empty reason is a configuration error. */
|
|
106
|
+
readonly exemptions?: AppDefaultsExemptions;
|
|
107
|
+
};
|
|
102
108
|
}
|
|
103
109
|
/** Where a config came from. Printed in the proof — a run against a derived
|
|
104
110
|
* default is a weaker claim than one against a declared step list. */
|
|
@@ -41,6 +41,14 @@ export interface WorkerDefaultsResult {
|
|
|
41
41
|
}[];
|
|
42
42
|
readonly findings: readonly WorkerDefaultsFinding[];
|
|
43
43
|
}
|
|
44
|
+
/** Every file the repo tracks, repo-relative. Generated output is gitignored, so this is the complete source list without a hand-kept exclude list. */
|
|
45
|
+
export declare function trackedFiles(repoRoot: string): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Wrangler configs the repo tracks. Generated output (`.wrangler/`, build
|
|
48
|
+
* directories) is gitignored, so `git ls-files` is the complete source list
|
|
49
|
+
* without a hand-kept exclude list.
|
|
50
|
+
*/
|
|
51
|
+
export declare function trackedWorkerConfigs(repoRoot: string): string[];
|
|
44
52
|
/**
|
|
45
53
|
* Check each Worker config, and every environment it deploys, against the
|
|
46
54
|
* defaults. `configs` are repo-relative paths; omit them to check every tracked
|
|
@@ -3,297 +3,18 @@ import {
|
|
|
3
3
|
sizedBatches
|
|
4
4
|
} from "../chunk-5UQJ7F3P.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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) {
|