@kody-ade/kody-engine 0.4.601 → 0.4.603

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 (2) hide show
  1. package/dist/bin/kody.js +109 -2
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.601",
18
+ version: "0.4.603",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -16521,7 +16521,7 @@ function composeAuthBlock(authProfile, login, password) {
16521
16521
  return `Auth: a saved Playwright \`storageState.json\` is available at \`${authProfile}\`. Pass it to the browser via the \`storageState\` parameter so the session starts pre-authenticated.`;
16522
16522
  }
16523
16523
  if (login && password) {
16524
- return `Auth: log in once at the app's login page. Username: \`${login}\` \xB7 Password: \`${password}\`. Type each field key-by-key (Playwright \`locator.pressSequentially()\` / the MCP \`browser_type\` tool), NOT a one-shot \`fill()\` or value assignment: pasting a value in a single step often fails to fire the login form's framework onChange handler, so the form submits empty and you get a FALSE "invalid email or password". After typing, confirm the field shows the value before clicking submit; if the first attempt is rejected, re-type key-by-key before treating the credentials as wrong. If a login form's inputs don't respond to typing-by-label/placeholder, inspect the DOM and target them by their \`id\`/\`name\` attribute instead \u2014 e.g. a Payload CMS admin login at \`/admin\` uses \`#field-email\` and \`#field-password\`. The app may have TWO separate logins (a public/frontend one and a Payload \`/admin\` one); if a change you must verify lives behind the admin, log into that form too. Re-use the session afterwards.`;
16524
+ return `Auth: QA credentials for \`${login}\` are configured. The engine will prepare the authenticated browser session before the agent starts; the password is never included in agent context.`;
16525
16525
  }
16526
16526
  if (login) {
16527
16527
  return `Auth: username \`${login}\` is configured but no \`LOGIN_PASSWORD\` secret was found. Note auth-gated surfaces as gaps.`;
@@ -18410,11 +18410,84 @@ function writeKodyStorageState(input) {
18410
18410
  fs48.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
18411
18411
  return { directory, file };
18412
18412
  }
18413
+ function parseSetCookie(value, hostname) {
18414
+ const parts = value.split(";").map((part) => part.trim());
18415
+ const pair = parts.shift() ?? "";
18416
+ const separator = pair.indexOf("=");
18417
+ if (separator <= 0) throw new Error("login response returned an invalid session cookie");
18418
+ const attributes = /* @__PURE__ */ new Map();
18419
+ for (const part of parts) {
18420
+ const index = part.indexOf("=");
18421
+ attributes.set(
18422
+ (index < 0 ? part : part.slice(0, index)).toLowerCase(),
18423
+ index < 0 ? "" : part.slice(index + 1)
18424
+ );
18425
+ }
18426
+ const sameSite = attributes.get("samesite")?.toLowerCase();
18427
+ return {
18428
+ name: pair.slice(0, separator),
18429
+ value: pair.slice(separator + 1),
18430
+ domain: attributes.get("domain")?.replace(/^\./, "") || hostname,
18431
+ path: attributes.get("path") || "/",
18432
+ expires: -1,
18433
+ httpOnly: attributes.has("httponly"),
18434
+ secure: attributes.has("secure"),
18435
+ sameSite: sameSite === "strict" ? "Strict" : sameSite === "none" ? "None" : "Lax"
18436
+ };
18437
+ }
18438
+ function writeCookieStorageState(targetUrl, setCookies) {
18439
+ const target = new URL(targetUrl);
18440
+ const directory = fs48.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
18441
+ fs48.chmodSync(directory, 448);
18442
+ const file = path45.join(directory, "storage-state.json");
18443
+ fs48.writeFileSync(
18444
+ file,
18445
+ JSON.stringify({
18446
+ cookies: setCookies.map((cookie) => parseSetCookie(cookie, target.hostname)),
18447
+ origins: []
18448
+ }),
18449
+ { mode: 384 }
18450
+ );
18451
+ return { directory, file };
18452
+ }
18453
+ function currentStorageStatePath(args) {
18454
+ for (let index = 0; index < args.length; index += 1) {
18455
+ const arg = args[index];
18456
+ if (arg === "--storage-state") return args[index + 1];
18457
+ if (arg.startsWith("--storage-state=")) return arg.slice("--storage-state=".length);
18458
+ }
18459
+ return void 0;
18460
+ }
18461
+ function mergeStorageStates(existingPath, nextPath) {
18462
+ if (existingPath === nextPath || !fs48.existsSync(existingPath)) return;
18463
+ const existing = JSON.parse(fs48.readFileSync(existingPath, "utf-8"));
18464
+ const next = JSON.parse(fs48.readFileSync(nextPath, "utf-8"));
18465
+ const cookies = /* @__PURE__ */ new Map();
18466
+ for (const cookie of [...existing.cookies ?? [], ...next.cookies ?? []]) {
18467
+ cookies.set(`${cookie.name}\0${cookie.domain}\0${cookie.path}`, cookie);
18468
+ }
18469
+ const origins = /* @__PURE__ */ new Map();
18470
+ for (const entry of [...existing.origins ?? [], ...next.origins ?? []]) {
18471
+ const current = origins.get(entry.origin);
18472
+ const localStorage = /* @__PURE__ */ new Map();
18473
+ for (const item of [...current?.localStorage ?? [], ...entry.localStorage ?? []]) {
18474
+ localStorage.set(item.name, item);
18475
+ }
18476
+ origins.set(entry.origin, { origin: entry.origin, localStorage: [...localStorage.values()] });
18477
+ }
18478
+ fs48.writeFileSync(
18479
+ nextPath,
18480
+ JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }),
18481
+ { mode: 384 }
18482
+ );
18483
+ }
18413
18484
  function configurePlaywright(profile, storageStatePath) {
18414
18485
  const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
18415
18486
  if (!playwright) throw new Error("Playwright MCP server is not configured");
18416
18487
  const args = [];
18417
18488
  const currentArgs = playwright.args ?? [];
18489
+ const existingStorageStatePath = currentStorageStatePath(currentArgs);
18490
+ if (existingStorageStatePath) mergeStorageStates(existingStorageStatePath, storageStatePath);
18418
18491
  for (let index = 0; index < currentArgs.length; index += 1) {
18419
18492
  const arg = currentArgs[index];
18420
18493
  if (arg === "--storage-state") {
@@ -18512,6 +18585,34 @@ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
18512
18585
  return false;
18513
18586
  }
18514
18587
  }
18588
+ async function prepareEmailPasswordBrowserAuth(ctx, profile, input) {
18589
+ const password = await resolveRuntimeSecret("LOGIN_PASSWORD", ctx);
18590
+ if (!input.login || !password.value) return false;
18591
+ let state;
18592
+ try {
18593
+ const origin = browserOrigin(input.targetUrl);
18594
+ const response = await fetch(`${origin}/api/auth/sign-in/email`, {
18595
+ method: "POST",
18596
+ headers: { "content-type": "application/json", origin },
18597
+ body: JSON.stringify({ email: input.login, password: password.value })
18598
+ });
18599
+ if (!response.ok) throw new Error(`app login returned ${response.status}`);
18600
+ const headers = response.headers;
18601
+ const cookies = headers.getSetCookie?.() ?? (headers.get("set-cookie") ? [headers.get("set-cookie")] : []);
18602
+ if (cookies.length === 0) throw new Error("app login returned no session cookie");
18603
+ state = writeCookieStorageState(input.targetUrl, cookies);
18604
+ configurePlaywright(profile, state.file);
18605
+ const authDirectory = state.directory;
18606
+ registerRuntimeCleanup(ctx, () => fs48.rmSync(authDirectory, { recursive: true, force: true }));
18607
+ ctx.data.qaAuthBlock = "Auth: the app is already signed in through an engine-provided browser session. The login credentials are not available to you; never request, reveal, or report them.";
18608
+ return true;
18609
+ } catch (error) {
18610
+ if (state) fs48.rmSync(state.directory, { recursive: true, force: true });
18611
+ const reason = error instanceof Error ? error.message : String(error);
18612
+ ctx.data.qaAuthBlock = `Auth: the engine could not prepare the app login (${reason}). Note this authenticated surface as a gap.`;
18613
+ return false;
18614
+ }
18615
+ }
18515
18616
  var prepareBrowserAuth;
18516
18617
  var init_prepareBrowserAuth = __esm({
18517
18618
  "src/scripts/prepareBrowserAuth.ts"() {
@@ -18738,6 +18839,12 @@ var init_prepareSimpleCapabilityRuntime = __esm({
18738
18839
  configureBrowser(ctx, profile, requirements);
18739
18840
  if (requirements.qaCredentials) {
18740
18841
  await loadQaContext(ctx, profile);
18842
+ const capabilityInput = ctx.data.capabilityInput && typeof ctx.data.capabilityInput === "object" && !Array.isArray(ctx.data.capabilityInput) ? ctx.data.capabilityInput : {};
18843
+ const targetUrl = typeof capabilityInput.url === "string" ? capabilityInput.url : typeof capabilityInput.targetUrl === "string" ? capabilityInput.targetUrl : "";
18844
+ await prepareEmailPasswordBrowserAuth(ctx, profile, {
18845
+ login: String(ctx.data.qaLogin ?? ""),
18846
+ targetUrl
18847
+ });
18741
18848
  }
18742
18849
  if (requirements.githubTestToken) {
18743
18850
  const capabilityInput = ctx.data.capabilityInput && typeof ctx.data.capabilityInput === "object" && !Array.isArray(ctx.data.capabilityInput) ? ctx.data.capabilityInput : {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.601",
3
+ "version": "0.4.603",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",