@wardby/cli 0.2.0 → 0.2.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 (157) hide show
  1. package/README.md +142 -67
  2. package/dist/claude-coding-worker/driver.d.ts +1 -0
  3. package/dist/claude-coding-worker/driver.js +4 -1
  4. package/dist/claude-coding-worker/main.js +3 -2
  5. package/dist/coding/protocol.d.ts +28 -3
  6. package/dist/coding/protocol.js +53 -2
  7. package/dist/coding-worker/artifact.d.ts +12 -0
  8. package/dist/coding-worker/artifact.js +48 -6
  9. package/dist/coding-worker/driver.d.ts +1 -0
  10. package/dist/coding-worker/driver.js +4 -1
  11. package/dist/coding-worker/errors.d.ts +7 -0
  12. package/dist/coding-worker/errors.js +18 -0
  13. package/dist/coding-worker/main.js +3 -2
  14. package/dist/core/budget-groups.d.ts +1 -1
  15. package/dist/core/coding-queue.d.ts +1 -1
  16. package/dist/core/datastores.d.ts +1 -1
  17. package/dist/core/db.d.ts +36 -2
  18. package/dist/core/db.js +69 -2
  19. package/dist/core/dispatch.d.ts +16 -1
  20. package/dist/core/dispatch.js +33 -5
  21. package/dist/core/http-runtime.js +3 -4
  22. package/dist/core/lease.d.ts +1 -1
  23. package/dist/core/private-directory.d.ts +20 -0
  24. package/dist/core/private-directory.js +36 -0
  25. package/dist/core/reconciler.d.ts +1 -1
  26. package/dist/core/runner.d.ts +1 -1
  27. package/dist/core/scheduler.d.ts +1 -1
  28. package/dist/core/secrets.d.ts +1 -1
  29. package/dist/core/subagent-memory-tools.d.ts +1 -1
  30. package/dist/core/webhooks.d.ts +1 -1
  31. package/dist/generated/prisma/browser.d.ts +228 -0
  32. package/dist/generated/prisma/browser.js +17 -0
  33. package/dist/generated/prisma/client.d.ts +247 -0
  34. package/dist/generated/prisma/client.js +34 -0
  35. package/dist/generated/prisma/commonInputTypes.d.ts +809 -0
  36. package/dist/generated/prisma/commonInputTypes.js +10 -0
  37. package/dist/generated/prisma/enums.d.ts +53 -0
  38. package/dist/generated/prisma/enums.js +54 -0
  39. package/dist/generated/prisma/internal/class.d.ts +462 -0
  40. package/dist/generated/prisma/internal/class.js +49 -0
  41. package/dist/generated/prisma/internal/prismaNamespace.d.ts +3325 -0
  42. package/dist/generated/prisma/internal/prismaNamespace.js +455 -0
  43. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +449 -0
  44. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +426 -0
  45. package/dist/generated/prisma/models/Agent.d.ts +3267 -0
  46. package/dist/generated/prisma/models/Agent.js +1 -0
  47. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  48. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  49. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  50. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  51. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  52. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  53. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  54. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  55. package/dist/generated/prisma/models/AgentTool.d.ts +1309 -0
  56. package/dist/generated/prisma/models/AgentTool.js +1 -0
  57. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  58. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  59. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  60. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  61. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  62. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  63. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  64. package/dist/generated/prisma/models/AuthSession.js +1 -0
  65. package/dist/generated/prisma/models/AuthUser.d.ts +1645 -0
  66. package/dist/generated/prisma/models/AuthUser.js +1 -0
  67. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  68. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  69. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1430 -0
  70. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  71. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  72. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  73. package/dist/generated/prisma/models/CodingProxySession.d.ts +1505 -0
  74. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  75. package/dist/generated/prisma/models/CodingRun.d.ts +2402 -0
  76. package/dist/generated/prisma/models/CodingRun.js +1 -0
  77. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  78. package/dist/generated/prisma/models/Datastore.js +1 -0
  79. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  80. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  81. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  82. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  83. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  84. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  85. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  86. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  87. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  88. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  89. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  90. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  91. package/dist/generated/prisma/models/Principal.d.ts +1790 -0
  92. package/dist/generated/prisma/models/Principal.js +1 -0
  93. package/dist/generated/prisma/models/Run.d.ts +2281 -0
  94. package/dist/generated/prisma/models/Run.js +1 -0
  95. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  96. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  97. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  98. package/dist/generated/prisma/models/Secret.js +1 -0
  99. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  100. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  101. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  102. package/dist/generated/prisma/models/Task.js +1 -0
  103. package/dist/generated/prisma/models/Tool.d.ts +1478 -0
  104. package/dist/generated/prisma/models/Tool.js +1 -0
  105. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  106. package/dist/generated/prisma/models/Webhook.js +1 -0
  107. package/dist/generated/prisma/models.d.ts +32 -0
  108. package/dist/generated/prisma/models.js +1 -0
  109. package/dist/import/create.d.ts +1 -1
  110. package/dist/import/create.js +1 -1
  111. package/dist/import/index.d.ts +1 -1
  112. package/dist/mcp/auth/ownership.d.ts +19 -19
  113. package/dist/mcp/auth/principal.d.ts +1 -1
  114. package/dist/mcp/auth/resource-server.d.ts +1 -1
  115. package/dist/mcp/auth/self-hosted/browser.js +20 -6
  116. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  117. package/dist/mcp/auth/self-hosted/credentials.d.ts +1 -1
  118. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  119. package/dist/mcp/auth/self-hosted/session.d.ts +1 -1
  120. package/dist/mcp/context.d.ts +1 -1
  121. package/dist/mcp/server.d.ts +1 -1
  122. package/dist/mcp/shared/page-style.d.ts +16 -0
  123. package/dist/mcp/shared/page-style.js +129 -0
  124. package/dist/mcp/tasks/manager.d.ts +1 -1
  125. package/dist/mcp/tools/agents.js +1 -1
  126. package/dist/mcp/tools/datastore.js +1 -1
  127. package/dist/mcp/tools/scheduling.js +1 -1
  128. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  129. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  130. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  131. package/dist/mcp/tools/subagents.js +1 -1
  132. package/dist/mcp/tools/tools.js +1 -1
  133. package/dist/mcp/transport/streamable-http.d.ts +1 -1
  134. package/dist/mcp/unattended-schedules.d.ts +1 -1
  135. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  136. package/dist/providers/auth/index.d.ts +1 -1
  137. package/dist/providers/auth/self-hosted.d.ts +2 -2
  138. package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -1
  139. package/dist/providers/coding-proxy/runtime.d.ts +1 -1
  140. package/dist/providers/datastore/postgres.d.ts +1 -1
  141. package/dist/providers/executor/build.d.ts +1 -1
  142. package/dist/providers/executor/composition.d.ts +1 -1
  143. package/dist/providers/executor/container.d.ts +1 -1
  144. package/dist/providers/executor/container.js +10 -3
  145. package/dist/providers/executor/dbos.d.ts +1 -1
  146. package/dist/providers/jobs/docker.d.ts +11 -0
  147. package/dist/providers/jobs/docker.js +34 -18
  148. package/dist/providers/jobs/kubernetes.js +5 -10
  149. package/dist/providers/jobs/types.d.ts +2 -0
  150. package/dist/providers/memory/postgres.d.ts +1 -1
  151. package/dist/providers/vcs/git.js +2 -1
  152. package/dist/quickstart/index.js +22 -31
  153. package/dist/quickstart/migrate.d.ts +23 -0
  154. package/dist/quickstart/migrate.js +125 -0
  155. package/package.json +20 -10
  156. package/prisma/migrate.config.mjs +20 -0
  157. package/prisma/schema.prisma +10 -2
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import type { AuthProfile, AuthProvider, AuthTokens, VerifiedToken } from "./types.js";
3
3
  import { Credentials } from "../../mcp/auth/self-hosted/credentials.js";
4
4
  import { Sessions } from "../../mcp/auth/self-hosted/session.js";
@@ -34,7 +34,7 @@ export declare class SelfHostedAuthProvider implements AuthProvider {
34
34
  consentPage(sessionToken: string, interactionId: string): Promise<{
35
35
  interaction: {
36
36
  client: {
37
- metadata: import("@prisma/client/runtime/library").JsonValue;
37
+ metadata: import("@prisma/client/runtime/client").JsonValue;
38
38
  createdAt: Date;
39
39
  clientId: string;
40
40
  };
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import type { CreateProxySessionInput, ProxyLedger, ProxyRequest, ProxySession, ProxyUsage, ReserveProxyRequestInput, ReserveProxyRequestResult } from "./types.js";
3
3
  export type PrismaProxyLedgerDb = Pick<PrismaClient, "$transaction" | "$queryRaw" | "$executeRaw">;
4
4
  export declare class PrismaProxyLedger implements ProxyLedger {
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import { startDenyPortListener } from "./deny-port.js";
3
3
  import { startCodingProxyServer, type CodingProxyServerHandle } from "./server.js";
4
4
  import type { ProxyAuditSink } from "./types.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Postgres-backed default Datastore adapter, via the `DatastoreEntry` table.
3
3
  */
4
- import type { PrismaClient } from "@prisma/client";
4
+ import type { PrismaClient } from "#prisma";
5
5
  import type { Datastore, DatastoreSetOptions, DatastoreValue } from "./types.js";
6
6
  import type { SecretCipher } from "../secrets/types.js";
7
7
  export type DatastoreDb = Pick<PrismaClient, "datastoreEntry" | "datastore" | "$queryRaw">;
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import type { ProviderRegistry } from "../index.js";
3
3
  import { type ProviderConfig } from "../../config/providers.js";
4
4
  import type { Executor } from "./types.js";
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import { type ProviderConfig } from "../../config/providers.js";
3
3
  import type { KubernetesApi } from "../jobs/kubernetes-api.js";
4
4
  import type { Executor } from "./types.js";
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  import { type CodingRunObserver } from "../../coding/observability.js";
3
3
  import { type CodingRunResult } from "../../coding/protocol.js";
4
4
  import type { ProxyProtocol } from "../coding-proxy/types.js";
@@ -3,6 +3,7 @@ import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { join, resolve, sep } from "node:path";
4
4
  import { CodingProfileSchema } from "../../coding/profile.js";
5
5
  import { logger } from "../../core/logger.js";
6
+ import { ensurePrivateDirectory } from "../../core/private-directory.js";
6
7
  import { codingRunObserver } from "../../coding/observability.js";
7
8
  import { CODING_PROTOCOL_VERSION, CodingRunResultSchema, CodingTaskInputSchema, parseCodingAgentOutputJson, redactAndTruncate, } from "../../coding/protocol.js";
8
9
  import { getModelPricing } from "../llm/pricing.js";
@@ -597,7 +598,7 @@ export class ContainerExecutor {
597
598
  const reason = collected?.diagnostic ?? collected?.reason ?? jobState;
598
599
  this.emit({ stage: "collected", runId: run.runId, jobId: handle.id });
599
600
  const status = jobState === "lost" ? "lost" : "failed";
600
- const failure = this.failure(`job_${reason}`);
601
+ const failure = this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
601
602
  await this.options.store.terminate(run.runId, status, failure.error, failure.audit);
602
603
  this.terminal(run, status, failure.audit);
603
604
  return;
@@ -755,7 +756,7 @@ export class ContainerExecutor {
755
756
  throw new Error("coding_provider_not_configured:claude-code");
756
757
  }
757
758
  async writeInput(run, deadlineAt) {
758
- await mkdir(this.artifactRoot, { recursive: true, mode: 0o700 });
759
+ await ensurePrivateDirectory(this.artifactRoot);
759
760
  const rootReal = await realpath(this.artifactRoot);
760
761
  const directory = resolve(rootReal, run.runId);
761
762
  if (!directory.startsWith(`${rootReal}${sep}`))
@@ -881,7 +882,13 @@ export class ContainerExecutor {
881
882
  // keyed by the same diagnostic id. Token-shaped values are redacted, and a
882
883
  // cause chain is kept because the outer message is often just a wrapper.
883
884
  // A user asking to stop is not a failure, so it gets the same id at info.
884
- const line = { diagnosticId, category, reason: describeFailure(error) };
885
+ // `issues` names only which output-schema fields failed (see SAFE_CODING_OUTPUT_ISSUE).
886
+ const line = {
887
+ diagnosticId,
888
+ category,
889
+ reason: describeFailure(error),
890
+ ...(options.issues ? { issues: options.issues } : {}),
891
+ };
885
892
  if (options.cancelled === true)
886
893
  containerLog.info(line, "coding run cancelled; the persisted error is its id only");
887
894
  else
@@ -12,7 +12,7 @@
12
12
  * stale-heartbeat detection is unchanged; the reconciler consults
13
13
  * `recover()` (Task 6) before declaring a DBOS run lost.
14
14
  */
15
- import type { PrismaClient } from "@prisma/client";
15
+ import type { PrismaClient } from "#prisma";
16
16
  import type { ProviderRegistry } from "../index.js";
17
17
  import type { DbosConfig } from "../../config/providers.js";
18
18
  import { type RunnerDb } from "../../core/runner.js";
@@ -1,5 +1,16 @@
1
1
  import type { JobHandle, JobResult, JobSpec, JobStatus, WorkspaceJobLauncher } from "./types.js";
2
2
  export declare const SAFE_WORKER_DIAGNOSTIC: RegExp;
3
+ export interface WorkerDiagnostic {
4
+ diagnostic: string;
5
+ diagnosticIssues?: string[];
6
+ }
7
+ /**
8
+ * Worker output is untrusted: recognizes only the fixed `{"error": code}` line,
9
+ * plus an optional `issues` list whose every entry must pass the shared pattern.
10
+ * A list with any entry that does not is dropped whole rather than filtered, so
11
+ * a malformed worker cannot smuggle a partial value through.
12
+ */
13
+ export declare function parseWorkerDiagnosticLine(line: string): WorkerDiagnostic | undefined;
3
14
  export interface DockerCommandOptions {
4
15
  env?: Record<string, string>;
5
16
  maxOutputBytes?: number;
@@ -3,7 +3,9 @@ import { spawn } from "node:child_process";
3
3
  import { lstat, mkdir, mkdtemp, open, opendir, readdir, readFile, readlink, realpath, rename, rm, unlink, } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import { logger } from "../../core/logger.js";
6
- import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES } from "../../coding/protocol.js";
6
+ import { ensurePrivateDirectory } from "../../core/private-directory.js";
7
+ import { readBoundedRegularFile } from "../../coding-worker/artifact.js";
8
+ import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES, MAX_CODING_OUTPUT_ISSUES, SAFE_CODING_OUTPUT_ISSUE, } from "../../coding/protocol.js";
7
9
  import { assertDockerHostSupportsIsolation, assertClaudeAgentContainerInspection, assertClaudeToolRunnerContainerInspection, assertIsolationNetworkInspection, assertKeeperContainerInspection, assertProxyContainerInspection, assertStorageVolumeInspection, assertWorkerContainerInspection, buildDockerIsolationPlan, } from "./docker-isolation.js";
8
10
  import { replaceDirectoryFromStaging } from "./workspace-swap.js";
9
11
  const BACKEND = "docker";
@@ -13,6 +15,31 @@ const MAX_DOCKER_SEED_DIAGNOSTIC_BYTES = 4 * 1024;
13
15
  const MAX_WORKSPACE_ENTRIES = 100_000;
14
16
  const TERMINAL_PHASES = new Set(["succeeded", "failed", "stopped", "lost", "removed"]);
15
17
  export const SAFE_WORKER_DIAGNOSTIC = /^(?:worker_[a-z_]+|coding_[a-z_]+|wardby_[a-z_]+)$/;
18
+ /**
19
+ * Worker output is untrusted: recognizes only the fixed `{"error": code}` line,
20
+ * plus an optional `issues` list whose every entry must pass the shared pattern.
21
+ * A list with any entry that does not is dropped whole rather than filtered, so
22
+ * a malformed worker cannot smuggle a partial value through.
23
+ */
24
+ export function parseWorkerDiagnosticLine(line) {
25
+ let value;
26
+ try {
27
+ value = JSON.parse(line);
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ if (typeof value?.error !== "string" || !SAFE_WORKER_DIAGNOSTIC.test(value.error))
33
+ return undefined;
34
+ const issues = value.issues;
35
+ const safeIssues = Array.isArray(issues) &&
36
+ issues.length > 0 &&
37
+ issues.length <= MAX_CODING_OUTPUT_ISSUES &&
38
+ issues.every((issue) => typeof issue === "string" && SAFE_CODING_OUTPUT_ISSUE.test(issue))
39
+ ? issues
40
+ : undefined;
41
+ return { diagnostic: value.error, ...(safeIssues ? { diagnosticIssues: safeIssues } : {}) };
42
+ }
16
43
  const dockerLog = logger.child({ module: "docker-jobs" });
17
44
  /**
18
45
  * Docker failures never include daemon output in their public message -- that
@@ -440,7 +467,7 @@ export class DockerJobLauncher {
440
467
  if (current.phase === "failed" && !current.result.diagnostic) {
441
468
  const diagnostic = await this.readWorkerFailureDiagnostic(current);
442
469
  if (diagnostic) {
443
- current.result = { ...current.result, diagnostic };
470
+ current.result = { ...current.result, ...diagnostic };
444
471
  await this.writeRecord(current);
445
472
  }
446
473
  }
@@ -500,7 +527,7 @@ export class DockerJobLauncher {
500
527
  });
501
528
  }
502
529
  async initialize() {
503
- await mkdir(this.stateRoot, { recursive: true, mode: 0o700 });
530
+ await ensurePrivateDirectory(this.stateRoot);
504
531
  await mkdir(join(this.stateRoot, ".home"), { recursive: true, mode: 0o700 });
505
532
  await realpath(this.stateRoot);
506
533
  const records = await this.allRecords();
@@ -792,13 +819,7 @@ export class DockerJobLauncher {
792
819
  const target = join(staging, "result.json");
793
820
  try {
794
821
  await this.run(["container", "cp", `${this.keeperName(record)}:/run/wardby/storage/output/result.json`, target]);
795
- const metadata = await lstat(target);
796
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_CODING_ARTIFACT_BYTES) {
797
- throw new Error("docker_result_artifact_invalid");
798
- }
799
- const raw = await readFile(target, "utf8");
800
- if (Buffer.byteLength(raw, "utf8") > MAX_CODING_ARTIFACT_BYTES)
801
- throw new Error("docker_result_artifact_invalid");
822
+ const raw = await readBoundedRegularFile(target, MAX_CODING_ARTIFACT_BYTES, "docker_result_artifact_invalid");
802
823
  const output = parseCodingAgentOutputJson(raw);
803
824
  if (output.runId !== record.runId)
804
825
  throw new Error("docker_result_run_mismatch");
@@ -818,14 +839,9 @@ export class DockerJobLauncher {
818
839
  const logs = await this.run(["container", "logs", "--tail", "8", record.handle.id]);
819
840
  const raw = `${logs.stdout}\n${logs.stderr}`;
820
841
  for (const line of raw.split("\n").reverse()) {
821
- try {
822
- const value = JSON.parse(line);
823
- if (typeof value.error === "string" && SAFE_WORKER_DIAGNOSTIC.test(value.error))
824
- return value.error;
825
- }
826
- catch {
827
- // Worker output is untrusted; only parse one fixed JSON shape.
828
- }
842
+ const diagnostic = parseWorkerDiagnosticLine(line);
843
+ if (diagnostic)
844
+ return diagnostic;
829
845
  }
830
846
  }
831
847
  catch {
@@ -31,7 +31,7 @@ import { isIP } from "node:net";
31
31
  import { logger } from "../../core/logger.js";
32
32
  import { MAX_CODING_ARTIFACT_BYTES, parseCodingAgentOutputJson } from "../../coding/protocol.js";
33
33
  import { CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "./docker-isolation.js";
34
- import { SAFE_WORKER_DIAGNOSTIC } from "./docker.js";
34
+ import { parseWorkerDiagnosticLine } from "./docker.js";
35
35
  import { KubernetesAlreadyExistsError, KubernetesConflictError } from "./kubernetes-api.js";
36
36
  import { ENFORCEMENT_PROBE_DENY_REACHABLE, ENFORCEMENT_PROBE_PROVEN, ENFORCEMENT_PROBE_DENY_REFUSED, ENFORCEMENT_PROBE_PROXY_UNREACHABLE, KEEPER_CONTAINER, KEEPER_SEEDED_MARKER, KUBERNETES_ISOLATION_ERROR, STORAGE_ROOT, WORKER_CONTAINER, assertRunNetworkPolicyMatches, assertRunPodMatches, buildCapabilitySecret, buildRunNetworkPolicy, buildRunPod, enforcementProbeScript, kubernetesRunNames, kubernetesRunNamesForToken, runLabels, validateKubernetesSpec, } from "./kubernetes-isolation.js";
37
37
  import { readProxyWitness } from "./kubernetes-witness.js";
@@ -428,7 +428,7 @@ export class KubernetesJobLauncher {
428
428
  if (phase === "failed" && !result.diagnostic) {
429
429
  const diagnostic = await this.readWorkerDiagnostic(names);
430
430
  if (diagnostic)
431
- result = { ...result, diagnostic };
431
+ result = { ...result, ...diagnostic };
432
432
  }
433
433
  if (phase === "succeeded" && !result.resultArtifact) {
434
434
  result = { ...result, resultArtifact: await this.readResultArtifact(names, record.runId) };
@@ -715,14 +715,9 @@ export class KubernetesJobLauncher {
715
715
  return undefined;
716
716
  }
717
717
  for (const line of raw.split("\n").reverse()) {
718
- try {
719
- const value = JSON.parse(line);
720
- if (typeof value?.error === "string" && SAFE_WORKER_DIAGNOSTIC.test(value.error))
721
- return value.error;
722
- }
723
- catch {
724
- // Worker output is untrusted; only one fixed JSON shape is recognized.
725
- }
718
+ const diagnostic = parseWorkerDiagnosticLine(line);
719
+ if (diagnostic)
720
+ return diagnostic;
726
721
  }
727
722
  return undefined;
728
723
  }
@@ -45,6 +45,8 @@ export interface JobResult {
45
45
  resultArtifact?: string;
46
46
  /** Fixed worker-owned failure code; never raw container output. */
47
47
  diagnostic?: string;
48
+ /** Output-schema failure locations (`path:code`) that passed SAFE_CODING_OUTPUT_ISSUE; never values. */
49
+ diagnosticIssues?: string[];
48
50
  }
49
51
  /**
50
52
  * Lifecycle invariants implemented by every launcher:
@@ -6,7 +6,7 @@
6
6
  * schema.prisma). Every other operation goes through the normal Prisma
7
7
  * Client since it never touches `contentTsv`.
8
8
  */
9
- import type { PrismaClient } from "@prisma/client";
9
+ import type { PrismaClient } from "#prisma";
10
10
  import type { AgentMemorySearchHit, AgentMemoryStore } from "./types.js";
11
11
  export type AgentMemoryDb = Pick<PrismaClient, "agentMemory" | "$executeRaw" | "$queryRaw">;
12
12
  export declare class PostgresAgentMemory implements AgentMemoryStore {
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { lstat, mkdir, opendir, readFile, readlink, realpath, rm } from "node:fs/promises";
3
3
  import { isAbsolute, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { ensurePrivateDirectory } from "../../core/private-directory.js";
5
6
  import { MAX_REDACTED_SPAN, redactTokenShapedValues, normalizeGitHubRepository, normalizeGitRef, } from "../../coding/protocol.js";
6
7
  import { isSafeGitHubInstallationToken } from "./github.js";
7
8
  export const DEFAULT_MAX_CHANGED_FILES = 1_000;
@@ -220,7 +221,7 @@ export class GitVcsProvider {
220
221
  }
221
222
  async prepareWorkspace(input) {
222
223
  const normalized = this.validateInput(input);
223
- await mkdir(this.rootDir, { recursive: true, mode: 0o700 });
224
+ await ensurePrivateDirectory(this.rootDir);
224
225
  await mkdir(resolve(this.rootDir, ".home"), { recursive: true, mode: 0o700 });
225
226
  const runRoot = resolve(this.rootDir, normalized.runId);
226
227
  if (!directChild(this.rootDir, runRoot))
@@ -1,5 +1,4 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { createRequire } from "node:module";
3
2
  import { createServer } from "node:net";
4
3
  import { readFileSync, statSync } from "node:fs";
5
4
  import { join } from "node:path";
@@ -7,12 +6,11 @@ import { fileURLToPath } from "node:url";
7
6
  import { parseArgs } from "node:util";
8
7
  import { createInterface } from "node:readline/promises";
9
8
  import { composeProjectName, defaultModel, ensureWardbyIgnored, generatedSecret, providerKeyName, quickstartPaths, readQuickstartEnv, readQuickstartState, resolveProjectDir, writeQuickstartEnv, writeQuickstartState, } from "./config.js";
9
+ import { FETCH_NOTICE, migrateFailureReason, prismaMigrateInvocation, REGISTRY_FAILURE_MESSAGE, runPrismaMigrateInvocation, } from "./migrate.js";
10
10
  const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
11
11
  const composeFile = join(packageRoot, "deploy/local/docker-compose.yml");
12
- const schemaFile = join(packageRoot, "prisma/schema.prisma");
13
12
  const wardbyBin = join(packageRoot, "bin/wardby.js");
14
13
  const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
15
- const require = createRequire(import.meta.url);
16
14
  const DEMO_AGENT = "hello-wardby";
17
15
  const DEMO_PROMPT = "You are the Wardby quickstart agent. Reply with a short greeting, then explain in one sentence that Wardby admitted this run against an explicit maximum budget. Do not call tools.";
18
16
  function runCommand(command, args, options = {}) {
@@ -200,22 +198,21 @@ async function chooseProvider(requested, previous, existing, nonInteractive) {
200
198
  return "anthropic";
201
199
  throw new Error(`Unknown provider selection "${answer}".`);
202
200
  }
203
- function prismaCliPath() {
204
- return require.resolve("prisma/build/index.js");
205
- }
206
201
  function applyMigrations(paths) {
207
- const result = runCommand(process.execPath, [prismaCliPath(), "migrate", "deploy", "--schema", schemaFile], {
208
- cwd: paths.projectDir,
209
- env: runtimeEnv(paths),
210
- });
202
+ const invocation = prismaMigrateInvocation("deploy", runtimeEnv(paths));
203
+ if (!invocation.local)
204
+ console.log(FETCH_NOTICE);
205
+ const result = runPrismaMigrateInvocation(invocation, paths.projectDir);
206
+ if (result.registryFailure) {
207
+ const detail = /^npm (?:error|ERR!) code .*$/m.exec(result.stderr)?.[0] ?? result.stderr.trim().split("\n")[0];
208
+ throw new Error(`${REGISTRY_FAILURE_MESSAGE}${detail ? ` (${detail})` : ""}`);
209
+ }
211
210
  if (result.status !== 0)
212
211
  commandFailure("Database migration", result);
213
212
  }
214
213
  async function seedDemo(paths, state, budget) {
215
- const previousDatabaseUrl = process.env.DATABASE_URL;
216
- process.env.DATABASE_URL = readQuickstartEnv(paths).DATABASE_URL;
217
- const { PrismaClient } = await import("@prisma/client");
218
- const db = new PrismaClient();
214
+ const { createPrismaClient } = await import("../core/db.js");
215
+ const db = createPrismaClient(readQuickstartEnv(paths).DATABASE_URL);
219
216
  try {
220
217
  const existing = await db.agent.findUnique({ where: { name: DEMO_AGENT } });
221
218
  if (existing && existing.systemPrompt !== DEMO_PROMPT) {
@@ -241,10 +238,6 @@ async function seedDemo(paths, state, budget) {
241
238
  }
242
239
  finally {
243
240
  await db.$disconnect();
244
- if (previousDatabaseUrl === undefined)
245
- delete process.env.DATABASE_URL;
246
- else
247
- process.env.DATABASE_URL = previousDatabaseUrl;
248
241
  }
249
242
  }
250
243
  function runDemo(paths) {
@@ -419,10 +412,8 @@ async function databaseHealthy(paths) {
419
412
  const env = readQuickstartEnv(paths);
420
413
  if (!env.DATABASE_URL)
421
414
  return false;
422
- const previous = process.env.DATABASE_URL;
423
- process.env.DATABASE_URL = env.DATABASE_URL;
424
- const { PrismaClient } = await import("@prisma/client");
425
- const db = new PrismaClient();
415
+ const { createPrismaClient } = await import("../core/db.js");
416
+ const db = createPrismaClient(env.DATABASE_URL);
426
417
  try {
427
418
  await db.$queryRawUnsafe("SELECT 1");
428
419
  return true;
@@ -432,10 +423,6 @@ async function databaseHealthy(paths) {
432
423
  }
433
424
  finally {
434
425
  await db.$disconnect();
435
- if (previous === undefined)
436
- delete process.env.DATABASE_URL;
437
- else
438
- process.env.DATABASE_URL = previous;
439
426
  }
440
427
  }
441
428
  export async function doctorCommand(args) {
@@ -468,11 +455,15 @@ export async function doctorCommand(args) {
468
455
  const ps = runCompose(paths, state, ["ps", "--status", "running", "--quiet"]);
469
456
  checks.push(["PostgreSQL container", ps.status === 0 && ps.stdout.trim().length > 0]);
470
457
  checks.push(["Database connection", await databaseHealthy(paths)]);
471
- const migrations = runCommand(process.execPath, [prismaCliPath(), "migrate", "status", "--schema", schemaFile], {
472
- cwd: paths.projectDir,
473
- env: runtimeEnv(paths),
474
- });
475
- checks.push(["Database migrations", migrations.status === 0]);
458
+ const invocation = prismaMigrateInvocation("status", runtimeEnv(paths));
459
+ if (!invocation.local)
460
+ console.log(FETCH_NOTICE);
461
+ const migrations = runPrismaMigrateInvocation(invocation, paths.projectDir);
462
+ checks.push([
463
+ "Database migrations",
464
+ migrations.status === 0,
465
+ migrations.status === 0 ? undefined : migrateFailureReason(migrations),
466
+ ]);
476
467
  }
477
468
  let failed = false;
478
469
  for (const [name, ok, detail] of checks) {
@@ -0,0 +1,23 @@
1
+ export declare const PRISMA_CLI_VERSION = "7.10.0";
2
+ export declare const FETCH_NOTICE = "Fetching the Prisma CLI (prisma@7.10.0)\u2026";
3
+ export declare const REGISTRY_FAILURE_MESSAGE: string;
4
+ export declare const migrateConfigFile: string;
5
+ export interface MigrateResult {
6
+ status: number;
7
+ stdout: string;
8
+ stderr: string;
9
+ registryFailure?: boolean;
10
+ }
11
+ export interface MigrateInvocation {
12
+ command: string;
13
+ args: string[];
14
+ env: NodeJS.ProcessEnv;
15
+ local: boolean;
16
+ }
17
+ export declare function parseLocalPrismaVersion(stdout: string): string | undefined;
18
+ export declare function probeLocalPrismaVersion(env: NodeJS.ProcessEnv): string | undefined;
19
+ export declare function prismaMigrateInvocation(subcommand: "deploy" | "status", env: NodeJS.ProcessEnv, probe?: (env: NodeJS.ProcessEnv) => string | undefined): MigrateInvocation;
20
+ export declare function classifyMigrateResult(result: MigrateResult, spawnError?: Error, local?: boolean): MigrateResult;
21
+ export declare function migrateFailureReason(result: MigrateResult): string;
22
+ export declare function runPrismaMigrateInvocation(invocation: MigrateInvocation, cwd: string): MigrateResult;
23
+ export declare function runPrismaMigrate(subcommand: "deploy" | "status", env: NodeJS.ProcessEnv, cwd: string, probe?: (env: NodeJS.ProcessEnv) => string | undefined): MigrateResult;
@@ -0,0 +1,125 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ // The installed npm package does not carry the Prisma CLI (a devDependency:
4
+ // its tree pulled advisories into every consumer install), so quickstart and
5
+ // doctor first look for a `prisma` executable on PATH and use it only if it
6
+ // reports exactly this version; anything else -- wrong version, not on PATH,
7
+ // a spawn error, a non-zero exit, or a timeout -- falls back to running the
8
+ // pinned version through npx, with a config file shipped in the package.
9
+ // Keep this equal to package.json's devDependencies.prisma (migrate.test.ts
10
+ // enforces it).
11
+ //
12
+ // Not supported on Windows: npx is `npx.cmd` there (and a local install would
13
+ // be `prisma.cmd`), which spawnSync can only run through a shell, and
14
+ // quickstart has no win32 handling anywhere else (Docker Compose paths,
15
+ // POSIX file modes) either.
16
+ export const PRISMA_CLI_VERSION = "7.10.0";
17
+ export const FETCH_NOTICE = `Fetching the Prisma CLI (prisma@${PRISMA_CLI_VERSION})…`;
18
+ export const REGISTRY_FAILURE_MESSAGE = `Could not download the Prisma CLI (prisma@${PRISMA_CLI_VERSION}) from the npm registry. ` +
19
+ "quickstart and doctor fetch it on demand. Check your internet connection or npm registry settings " +
20
+ "(.npmrc, proxy), then retry. You can also install it yourself " +
21
+ `(\`npm i -g prisma@${PRISMA_CLI_VERSION}\`) and rerun quickstart -- doctor benefits too.`;
22
+ export const migrateConfigFile = fileURLToPath(new URL("../../prisma/migrate.config.mjs", import.meta.url));
23
+ // A short timeout so a wedged or misbehaving local binary can't stall
24
+ // quickstart/doctor -- any hang past this falls back to npx.
25
+ const LOCAL_PRISMA_PROBE_TIMEOUT_MS = 5000;
26
+ // `prisma --version` prints a fixed-label, padded-column report, e.g.:
27
+ // prisma : 7.10.0
28
+ // @prisma/client : 7.10.0
29
+ // Operating System : darwin
30
+ // ...
31
+ // Several other lines carry their own version-shaped numbers (Node.js,
32
+ // TypeScript, the schema engine hash), so key off the "prisma" label
33
+ // specifically -- not the first x.y.z token anywhere in the output.
34
+ const LOCAL_VERSION_LINE = /^prisma\s*:\s*(\S+)$/m;
35
+ export function parseLocalPrismaVersion(stdout) {
36
+ return LOCAL_VERSION_LINE.exec(stdout)?.[1];
37
+ }
38
+ // Runs a local `prisma --version` under the given env's PATH and returns the
39
+ // version it reports, or undefined for anything that isn't a clean match:
40
+ // not on PATH, a spawn error, a non-zero exit, a timeout, or unparseable
41
+ // output. Never throws.
42
+ export function probeLocalPrismaVersion(env) {
43
+ const result = spawnSync("prisma", ["--version"], {
44
+ env,
45
+ encoding: "utf8",
46
+ stdio: "pipe",
47
+ timeout: LOCAL_PRISMA_PROBE_TIMEOUT_MS,
48
+ });
49
+ if (result.error || result.status !== 0)
50
+ return undefined;
51
+ return parseLocalPrismaVersion(result.stdout ?? "");
52
+ }
53
+ // DATABASE_URL travels in the child's environment only -- never argv, where
54
+ // any local user could read it from the process table. `probe` is injectable
55
+ // so tests never depend on the developer's real PATH.
56
+ export function prismaMigrateInvocation(subcommand, env, probe = probeLocalPrismaVersion) {
57
+ const childEnv = {
58
+ ...env,
59
+ PRISMA_HIDE_UPDATE_MESSAGE: "1",
60
+ npm_config_update_notifier: "false",
61
+ npm_config_fund: "false",
62
+ };
63
+ if (probe(childEnv) === PRISMA_CLI_VERSION) {
64
+ return {
65
+ command: "prisma",
66
+ args: ["migrate", subcommand, "--config", migrateConfigFile],
67
+ env: childEnv,
68
+ local: true,
69
+ };
70
+ }
71
+ return {
72
+ command: "npx",
73
+ args: ["--yes", `prisma@${PRISMA_CLI_VERSION}`, "migrate", subcommand, "--config", migrateConfigFile],
74
+ env: childEnv,
75
+ local: false,
76
+ };
77
+ }
78
+ // npm's own error lines when it cannot fetch a package (npm 7+ prints
79
+ // "npm error", older "npm ERR!"): network failures and registry HTTP / version
80
+ // errors. A failed migration is printed by Prisma itself and never carries
81
+ // these -- in particular a database ECONNREFUSED is Prisma's P1001, not an
82
+ // "npm error code" line.
83
+ const REGISTRY_ERROR = /^npm (?:error|ERR!) (?:code|errno) (?:ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ETARGET|E[45]\d\d)\b|^npm (?:error|ERR!) network /m;
84
+ // `local` marks that `result`/`spawnError` came from the locally installed
85
+ // binary rather than npx: nothing was fetched, so neither an npm-shaped
86
+ // error line nor a spawn error is a registry failure there -- a spawn error
87
+ // still gets a clear message (below), just not that one.
88
+ export function classifyMigrateResult(result, spawnError, local = false) {
89
+ if (spawnError) {
90
+ if (local)
91
+ return { ...result, status: 1, stderr: spawnError.message };
92
+ return { ...result, status: 1, stderr: spawnError.message, registryFailure: true };
93
+ }
94
+ if (!local && result.status !== 0 && REGISTRY_ERROR.test(result.stderr))
95
+ return { ...result, registryFailure: true };
96
+ return result;
97
+ }
98
+ // The one-line reason for a failed run: the registry message, or Prisma's own
99
+ // first error line (URLs masked, though Prisma prints host and database only).
100
+ export function migrateFailureReason(result) {
101
+ if (result.registryFailure)
102
+ return REGISTRY_FAILURE_MESSAGE;
103
+ const lines = `${result.stderr}\n${result.stdout}`
104
+ .split("\n")
105
+ .map((line) => line.trim())
106
+ .filter(Boolean);
107
+ // A line that starts with "Error", or that carries a Prisma error code anywhere.
108
+ const line = lines.find((candidate) => candidate.startsWith("Error") || /\bP\d{4}\b/.test(candidate)) ?? lines[0] ?? "no output";
109
+ return line.replace(/postgres(ql)?:\/\/[^\s`'"]+/gi, "<database url>");
110
+ }
111
+ // Runs an already-resolved invocation (from `prismaMigrateInvocation`). Split
112
+ // out so callers can decide whether to print `FETCH_NOTICE` -- which only
113
+ // applies to the npx path -- before running it.
114
+ export function runPrismaMigrateInvocation(invocation, cwd) {
115
+ const result = spawnSync(invocation.command, invocation.args, {
116
+ cwd,
117
+ env: invocation.env,
118
+ encoding: "utf8",
119
+ stdio: "pipe",
120
+ });
121
+ return classifyMigrateResult({ status: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }, result.error, invocation.local);
122
+ }
123
+ export function runPrismaMigrate(subcommand, env, cwd, probe) {
124
+ return runPrismaMigrateInvocation(prismaMigrateInvocation(subcommand, env, probe), cwd);
125
+ }