@workos/quickstudy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/examples/harbor-notes/README.md +40 -0
  4. package/examples/harbor-notes/evals/create-note/EVAL.ts +14 -0
  5. package/examples/harbor-notes/evals/create-note/PROMPT.md +9 -0
  6. package/examples/harbor-notes/evals/create-note/local/README.txt +1 -0
  7. package/examples/harbor-notes/experiments/scripted.ts +6 -0
  8. package/examples/harbor-notes/package.json +6 -0
  9. package/examples/harbor-notes/quickstudy.identity.json +1 -0
  10. package/examples/harbor-notes/runtime.ts +48 -0
  11. package/examples/harbor-notes/semantic-example.ts +21 -0
  12. package/images/agent-runtime/Dockerfile +58 -0
  13. package/images/egress-proxy/Dockerfile +28 -0
  14. package/images/mcp-proxy/Dockerfile +30 -0
  15. package/package.json +53 -0
  16. package/src/adapters/claude.ts +107 -0
  17. package/src/adapters/codex.ts +107 -0
  18. package/src/adapters/echo.ts +57 -0
  19. package/src/adapters/parse.ts +117 -0
  20. package/src/adapters/types.ts +152 -0
  21. package/src/build-info.generated.ts +12 -0
  22. package/src/cli.ts +787 -0
  23. package/src/completeness.ts +104 -0
  24. package/src/diagnose/excerpt.ts +106 -0
  25. package/src/diagnose/prompt.ts +175 -0
  26. package/src/diagnose/render.ts +55 -0
  27. package/src/diagnose/run.ts +290 -0
  28. package/src/diagnose/select.ts +110 -0
  29. package/src/diagnose/types.ts +88 -0
  30. package/src/evals/discovery.ts +173 -0
  31. package/src/evals/prompt.ts +190 -0
  32. package/src/evals/result.ts +10 -0
  33. package/src/evals/types.ts +115 -0
  34. package/src/execution-policy.ts +71 -0
  35. package/src/experiments/discovery.ts +76 -0
  36. package/src/experiments/groups.ts +119 -0
  37. package/src/experiments/types.ts +116 -0
  38. package/src/export-types.ts +127 -0
  39. package/src/export.ts +381 -0
  40. package/src/hash.ts +74 -0
  41. package/src/identity-diff.ts +30 -0
  42. package/src/ids.ts +30 -0
  43. package/src/index.ts +58 -0
  44. package/src/isolation/docker.ts +639 -0
  45. package/src/isolation/image-contexts.generated.ts +927 -0
  46. package/src/isolation/images.ts +138 -0
  47. package/src/isolation/mcp-proxy/server.ts +260 -0
  48. package/src/isolation/mcp.ts +144 -0
  49. package/src/isolation/proxy/allowlist.ts +148 -0
  50. package/src/isolation/proxy/server.ts +382 -0
  51. package/src/llm.ts +132 -0
  52. package/src/manifest.ts +228 -0
  53. package/src/model-identity.ts +12 -0
  54. package/src/plan.ts +55 -0
  55. package/src/probe.ts +426 -0
  56. package/src/report/pass-at-k.ts +76 -0
  57. package/src/report/report.ts +731 -0
  58. package/src/runner/context.ts +96 -0
  59. package/src/runner/deadline.ts +37 -0
  60. package/src/runner/execute.ts +992 -0
  61. package/src/runner/run-lock.ts +32 -0
  62. package/src/runner/scheduler.ts +62 -0
  63. package/src/runner/score-worker.ts +107 -0
  64. package/src/runner/scorer-worker.ts +61 -0
  65. package/src/runtime/types.ts +89 -0
  66. package/src/secrets.ts +151 -0
  67. package/src/semantic.ts +185 -0
  68. package/src/serve.ts +52 -0
  69. package/src/source-identity.ts +76 -0
  70. package/src/store/artifacts.ts +146 -0
  71. package/src/store/db.ts +318 -0
  72. package/src/store/schema.ts +39 -0
  73. package/src/surface-usage.ts +297 -0
  74. package/src/ui-bundle.generated.ts +12 -0
  75. package/ui/dist/index.html +32 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Builds the {@link EvalContext} a scorer receives.
3
+ *
4
+ * File helpers read the EXPORTED workspace — stable on disk after the run.
5
+ * `exec` is proxied into the live sandbox when the experiment ran in a
6
+ * container (the runner keeps the container alive until the scorer resolves);
7
+ * host experiments exec in the exported workspace directory. Capability slots
8
+ * (`query`, `getClient`) are wired by experiment runtimes in a later phase —
9
+ * until a runtime provides one, calling it throws a precise error instead of
10
+ * failing mysteriously downstream.
11
+ */
12
+
13
+ import { existsSync } from "node:fs";
14
+ import { readFile } from "node:fs/promises";
15
+ import { relative, resolve } from "node:path";
16
+ import type { EvalContext, EvalExecResult } from "../evals/types.ts";
17
+
18
+ export interface BuildEvalContextOptions {
19
+ signal?: AbortSignal;
20
+ agentOutput?: EvalContext["agentOutput"];
21
+ redact?: EvalContext["redact"];
22
+ evalId: string;
23
+ experimentId: string;
24
+ trialIndex: number;
25
+ /** The eval's framework (metadata.framework); omitted for framework-neutral evals. */
26
+ framework?: string;
27
+ /** Absolute path of the exported workspace. */
28
+ workspaceDir: string;
29
+ /** Container-provided exec; omitted = host exec in the workspace dir. */
30
+ exec?: (cmd: string[], opts?: { timeoutMs?: number }) => Promise<EvalExecResult>;
31
+ /** Runtime capability slots (phase 2 wires these). */
32
+ query?: (request: unknown) => Promise<unknown>;
33
+ getClient?: (name: string) => unknown;
34
+ }
35
+
36
+ /** Resolve a workspace-relative path, refusing escapes. */
37
+ function insideWorkspace(workspaceDir: string, path: string): string {
38
+ const abs = resolve(workspaceDir, path);
39
+ const rel = relative(workspaceDir, abs);
40
+ if (rel === ".." || rel.startsWith("../")) {
41
+ throw new Error(`eval context paths must stay inside the workspace; got "${path}"`);
42
+ }
43
+ return abs;
44
+ }
45
+
46
+ async function hostExec(cwd: string, cmd: string[], opts?: { timeoutMs?: number }, signal?: AbortSignal): Promise<EvalExecResult> {
47
+ const [command, ...args] = cmd;
48
+ if (command === undefined) throw new Error("exec: command must not be empty");
49
+ const proc = Bun.spawn([command, ...args], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe", detached: true });
50
+ const kill = () => { try { process.kill(-proc.pid, "SIGKILL"); } catch { proc.kill("SIGKILL"); } };
51
+ signal?.addEventListener("abort", kill, { once: true });
52
+ if (signal?.aborted) kill();
53
+ let timedOut = false;
54
+ let timer: ReturnType<typeof setTimeout> | undefined;
55
+ if (opts?.timeoutMs !== undefined) {
56
+ timer = setTimeout(() => {
57
+ timedOut = true;
58
+ kill();
59
+ }, opts.timeoutMs);
60
+ }
61
+ const [stdout, stderr, exitCode] = await Promise.all([
62
+ new Response(proc.stdout).text(),
63
+ new Response(proc.stderr).text(),
64
+ proc.exited,
65
+ ]);
66
+ if (timer !== undefined) clearTimeout(timer);
67
+ signal?.removeEventListener("abort", kill);
68
+ return { exitCode, stdout, stderr, timedOut };
69
+ }
70
+
71
+ export function buildEvalContext(options: BuildEvalContextOptions): EvalContext {
72
+ const { workspaceDir } = options;
73
+ return {
74
+ signal: options.signal,
75
+ ...(options.agentOutput ? { agentOutput: options.agentOutput } : {}),
76
+ ...(options.redact ? { redact: options.redact } : {}),
77
+ evalId: options.evalId,
78
+ experimentId: options.experimentId,
79
+ trialIndex: options.trialIndex,
80
+ ...(options.framework !== undefined ? { framework: options.framework } : {}),
81
+ workspaceDir,
82
+ fileExists: (path) => Promise.resolve(existsSync(insideWorkspace(workspaceDir, path))),
83
+ readFile: (path) => readFile(insideWorkspace(workspaceDir, path), "utf8"),
84
+ exec: (cmd, opts) => { options.signal?.throwIfAborted(); return options.exec ? options.exec(cmd, opts) : hostExec(workspaceDir, cmd, opts, options.signal); },
85
+ query:
86
+ options.query ??
87
+ (() => {
88
+ throw new Error(`this experiment runtime does not provide query() (experiment "${options.experimentId}")`);
89
+ }),
90
+ getClient:
91
+ options.getClient ??
92
+ (() => {
93
+ throw new Error(`this experiment runtime does not provide getClient() (experiment "${options.experimentId}")`);
94
+ }),
95
+ };
96
+ }
@@ -0,0 +1,37 @@
1
+ /** Trusted runtime hooks must honor the signal; late provision results get cleanup. */
2
+ export class StageTimeoutError extends Error {
3
+ constructor(stage: string, ms: number) {
4
+ super(`${stage} timed out after ${ms}ms`);
5
+ this.name = "StageTimeoutError";
6
+ }
7
+ }
8
+ export async function deadline<T>(
9
+ stage: string,
10
+ ms: number,
11
+ work: (signal: AbortSignal) => Promise<T>,
12
+ onLate?: (value: T) => Promise<void>,
13
+ ): Promise<T> {
14
+ const controller = new AbortController();
15
+ let expired = false;
16
+ let timer: ReturnType<typeof setTimeout> | undefined;
17
+ const task = Promise.resolve().then(() => work(controller.signal));
18
+ const bounded = new Promise<never>((_, reject) => {
19
+ timer = setTimeout(() => {
20
+ expired = true;
21
+ const error = new StageTimeoutError(stage, ms);
22
+ controller.abort(error);
23
+ reject(error);
24
+ }, ms);
25
+ });
26
+ void task
27
+ .then(async (value) => {
28
+ if (expired && onLate) await onLate(value);
29
+ })
30
+ .catch(() => {});
31
+ try {
32
+ return await Promise.race([task, bounded]);
33
+ } finally {
34
+ if (timer) clearTimeout(timer);
35
+ controller.abort(new Error(`${stage} scope ended`));
36
+ }
37
+ }