@wardby/cli 0.1.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.
- package/README.md +183 -96
- package/deploy/local/docker-compose.yml +5 -5
- package/dist/claude-coding-worker/driver.d.ts +1 -0
- package/dist/claude-coding-worker/driver.js +4 -1
- package/dist/claude-coding-worker/main.js +3 -2
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -0
- package/dist/cli.js +0 -0
- package/dist/coding/protocol.d.ts +28 -3
- package/dist/coding/protocol.js +53 -2
- package/dist/coding-worker/artifact.d.ts +12 -0
- package/dist/coding-worker/artifact.js +48 -6
- package/dist/coding-worker/driver.d.ts +1 -0
- package/dist/coding-worker/driver.js +4 -1
- package/dist/coding-worker/errors.d.ts +7 -0
- package/dist/coding-worker/errors.js +18 -0
- package/dist/coding-worker/main.js +3 -2
- package/dist/core/budget-groups.d.ts +1 -1
- package/dist/core/coding-queue.d.ts +1 -1
- package/dist/core/datastores.d.ts +1 -1
- package/dist/core/db.d.ts +36 -2
- package/dist/core/db.js +69 -2
- package/dist/core/dispatch.d.ts +16 -1
- package/dist/core/dispatch.js +33 -5
- package/dist/core/http-runtime.js +3 -4
- package/dist/core/lease.d.ts +1 -1
- package/dist/core/private-directory.d.ts +20 -0
- package/dist/core/private-directory.js +36 -0
- package/dist/core/reconciler.d.ts +1 -1
- package/dist/core/runner.d.ts +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/secrets.d.ts +1 -1
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/webhooks.d.ts +1 -1
- package/dist/env.js +8 -1
- package/dist/generated/prisma/browser.d.ts +228 -0
- package/dist/generated/prisma/browser.js +17 -0
- package/dist/generated/prisma/client.d.ts +247 -0
- package/dist/generated/prisma/client.js +34 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +809 -0
- package/dist/generated/prisma/commonInputTypes.js +10 -0
- package/dist/generated/prisma/enums.d.ts +53 -0
- package/dist/generated/prisma/enums.js +54 -0
- package/dist/generated/prisma/internal/class.d.ts +462 -0
- package/dist/generated/prisma/internal/class.js +49 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +3325 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +455 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +449 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +426 -0
- package/dist/generated/prisma/models/Agent.d.ts +3267 -0
- package/dist/generated/prisma/models/Agent.js +1 -0
- package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
- package/dist/generated/prisma/models/AgentDatastore.js +1 -0
- package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
- package/dist/generated/prisma/models/AgentMemory.js +1 -0
- package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
- package/dist/generated/prisma/models/AgentSecret.js +1 -0
- package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
- package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +1309 -0
- package/dist/generated/prisma/models/AgentTool.js +1 -0
- package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
- package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
- package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
- package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
- package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
- package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
- package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
- package/dist/generated/prisma/models/AuthSession.js +1 -0
- package/dist/generated/prisma/models/AuthUser.d.ts +1645 -0
- package/dist/generated/prisma/models/AuthUser.js +1 -0
- package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
- package/dist/generated/prisma/models/BudgetGroup.js +1 -0
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1430 -0
- package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
- package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
- package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
- package/dist/generated/prisma/models/CodingProxySession.d.ts +1505 -0
- package/dist/generated/prisma/models/CodingProxySession.js +1 -0
- package/dist/generated/prisma/models/CodingRun.d.ts +2402 -0
- package/dist/generated/prisma/models/CodingRun.js +1 -0
- package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
- package/dist/generated/prisma/models/Datastore.js +1 -0
- package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
- package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
- package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
- package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
- package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
- package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
- package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
- package/dist/generated/prisma/models/OAuthClient.js +1 -0
- package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
- package/dist/generated/prisma/models/OAuthFamily.js +1 -0
- package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
- package/dist/generated/prisma/models/OAuthGrant.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +1790 -0
- package/dist/generated/prisma/models/Principal.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +2281 -0
- package/dist/generated/prisma/models/Run.js +1 -0
- package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
- package/dist/generated/prisma/models/SchedulerLease.js +1 -0
- package/dist/generated/prisma/models/Secret.d.ts +1399 -0
- package/dist/generated/prisma/models/Secret.js +1 -0
- package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
- package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
- package/dist/generated/prisma/models/Task.d.ts +1176 -0
- package/dist/generated/prisma/models/Task.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +1478 -0
- package/dist/generated/prisma/models/Tool.js +1 -0
- package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
- package/dist/generated/prisma/models/Webhook.js +1 -0
- package/dist/generated/prisma/models.d.ts +32 -0
- package/dist/generated/prisma/models.js +1 -0
- package/dist/import/create.d.ts +1 -1
- package/dist/import/create.js +1 -1
- package/dist/import/index.d.ts +1 -1
- package/dist/mcp/auth/ownership.d.ts +19 -19
- package/dist/mcp/auth/principal.d.ts +1 -1
- package/dist/mcp/auth/resource-server.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/browser.js +20 -6
- package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/credentials.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/session.d.ts +1 -1
- package/dist/mcp/context.d.ts +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/shared/page-style.d.ts +16 -0
- package/dist/mcp/shared/page-style.js +129 -0
- package/dist/mcp/tasks/manager.d.ts +1 -1
- package/dist/mcp/tools/agents.js +1 -1
- package/dist/mcp/tools/datastore.js +1 -1
- package/dist/mcp/tools/scheduling.js +1 -1
- package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
- package/dist/mcp/tools/secret-elicitation-form.js +10 -28
- package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
- package/dist/mcp/tools/subagents.js +1 -1
- package/dist/mcp/tools/tools.js +1 -1
- package/dist/mcp/transport/streamable-http.d.ts +1 -1
- package/dist/mcp/unattended-schedules.d.ts +1 -1
- package/dist/mcp/webhooks/ingress.d.ts +1 -1
- package/dist/providers/auth/index.d.ts +1 -1
- package/dist/providers/auth/self-hosted.d.ts +2 -2
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -1
- package/dist/providers/coding-proxy/runtime.d.ts +1 -1
- package/dist/providers/datastore/postgres.d.ts +1 -1
- package/dist/providers/executor/build.d.ts +1 -1
- package/dist/providers/executor/composition.d.ts +1 -1
- package/dist/providers/executor/container.d.ts +1 -1
- package/dist/providers/executor/container.js +10 -3
- package/dist/providers/executor/dbos.d.ts +1 -1
- package/dist/providers/jobs/docker.d.ts +11 -0
- package/dist/providers/jobs/docker.js +34 -18
- package/dist/providers/jobs/kubernetes.js +5 -10
- package/dist/providers/jobs/types.d.ts +2 -0
- package/dist/providers/memory/postgres.d.ts +1 -1
- package/dist/providers/vcs/git.js +2 -1
- package/dist/quickstart/config.d.ts +30 -0
- package/dist/quickstart/config.js +106 -0
- package/dist/quickstart/index.d.ts +5 -0
- package/dist/quickstart/index.js +527 -0
- package/dist/quickstart/migrate.d.ts +23 -0
- package/dist/quickstart/migrate.js +125 -0
- package/dist/wardby-bin.js +35 -9
- package/package.json +22 -9
- package/prisma/migrate.config.mjs +20 -0
- package/prisma/schema.prisma +10 -2
- package/dist/providers/auth/release-gate.d.ts +0 -1
- package/dist/providers/auth/release-gate.js +0 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PrismaClient } from "
|
|
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/
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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 "
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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 {
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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 "
|
|
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
|
|
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))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type QuickstartProvider = "openai" | "anthropic";
|
|
2
|
+
export type McpClient = "none" | "codex" | "claude" | "both";
|
|
3
|
+
export interface QuickstartState {
|
|
4
|
+
version: 1;
|
|
5
|
+
projectDir: string;
|
|
6
|
+
composeProject: string;
|
|
7
|
+
postgresPort: number;
|
|
8
|
+
provider: QuickstartProvider;
|
|
9
|
+
model: string;
|
|
10
|
+
packageVersion: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
13
|
+
export interface QuickstartPaths {
|
|
14
|
+
projectDir: string;
|
|
15
|
+
wardbyDir: string;
|
|
16
|
+
envFile: string;
|
|
17
|
+
stateFile: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveProjectDir(cwd?: string, env?: NodeJS.ProcessEnv): string;
|
|
20
|
+
export declare function quickstartPaths(projectDir: string): QuickstartPaths;
|
|
21
|
+
export declare function composeProjectName(projectDir: string): string;
|
|
22
|
+
export declare function parseEnvFile(contents: string): Record<string, string>;
|
|
23
|
+
export declare function readQuickstartEnv(paths: QuickstartPaths): Record<string, string>;
|
|
24
|
+
export declare function writeQuickstartEnv(paths: QuickstartPaths, values: Record<string, string>): void;
|
|
25
|
+
export declare function readQuickstartState(paths: QuickstartPaths): QuickstartState | undefined;
|
|
26
|
+
export declare function writeQuickstartState(paths: QuickstartPaths, state: QuickstartState): void;
|
|
27
|
+
export declare function generatedSecret(): string;
|
|
28
|
+
export declare function ensureWardbyIgnored(projectDir: string): void;
|
|
29
|
+
export declare function defaultModel(provider: QuickstartProvider): string;
|
|
30
|
+
export declare function providerKeyName(provider: QuickstartProvider): "OPENAI_API_KEY" | "ANTHROPIC_API_KEY";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
export function resolveProjectDir(cwd = process.cwd(), env = process.env) {
|
|
5
|
+
return resolve(env.WARDBY_PROJECT_DIR || cwd);
|
|
6
|
+
}
|
|
7
|
+
export function quickstartPaths(projectDir) {
|
|
8
|
+
const wardbyDir = join(projectDir, ".wardby");
|
|
9
|
+
return {
|
|
10
|
+
projectDir,
|
|
11
|
+
wardbyDir,
|
|
12
|
+
envFile: join(wardbyDir, ".env"),
|
|
13
|
+
stateFile: join(wardbyDir, "state.json"),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function composeProjectName(projectDir) {
|
|
17
|
+
const slug = basename(projectDir)
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
20
|
+
.replace(/^-+|-+$/g, "")
|
|
21
|
+
.slice(0, 30);
|
|
22
|
+
const digest = createHash("sha256").update(resolve(projectDir)).digest("hex").slice(0, 10);
|
|
23
|
+
return `wardby-${slug || "project"}-${digest}`;
|
|
24
|
+
}
|
|
25
|
+
export function parseEnvFile(contents) {
|
|
26
|
+
const values = {};
|
|
27
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
28
|
+
const line = rawLine.trim();
|
|
29
|
+
if (!line || line.startsWith("#"))
|
|
30
|
+
continue;
|
|
31
|
+
const equals = line.indexOf("=");
|
|
32
|
+
if (equals <= 0)
|
|
33
|
+
continue;
|
|
34
|
+
const key = line.slice(0, equals).trim();
|
|
35
|
+
const rawValue = line.slice(equals + 1).trim();
|
|
36
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
37
|
+
continue;
|
|
38
|
+
if (rawValue.startsWith('"') && rawValue.endsWith('"')) {
|
|
39
|
+
try {
|
|
40
|
+
values[key] = JSON.parse(rawValue);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Preserve malformed quoted values verbatim so setup never destroys them.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
values[key] = rawValue.startsWith("'") && rawValue.endsWith("'") ? rawValue.slice(1, -1) : rawValue;
|
|
48
|
+
}
|
|
49
|
+
return values;
|
|
50
|
+
}
|
|
51
|
+
export function readQuickstartEnv(paths) {
|
|
52
|
+
if (!existsSync(paths.envFile))
|
|
53
|
+
return {};
|
|
54
|
+
return parseEnvFile(readFileSync(paths.envFile, "utf8"));
|
|
55
|
+
}
|
|
56
|
+
export function writeQuickstartEnv(paths, values) {
|
|
57
|
+
mkdirSync(paths.wardbyDir, { recursive: true, mode: 0o700 });
|
|
58
|
+
const heading = [
|
|
59
|
+
"# Managed by `wardby quickstart`.",
|
|
60
|
+
"# Keep this file private; it contains local credentials and encryption keys.",
|
|
61
|
+
];
|
|
62
|
+
const lines = Object.entries(values)
|
|
63
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
64
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`);
|
|
65
|
+
writeFileSync(paths.envFile, `${[...heading, ...lines].join("\n")}\n`, { mode: 0o600 });
|
|
66
|
+
chmodSync(paths.wardbyDir, 0o700);
|
|
67
|
+
chmodSync(paths.envFile, 0o600);
|
|
68
|
+
}
|
|
69
|
+
export function readQuickstartState(paths) {
|
|
70
|
+
if (!existsSync(paths.stateFile))
|
|
71
|
+
return undefined;
|
|
72
|
+
const value = JSON.parse(readFileSync(paths.stateFile, "utf8"));
|
|
73
|
+
if (value.version !== 1 ||
|
|
74
|
+
typeof value.projectDir !== "string" ||
|
|
75
|
+
typeof value.composeProject !== "string" ||
|
|
76
|
+
typeof value.postgresPort !== "number" ||
|
|
77
|
+
(value.provider !== "openai" && value.provider !== "anthropic") ||
|
|
78
|
+
typeof value.model !== "string" ||
|
|
79
|
+
typeof value.packageVersion !== "string" ||
|
|
80
|
+
typeof value.createdAt !== "string") {
|
|
81
|
+
throw new Error(`invalid Wardby quickstart state: ${paths.stateFile}`);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
export function writeQuickstartState(paths, state) {
|
|
86
|
+
mkdirSync(paths.wardbyDir, { recursive: true, mode: 0o700 });
|
|
87
|
+
writeFileSync(paths.stateFile, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
88
|
+
chmodSync(paths.stateFile, 0o600);
|
|
89
|
+
}
|
|
90
|
+
export function generatedSecret() {
|
|
91
|
+
return randomBytes(32).toString("hex");
|
|
92
|
+
}
|
|
93
|
+
export function ensureWardbyIgnored(projectDir) {
|
|
94
|
+
const gitignore = join(projectDir, ".gitignore");
|
|
95
|
+
const existing = existsSync(gitignore) ? readFileSync(gitignore, "utf8") : "";
|
|
96
|
+
if (existing.split(/\r?\n/).some((line) => line.trim() === ".wardby/"))
|
|
97
|
+
return;
|
|
98
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
99
|
+
writeFileSync(gitignore, `${existing}${prefix}\n# Wardby local runtime and credentials\n.wardby/\n`);
|
|
100
|
+
}
|
|
101
|
+
export function defaultModel(provider) {
|
|
102
|
+
return provider === "openai" ? "gpt-5.6-luna" : "claude-haiku-4-5";
|
|
103
|
+
}
|
|
104
|
+
export function providerKeyName(provider) {
|
|
105
|
+
return provider === "openai" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
106
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function quickstartCommand(args: string[]): Promise<void>;
|
|
2
|
+
export declare function doctorCommand(args: string[]): Promise<void>;
|
|
3
|
+
export declare function statusCommand(args: string[]): Promise<void>;
|
|
4
|
+
export declare function logsCommand(args: string[]): Promise<void>;
|
|
5
|
+
export declare function downCommand(args: string[]): Promise<void>;
|